From 565143ba6655b7e5b4164e4c25844e7755379515 Mon Sep 17 00:00:00 2001 From: mwinkels Date: Mon, 13 Jan 2014 17:07:22 +0100 Subject: [PATCH] Enable generating JAX-RS documentation from interface. --- .../java/com/lunatech/doclets/jax/Utils.java | 1801 +++++++++-------- .../jax/jaxrs/model/JAXRSApplication.java | 302 +-- 2 files changed, 1056 insertions(+), 1047 deletions(-) diff --git a/doclets/src/main/java/com/lunatech/doclets/jax/Utils.java b/doclets/src/main/java/com/lunatech/doclets/jax/Utils.java index fba8b57..5105f11 100644 --- a/doclets/src/main/java/com/lunatech/doclets/jax/Utils.java +++ b/doclets/src/main/java/com/lunatech/doclets/jax/Utils.java @@ -1,897 +1,904 @@ -/* - Copyright 2009 Lunatech Research - - This file is part of jax-doclets. - - jax-doclets is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - jax-doclets is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with jax-doclets. If not, see . - */ -package com.lunatech.doclets.jax; - -import java.io.*; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.lunatech.doclets.jax.jaxb.model.JAXBClass; -import com.lunatech.doclets.jax.jaxrs.model.Resource; -import com.lunatech.doclets.jax.jaxrs.model.ResourceMethod; -import com.lunatech.doclets.jax.jpa.model.JPAClass; -import com.lunatech.doclets.jax.writers.DocletWriter; -import com.sun.javadoc.AnnotationDesc; -import com.sun.javadoc.AnnotationDesc.ElementValuePair; -import com.sun.javadoc.AnnotationTypeDoc; -import com.sun.javadoc.AnnotationValue; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.Doc; -import com.sun.javadoc.MethodDoc; -import com.sun.javadoc.PackageDoc; -import com.sun.javadoc.ParamTag; -import com.sun.javadoc.Parameter; -import com.sun.javadoc.ParameterizedType; -import com.sun.javadoc.ProgramElementDoc; -import com.sun.javadoc.Tag; -import com.sun.javadoc.Type; -import com.sun.tools.doclets.formats.html.HtmlDocletWriter; -import com.sun.tools.doclets.internal.toolkit.Configuration; -import com.sun.tools.doclets.internal.toolkit.taglets.DeprecatedTaglet; -import com.sun.tools.doclets.internal.toolkit.taglets.ParamTaglet; -import com.sun.tools.doclets.internal.toolkit.taglets.Taglet; -import com.sun.tools.doclets.internal.toolkit.taglets.TagletManager; -import com.sun.tools.doclets.internal.toolkit.taglets.TagletOutput; -import com.sun.tools.doclets.internal.toolkit.taglets.TagletWriter; -import com.sun.tools.doclets.internal.toolkit.util.DirectoryManager; - -public class Utils { - - public static boolean isEmptyOrNull(String str) { - return str == null || str.length() == 0; - } - - public static String getParamDoc(final MethodDoc declaringMethod, final String name) { - for (final ParamTag paramTag : declaringMethod.paramTags()) { - if (paramTag.parameterName().equals(name)) { - return paramTag.parameterComment(); - } - } - return ""; - } - - public static Object getAnnotationValue(final AnnotationDesc annotation) { - return getAnnotationValue(annotation, "value"); - } - - public static Object getAnnotationValue(AnnotationDesc annotation, String name) { - for (final ElementValuePair elementValuePair : annotation.elementValues()) { - if (elementValuePair.element().name().equals(name)) { - return elementValuePair.value().value(); - } - } - return null; - } - - public static String[] getAnnotationValues(final AnnotationDesc annotation) { - final Object value = getAnnotationValue(annotation); - if (value instanceof String) { - return new String[] { (String) value }; - } - if (value instanceof AnnotationValue[]) { - final AnnotationValue[] values = (AnnotationValue[]) value; - final String[] ret = new String[values.length]; - for (int i = 0; i < ret.length; i++) { - ret[i] = (String) values[i].value(); - } - return ret; - } - throw new IllegalArgumentException("value is not string or string[]: " + value); - } - - public static boolean hasAnnotation(final ProgramElementDoc programElementDoc, final Class... soughtAnnotations) { - return findAnnotation(programElementDoc, soughtAnnotations) != null; - } - - public static MethodDoc findAnnotatedMethod(final ClassDoc declaringClass, final MethodDoc method, final Class... soughtAnnotations) { - if(isExcluded(method)) { - return null; - } - final AnnotationDesc onMethod = findAnnotation(method, soughtAnnotations); - if (onMethod != null) { - return method; - } - // try on the declaring class - for (final MethodDoc declaringMethod : declaringClass.methods(false)) { - if (overrides(method, declaringMethod)) { - if (hasAnnotation(declaringMethod, soughtAnnotations)) { - return declaringMethod; - } - return null; - } - } - return null; - } - - /** - * JavaDoc's MethodDoc.overrides(MethodDoc) is just broken as it doesn't do - * interfaces properly - */ - private static boolean overrides(MethodDoc overridingMethod, MethodDoc overriddenMethod) { - return overridingMethod.name().equals(overriddenMethod.name()) && overridingMethod.signature().equals(overriddenMethod.signature()); - } - - public static AnnotationDesc findMethodAnnotation(final ClassDoc declaringClass, final MethodDoc method, - final Class... soughtAnnotations) { - final AnnotationDesc onMethod = findAnnotation(method, soughtAnnotations); - if (onMethod != null) { - return onMethod; - } - // try on the declaring class - for (final MethodDoc declaringMethod : declaringClass.methods(false)) { - if (overrides(method, declaringMethod)) { - return findAnnotation(declaringMethod, soughtAnnotations); - } - } - return null; - } - - public static AnnotationDesc findParameterAnnotation(final MethodDoc declaringMethod, final Parameter parameter, int parameterIndex, - final Class... soughtAnnotations) { - final AnnotationDesc onParameter = findAnnotation(parameter, soughtAnnotations); - if (onParameter != null) { - return onParameter; - } - // try on the declaring method - Parameter overriddenParameter = declaringMethod.parameters()[parameterIndex]; - return findAnnotation(overriddenParameter, soughtAnnotations); - } - - public static List findAnnotations(final ProgramElementDoc programElementDoc, final Class... soughtAnnotations) { - return findAnnotations(programElementDoc.annotations(), soughtAnnotations); - } - - public static AnnotationDesc findAnnotation(final ProgramElementDoc programElementDoc, final Class... soughtAnnotations) { - return findAnnotation(programElementDoc.annotations(), soughtAnnotations); - } - - public static AnnotationDesc findAnnotation(final Parameter parameter, final Class... soughtAnnotations) { - return findAnnotation(parameter.annotations(), soughtAnnotations); - } - - public static AnnotationDesc findAnnotation(final PackageDoc pack, final Class... soughtAnnotations) { - return findAnnotation(pack.annotations(), soughtAnnotations); - } - - public static AnnotationDesc findAnnotation(final AnnotationDesc[] annotations, final Class... soughtAnnotations) { - for (final AnnotationDesc annotation : annotations) { - final AnnotationTypeDoc annotationType = annotation.annotationType(); - for (final Class soughtAnnotation : soughtAnnotations) { - if (annotationType.qualifiedTypeName().equals(soughtAnnotation.getName())) { - return annotation; - } - } - } - return null; - } - - public static AnnotationDesc findAnnotation(final AnnotationDesc[] annotations, final String... soughtAnnotations) { - for (final AnnotationDesc annotation : annotations) { - final AnnotationTypeDoc annotationType = annotation.annotationType(); - for (final String soughtAnnotation : soughtAnnotations) { - if (annotationType.qualifiedTypeName().equals(soughtAnnotation)) { - return annotation; - } - } - } - return null; - } - - public static List findAnnotations(final AnnotationDesc[] annotations, final Class... soughtAnnotations) { - List ret = new LinkedList(); - for (final AnnotationDesc annotation : annotations) { - final AnnotationTypeDoc annotationType = annotation.annotationType(); - for (final Class soughtAnnotation : soughtAnnotations) { - if (annotationType.qualifiedTypeName().equals(soughtAnnotation.getName())) { - ret.add(annotation); - } - } - } - return ret; - } - - public static ClassDoc findAnnotatedInterface(final ClassDoc klass, final Class... soughtAnnotations) { - // find it in the interfaces - final Type[] interfaceTypes = klass.interfaceTypes(); - for (final Type interfaceType : interfaceTypes) { - final ClassDoc interfaceClassDoc = interfaceType.asClassDoc(); - if (interfaceClassDoc != null && !isExcluded(interfaceClassDoc)) { - if (hasAnnotation(interfaceClassDoc, soughtAnnotations)) { - return interfaceClassDoc; - } - final ClassDoc foundClassDoc = findAnnotatedInterface(interfaceClassDoc, soughtAnnotations); - if (foundClassDoc != null) { - return foundClassDoc; - } - } - } - return null; - } - - public static ClassDoc findAnnotatedClass(final ClassDoc klass, final Class... soughtAnnotations) { - if (!klass.isClass() || isExcluded(klass)) - return null; - if (hasAnnotation(klass, soughtAnnotations)) { - return klass; - } - // find it in the interfaces - final ClassDoc foundClassDoc = findAnnotatedInterface(klass, soughtAnnotations); - if (foundClassDoc != null) { - return foundClassDoc; - } - - final Type superclass = klass.superclassType(); - if (superclass != null && superclass.asClassDoc() != null) { - return findAnnotatedClass(superclass.asClassDoc(), soughtAnnotations); - } - return null; - } - - public static Type findSuperType(final Type type, String typeName) { - ClassDoc doc = type.asClassDoc(); - if (doc == null) - return null; - if (doc.qualifiedTypeName().equals(typeName)) - return type; - if (doc.isInterface()) - return findSuperTypeFromInterface(doc, typeName); - if (doc.isClass() && !doc.isEnum() && !doc.isError() && !doc.isException()) - return findSuperTypeFromClass(doc, typeName); - return null; - } - - public static Type findSuperTypeFromInterface(final ClassDoc klass, String typeName) { - // find it in the interfaces - final Type[] interfaceTypes = klass.interfaceTypes(); - for (final Type interfaceType : interfaceTypes) { - final ClassDoc interfaceClassDoc = interfaceType.asClassDoc(); - if (interfaceClassDoc != null) { - if (interfaceClassDoc.qualifiedTypeName().equals(typeName)) - return interfaceClassDoc; - final Type foundType = findSuperTypeFromInterface(interfaceClassDoc, typeName); - if (foundType != null) { - return foundType; - } - } - } - return null; - } - - public static Type findSuperTypeFromClass(final ClassDoc klass, String typeName) { - if (klass.qualifiedTypeName().equals(typeName)) - return klass; - - // find it in the interfaces - final Type foundType = findSuperTypeFromInterface(klass, typeName); - if (foundType != null) { - return foundType; - } - - final Type superclass = klass.superclassType(); - if (superclass != null && superclass.asClassDoc() != null) { - return findSuperTypeFromClass(superclass.asClassDoc(), typeName); - } - return null; - } - - public static String appendURLFragments(String... fragments) { - StringBuilder strbuf = new StringBuilder(); - for (String fragment : fragments) { - // skip empty fragments - if (fragment == null || fragment.length() == 0) - continue; - if (!strbuf.toString().endsWith("/") && !fragment.startsWith("/")) { - strbuf.append("/"); - } - if (strbuf.toString().endsWith("/") && fragment.startsWith("/")) { - fragment = fragment.substring(1); - } - strbuf.append(fragment); - } - return strbuf.toString(); - } - - public static String getFirstURLFragment(String path) { - if (path.startsWith("/")) - path = path.substring(1); - if (path.length() == 0) - return null; - String[] fragments = path.split("/+"); - if (fragments.length == 0) - return null; - return fragments[0]; - } - - public static String slashify(String url) { - if (url == null) - return ""; - if (!url.endsWith("/")) - return url + "/"; - return url; - } - - public static String unEndSlashify(String url) { - if (url == null) - return ""; - if (url.endsWith("/")) - return url.substring(0, url.length() - 1); - return url; - } - - public static String unStartSlashify(String url) { - if (url == null) - return ""; - if (!url.startsWith("/")) - return url; - if (url.length() == 1) { - return ""; - } - return url.substring(1); - } - - public static String classToPath(JAXBClass jaxbClass) { - return DirectoryManager.getPath(jaxbClass.getPackageName()); - } - - public static String classToPath(JPAClass jpaClass) { - return DirectoryManager.getPath(jpaClass.getPackageName()); - } - - public static String classToPath(ClassDoc cDoc) { - return DirectoryManager.getPath(cDoc.containingPackage().name()); - } - - public static String urlToPath(Resource resource) { - String name = resource.getAbsolutePath(); - if (name.startsWith("/")) - name = name.substring(1); - return name; - } - - public static String urlToSystemPath(Resource resource) { - String name = resource.getAbsolutePath(); - if (name.startsWith("/")) - name = name.substring(1); - return name.replace('/', File.separatorChar); - } - - public static String urlToClass(ClassDoc from, ClassDoc to) { - return classToRoot(from) + classToPath(to) + "/" + to.name() + ".html"; - } - - public static String urlToClass(JAXBClass from, JAXBClass to) { - return classToRoot(from) + classToPath(to) + "/" + to.getShortClassName() + ".html"; - } - - public static String urlToClass(JPAClass from, JPAClass to) { - return classToRoot(from) + classToPath(to) + "/" + to.getShortClassName() + ".html"; - } - - public static String urlToType(ClassDoc klass) { - return DirectoryManager.getPathToClass(klass); - } - - public static String classToRoot(JAXBClass klass) { - return DirectoryManager.getRelativePath(klass.getPackageName()); - } - - public static String classToRoot(ClassDoc cDoc) { - return DirectoryManager.getRelativePath(cDoc.containingPackage()); - } - - public static String classToRoot(JPAClass klass) { - return DirectoryManager.getRelativePath(klass.getPackageName()); - } - - public static String urlToRoot(Resource resource) { - String from = resource.getAbsolutePath(); - if (from.startsWith("/")) - from = from.substring(1); - return urlToRoot(from); - } - - public static String urlToRoot(String from) { - if (from == null || from.length() == 0) { - return ""; - } - StringBuilder pathstr = new StringBuilder(); - for (int i = 0; i < from.length(); i++) { - char ch = from.charAt(i); - if (ch == '/') { - pathstr.append("../"); - } - } - pathstr.append("../"); - return pathstr.toString(); - } - - public static void createDirectory(String path) { - if (path == null || path.length() == 0) { - return; - } - File dir = new File(path); - if (!dir.exists() && !dir.mkdirs()) { - throw new RuntimeException("Could not create path: " + path); - } - } - - public static void genTagOuput(TagletManager tagletManager, Doc doc, Taglet[] taglets, TagletWriter writer, TagletOutput output, - Set tagletsToPrint) { - tagletManager.checkTags(doc, doc.tags(), false); - tagletManager.checkTags(doc, doc.inlineTags(), true); - TagletOutput currentOutput = null; - for (int i = 0; i < taglets.length; i++) { - if (!tagletsToPrint.contains(taglets[i].getName())) - continue; - if (doc instanceof ClassDoc && taglets[i] instanceof ParamTaglet) { - // The type parameters are documented in a special section away - // from the tag info, so skip here. - continue; - } - if (taglets[i] instanceof DeprecatedTaglet) { - // Deprecated information is documented "inline", not in tag - // info - // section. - continue; - } - try { - currentOutput = taglets[i].getTagletOutput(doc, writer); - } catch (IllegalArgumentException e) { - // The taglet does not take a member as an argument. Let's try - // a single tag. - Tag[] tags = doc.tags(taglets[i].getName()); - if (tags.length > 0) { - currentOutput = taglets[i].getTagletOutput(tags[0], writer); - } - } - if (currentOutput != null) { - tagletManager.seenCustomTag(taglets[i].getName()); - output.appendOutput(currentOutput); - } - } - } - - public static void copyResources(JAXConfiguration configuration) { - InputStream defaultCSS = Utils.class.getResourceAsStream("/doclet.css"); - if (defaultCSS == null) - throw new RuntimeException("Failed to find doclet CSS (incorrect jax-doclets packaging?)"); - if (!isEmptyOrNull(configuration.parentConfiguration.stylesheetfile)) { - try { - InputStream stream = new FileInputStream(configuration.parentConfiguration.stylesheetfile); - copyResource(stream, new File(configuration.parentConfiguration.destDirName, "doclet.css")); - // also put the original stylesheet in case it's needed - copyResource(defaultCSS, new File(configuration.parentConfiguration.destDirName, "default-doclet.css")); - } catch (Exception x) { - throw new RuntimeException("Failed to read user stylesheet " + configuration.parentConfiguration.stylesheetfile, x); - } - } else - copyResource(defaultCSS, new File(configuration.parentConfiguration.destDirName, "doclet.css")); - } - - public static void copyJPAResources(JAXConfiguration configuration) { - copyResource(configuration, "graph.js"); - copyResource(configuration, "jit.js"); - } - - private static void copyResource(JAXConfiguration configuration, String name) { - InputStream graphHTML = Utils.class.getResourceAsStream("/" + name); - if (graphHTML == null) - throw new RuntimeException("Failed to find " + name + " (incorrect jax-doclets packaging?)"); - copyResource(graphHTML, new File(configuration.parentConfiguration.destDirName, name)); - } - - private static void copyResource(InputStream stream, File output) { - try { - OutputStream os = new FileOutputStream(output); - byte[] buffer = new byte[1024]; - int read; - while ((read = stream.read(buffer)) >= 0) { - os.write(buffer, 0, read); - } - os.flush(); - os.close(); - stream.close(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static String readResource(File input) { - try { - Reader stream = new FileReader(input); - StringBuilder output = new StringBuilder(); - char[] buffer = new char[1024]; - int read; - while ((read = stream.read(buffer)) >= 0) { - output.append(buffer, 0, read); - } - stream.close(); - return output.toString(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Returns either Produces.class or ProduceMime.class (old version) - * - * @return - */ - public static Class getProducesClass() { - try { - return Class.forName("javax.ws.rs.Produces"); - } catch (ClassNotFoundException e) { - try { - return Class.forName("javax.ws.rs.ProduceMime"); - } catch (ClassNotFoundException e1) { - throw new RuntimeException(e1); - } - } - } - - /** - * Returns either Consumes.class or ConsumeMime.class (old version) - * - * @return - */ - public static Class getConsumesClass() { - try { - return Class.forName("javax.ws.rs.Consumes"); - } catch (ClassNotFoundException e) { - try { - return Class.forName("javax.ws.rs.ConsumeMime"); - } catch (ClassNotFoundException e1) { - throw new RuntimeException(e1); - } - } - } - - public static String getExternalLink(Configuration configuration, Type type, HtmlDocletWriter writer) { - return getExternalLink(configuration, type.asClassDoc().containingPackage().name(), type.typeName(), writer); - } - - public static String getExternalLink(Configuration configuration, String className, HtmlDocletWriter writer) { - int lastSep = className.lastIndexOf('.'); - if (lastSep == -1) - return getExternalLink(configuration, "", className, writer); - String link; - // since classes can be internal, look up backwards with an ever shrinking - // package name - do { - link = getExternalLink(configuration, className.substring(0, lastSep), className.substring(lastSep + 1), writer); - if (link != null) - return link; - lastSep = className.lastIndexOf('.', lastSep - 1); - } while (lastSep > -1); - return null; - } - - private static String getExternalLink(Configuration configuration, String packageName, String className, HtmlDocletWriter writer) { - return configuration.extern.getExternalLink(packageName, writer.relativePath, className + ".html"); - } - - public static String getLinkTypeName(String url) { - int lastSep = url.lastIndexOf('/'); - if (lastSep != -1 && url.endsWith(".html")) - return url.substring(lastSep + 1, url.length() - 5); - throw new IllegalArgumentException("Invalid type link: " + url); - } - - public static Tag getTag(Doc doc, String tagName) { - Tag[] tags = doc.tags("@" + tagName); - if (tags != null && tags.length > 0) { - return tags[0]; - } - return null; - } - - public static Tag[] getTags(Doc doc, String tagName) { - Tag[] tags = doc.tags("@" + tagName); - if (tags != null && tags.length > 0) { - return tags; - } - return null; - } - - public static String getOption(String options[][], String optionName) { - for (String option[] : options) { - String name = option[0]; - if (!optionName.equals(name)) { - continue; - } - String value = option.length > 1 ? option[1] : null; - return value; - } - return null; - } - - public static List getOptions(String options[][], String optionName) { - List result = new ArrayList(); - for (String option[] : options) { - String name = option[0]; - if (!optionName.equals(name)) { - continue; - } - String value = option.length > 1 ? option[1] : null; - result.add(value); - } - return result; - } - - /** - * @return true if optionName exists in one of the options. - */ - public static boolean hasOption(String options[][], String optionName) { - for (String option[] : options) { - String name = option[0]; - if (!optionName.equals(name)) { - continue; - } - return true; - } - return false; - } - - public static boolean isCollection(Type type) { - Type collectionType = Utils.findSuperType(type, "java.util.Collection"); - // FIXME: this is dodgy at best - return collectionType != null; - } - - public static boolean isArray(Type type) { - String dimension = type.dimension(); - return dimension != null && dimension.length() > 0; - } - - public static Type getCollectionType(Type type, JAXDoclet doclet) { - Type collectionType = Utils.findSuperType(type, "java.util.Collection"); - // FIXME: this is dodgy at best - if (collectionType != null) { - ParameterizedType parameterizedType = type.asParameterizedType(); - Type[] types = parameterizedType == null ? null : parameterizedType.typeArguments(); - if (types != null && types.length == 1) - return types[0]; - return doclet.forName("java.lang.Object"); - } - return type; - } - - @SuppressWarnings("deprecation") - public static Type resolveType(String typeName, ClassDoc klass, JAXDoclet doclet) { - log("resolving " + typeName + " in " + klass.qualifiedTypeName()); - // first look in inner classes - for (ClassDoc innerClass : klass.innerClasses(false)) { - if (innerClass.simpleTypeName().equals(typeName)) - return innerClass; - } - // then the class itself - if (klass.typeName().equals(typeName)) - return klass; - try { - // then go through the named imports - for (ClassDoc importedClass : klass.importedClasses()) { - if (importedClass.typeName().equals(typeName)) - return importedClass; - } - // then the package imports - for (PackageDoc importedPackage : klass.importedPackages()) { - for (ClassDoc importedClass : importedPackage.allClasses(false)) { - if (importedClass.typeName().equals(typeName)) - return importedClass; - } - } - } catch (NullPointerException e) { - - } - // now try FQDN - Type type = doclet.forName(typeName); - if (type != null) - return type; - log("resolving failed for " + typeName + " in " + klass.qualifiedTypeName()); - return null; - } - - public static JaxType parseType(String typeName, ClassDoc containingClass, JAXDoclet doclet) throws InvalidJaxTypeException { - typeName = typeName.trim(); - char[] chars = typeName.toCharArray(); - Stack types = new Stack(); - JaxType currentType = new JaxType(); - types.push(currentType); - StringBuffer currentTypeName = new StringBuffer(); - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - log("Looking at char " + c); - if (c == '<') { - log("Start params for " + currentTypeName); - // we're done for the type name - setupType(currentType, currentTypeName, containingClass, doclet); - // add a parameter to the current type - JaxType parameterType = new JaxType(); - currentType.parameters.add(parameterType); - currentType = parameterType; - // prepare for the parameter type - types.push(currentType); - } else if (c == '>') { - // we're done for the parameter type - if (currentTypeName.length() > 0) - setupType(currentType, currentTypeName, containingClass, doclet); - // reset and pop - types.pop(); - currentType = types.peek(); - log("End params for " + currentType.typeName); - // we should have at least the top type - if (types.size() < 1) - throw new InvalidJaxTypeException(); - } else if (c == ',') { - // we're done for the parameter type, unless it was already done by - // closing its parameter list - if (currentTypeName.length() > 0) { - setupType(currentType, currentTypeName, containingClass, doclet); - // reset, pop - types.pop(); - currentType = types.peek(); - } - log("Next params for " + currentType.typeName); - // we should have at least the top type - if (types.size() < 1) - throw new InvalidJaxTypeException(); - // add a parameter to the current type - JaxType parameterType = new JaxType(); - currentType.parameters.add(parameterType); - currentType = parameterType; - // prepare for the parameter type - types.push(currentType); - } else if (c == '[' || c == ']') { - log("Dimension for " + currentType.typeName); - // done for the class name unless it was already done by - // closing its parameter list - if (currentTypeName.length() > 0) { - setupType(currentType, currentTypeName, containingClass, doclet); - } - // FIXME: check dimension correctness - currentType.dimension += c; - } else { - log("Name char: " + currentTypeName); - // if the currentType already has a name, barf - if (currentType.typeName != null) - throw new InvalidJaxTypeException(); - currentTypeName.append(c); - } - } - // perhaps we didn't have any parameters or dimension - if (currentTypeName.length() > 0) { - log("End of type without param or dimension for " + currentTypeName); - setupType(currentType, currentTypeName, containingClass, doclet); - } - // we should have the original type to return - if (types.size() != 1) - throw new InvalidJaxTypeException(); - return currentType; - } - - public static String removeFragmentRegexes(String fragment, Map regexFragments) { - Pattern regexPattern = Pattern.compile("\\{(\\w[\\w\\.-]*)\\s*:"); - Matcher regexMatcher = regexPattern.matcher(fragment); - int start = 0; - char[] fragmentArray = fragment.toCharArray(); - StringBuffer strbuf = new StringBuffer(); - while (regexMatcher.find(start)) { - strbuf.append(fragment.substring(start, regexMatcher.start())); - String name = regexMatcher.group(1); - strbuf.append("{").append(name); - // now move on until after the last "}" - int openBraces = 1; - start = regexMatcher.end(); - while (start < fragmentArray.length) { - char c = fragmentArray[start++]; - if (c == '{') - openBraces++; - else if (c == '}') { - openBraces--; - if (openBraces == 0) - break; - } - } - if (openBraces > 0) - throw new RuntimeException("Invalid Path fragment: " + fragment); - String regex = fragment.substring(regexMatcher.end(), start - 1); - if (regexFragments != null) - regexFragments.put(name, regex); - strbuf.append("}"); - } - // add all that remains - strbuf.append(fragment.substring(start, fragmentArray.length)); - return strbuf.toString(); - } - - @SuppressWarnings("serial") - public static class InvalidJaxTypeException extends Exception {} - - private static void setupType(JaxType currentType, StringBuffer currentTypeName, ClassDoc containingClass, JAXDoclet doclet) - throws InvalidJaxTypeException { - if (currentTypeName.length() == 0) { - throw new InvalidJaxTypeException(); - } - currentType.typeName = currentTypeName.toString(); - currentType.type = resolveType(currentType.typeName, containingClass, doclet); - currentTypeName.setLength(0); - } - - public static class JaxType { - - String typeName; - - Type type; - - List parameters = new LinkedList(); - - String dimension = ""; - - public String getDimension() { - return dimension; - } - - public String getTypeName() { - return typeName; - } - - public Type getType() { - return type; - } - - public List getParameters() { - return parameters; - } - - public boolean hasParameters() { - return !parameters.isEmpty(); - } - } - - private static String addContextPath(JAXConfiguration config, String resource) { - // FIXME: move this to JAXRSConfiguration - String jaxrscontext = getOption(config.parentConfiguration.root.options(), "-jaxrscontext"); - if (jaxrscontext == null) { - return appendURLFragments("/", resource); - } else { - return appendURLFragments(jaxrscontext, resource); - } - } - - public static String getDisplayURL(DocletWriter writer, Resource resource, ResourceMethod method) { - return addContextPath(writer.getConfiguration(), method.getURL(resource)); - } - - public static String getAbsolutePath(DocletWriter writer, Resource resource) { - return addContextPath(writer.getConfiguration(), resource.getAbsolutePath()); - } - - public static String getAbsolutePath(JAXConfiguration config, Resource resource) { - return addContextPath(config, resource.getAbsolutePath()); - } - - public static void log(String mesg) { - // System.err.println(mesg); - } - - private static boolean isExcluded(Doc doc) { - return doc.tags("exclude").length != 0; - } -} +/* + Copyright 2009 Lunatech Research + + This file is part of jax-doclets. + + jax-doclets is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + jax-doclets is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with jax-doclets. If not, see . + */ +package com.lunatech.doclets.jax; + +import java.io.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.lunatech.doclets.jax.jaxb.model.JAXBClass; +import com.lunatech.doclets.jax.jaxrs.model.Resource; +import com.lunatech.doclets.jax.jaxrs.model.ResourceMethod; +import com.lunatech.doclets.jax.jpa.model.JPAClass; +import com.lunatech.doclets.jax.writers.DocletWriter; +import com.sun.javadoc.AnnotationDesc; +import com.sun.javadoc.AnnotationDesc.ElementValuePair; +import com.sun.javadoc.AnnotationTypeDoc; +import com.sun.javadoc.AnnotationValue; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.Doc; +import com.sun.javadoc.MethodDoc; +import com.sun.javadoc.PackageDoc; +import com.sun.javadoc.ParamTag; +import com.sun.javadoc.Parameter; +import com.sun.javadoc.ParameterizedType; +import com.sun.javadoc.ProgramElementDoc; +import com.sun.javadoc.Tag; +import com.sun.javadoc.Type; +import com.sun.tools.doclets.formats.html.HtmlDocletWriter; +import com.sun.tools.doclets.internal.toolkit.Configuration; +import com.sun.tools.doclets.internal.toolkit.taglets.DeprecatedTaglet; +import com.sun.tools.doclets.internal.toolkit.taglets.ParamTaglet; +import com.sun.tools.doclets.internal.toolkit.taglets.Taglet; +import com.sun.tools.doclets.internal.toolkit.taglets.TagletManager; +import com.sun.tools.doclets.internal.toolkit.taglets.TagletOutput; +import com.sun.tools.doclets.internal.toolkit.taglets.TagletWriter; +import com.sun.tools.doclets.internal.toolkit.util.DirectoryManager; + +public class Utils { + + public static boolean isEmptyOrNull(String str) { + return str == null || str.length() == 0; + } + + public static String getParamDoc(final MethodDoc declaringMethod, final String name) { + for (final ParamTag paramTag : declaringMethod.paramTags()) { + if (paramTag.parameterName().equals(name)) { + return paramTag.parameterComment(); + } + } + return ""; + } + + public static Object getAnnotationValue(final AnnotationDesc annotation) { + return getAnnotationValue(annotation, "value"); + } + + public static Object getAnnotationValue(AnnotationDesc annotation, String name) { + for (final ElementValuePair elementValuePair : annotation.elementValues()) { + if (elementValuePair.element().name().equals(name)) { + return elementValuePair.value().value(); + } + } + return null; + } + + public static String[] getAnnotationValues(final AnnotationDesc annotation) { + final Object value = getAnnotationValue(annotation); + if (value instanceof String) { + return new String[] { (String) value }; + } + if (value instanceof AnnotationValue[]) { + final AnnotationValue[] values = (AnnotationValue[]) value; + final String[] ret = new String[values.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (String) values[i].value(); + } + return ret; + } + throw new IllegalArgumentException("value is not string or string[]: " + value); + } + + public static boolean hasAnnotation(final ProgramElementDoc programElementDoc, final Class... soughtAnnotations) { + return findAnnotation(programElementDoc, soughtAnnotations) != null; + } + + public static MethodDoc findAnnotatedMethod(final ClassDoc declaringClass, final MethodDoc method, final Class... soughtAnnotations) { + if(isExcluded(method)) { + return null; + } + final AnnotationDesc onMethod = findAnnotation(method, soughtAnnotations); + if (onMethod != null) { + return method; + } + // try on the declaring class + for (final MethodDoc declaringMethod : declaringClass.methods(false)) { + if (overrides(method, declaringMethod)) { + if (hasAnnotation(declaringMethod, soughtAnnotations)) { + return declaringMethod; + } + return null; + } + } + return null; + } + + /** + * JavaDoc's MethodDoc.overrides(MethodDoc) is just broken as it doesn't do + * interfaces properly + */ + private static boolean overrides(MethodDoc overridingMethod, MethodDoc overriddenMethod) { + return overridingMethod.name().equals(overriddenMethod.name()) && overridingMethod.signature().equals(overriddenMethod.signature()); + } + + public static AnnotationDesc findMethodAnnotation(final ClassDoc declaringClass, final MethodDoc method, + final Class... soughtAnnotations) { + final AnnotationDesc onMethod = findAnnotation(method, soughtAnnotations); + if (onMethod != null) { + return onMethod; + } + // try on the declaring class + for (final MethodDoc declaringMethod : declaringClass.methods(false)) { + if (overrides(method, declaringMethod)) { + return findAnnotation(declaringMethod, soughtAnnotations); + } + } + return null; + } + + public static AnnotationDesc findParameterAnnotation(final MethodDoc declaringMethod, final Parameter parameter, int parameterIndex, + final Class... soughtAnnotations) { + final AnnotationDesc onParameter = findAnnotation(parameter, soughtAnnotations); + if (onParameter != null) { + return onParameter; + } + // try on the declaring method + Parameter overriddenParameter = declaringMethod.parameters()[parameterIndex]; + return findAnnotation(overriddenParameter, soughtAnnotations); + } + + public static List findAnnotations(final ProgramElementDoc programElementDoc, final Class... soughtAnnotations) { + return findAnnotations(programElementDoc.annotations(), soughtAnnotations); + } + + public static AnnotationDesc findAnnotation(final ProgramElementDoc programElementDoc, final Class... soughtAnnotations) { + return findAnnotation(programElementDoc.annotations(), soughtAnnotations); + } + + public static AnnotationDesc findAnnotation(final Parameter parameter, final Class... soughtAnnotations) { + return findAnnotation(parameter.annotations(), soughtAnnotations); + } + + public static AnnotationDesc findAnnotation(final PackageDoc pack, final Class... soughtAnnotations) { + return findAnnotation(pack.annotations(), soughtAnnotations); + } + + public static AnnotationDesc findAnnotation(final AnnotationDesc[] annotations, final Class... soughtAnnotations) { + for (final AnnotationDesc annotation : annotations) { + final AnnotationTypeDoc annotationType = annotation.annotationType(); + for (final Class soughtAnnotation : soughtAnnotations) { + if (annotationType.qualifiedTypeName().equals(soughtAnnotation.getName())) { + return annotation; + } + } + } + return null; + } + + public static AnnotationDesc findAnnotation(final AnnotationDesc[] annotations, final String... soughtAnnotations) { + for (final AnnotationDesc annotation : annotations) { + final AnnotationTypeDoc annotationType = annotation.annotationType(); + for (final String soughtAnnotation : soughtAnnotations) { + if (annotationType.qualifiedTypeName().equals(soughtAnnotation)) { + return annotation; + } + } + } + return null; + } + + public static List findAnnotations(final AnnotationDesc[] annotations, final Class... soughtAnnotations) { + List ret = new LinkedList(); + for (final AnnotationDesc annotation : annotations) { + final AnnotationTypeDoc annotationType = annotation.annotationType(); + for (final Class soughtAnnotation : soughtAnnotations) { + if (annotationType.qualifiedTypeName().equals(soughtAnnotation.getName())) { + ret.add(annotation); + } + } + } + return ret; + } + + public static ClassDoc findAnnotatedInterface(final ClassDoc klass, final Class... soughtAnnotations) { + // find it in the interfaces + final Type[] interfaceTypes = klass.interfaceTypes(); + for (final Type interfaceType : interfaceTypes) { + final ClassDoc interfaceClassDoc = interfaceType.asClassDoc(); + if (interfaceClassDoc != null && !isExcluded(interfaceClassDoc)) { + if (hasAnnotation(interfaceClassDoc, soughtAnnotations)) { + return interfaceClassDoc; + } + final ClassDoc foundClassDoc = findAnnotatedInterface(interfaceClassDoc, soughtAnnotations); + if (foundClassDoc != null) { + return foundClassDoc; + } + } + } + return null; + } + + public static ClassDoc findAnnotatedClass(final ClassDoc klass, final Class... soughtAnnotations) { + if (!klass.isClass()) + return null; + return findAnnotatedClassOrInterface(klass, soughtAnnotations); + } + + public static ClassDoc findAnnotatedClassOrInterface(ClassDoc klass, Class... soughtAnnotations) { + if (isExcluded(klass)) + return null; + if (hasAnnotation(klass, soughtAnnotations)) { + return klass; + } + // find it in the interfaces + final ClassDoc foundClassDoc = findAnnotatedInterface(klass, soughtAnnotations); + if (foundClassDoc != null) { + return foundClassDoc; + } + + final Type superclass = klass.superclassType(); + if (superclass != null && superclass.asClassDoc() != null) { + return findAnnotatedClass(superclass.asClassDoc(), soughtAnnotations); + } + return null; + } + + public static Type findSuperType(final Type type, String typeName) { + ClassDoc doc = type.asClassDoc(); + if (doc == null) + return null; + if (doc.qualifiedTypeName().equals(typeName)) + return type; + if (doc.isInterface()) + return findSuperTypeFromInterface(doc, typeName); + if (doc.isClass() && !doc.isEnum() && !doc.isError() && !doc.isException()) + return findSuperTypeFromClass(doc, typeName); + return null; + } + + public static Type findSuperTypeFromInterface(final ClassDoc klass, String typeName) { + // find it in the interfaces + final Type[] interfaceTypes = klass.interfaceTypes(); + for (final Type interfaceType : interfaceTypes) { + final ClassDoc interfaceClassDoc = interfaceType.asClassDoc(); + if (interfaceClassDoc != null) { + if (interfaceClassDoc.qualifiedTypeName().equals(typeName)) + return interfaceClassDoc; + final Type foundType = findSuperTypeFromInterface(interfaceClassDoc, typeName); + if (foundType != null) { + return foundType; + } + } + } + return null; + } + + public static Type findSuperTypeFromClass(final ClassDoc klass, String typeName) { + if (klass.qualifiedTypeName().equals(typeName)) + return klass; + + // find it in the interfaces + final Type foundType = findSuperTypeFromInterface(klass, typeName); + if (foundType != null) { + return foundType; + } + + final Type superclass = klass.superclassType(); + if (superclass != null && superclass.asClassDoc() != null) { + return findSuperTypeFromClass(superclass.asClassDoc(), typeName); + } + return null; + } + + public static String appendURLFragments(String... fragments) { + StringBuilder strbuf = new StringBuilder(); + for (String fragment : fragments) { + // skip empty fragments + if (fragment == null || fragment.length() == 0) + continue; + if (!strbuf.toString().endsWith("/") && !fragment.startsWith("/")) { + strbuf.append("/"); + } + if (strbuf.toString().endsWith("/") && fragment.startsWith("/")) { + fragment = fragment.substring(1); + } + strbuf.append(fragment); + } + return strbuf.toString(); + } + + public static String getFirstURLFragment(String path) { + if (path.startsWith("/")) + path = path.substring(1); + if (path.length() == 0) + return null; + String[] fragments = path.split("/+"); + if (fragments.length == 0) + return null; + return fragments[0]; + } + + public static String slashify(String url) { + if (url == null) + return ""; + if (!url.endsWith("/")) + return url + "/"; + return url; + } + + public static String unEndSlashify(String url) { + if (url == null) + return ""; + if (url.endsWith("/")) + return url.substring(0, url.length() - 1); + return url; + } + + public static String unStartSlashify(String url) { + if (url == null) + return ""; + if (!url.startsWith("/")) + return url; + if (url.length() == 1) { + return ""; + } + return url.substring(1); + } + + public static String classToPath(JAXBClass jaxbClass) { + return DirectoryManager.getPath(jaxbClass.getPackageName()); + } + + public static String classToPath(JPAClass jpaClass) { + return DirectoryManager.getPath(jpaClass.getPackageName()); + } + + public static String classToPath(ClassDoc cDoc) { + return DirectoryManager.getPath(cDoc.containingPackage().name()); + } + + public static String urlToPath(Resource resource) { + String name = resource.getAbsolutePath(); + if (name.startsWith("/")) + name = name.substring(1); + return name; + } + + public static String urlToSystemPath(Resource resource) { + String name = resource.getAbsolutePath(); + if (name.startsWith("/")) + name = name.substring(1); + return name.replace('/', File.separatorChar); + } + + public static String urlToClass(ClassDoc from, ClassDoc to) { + return classToRoot(from) + classToPath(to) + "/" + to.name() + ".html"; + } + + public static String urlToClass(JAXBClass from, JAXBClass to) { + return classToRoot(from) + classToPath(to) + "/" + to.getShortClassName() + ".html"; + } + + public static String urlToClass(JPAClass from, JPAClass to) { + return classToRoot(from) + classToPath(to) + "/" + to.getShortClassName() + ".html"; + } + + public static String urlToType(ClassDoc klass) { + return DirectoryManager.getPathToClass(klass); + } + + public static String classToRoot(JAXBClass klass) { + return DirectoryManager.getRelativePath(klass.getPackageName()); + } + + public static String classToRoot(ClassDoc cDoc) { + return DirectoryManager.getRelativePath(cDoc.containingPackage()); + } + + public static String classToRoot(JPAClass klass) { + return DirectoryManager.getRelativePath(klass.getPackageName()); + } + + public static String urlToRoot(Resource resource) { + String from = resource.getAbsolutePath(); + if (from.startsWith("/")) + from = from.substring(1); + return urlToRoot(from); + } + + public static String urlToRoot(String from) { + if (from == null || from.length() == 0) { + return ""; + } + StringBuilder pathstr = new StringBuilder(); + for (int i = 0; i < from.length(); i++) { + char ch = from.charAt(i); + if (ch == '/') { + pathstr.append("../"); + } + } + pathstr.append("../"); + return pathstr.toString(); + } + + public static void createDirectory(String path) { + if (path == null || path.length() == 0) { + return; + } + File dir = new File(path); + if (!dir.exists() && !dir.mkdirs()) { + throw new RuntimeException("Could not create path: " + path); + } + } + + public static void genTagOuput(TagletManager tagletManager, Doc doc, Taglet[] taglets, TagletWriter writer, TagletOutput output, + Set tagletsToPrint) { + tagletManager.checkTags(doc, doc.tags(), false); + tagletManager.checkTags(doc, doc.inlineTags(), true); + TagletOutput currentOutput = null; + for (int i = 0; i < taglets.length; i++) { + if (!tagletsToPrint.contains(taglets[i].getName())) + continue; + if (doc instanceof ClassDoc && taglets[i] instanceof ParamTaglet) { + // The type parameters are documented in a special section away + // from the tag info, so skip here. + continue; + } + if (taglets[i] instanceof DeprecatedTaglet) { + // Deprecated information is documented "inline", not in tag + // info + // section. + continue; + } + try { + currentOutput = taglets[i].getTagletOutput(doc, writer); + } catch (IllegalArgumentException e) { + // The taglet does not take a member as an argument. Let's try + // a single tag. + Tag[] tags = doc.tags(taglets[i].getName()); + if (tags.length > 0) { + currentOutput = taglets[i].getTagletOutput(tags[0], writer); + } + } + if (currentOutput != null) { + tagletManager.seenCustomTag(taglets[i].getName()); + output.appendOutput(currentOutput); + } + } + } + + public static void copyResources(JAXConfiguration configuration) { + InputStream defaultCSS = Utils.class.getResourceAsStream("/doclet.css"); + if (defaultCSS == null) + throw new RuntimeException("Failed to find doclet CSS (incorrect jax-doclets packaging?)"); + if (!isEmptyOrNull(configuration.parentConfiguration.stylesheetfile)) { + try { + InputStream stream = new FileInputStream(configuration.parentConfiguration.stylesheetfile); + copyResource(stream, new File(configuration.parentConfiguration.destDirName, "doclet.css")); + // also put the original stylesheet in case it's needed + copyResource(defaultCSS, new File(configuration.parentConfiguration.destDirName, "default-doclet.css")); + } catch (Exception x) { + throw new RuntimeException("Failed to read user stylesheet " + configuration.parentConfiguration.stylesheetfile, x); + } + } else + copyResource(defaultCSS, new File(configuration.parentConfiguration.destDirName, "doclet.css")); + } + + public static void copyJPAResources(JAXConfiguration configuration) { + copyResource(configuration, "graph.js"); + copyResource(configuration, "jit.js"); + } + + private static void copyResource(JAXConfiguration configuration, String name) { + InputStream graphHTML = Utils.class.getResourceAsStream("/" + name); + if (graphHTML == null) + throw new RuntimeException("Failed to find " + name + " (incorrect jax-doclets packaging?)"); + copyResource(graphHTML, new File(configuration.parentConfiguration.destDirName, name)); + } + + private static void copyResource(InputStream stream, File output) { + try { + OutputStream os = new FileOutputStream(output); + byte[] buffer = new byte[1024]; + int read; + while ((read = stream.read(buffer)) >= 0) { + os.write(buffer, 0, read); + } + os.flush(); + os.close(); + stream.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static String readResource(File input) { + try { + Reader stream = new FileReader(input); + StringBuilder output = new StringBuilder(); + char[] buffer = new char[1024]; + int read; + while ((read = stream.read(buffer)) >= 0) { + output.append(buffer, 0, read); + } + stream.close(); + return output.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Returns either Produces.class or ProduceMime.class (old version) + * + * @return + */ + public static Class getProducesClass() { + try { + return Class.forName("javax.ws.rs.Produces"); + } catch (ClassNotFoundException e) { + try { + return Class.forName("javax.ws.rs.ProduceMime"); + } catch (ClassNotFoundException e1) { + throw new RuntimeException(e1); + } + } + } + + /** + * Returns either Consumes.class or ConsumeMime.class (old version) + * + * @return + */ + public static Class getConsumesClass() { + try { + return Class.forName("javax.ws.rs.Consumes"); + } catch (ClassNotFoundException e) { + try { + return Class.forName("javax.ws.rs.ConsumeMime"); + } catch (ClassNotFoundException e1) { + throw new RuntimeException(e1); + } + } + } + + public static String getExternalLink(Configuration configuration, Type type, HtmlDocletWriter writer) { + return getExternalLink(configuration, type.asClassDoc().containingPackage().name(), type.typeName(), writer); + } + + public static String getExternalLink(Configuration configuration, String className, HtmlDocletWriter writer) { + int lastSep = className.lastIndexOf('.'); + if (lastSep == -1) + return getExternalLink(configuration, "", className, writer); + String link; + // since classes can be internal, look up backwards with an ever shrinking + // package name + do { + link = getExternalLink(configuration, className.substring(0, lastSep), className.substring(lastSep + 1), writer); + if (link != null) + return link; + lastSep = className.lastIndexOf('.', lastSep - 1); + } while (lastSep > -1); + return null; + } + + private static String getExternalLink(Configuration configuration, String packageName, String className, HtmlDocletWriter writer) { + return configuration.extern.getExternalLink(packageName, writer.relativePath, className + ".html"); + } + + public static String getLinkTypeName(String url) { + int lastSep = url.lastIndexOf('/'); + if (lastSep != -1 && url.endsWith(".html")) + return url.substring(lastSep + 1, url.length() - 5); + throw new IllegalArgumentException("Invalid type link: " + url); + } + + public static Tag getTag(Doc doc, String tagName) { + Tag[] tags = doc.tags("@" + tagName); + if (tags != null && tags.length > 0) { + return tags[0]; + } + return null; + } + + public static Tag[] getTags(Doc doc, String tagName) { + Tag[] tags = doc.tags("@" + tagName); + if (tags != null && tags.length > 0) { + return tags; + } + return null; + } + + public static String getOption(String options[][], String optionName) { + for (String option[] : options) { + String name = option[0]; + if (!optionName.equals(name)) { + continue; + } + String value = option.length > 1 ? option[1] : null; + return value; + } + return null; + } + + public static List getOptions(String options[][], String optionName) { + List result = new ArrayList(); + for (String option[] : options) { + String name = option[0]; + if (!optionName.equals(name)) { + continue; + } + String value = option.length > 1 ? option[1] : null; + result.add(value); + } + return result; + } + + /** + * @return true if optionName exists in one of the options. + */ + public static boolean hasOption(String options[][], String optionName) { + for (String option[] : options) { + String name = option[0]; + if (!optionName.equals(name)) { + continue; + } + return true; + } + return false; + } + + public static boolean isCollection(Type type) { + Type collectionType = Utils.findSuperType(type, "java.util.Collection"); + // FIXME: this is dodgy at best + return collectionType != null; + } + + public static boolean isArray(Type type) { + String dimension = type.dimension(); + return dimension != null && dimension.length() > 0; + } + + public static Type getCollectionType(Type type, JAXDoclet doclet) { + Type collectionType = Utils.findSuperType(type, "java.util.Collection"); + // FIXME: this is dodgy at best + if (collectionType != null) { + ParameterizedType parameterizedType = type.asParameterizedType(); + Type[] types = parameterizedType == null ? null : parameterizedType.typeArguments(); + if (types != null && types.length == 1) + return types[0]; + return doclet.forName("java.lang.Object"); + } + return type; + } + + @SuppressWarnings("deprecation") + public static Type resolveType(String typeName, ClassDoc klass, JAXDoclet doclet) { + log("resolving " + typeName + " in " + klass.qualifiedTypeName()); + // first look in inner classes + for (ClassDoc innerClass : klass.innerClasses(false)) { + if (innerClass.simpleTypeName().equals(typeName)) + return innerClass; + } + // then the class itself + if (klass.typeName().equals(typeName)) + return klass; + try { + // then go through the named imports + for (ClassDoc importedClass : klass.importedClasses()) { + if (importedClass.typeName().equals(typeName)) + return importedClass; + } + // then the package imports + for (PackageDoc importedPackage : klass.importedPackages()) { + for (ClassDoc importedClass : importedPackage.allClasses(false)) { + if (importedClass.typeName().equals(typeName)) + return importedClass; + } + } + } catch (NullPointerException e) { + + } + // now try FQDN + Type type = doclet.forName(typeName); + if (type != null) + return type; + log("resolving failed for " + typeName + " in " + klass.qualifiedTypeName()); + return null; + } + + public static JaxType parseType(String typeName, ClassDoc containingClass, JAXDoclet doclet) throws InvalidJaxTypeException { + typeName = typeName.trim(); + char[] chars = typeName.toCharArray(); + Stack types = new Stack(); + JaxType currentType = new JaxType(); + types.push(currentType); + StringBuffer currentTypeName = new StringBuffer(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + log("Looking at char " + c); + if (c == '<') { + log("Start params for " + currentTypeName); + // we're done for the type name + setupType(currentType, currentTypeName, containingClass, doclet); + // add a parameter to the current type + JaxType parameterType = new JaxType(); + currentType.parameters.add(parameterType); + currentType = parameterType; + // prepare for the parameter type + types.push(currentType); + } else if (c == '>') { + // we're done for the parameter type + if (currentTypeName.length() > 0) + setupType(currentType, currentTypeName, containingClass, doclet); + // reset and pop + types.pop(); + currentType = types.peek(); + log("End params for " + currentType.typeName); + // we should have at least the top type + if (types.size() < 1) + throw new InvalidJaxTypeException(); + } else if (c == ',') { + // we're done for the parameter type, unless it was already done by + // closing its parameter list + if (currentTypeName.length() > 0) { + setupType(currentType, currentTypeName, containingClass, doclet); + // reset, pop + types.pop(); + currentType = types.peek(); + } + log("Next params for " + currentType.typeName); + // we should have at least the top type + if (types.size() < 1) + throw new InvalidJaxTypeException(); + // add a parameter to the current type + JaxType parameterType = new JaxType(); + currentType.parameters.add(parameterType); + currentType = parameterType; + // prepare for the parameter type + types.push(currentType); + } else if (c == '[' || c == ']') { + log("Dimension for " + currentType.typeName); + // done for the class name unless it was already done by + // closing its parameter list + if (currentTypeName.length() > 0) { + setupType(currentType, currentTypeName, containingClass, doclet); + } + // FIXME: check dimension correctness + currentType.dimension += c; + } else { + log("Name char: " + currentTypeName); + // if the currentType already has a name, barf + if (currentType.typeName != null) + throw new InvalidJaxTypeException(); + currentTypeName.append(c); + } + } + // perhaps we didn't have any parameters or dimension + if (currentTypeName.length() > 0) { + log("End of type without param or dimension for " + currentTypeName); + setupType(currentType, currentTypeName, containingClass, doclet); + } + // we should have the original type to return + if (types.size() != 1) + throw new InvalidJaxTypeException(); + return currentType; + } + + public static String removeFragmentRegexes(String fragment, Map regexFragments) { + Pattern regexPattern = Pattern.compile("\\{(\\w[\\w\\.-]*)\\s*:"); + Matcher regexMatcher = regexPattern.matcher(fragment); + int start = 0; + char[] fragmentArray = fragment.toCharArray(); + StringBuffer strbuf = new StringBuffer(); + while (regexMatcher.find(start)) { + strbuf.append(fragment.substring(start, regexMatcher.start())); + String name = regexMatcher.group(1); + strbuf.append("{").append(name); + // now move on until after the last "}" + int openBraces = 1; + start = regexMatcher.end(); + while (start < fragmentArray.length) { + char c = fragmentArray[start++]; + if (c == '{') + openBraces++; + else if (c == '}') { + openBraces--; + if (openBraces == 0) + break; + } + } + if (openBraces > 0) + throw new RuntimeException("Invalid Path fragment: " + fragment); + String regex = fragment.substring(regexMatcher.end(), start - 1); + if (regexFragments != null) + regexFragments.put(name, regex); + strbuf.append("}"); + } + // add all that remains + strbuf.append(fragment.substring(start, fragmentArray.length)); + return strbuf.toString(); + } + + @SuppressWarnings("serial") + public static class InvalidJaxTypeException extends Exception {} + + private static void setupType(JaxType currentType, StringBuffer currentTypeName, ClassDoc containingClass, JAXDoclet doclet) + throws InvalidJaxTypeException { + if (currentTypeName.length() == 0) { + throw new InvalidJaxTypeException(); + } + currentType.typeName = currentTypeName.toString(); + currentType.type = resolveType(currentType.typeName, containingClass, doclet); + currentTypeName.setLength(0); + } + + public static class JaxType { + + String typeName; + + Type type; + + List parameters = new LinkedList(); + + String dimension = ""; + + public String getDimension() { + return dimension; + } + + public String getTypeName() { + return typeName; + } + + public Type getType() { + return type; + } + + public List getParameters() { + return parameters; + } + + public boolean hasParameters() { + return !parameters.isEmpty(); + } + } + + private static String addContextPath(JAXConfiguration config, String resource) { + // FIXME: move this to JAXRSConfiguration + String jaxrscontext = getOption(config.parentConfiguration.root.options(), "-jaxrscontext"); + if (jaxrscontext == null) { + return appendURLFragments("/", resource); + } else { + return appendURLFragments(jaxrscontext, resource); + } + } + + public static String getDisplayURL(DocletWriter writer, Resource resource, ResourceMethod method) { + return addContextPath(writer.getConfiguration(), method.getURL(resource)); + } + + public static String getAbsolutePath(DocletWriter writer, Resource resource) { + return addContextPath(writer.getConfiguration(), resource.getAbsolutePath()); + } + + public static String getAbsolutePath(JAXConfiguration config, Resource resource) { + return addContextPath(config, resource.getAbsolutePath()); + } + + public static void log(String mesg) { + // System.err.println(mesg); + } + + private static boolean isExcluded(Doc doc) { + return doc.tags("exclude").length != 0; + } + +} diff --git a/doclets/src/main/java/com/lunatech/doclets/jax/jaxrs/model/JAXRSApplication.java b/doclets/src/main/java/com/lunatech/doclets/jax/jaxrs/model/JAXRSApplication.java index 2a9d156..14a6856 100644 --- a/doclets/src/main/java/com/lunatech/doclets/jax/jaxrs/model/JAXRSApplication.java +++ b/doclets/src/main/java/com/lunatech/doclets/jax/jaxrs/model/JAXRSApplication.java @@ -1,150 +1,152 @@ -package com.lunatech.doclets.jax.jaxrs.model; - -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.ws.rs.Path; - -import com.lunatech.doclets.jax.Utils; -import com.lunatech.doclets.jax.jaxrs.JAXRSConfiguration; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.MethodDoc; -import com.sun.javadoc.Parameter; - -public class JAXRSApplication { - - private static final Class[] jaxrsAnnotations = new Class[] { Path.class }; - - private List jaxrsMethods = new LinkedList(); - - private Resource rootResource; - - private final JAXRSConfiguration conf; - - public JAXRSApplication(JAXRSConfiguration conf) { - this.conf = conf; - discoverJAXRSResources(); - } - - private void discoverJAXRSResources() { - final ClassDoc[] classes = conf.parentConfiguration.root.classes(); - for (final ClassDoc klass : classes) { - if (Utils.findAnnotatedClass(klass, jaxrsAnnotations) != null) { - handleJAXRSClass(klass); - } - } - Collections.sort(jaxrsMethods); - filterMethods(); - rootResource = buildRootResource(); - } - - private void filterMethods() { - if (conf.pathExcludeFilters.isEmpty()) - return; - - // collection for paths removing, since jaxrsMethods is immutable - List toRemove = new LinkedList(); - for (Pattern regexpFilter : conf.pathExcludeFilters) { - Iterator irm = jaxrsMethods.iterator(); - while (irm.hasNext()) { - ResourceMethod rm = irm.next(); - - if (regexpFilter.matcher(rm.getPath()).matches()) { - conf.parentConfiguration.root.printNotice("Resource method removed: " + rm); - toRemove.add(rm); - } - } - } - jaxrsMethods.removeAll(toRemove); - } - - private void handleJAXRSClass(final ClassDoc klass) { - if (conf.onlyOutputResourcesMatching != null) { - Matcher m = conf.onlyOutputResourcesMatching.matcher(klass.qualifiedTypeName()); - if(!m.matches()) { - return; - } - } - jaxrsMethods.addAll(new ResourceClass(klass, null).getMethods()); - } - - public Resource getRootResource() { - return rootResource; - } - - public Resource findResourceClass(ClassDoc cDoc) { - return findResourceClass(cDoc, null, rootResource); - } - - public Resource findResourceForMethod(ClassDoc cDoc, MethodDoc member) { - return findResourceClass(cDoc, member, rootResource); - } - - private Resource findResourceClass(ClassDoc cDoc, MethodDoc mDoc, Resource resource) { - for (ResourceMethod rMethod : resource.getMethods()) { - if (isImplementedBy(cDoc, rMethod.getDeclaringClass())) { - if ((mDoc == null) || areEqual(mDoc, rMethod.getMethodDoc())) { - return resource; - } - } - } - for (Resource subResource : resource.getResources().values()) { - Resource match = findResourceClass(cDoc, mDoc, subResource); - if (match != null) { - return match; - } - } - return null; - } - - private boolean isImplementedBy(ClassDoc cDoc, ClassDoc declaringClass) { - if (declaringClass.qualifiedTypeName().equals(cDoc.qualifiedTypeName())) { - return true; - } - if ((declaringClass.superclass() != null) && isImplementedBy(cDoc, declaringClass.superclass())) { - return true; - } - for (ClassDoc intDoc : declaringClass.interfaces()) { - if (isImplementedBy(cDoc, intDoc)) { - return true; - } - } - return false; - } - - static boolean areEqual(MethodDoc m1, MethodDoc m2) { - if (!m1.name().equals(m2.name())) { - return false; - } - Parameter[] p1 = m1.parameters(); - Parameter[] p2 = m1.parameters(); - - if (p1.length != p2.length) { - return false; - } - for (int i = 0; i < p1.length; i++) { - Parameter pi1 = p1[i]; - Parameter pi2 = p2[i]; - - if (!pi1.typeName().equals(pi2.typeName())) { - return false; - } - } - return true; - } - - private Resource buildRootResource() { - Resource rootResource = new Resource("", null); - for (ResourceMethod resourceMethod : jaxrsMethods) { - rootResource.addResourceMethod(resourceMethod); - } - // TODO: Avoid/Prune resource paths that have no resource methods (e.g. a - // Java resource method with a multi-part path) - return rootResource; - } - -} +package com.lunatech.doclets.jax.jaxrs.model; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.ws.rs.Path; + +import com.lunatech.doclets.jax.Utils; +import com.lunatech.doclets.jax.jaxrs.JAXRSConfiguration; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.MethodDoc; +import com.sun.javadoc.Parameter; + +public class JAXRSApplication { + + private static final Class[] jaxrsAnnotations = new Class[] { Path.class }; + + private List jaxrsMethods = new LinkedList(); + + private Resource rootResource; + + private final JAXRSConfiguration conf; + + public JAXRSApplication(JAXRSConfiguration conf) { + this.conf = conf; + discoverJAXRSResources(); + } + + private void discoverJAXRSResources() { + final ClassDoc[] classes = conf.parentConfiguration.root.classes(); + for (final ClassDoc klass : classes) { + + if (Utils.findAnnotatedClassOrInterface(klass, jaxrsAnnotations) != null) { + handleJAXRSClass(klass); + } + } + Collections.sort(jaxrsMethods); + filterMethods(); + rootResource = buildRootResource(); + } + + private void filterMethods() { + if (conf.pathExcludeFilters.isEmpty()) + return; + + // collection for paths removing, since jaxrsMethods is immutable + List toRemove = new LinkedList(); + for (Pattern regexpFilter : conf.pathExcludeFilters) { + Iterator irm = jaxrsMethods.iterator(); + while (irm.hasNext()) { + ResourceMethod rm = irm.next(); + + if (regexpFilter.matcher(rm.getPath()).matches()) { + conf.parentConfiguration.root.printNotice("Resource method removed: " + rm); + toRemove.add(rm); + } + } + } + jaxrsMethods.removeAll(toRemove); + } + + private void handleJAXRSClass(final ClassDoc klass) { + if (conf.onlyOutputResourcesMatching != null) { + Matcher m = conf.onlyOutputResourcesMatching.matcher(klass.qualifiedTypeName()); + if(!m.matches()) { + return; + } + } + jaxrsMethods.addAll(new ResourceClass(klass, null).getMethods()); + } + + public Resource getRootResource() { + return rootResource; + } + + public Resource findResourceClass(ClassDoc cDoc) { + return findResourceClass(cDoc, null, rootResource); + } + + public Resource findResourceForMethod(ClassDoc cDoc, MethodDoc member) { + return findResourceClass(cDoc, member, rootResource); + } + + private Resource findResourceClass(ClassDoc cDoc, MethodDoc mDoc, Resource resource) { + for (ResourceMethod rMethod : resource.getMethods()) { + if (isImplementedBy(cDoc, rMethod.getDeclaringClass())) { + if ((mDoc == null) || areEqual(mDoc, rMethod.getMethodDoc())) { + return resource; + } + } + } + for (Resource subResource : resource.getResources().values()) { + Resource match = findResourceClass(cDoc, mDoc, subResource); + if (match != null) { + return match; + } + } + return null; + } + + private boolean isImplementedBy(ClassDoc cDoc, ClassDoc declaringClass) { + if (declaringClass.qualifiedTypeName().equals(cDoc.qualifiedTypeName())) { + return true; + } + if ((declaringClass.superclass() != null) && isImplementedBy(cDoc, declaringClass.superclass())) { + return true; + } + for (ClassDoc intDoc : declaringClass.interfaces()) { + if (isImplementedBy(cDoc, intDoc)) { + return true; + } + } + return false; + } + + static boolean areEqual(MethodDoc m1, MethodDoc m2) { + if (!m1.name().equals(m2.name())) { + return false; + } + Parameter[] p1 = m1.parameters(); + Parameter[] p2 = m1.parameters(); + + if (p1.length != p2.length) { + return false; + } + for (int i = 0; i < p1.length; i++) { + Parameter pi1 = p1[i]; + Parameter pi2 = p2[i]; + + if (!pi1.typeName().equals(pi2.typeName())) { + return false; + } + } + return true; + } + + private Resource buildRootResource() { + Resource rootResource = new Resource("", null); + for (ResourceMethod resourceMethod : jaxrsMethods) { + rootResource.addResourceMethod(resourceMethod); + } + // TODO: Avoid/Prune resource paths that have no resource methods (e.g. a + // Java resource method with a multi-part path) + return rootResource; + } + +}