/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.doclava; import com.sun.javadoc.AnnotationDesc; import com.sun.javadoc.AnnotationTypeDoc; import com.sun.javadoc.AnnotationTypeElementDoc; import com.sun.javadoc.AnnotationValue; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ConstructorDoc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.MemberDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.ParamTag; import com.sun.javadoc.Parameter; import com.sun.javadoc.RootDoc; import com.sun.javadoc.SeeTag; import com.sun.javadoc.SourcePosition; import com.sun.javadoc.Tag; import com.sun.javadoc.ThrowsTag; import com.sun.javadoc.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; public class Converter { private static RootDoc root; public static void makeInfo(RootDoc r) { root = r; // create the objects ClassDoc[] classes = getClasses(r); for (ClassDoc c : classes) { Converter.obtainClass(c); } ArrayList classesNeedingInit2 = new ArrayList(); int i; // fill in the fields that reference other classes while (mClassesNeedingInit.size() > 0) { i = mClassesNeedingInit.size() - 1; ClassNeedingInit clni = mClassesNeedingInit.get(i); mClassesNeedingInit.remove(i); initClass(clni.c, clni.cl); classesNeedingInit2.add(clni.cl); } mClassesNeedingInit = null; for (ClassInfo cl : classesNeedingInit2) { cl.init2(); } finishAnnotationValueInit(); // fill in the "root" stuff mRootClasses = Converter.convertClasses(classes); } private static ClassDoc[] getClasses(RootDoc r) { ClassDoc[] classDocs = r.classes(); ArrayList filtered = new ArrayList(classDocs.length); for (ClassDoc c : classDocs) { if (c.position() != null) { // Work around a javadoc bug in Java 7: We sometimes spuriously // receive duplicate top level ClassDocs with null positions and no type // information. Ignore them, since every ClassDoc must have a non null // position. filtered.add(c); } } ClassDoc[] filteredArray = new ClassDoc[filtered.size()]; filtered.toArray(filteredArray); return filteredArray; } private static ClassInfo[] mRootClasses; public static ClassInfo[] rootClasses() { return mRootClasses; } public static ClassInfo[] allClasses() { return (ClassInfo[]) mClasses.all(); } private static final MethodDoc[] EMPTY_METHOD_DOC = new MethodDoc[0]; private static void initClass(ClassDoc c, ClassInfo cl) { MethodDoc[] annotationElements; if (c instanceof AnnotationTypeDoc) { annotationElements = ((AnnotationTypeDoc) c).elements(); } else { annotationElements = EMPTY_METHOD_DOC; } cl.init(Converter.obtainType(c), new ArrayList(Arrays.asList(Converter.convertClasses(c.interfaces()))), new ArrayList(Arrays.asList(Converter.convertTypes(c.interfaceTypes()))), new ArrayList(Arrays.asList(Converter.convertClasses(c.innerClasses()))), new ArrayList(Arrays.asList( Converter.convertMethods(c.constructors(false)))), new ArrayList(Arrays.asList(Converter.convertMethods(c.methods(false)))), new ArrayList(Arrays.asList(Converter.convertMethods(annotationElements))), new ArrayList(Arrays.asList(Converter.convertFields(c.fields(false)))), new ArrayList(Arrays.asList(Converter.convertFields(c.enumConstants()))), Converter.obtainPackage(c.containingPackage()), Converter.obtainClass(c.containingClass()), Converter.obtainClass(c.superclass()), Converter.obtainType(c.superclassType()), new ArrayList(Arrays.asList( Converter.convertAnnotationInstances(c.annotations())))); cl.setHiddenMethods( new ArrayList(Arrays.asList(Converter.getHiddenMethods(c.methods(false))))); cl.setRemovedMethods( new ArrayList(Arrays.asList(Converter.getRemovedMethods(c.methods(false))))); cl.setRemovedSelfMethods( new ArrayList(Converter.convertAllMethods(c.methods(false)))); cl.setRemovedConstructors( new ArrayList(Converter.convertAllMethods(c.constructors(false)))); cl.setRemovedSelfFields( new ArrayList(Converter.convertAllFields(c.fields(false)))); cl.setRemovedEnumConstants( new ArrayList(Converter.convertAllFields(c.enumConstants()))); cl.setNonWrittenConstructors( new ArrayList(Arrays.asList(Converter.convertNonWrittenConstructors( c.constructors(false))))); cl.init3( new ArrayList(Arrays.asList(Converter.convertTypes(c.typeParameters()))), new ArrayList(Arrays.asList( Converter.convertClasses(c.innerClasses(false))))); } public static ClassInfo obtainClass(String className) { return Converter.obtainClass(root.classNamed(className)); } public static PackageInfo obtainPackage(String packageName) { return Converter.obtainPackage(root.packageNamed(packageName)); } private static TagInfo convertTag(Tag tag) { return new TextTagInfo(tag.name(), tag.kind(), tag.text(), Converter.convertSourcePosition(tag.position())); } private static ThrowsTagInfo convertThrowsTag(ThrowsTag tag, ContainerInfo base) { return new ThrowsTagInfo(tag.name(), tag.text(), tag.kind(), Converter.obtainClass(tag .exception()), tag.exceptionComment(), base, Converter .convertSourcePosition(tag.position())); } private static ParamTagInfo convertParamTag(ParamTag tag, ContainerInfo base) { return new ParamTagInfo(tag.name(), tag.kind(), tag.text(), tag.isTypeParameter(), tag .parameterComment(), tag.parameterName(), base, Converter.convertSourcePosition(tag .position())); } private static SeeTagInfo convertSeeTag(SeeTag tag, ContainerInfo base) { return new SeeTagInfo(tag.name(), tag.kind(), tag.text(), base, Converter .convertSourcePosition(tag.position())); } private static SourcePositionInfo convertSourcePosition(SourcePosition sp) { if (sp == null) { return null; } return new SourcePositionInfo(sp.file().toString(), sp.line(), sp.column()); } public static TagInfo[] convertTags(Tag[] tags, ContainerInfo base) { int len = tags.length; TagInfo[] out = TagInfo.getArray(len); for (int i = 0; i < len; i++) { Tag t = tags[i]; /* * System.out.println("Tag name='" + t.name() + "' kind='" + t.kind() + "'"); */ if (t instanceof SeeTag) { out[i] = Converter.convertSeeTag((SeeTag) t, base); } else if (t instanceof ThrowsTag) { out[i] = Converter.convertThrowsTag((ThrowsTag) t, base); } else if (t instanceof ParamTag) { out[i] = Converter.convertParamTag((ParamTag) t, base); } else { out[i] = Converter.convertTag(t); } } return out; } public static ClassInfo[] convertClasses(ClassDoc[] classes) { if (classes == null) return null; int N = classes.length; ClassInfo[] result = new ClassInfo[N]; for (int i = 0; i < N; i++) { result[i] = Converter.obtainClass(classes[i]); } return result; } private static ParameterInfo convertParameter(Parameter p, SourcePosition pos, boolean isVarArg) { if (p == null) return null; ParameterInfo pi = new ParameterInfo(p.name(), p.typeName(), Converter.obtainType(p.type()), isVarArg, Converter.convertSourcePosition(pos), Arrays.asList(Converter.convertAnnotationInstances(p.annotations()))); return pi; } private static ParameterInfo[] convertParameters(Parameter[] p, ExecutableMemberDoc m) { SourcePosition pos = m.position(); int len = p.length; ParameterInfo[] q = new ParameterInfo[len]; for (int i = 0; i < len; i++) { boolean isVarArg = (m.isVarArgs() && i == len - 1); q[i] = Converter.convertParameter(p[i], pos, isVarArg); } return q; } private static TypeInfo[] convertTypes(Type[] p) { if (p == null) return null; int len = p.length; TypeInfo[] q = new TypeInfo[len]; for (int i = 0; i < len; i++) { q[i] = Converter.obtainType(p[i]); } return q; } private Converter() {} private static class ClassNeedingInit { ClassNeedingInit(ClassDoc c, ClassInfo cl) { this.c = c; this.cl = cl; } ClassDoc c; ClassInfo cl; } private static ArrayList mClassesNeedingInit = new ArrayList(); static ClassInfo obtainClass(ClassDoc o) { return (ClassInfo) mClasses.obtain(o); } private static Cache mClasses = new Cache() { @Override protected Object make(Object o) { ClassDoc c = (ClassDoc) o; ClassInfo cl = new ClassInfo(c, c.getRawCommentText(), Converter.convertSourcePosition(c.position()), c .isPublic(), c.isProtected(), c.isPackagePrivate(), c.isPrivate(), c.isStatic(), c .isInterface(), c.isAbstract(), c.isOrdinaryClass(), c.isException(), c.isError(), c .isEnum(), (c instanceof AnnotationTypeDoc), c.isFinal(), c.isIncluded(), c.name(), c .qualifiedName(), c.qualifiedTypeName(), c.isPrimitive()); if (mClassesNeedingInit != null) { mClassesNeedingInit.add(new ClassNeedingInit(c, cl)); } return cl; } @Override protected void made(Object o, Object r) { if (mClassesNeedingInit == null) { initClass((ClassDoc) o, (ClassInfo) r); ((ClassInfo) r).init2(); } } @Override ClassInfo[] all() { return mCache.values().toArray(new ClassInfo[mCache.size()]); } }; private static MethodInfo[] getHiddenMethods(MethodDoc[] methods) { if (methods == null) return null; ArrayList hiddenMethods = new ArrayList(); for (MethodDoc method : methods) { MethodInfo methodInfo = Converter.obtainMethod(method); if (methodInfo.isHidden()) { hiddenMethods.add(methodInfo); } } return hiddenMethods.toArray(new MethodInfo[hiddenMethods.size()]); } // Gets the removed methods regardless of access levels private static MethodInfo[] getRemovedMethods(MethodDoc[] methods) { if (methods == null) return null; ArrayList removedMethods = new ArrayList(); for (MethodDoc method : methods) { MethodInfo methodInfo = Converter.obtainMethod(method); if (methodInfo.isRemoved()) { removedMethods.add(methodInfo); } } return removedMethods.toArray(new MethodInfo[removedMethods.size()]); } /** * Converts FieldDoc[] into List. No filtering is done. */ private static List convertAllFields(FieldDoc[] fields) { if (fields == null) return null; List allFields = new ArrayList(); for (FieldDoc field : fields) { FieldInfo fieldInfo = Converter.obtainField(field); allFields.add(fieldInfo); } return allFields; } /** * Converts ExecutableMemberDoc[] into List. No filtering is done. */ private static List convertAllMethods(ExecutableMemberDoc[] methods) { if (methods == null) return null; List allMethods = new ArrayList(); for (ExecutableMemberDoc method : methods) { MethodInfo methodInfo = Converter.obtainMethod(method); allMethods.add(methodInfo); } return allMethods; } /** * Convert MethodDoc[] or ConstructorDoc[] into MethodInfo[]. * Also filters according to the -private, -public option, * because the filtering doesn't seem to be working in the ClassDoc.constructors(boolean) call. */ private static MethodInfo[] convertMethods(ExecutableMemberDoc[] methods) { if (methods == null) return null; List filteredMethods = new ArrayList(); for (ExecutableMemberDoc method : methods) { MethodInfo methodInfo = Converter.obtainMethod(method); if (methodInfo.checkLevel()) { filteredMethods.add(methodInfo); } } return filteredMethods.toArray(new MethodInfo[filteredMethods.size()]); } private static MethodInfo[] convertNonWrittenConstructors(ConstructorDoc[] methods) { if (methods == null) return null; ArrayList ctors = new ArrayList(); for (ConstructorDoc method : methods) { MethodInfo methodInfo = Converter.obtainMethod(method); if (!methodInfo.checkLevel()) { ctors.add(methodInfo); } } return ctors.toArray(new MethodInfo[ctors.size()]); } private static MethodInfo obtainMethod(E o) { return (MethodInfo) mMethods.obtain(o); } private static Cache mMethods = new Cache() { @Override protected Object make(Object o) { if (o instanceof AnnotationTypeElementDoc) { AnnotationTypeElementDoc m = (AnnotationTypeElementDoc) o; MethodInfo result = new MethodInfo(m.getRawCommentText(), new ArrayList(Arrays.asList( Converter.convertTypes(m.typeParameters()))), m.name(), m.signature(), Converter.obtainClass(m.containingClass()), Converter.obtainClass(m.containingClass()), m.isPublic(), m.isProtected(), m .isPackagePrivate(), m.isPrivate(), m.isFinal(), m.isStatic(), m.isSynthetic(), m.isAbstract(), m.isSynchronized(), m.isNative(), m.isDefault(), true, "annotationElement", m.flatSignature(), Converter.obtainMethod(m.overriddenMethod()), Converter.obtainType(m.returnType()), new ArrayList(Arrays.asList( Converter.convertParameters(m.parameters(), m))), new ArrayList(Arrays.asList(Converter.convertClasses( m.thrownExceptions()))), Converter.convertSourcePosition(m.position()), new ArrayList(Arrays.asList( Converter.convertAnnotationInstances(m.annotations())))); result.setVarargs(m.isVarArgs()); result.init(Converter.obtainAnnotationValue(m.defaultValue(), result)); return result; } else if (o instanceof MethodDoc) { MethodDoc m = (MethodDoc) o; MethodInfo result = new MethodInfo(m.getRawCommentText(), new ArrayList(Arrays.asList( Converter.convertTypes(m.typeParameters()))), m.name(), m.signature(), Converter.obtainClass(m.containingClass()), Converter.obtainClass(m.containingClass()), m.isPublic(), m.isProtected(), m.isPackagePrivate(), m.isPrivate(), m.isFinal(), m.isStatic(), m.isSynthetic(), m.isAbstract(), m.isSynchronized(), m.isNative(), m.isDefault(), false, "method", m.flatSignature(), Converter.obtainMethod(m.overriddenMethod()), Converter.obtainType(m.returnType()), new ArrayList(Arrays.asList( Converter.convertParameters(m.parameters(), m))), new ArrayList(Arrays.asList( Converter.convertClasses(m.thrownExceptions()))), Converter.convertSourcePosition(m.position()), new ArrayList(Arrays.asList( Converter.convertAnnotationInstances(m.annotations())))); result.setVarargs(m.isVarArgs()); result.init(null); return result; } else { ConstructorDoc m = (ConstructorDoc) o; // Workaround for a JavaDoc behavior change introduced in OpenJDK 8 that breaks // links in documentation and the content of API files like current.txt. // http://b/18051133. String name = m.name(); ClassDoc containingClass = m.containingClass(); if (containingClass.containingClass() != null) { // This should detect the new behavior and be bypassed otherwise. if (!name.contains(".")) { // Constructors of inner classes do not contain the name of the enclosing class // with OpenJDK 8. This simulates the old behavior: name = containingClass.name(); } } // End of workaround. MethodInfo result = new MethodInfo(m.getRawCommentText(), new ArrayList(Arrays.asList(Converter.convertTypes(m.typeParameters()))), name, m.signature(), Converter.obtainClass(m.containingClass()), Converter .obtainClass(m.containingClass()), m.isPublic(), m.isProtected(), m .isPackagePrivate(), m.isPrivate(), m.isFinal(), m.isStatic(), m.isSynthetic(), false, m.isSynchronized(), m.isNative(), false/*isDefault*/, false, "constructor", m.flatSignature(), null, null, new ArrayList(Arrays.asList(Converter.convertParameters(m.parameters(), m))), new ArrayList(Arrays.asList(Converter.convertClasses(m.thrownExceptions()))), Converter.convertSourcePosition(m .position()), new ArrayList(Arrays.asList(Converter.convertAnnotationInstances(m.annotations())))); result.setVarargs(m.isVarArgs()); result.init(null); return result; } } }; private static FieldInfo[] convertFields(FieldDoc[] fields) { if (fields == null) return null; ArrayList out = new ArrayList(); int N = fields.length; for (int i = 0; i < N; i++) { FieldInfo f = Converter.obtainField(fields[i]); if (f.checkLevel()) { out.add(f); } } return out.toArray(new FieldInfo[out.size()]); } private static FieldInfo obtainField(FieldDoc o) { return (FieldInfo) mFields.obtain(o); } private static FieldInfo obtainField(ConstructorDoc o) { return (FieldInfo) mFields.obtain(o); } private static Cache mFields = new Cache() { @Override protected Object make(Object o) { FieldDoc f = (FieldDoc) o; return new FieldInfo(f.name(), Converter.obtainClass(f.containingClass()), Converter .obtainClass(f.containingClass()), f.isPublic(), f.isProtected(), f.isPackagePrivate(), f .isPrivate(), f.isFinal(), f.isStatic(), f.isTransient(), f.isVolatile(), f.isSynthetic(), Converter.obtainType(f.type()), f.getRawCommentText(), f.constantValue(), Converter.convertSourcePosition(f.position()), new ArrayList(Arrays.asList(Converter .convertAnnotationInstances(f.annotations())))); } }; private static PackageInfo obtainPackage(PackageDoc o) { return (PackageInfo) mPackagees.obtain(o); } private static Cache mPackagees = new Cache() { @Override protected Object make(Object o) { PackageDoc p = (PackageDoc) o; return new PackageInfo(p, p.name(), Converter.convertSourcePosition(p.position())); } }; private static TypeInfo obtainType(Type o) { return (TypeInfo) mTypes.obtain(o); } private static Cache mTypes = new Cache() { @Override protected Object make(Object o) { Type t = (Type) o; String simpleTypeName; if (t instanceof ClassDoc) { simpleTypeName = ((ClassDoc) t).name(); } else { simpleTypeName = t.simpleTypeName(); } TypeInfo ti = new TypeInfo(t.isPrimitive(), t.dimension(), simpleTypeName, t.qualifiedTypeName(), Converter.obtainClass(t.asClassDoc())); return ti; } @Override protected void made(Object o, Object r) { Type t = (Type) o; TypeInfo ti = (TypeInfo) r; if (t.asParameterizedType() != null) { ti.setTypeArguments(new ArrayList(Arrays.asList(Converter.convertTypes(t.asParameterizedType().typeArguments())))); } else if (t instanceof ClassDoc) { ti.setTypeArguments(new ArrayList(Arrays.asList(Converter.convertTypes(((ClassDoc) t).typeParameters())))); } else if (t.asTypeVariable() != null) { ti.setBounds(null, new ArrayList(Arrays.asList(Converter.convertTypes((t.asTypeVariable().bounds()))))); ti.setIsTypeVariable(true); } else if (t.asWildcardType() != null) { ti.setIsWildcard(true); ti.setBounds(new ArrayList(Arrays.asList(Converter.convertTypes(t.asWildcardType().superBounds()))), new ArrayList(Arrays.asList(Converter.convertTypes(t.asWildcardType().extendsBounds())))); } } @Override protected Object keyFor(Object o) { Type t = (Type) o; String keyString = o.getClass().getName() + "/" + o.toString() + "/"; if (t.asParameterizedType() != null) { keyString += t.asParameterizedType().toString() + "/"; if (t.asParameterizedType().typeArguments() != null) { for (Type ty : t.asParameterizedType().typeArguments()) { keyString += ty.toString() + "/"; } } } else { keyString += "NoParameterizedType//"; } if (t.asTypeVariable() != null) { keyString += t.asTypeVariable().toString() + "/"; if (t.asTypeVariable().bounds() != null) { for (Type ty : t.asTypeVariable().bounds()) { keyString += ty.toString() + "/"; } } } else { keyString += "NoTypeVariable//"; } if (t.asWildcardType() != null) { keyString += t.asWildcardType().toString() + "/"; if (t.asWildcardType().superBounds() != null) { for (Type ty : t.asWildcardType().superBounds()) { keyString += ty.toString() + "/"; } } if (t.asWildcardType().extendsBounds() != null) { for (Type ty : t.asWildcardType().extendsBounds()) { keyString += ty.toString() + "/"; } } } else { keyString += "NoWildCardType//"; } return keyString; } }; public static TypeInfo obtainTypeFromString(String type) { return (TypeInfo) mTypesFromString.obtain(type); } private static final Cache mTypesFromString = new Cache() { @Override protected Object make(Object o) { String name = (String) o; return new TypeInfo(name); } @Override protected void made(Object o, Object r) { } @Override protected Object keyFor(Object o) { return o; } }; private static MemberInfo obtainMember(MemberDoc o) { return (MemberInfo) mMembers.obtain(o); } private static Cache mMembers = new Cache() { @Override protected Object make(Object o) { if (o instanceof MethodDoc) { return Converter.obtainMethod((MethodDoc) o); } else if (o instanceof ConstructorDoc) { return Converter.obtainMethod((ConstructorDoc) o); } else if (o instanceof FieldDoc) { return Converter.obtainField((FieldDoc) o); } else { return null; } } }; private static AnnotationInstanceInfo[] convertAnnotationInstances(AnnotationDesc[] orig) { int len = orig.length; AnnotationInstanceInfo[] out = new AnnotationInstanceInfo[len]; for (int i = 0; i < len; i++) { out[i] = Converter.obtainAnnotationInstance(orig[i]); } return out; } private static AnnotationInstanceInfo obtainAnnotationInstance(AnnotationDesc o) { return (AnnotationInstanceInfo) mAnnotationInstances.obtain(o); } private static Cache mAnnotationInstances = new Cache() { @Override protected Object make(Object o) { AnnotationDesc a = (AnnotationDesc) o; ClassInfo annotationType = Converter.obtainClass(a.annotationType()); AnnotationDesc.ElementValuePair[] ev = a.elementValues(); AnnotationValueInfo[] elementValues = new AnnotationValueInfo[ev.length]; for (int i = 0; i < ev.length; i++) { elementValues[i] = obtainAnnotationValue(ev[i].value(), Converter.obtainMethod(ev[i].element())); } return new AnnotationInstanceInfo(annotationType, elementValues); } }; private abstract static class Cache { void put(Object key, Object value) { mCache.put(key, value); } Object obtain(Object o) { if (o == null) { return null; } Object k = keyFor(o); Object r = mCache.get(k); if (r == null) { r = make(o); mCache.put(k, r); made(o, r); } return r; } protected HashMap mCache = new HashMap(); protected abstract Object make(Object o); protected void made(Object o, Object r) {} protected Object keyFor(Object o) { return o; } Object[] all() { return null; } } // annotation values private static HashMap mAnnotationValues = new HashMap(); private static HashSet mAnnotationValuesNeedingInit = new HashSet(); private static AnnotationValueInfo obtainAnnotationValue(AnnotationValue o, MethodInfo element) { if (o == null) { return null; } AnnotationValueInfo v = mAnnotationValues.get(o); if (v != null) return v; v = new AnnotationValueInfo(element); mAnnotationValues.put(o, v); if (mAnnotationValuesNeedingInit != null) { mAnnotationValuesNeedingInit.add(o); } else { initAnnotationValue(o, v); } return v; } private static void initAnnotationValue(AnnotationValue o, AnnotationValueInfo v) { Object orig = o.value(); Object converted; if (orig instanceof Type) { // class literal converted = Converter.obtainType((Type) orig); } else if (orig instanceof FieldDoc) { // enum constant converted = Converter.obtainField((FieldDoc) orig); } else if (orig instanceof AnnotationDesc) { // annotation instance converted = Converter.obtainAnnotationInstance((AnnotationDesc) orig); } else if (orig instanceof AnnotationValue[]) { AnnotationValue[] old = (AnnotationValue[]) orig; ArrayList values = new ArrayList(); for (int i = 0; i < old.length; i++) { values.add(Converter.obtainAnnotationValue(old[i], null)); } converted = values; } else { converted = orig; } v.init(converted); } private static void finishAnnotationValueInit() { int depth = 0; while (mAnnotationValuesNeedingInit.size() > 0) { HashSet set = mAnnotationValuesNeedingInit; mAnnotationValuesNeedingInit = new HashSet(); for (AnnotationValue o : set) { AnnotationValueInfo v = mAnnotationValues.get(o); initAnnotationValue(o, v); } depth++; } mAnnotationValuesNeedingInit = null; } }