diff options
Diffstat (limited to 'tools/droiddoc/src')
39 files changed, 9647 insertions, 0 deletions
diff --git a/tools/droiddoc/src/Android.mk b/tools/droiddoc/src/Android.mk new file mode 100644 index 0000000..c0c583d --- /dev/null +++ b/tools/droiddoc/src/Android.mk @@ -0,0 +1,68 @@ +# Copyright (C) 2008 The Android Open Source Project +# +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := docs + +LOCAL_SRC_FILES := \ + AnnotationInstanceInfo.java \ + AnnotationValueInfo.java \ + AttributeInfo.java \ + AttrTagInfo.java \ + ClassInfo.java \ + DroidDoc.java \ + ClearPage.java \ + Comment.java \ + ContainerInfo.java \ + Converter.java \ + DocFile.java \ + DocInfo.java \ + Errors.java \ + FieldInfo.java \ + Hierarchy.java \ + InheritedTags.java \ + KeywordEntry.java \ + LinkReference.java \ + LiteralTagInfo.java \ + MemberInfo.java \ + MethodInfo.java \ + PackageInfo.java \ + ParamTagInfo.java \ + ParameterInfo.java \ + ParsedTagInfo.java \ + Proofread.java \ + SampleCode.java \ + SampleTagInfo.java \ + Scoped.java \ + SeeTagInfo.java \ + Sorter.java \ + SourcePositionInfo.java \ + Stubs.java \ + TagInfo.java \ + TextTagInfo.java \ + ThrowsTagInfo.java \ + TodoFile.java \ + TypeInfo.java + +LOCAL_JAVA_LIBRARIES := \ + clearsilver + +LOCAL_CLASSPATH := \ + $(HOST_JDK_TOOLS_JAR) + +LOCAL_MODULE:= droiddoc + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/droiddoc/src/AnnotationInstanceInfo.java b/tools/droiddoc/src/AnnotationInstanceInfo.java new file mode 100644 index 0000000..07d4aa3 --- /dev/null +++ b/tools/droiddoc/src/AnnotationInstanceInfo.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +class AnnotationInstanceInfo +{ + private ClassInfo mType; + private AnnotationValueInfo[] mElementValues; + + public AnnotationInstanceInfo(ClassInfo type, AnnotationValueInfo[] elementValues) + { + mType = type; + mElementValues = elementValues; + } + + ClassInfo type() + { + return mType; + } + + AnnotationValueInfo[] elementValues() + { + return mElementValues; + } + + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append("@"); + str.append(mType.qualifiedName()); + str.append("("); + AnnotationValueInfo[] values = mElementValues; + final int N = values.length; + for (int i=0; i<N; i++) { + AnnotationValueInfo value = values[i]; + str.append(value.element().name()); + str.append("="); + str.append(value.valueString()); + if (i != N-1) { + str.append(","); + } + } + str.append(")"); + return str.toString(); + } +} + diff --git a/tools/droiddoc/src/AnnotationValueInfo.java b/tools/droiddoc/src/AnnotationValueInfo.java new file mode 100644 index 0000000..a2d869a --- /dev/null +++ b/tools/droiddoc/src/AnnotationValueInfo.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public class AnnotationValueInfo +{ + private Object mValue; + private String mString; + private MethodInfo mElement; + + public AnnotationValueInfo(MethodInfo element) + { + mElement = element; + } + + public void init(Object value) + { + mValue = value; + } + + public MethodInfo element() + { + return mElement; + } + + public Object value() + { + return mValue; + } + + public String valueString() + { + Object v = mValue; + if (v instanceof TypeInfo) { + return ((TypeInfo)v).fullName(); + } + else if (v instanceof FieldInfo) { + StringBuilder str = new StringBuilder(); + FieldInfo f = (FieldInfo)v; + str.append(f.containingClass().qualifiedName()); + str.append('.'); + str.append(f.name()); + return str.toString(); + } + else if (v instanceof AnnotationInstanceInfo) { + return v.toString(); + } + else if (v instanceof AnnotationValueInfo[]) { + StringBuilder str = new StringBuilder(); + AnnotationValueInfo[] array = (AnnotationValueInfo[])v; + final int N = array.length; + str.append("{"); + for (int i=0; i<array.length; i++) { + str.append(array[i].valueString()); + if (i != N-1) { + str.append(","); + } + } + str.append("}"); + return str.toString(); + } + else { + return FieldInfo.constantLiteralValue(v); + } + } +} + diff --git a/tools/droiddoc/src/AttrTagInfo.java b/tools/droiddoc/src/AttrTagInfo.java new file mode 100644 index 0000000..abc5452 --- /dev/null +++ b/tools/droiddoc/src/AttrTagInfo.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import org.clearsilver.HDF; +import org.clearsilver.CS; + + +public class AttrTagInfo extends TagInfo +{ + private static final String REF_COMMAND = "ref"; + private static final String NAME_COMMAND = "name"; + private static final String DESCRIPTION_COMMAND = "description"; + private static final Pattern TEXT = Pattern.compile("(\\S+)\\s*(.*)", Pattern.DOTALL); + private static final Pattern NAME_TEXT = Pattern.compile("(\\S+)(.*)", + Pattern.DOTALL); + + private ContainerInfo mBase; + private String mCommand; + + // if mCommand == "ref" + private FieldInfo mRefField; + private AttributeInfo mAttrInfo; + + // if mCommand == "name" + private String mAttrName; + + // if mCommand == "description" + private Comment mDescrComment; + + AttrTagInfo(String name, String kind, String text, ContainerInfo base, + SourcePositionInfo position) + { + super(name, kind, text, position); + mBase = base; + + parse(text, base, position); + } + + void parse(String text, ContainerInfo base, SourcePositionInfo position) { + Matcher m; + + m = TEXT.matcher(text); + if (!m.matches()) { + Errors.error(Errors.BAD_ATTR_TAG, position, "Bad @attr tag: " + text); + return; + } + + String command = m.group(1); + String more = m.group(2); + + if (REF_COMMAND.equals(command)) { + String ref = more.trim(); + LinkReference linkRef = LinkReference.parse(ref, mBase, position, false); + if (!linkRef.good) { + Errors.error(Errors.BAD_ATTR_TAG, position, "Unresolved @attr ref: " + ref); + return; + } + if (!(linkRef.memberInfo instanceof FieldInfo)) { + Errors.error(Errors.BAD_ATTR_TAG, position, "@attr must be a field: " + ref); + return; + } + mCommand = command; + mRefField = (FieldInfo)linkRef.memberInfo; + } + else if (NAME_COMMAND.equals(command)) { + m = NAME_TEXT.matcher(more); + if (!m.matches() || m.group(2).trim().length() != 0) { + Errors.error(Errors.BAD_ATTR_TAG, position, "Bad @attr name tag: " + more); + return; + } + mCommand = command; + mAttrName = m.group(1); + } + else if (DESCRIPTION_COMMAND.equals(command)) { + mCommand = command; + mDescrComment = new Comment(more, base, position); + } + else { + Errors.error(Errors.BAD_ATTR_TAG, position, "Bad @attr command: " + command); + } + } + + public FieldInfo reference() { + return REF_COMMAND.equals(mCommand) ? mRefField : null; + } + + public String name() { + return NAME_COMMAND.equals(mCommand) ? mAttrName : null; + } + + public Comment description() { + return DESCRIPTION_COMMAND.equals(mCommand) ? mDescrComment : null; + } + + public void makeHDF(HDF data, String base) + { + super.makeHDF(data, base); + } + + public void setAttribute(AttributeInfo info) { + mAttrInfo = info; + } + + public static void makeReferenceHDF(HDF data, String base, AttrTagInfo[] tags) + { + int i=0; + for (AttrTagInfo t: tags) { + if (REF_COMMAND.equals(t.mCommand)) { + if (t.mAttrInfo == null) { + String msg = "ERROR: unlinked attr: " + t.mRefField.name(); + if (false) { + System.out.println(msg); + } else { + throw new RuntimeException(msg); + } + } else { + data.setValue(base + "." + i + ".name", t.mAttrInfo.name()); + data.setValue(base + "." + i + ".href", t.mAttrInfo.htmlPage()); + i++; + } + } + } + } + +} diff --git a/tools/droiddoc/src/AttributeInfo.java b/tools/droiddoc/src/AttributeInfo.java new file mode 100644 index 0000000..a24106b --- /dev/null +++ b/tools/droiddoc/src/AttributeInfo.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.ArrayList; +import java.util.Comparator; + +import org.clearsilver.HDF; +import org.clearsilver.CS; + +public class AttributeInfo { + public static final Comparator<AttributeInfo> comparator = new Comparator<AttributeInfo>() { + public int compare(AttributeInfo a, AttributeInfo b) { + return a.name().compareTo(b.name()); + } + }; + + public FieldInfo attrField; + public ArrayList<MethodInfo> methods = new ArrayList<MethodInfo>(); + + private ClassInfo mClass; + private String mName; + private Comment mComment; + + public AttributeInfo(ClassInfo cl, FieldInfo f) { + mClass = cl; + attrField = f; + } + + public String name() { + if (mName == null) { + for (AttrTagInfo comment: attrField.comment().attrTags()) { + String n = comment.name(); + if (n != null) { + mName = n; + return n; + } + } + } + return mName; + } + + public Comment comment() { + if (mComment == null) { + for (AttrTagInfo attr: attrField.comment().attrTags()) { + Comment c = attr.description(); + if (c != null) { + mComment = c; + return c; + } + } + } + if (mComment == null) { + return new Comment("", mClass, new SourcePositionInfo()); + } + return mComment; + } + + public String anchor() { + return "attr_" + name(); + } + public String htmlPage() { + return mClass.htmlPage() + "#" + anchor(); + } + + public void makeHDF(HDF data, String base) { + data.setValue(base + ".name", name()); + data.setValue(base + ".anchor", anchor()); + data.setValue(base + ".href", htmlPage()); + data.setValue(base + ".R.name", attrField.name()); + data.setValue(base + ".R.href", attrField.htmlPage()); + TagInfo.makeHDF(data, base + ".deprecated", attrField.comment().deprecatedTags()); + TagInfo.makeHDF(data, base + ".shortDescr", comment().briefTags()); + TagInfo.makeHDF(data, base + ".descr", comment().tags()); + + int i=0; + for (MethodInfo m: methods) { + String s = base + ".methods." + i; + data.setValue(s + ".href", m.htmlPage()); + data.setValue(s + ".name", m.name() + m.prettySignature()); + } + } + + public boolean checkLevel() { + return attrField.checkLevel(); + } +} + diff --git a/tools/droiddoc/src/ClassInfo.java b/tools/droiddoc/src/ClassInfo.java new file mode 100644 index 0000000..36edbf8 --- /dev/null +++ b/tools/droiddoc/src/ClassInfo.java @@ -0,0 +1,1463 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import com.sun.javadoc.*; +import com.sun.tools.doclets.*; +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; + +public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Scoped +{ + public static final Comparator<ClassInfo> comparator = new Comparator<ClassInfo>() { + public int compare(ClassInfo a, ClassInfo b) { + return a.name().compareTo(b.name()); + } + }; + + public static final Comparator<ClassInfo> qualifiedComparator = new Comparator<ClassInfo>() { + public int compare(ClassInfo a, ClassInfo b) { + return a.qualifiedName().compareTo(b.qualifiedName()); + } + }; + + public ClassInfo( + ClassDoc cl, + String rawCommentText, SourcePositionInfo position, + boolean isPublic, boolean isProtected, boolean isPackagePrivate, + boolean isPrivate, boolean isStatic, + boolean isInterface, boolean isAbstract, boolean isOrdinaryClass, + boolean isException, boolean isError, boolean isEnum, boolean isAnnotation, + boolean isFinal, boolean isIncluded, String name, + String qualifiedName, String qualifiedTypeName, boolean isPrimitive) + { + super(rawCommentText, position); + + mClass = cl; + mIsPublic = isPublic; + mIsProtected = isProtected; + mIsPackagePrivate = isPackagePrivate; + mIsPrivate = isPrivate; + mIsStatic = isStatic; + mIsInterface = isInterface; + mIsAbstract = isAbstract; + mIsOrdinaryClass = isOrdinaryClass; + mIsException = isException; + mIsError = isError; + mIsEnum = isEnum; + mIsAnnotation = isAnnotation; + mIsFinal = isFinal; + mIsIncluded = isIncluded; + mName = name; + mQualifiedName = qualifiedName; + mQualifiedTypeName = qualifiedTypeName; + mIsPrimitive = isPrimitive; + mNameParts = name.split("\\."); + } + + public void init(TypeInfo typeInfo, ClassInfo[] interfaces, TypeInfo[] interfaceTypes, + ClassInfo[] innerClasses, + MethodInfo[] constructors, MethodInfo[] methods, MethodInfo[] annotationElements, + FieldInfo[] fields, FieldInfo[] enumConstants, + PackageInfo containingPackage, ClassInfo containingClass, + ClassInfo superclass, TypeInfo superclassType, AnnotationInstanceInfo[] annotations) + { + mTypeInfo = typeInfo; + mRealInterfaces = interfaces; + mRealInterfaceTypes = interfaceTypes; + mInnerClasses = innerClasses; + mAllConstructors = constructors; + mAllSelfMethods = methods; + mAnnotationElements = annotationElements; + mAllSelfFields = fields; + mEnumConstants = enumConstants; + mContainingPackage = containingPackage; + mContainingClass = containingClass; + mRealSuperclass = superclass; + mRealSuperclassType = superclassType; + mAnnotations = annotations; + + // after providing new methods and new superclass info,clear any cached + // lists of self + superclass methods, ctors, etc. + mSuperclassInit = false; + mConstructors = null; + mMethods = null; + mSelfMethods = null; + mFields = null; + mSelfFields = null; + mSelfAttributes = null; + mDeprecatedKnown = false; + + Arrays.sort(mEnumConstants, FieldInfo.comparator); + Arrays.sort(mInnerClasses, ClassInfo.comparator); + } + + public void init2() { + // calling this here forces the AttrTagInfo objects to be linked to the AttribtueInfo + // objects + selfAttributes(); + } + + public void init3(TypeInfo[] types, ClassInfo[] realInnerClasses){ + mTypeParameters = types; + mRealInnerClasses = realInnerClasses; + } + + public ClassInfo[] getRealInnerClasses(){ + return mRealInnerClasses; + } + + public TypeInfo[] getTypeParameters(){ + return mTypeParameters; + } + + public boolean checkLevel() + { + int val = mCheckLevel; + if (val >= 0) { + return val != 0; + } else { + boolean v = DroidDoc.checkLevel(mIsPublic, mIsProtected, + mIsPackagePrivate, mIsPrivate, isHidden()); + mCheckLevel = v ? 1 : 0; + return v; + } + } + + public int compareTo(Object that) { + if (that instanceof ClassInfo) { + return mQualifiedName.compareTo(((ClassInfo)that).mQualifiedName); + } else { + return this.hashCode() - that.hashCode(); + } + } + + public ContainerInfo parent() + { + return this; + } + + public boolean isPublic() + { + return mIsPublic; + } + + public boolean isProtected() + { + return mIsProtected; + } + + public boolean isPackagePrivate() + { + return mIsPackagePrivate; + } + + public boolean isPrivate() + { + return mIsPrivate; + } + + public boolean isStatic() + { + return mIsStatic; + } + + public boolean isInterface() + { + return mIsInterface; + } + + public boolean isAbstract() + { + return mIsAbstract; + } + + public PackageInfo containingPackage() + { + return mContainingPackage; + } + + public ClassInfo containingClass() + { + return mContainingClass; + } + + public boolean isOrdinaryClass() + { + return mIsOrdinaryClass; + } + + public boolean isException() + { + return mIsException; + } + + public boolean isError() + { + return mIsError; + } + + public boolean isEnum() + { + return mIsEnum; + } + + public boolean isAnnotation() + { + return mIsAnnotation; + } + + public boolean isFinal() + { + return mIsFinal; + } + + public boolean isIncluded() + { + return mIsIncluded; + } + + public HashSet<String> typeVariables() + { + HashSet<String> result = TypeInfo.typeVariables(mTypeInfo.typeArguments()); + ClassInfo cl = containingClass(); + while (cl != null) { + TypeInfo[] types = cl.asTypeInfo().typeArguments(); + if (types != null) { + TypeInfo.typeVariables(types, result); + } + cl = cl.containingClass(); + } + return result; + } + + private static void gatherHiddenInterfaces(ClassInfo cl, HashSet<ClassInfo> interfaces) { + for (ClassInfo iface: cl.mRealInterfaces) { + if (iface.checkLevel()) { + interfaces.add(iface); + } else { + gatherHiddenInterfaces(iface, interfaces); + } + } + } + + public ClassInfo[] interfaces() + { + if (mInterfaces == null) { + if (checkLevel()) { + HashSet<ClassInfo> interfaces = new HashSet<ClassInfo>(); + ClassInfo superclass = mRealSuperclass; + while (superclass != null && !superclass.checkLevel()) { + gatherHiddenInterfaces(superclass, interfaces); + superclass = superclass.mRealSuperclass; + } + gatherHiddenInterfaces(this, interfaces); + mInterfaces = interfaces.toArray(new ClassInfo[interfaces.size()]); + } else { + // put something here in case someone uses it + mInterfaces = mRealInterfaces; + } + Arrays.sort(mInterfaces, ClassInfo.qualifiedComparator); + } + return mInterfaces; + } + + public ClassInfo[] realInterfaces() + { + return mRealInterfaces; + } + + TypeInfo[] realInterfaceTypes() + { + return mRealInterfaceTypes; + } + + public String name() + { + return mName; + } + + public String[] nameParts() + { + return mNameParts; + } + + public String leafName() + { + return mNameParts[mNameParts.length-1]; + } + + public String qualifiedName() + { + return mQualifiedName; + } + + public String qualifiedTypeName() + { + return mQualifiedTypeName; + } + + public boolean isPrimitive() + { + return mIsPrimitive; + } + + public MethodInfo[] allConstructors() { + return mAllConstructors; + } + + public MethodInfo[] constructors() + { + if (mConstructors == null) { + MethodInfo[] methods = mAllConstructors; + ArrayList<MethodInfo> ctors = new ArrayList<MethodInfo>(); + for (int i=0; i<methods.length; i++) { + MethodInfo m = methods[i]; + if (!m.isHidden()) { + ctors.add(m); + } + } + mConstructors = ctors.toArray(new MethodInfo[ctors.size()]); + Arrays.sort(mConstructors, MethodInfo.comparator); + } + return mConstructors; + } + + public ClassInfo[] innerClasses() + { + return mInnerClasses; + } + + public TagInfo[] inlineTags() + { + return comment().tags(); + } + + public TagInfo[] firstSentenceTags() + { + return comment().briefTags(); + } + + public boolean isDeprecated() { + boolean deprecated = false; + if (!mDeprecatedKnown) { + boolean commentDeprecated = (comment().deprecatedTags().length > 0); + boolean annotationDeprecated = false; + for (AnnotationInstanceInfo annotation : annotations()) { + if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) { + annotationDeprecated = true; + break; + } + } + + if (commentDeprecated != annotationDeprecated) { + Errors.error(Errors.DEPRECATION_MISMATCH, position(), + "Class " + qualifiedName() + + ": @Deprecated annotation and @deprecated comment do not match"); + } + + mIsDeprecated = commentDeprecated | annotationDeprecated; + mDeprecatedKnown = true; + } + return mIsDeprecated; + } + + public TagInfo[] deprecatedTags() + { + TagInfo[] result = comment().deprecatedTags(); + if (result.length == 0) { + if (comment().undeprecateTags().length == 0) { + if (superclass() != null) { + result = superclass().deprecatedTags(); + } + } + } + // should we also do the interfaces? + return result; + } + + public MethodInfo[] methods() + { + if (mMethods == null) { + TreeMap<String,MethodInfo> all = new TreeMap<String,MethodInfo>(); + + ClassInfo[] ifaces = interfaces(); + for (ClassInfo iface: ifaces) { + if (iface != null) { + MethodInfo[] inhereted = iface.methods(); + for (MethodInfo method: inhereted) { + String key = method.name() + method.signature(); + all.put(key, method); + } + } + } + + ClassInfo superclass = superclass(); + if (superclass != null) { + MethodInfo[] inhereted = superclass.methods(); + for (MethodInfo method: inhereted) { + String key = method.name() + method.signature(); + all.put(key, method); + } + } + + MethodInfo[] methods = selfMethods(); + for (MethodInfo method: methods) { + String key = method.name() + method.signature(); + MethodInfo old = all.put(key, method); + } + + mMethods = all.values().toArray(new MethodInfo[all.size()]); + } + return mMethods; + } + + public MethodInfo[] annotationElements() + { + return mAnnotationElements; + } + + public AnnotationInstanceInfo[] annotations() + { + return mAnnotations; + } + + private static void addFields(ClassInfo cl, TreeMap<String,FieldInfo> all) + { + FieldInfo[] fields = cl.fields(); + int N = fields.length; + for (int i=0; i<N; i++) { + FieldInfo f = fields[i]; + all.put(f.name(), f); + } + } + + public FieldInfo[] fields() + { + if (mFields == null) { + int N; + TreeMap<String,FieldInfo> all = new TreeMap<String,FieldInfo>(); + + ClassInfo[] interfaces = interfaces(); + N = interfaces.length; + for (int i=0; i<N; i++) { + addFields(interfaces[i], all); + } + + ClassInfo superclass = superclass(); + if (superclass != null) { + addFields(superclass, all); + } + + FieldInfo[] fields = selfFields(); + N = fields.length; + for (int i=0; i<N; i++) { + FieldInfo f = fields[i]; + if (!f.isHidden()) { + String key = f.name(); + all.put(key, f); + } + } + + mFields = all.values().toArray(new FieldInfo[0]); + } + return mFields; + } + + public void gatherFields(ClassInfo owner, ClassInfo cl, HashMap<String,FieldInfo> fields) { + FieldInfo[] flds = cl.selfFields(); + for (FieldInfo f: flds) { + if (f.checkLevel()) { + fields.put(f.name(), f.cloneForClass(owner)); + } + } + } + + public FieldInfo[] selfFields() + { + if (mSelfFields == null) { + HashMap<String,FieldInfo> fields = new HashMap<String,FieldInfo>(); + // our hidden parents + if (mRealSuperclass != null && !mRealSuperclass.checkLevel()) { + gatherFields(this, mRealSuperclass, fields); + } + for (ClassInfo iface: mRealInterfaces) { + if (!iface.checkLevel()) { + gatherFields(this, iface, fields); + } + } + // mine + FieldInfo[] selfFields = mAllSelfFields; + for (int i=0; i<selfFields.length; i++) { + FieldInfo f = selfFields[i]; + if (!f.isHidden()) { + fields.put(f.name(), f); + } + } + // combine and return in + mSelfFields = fields.values().toArray(new FieldInfo[fields.size()]); + Arrays.sort(mSelfFields, FieldInfo.comparator); + } + return mSelfFields; + } + + public FieldInfo[] allSelfFields() { + return mAllSelfFields; + } + + public void gatherMethods(ClassInfo owner, ClassInfo cl, HashMap<String,MethodInfo> methods) { + MethodInfo[] meth = cl.selfMethods(); + for (MethodInfo m: meth) { + if (m.checkLevel()) { + methods.put(m.name()+m.signature(), m.cloneForClass(owner)); + } + } + } + + public MethodInfo[] selfMethods() + { + if (mSelfMethods == null) { + HashMap<String,MethodInfo> methods = new HashMap<String,MethodInfo>(); + // our hidden parents + if (mRealSuperclass != null && !mRealSuperclass.checkLevel()) { + gatherMethods(this, mRealSuperclass, methods); + } + for (ClassInfo iface: mRealInterfaces) { + if (!iface.checkLevel()) { + gatherMethods(this, iface, methods); + } + } + // mine + MethodInfo[] selfMethods = mAllSelfMethods; + for (int i=0; i<selfMethods.length; i++) { + MethodInfo m = selfMethods[i]; + if (m.checkLevel()) { + methods.put(m.name()+m.signature(), m); + } + } + // combine and return it + mSelfMethods = methods.values().toArray(new MethodInfo[methods.size()]); + Arrays.sort(mSelfMethods, MethodInfo.comparator); + } + return mSelfMethods; + } + + public MethodInfo[] allSelfMethods() { + return mAllSelfMethods; + } + + public void addMethod(MethodInfo method) { + MethodInfo[] methods = new MethodInfo[mAllSelfMethods.length + 1]; + int i = 0; + for (MethodInfo m : mAllSelfMethods) { + methods[i] = m; + i++; + } + methods[i] = method; + mAllSelfMethods = methods; + } + + public AttributeInfo[] selfAttributes() + { + if (mSelfAttributes == null) { + TreeMap<FieldInfo,AttributeInfo> attrs = new TreeMap<FieldInfo,AttributeInfo>(); + + // the ones in the class comment won't have any methods + for (AttrTagInfo tag: comment().attrTags()) { + FieldInfo field = tag.reference(); + if (field != null) { + AttributeInfo attr = attrs.get(field); + if (attr == null) { + attr = new AttributeInfo(this, field); + attrs.put(field, attr); + } + tag.setAttribute(attr); + } + } + + // in the methods + for (MethodInfo m: selfMethods()) { + for (AttrTagInfo tag: m.comment().attrTags()) { + FieldInfo field = tag.reference(); + if (field != null) { + AttributeInfo attr = attrs.get(field); + if (attr == null) { + attr = new AttributeInfo(this, field); + attrs.put(field, attr); + } + tag.setAttribute(attr); + attr.methods.add(m); + } + } + } + + //constructors too + for (MethodInfo m: constructors()) { + for (AttrTagInfo tag: m.comment().attrTags()) { + FieldInfo field = tag.reference(); + if (field != null) { + AttributeInfo attr = attrs.get(field); + if (attr == null) { + attr = new AttributeInfo(this, field); + attrs.put(field, attr); + } + tag.setAttribute(attr); + attr.methods.add(m); + } + } + } + + mSelfAttributes = attrs.values().toArray(new AttributeInfo[attrs.size()]); + Arrays.sort(mSelfAttributes, AttributeInfo.comparator); + } + return mSelfAttributes; + } + + public FieldInfo[] enumConstants() + { + return mEnumConstants; + } + + public ClassInfo superclass() + { + if (!mSuperclassInit) { + if (this.checkLevel()) { + // rearrange our little inheritance hierarchy, because we need to hide classes that + // don't pass checkLevel + ClassInfo superclass = mRealSuperclass; + while (superclass != null && !superclass.checkLevel()) { + superclass = superclass.mRealSuperclass; + } + mSuperclass = superclass; + } else { + mSuperclass = mRealSuperclass; + } + } + return mSuperclass; + } + + public ClassInfo realSuperclass() + { + return mRealSuperclass; + } + + /** always the real superclass, not the collapsed one we get through superclass(), + * also has the type parameter info if it's generic. + */ + public TypeInfo superclassType() + { + return mRealSuperclassType; + } + + public TypeInfo asTypeInfo() + { + return mTypeInfo; + } + + TypeInfo[] interfaceTypes() + { + ClassInfo[] infos = interfaces(); + int len = infos.length; + TypeInfo[] types = new TypeInfo[len]; + for (int i=0; i<len; i++) { + types[i] = infos[i].asTypeInfo(); + } + return types; + } + + public String htmlPage() + { + String s = containingPackage().name(); + s = s.replace('.', '/'); + s += '/'; + s += name(); + s += ".html"; + s = DroidDoc.javadocDir + s; + return s; + } + + /** Even indirectly */ + public boolean isDerivedFrom(ClassInfo cl) + { + ClassInfo dad = this.superclass(); + if (dad != null) { + if (dad.equals(cl)) { + return true; + } else { + if (dad.isDerivedFrom(cl)) { + return true; + } + } + } + for (ClassInfo iface: interfaces()) { + if (iface.equals(cl)) { + return true; + } else { + if (iface.isDerivedFrom(cl)) { + return true; + } + } + } + return false; + } + + public void makeKeywordEntries(List<KeywordEntry> keywords) + { + if (!checkLevel()) { + return; + } + + String htmlPage = htmlPage(); + String qualifiedName = qualifiedName(); + + keywords.add(new KeywordEntry(name(), htmlPage, + "class in " + containingPackage().name())); + + FieldInfo[] fields = selfFields(); + FieldInfo[] enumConstants = enumConstants(); + MethodInfo[] ctors = constructors(); + MethodInfo[] methods = selfMethods(); + + // enum constants + for (FieldInfo field: enumConstants()) { + if (field.checkLevel()) { + keywords.add(new KeywordEntry(field.name(), + htmlPage + "#" + field.anchor(), + "enum constant in " + qualifiedName)); + } + } + + // constants + for (FieldInfo field: fields) { + if (field.isConstant() && field.checkLevel()) { + keywords.add(new KeywordEntry(field.name(), + htmlPage + "#" + field.anchor(), + "constant in " + qualifiedName)); + } + } + + // fields + for (FieldInfo field: fields) { + if (!field.isConstant() && field.checkLevel()) { + keywords.add(new KeywordEntry(field.name(), + htmlPage + "#" + field.anchor(), + "field in " + qualifiedName)); + } + } + + // public constructors + for (MethodInfo m: ctors) { + if (m.isPublic() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "constructor in " + qualifiedName)); + } + } + + // protected constructors + if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) { + for (MethodInfo m: ctors) { + if (m.isProtected() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "constructor in " + qualifiedName)); + } + } + } + + // package private constructors + if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) { + for (MethodInfo m: ctors) { + if (m.isPackagePrivate() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "constructor in " + qualifiedName)); + } + } + } + + // private constructors + if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) { + for (MethodInfo m: ctors) { + if (m.isPrivate() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "constructor in " + qualifiedName)); + } + } + } + + // public methods + for (MethodInfo m: methods) { + if (m.isPublic() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "method in " + qualifiedName)); + } + } + + // protected methods + if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) { + for (MethodInfo m: methods) { + if (m.isProtected() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "method in " + qualifiedName)); + } + } + } + + // package private methods + if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) { + for (MethodInfo m: methods) { + if (m.isPackagePrivate() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "method in " + qualifiedName)); + } + } + } + + // private methods + if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) { + for (MethodInfo m: methods) { + if (m.isPrivate() && m.checkLevel()) { + keywords.add(new KeywordEntry(m.name() + m.prettySignature(), + htmlPage + "#" + m.anchor(), + "method in " + qualifiedName)); + } + } + } + } + + public void makeLink(HDF data, String base) + { + data.setValue(base + ".label", this.name()); + if (!this.isPrimitive() && this.isIncluded() && this.checkLevel()) { + data.setValue(base + ".link", this.htmlPage()); + } + } + + public static void makeLinkListHDF(HDF data, String base, ClassInfo[] classes) { + final int N = classes.length; + for (int i=0; i<N; i++) { + ClassInfo cl = classes[i]; + if (cl.checkLevel()) { + cl.asTypeInfo().makeHDF(data, base + "." + i); + } + } + } + + /** + * Used in lists of this class (packages, nested classes, known subclasses) + */ + public void makeShortDescrHDF(HDF data, String base) + { + mTypeInfo.makeHDF(data, base + ".type"); + data.setValue(base + ".kind", this.kind()); + TagInfo.makeHDF(data, base + ".shortDescr", this.firstSentenceTags()); + TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); + } + + /** + * Turns into the main class page + */ + public void makeHDF(HDF data) + { + int i, j, n; + String name = name(); + String qualified = qualifiedName(); + AttributeInfo[] selfAttributes = selfAttributes(); + MethodInfo[] methods = selfMethods(); + FieldInfo[] fields = selfFields(); + FieldInfo[] enumConstants = enumConstants(); + MethodInfo[] ctors = constructors(); + ClassInfo[] inners = innerClasses(); + + // class name + mTypeInfo.makeHDF(data, "class.type"); + mTypeInfo.makeQualifiedHDF(data, "class.qualifiedType"); + data.setValue("class.name", name); + data.setValue("class.qualified", qualified); + String scope = ""; + if (isProtected()) { + data.setValue("class.scope", "protected"); + } + else if (isPublic()) { + data.setValue("class.scope", "public"); + } + if (isStatic()) { + data.setValue("class.static", "static"); + } + if (isFinal()) { + data.setValue("class.final", "final"); + } + if (isAbstract() && !isInterface()) { + data.setValue("class.abstract", "abstract"); + } + + // class info + String kind = kind(); + if (kind != null) { + data.setValue("class.kind", kind); + } + + // the containing package -- note that this can be passed to type_link, + // but it also contains the list of all of the packages + containingPackage().makeClassLinkListHDF(data, "class.package"); + + // inheritance hierarchy + Vector<ClassInfo> superClasses = new Vector<ClassInfo>(); + superClasses.add(this); + ClassInfo supr = superclass(); + while (supr != null) { + superClasses.add(supr); + supr = supr.superclass(); + } + n = superClasses.size(); + for (i=0; i<n; i++) { + supr = superClasses.elementAt(n-i-1); + + supr.asTypeInfo().makeQualifiedHDF(data, "class.inheritance." + i + ".class"); + supr.asTypeInfo().makeHDF(data, "class.inheritance." + i + ".short_class"); + j = 0; + for (TypeInfo t: supr.interfaceTypes()) { + t.makeHDF(data, "class.inheritance." + i + ".interfaces." + j); + j++; + } + } + + // class description + TagInfo.makeHDF(data, "class.descr", inlineTags()); + TagInfo.makeHDF(data, "class.seeAlso", comment().seeTags()); + TagInfo.makeHDF(data, "class.deprecated", deprecatedTags()); + + // known subclasses + TreeMap<String, ClassInfo> direct = new TreeMap<String, ClassInfo>(); + TreeMap<String, ClassInfo> indirect = new TreeMap<String, ClassInfo>(); + ClassInfo[] all = Converter.rootClasses(); + for (ClassInfo cl: all) { + if (cl.superclass() != null && cl.superclass().equals(this)) { + direct.put(cl.name(), cl); + } + else if (cl.isDerivedFrom(this)) { + indirect.put(cl.name(), cl); + } + } + // direct + i = 0; + for (ClassInfo cl: direct.values()) { + if (cl.checkLevel()) { + cl.makeShortDescrHDF(data, "class.subclasses.direct." + i); + } + i++; + } + // indirect + i = 0; + for (ClassInfo cl: indirect.values()) { + if (cl.checkLevel()) { + cl.makeShortDescrHDF(data, "class.subclasses.indirect." + i); + } + i++; + } + + // nested classes + i=0; + for (ClassInfo inner: inners) { + if (inner.checkLevel()) { + inner.makeShortDescrHDF(data, "class.inners." + i); + } + i++; + } + + // enum constants + i=0; + for (FieldInfo field: enumConstants) { + if (field.isConstant()) { + field.makeHDF(data, "class.enumConstants." + i); + i++; + } + } + + // constants + i=0; + for (FieldInfo field: fields) { + if (field.isConstant()) { + field.makeHDF(data, "class.constants." + i); + i++; + } + } + + // fields + i=0; + for (FieldInfo field: fields) { + if (!field.isConstant()) { + field.makeHDF(data, "class.fields." + i); + i++; + } + } + + // public constructors + i=0; + for (MethodInfo ctor: ctors) { + if (ctor.isPublic()) { + ctor.makeHDF(data, "class.ctors.public." + i); + i++; + } + } + + // protected constructors + if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) { + i=0; + for (MethodInfo ctor: ctors) { + if (ctor.isProtected()) { + ctor.makeHDF(data, "class.ctors.protected." + i); + i++; + } + } + } + + // package private constructors + if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) { + i=0; + for (MethodInfo ctor: ctors) { + if (ctor.isPackagePrivate()) { + ctor.makeHDF(data, "class.ctors.package." + i); + i++; + } + } + } + + // private constructors + if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) { + i=0; + for (MethodInfo ctor: ctors) { + if (ctor.isPrivate()) { + ctor.makeHDF(data, "class.ctors.private." + i); + i++; + } + } + } + + // public methods + i=0; + for (MethodInfo method: methods) { + if (method.isPublic()) { + method.makeHDF(data, "class.methods.public." + i); + i++; + } + } + + // protected methods + if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) { + i=0; + for (MethodInfo method: methods) { + if (method.isProtected()) { + method.makeHDF(data, "class.methods.protected." + i); + i++; + } + } + } + + // package private methods + if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) { + i=0; + for (MethodInfo method: methods) { + if (method.isPackagePrivate()) { + method.makeHDF(data, "class.methods.package." + i); + i++; + } + } + } + + // private methods + if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) { + i=0; + for (MethodInfo method: methods) { + if (method.isPrivate()) { + method.makeHDF(data, "class.methods.private." + i); + i++; + } + } + } + + // xml attributes + i=0; + for (AttributeInfo attr: selfAttributes) { + if (attr.checkLevel()) { + attr.makeHDF(data, "class.attrs." + i); + i++; + } + } + + // inherited methods + Set<ClassInfo> interfaces = new TreeSet<ClassInfo>(); + addInterfaces(interfaces(), interfaces); + ClassInfo cl = superclass(); + i=0; + while (cl != null) { + addInterfaces(cl.interfaces(), interfaces); + makeInheritedHDF(data, i, cl); + cl = cl.superclass(); + i++; + } + for (ClassInfo iface: interfaces) { + makeInheritedHDF(data, i, iface); + i++; + } + } + + private static void addInterfaces(ClassInfo[] ifaces, Set<ClassInfo> out) + { + for (ClassInfo cl: ifaces) { + out.add(cl); + addInterfaces(cl.interfaces(), out); + } + } + + private static void makeInheritedHDF(HDF data, int index, ClassInfo cl) + { + int i; + + String base = "class.inherited." + index; + data.setValue(base + ".qualified", cl.qualifiedName()); + if (cl.checkLevel()) { + data.setValue(base + ".link", cl.htmlPage()); + } + String kind = cl.kind(); + if (kind != null) { + data.setValue(base + ".kind", kind); + } + + // xml attributes + i=0; + for (AttributeInfo attr: cl.selfAttributes()) { + attr.makeHDF(data, base + ".attrs." + i); + i++; + } + + // methods + i=0; + for (MethodInfo method: cl.selfMethods()) { + method.makeHDF(data, base + ".methods." + i); + i++; + } + + // fields + i=0; + for (FieldInfo field: cl.selfFields()) { + if (!field.isConstant()) { + field.makeHDF(data, base + ".fields." + i); + i++; + } + } + + // constants + i=0; + for (FieldInfo field: cl.selfFields()) { + if (field.isConstant()) { + field.makeHDF(data, base + ".constants." + i); + i++; + } + } + } + + public boolean isHidden() + { + int val = mHidden; + if (val >= 0) { + return val != 0; + } else { + boolean v = isHiddenImpl(); + mHidden = v ? 1 : 0; + return v; + } + } + + public boolean isHiddenImpl() + { + ClassInfo cl = this; + while (cl != null) { + PackageInfo pkg = cl.containingPackage(); + if (pkg.isHidden()) { + return true; + } + if (cl.comment().isHidden()) { + return true; + } + cl = cl.containingClass(); + } + return false; + } + + private MethodInfo matchMethod(MethodInfo[] methods, String name, + String[] params, String[] dimensions) + { + int len = methods.length; + for (int i=0; i<len; i++) { + MethodInfo method = methods[i]; + if (method.name().equals(name)) { + if (params == null) { + return method; + } else { + if (method.matchesParams(params, dimensions)) { + return method; + } + } + } + } + return null; + } + + public MethodInfo findMethod(String name, + String[] params, String[] dimensions) + { + // first look on our class, and our superclasses + + // for methods + MethodInfo rv; + rv = matchMethod(methods(), name, params, dimensions); + + if (rv != null) { + return rv; + } + + // for constructors + rv = matchMethod(constructors(), name, params, dimensions); + if (rv != null) { + return rv; + } + + // then recursively look at our containing class + ClassInfo containing = containingClass(); + if (containing != null) { + return containing.findMethod(name, params, dimensions); + } + + return null; + } + + private ClassInfo searchInnerClasses(String[] nameParts, int index) + { + String part = nameParts[index]; + + ClassInfo[] inners = mInnerClasses; + for (ClassInfo in: inners) { + String[] innerParts = in.nameParts(); + if (part.equals(innerParts[innerParts.length-1])) { + if (index == nameParts.length-1) { + return in; + } else { + return in.searchInnerClasses(nameParts, index+1); + } + } + } + return null; + } + + public ClassInfo extendedFindClass(String className) + { + // ClassDoc.findClass has this bug that we're working around here: + // If you have a class PackageManager with an inner class PackageInfo + // and you call it with "PackageInfo" it doesn't find it. + return searchInnerClasses(className.split("\\."), 0); + } + + public ClassInfo findClass(String className) + { + return Converter.obtainClass(mClass.findClass(className)); + } + + public ClassInfo findInnerClass(String className) + { + // ClassDoc.findClass won't find inner classes. To deal with that, + // we try what they gave us first, but if that didn't work, then + // we see if there are any periods in className, and start searching + // from there. + String[] nodes = className.split("\\."); + ClassDoc cl = mClass; + for (String n: nodes) { + cl = cl.findClass(n); + if (cl == null) { + return null; + } + } + return Converter.obtainClass(cl); + } + + public FieldInfo findField(String name) + { + // first look on our class, and our superclasses + for (FieldInfo f: fields()) { + if (f.name().equals(name)) { + return f; + } + } + + // then look at our enum constants (these are really fields, maybe + // they should be mixed into fields(). not sure) + for (FieldInfo f: enumConstants()) { + if (f.name().equals(name)) { + return f; + } + } + + // then recursively look at our containing class + ClassInfo containing = containingClass(); + if (containing != null) { + return containing.findField(name); + } + + return null; + } + + public static ClassInfo[] sortByName(ClassInfo[] classes) + { + int i; + Sorter[] sorted = new Sorter[classes.length]; + for (i=0; i<sorted.length; i++) { + ClassInfo cl = classes[i]; + sorted[i] = new Sorter(cl.name(), cl); + } + + Arrays.sort(sorted); + + ClassInfo[] rv = new ClassInfo[classes.length]; + for (i=0; i<rv.length; i++) { + rv[i] = (ClassInfo)sorted[i].data; + } + + return rv; + } + + public boolean equals(ClassInfo that) + { + if (that != null) { + return this.qualifiedName().equals(that.qualifiedName()); + } else { + return false; + } + } + + public void setNonWrittenConstructors(MethodInfo[] nonWritten) { + mNonWrittenConstructors = nonWritten; + } + + public MethodInfo[] getNonWrittenConstructors() { + return mNonWrittenConstructors; + } + + public String kind() + { + if (isOrdinaryClass()) { + return "class"; + } + else if (isInterface()) { + return "interface"; + } + else if (isEnum()) { + return "enum"; + } + else if (isError()) { + return "class"; + } + else if (isException()) { + return "class"; + } + else if (isAnnotation()) { + return "@interface"; + } + return null; + } + + public void setHiddenMethods(MethodInfo[] mInfo){ + mHiddenMethods = mInfo; + } + public MethodInfo[] getHiddenMethods(){ + return mHiddenMethods; + } + public String toString(){ + return this.qualifiedName(); + } + + public void setReasonIncluded(String reason) { + mReasonIncluded = reason; + } + + public String getReasonIncluded() { + return mReasonIncluded; + } + + private ClassDoc mClass; + + // ctor + private boolean mIsPublic; + private boolean mIsProtected; + private boolean mIsPackagePrivate; + private boolean mIsPrivate; + private boolean mIsStatic; + private boolean mIsInterface; + private boolean mIsAbstract; + private boolean mIsOrdinaryClass; + private boolean mIsException; + private boolean mIsError; + private boolean mIsEnum; + private boolean mIsAnnotation; + private boolean mIsFinal; + private boolean mIsIncluded; + private String mName; + private String mQualifiedName; + private String mQualifiedTypeName; + private boolean mIsPrimitive; + private TypeInfo mTypeInfo; + private String[] mNameParts; + + // init + private ClassInfo[] mRealInterfaces; + private ClassInfo[] mInterfaces; + private TypeInfo[] mRealInterfaceTypes; + private ClassInfo[] mInnerClasses; + private MethodInfo[] mAllConstructors; + private MethodInfo[] mAllSelfMethods; + private MethodInfo[] mAnnotationElements; // if this class is an annotation + private FieldInfo[] mAllSelfFields; + private FieldInfo[] mEnumConstants; + private PackageInfo mContainingPackage; + private ClassInfo mContainingClass; + private ClassInfo mRealSuperclass; + private TypeInfo mRealSuperclassType; + private ClassInfo mSuperclass; + private AnnotationInstanceInfo[] mAnnotations; + private boolean mSuperclassInit; + private boolean mDeprecatedKnown; + + // lazy + private MethodInfo[] mConstructors; + private ClassInfo[] mRealInnerClasses; + private MethodInfo[] mSelfMethods; + private FieldInfo[] mSelfFields; + private AttributeInfo[] mSelfAttributes; + private MethodInfo[] mMethods; + private FieldInfo[] mFields; + private TypeInfo[] mTypeParameters; + private MethodInfo[] mHiddenMethods; + private int mHidden = -1; + private int mCheckLevel = -1; + private String mReasonIncluded; + private MethodInfo[] mNonWrittenConstructors; + private boolean mIsDeprecated; +} diff --git a/tools/droiddoc/src/ClearPage.java b/tools/droiddoc/src/ClearPage.java new file mode 100644 index 0000000..2a8fced --- /dev/null +++ b/tools/droiddoc/src/ClearPage.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import com.sun.javadoc.*; +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.ArrayList; + +public class ClearPage +{ + /* + public ClearPage() + { + String templ = "templates/index.cs"; + String filename = "docs/index.html"; + + data.setValue("A.B.C", "1"); + data.setValue("A.B.D", "2"); + } + */ + + public static ArrayList<String> hdfFiles = new ArrayList<String>(); + + private static ArrayList<String> mTemplateDirs = new ArrayList<String>(); + private static boolean mTemplateDirSet = false; + + public static String outputDir = "docs"; + public static String htmlDir = null; + public static String toroot = null; + + public static void addTemplateDir(String dir) + { + mTemplateDirSet = true; + mTemplateDirs.add(dir); + + File hdfFile = new File(dir, "data.hdf"); + if (hdfFile.canRead()) { + hdfFiles.add(hdfFile.getPath()); + } + } + + private static int countSlashes(String s) + { + final int N = s.length(); + int slashcount = 0; + for (int i=0; i<N; i++) { + if (s.charAt(i) == '/') { + slashcount++; + } + } + return slashcount; + } + + public static void write(HDF data, String templ, String filename) + { + write(data, templ, filename, false); + } + + public static void write(HDF data, String templ, String filename, boolean fullPath) + { + if (htmlDir != null) { + data.setValue("hasindex", "true"); + } + + String toroot; + if (ClearPage.toroot != null) { + toroot = ClearPage.toroot; + } else { + int slashcount = countSlashes(filename); + if (slashcount > 0) { + toroot = ""; + for (int i=0; i<slashcount; i++) { + toroot += "../"; + } + } else { + toroot = "./"; + } + } + data.setValue("toroot", toroot); + + data.setValue("filename", filename); + + if (!fullPath) { + filename = outputDir + "/" + filename; + } + + int i=0; + if (htmlDir != null) { + data.setValue("hdf.loadpaths." + i, htmlDir); + i++; + } + if (mTemplateDirSet) { + for (String dir: mTemplateDirs) { + data.setValue("hdf.loadpaths." + i, dir); + i++; + } + } else { + data.setValue("hdf.loadpaths." + i, "templates"); + } + + CS cs = new CS(data); + cs.parseFile(templ); + + File file = new File(outputFilename(filename)); + + ensureDirectory(file); + + OutputStreamWriter stream = null; + try { + stream = new OutputStreamWriter( + new FileOutputStream(file)); + String rendered = cs.render(); + stream.write(rendered, 0, rendered.length()); + } + catch (IOException e) { + System.out.println("error: " + e.getMessage() + "; when writing file: " + filename); + } + finally { + if (stream != null) { + try { + stream.close(); + } + catch (IOException e) { + } + } + } + } + + // recursively create the directories to the output + public static void ensureDirectory(File f) + { + File parent = f.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + } + + public static void copyFile(File from, String toPath) + { + File to = new File(outputDir + "/" + toPath); + FileInputStream in; + FileOutputStream out; + try { + if (!from.exists()) { + throw new IOException(); + } + in = new FileInputStream(from); + } + catch (IOException e) { + System.err.println(from.getAbsolutePath() + ": Error opening file"); + return ; + } + ensureDirectory(to); + try { + out = new FileOutputStream(to); + } + catch (IOException e) { + System.err.println(from.getAbsolutePath() + ": Error opening file"); + return ; + } + + long sizel = from.length(); + final int maxsize = 64*1024; + int size = sizel > maxsize ? maxsize : (int)sizel; + byte[] buf = new byte[size]; + while (true) { + try { + size = in.read(buf); + } + catch (IOException e) { + System.err.println(from.getAbsolutePath() + + ": error reading file"); + break; + } + if (size > 0) { + try { + out.write(buf, 0, size); + } + catch (IOException e) { + System.err.println(from.getAbsolutePath() + + ": error writing file"); + } + } else { + break; + } + } + try { + in.close(); + } + catch (IOException e) { + } + try { + out.close(); + } + catch (IOException e) { + } + } + + /** Takes a string that ends w/ .html and changes the .html to htmlExtension */ + public static String outputFilename(String htmlFile) { + if (!DroidDoc.htmlExtension.equals(".html") && htmlFile.endsWith(".html")) { + return htmlFile.substring(0, htmlFile.length()-5) + DroidDoc.htmlExtension; + } else { + return htmlFile; + } + } + +} diff --git a/tools/droiddoc/src/Comment.java b/tools/droiddoc/src/Comment.java new file mode 100644 index 0000000..3a24357 --- /dev/null +++ b/tools/droiddoc/src/Comment.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; + +public class Comment +{ + static final Pattern LEADING_WHITESPACE = Pattern.compile( + "^[ \t\n\r]*(.*)$", + Pattern.DOTALL); + + static final Pattern TAG_BEGIN = Pattern.compile( + "[\r\n][\r\n \t]*@", + Pattern.DOTALL); + + static final Pattern TAG = Pattern.compile( + "(@[^ \t\r\n]+)[ \t\r\n]+(.*)", + Pattern.DOTALL); + + static final Pattern INLINE_TAG = Pattern.compile( + "(.*?)\\{(@[^ \t\r\n\\}]+)[ \t\r\n]*(.*?)\\}", + Pattern.DOTALL); + + static final Pattern FIRST_SENTENCE = Pattern.compile( + "((.*?)\\.)[ \t\r\n\\<](.*)", + Pattern.DOTALL); + + private static final String[] KNOWN_TAGS = new String[] { + "@author", + "@since", + "@version", + "@deprecated", + "@undeprecate", + "@docRoot", + "@inheritDoc", + "@more", + "@code", + "@samplecode", + "@sample", + "@include", + "@serial", + "@com.intel.drl.spec_ref", + "@ar.org.fitc.spec_ref", + }; + + public Comment(String text, ContainerInfo base, SourcePositionInfo sp) + { + mText = text; + mBase = base; + // sp now points to the end of the text, not the beginning! + mPosition = SourcePositionInfo.findBeginning(sp, text); + } + + private void parseRegex(String text) + { + Matcher m; + + m = LEADING_WHITESPACE.matcher(text); + m.matches(); + text = m.group(1); + + m = TAG_BEGIN.matcher(text); + + int start = 0; + int end = 0; + while (m.find()) { + end = m.start(); + + tag(text, start, end); + + start = m.end()-1; // -1 is the @ + } + end = text.length(); + tag(text, start, end); + } + + private void tag(String text, int start, int end) + { + SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, start); + + if (start >= 0 && end > 0 && (end-start) > 0) { + text = text.substring(start, end); + + Matcher m = TAG.matcher(text); + if (m.matches()) { + // out of line tag + tag(m.group(1), m.group(2), false, pos); + } else { + // look for inline tags + m = INLINE_TAG.matcher(text); + start = 0; + while (m.find()) { + String str = m.group(1); + String tagname = m.group(2); + String tagvalue = m.group(3); + tag(null, m.group(1), true, pos); + tag(tagname, tagvalue, true, pos); + start = m.end(); + } + int len = text.length(); + if (start != len) { + tag(null, text.substring(start), true, pos); + } + } + } + } + + private void tag(String name, String text, boolean isInline, SourcePositionInfo pos) + { + /* + String s = isInline ? "inline" : "outofline"; + System.out.println("---> " + s + + " name=[" + name + "] text=[" + text + "]"); + */ + if (name == null) { + mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos)); + } + else if (name.equals("@param")) { + mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos)); + } + else if (name.equals("@see")) { + mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos)); + } + else if (name.equals("@link") || name.equals("@linkplain")) { + mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos)); + } + else if (name.equals("@throws") || name.equals("@exception")) { + mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos)); + } + else if (name.equals("@return")) { + mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos)); + } + else if (name.equals("@deprecated")) { + if (text.length() == 0) { + Errors.error(Errors.MISSING_COMMENT, pos, + "@deprecated tag with no explanatory comment"); + text = "No replacement."; + } + mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos)); + } + else if (name.equals("@literal")) { + mInlineTagsList.add(new LiteralTagInfo(name, name, text, pos)); + } + else if (name.equals("@hide") || name.equals("@doconly")) { + // nothing + } + else if (name.equals("@attr")) { + AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos); + mAttrTagsList.add(tag); + Comment c = tag.description(); + if (c != null) { + for (TagInfo t: c.tags()) { + mInlineTagsList.add(t); + } + } + } + else if (name.equals("@undeprecate")) { + mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos)); + } + else if (name.equals("@include") || name.equals("@sample")) { + mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos)); + } + else { + boolean known = false; + for (String s: KNOWN_TAGS) { + if (s.equals(name)) { + known = true; + break; + } + } + if (!known) { + Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos), + "Unknown tag: " + name); + } + TagInfo t = new TextTagInfo(name, name, text, pos); + if (isInline) { + mInlineTagsList.add(t); + } else { + mTagsList.add(t); + } + } + } + + private void parseBriefTags() + { + int N = mInlineTagsList.size(); + + // look for "@more" tag, which means that we might go past the first sentence. + int more = -1; + for (int i=0; i<N; i++) { + if (mInlineTagsList.get(i).name().equals("@more")) { + more = i; + } + } + if (more >= 0) { + for (int i=0; i<more; i++) { + mBriefTagsList.add(mInlineTagsList.get(i)); + } + } else { + for (int i=0; i<N; i++) { + TagInfo t = mInlineTagsList.get(i); + if (t.name().equals("Text")) { + Matcher m = FIRST_SENTENCE.matcher(t.text()); + if (m.matches()) { + String text = m.group(1); + TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position()); + mBriefTagsList.add(firstSentenceTag); + break; + } + } + mBriefTagsList.add(t); + + } + } + } + + public TagInfo[] tags() + { + init(); + return mInlineTags; + } + + public TagInfo[] tags(String name) + { + init(); + ArrayList<TagInfo> results = new ArrayList<TagInfo>(); + int N = mInlineTagsList.size(); + for (int i=0; i<N; i++) { + TagInfo t = mInlineTagsList.get(i); + if (t.name().equals(name)) { + results.add(t); + } + } + return results.toArray(new TagInfo[results.size()]); + } + + public ParamTagInfo[] paramTags() + { + init(); + return mParamTags; + } + + public SeeTagInfo[] seeTags() + { + init(); + return mSeeTags; + } + + public ThrowsTagInfo[] throwsTags() + { + init(); + return mThrowsTags; + } + + public TagInfo[] returnTags() + { + init(); + return mReturnTags; + } + + public TagInfo[] deprecatedTags() + { + init(); + return mDeprecatedTags; + } + + public TagInfo[] undeprecateTags() + { + init(); + return mUndeprecateTags; + } + + public AttrTagInfo[] attrTags() + { + init(); + return mAttrTags; + } + + public TagInfo[] briefTags() + { + init(); + return mBriefTags; + } + + public boolean isHidden() + { + if (mHidden >= 0) { + return mHidden != 0; + } else { + if (DroidDoc.checkLevel(DroidDoc.SHOW_HIDDEN)) { + mHidden = 0; + return false; + } + boolean b = mText.indexOf("@hide") >= 0; + mHidden = b ? 1 : 0; + return b; + } + } + + public boolean isDocOnly() { + if (mDocOnly >= 0) { + return mDocOnly != 0; + } else { + boolean b = (mText != null) && (mText.indexOf("@doconly") >= 0); + mDocOnly = b ? 1 : 0; + return b; + } + } + + private void init() + { + if (!mInitialized) { + initImpl(); + } + } + + private void initImpl() + { + isHidden(); + isDocOnly(); + parseRegex(mText); + parseBriefTags(); + mText = null; + mInitialized = true; + + mInlineTags = mInlineTagsList.toArray(new TagInfo[mInlineTagsList.size()]); + mParamTags = mParamTagsList.toArray(new ParamTagInfo[mParamTagsList.size()]); + mSeeTags = mSeeTagsList.toArray(new SeeTagInfo[mSeeTagsList.size()]); + mThrowsTags = mThrowsTagsList.toArray(new ThrowsTagInfo[mThrowsTagsList.size()]); + mReturnTags = ParsedTagInfo.joinTags(mReturnTagsList.toArray( + new ParsedTagInfo[mReturnTagsList.size()])); + mDeprecatedTags = ParsedTagInfo.joinTags(mDeprecatedTagsList.toArray( + new ParsedTagInfo[mDeprecatedTagsList.size()])); + mUndeprecateTags = mUndeprecateTagsList.toArray(new TagInfo[mUndeprecateTagsList.size()]); + mAttrTags = mAttrTagsList.toArray(new AttrTagInfo[mAttrTagsList.size()]); + mBriefTags = mBriefTagsList.toArray(new TagInfo[mBriefTagsList.size()]); + + mParamTagsList = null; + mSeeTagsList = null; + mThrowsTagsList = null; + mReturnTagsList = null; + mDeprecatedTagsList = null; + mUndeprecateTagsList = null; + mAttrTagsList = null; + mBriefTagsList = null; + } + + boolean mInitialized; + int mHidden = -1; + int mDocOnly = -1; + String mText; + ContainerInfo mBase; + SourcePositionInfo mPosition; + int mLine = 1; + + TagInfo[] mInlineTags; + TagInfo[] mTags; + ParamTagInfo[] mParamTags; + SeeTagInfo[] mSeeTags; + ThrowsTagInfo[] mThrowsTags; + TagInfo[] mBriefTags; + TagInfo[] mReturnTags; + TagInfo[] mDeprecatedTags; + TagInfo[] mUndeprecateTags; + AttrTagInfo[] mAttrTags; + + ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>(); + ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>(); + ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>(); + ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>(); + ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>(); + ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>(); + ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>(); + ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>(); + ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>(); + ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>(); + + +} diff --git a/tools/droiddoc/src/ContainerInfo.java b/tools/droiddoc/src/ContainerInfo.java new file mode 100644 index 0000000..b8a3e10 --- /dev/null +++ b/tools/droiddoc/src/ContainerInfo.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public interface ContainerInfo +{ + public String qualifiedName(); + public boolean checkLevel(); +} diff --git a/tools/droiddoc/src/Converter.java b/tools/droiddoc/src/Converter.java new file mode 100644 index 0000000..4014f7f --- /dev/null +++ b/tools/droiddoc/src/Converter.java @@ -0,0 +1,744 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import com.sun.javadoc.*; +import com.sun.tools.doclets.*; +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class Converter +{ + private static RootDoc root; + + public static void makeInfo(RootDoc r) + { + root = r; + + int N, i; + + // create the objects + ClassDoc[] classDocs = r.classes(); + N = classDocs.length; + for (i=0; i<N; i++) { + Converter.obtainClass(classDocs[i]); + } + ArrayList<ClassInfo> classesNeedingInit2 = new ArrayList<ClassInfo>(); + // 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(r.classes()); + } + + private static ClassInfo[] mRootClasses; + public static ClassInfo[] rootClasses() + { + return mRootClasses; + } + + public static ClassInfo[] allClasses() { + return (ClassInfo[])mClasses.all(); + } + + private static void initClass(ClassDoc c, ClassInfo cl) + { + MethodDoc[] annotationElements; + if (c instanceof AnnotationTypeDoc) { + annotationElements = ((AnnotationTypeDoc)c).elements(); + } else { + annotationElements = new MethodDoc[0]; + } + cl.init(Converter.obtainType(c), + Converter.convertClasses(c.interfaces()), + Converter.convertTypes(c.interfaceTypes()), + Converter.convertClasses(c.innerClasses()), + Converter.convertMethods(c.constructors(false)), + Converter.convertMethods(c.methods(false)), + Converter.convertMethods(annotationElements), + Converter.convertFields(c.fields(false)), + Converter.convertFields(c.enumConstants()), + Converter.obtainPackage(c.containingPackage()), + Converter.obtainClass(c.containingClass()), + Converter.obtainClass(c.superclass()), + Converter.obtainType(c.superclassType()), + Converter.convertAnnotationInstances(c.annotations()) + ); + cl.setHiddenMethods(Converter.getHiddenMethods(c.methods(false))); + cl.setNonWrittenConstructors(Converter.convertNonWrittenConstructors(c.constructors(false))); + cl.init3(Converter.convertTypes(c.typeParameters()), 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 = new TagInfo[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) + { + if (p == null) return null; + ParameterInfo pi = new ParameterInfo(p.name(), p.typeName(), + Converter.obtainType(p.type()), + Converter.convertSourcePosition(pos)); + return pi; + } + + private static ParameterInfo[] convertParameters(Parameter[] p, MemberDoc m) + { + SourcePosition pos = m.position(); + int len = p.length; + ParameterInfo[] q = new ParameterInfo[len]; + for (int i=0; i<len; i++) { + q[i] = Converter.convertParameter(p[i], pos); + } + 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<ClassNeedingInit> mClassesNeedingInit + = new ArrayList<ClassNeedingInit>(); + + static ClassInfo obtainClass(ClassDoc o) + { + return (ClassInfo)mClasses.obtain(o); + } + private static Cache mClasses = new Cache() + { + 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; + } + protected void made(Object o, Object r) + { + if (mClassesNeedingInit == null) { + initClass((ClassDoc)o, (ClassInfo)r); + ((ClassInfo)r).init2(); + } + } + ClassInfo[] all() + { + return (ClassInfo[])mCache.values().toArray(new ClassInfo[mCache.size()]); + } + }; + + private static MethodInfo[] getHiddenMethods(MethodDoc[] methods){ + if (methods == null) return null; + ArrayList<MethodInfo> out = new ArrayList<MethodInfo>(); + int N = methods.length; + for (int i=0; i<N; i++) { + MethodInfo m = Converter.obtainMethod(methods[i]); + //System.out.println(m.toString() + ": "); + //for (TypeInfo ti : m.getTypeParameters()){ + // if (ti.asClassInfo() != null){ + //System.out.println(" " +ti.asClassInfo().toString()); + //} else { + //System.out.println(" null"); + //} + //} + if (m.isHidden()) { + out.add(m); + } + } + return out.toArray(new MethodInfo[out.size()]); + } + + /** + * Convert MethodDoc[] 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(MethodDoc[] methods) + { + if (methods == null) return null; + ArrayList<MethodInfo> out = new ArrayList<MethodInfo>(); + int N = methods.length; + for (int i=0; i<N; i++) { + MethodInfo m = Converter.obtainMethod(methods[i]); + //System.out.println(m.toString() + ": "); + //for (TypeInfo ti : m.getTypeParameters()){ + // if (ti.asClassInfo() != null){ + //System.out.println(" " +ti.asClassInfo().toString()); + //} else { + //System.out.println(" null"); + //} + //} + if (m.checkLevel()) { + out.add(m); + } + } + return out.toArray(new MethodInfo[out.size()]); + } + + private static MethodInfo[] convertMethods(ConstructorDoc[] methods) + { + if (methods == null) return null; + ArrayList<MethodInfo> out = new ArrayList<MethodInfo>(); + int N = methods.length; + for (int i=0; i<N; i++) { + MethodInfo m = Converter.obtainMethod(methods[i]); + if (m.checkLevel()) { + out.add(m); + } + } + return out.toArray(new MethodInfo[out.size()]); + } + + private static MethodInfo[] convertNonWrittenConstructors(ConstructorDoc[] methods) + { + if (methods == null) return null; + ArrayList<MethodInfo> out = new ArrayList<MethodInfo>(); + int N = methods.length; + for (int i=0; i<N; i++) { + MethodInfo m = Converter.obtainMethod(methods[i]); + if (!m.checkLevel()) { + out.add(m); + } + } + return out.toArray(new MethodInfo[out.size()]); + } + + private static MethodInfo obtainMethod(MethodDoc o) + { + return (MethodInfo)mMethods.obtain(o); + } + private static MethodInfo obtainMethod(ConstructorDoc o) + { + return (MethodInfo)mMethods.obtain(o); + } + private static Cache mMethods = new Cache() + { + protected Object make(Object o) + { + if (o instanceof AnnotationTypeElementDoc) { + AnnotationTypeElementDoc m = (AnnotationTypeElementDoc)o; + MethodInfo result = new MethodInfo( + m.getRawCommentText(), + 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(), true, + "annotationElement", + m.flatSignature(), + Converter.obtainMethod(m.overriddenMethod()), + Converter.obtainType(m.returnType()), + Converter.convertParameters(m.parameters(), m), + Converter.convertClasses(m.thrownExceptions()), + Converter.convertSourcePosition(m.position()), + 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(), + 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(), false, + "method", + m.flatSignature(), + Converter.obtainMethod(m.overriddenMethod()), + Converter.obtainType(m.returnType()), + Converter.convertParameters(m.parameters(), m), + Converter.convertClasses(m.thrownExceptions()), + Converter.convertSourcePosition(m.position()), + Converter.convertAnnotationInstances(m.annotations()) + ); + result.setVarargs(m.isVarArgs()); + result.init(null); + return result; + } + else { + ConstructorDoc m = (ConstructorDoc)o; + MethodInfo result = new MethodInfo( + m.getRawCommentText(), + 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(), + false, m.isSynchronized(), m.isNative(), false, + "constructor", + m.flatSignature(), + null, + null, + Converter.convertParameters(m.parameters(), m), + Converter.convertClasses(m.thrownExceptions()), + Converter.convertSourcePosition(m.position()), + 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<FieldInfo> out = new ArrayList<FieldInfo>(); + 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() + { + 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()), + Converter.convertAnnotationInstances(f.annotations()) + ); + } + }; + + private static PackageInfo obtainPackage(PackageDoc o) + { + return (PackageInfo)mPackagees.obtain(o); + } + private static Cache mPackagees = new Cache() + { + 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() + { + 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; + } + protected void made(Object o, Object r) + { + Type t = (Type)o; + TypeInfo ti = (TypeInfo)r; + if (t.asParameterizedType() != null) { + ti.setTypeArguments(Converter.convertTypes( + t.asParameterizedType().typeArguments())); + } + else if (t instanceof ClassDoc) { + ti.setTypeArguments(Converter.convertTypes(((ClassDoc)t).typeParameters())); + } + else if (t.asTypeVariable() != null) { + ti.setBounds(null, Converter.convertTypes((t.asTypeVariable().bounds()))); + ti.setIsTypeVariable(true); + } + else if (t.asWildcardType() != null) { + ti.setIsWildcard(true); + ti.setBounds(Converter.convertTypes(t.asWildcardType().superBounds()), + Converter.convertTypes(t.asWildcardType().extendsBounds())); + } + } + 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; + } + }; + + + + private static MemberInfo obtainMember(MemberDoc o) + { + return (MemberInfo)mMembers.obtain(o); + } + private static Cache mMembers = new Cache() + { + 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() + { + 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<Object,Object> mCache = new HashMap<Object,Object>(); + 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<AnnotationValue,AnnotationValueInfo> mAnnotationValues = new HashMap(); + private static HashSet<AnnotationValue> 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; + AnnotationValueInfo[] array = new AnnotationValueInfo[old.length]; + for (int i=0; i<array.length; i++) { + array[i] = Converter.obtainAnnotationValue(old[i], null); + } + converted = array; + } + else { + converted = orig; + } + v.init(converted); + } + + private static void finishAnnotationValueInit() + { + int depth = 0; + while (mAnnotationValuesNeedingInit.size() > 0) { + HashSet<AnnotationValue> set = mAnnotationValuesNeedingInit; + mAnnotationValuesNeedingInit = new HashSet(); + for (AnnotationValue o: set) { + AnnotationValueInfo v = mAnnotationValues.get(o); + initAnnotationValue(o, v); + } + depth++; + } + mAnnotationValuesNeedingInit = null; + } +} diff --git a/tools/droiddoc/src/DocFile.java b/tools/droiddoc/src/DocFile.java new file mode 100644 index 0000000..38ac55c --- /dev/null +++ b/tools/droiddoc/src/DocFile.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + + +public class DocFile +{ + private static final Pattern LINE = Pattern.compile("(.*)[\r]?\n", + Pattern.MULTILINE); + private static final Pattern PROP = Pattern.compile("([^=]+)=(.*)"); + + public static String readFile(String filename) + { + try { + File f = new File(filename); + int length = (int)f.length(); + FileReader reader = new FileReader(f); + char[] buf = new char[length]; + int index = 0; + int amt; + while (true) { + amt = reader.read(buf, index, length-index); + + if (amt < 1) { + break; + } + + index += amt; + } + return new String(buf, 0, index); + } + catch (IOException e) { + return null; + } + } + + public static void writePage(String docfile, String relative, + String outfile) + { + HDF hdf = DroidDoc.makeHDF(); + + /* + System.out.println("docfile='" + docfile + + "' relative='" + relative + "'" + + "' outfile='" + outfile + "'"); + */ + + String filedata = readFile(docfile); + + // The document is properties up until the line "@jd:body". + // Any blank lines are ignored. + int start = -1; + int lineno = 1; + Matcher lines = LINE.matcher(filedata); + String line = null; + while (lines.find()) { + line = lines.group(1); + if (line.length() > 0) { + if (line.equals("@jd:body")) { + start = lines.end(); + break; + } + Matcher prop = PROP.matcher(line); + if (prop.matches()) { + String key = prop.group(1); + String value = prop.group(2); + hdf.setValue(key, value); + } else { + break; + } + } + lineno++; + } + if (start < 0) { + System.err.println(docfile + ":" + lineno + ": error parsing docfile"); + if (line != null) { + System.err.println(docfile + ":" + lineno + ":" + line); + } + System.exit(1); + } + + // if they asked to only be for a certain template, maybe skip it + String fromTemplate = hdf.getValue("template.which", ""); + String fromPage = hdf.getValue("page.onlyfortemplate", ""); + if (!"".equals(fromPage) && !fromTemplate.equals(fromPage)) { + return; + } + + // and the actual text after that + String commentText = filedata.substring(start); + + Comment comment = new Comment(commentText, null, + new SourcePositionInfo(docfile, lineno, 1)); + TagInfo[] tags = comment.tags(); + + TagInfo.makeHDF(hdf, "root.descr", tags); + + hdf.setValue("commentText", commentText); + + if(outfile.indexOf("sdk/") != -1) { + hdf.setValue("sdk", "true"); + if(outfile.indexOf("index.html") != -1) { + ClearPage.write(hdf, "sdkpage.cs", outfile); + }else{ + ClearPage.write(hdf, "docpage.cs", outfile); + } + }else if(outfile.indexOf("guide/") != -1){ + hdf.setValue("guide", "true"); + ClearPage.write(hdf, "docpage.cs", outfile); + }else if(outfile.indexOf("publish/") != -1){ + hdf.setValue("publish", "true"); + ClearPage.write(hdf, "docpage.cs", outfile); + }else{ + ClearPage.write(hdf, "nosidenavpage.cs", outfile); + } + } + +} diff --git a/tools/droiddoc/src/DocInfo.java b/tools/droiddoc/src/DocInfo.java new file mode 100644 index 0000000..2530dc2 --- /dev/null +++ b/tools/droiddoc/src/DocInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public abstract class DocInfo +{ + public DocInfo(String rawCommentText, SourcePositionInfo sp) + { + mRawCommentText = rawCommentText; + mPosition = sp; + } + + public boolean isHidden() + { + return comment().isHidden(); + } + + public boolean isDocOnly() { + return comment().isDocOnly(); + } + + public String getRawCommentText() + { + return mRawCommentText; + } + + public Comment comment() + { + if (mComment == null) { + mComment = new Comment(mRawCommentText, parent(), mPosition); + } + return mComment; + } + + public SourcePositionInfo position() + { + return mPosition; + } + + public abstract ContainerInfo parent(); + + private String mRawCommentText; + Comment mComment; + SourcePositionInfo mPosition; +} + diff --git a/tools/droiddoc/src/DroidDoc.java b/tools/droiddoc/src/DroidDoc.java new file mode 100644 index 0000000..b0412c9 --- /dev/null +++ b/tools/droiddoc/src/DroidDoc.java @@ -0,0 +1,1326 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import com.sun.javadoc.*; + +import org.clearsilver.HDF; + +import java.util.*; +import java.io.*; +import java.lang.reflect.Proxy; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class DroidDoc +{ + private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant"; + private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION = "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION"; + private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION = "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION"; + private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION = "android.annotation.SdkConstant.SdkConstantType.SERVICE_INTENT_ACTION"; + private static final String SDK_CONSTANT_TYPE_CATEGORY = "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY"; + private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget"; + private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout"; + + private static final int TYPE_NONE = 0; + private static final int TYPE_WIDGET = 1; + private static final int TYPE_LAYOUT = 2; + private static final int TYPE_LAYOUT_PARAM = 3; + + public static final int SHOW_PUBLIC = 0x00000001; + public static final int SHOW_PROTECTED = 0x00000003; + public static final int SHOW_PACKAGE = 0x00000007; + public static final int SHOW_PRIVATE = 0x0000000f; + public static final int SHOW_HIDDEN = 0x0000001f; + + public static int showLevel = SHOW_PROTECTED; + + public static final String javadocDir = "reference/"; + public static String htmlExtension; + + public static RootDoc root; + public static ArrayList<String[]> mHDFData = new ArrayList<String[]>(); + public static Map<Character,String> escapeChars = new HashMap<Character,String>(); + public static String title = ""; + + public static boolean checkLevel(int level) + { + return (showLevel & level) == level; + } + + public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, + boolean priv, boolean hidden) + { + int level = 0; + if (hidden && !checkLevel(SHOW_HIDDEN)) { + return false; + } + if (pub && checkLevel(SHOW_PUBLIC)) { + return true; + } + if (prot && checkLevel(SHOW_PROTECTED)) { + return true; + } + if (pkgp && checkLevel(SHOW_PACKAGE)) { + return true; + } + if (priv && checkLevel(SHOW_PRIVATE)) { + return true; + } + return false; + } + + public static boolean start(RootDoc r) + { + String keepListFile = null; + String proofreadFile = null; + String todoFile = null; + String sdkValuePath = null; + ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>(); + String stubsDir = null; + //Create the dependency graph for the stubs directory + boolean apiXML = false; + String apiFile = null; + String debugStubsFile = ""; + HashSet<String> stubPackages = null; + + root = r; + + String[][] options = r.options(); + for (String[] a: options) { + if (a[0].equals("-d")) { + ClearPage.outputDir = a[1]; + } + else if (a[0].equals("-templatedir")) { + ClearPage.addTemplateDir(a[1]); + } + else if (a[0].equals("-hdf")) { + mHDFData.add(new String[] {a[1], a[2]}); + } + else if (a[0].equals("-toroot")) { + ClearPage.toroot = a[1]; + } + else if (a[0].equals("-samplecode")) { + sampleCodes.add(new SampleCode(a[1], a[2], a[3])); + } + else if (a[0].equals("-htmldir")) { + ClearPage.htmlDir = a[1]; + } + else if (a[0].equals("-title")) { + DroidDoc.title = a[1]; + } + else if (a[0].equals("-werror")) { + Errors.setWarningsAreErrors(true); + } + else if (a[0].equals("-error") || a[0].equals("-warning") + || a[0].equals("-hide")) { + try { + int level = -1; + if (a[0].equals("-error")) { + level = Errors.ERROR; + } + else if (a[0].equals("-warning")) { + level = Errors.WARNING; + } + else if (a[0].equals("-hide")) { + level = Errors.HIDDEN; + } + Errors.setErrorLevel(Integer.parseInt(a[1]), level); + } + catch (NumberFormatException e) { + // already printed below + return false; + } + } + else if (a[0].equals("-keeplist")) { + keepListFile = a[1]; + } + else if (a[0].equals("-proofread")) { + proofreadFile = a[1]; + } + else if (a[0].equals("-todo")) { + todoFile = a[1]; + } + else if (a[0].equals("-public")) { + showLevel = SHOW_PUBLIC; + } + else if (a[0].equals("-protected")) { + showLevel = SHOW_PROTECTED; + } + else if (a[0].equals("-package")) { + showLevel = SHOW_PACKAGE; + } + else if (a[0].equals("-private")) { + showLevel = SHOW_PRIVATE; + } + else if (a[0].equals("-hidden")) { + showLevel = SHOW_HIDDEN; + } + else if (a[0].equals("-stubs")) { + stubsDir = a[1]; + } + else if (a[0].equals("-stubpackages")) { + stubPackages = new HashSet(); + for (String pkg: a[1].split(":")) { + stubPackages.add(pkg); + } + } + else if (a[0].equals("-sdkvalues")) { + sdkValuePath = a[1]; + } + else if (a[0].equals("-apixml")) { + apiXML = true; + apiFile = a[1]; + } + } + + // read some prefs from the template + if (!readTemplateSettings()) { + return false; + } + + // Set up the data structures + Converter.makeInfo(r); + + // Files for proofreading + if (proofreadFile != null) { + Proofread.initProofread(proofreadFile); + } + if (todoFile != null) { + TodoFile.writeTodoFile(todoFile); + } + + // HTML Pages + if (ClearPage.htmlDir != null) { + writeHTMLPages(); + } + + // Packages Pages + writePackages(javadocDir + + (ClearPage.htmlDir!=null + ? "packages" + htmlExtension + : "index" + htmlExtension)); + + // Classes + writeClassLists(); + writeClasses(); + writeHierarchy(); + // writeKeywords(); + + // Lists for JavaScript + writeLists(); + if (keepListFile != null) { + writeKeepList(keepListFile); + } + + // Sample Code + for (SampleCode sc: sampleCodes) { + sc.write(); + } + + // Index page + writeIndex(); + + Proofread.finishProofread(proofreadFile); + + // Stubs + if (stubsDir != null) { + Stubs.writeStubs(stubsDir, apiXML, apiFile, stubPackages); + } + + if (sdkValuePath != null) { + writeSdkValues(sdkValuePath); + } + + Errors.printErrors(); + return !Errors.hadError; + } + + private static void writeIndex() { + HDF data = makeHDF(); + ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension); + } + + private static boolean readTemplateSettings() + { + HDF data = makeHDF(); + htmlExtension = data.getValue("template.extension", ".html"); + int i=0; + while (true) { + String k = data.getValue("template.escape." + i + ".key", ""); + String v = data.getValue("template.escape." + i + ".value", ""); + if ("".equals(k)) { + break; + } + if (k.length() != 1) { + System.err.println("template.escape." + i + ".key must have a length of 1: " + k); + return false; + } + escapeChars.put(k.charAt(0), v); + i++; + } + return true; + } + + public static String escape(String s) { + if (escapeChars.size() == 0) { + return s; + } + StringBuffer b = null; + int begin = 0; + final int N = s.length(); + for (int i=0; i<N; i++) { + char c = s.charAt(i); + String mapped = escapeChars.get(c); + if (mapped != null) { + if (b == null) { + b = new StringBuffer(s.length() + mapped.length()); + } + if (begin != i) { + b.append(s.substring(begin, i)); + } + b.append(mapped); + begin = i+1; + } + } + if (b != null) { + if (begin != N) { + b.append(s.substring(begin, N)); + } + return b.toString(); + } + return s; + } + + public static void setPageTitle(HDF data, String title) + { + String s = title; + if (DroidDoc.title.length() > 0) { + s += " - " + DroidDoc.title; + } + data.setValue("page.title", s); + } + + public static LanguageVersion languageVersion() + { + return LanguageVersion.JAVA_1_5; + } + + public static int optionLength(String option) + { + if (option.equals("-d")) { + return 2; + } + if (option.equals("-templatedir")) { + return 2; + } + if (option.equals("-hdf")) { + return 3; + } + if (option.equals("-toroot")) { + return 2; + } + if (option.equals("-samplecode")) { + return 4; + } + if (option.equals("-htmldir")) { + return 2; + } + if (option.equals("-title")) { + return 2; + } + if (option.equals("-werror")) { + return 1; + } + if (option.equals("-hide")) { + return 2; + } + if (option.equals("-warning")) { + return 2; + } + if (option.equals("-error")) { + return 2; + } + if (option.equals("-keeplist")) { + return 2; + } + if (option.equals("-proofread")) { + return 2; + } + if (option.equals("-todo")) { + return 2; + } + if (option.equals("-public")) { + return 1; + } + if (option.equals("-protected")) { + return 1; + } + if (option.equals("-package")) { + return 1; + } + if (option.equals("-private")) { + return 1; + } + if (option.equals("-hidden")) { + return 1; + } + if (option.equals("-stubs")) { + return 2; + } + if (option.equals("-stubpackages")) { + return 2; + } + if (option.equals("-sdkvalues")) { + return 2; + } + if (option.equals("-apixml")) { + return 2; + } + return 0; + } + + public static boolean validOptions(String[][] options, DocErrorReporter r) + { + for (String[] a: options) { + if (a[0].equals("-error") || a[0].equals("-warning") + || a[0].equals("-hide")) { + try { + Integer.parseInt(a[1]); + } + catch (NumberFormatException e) { + r.printError("bad -" + a[0] + " value must be a number: " + + a[1]); + return false; + } + } + } + + return true; + } + + public static HDF makeHDF() + { + HDF data = new HDF(); + + for (String[] p: mHDFData) { + data.setValue(p[0], p[1]); + } + + try { + for (String p: ClearPage.hdfFiles) { + data.readFile(p); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + + return data; + } + + public static HDF makePackageHDF() + { + HDF data = makeHDF(); + ClassInfo[] classes = Converter.rootClasses(); + + SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); + for (ClassInfo cl: classes) { + PackageInfo pkg = cl.containingPackage(); + String name; + if (pkg == null) { + name = ""; + } else { + name = pkg.name(); + } + sorted.put(name, pkg); + } + + int i = 0; + for (String s: sorted.keySet()) { + PackageInfo pkg = sorted.get(s); + + if (pkg.isHidden()) { + continue; + } + Boolean allHidden = true; + int pass = 1; + ClassInfo[] classesToCheck = pkg.ordinaryClasses(); + while (pass < 5 ) { + switch(pass) { + case 1: + classesToCheck = pkg.enums(); + break; + case 2: + classesToCheck = pkg.errors(); + break; + case 3: + classesToCheck = pkg.exceptions(); + break; + case 4: + classesToCheck = pkg.interfaces(); + break; + default: + System.out.println("Error reading package: " + pkg.name()); + break; + } + for (ClassInfo cl : classesToCheck) { + if (!cl.isHidden()) { + allHidden = false; + break; + } + } + if (!allHidden) { + break; + } + pass++; + } + if (allHidden) { + continue; + } + + data.setValue("reference", "true"); + data.setValue("docs.packages." + i + ".name", s); + data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); + TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", + pkg.firstSentenceTags()); + i++; + } + + return data; + } + + public static void writeDirectory(File dir, String relative) + { + File[] files = dir.listFiles(); + int i, count = files.length; + for (i=0; i<count; i++) { + File f = files[i]; + if (f.isFile()) { + String templ = relative + f.getName(); + int len = templ.length(); + if (len > 3 && ".cs".equals(templ.substring(len-3))) { + HDF data = makeHDF(); + String filename = templ.substring(0,len-3) + htmlExtension; + System.out.println("Writing CS: " + filename); + ClearPage.write(data, templ, filename); + } + else if (len > 3 && ".jd".equals(templ.substring(len-3))) { + String filename = templ.substring(0,len-3) + htmlExtension; + System.out.println("Writing JD: " + filename); + DocFile.writePage(f.getAbsolutePath(), relative, filename); + } + else { +// System.out.println("relative=" + relative +// + " f.getAbsolutePath()=" + f.getAbsolutePath() +// + " templ=" + templ); + System.out.println("Copying: " + templ); + ClearPage.copyFile(f, templ); + } + } + else if (f.isDirectory()) { + System.out.println("Writing dir: " + relative + f.getName() + "/"); + writeDirectory(f, relative + f.getName() + "/"); + } + } + } + + public static void writeHTMLPages() + { + File f = new File(ClearPage.htmlDir); + if (!f.isDirectory()) { + System.out.println("htmlDir not a directory: " + ClearPage.htmlDir); + } + writeDirectory(f, ""); + } + + public static void writeLists() + { + HDF data = makeHDF(); + + ClassInfo[] classes = Converter.rootClasses(); + + SortedMap<String, Object> sorted = new TreeMap<String, Object>(); + for (ClassInfo cl: classes) { + if (cl.isHidden()) { + continue; + } + sorted.put(cl.qualifiedName(), cl); + PackageInfo pkg = cl.containingPackage(); + String name; + if (pkg == null) { + name = ""; + } else { + name = pkg.name(); + } + sorted.put(name, pkg); + } + + int i = 0; + for (String s: sorted.keySet()) { + data.setValue("docs.pages." + i + ".id" , ""+i); + data.setValue("docs.pages." + i + ".label" , s); + + Object o = sorted.get(s); + if (o instanceof PackageInfo) { + PackageInfo pkg = (PackageInfo)o; + data.setValue("docs.pages." + i + ".link" , pkg.htmlPage()); + data.setValue("docs.pages." + i + ".type" , "package"); + } + else if (o instanceof ClassInfo) { + ClassInfo cl = (ClassInfo)o; + data.setValue("docs.pages." + i + ".link" , cl.htmlPage()); + data.setValue("docs.pages." + i + ".type" , "class"); + } + i++; + } + + ClearPage.write(data, "lists.cs", javadocDir + "lists.js"); + } + + public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) { + if (!notStrippable.add(cl)) { + // slight optimization: if it already contains cl, it already contains + // all of cl's parents + return; + } + ClassInfo supr = cl.superclass(); + if (supr != null) { + cantStripThis(supr, notStrippable); + } + for (ClassInfo iface: cl.interfaces()) { + cantStripThis(iface, notStrippable); + } + } + + private static String getPrintableName(ClassInfo cl) { + ClassInfo containingClass = cl.containingClass(); + if (containingClass != null) { + // This is an inner class. + String baseName = cl.name(); + baseName = baseName.substring(baseName.lastIndexOf('.') + 1); + return getPrintableName(containingClass) + '$' + baseName; + } + return cl.qualifiedName(); + } + + /** + * Writes the list of classes that must be present in order to + * provide the non-hidden APIs known to javadoc. + * + * @param filename the path to the file to write the list to + */ + public static void writeKeepList(String filename) { + HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>(); + ClassInfo[] all = Converter.allClasses(); + Arrays.sort(all); // just to make the file a little more readable + + // If a class is public and not hidden, then it and everything it derives + // from cannot be stripped. Otherwise we can strip it. + for (ClassInfo cl: all) { + if (cl.isPublic() && !cl.isHidden()) { + cantStripThis(cl, notStrippable); + } + } + PrintStream stream = null; + try { + stream = new PrintStream(filename); + for (ClassInfo cl: notStrippable) { + stream.println(getPrintableName(cl)); + } + } + catch (FileNotFoundException e) { + System.out.println("error writing file: " + filename); + } + finally { + if (stream != null) { + stream.close(); + } + } + } + + public static void writePackages(String filename) + { + System.out.println("Writing packages..."); + HDF data = makePackageHDF(); + + ClassInfo[] classes = Converter.rootClasses(); + + SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); + for (ClassInfo cl: classes) { + PackageInfo pkg = cl.containingPackage(); + String name; + if (pkg == null) { + name = ""; + } else { + name = pkg.name(); + } + sorted.put(name, pkg); + } + + int i = 0; + for (String s: sorted.keySet()) { + PackageInfo pkg = sorted.get(s); + + if (pkg.isHidden()) { + continue; + } + Boolean allHidden = true; + int pass = 1; + ClassInfo[] classesToCheck = pkg.ordinaryClasses(); + while (pass < 5 ) { + switch(pass) { + case 1: + classesToCheck = pkg.enums(); + break; + case 2: + classesToCheck = pkg.errors(); + break; + case 3: + classesToCheck = pkg.exceptions(); + break; + case 4: + classesToCheck = pkg.interfaces(); + break; + default: + System.out.println("Error reading package: " + pkg.name()); + break; + } + for (ClassInfo cl : classesToCheck) { + if (!cl.isHidden()) { + allHidden = false; + break; + } + } + if (!allHidden) { + break; + } + pass++; + } + if (allHidden) { + continue; + } + + writePackage(pkg); + + i++; + } + + setPageTitle(data, "Package Index"); + + TagInfo.makeHDF(data, "root.descr", + Converter.convertTags(root.inlineTags(), null)); + + ClearPage.write(data, "packages.cs", filename); + ClearPage.write(data, "package-list.cs", javadocDir + "package-list"); + + Proofread.writePackages(filename, + Converter.convertTags(root.inlineTags(), null)); + } + + public static void writePackage(PackageInfo pkg) + { + // these this and the description are in the same directory, + // so it's okay + HDF data = makePackageHDF(); + + String name = pkg.name(); + System.out.println("Writing " + name); + + data.setValue("package.name", name); + data.setValue("package.descr", "...description..."); + + makeClassListHDF(data, "package.interfaces", + ClassInfo.sortByName(pkg.interfaces())); + makeClassListHDF(data, "package.classes", + ClassInfo.sortByName(pkg.ordinaryClasses())); + makeClassListHDF(data, "package.enums", + ClassInfo.sortByName(pkg.enums())); + makeClassListHDF(data, "package.exceptions", + ClassInfo.sortByName(pkg.exceptions())); + makeClassListHDF(data, "package.errors", + ClassInfo.sortByName(pkg.errors())); + TagInfo.makeHDF(data, "package.shortDescr", + pkg.firstSentenceTags()); + TagInfo.makeHDF(data, "package.descr", pkg.inlineTags()); + + String filename = pkg.htmlPage(); + setPageTitle(data, name); + ClearPage.write(data, "package.cs", filename); + + filename = filename.substring(0, filename.lastIndexOf('/')+1) + + "package-descr" + htmlExtension; + setPageTitle(data, name + " Details"); + ClearPage.write(data, "package-descr.cs", filename); + + Proofread.writePackage(filename, pkg.inlineTags()); + } + + public static void writeClassLists() + { + int i; + HDF data = makePackageHDF(); + + ClassInfo[] classes = PackageInfo.filterHidden( + Converter.convertClasses(root.classes())); + if (classes.length == 0) { + return ; + } + + Sorter[] sorted = new Sorter[classes.length]; + for (i=0; i<sorted.length; i++) { + ClassInfo cl = classes[i]; + String name = cl.name(); + sorted[i] = new Sorter(name, cl); + } + + Arrays.sort(sorted); + + // make a pass and resolve ones that have the same name + int firstMatch = 0; + String lastName = sorted[0].label; + for (i=1; i<sorted.length; i++) { + String s = sorted[i].label; + if (!lastName.equals(s)) { + if (firstMatch != i-1) { + // there were duplicates + for (int j=firstMatch; j<i; j++) { + PackageInfo pkg = ((ClassInfo)sorted[j].data).containingPackage(); + if (pkg != null) { + sorted[j].label = sorted[j].label + " (" + pkg.name() + ")"; + } + } + } else { + //System.out.println("not duplicate: " + sorted[i].label); + } + firstMatch = i; + lastName = s; + } + } + + // and sort again + Arrays.sort(sorted); + + for (i=0; i<sorted.length; i++) { + String s = sorted[i].label; + ClassInfo cl = (ClassInfo)sorted[i].data; + char first = Character.toUpperCase(s.charAt(0)); + cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i); + } + + setPageTitle(data, "Class Index"); + ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension); + } + + // we use the word keywords because "index" means something else in html land + // the user only ever sees the word index +/* public static void writeKeywords() + { + ArrayList<KeywordEntry> keywords = new ArrayList<KeywordEntry>(); + + ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes())); + + for (ClassInfo cl: classes) { + cl.makeKeywordEntries(keywords); + } + + HDF data = makePackageHDF(); + + Collections.sort(keywords); + + int i=0; + for (KeywordEntry entry: keywords) { + String base = "keywords." + entry.firstChar() + "." + i; + entry.makeHDF(data, base); + i++; + } + + setPageTitle(data, "Index"); + ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + htmlExtension); + } */ + + public static void writeHierarchy() + { + ClassInfo[] classes = Converter.rootClasses(); + ArrayList<ClassInfo> info = new ArrayList<ClassInfo>(); + for (ClassInfo cl: classes) { + if (!cl.isHidden()) { + info.add(cl); + } + } + HDF data = makePackageHDF(); + Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()])); + setPageTitle(data, "Class Hierarchy"); + ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension); + } + + public static void writeClasses() + { + ClassInfo[] classes = Converter.rootClasses(); + + for (ClassInfo cl: classes) { + HDF data = makePackageHDF(); + if (!cl.isHidden()) { + writeClass(cl, data); + } + } + } + + public static void writeClass(ClassInfo cl, HDF data) + { + cl.makeHDF(data); + + System.out.println("Writing " + cl.name()); + setPageTitle(data, cl.name()); + ClearPage.write(data, "class.cs", cl.htmlPage()); + + Proofread.writeClass(cl.htmlPage(), cl); + } + + public static void makeClassListHDF(HDF data, String base, + ClassInfo[] classes) + { + for (int i=0; i<classes.length; i++) { + ClassInfo cl = classes[i]; + if (!cl.isHidden()) { + cl.makeShortDescrHDF(data, base + "." + i); + } + } + } + + public static String linkTarget(String source, String target) + { + String[] src = source.split("/"); + String[] tgt = target.split("/"); + + int srclen = src.length; + int tgtlen = tgt.length; + + int same = 0; + while (same < (srclen-1) + && same < (tgtlen-1) + && (src[same].equals(tgt[same]))) { + same++; + } + + String s = ""; + + int up = srclen-same-1; + for (int i=0; i<up; i++) { + s += "../"; + } + + + int N = tgtlen-1; + for (int i=same; i<N; i++) { + s += tgt[i] + '/'; + } + s += tgt[tgtlen-1]; + + return s; + } + + /** + * Returns true if the given element has an @hide annotation. + */ + private static boolean hasHideAnnotation(Doc doc) { + return doc.getRawCommentText().indexOf("@hide") != -1; + } + + /** + * Returns true if the given element is hidden. + */ + private static boolean isHidden(Doc doc) { + // Methods, fields, constructors. + if (doc instanceof MemberDoc) { + return hasHideAnnotation(doc); + } + + // Classes, interfaces, enums, annotation types. + if (doc instanceof ClassDoc) { + ClassDoc classDoc = (ClassDoc) doc; + + // Check the containing package. + if (hasHideAnnotation(classDoc.containingPackage())) { + return true; + } + + // Check the class doc and containing class docs if this is a + // nested class. + ClassDoc current = classDoc; + do { + if (hasHideAnnotation(current)) { + return true; + } + + current = current.containingClass(); + } while (current != null); + } + + return false; + } + + /** + * Filters out hidden elements. + */ + private static Object filterHidden(Object o, Class<?> expected) { + if (o == null) { + return null; + } + + Class type = o.getClass(); + if (type.getName().startsWith("com.sun.")) { + // TODO: Implement interfaces from superclasses, too. + return Proxy.newProxyInstance(type.getClassLoader(), + type.getInterfaces(), new HideHandler(o)); + } else if (o instanceof Object[]) { + Class<?> componentType = expected.getComponentType(); + Object[] array = (Object[]) o; + List<Object> list = new ArrayList<Object>(array.length); + for (Object entry : array) { + if ((entry instanceof Doc) && isHidden((Doc) entry)) { + continue; + } + list.add(filterHidden(entry, componentType)); + } + return list.toArray( + (Object[]) Array.newInstance(componentType, list.size())); + } else { + return o; + } + } + + /** + * Filters hidden elements out of method return values. + */ + private static class HideHandler implements InvocationHandler { + + private final Object target; + + public HideHandler(Object target) { + this.target = target; + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + String methodName = method.getName(); + if (args != null) { + if (methodName.equals("compareTo") || + methodName.equals("equals") || + methodName.equals("overrides") || + methodName.equals("subclassOf")) { + args[0] = unwrap(args[0]); + } + } + + if (methodName.equals("getRawCommentText")) { + return filterComment((String) method.invoke(target, args)); + } + + // escape "&" in disjunctive types. + if (proxy instanceof Type && methodName.equals("toString")) { + return ((String) method.invoke(target, args)) + .replace("&", "&"); + } + + try { + return filterHidden(method.invoke(target, args), + method.getReturnType()); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + private String filterComment(String s) { + if (s == null) { + return null; + } + + s = s.trim(); + + // Work around off by one error + while (s.length() >= 5 + && s.charAt(s.length() - 5) == '{') { + s += " "; + } + + return s; + } + + private static Object unwrap(Object proxy) { + if (proxy instanceof Proxy) + return ((HideHandler)Proxy.getInvocationHandler(proxy)).target; + return proxy; + } + } + + public static String scope(Scoped scoped) { + if (scoped.isPublic()) { + return "public"; + } + else if (scoped.isProtected()) { + return "protected"; + } + else if (scoped.isPackagePrivate()) { + return ""; + } + else if (scoped.isPrivate()) { + return "private"; + } + else { + throw new RuntimeException("invalid scope for object " + scoped); + } + } + + /** + * Collect the values used by the Dev tools and write them in files packaged with the SDK + * @param output the ouput directory for the files. + */ + private static void writeSdkValues(String output) { + ArrayList<String> activityActions = new ArrayList<String>(); + ArrayList<String> broadcastActions = new ArrayList<String>(); + ArrayList<String> serviceActions = new ArrayList<String>(); + ArrayList<String> categories = new ArrayList<String>(); + + ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>(); + ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>(); + ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>(); + + ClassInfo[] classes = Converter.allClasses(); + + // Go through all the fields of all the classes, looking SDK stuff. + for (ClassInfo clazz : classes) { + + // first check constant fields for the SdkConstant annotation. + FieldInfo[] fields = clazz.allSelfFields(); + for (FieldInfo field : fields) { + Object cValue = field.constantValue(); + if (cValue != null) { + AnnotationInstanceInfo[] annotations = field.annotations(); + if (annotations.length > 0) { + for (AnnotationInstanceInfo annotation : annotations) { + if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) { + AnnotationValueInfo[] values = annotation.elementValues(); + if (values.length > 0) { + String type = values[0].valueString(); + if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) { + activityActions.add(cValue.toString()); + } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) { + broadcastActions.add(cValue.toString()); + } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) { + serviceActions.add(cValue.toString()); + } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) { + categories.add(cValue.toString()); + } + } + break; + } + } + } + } + } + + // Now check the class for @Widget or if its in the android.widget package + // (unless the class is hidden or abstract, or non public) + if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) { + boolean annotated = false; + AnnotationInstanceInfo[] annotations = clazz.annotations(); + if (annotations.length > 0) { + for (AnnotationInstanceInfo annotation : annotations) { + if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) { + widgets.add(clazz); + annotated = true; + break; + } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) { + layouts.add(clazz); + annotated = true; + break; + } + } + } + + if (annotated == false) { + // lets check if this is inside android.widget + PackageInfo pckg = clazz.containingPackage(); + String packageName = pckg.name(); + if ("android.widget".equals(packageName) || + "android.view".equals(packageName)) { + // now we check what this class inherits either from android.view.ViewGroup + // or android.view.View, or android.view.ViewGroup.LayoutParams + int type = checkInheritance(clazz); + switch (type) { + case TYPE_WIDGET: + widgets.add(clazz); + break; + case TYPE_LAYOUT: + layouts.add(clazz); + break; + case TYPE_LAYOUT_PARAM: + layoutParams.add(clazz); + break; + } + } + } + } + } + + // now write the files, whether or not the list are empty. + // the SDK built requires those files to be present. + + Collections.sort(activityActions); + writeValues(output + "/activity_actions.txt", activityActions); + + Collections.sort(broadcastActions); + writeValues(output + "/broadcast_actions.txt", broadcastActions); + + Collections.sort(serviceActions); + writeValues(output + "/service_actions.txt", serviceActions); + + Collections.sort(categories); + writeValues(output + "/categories.txt", categories); + + // before writing the list of classes, we do some checks, to make sure the layout params + // are enclosed by a layout class (and not one that has been declared as a widget) + for (int i = 0 ; i < layoutParams.size();) { + ClassInfo layoutParamClass = layoutParams.get(i); + ClassInfo containingClass = layoutParamClass.containingClass(); + if (containingClass == null || layouts.indexOf(containingClass) == -1) { + layoutParams.remove(i); + } else { + i++; + } + } + + writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams); + } + + /** + * Writes a list of values into a text files. + * @param pathname the absolute os path of the output file. + * @param values the list of values to write. + */ + private static void writeValues(String pathname, ArrayList<String> values) { + FileWriter fw = null; + BufferedWriter bw = null; + try { + fw = new FileWriter(pathname, false); + bw = new BufferedWriter(fw); + + for (String value : values) { + bw.append(value).append('\n'); + } + } catch (IOException e) { + // pass for now + } finally { + try { + if (bw != null) bw.close(); + } catch (IOException e) { + // pass for now + } + try { + if (fw != null) fw.close(); + } catch (IOException e) { + // pass for now + } + } + } + + /** + * Writes the widget/layout/layout param classes into a text files. + * @param pathname the absolute os path of the output file. + * @param widgets the list of widget classes to write. + * @param layouts the list of layout classes to write. + * @param layoutParams the list of layout param classes to write. + */ + private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets, + ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) { + FileWriter fw = null; + BufferedWriter bw = null; + try { + fw = new FileWriter(pathname, false); + bw = new BufferedWriter(fw); + + // write the 3 types of classes. + for (ClassInfo clazz : widgets) { + writeClass(bw, clazz, 'W'); + } + for (ClassInfo clazz : layoutParams) { + writeClass(bw, clazz, 'P'); + } + for (ClassInfo clazz : layouts) { + writeClass(bw, clazz, 'L'); + } + } catch (IOException e) { + // pass for now + } finally { + try { + if (bw != null) bw.close(); + } catch (IOException e) { + // pass for now + } + try { + if (fw != null) fw.close(); + } catch (IOException e) { + // pass for now + } + } + } + + /** + * Writes a class name and its super class names into a {@link BufferedWriter}. + * @param writer the BufferedWriter to write into + * @param clazz the class to write + * @param prefix the prefix to put at the beginning of the line. + * @throws IOException + */ + private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix) + throws IOException { + writer.append(prefix).append(clazz.qualifiedName()); + ClassInfo superClass = clazz; + while ((superClass = superClass.superclass()) != null) { + writer.append(' ').append(superClass.qualifiedName()); + } + writer.append('\n'); + } + + /** + * Checks the inheritance of {@link ClassInfo} objects. This method return + * <ul> + * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li> + * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li> + * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends <code>android.view.ViewGroup$LayoutParams</code></li> + * <li>{@link #TYPE_NONE}: in all other cases</li> + * </ul> + * @param clazz the {@link ClassInfo} to check. + */ + private static int checkInheritance(ClassInfo clazz) { + if ("android.view.ViewGroup".equals(clazz.qualifiedName())) { + return TYPE_LAYOUT; + } else if ("android.view.View".equals(clazz.qualifiedName())) { + return TYPE_WIDGET; + } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) { + return TYPE_LAYOUT_PARAM; + } + + ClassInfo parent = clazz.superclass(); + if (parent != null) { + return checkInheritance(parent); + } + + return TYPE_NONE; + } +} diff --git a/tools/droiddoc/src/Errors.java b/tools/droiddoc/src/Errors.java new file mode 100644 index 0000000..1431314 --- /dev/null +++ b/tools/droiddoc/src/Errors.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.SortedSet; +import java.util.TreeSet; + +public class Errors +{ + public static boolean hadError = false; + private static boolean warningsAreErrors = false; + private static TreeSet<Message> allErrors = new TreeSet<Message>(); + + private static class Message implements Comparable { + SourcePositionInfo pos; + String msg; + + Message(SourcePositionInfo p, String m) { + pos = p; + msg = m; + } + + public int compareTo(Object o) { + Message that = (Message)o; + int r = this.pos.compareTo(that.pos); + if (r != 0) return r; + return this.msg.compareTo(that.msg); + } + + public String toString() { + String whereText = this.pos == null ? "unknown: " : this.pos.toString() + ':'; + return whereText + this.msg; + } + } + + public static void error(Error error, SourcePositionInfo where, String text) { + if (error.level == HIDDEN) { + return; + } + + String which = (!warningsAreErrors && error.level == WARNING) ? " warning " : " error "; + String message = which + error.code + ": " + text; + + if (where == null) { + where = new SourcePositionInfo("unknown", 0, 0); + } + + allErrors.add(new Message(where, message)); + + if (error.level == ERROR || (warningsAreErrors && error.level == WARNING)) { + hadError = true; + } + } + + public static void printErrors() { + for (Message m: allErrors) { + System.err.println(m.toString()); + } + } + + public static int HIDDEN = 0; + public static int WARNING = 1; + public static int ERROR = 2; + + public static void setWarningsAreErrors(boolean val) { + warningsAreErrors = val; + } + + public static class Error { + public int code; + public int level; + + public Error(int code, int level) + { + this.code = code; + this.level = level; + } + } + + public static Error UNRESOLVED_LINK = new Error(1, WARNING); + public static Error BAD_INCLUDE_TAG = new Error(2, WARNING); + public static Error UNKNOWN_TAG = new Error(3, WARNING); + public static Error UNKNOWN_PARAM_TAG_NAME = new Error(4, WARNING); + public static Error UNDOCUMENTED_PARAMETER = new Error(5, HIDDEN); + public static Error BAD_ATTR_TAG = new Error(6, ERROR); + public static Error BAD_INHERITDOC = new Error(7, HIDDEN); + public static Error HIDDEN_LINK = new Error(8, WARNING); + public static Error HIDDEN_CONSTRUCTOR = new Error(9, WARNING); + public static Error UNAVAILABLE_SYMBOL = new Error(10, ERROR); + public static Error HIDDEN_SUPERCLASS = new Error(11, WARNING); + public static Error DEPRECATED = new Error(12, HIDDEN); + public static Error DEPRECATION_MISMATCH = new Error(13, WARNING); + public static Error MISSING_COMMENT = new Error(14, WARNING); + public static Error IO_ERROR = new Error(15, HIDDEN); + + public static Error[] ERRORS = { + UNRESOLVED_LINK, + BAD_INCLUDE_TAG, + UNKNOWN_TAG, + UNKNOWN_PARAM_TAG_NAME, + UNDOCUMENTED_PARAMETER, + BAD_ATTR_TAG, + BAD_INHERITDOC, + HIDDEN_LINK, + HIDDEN_CONSTRUCTOR, + UNAVAILABLE_SYMBOL, + HIDDEN_SUPERCLASS, + DEPRECATED, + IO_ERROR, + }; + + public static boolean setErrorLevel(int code, int level) { + for (Error e: ERRORS) { + if (e.code == code) { + e.level = level; + return true; + } + } + return false; + } +} diff --git a/tools/droiddoc/src/FieldInfo.java b/tools/droiddoc/src/FieldInfo.java new file mode 100644 index 0000000..536d798 --- /dev/null +++ b/tools/droiddoc/src/FieldInfo.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; + +import java.util.Comparator; + +public class FieldInfo extends MemberInfo +{ + public static final Comparator<FieldInfo> comparator = new Comparator<FieldInfo>() { + public int compare(FieldInfo a, FieldInfo b) { + return a.name().compareTo(b.name()); + } + }; + + public FieldInfo(String name, ClassInfo containingClass, ClassInfo realContainingClass, + boolean isPublic, boolean isProtected, + boolean isPackagePrivate, boolean isPrivate, + boolean isFinal, boolean isStatic, boolean isTransient, boolean isVolatile, + boolean isSynthetic, TypeInfo type, String rawCommentText, + Object constantValue, + SourcePositionInfo position, + AnnotationInstanceInfo[] annotations) + { + super(rawCommentText, name, null, containingClass, realContainingClass, + isPublic, isProtected, isPackagePrivate, isPrivate, + isFinal, isStatic, isSynthetic, chooseKind(isFinal, isStatic), position, + annotations); + mIsTransient = isTransient; + mIsVolatile = isVolatile; + mType = type; + mConstantValue = constantValue; + } + + public FieldInfo cloneForClass(ClassInfo newContainingClass) { + return new FieldInfo(name(), newContainingClass, realContainingClass(), + isPublic(), isProtected(), isPackagePrivate(), + isPrivate(), isFinal(), isStatic(), isTransient(), isVolatile(), + isSynthetic(), mType, getRawCommentText(), mConstantValue, position(), + annotations()); + } + + static String chooseKind(boolean isFinal, boolean isStatic) + { + if (isStatic && isFinal) { + return "constant"; + } else { + return "field"; + } + } + + public TypeInfo type() + { + return mType; + } + + public boolean isConstant() + { + return isStatic() && isFinal(); + } + + public TagInfo[] firstSentenceTags() + { + return comment().briefTags(); + } + + public TagInfo[] inlineTags() + { + return comment().tags(); + } + + public Object constantValue() + { + return mConstantValue; + } + + public String constantLiteralValue() + { + return constantLiteralValue(mConstantValue); + } + + public boolean isDeprecated() { + boolean deprecated = false; + if (!mDeprecatedKnown) { + boolean commentDeprecated = (comment().deprecatedTags().length > 0); + boolean annotationDeprecated = false; + for (AnnotationInstanceInfo annotation : annotations()) { + if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) { + annotationDeprecated = true; + break; + } + } + + if (commentDeprecated != annotationDeprecated) { + Errors.error(Errors.DEPRECATION_MISMATCH, position(), + "Field " + mContainingClass.qualifiedName() + "." + name() + + ": @Deprecated annotation and @deprecated comment do not match"); + } + + mIsDeprecated = commentDeprecated | annotationDeprecated; + mDeprecatedKnown = true; + } + return mIsDeprecated; + } + + public static String constantLiteralValue(Object val) + { + String str = null; + if (val != null) { + if (val instanceof Boolean + || val instanceof Byte + || val instanceof Short + || val instanceof Integer) + { + str = val.toString(); + } + //catch all special values + else if (val instanceof Double){ + Double dbl = (Double) val; + if (dbl.toString().equals("Infinity")){ + str = "(1.0 / 0.0)"; + } else if (dbl.toString().equals("-Infinity")) { + str = "(-1.0 / 0.0)"; + } else if (dbl.isNaN()) { + str = "(0.0 / 0.0)"; + } else { + str = dbl.toString(); + } + } + else if (val instanceof Long) { + str = val.toString() + "L"; + } + else if (val instanceof Float) { + Float fl = (Float) val; + if (fl.toString().equals("Infinity")) { + str = "(1.0f / 0.0f)"; + } else if (fl.toString().equals("-Infinity")) { + str = "(-1.0f / 0.0f)"; + } else if (fl.isNaN()) { + str = "(0.0f / 0.0f)"; + } else { + str = val.toString() + "f"; + } + } + else if (val instanceof Character) { + str = String.format("\'\\u%04x\'", val); + } + else if (val instanceof String) { + str = "\"" + javaEscapeString((String)val) + "\""; + } + else { + str = "<<<<" +val.toString() + ">>>>"; + } + } + if (str == null) { + str = "null"; + } + return str; + } + + public static String javaEscapeString(String str) { + String result = ""; + final int N = str.length(); + for (int i=0; i<N; i++) { + char c = str.charAt(i); + if (c == '\\') { + result += "\\\\"; + } + else if (c == '\t') { + result += "\\t"; + } + else if (c == '\b') { + result += "\\b"; + } + else if (c == '\r') { + result += "\\r"; + } + else if (c == '\n') { + result += "\\n"; + } + else if (c == '\f') { + result += "\\f"; + } + else if (c == '\'') { + result += "\\'"; + } + else if (c == '\"') { + result += "\\\""; + } + else if (c >= ' ' && c <= '~') { + result += c; + } + else { + result += String.format("\\u%04x", new Integer((int)c)); + } + } + return result; + } + + + public void makeHDF(HDF data, String base) + { + data.setValue(base + ".kind", kind()); + type().makeHDF(data, base + ".type"); + data.setValue(base + ".name", name()); + data.setValue(base + ".href", htmlPage()); + data.setValue(base + ".anchor", anchor()); + TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); + TagInfo.makeHDF(data, base + ".descr", inlineTags()); + TagInfo.makeHDF(data, base + ".deprecated", comment().deprecatedTags()); + TagInfo.makeHDF(data, base + ".seeAlso", comment().seeTags()); + data.setValue(base + ".final", isFinal() ? "final" : ""); + data.setValue(base + ".static", isStatic() ? "static" : ""); + if (isPublic()) { + data.setValue(base + ".scope", "public"); + } + else if (isProtected()) { + data.setValue(base + ".scope", "protected"); + } + else if (isPackagePrivate()) { + data.setValue(base + ".scope", ""); + } + else if (isPrivate()) { + data.setValue(base + ".scope", "private"); + } + Object val = mConstantValue; + if (val != null) { + String dec = null; + String hex = null; + String str = null; + + if (val instanceof Boolean) { + str = ((Boolean)val).toString(); + } + else if (val instanceof Byte) { + dec = String.format("%d", val); + hex = String.format("0x%02x", val); + } + else if (val instanceof Character) { + dec = String.format("\'%c\'", val); + hex = String.format("0x%04x", val); + } + else if (val instanceof Double) { + str = ((Double)val).toString(); + } + else if (val instanceof Float) { + str = ((Float)val).toString(); + } + else if (val instanceof Integer) { + dec = String.format("%d", val); + hex = String.format("0x%08x", val); + } + else if (val instanceof Long) { + dec = String.format("%d", val); + hex = String.format("0x%016x", val); + } + else if (val instanceof Short) { + dec = String.format("%d", val); + hex = String.format("0x%04x", val); + } + else if (val instanceof String) { + str = "\"" + ((String)val) + "\""; + } + else { + str = ""; + } + + if (dec != null && hex != null) { + data.setValue(base + ".constantValue.dec", DroidDoc.escape(dec)); + data.setValue(base + ".constantValue.hex", DroidDoc.escape(hex)); + } + else { + data.setValue(base + ".constantValue.str", DroidDoc.escape(str)); + data.setValue(base + ".constantValue.isString", "1"); + } + } + } + + public boolean isExecutable() + { + return false; + } + + public boolean isTransient() + { + return mIsTransient; + } + + public boolean isVolatile() + { + return mIsVolatile; + } + + boolean mIsTransient; + boolean mIsVolatile; + boolean mDeprecatedKnown; + boolean mIsDeprecated; + TypeInfo mType; + Object mConstantValue; +} + diff --git a/tools/droiddoc/src/Hierarchy.java b/tools/droiddoc/src/Hierarchy.java new file mode 100755 index 0000000..ac5e1dc --- /dev/null +++ b/tools/droiddoc/src/Hierarchy.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.HashMap; +import java.util.TreeSet; +import java.util.Set; +import org.clearsilver.HDF; + +public class Hierarchy +{ + public static void makeHierarchy(HDF hdf, ClassInfo[] classes) + { + HashMap<String,TreeSet<String>> nodes + = new HashMap<String,TreeSet<String>>(); + + for (ClassInfo cl: classes) { + String name = cl.qualifiedName(); + + TreeSet<String> me = nodes.get(name); + if (me == null) { + me = new TreeSet<String>(); + nodes.put(name, me); + } + + ClassInfo superclass = cl.superclass(); + String sname = superclass != null + ? superclass.qualifiedName() : null; + if (sname != null) { + TreeSet<String> s = nodes.get(sname); + if (s == null) { + s = new TreeSet<String>(); + nodes.put(sname, s); + } + s.add(name); + } + } + + /* + Set<String> keys = nodes.keySet(); + for (String n: keys) { + System.out.println("class: " + n); + + TreeSet<String> values = nodes.get(n); + for (String v: values) { + System.out.println(" - " + v); + } + } + */ + + int depth = depth(nodes, "java.lang.Object"); + + hdf.setValue("classes.0", ""); + hdf.setValue("colspan", "" + depth); + + recurse(nodes, "java.lang.Object", hdf.getObj("classes.0"),depth,depth); + + if (false) { + Set<String> keys = nodes.keySet(); + if (keys.size() > 0) { + System.err.println("The following classes are hidden but" + + " are superclasses of not-hidden classes"); + for (String n: keys) { + System.err.println(" " + n); + } + } + } + } + + private static int depth(HashMap<String,TreeSet<String>> nodes, + String name) + { + int d = 0; + TreeSet<String> derived = nodes.get(name); + if (derived != null && derived.size() > 0) { + for (String s: derived) { + int n = depth(nodes, s); + if (n > d) { + d = n; + } + } + } + return d + 1; + } + + private static boolean exists(ClassInfo cl) + { + return cl != null && !cl.isHidden() && cl.isIncluded(); + } + + private static void recurse(HashMap<String,TreeSet<String>> nodes, + String name, HDF hdf, + int totalDepth, int remainingDepth) + { + int i; + + hdf.setValue("indent", "" + (totalDepth-remainingDepth-1)); + hdf.setValue("colspan", "" + remainingDepth); + + ClassInfo cl = Converter.obtainClass(name); + + hdf.setValue("class.label", cl.name()); + hdf.setValue("class.qualified", cl.qualifiedName()); + if (cl.checkLevel()) { + hdf.setValue("class.link", cl.htmlPage()); + } + + if (exists(cl)) { + hdf.setValue("exists", "1"); + } + + i = 0; + for (ClassInfo iface: cl.interfaces()) { + hdf.setValue("interfaces." + i + ".class.label", iface.name()); + hdf.setValue("interfaces." + i + ".class.qualified", iface.qualifiedName()); + if (iface.checkLevel()) { + hdf.setValue("interfaces." + i + ".class.link", iface.htmlPage()); + } + if (exists(cl)) { + hdf.setValue("interfaces." + i + ".exists", "1"); + } + i++; + } + + TreeSet<String> derived = nodes.get(name); + if (derived != null && derived.size() > 0) { + hdf.setValue("derived", ""); + HDF children = hdf.getObj("derived"); + i = 0; + remainingDepth--; + for (String s: derived) { + String index = "" + i; + children.setValue(index, ""); + recurse(nodes, s, children.getObj(index), totalDepth, + remainingDepth); + i++; + } + } + + nodes.remove(name); + } +} + diff --git a/tools/droiddoc/src/InheritedTags.java b/tools/droiddoc/src/InheritedTags.java new file mode 100644 index 0000000..242170c --- /dev/null +++ b/tools/droiddoc/src/InheritedTags.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; + +public interface InheritedTags +{ + TagInfo[] tags(); + InheritedTags inherited(); +} + diff --git a/tools/droiddoc/src/KeywordEntry.java b/tools/droiddoc/src/KeywordEntry.java new file mode 100644 index 0000000..7e5e357 --- /dev/null +++ b/tools/droiddoc/src/KeywordEntry.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; + +class KeywordEntry implements Comparable +{ + KeywordEntry(String label, String href, String comment) + { + this.label = label; + this.href = href; + this.comment = comment; + } + + public void makeHDF(HDF data, String base) + { + data.setValue(base + ".label", this.label); + data.setValue(base + ".href", this.href); + data.setValue(base + ".comment", this.comment); + } + + public char firstChar() + { + return Character.toUpperCase(this.label.charAt(0)); + } + + public int compareTo(Object that) + { + return this.label.compareToIgnoreCase(((KeywordEntry)that).label); + } + + private String label; + private String href; + private String comment; +} + + diff --git a/tools/droiddoc/src/LinkReference.java b/tools/droiddoc/src/LinkReference.java new file mode 100644 index 0000000..fa821cf --- /dev/null +++ b/tools/droiddoc/src/LinkReference.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; + +/** + * Class that represents what you see in an link or see tag. This is + * factored out of SeeTagInfo so it can be used elsewhere (like AttrTagInfo). + */ +public class LinkReference { + + /** The original text. */ + public String text; + + /** The kind of this tag, if we have a new suggestion after parsing. */ + public String kind; + + /** The user visible text. */ + public String label; + + /** The link. */ + public String href; + + /** The {@link PackageInfo} if any. */ + public PackageInfo packageInfo; + + /** The {@link ClassInfo} if any. */ + public ClassInfo classInfo; + + /** The {@link MemberInfo} if any. */ + public MemberInfo memberInfo; + + /** The name of the referenced member PackageInfo} if any. */ + public String referencedMemberName; + + /** Set to true if everything is a-ok */ + public boolean good; + + /** + * regex pattern to use when matching explicit "<a href" reference text + */ + private static final Pattern HREF_PATTERN + = Pattern.compile("^<a href=\"([^\"]*)\">([^<]*)</a>[ \n\r\t]*$", + Pattern.CASE_INSENSITIVE); + + /** + * Parse and resolve a link string. + * + * @param text the original text + * @param base the class or whatever that this link is on + * @param pos the original position in the source document + * @return a new link reference. It always returns something. If there was an + * error, it logs it and fills in href and label with error text. + */ + public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos, + boolean printOnErrors) { + LinkReference result = new LinkReference(); + result.text = text; + + int index; + int len = text.length(); + int pairs = 0; + int pound = -1; + // split the string + done: { + for (index=0; index<len; index++) { + char c = text.charAt(index); + switch (c) + { + case '(': + pairs++; + break; + case '[': + pairs++; + break; + case ')': + pairs--; + break; + case ']': + pairs--; + break; + case ' ': + case '\t': + case '\r': + case '\n': + if (pairs == 0) { + break done; + } + break; + case '#': + if (pound < 0) { + pound = index; + } + break; + } + } + } + if (index == len && pairs != 0) { + Errors.error(Errors.UNRESOLVED_LINK, pos, + "unable to parse link/see tag: " + text.trim()); + return result; + } + + int linkend = index; + + for (; index<len; index++) { + char c = text.charAt(index); + if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { + break; + } + } + + result.label = text.substring(index); + + String ref; + String mem; + if (pound == 0) { + ref = null; + mem = text.substring(1, linkend); + } + else if (pound > 0) { + ref = text.substring(0, pound); + mem = text.substring(pound+1, linkend); + } + else { + ref = text.substring(0, linkend); + mem = null; + } + + // parse parameters, if any + String[] params = null; + String[] paramDimensions = null; + if (mem != null) { + index = mem.indexOf('('); + if (index > 0) { + ArrayList<String> paramList = new ArrayList<String>(); + ArrayList<String> paramDimensionList = new ArrayList<String>(); + len = mem.length(); + int start = index+1; + final int START = 0; + final int TYPE = 1; + final int NAME = 2; + int dimension = 0; + int arraypair = 0; + int state = START; + int typestart = 0; + int typeend = -1; + for (int i=start; i<len; i++) { + char c = mem.charAt(i); + switch (state) + { + case START: + if (c!=' ' && c!='\t' && c!='\r' && c!='\n') { + state = TYPE; + typestart = i; + } + break; + case TYPE: + if (c == '[') { + if (typeend < 0) { + typeend = i; + } + dimension++; + arraypair++; + } + else if (c == ']') { + arraypair--; + } + else if (c==' ' || c=='\t' || c=='\r' || c=='\n') { + if (typeend < 0) { + typeend = i; + } + } + else { + if (typeend >= 0 || c == ')' || c == ',') { + if (typeend < 0) { + typeend = i; + } + String s = mem.substring(typestart, typeend); + paramList.add(s); + s = ""; + for (int j=0; j<dimension; j++) { + s += "[]"; + } + paramDimensionList.add(s); + state = START; + typeend = -1; + dimension = 0; + if (c == ',' || c == ')') { + state = START; + } else { + state = NAME; + } + } + } + break; + case NAME: + if (c == ',' || c == ')') { + state = START; + } + break; + } + + } + params = paramList.toArray(new String[paramList.size()]); + paramDimensions = paramDimensionList.toArray(new String[paramList.size()]); + mem = mem.substring(0, index); + } + } + + ClassInfo cl = null; + if (base instanceof ClassInfo) { + cl = (ClassInfo)base; + } + + if (ref == null) { + // no class or package was provided, assume it's this class + if (cl != null) { + result.classInfo = cl; + } + } else { + // they provided something, maybe it's a class or a package + if (cl != null) { + result.classInfo = cl.extendedFindClass(ref); + if (result.classInfo == null) { + result.classInfo = cl.findClass(ref); + } + if (result.classInfo == null) { + result.classInfo = cl.findInnerClass(ref); + } + } + if (result.classInfo == null) { + result.classInfo = Converter.obtainClass(ref); + } + if (result.classInfo == null) { + result.packageInfo = Converter.obtainPackage(ref); + } + } + + if (result.classInfo != null && mem != null) { + // it's either a field or a method, prefer a field + if (params == null) { + FieldInfo field = result.classInfo.findField(mem); + // findField looks in containing classes, so it might actually + // be somewhere else; link to where it really is, not what they + // typed. + if (field != null) { + result.classInfo = field.containingClass(); + result.memberInfo = field; + } + } + if (result.memberInfo == null) { + MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions); + if (method != null) { + result.classInfo = method.containingClass(); + result.memberInfo = method; + } + } + } + + result.referencedMemberName = mem; + if (params != null) { + result.referencedMemberName = result.referencedMemberName + '('; + len = params.length; + if (len > 0) { + len--; + for (int i=0; i<len; i++) { + result.referencedMemberName = result.referencedMemberName + params[i] + + paramDimensions[i] + ", "; + } + result.referencedMemberName = result.referencedMemberName + params[len] + + paramDimensions[len]; + } + result.referencedMemberName = result.referencedMemberName + ")"; + } + + // debugging spew + if (false) { + result.label = result.label + "/" + ref + "/" + mem + '/'; + if (params != null) { + for (int i=0; i<params.length; i++) { + result.label += params[i] + "|"; + } + } + + FieldInfo f = (result.memberInfo instanceof FieldInfo) + ? (FieldInfo)result.memberInfo + : null; + MethodInfo m = (result.memberInfo instanceof MethodInfo) + ? (MethodInfo)result.memberInfo + : null; + result.label = result.label + + "/package=" + (result.packageInfo!=null?result.packageInfo.name():"") + + "/class=" + (result.classInfo!=null?result.classInfo.qualifiedName():"") + + "/field=" + (f!=null?f.name():"") + + "/method=" + (m!=null?m.name():""); + + } + + MethodInfo method = null; + boolean skipHref = false; + + if (result.memberInfo != null && result.memberInfo.isExecutable()) { + method = (MethodInfo)result.memberInfo; + } + + if (text.startsWith("\"")) { + // literal quoted reference (e.g., a book title) + result.label = text.substring(1); + skipHref = true; + if (!result.label.endsWith("\"")) { + Errors.error(Errors.UNRESOLVED_LINK, pos, + "unbalanced quoted link/see tag: " + text.trim()); + result.makeError(); + return result; + } + result.label = result.label.substring(0, result.label.length() - 1); + result.kind = "@seeJustLabel"; + } + else if (text.startsWith("<")) { + // explicit "<a href" form + Matcher matcher = HREF_PATTERN.matcher(text); + if (! matcher.matches()) { + Errors.error(Errors.UNRESOLVED_LINK, pos, + "invalid <a> link/see tag: " + text.trim()); + result.makeError(); + return result; + } + result.href = matcher.group(1); + result.label = matcher.group(2); + result.kind = "@seeHref"; + } + else if (result.packageInfo != null) { + result.href = result.packageInfo.htmlPage(); + if (result.label.length() == 0) { + result.href = result.packageInfo.htmlPage(); + result.label = result.packageInfo.name(); + } + } + else if (result.classInfo != null && result.referencedMemberName == null) { + // class reference + if (result.label.length() == 0) { + result.label = result.classInfo.name(); + } + result.href = result.classInfo.htmlPage(); + } + else if (result.memberInfo != null) { + // member reference + ClassInfo containing = result.memberInfo.containingClass(); + if (result.memberInfo.isExecutable()) { + if (result.referencedMemberName.indexOf('(') < 0) { + result.referencedMemberName += method.flatSignature(); + } + } + if (result.label.length() == 0) { + result.label = result.referencedMemberName; + } + result.href = containing.htmlPage() + '#' + result.memberInfo.anchor(); + } + + if (result.href == null && !skipHref) { + if (printOnErrors && (base == null || base.checkLevel())) { + Errors.error(Errors.UNRESOLVED_LINK, pos, + "Unresolved link/see tag: " + text.trim()); + } + result.makeError(); + } + else if (result.memberInfo != null && !result.memberInfo.checkLevel()) { + if (printOnErrors && (base == null || base.checkLevel())) { + Errors.error(Errors.HIDDEN_LINK, pos, + "Link to hidden member: " + text.trim()); + result.href = null; + } + result.kind = "@seeJustLabel"; + } + else if (result.classInfo != null && !result.classInfo.checkLevel()) { + if (printOnErrors && (base == null || base.checkLevel())) { + Errors.error(Errors.HIDDEN_LINK, pos, + "Link to hidden class: " + text.trim() + " label=" + result.label); + result.href = null; + } + result.kind = "@seeJustLabel"; + } + else if (result.packageInfo != null && !result.packageInfo.checkLevel()) { + if (printOnErrors && (base == null || base.checkLevel())) { + Errors.error(Errors.HIDDEN_LINK, pos, + "Link to hidden package: " + text.trim()); + result.href = null; + } + result.kind = "@seeJustLabel"; + } + + result.good = true; + + return result; + } + + public boolean checkLevel() { + if (memberInfo != null) { + return memberInfo.checkLevel(); + } + if (classInfo != null) { + return classInfo.checkLevel(); + } + if (packageInfo != null) { + return packageInfo.checkLevel(); + } + return false; + } + + /** turn this LinkReference into one with an error message */ + private void makeError() { + //this.href = "ERROR(" + this.text.trim() + ")"; + this.href = null; + if (this.label == null) { + this.label = ""; + } + this.label = "ERROR(" + this.label + "/" + text.trim() + ")"; + } + + /** private. **/ + private LinkReference() { + } +} diff --git a/tools/droiddoc/src/LiteralTagInfo.java b/tools/droiddoc/src/LiteralTagInfo.java new file mode 100644 index 0000000..b39490d --- /dev/null +++ b/tools/droiddoc/src/LiteralTagInfo.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public class LiteralTagInfo extends TagInfo +{ + private static String encode(String t) + { + t = t.replace("&", "&"); + t = t.replace("<", "<"); + t = t.replace(">", ">"); + return t; + } + + public LiteralTagInfo(String n, String k, String t, SourcePositionInfo sp) + { + super("Text", "Text", encode(t), sp); + } +} diff --git a/tools/droiddoc/src/MemberInfo.java b/tools/droiddoc/src/MemberInfo.java new file mode 100644 index 0000000..2a2572a --- /dev/null +++ b/tools/droiddoc/src/MemberInfo.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public abstract class MemberInfo extends DocInfo implements Comparable, Scoped +{ + public MemberInfo(String rawCommentText, String name, String signature, + ClassInfo containingClass, ClassInfo realContainingClass, + boolean isPublic, boolean isProtected, + boolean isPackagePrivate, boolean isPrivate, + boolean isFinal, boolean isStatic, boolean isSynthetic, + String kind, + SourcePositionInfo position, + AnnotationInstanceInfo[] annotations) + { + super(rawCommentText, position); + mName = name; + mSignature = signature; + mContainingClass = containingClass; + mRealContainingClass = realContainingClass; + mIsPublic = isPublic; + mIsProtected = isProtected; + mIsPackagePrivate = isPackagePrivate; + mIsPrivate = isPrivate; + mIsFinal = isFinal; + mIsStatic = isStatic; + mIsSynthetic = isSynthetic; + mKind = kind; + mAnnotations = annotations; + } + + public abstract boolean isExecutable(); + + public String anchor() + { + if (mSignature != null) { + return mName + mSignature; + } else { + return mName; + } + } + + public String htmlPage() { + return mContainingClass.htmlPage() + "#" + anchor(); + } + + public int compareTo(Object that) { + return this.htmlPage().compareTo(((MemberInfo)that).htmlPage()); + } + + public String name() + { + return mName; + } + + public String signature() + { + return mSignature; + } + + public ClassInfo realContainingClass() + { + return mRealContainingClass; + } + + public ClassInfo containingClass() + { + return mContainingClass; + } + + public boolean isPublic() + { + return mIsPublic; + } + + public boolean isProtected() + { + return mIsProtected; + } + + public boolean isPackagePrivate() + { + return mIsPackagePrivate; + } + + public boolean isPrivate() + { + return mIsPrivate; + } + + public boolean isStatic() + { + return mIsStatic; + } + + public boolean isFinal() + { + return mIsFinal; + } + + public boolean isSynthetic() + { + return mIsSynthetic; + } + + public ContainerInfo parent() + { + return mContainingClass; + } + + public boolean checkLevel() + { + return DroidDoc.checkLevel(mIsPublic, mIsProtected, + mIsPackagePrivate, mIsPrivate, isHidden()); + } + + public String kind() + { + return mKind; + } + + public AnnotationInstanceInfo[] annotations() + { + return mAnnotations; + } + + ClassInfo mContainingClass; + ClassInfo mRealContainingClass; + String mName; + String mSignature; + boolean mIsPublic; + boolean mIsProtected; + boolean mIsPackagePrivate; + boolean mIsPrivate; + boolean mIsFinal; + boolean mIsStatic; + boolean mIsSynthetic; + String mKind; + private AnnotationInstanceInfo[] mAnnotations; + +} + diff --git a/tools/droiddoc/src/MethodInfo.java b/tools/droiddoc/src/MethodInfo.java new file mode 100644 index 0000000..ed98378 --- /dev/null +++ b/tools/droiddoc/src/MethodInfo.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; + +public class MethodInfo extends MemberInfo +{ + public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() { + public int compare(MethodInfo a, MethodInfo b) { + return a.name().compareTo(b.name()); + } + }; + + private class InlineTags implements InheritedTags + { + public TagInfo[] tags() + { + return comment().tags(); + } + public InheritedTags inherited() + { + MethodInfo m = findOverriddenMethod(name(), signature()); + if (m != null) { + return m.inlineTags(); + } else { + return null; + } + } + } + + private static void addInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue) + { + for (ClassInfo i: ifaces) { + queue.add(i); + } + for (ClassInfo i: ifaces) { + addInterfaces(i.interfaces(), queue); + } + } + + // first looks for a superclass, and then does a breadth first search to + // find the least far away match + public MethodInfo findOverriddenMethod(String name, String signature) + { + if (mReturnType == null) { + // ctor + return null; + } + if (mOverriddenMethod != null) { + return mOverriddenMethod; + } + + ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); + addInterfaces(containingClass().interfaces(), queue); + for (ClassInfo iface: queue) { + for (MethodInfo me: iface.methods()) { + if (me.name().equals(name) + && me.signature().equals(signature) + && me.inlineTags().tags() != null + && me.inlineTags().tags().length > 0) { + return me; + } + } + } + return null; + } + + private static void addRealInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue) + { + for (ClassInfo i: ifaces) { + queue.add(i); + if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) { + queue.add(i.superclass()); + } + } + for (ClassInfo i: ifaces) { + addInterfaces(i.realInterfaces(), queue); + } + } + + public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) { + if (mReturnType == null) { + // ctor + return null; + } + if (mOverriddenMethod != null) { + return mOverriddenMethod; + } + + ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); + if (containingClass().realSuperclass() != null && + containingClass().realSuperclass().isAbstract()) { + queue.add(containingClass()); + } + addInterfaces(containingClass().realInterfaces(), queue); + for (ClassInfo iface: queue) { + for (MethodInfo me: iface.methods()) { + if (me.name().equals(name) + && me.signature().equals(signature) + && me.inlineTags().tags() != null + && me.inlineTags().tags().length > 0 + && notStrippable.contains(me.containingClass())) { + return me; + } + } + } + return null; + } + + public MethodInfo findSuperclassImplementation(HashSet notStrippable) { + if (mReturnType == null) { + // ctor + return null; + } + if (mOverriddenMethod != null) { + // Even if we're told outright that this was the overridden method, we want to + // be conservative and ignore mismatches of parameter types -- they arise from + // extending generic specializations, and we want to consider the derived-class + // method to be a non-override. + if (this.signature().equals(mOverriddenMethod.signature())) { + return mOverriddenMethod; + } + } + + ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); + if (containingClass().realSuperclass() != null && + containingClass().realSuperclass().isAbstract()) { + queue.add(containingClass()); + } + addInterfaces(containingClass().realInterfaces(), queue); + for (ClassInfo iface: queue) { + for (MethodInfo me: iface.methods()) { + if (me.name().equals(this.name()) + && me.signature().equals(this.signature()) + && notStrippable.contains(me.containingClass())) { + return me; + } + } + } + return null; + } + + public ClassInfo findRealOverriddenClass(String name, String signature) { + if (mReturnType == null) { + // ctor + return null; + } + if (mOverriddenMethod != null) { + return mOverriddenMethod.mRealContainingClass; + } + + ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); + if (containingClass().realSuperclass() != null && + containingClass().realSuperclass().isAbstract()) { + queue.add(containingClass()); + } + addInterfaces(containingClass().realInterfaces(), queue); + for (ClassInfo iface: queue) { + for (MethodInfo me: iface.methods()) { + if (me.name().equals(name) + && me.signature().equals(signature) + && me.inlineTags().tags() != null + && me.inlineTags().tags().length > 0) { + return iface; + } + } + } + return null; + } + + private class FirstSentenceTags implements InheritedTags + { + public TagInfo[] tags() + { + return comment().briefTags(); + } + public InheritedTags inherited() + { + MethodInfo m = findOverriddenMethod(name(), signature()); + if (m != null) { + return m.firstSentenceTags(); + } else { + return null; + } + } + } + + private class ReturnTags implements InheritedTags { + public TagInfo[] tags() { + return comment().returnTags(); + } + public InheritedTags inherited() { + MethodInfo m = findOverriddenMethod(name(), signature()); + if (m != null) { + return m.returnTags(); + } else { + return null; + } + } + } + + public boolean isDeprecated() { + boolean deprecated = false; + if (!mDeprecatedKnown) { + boolean commentDeprecated = (comment().deprecatedTags().length > 0); + boolean annotationDeprecated = false; + for (AnnotationInstanceInfo annotation : annotations()) { + if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) { + annotationDeprecated = true; + break; + } + } + + if (commentDeprecated != annotationDeprecated) { + Errors.error(Errors.DEPRECATION_MISMATCH, position(), + "Method " + mContainingClass.qualifiedName() + "." + name() + + ": @Deprecated annotation and @deprecated doc tag do not match"); + } + + mIsDeprecated = commentDeprecated | annotationDeprecated; + mDeprecatedKnown = true; + } + return mIsDeprecated; + } + + public TypeInfo[] getTypeParameters(){ + return mTypeParameters; + } + + public MethodInfo cloneForClass(ClassInfo newContainingClass) { + MethodInfo result = new MethodInfo(getRawCommentText(), mTypeParameters, + name(), signature(), newContainingClass, realContainingClass(), + isPublic(), isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(), + isSynthetic(), mIsAbstract, mIsSynchronized, mIsNative, mIsAnnotationElement, + kind(), mFlatSignature, mOverriddenMethod, + mReturnType, mParameters, mThrownExceptions, position(), annotations()); + result.init(mDefaultAnnotationElementValue); + return result; + } + + public MethodInfo(String rawCommentText, TypeInfo[] typeParameters, String name, + String signature, ClassInfo containingClass, ClassInfo realContainingClass, + boolean isPublic, boolean isProtected, + boolean isPackagePrivate, boolean isPrivate, + boolean isFinal, boolean isStatic, boolean isSynthetic, + boolean isAbstract, boolean isSynchronized, boolean isNative, + boolean isAnnotationElement, String kind, + String flatSignature, MethodInfo overriddenMethod, + TypeInfo returnType, ParameterInfo[] parameters, + ClassInfo[] thrownExceptions, SourcePositionInfo position, + AnnotationInstanceInfo[] annotations) + { + super(rawCommentText, name, signature, containingClass, realContainingClass, + isPublic, isProtected, isPackagePrivate, isPrivate, + isFinal, isStatic, isSynthetic, kind, position, annotations); + + // The underlying MethodDoc for an interface's declared methods winds up being marked + // non-abstract. Correct that here by looking at the immediate-parent class, and marking + // this method abstract if it is an unimplemented interface method. + if (containingClass.isInterface()) { + isAbstract = true; + } + + mReasonOpened = "0:0"; + mIsAnnotationElement = isAnnotationElement; + mTypeParameters = typeParameters; + mIsAbstract = isAbstract; + mIsSynchronized = isSynchronized; + mIsNative = isNative; + mFlatSignature = flatSignature; + mOverriddenMethod = overriddenMethod; + mReturnType = returnType; + mParameters = parameters; + mThrownExceptions = thrownExceptions; + } + + public void init(AnnotationValueInfo defaultAnnotationElementValue) + { + mDefaultAnnotationElementValue = defaultAnnotationElementValue; + } + + public boolean isAbstract() + { + return mIsAbstract; + } + + public boolean isSynchronized() + { + return mIsSynchronized; + } + + public boolean isNative() + { + return mIsNative; + } + + public String flatSignature() + { + return mFlatSignature; + } + + public InheritedTags inlineTags() + { + return new InlineTags(); + } + + public InheritedTags firstSentenceTags() + { + return new FirstSentenceTags(); + } + + public InheritedTags returnTags() { + return new ReturnTags(); + } + + public TypeInfo returnType() + { + return mReturnType; + } + + public String prettySignature() + { + String s = "("; + int N = mParameters.length; + for (int i=0; i<N; i++) { + ParameterInfo p = mParameters[i]; + TypeInfo t = p.type(); + if (t.isPrimitive()) { + s += t.simpleTypeName(); + } else { + s += t.asClassInfo().name(); + } + if (i != N-1) { + s += ','; + } + } + s += ')'; + return s; + } + + private boolean inList(ClassInfo item, ThrowsTagInfo[] list) + { + int len = list.length; + String qn = item.qualifiedName(); + for (int i=0; i<len; i++) { + ClassInfo ex = list[i].exception(); + if (ex != null && ex.qualifiedName().equals(qn)) { + return true; + } + } + return false; + } + + public ThrowsTagInfo[] throwsTags() + { + if (mThrowsTags == null) { + ThrowsTagInfo[] documented = comment().throwsTags(); + ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>(); + + int len = documented.length; + for (int i=0; i<len; i++) { + rv.add(documented[i]); + } + + ClassInfo[] all = mThrownExceptions; + len = all.length; + for (int i=0; i<len; i++) { + ClassInfo cl = all[i]; + if (documented == null || !inList(cl, documented)) { + rv.add(new ThrowsTagInfo("@throws", "@throws", + cl.qualifiedName(), cl, "", + containingClass(), position())); + } + } + mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]); + } + return mThrowsTags; + } + + private static int indexOfParam(String name, String[] list) + { + final int N = list.length; + for (int i=0; i<N; i++) { + if (name.equals(list[i])) { + return i; + } + } + return -1; + } + + public ParamTagInfo[] paramTags() + { + if (mParamTags == null) { + final int N = mParameters.length; + + String[] names = new String[N]; + String[] comments = new String[N]; + SourcePositionInfo[] positions = new SourcePositionInfo[N]; + + // get the right names so we can handle our names being different from + // our parent's names. + for (int i=0; i<N; i++) { + names[i] = mParameters[i].name(); + comments[i] = ""; + positions[i] = mParameters[i].position(); + } + + // gather our comments, and complain about misnamed @param tags + for (ParamTagInfo tag: comment().paramTags()) { + int index = indexOfParam(tag.parameterName(), names); + if (index >= 0) { + comments[index] = tag.parameterComment(); + positions[index] = tag.position(); + } else { + Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(), + "@param tag with name that doesn't match the parameter list: '" + + tag.parameterName() + "'"); + } + } + + // get our parent's tags to fill in the blanks + MethodInfo overridden = this.findOverriddenMethod(name(), signature()); + if (overridden != null) { + ParamTagInfo[] maternal = overridden.paramTags(); + for (int i=0; i<N; i++) { + if (comments[i].equals("")) { + comments[i] = maternal[i].parameterComment(); + positions[i] = maternal[i].position(); + } + } + } + + // construct the results, and cache them for next time + mParamTags = new ParamTagInfo[N]; + for (int i=0; i<N; i++) { + mParamTags[i] = new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], + parent(), positions[i]); + + // while we're here, if we find any parameters that are still undocumented at this + // point, complain. (this warning is off by default, because it's really, really + // common; but, it's good to be able to enforce it) + if (comments[i].equals("")) { + Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], + "Undocumented parameter '" + names[i] + "' on method '" + + name() + "'"); + } + } + } + return mParamTags; + } + + public SeeTagInfo[] seeTags() + { + SeeTagInfo[] result = comment().seeTags(); + if (result == null) { + if (mOverriddenMethod != null) { + result = mOverriddenMethod.seeTags(); + } + } + return result; + } + + public TagInfo[] deprecatedTags() + { + TagInfo[] result = comment().deprecatedTags(); + if (result.length == 0) { + if (comment().undeprecateTags().length == 0) { + if (mOverriddenMethod != null) { + result = mOverriddenMethod.deprecatedTags(); + } + } + } + return result; + } + + public ParameterInfo[] parameters() + { + return mParameters; + } + + + public boolean matchesParams(String[] params, String[] dimensions) + { + if (mParamStrings == null) { + ParameterInfo[] mine = mParameters; + int len = mine.length; + if (len != params.length) { + return false; + } + for (int i=0; i<len; i++) { + TypeInfo t = mine[i].type(); + if (!t.dimension().equals(dimensions[i])) { + return false; + } + String qn = t.qualifiedTypeName(); + String s = params[i]; + int slen = s.length(); + int qnlen = qn.length(); + if (!(qn.equals(s) || + ((slen+1)<qnlen && qn.charAt(qnlen-slen-1)=='.' + && qn.endsWith(s)))) { + return false; + } + } + } + return true; + } + + public void makeHDF(HDF data, String base) + { + data.setValue(base + ".kind", kind()); + data.setValue(base + ".name", name()); + data.setValue(base + ".href", htmlPage()); + data.setValue(base + ".anchor", anchor()); + + if (mReturnType != null) { + returnType().makeHDF(data, base + ".returnType", false, typeVariables()); + data.setValue(base + ".abstract", mIsAbstract ? "abstract" : ""); + } + + data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : ""); + data.setValue(base + ".final", isFinal() ? "final" : ""); + data.setValue(base + ".static", isStatic() ? "static" : ""); + + TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); + TagInfo.makeHDF(data, base + ".descr", inlineTags()); + TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); + TagInfo.makeHDF(data, base + ".seeAlso", seeTags()); + ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); + AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); + ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); + ParameterInfo.makeHDF(data, base + ".params", parameters(), isVarArgs(), typeVariables()); + if (isProtected()) { + data.setValue(base + ".scope", "protected"); + } + else if (isPublic()) { + data.setValue(base + ".scope", "public"); + } + TagInfo.makeHDF(data, base + ".returns", returnTags()); + + if (mTypeParameters != null) { + TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); + } + } + + public HashSet<String> typeVariables() + { + HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); + ClassInfo cl = containingClass(); + while (cl != null) { + TypeInfo[] types = cl.asTypeInfo().typeArguments(); + if (types != null) { + TypeInfo.typeVariables(types, result); + } + cl = cl.containingClass(); + } + return result; + } + + public boolean isExecutable() + { + return true; + } + + public ClassInfo[] thrownExceptions() + { + return mThrownExceptions; + } + + public String typeArgumentsName(HashSet<String> typeVars) + { + if (mTypeParameters == null || mTypeParameters.length == 0) { + return ""; + } else { + return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); + } + } + + public boolean isAnnotationElement() + { + return mIsAnnotationElement; + } + + public AnnotationValueInfo defaultAnnotationElementValue() + { + return mDefaultAnnotationElementValue; + } + + public void setVarargs(boolean set){ + mIsVarargs = set; + } + public boolean isVarArgs(){ + return mIsVarargs; + } + public String toString(){ + return this.name(); + } + + public void setReason(String reason) { + mReasonOpened = reason; + } + + public String getReason() { + return mReasonOpened; + } + + private String mFlatSignature; + private MethodInfo mOverriddenMethod; + private TypeInfo mReturnType; + private boolean mIsAnnotationElement; + private boolean mIsAbstract; + private boolean mIsSynchronized; + private boolean mIsNative; + private boolean mIsVarargs; + private boolean mDeprecatedKnown; + private boolean mIsDeprecated; + private ParameterInfo[] mParameters; + private ClassInfo[] mThrownExceptions; + private String[] mParamStrings; + ThrowsTagInfo[] mThrowsTags; + private ParamTagInfo[] mParamTags; + private TypeInfo[] mTypeParameters; + private AnnotationValueInfo mDefaultAnnotationElementValue; + private String mReasonOpened; +} + diff --git a/tools/droiddoc/src/PackageInfo.java b/tools/droiddoc/src/PackageInfo.java new file mode 100644 index 0000000..c696112 --- /dev/null +++ b/tools/droiddoc/src/PackageInfo.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import com.sun.javadoc.*; +import com.sun.tools.doclets.*; +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; + +public class PackageInfo extends DocInfo implements ContainerInfo +{ + public static final Comparator<PackageInfo> comparator = new Comparator<PackageInfo>() { + public int compare(PackageInfo a, PackageInfo b) { + return a.name().compareTo(b.name()); + } + }; + + public PackageInfo(PackageDoc pkg, String name, SourcePositionInfo position) + { + super(pkg.getRawCommentText(), position); + mName = name; + + if (pkg == null) { + throw new RuntimeException("pkg is null"); + } + mPackage = pkg; + } + + public String htmlPage() + { + String s = mName; + s = s.replace('.', '/'); + s += "/package-summary.html"; + s = DroidDoc.javadocDir + s; + return s; + } + + public String htmlLinksPage() + { + String s = mName; + s = s.replace('.', '/'); + s += "/package-links.html"; + s = DroidDoc.javadocDir + s; + return s; + } + + public ContainerInfo parent() + { + return null; + } + + public boolean isHidden() + { + return comment().isHidden(); + } + + public boolean checkLevel() { + // TODO should return false if all classes are hidden but the package isn't. + // We don't have this so I'm not doing it now. + return !isHidden(); + } + + public String name() + { + return mName; + } + + public String qualifiedName() + { + return mName; + } + + public TagInfo[] inlineTags() + { + return comment().tags(); + } + + public TagInfo[] firstSentenceTags() + { + return comment().briefTags(); + } + + public static ClassInfo[] filterHidden(ClassInfo[] classes) + { + ArrayList<ClassInfo> out = new ArrayList<ClassInfo>(); + + for (ClassInfo cl: classes) { + if (!cl.isHidden()) { + out.add(cl); + } + } + + return out.toArray(new ClassInfo[0]); + } + + public void makeLink(HDF data, String base) + { + if (checkLevel()) { + data.setValue(base + ".link", htmlPage()); + } + data.setValue(base + ".name", name()); + } + + public void makeClassLinkListHDF(HDF data, String base) + { + makeLink(data, base); + ClassInfo.makeLinkListHDF(data, base + ".interfaces", ClassInfo.sortByName(interfaces())); + ClassInfo.makeLinkListHDF(data, base + ".classes", ClassInfo.sortByName(ordinaryClasses())); + ClassInfo.makeLinkListHDF(data, base + ".enums", ClassInfo.sortByName(enums())); + ClassInfo.makeLinkListHDF(data, base + ".exceptions", ClassInfo.sortByName(exceptions())); + ClassInfo.makeLinkListHDF(data, base + ".errors", ClassInfo.sortByName(errors())); + } + + public ClassInfo[] interfaces() + { + return filterHidden(Converter.convertClasses(mPackage.interfaces())); + } + + public ClassInfo[] ordinaryClasses() + { + return filterHidden(Converter.convertClasses(mPackage.ordinaryClasses())); + } + + public ClassInfo[] enums() + { + return filterHidden(Converter.convertClasses(mPackage.enums())); + } + + public ClassInfo[] exceptions() + { + return filterHidden(Converter.convertClasses(mPackage.exceptions())); + } + + public ClassInfo[] errors() + { + return filterHidden(Converter.convertClasses(mPackage.errors())); + } + + // in hashed containers, treat the name as the key + @Override + public int hashCode() { + return mName.hashCode(); + } + + private String mName; + private PackageDoc mPackage; +} + diff --git a/tools/droiddoc/src/ParamTagInfo.java b/tools/droiddoc/src/ParamTagInfo.java new file mode 100644 index 0000000..c21ecd5 --- /dev/null +++ b/tools/droiddoc/src/ParamTagInfo.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import org.clearsilver.HDF; +import org.clearsilver.CS; + +public class ParamTagInfo extends ParsedTagInfo +{ + static final Pattern PATTERN = Pattern.compile( + "([^ \t\r\n]+)[ \t\r\n]+(.*)", + Pattern.DOTALL); + + private boolean mIsTypeParameter; + private String mParameterComment; + private String mParameterName; + + ParamTagInfo(String name, String kind, String text, ContainerInfo base, + SourcePositionInfo sp) + { + super(name, kind, text, base, sp); + + Matcher m = PATTERN.matcher(text); + if (m.matches()) { + mParameterName = m.group(1); + mParameterComment = m.group(2); + int len = mParameterName.length(); + mIsTypeParameter = len > 2 + && mParameterName.charAt(0) == '<' + && mParameterName.charAt(len-1) == '>'; + } else { + mParameterName = text.trim(); + mParameterComment = ""; + mIsTypeParameter = false; + } + setCommentText(mParameterComment); + } + + ParamTagInfo(String name, String kind, String text, + boolean isTypeParameter, String parameterComment, + String parameterName, ContainerInfo base, + SourcePositionInfo sp) + { + super(name, kind, text, base, sp); + mIsTypeParameter = isTypeParameter; + mParameterComment = parameterComment; + mParameterName = parameterName; + } + + public boolean isTypeParameter() + { + return mIsTypeParameter; + } + + public String parameterComment() + { + return mParameterComment; + } + + public String parameterName() + { + return mParameterName; + } + + public void makeHDF(HDF data, String base) + { + data.setValue(base + ".name", parameterName()); + data.setValue(base + ".isTypeParameter", isTypeParameter() ? "1" : "0"); + TagInfo.makeHDF(data, base + ".comment", commentTags()); + } + + public static void makeHDF(HDF data, String base, ParamTagInfo[] tags) + { + for (int i=0; i<tags.length; i++) { + // don't output if the comment is "" + if (!"".equals(tags[i].parameterComment())) { + tags[i].makeHDF(data, base + "." + i); + } + } + } +} diff --git a/tools/droiddoc/src/ParameterInfo.java b/tools/droiddoc/src/ParameterInfo.java new file mode 100644 index 0000000..44608be --- /dev/null +++ b/tools/droiddoc/src/ParameterInfo.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.HashSet; + +public class ParameterInfo +{ + ParameterInfo(String name, String typeName, TypeInfo type, SourcePositionInfo position) + { + mName = name; + mTypeName = typeName; + mType = type; + mPosition = position; + } + + TypeInfo type() + { + return mType; + } + + String name() + { + return mName; + } + + String typeName() + { + return mTypeName; + } + + SourcePositionInfo position() + { + return mPosition; + } + + public void makeHDF(HDF data, String base, boolean isLastVararg, + HashSet<String> typeVariables) + { + data.setValue(base + ".name", this.name()); + type().makeHDF(data, base + ".type", isLastVararg, typeVariables); + } + + public static void makeHDF(HDF data, String base, ParameterInfo[] params, + boolean isVararg, HashSet<String> typeVariables) + { + for (int i=0; i<params.length; i++) { + params[i].makeHDF(data, base + "." + i, + isVararg && (i == params.length - 1), typeVariables); + } + } + + String mName; + String mTypeName; + TypeInfo mType; + SourcePositionInfo mPosition; +} + diff --git a/tools/droiddoc/src/ParsedTagInfo.java b/tools/droiddoc/src/ParsedTagInfo.java new file mode 100755 index 0000000..c2e4806 --- /dev/null +++ b/tools/droiddoc/src/ParsedTagInfo.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.ArrayList; + +public class ParsedTagInfo extends TagInfo +{ + private ContainerInfo mContainer; + private String mCommentText; + private Comment mComment; + + ParsedTagInfo(String name, String kind, String text, ContainerInfo base, SourcePositionInfo sp) + { + super(name, kind, text, SourcePositionInfo.findBeginning(sp, text)); + mContainer = base; + mCommentText = text; + } + + public TagInfo[] commentTags() + { + if (mComment == null) { + mComment = new Comment(mCommentText, mContainer, position()); + } + return mComment.tags(); + } + + protected void setCommentText(String comment) + { + mCommentText = comment; + } + + public static <T extends ParsedTagInfo> TagInfo[] + joinTags(T[] tags) + { + ArrayList<TagInfo> list = new ArrayList<TagInfo>(); + final int N = tags.length; + for (int i=0; i<N; i++) { + TagInfo[] t = tags[i].commentTags(); + final int M = t.length; + for (int j=0; j<M; j++) { + list.add(t[j]); + } + } + return list.toArray(new TagInfo[list.size()]); + } +} diff --git a/tools/droiddoc/src/Proofread.java b/tools/droiddoc/src/Proofread.java new file mode 100644 index 0000000..ec9f523 --- /dev/null +++ b/tools/droiddoc/src/Proofread.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.io.IOException; +import java.io.FileWriter; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +public class Proofread +{ + static FileWriter out = null; + static final Pattern WHITESPACE = Pattern.compile("\\r?\\n"); + static final String INDENT = " "; + static final String NEWLINE = "\n" + INDENT; + + public static void initProofread(String filename) + { + try { + out = new FileWriter(filename); + out.write("javadoc proofread file: " + filename + "\n"); + } + catch (IOException e) { + if (out != null) { + try { + out.close(); + } + catch (IOException ex) { + } + out = null; + } + System.err.println("error opening file: " + filename); + } + } + + public static void finishProofread(String filename) + { + if (out == null) { + return; + } + + try { + out.close(); + } + catch (IOException e) { + } + } + + public static void write(String s) + { + if (out == null) { + return ; + } + try { + out.write(s); + } + catch (IOException e) { + } + } + + public static void writeIndented(String s) + { + s = s.trim(); + Matcher m = WHITESPACE.matcher(s); + s = m.replaceAll(NEWLINE); + write(INDENT); + write(s); + write("\n"); + } + + public static void writeFileHeader(String filename) + { + write("\n\n=== "); + write(filename); + write(" ===\n"); + } + + public static void writeTagList(TagInfo[] tags) + { + if (out == null) { + return; + } + + for (TagInfo t: tags) { + String k = t.kind(); + if ("Text".equals(t.name())) { + writeIndented(t.text()); + } + else if ("@more".equals(k)) { + writeIndented(""); + } + else if ("@see".equals(k)) { + SeeTagInfo see = (SeeTagInfo)t; + String label = see.label(); + if (label == null) { + label = ""; + } + writeIndented("{" + see.name() + " ... " + label + "}"); + } + else if ("@code".equals(k)) { + writeIndented(t.text()); + } + else if ("@samplecode".equals(k)) { + writeIndented(t.text()); + } + else { + writeIndented("{" + (t.name() != null ? t.name() : "") + "/" + + t.text() + "}"); + } + } + } + + public static void writePackages(String filename, TagInfo[] tags) + { + if (out == null) { + return; + } + + writeFileHeader(filename); + writeTagList(tags); + } + + public static void writePackage(String filename, TagInfo[] tags) + { + if (out == null) { + return; + } + + writeFileHeader(filename); + writeTagList(tags); + } + + public static void writeClass(String filename, ClassInfo cl) + { + if (out == null) { + return; + } + + writeFileHeader(filename); + writeTagList(cl.inlineTags()); + + // enum constants + for (FieldInfo f: cl.enumConstants()) { + write("ENUM: " + f.name() + "\n"); + writeTagList(f.inlineTags()); + } + + // fields + for (FieldInfo f: cl.selfFields()) { + write("FIELD: " + f.name() + "\n"); + writeTagList(f.inlineTags()); + } + + // constructors + for (MethodInfo m: cl.constructors()) { + write("CONSTRUCTOR: " + m.name() + "\n"); + writeTagList(m.inlineTags().tags()); + } + + // methods + for (MethodInfo m: cl.selfMethods()) { + write("METHOD: " + m.name() + "\n"); + writeTagList(m.inlineTags().tags()); + } + } +} diff --git a/tools/droiddoc/src/SampleCode.java b/tools/droiddoc/src/SampleCode.java new file mode 100644 index 0000000..e2283bd --- /dev/null +++ b/tools/droiddoc/src/SampleCode.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + + +public class SampleCode { + String mSource; + String mDest; + String mTitle; + + public SampleCode(String source, String dest, String title) { + mSource = source; + mTitle = title; + int len = dest.length(); + if (len > 1 && dest.charAt(len-1) != '/') { + mDest = dest + '/'; + } else { + mDest = dest; + } + } + + public void write() { + File f = new File(mSource); + if (!f.isDirectory()) { + System.out.println("-samplecode not a directory: " + mSource); + return; + } + writeDirectory(f, mDest); + } + + public static String convertExtension(String s, String ext) { + return s.substring(0, s.lastIndexOf('.')) + ext; + } + + public static String[] IMAGES = { ".png", ".jpg", ".gif" }; + public static String[] TEMPLATED = { ".java", ".xml" }; + + public static boolean inList(String s, String[] list) { + for (String t: list) { + if (s.endsWith(t)) { + return true; + } + } + return false; + } + + public void writeDirectory(File dir, String relative) { + TreeSet<String> dirs = new TreeSet<String>(); + TreeSet<String> files = new TreeSet<String>(); + + String subdir = relative; //.substring(mDest.length()); + + for (File f: dir.listFiles()) { + String name = f.getName(); + if (name.startsWith(".") || name.startsWith("_")) { + continue; + } + if (f.isFile()) { + String out = relative + name; + + if (inList(out, IMAGES)) { + // copied directly + ClearPage.copyFile(f, out); + writeImagePage(f, convertExtension(out, DroidDoc.htmlExtension), subdir); + files.add(name); + } + if (inList(out, TEMPLATED)) { + // copied and goes through the template + ClearPage.copyFile(f, out); + writePage(f, convertExtension(out, DroidDoc.htmlExtension), subdir); + files.add(name); + } + // else ignored + } + else if (f.isDirectory()) { + writeDirectory(f, relative + name + "/"); + dirs.add(name); + } + } + + // write the index page + int i; + HDF hdf = DroidDoc.makeHDF(); + + hdf.setValue("page.title", dir.getName() + " - " + mTitle); + hdf.setValue("projectTitle", mTitle); + hdf.setValue("subdir", subdir); + i=0; + for (String d: dirs) { + hdf.setValue("subdirs." + i + ".name", d); + i++; + } + i=0; + for (String f: files) { + hdf.setValue("files." + i + ".name", f); + hdf.setValue("files." + i + ".href", convertExtension(f, ".html")); + i++; + } + String filename = dir.getPath() + "/_index.html"; + String summary = SampleTagInfo.readFile(new SourcePositionInfo(filename, -1,-1), filename, + "sample code", true, false, true); + if (summary == null) { + summary = ""; + } + hdf.setValue("summary", summary); + + ClearPage.write(hdf, "sampleindex.cs", relative + "/index" + DroidDoc.htmlExtension); + } + + public void writePage(File f, String out, String subdir) { + String name = f.getName(); + + String filename = f.getPath(); + String data = SampleTagInfo.readFile(new SourcePositionInfo(filename, -1,-1), filename, + "sample code", true, true, true); + data = DroidDoc.escape(data); + + HDF hdf = DroidDoc.makeHDF(); + + hdf.setValue("page.title", name); + hdf.setValue("subdir", subdir); + hdf.setValue("realFile", name); + hdf.setValue("fileContents", data); + + ClearPage.write(hdf, "sample.cs", out); + } + + public void writeImagePage(File f, String out, String subdir) { + String name = f.getName(); + + String data = "<img src=\"" + name + "\" title=\"" + name + "\" />"; + + HDF hdf = DroidDoc.makeHDF(); + + hdf.setValue("page.title", name); + hdf.setValue("subdir", subdir); + hdf.setValue("realFile", name); + hdf.setValue("fileContents", data); + + ClearPage.write(hdf, "sample.cs", out); + } +} diff --git a/tools/droiddoc/src/SampleTagInfo.java b/tools/droiddoc/src/SampleTagInfo.java new file mode 100644 index 0000000..c80083b --- /dev/null +++ b/tools/droiddoc/src/SampleTagInfo.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; + +import java.io.Reader; +import java.io.IOException; +import java.io.FileReader; +import java.io.LineNumberReader; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +/* + * SampleTagInfo copies text from a given file into the javadoc comment. + * + * The @include tag copies the text verbatim from the given file. + * + * The @sample tag copies the text from the given file, stripping leading and + * trailing whitespace, and reducing the indent level of the text to the indent + * level of the first non-whitespace line. + * + * Both tags accept either a filename and an id or just a filename. If no id + * is provided, the entire file is copied. If an id is provided, the lines + * in the given file between the first two lines containing BEGIN_INCLUDE(id) + * and END_INCLUDE(id), for the given id, are copied. The id may be only + * letters, numbers and underscore (_). + * + * Four examples: + * {@include samples/ApiDemos/src/com/google/app/Notification1.java} + * {@sample samples/ApiDemos/src/com/google/app/Notification1.java} + * {@include samples/ApiDemos/src/com/google/app/Notification1.java Bleh} + * {@sample samples/ApiDemos/src/com/google/app/Notification1.java Bleh} + * + */ +public class SampleTagInfo extends TagInfo +{ + static final int STATE_BEGIN = 0; + static final int STATE_MATCHING = 1; + + static final Pattern TEXT = Pattern.compile( + "[\r\n \t]*([^\r\n \t]*)[\r\n \t]*([0-9A-Za-z_]*)[\r\n \t]*", + Pattern.DOTALL); + + private static final String BEGIN_INCLUDE = "BEGIN_INCLUDE"; + private static final String END_INCLUDE = "END_INCLUDE"; + + private ContainerInfo mBase; + private String mIncluded; + + public static String escapeHtml(String str) { + return str.replace("&", "&").replace("<", "<").replace(">", ">"); + } + + private static boolean isIncludeLine(String str) { + return str.indexOf(BEGIN_INCLUDE)>=0 || str.indexOf(END_INCLUDE)>=0; + } + + SampleTagInfo(String name, String kind, String text, ContainerInfo base, + SourcePositionInfo position) + { + super(name, kind, text, position); + mBase = base; + + Matcher m = TEXT.matcher(text); + if (!m.matches()) { + Errors.error(Errors.BAD_INCLUDE_TAG, position, "Bad @include tag: " + + text); + return; + } + String filename = m.group(1); + String id = m.group(2); + boolean trim = "@sample".equals(name); + + if (id == null || "".equals(id)) { + mIncluded = readFile(position, filename, id, trim, true, false); + } else { + mIncluded = loadInclude(position, filename, id, trim); + } + + if (mIncluded == null) { + Errors.error(Errors.BAD_INCLUDE_TAG, position, "include tag '" + id + + "' not found in file: " + filename); + } + } + + static String getTrimString(String line) + { + int i = 0; + int len = line.length(); + for (; i<len; i++) { + char c = line.charAt(i); + if (c != ' ' && c != '\t') { + break; + } + } + if (i == len) { + return null; + } else { + return line.substring(0, i); + } + } + + static String loadInclude(SourcePositionInfo pos, String filename, + String id, boolean trim) + { + Reader input = null; + StringBuilder result = new StringBuilder(); + + String begin = BEGIN_INCLUDE + "(" + id + ")"; + String end = END_INCLUDE + "(" + id + ")"; + + try { + input = new FileReader(filename); + LineNumberReader lines = new LineNumberReader(input); + + int state = STATE_BEGIN; + + int trimLength = -1; + String trimString = null; + int trailing = 0; + + while (true) { + String line = lines.readLine(); + if (line == null) { + return null; + } + switch (state) { + case STATE_BEGIN: + if (line.indexOf(begin) >= 0) { + state = STATE_MATCHING; + } + break; + case STATE_MATCHING: + if (line.indexOf(end) >= 0) { + return result.substring(0); + } else { + boolean empty = "".equals(line.trim()); + if (trim) { + if (isIncludeLine(line)) { + continue; + } + if (trimLength < 0 && !empty) { + trimString = getTrimString(line); + if (trimString != null) { + trimLength = trimString.length(); + } + } + if (trimLength >= 0 && line.length() > trimLength) { + boolean trimThisLine = true; + for (int i=0; i<trimLength; i++) { + if (line.charAt(i) != trimString.charAt(i)){ + trimThisLine = false; + break; + } + } + if (trimThisLine) { + line = line.substring(trimLength); + } + } + if (trimLength >= 0) { + if (!empty) { + for (int i=0; i<trailing; i++) { + result.append('\n'); + } + line = escapeHtml(line); + result.append(line); + trailing = 1; // add \n next time, maybe + } else { + trailing++; + } + } + } else { + result.append(line); + result.append('\n'); + } + } + break; + } + } + } + catch (IOException e) { + Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + + " include \"" + id + "\" " + filename); + } + finally { + if (input != null) { + try { + input.close(); + } + catch (IOException ex) { + } + } + } + Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Did not find " + end + + " in file " + filename); + return null; + } + + static String readFile(SourcePositionInfo pos, String filename, + String id, boolean trim, boolean escape, + boolean errorOk) + { + Reader input = null; + StringBuilder result = new StringBuilder(); + int trailing = 0; + boolean started = false; + try { + input = new FileReader(filename); + LineNumberReader lines = new LineNumberReader(input); + + while (true) { + String line = lines.readLine(); + if (line == null) { + break; + } + if (trim) { + if (isIncludeLine(line)) { + continue; + } + if (!"".equals(line.trim())) { + if (started) { + for (int i=0; i<trailing; i++) { + result.append('\n'); + } + } + if (escape) { + line = escapeHtml(line); + } + result.append(line); + trailing = 1; // add \n next time, maybe + started = true; + } else { + if (started) { + trailing++; + } + } + } else { + result.append(line); + result.append('\n'); + } + } + } + catch (IOException e) { + if (errorOk) { + return null; + } else { + Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for" + + " include \"" + id + "\" " + filename); + } + } + finally { + if (input != null) { + try { + input.close(); + } + catch (IOException ex) { + } + } + } + return result.substring(0); + } + + public void makeHDF(HDF data, String base) + { + data.setValue(base + ".name", name()); + data.setValue(base + ".kind", kind()); + if (mIncluded != null) { + data.setValue(base + ".text", mIncluded); + } else { + data.setValue(base + ".text", "INCLUDE_ERROR"); + } + } +} + diff --git a/tools/droiddoc/src/Scoped.java b/tools/droiddoc/src/Scoped.java new file mode 100644 index 0000000..cca61ed --- /dev/null +++ b/tools/droiddoc/src/Scoped.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public interface Scoped { + boolean isPublic(); + boolean isProtected(); + boolean isPackagePrivate(); + boolean isPrivate(); + boolean isHidden(); +} diff --git a/tools/droiddoc/src/SeeTagInfo.java b/tools/droiddoc/src/SeeTagInfo.java new file mode 100644 index 0000000..94863b5 --- /dev/null +++ b/tools/droiddoc/src/SeeTagInfo.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.ArrayList; + +public class SeeTagInfo extends TagInfo +{ + private ContainerInfo mBase; + LinkReference mLink; + + SeeTagInfo(String name, String kind, String text, ContainerInfo base, + SourcePositionInfo position) + { + super(name, kind, text, position); + mBase = base; + } + + protected LinkReference linkReference() { + if (mLink == null) { + mLink = LinkReference.parse(text(), mBase, position(), + (!"@see".equals(name())) && (mBase != null ? mBase.checkLevel() : true)); + } + return mLink; + } + + public String label() + { + return linkReference().label; + } + + public void makeHDF(HDF data, String base) + { + LinkReference linkRef = linkReference(); + if (linkRef.kind != null) { + // if they have a better suggestion about "kind" use that. + // do this before super.makeHDF() so it picks it up + setKind(linkRef.kind); + } + + super.makeHDF(data, base); + + data.setValue(base + ".label", linkRef.label); + if (linkRef.href != null) { + data.setValue(base + ".href", linkRef.href); + } + } + + public boolean checkLevel() { + return linkReference().checkLevel(); + } + + public static void makeHDF(HDF data, String base, SeeTagInfo[] tags) + { + int j=0; + for (SeeTagInfo tag: tags) { + if (tag.mBase.checkLevel() && tag.checkLevel()) { + tag.makeHDF(data, base + "." + j); + j++; + } + } + } +} diff --git a/tools/droiddoc/src/Sorter.java b/tools/droiddoc/src/Sorter.java new file mode 100644 index 0000000..92039d4 --- /dev/null +++ b/tools/droiddoc/src/Sorter.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public class Sorter implements Comparable +{ + public String label; + public Object data; + + public Sorter(String l, Object d) + { + label = l; + data = d; + } + + public int compareTo(Object other) + { + return label.compareToIgnoreCase(((Sorter)other).label); + } +} diff --git a/tools/droiddoc/src/SourcePositionInfo.java b/tools/droiddoc/src/SourcePositionInfo.java new file mode 100644 index 0000000..6244803 --- /dev/null +++ b/tools/droiddoc/src/SourcePositionInfo.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public class SourcePositionInfo implements Comparable +{ + public SourcePositionInfo() { + this.file = "<unknown>"; + this.line = 0; + this.column = 0; + } + + public SourcePositionInfo(String file, int line, int column) + { + this.file = file; + this.line = line; + this.column = column; + } + + public SourcePositionInfo(SourcePositionInfo that) + { + this.file = that.file; + this.line = that.line; + this.column = that.column; + } + + /** + * Given this position and str which occurs at that position, as well as str an index into str, + * find the SourcePositionInfo. + * + * @throw StringIndexOutOfBoundsException if index > str.length() + */ + public static SourcePositionInfo add(SourcePositionInfo that, String str, int index) + { + if (that == null) { + return null; + } + int line = that.line; + char prev = 0; + for (int i=0; i<index; i++) { + char c = str.charAt(i); + if (c == '\r' || (c == '\n' && prev != '\r')) { + line++; + } + prev = c; + } + return new SourcePositionInfo(that.file, line, 0); + } + + public static SourcePositionInfo findBeginning(SourcePositionInfo that, String str) + { + if (that == null) { + return null; + } + int line = that.line-1; // -1 because, well, it seems to work + int prev = 0; + for (int i=str.length()-1; i>=0; i--) { + char c = str.charAt(i); + if ((c == '\r' && prev != '\n') || (c == '\n')) { + line--; + } + prev = c; + } + return new SourcePositionInfo(that.file, line, 0); + } + + public String toString() + { + return file + ':' + line; + } + + public int compareTo(Object o) { + SourcePositionInfo that = (SourcePositionInfo)o; + int r = this.file.compareTo(that.file); + if (r != 0) return r; + return this.line - that.line; + } + + public String file; + public int line; + public int column; +} diff --git a/tools/droiddoc/src/Stubs.java b/tools/droiddoc/src/Stubs.java new file mode 100644 index 0000000..a233d68 --- /dev/null +++ b/tools/droiddoc/src/Stubs.java @@ -0,0 +1,958 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.Comparator; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintStream; + +public class Stubs { + private static HashSet<ClassInfo> notStrippable; + public static void writeStubs(String stubsDir, Boolean writeXML, String xmlFile, + HashSet<String> stubPackages) { + // figure out which classes we need + notStrippable = new HashSet(); + ClassInfo[] all = Converter.allClasses(); + File xml = new File(xmlFile); + xml.getParentFile().mkdirs(); + PrintStream xmlWriter = null; + if (writeXML) { + try { + xmlWriter = new PrintStream(xml); + } catch (FileNotFoundException e) { + Errors.error(Errors.IO_ERROR, new SourcePositionInfo(xmlFile, 0, 0), + "Cannot open file for write."); + } + } + // If a class is public or protected, not hidden, and marked as included, + // then we can't strip it + for (ClassInfo cl: all) { + if (cl.checkLevel() && cl.isIncluded()) { + cantStripThis(cl, notStrippable, "0:0"); + } + } + + // complain about anything that looks includeable but is not supposed to + // be written, e.g. hidden things + for (ClassInfo cl: notStrippable) { + if (!cl.isHidden()) { + MethodInfo[] methods = cl.selfMethods(); + for (MethodInfo m: methods) { + if (m.isHidden()) { + Errors.error(Errors.UNAVAILABLE_SYMBOL, + m.position(), "Reference to hidden method " + + m.name()); + } else if (m.isDeprecated()) { + // don't bother reporting deprecated methods + // unless they are public + Errors.error(Errors.DEPRECATED, + m.position(), "Method " + + cl.qualifiedName() + "." + m.name() + + " is deprecated"); + } + + ClassInfo returnClass = m.returnType().asClassInfo(); + if (returnClass != null && returnClass.isHidden()) { + Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), + "Method " + cl.qualifiedName() + "." + m.name() + + " returns unavailable type " + returnClass.name()); + } + + ParameterInfo[] params = m.parameters(); + for (ParameterInfo p: params) { + TypeInfo t = p.type(); + if (!t.isPrimitive()) { + if (t.asClassInfo().isHidden()) { + Errors.error(Errors.UNAVAILABLE_SYMBOL, + p.position(), "Reference to unavailable class " + + t.fullName()); + } + } + } + } + + // annotations are handled like methods + methods = cl.annotationElements(); + for (MethodInfo m: methods) { + if (m.isHidden()) { + Errors.error(Errors.UNAVAILABLE_SYMBOL, + m.position(), "Reference to hidden annotation " + + m.name()); + } + + ClassInfo returnClass = m.returnType().asClassInfo(); + if (returnClass != null && returnClass.isHidden()) { + Errors.error(Errors.UNAVAILABLE_SYMBOL, + m.position(), "Annotation '" + m.name() + + "' returns unavailable type " + returnClass.name()); + } + + ParameterInfo[] params = m.parameters(); + for (ParameterInfo p: params) { + TypeInfo t = p.type(); + if (!t.isPrimitive()) { + if (t.asClassInfo().isHidden()) { + Errors.error(Errors.UNAVAILABLE_SYMBOL, + p.position(), "Reference to unavailable annotation class " + + t.fullName()); + } + } + } + } + } else if (cl.isDeprecated()) { + // not hidden, but deprecated + Errors.error(Errors.DEPRECATED, + cl.position(), "Class " + cl.qualifiedName() + + " is deprecated"); + } + } + + // write out the stubs + HashMap<PackageInfo, List<ClassInfo>> packages = new HashMap<PackageInfo, List<ClassInfo>>(); + for (ClassInfo cl: notStrippable) { + if (!cl.isDocOnly()) { + if (stubPackages == null || stubPackages.contains(cl.containingPackage().name())) { + writeClassFile(stubsDir, cl); + if (packages.containsKey(cl.containingPackage())) { + packages.get(cl.containingPackage()).add(cl); + } else { + ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>(); + classes.add(cl); + packages.put(cl.containingPackage(), classes); + } + } + } + } + + // write out the XML + if (writeXML && xmlWriter != null) { + writeXML(xmlWriter, packages, notStrippable); + } + + if (xmlWriter != null) { + xmlWriter.close(); + } + + } + + public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) { + + if (!notStrippable.add(cl)) { + // slight optimization: if it already contains cl, it already contains + // all of cl's parents + return; + } + cl.setReasonIncluded(why); + + // cant strip annotations + /*if (cl.annotations() != null){ + for (AnnotationInstanceInfo ai : cl.annotations()){ + if (ai.type() != null){ + cantStripThis(ai.type(), notStrippable, "1:" + cl.qualifiedName()); + } + } + }*/ + // cant strip any public fields or their generics + if (cl.allSelfFields() != null){ + for (FieldInfo fInfo : cl.allSelfFields()){ + if (fInfo.type() != null){ + if (fInfo.type().asClassInfo() != null){ + cantStripThis(fInfo.type().asClassInfo(), notStrippable, + "2:" + cl.qualifiedName()); + } + if (fInfo.type().typeArguments() != null){ + for (TypeInfo tTypeInfo : fInfo.type().typeArguments()){ + if (tTypeInfo.asClassInfo() != null){ + cantStripThis(tTypeInfo.asClassInfo(), notStrippable, + "3:" + cl.qualifiedName()); + } + } + } + } + } + } + //cant strip any of the type's generics + if (cl.asTypeInfo() != null){ + if (cl.asTypeInfo().typeArguments() != null){ + for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()){ + if (tInfo.asClassInfo() != null){ + cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName()); + } + } + } + } + //cant strip any of the annotation elements + //cantStripThis(cl.annotationElements(), notStrippable); + // take care of methods + cantStripThis(cl.allSelfMethods(), notStrippable); + cantStripThis(cl.allConstructors(), notStrippable); + // blow the outer class open if this is an inner class + if(cl.containingClass() != null){ + cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName()); + } + // blow open super class and interfaces + ClassInfo supr = cl.realSuperclass(); + if (supr != null) { + if (supr.isHidden()) { + // cl is a public class declared as extending a hidden superclass. + // this is not a desired practice but it's happened, so we deal + // with it by stripping off the superclass relation for purposes of + // generating the doc & stub information, and proceeding normally. + cl.init(cl.asTypeInfo(), cl.realInterfaces(), cl.realInterfaceTypes(), + cl.innerClasses(), cl.allConstructors(), cl.allSelfMethods(), + cl.annotationElements(), cl.allSelfFields(), cl.enumConstants(), + cl.containingPackage(), cl.containingClass(), + null, null, cl.annotations()); + Errors.error(Errors.HIDDEN_SUPERCLASS, + cl.position(), "Public class " + cl.qualifiedName() + + " stripped of unavailable superclass " + + supr.qualifiedName()); + } else { + cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + + cl.qualifiedName()); + } + } + } + + private static void cantStripThis(MethodInfo[] mInfos , HashSet<ClassInfo> notStrippable) { + //for each method, blow open the parameters, throws and return types. also blow open their generics + if (mInfos != null){ + for (MethodInfo mInfo : mInfos){ + if (mInfo.getTypeParameters() != null){ + for (TypeInfo tInfo : mInfo.getTypeParameters()){ + if (tInfo.asClassInfo() != null){ + cantStripThis(tInfo.asClassInfo(), notStrippable, "8:" + + mInfo.realContainingClass().qualifiedName() + ":" + + mInfo.name()); + } + } + } + if (mInfo.parameters() != null){ + for (ParameterInfo pInfo : mInfo.parameters()){ + if (pInfo.type() != null && pInfo.type().asClassInfo() != null){ + cantStripThis(pInfo.type().asClassInfo(), notStrippable, + "9:"+ mInfo.realContainingClass().qualifiedName() + + ":" + mInfo.name()); + if (pInfo.type().typeArguments() != null){ + for (TypeInfo tInfoType : pInfo.type().typeArguments()){ + if (tInfoType.asClassInfo() != null){ + cantStripThis(tInfoType.asClassInfo(), notStrippable, + "10:" + + mInfo.realContainingClass().qualifiedName() + ":" + + mInfo.name()); + } + } + } + } + } + } + for (ClassInfo thrown : mInfo.thrownExceptions()){ + cantStripThis(thrown, notStrippable, "11:" + + mInfo.realContainingClass().qualifiedName() + +":" + mInfo.name()); + } + if (mInfo.returnType() != null && mInfo.returnType().asClassInfo() != null){ + cantStripThis(mInfo.returnType().asClassInfo(), notStrippable, + "12:" + mInfo.realContainingClass().qualifiedName() + + ":" + mInfo.name()); + if (mInfo.returnType().typeArguments() != null){ + for (TypeInfo tyInfo: mInfo.returnType().typeArguments() ){ + if (tyInfo.asClassInfo() != null){ + cantStripThis(tyInfo.asClassInfo(), notStrippable, + "13:" + + mInfo.realContainingClass().qualifiedName() + + ":" + mInfo.name()); + } + } + } + } + } + } + } + + static String javaFileName(ClassInfo cl) { + String dir = ""; + PackageInfo pkg = cl.containingPackage(); + if (pkg != null) { + dir = pkg.name(); + dir = dir.replace('.', '/') + '/'; + } + return dir + cl.name() + ".java"; + } + + static void writeClassFile(String stubsDir, ClassInfo cl) { + // inner classes are written by their containing class + if (cl.containingClass() != null) { + return; + } + + String filename = stubsDir + '/' + javaFileName(cl); + File file = new File(filename); + ClearPage.ensureDirectory(file); + + PrintStream stream = null; + try { + stream = new PrintStream(file); + writeClassFile(stream, cl); + } + catch (FileNotFoundException e) { + System.err.println("error writing file: " + filename); + } + finally { + if (stream != null) { + stream.close(); + } + } + } + + static void writeClassFile(PrintStream stream, ClassInfo cl) { + PackageInfo pkg = cl.containingPackage(); + if (pkg != null) { + stream.println("package " + pkg.name() + ";"); + } + writeClass(stream, cl); + } + + static void writeClass(PrintStream stream, ClassInfo cl) { + writeAnnotations(stream, cl.annotations()); + + stream.print(DroidDoc.scope(cl) + " "); + if (cl.isAbstract() && !cl.isAnnotation() && !cl.isInterface()) { + stream.print("abstract "); + } + if (cl.isStatic()){ + stream.print("static "); + } + if (cl.isFinal() && !cl.isEnum()) { + stream.print("final "); + } + if (false) { + stream.print("strictfp "); + } + + HashSet<String> classDeclTypeVars = new HashSet(); + String leafName = cl.asTypeInfo().fullName(classDeclTypeVars); + int bracket = leafName.indexOf('<'); + if (bracket < 0) bracket = leafName.length() - 1; + int period = leafName.lastIndexOf('.', bracket); + if (period < 0) period = -1; + leafName = leafName.substring(period+1); + + String kind = cl.kind(); + stream.println(kind + " " + leafName); + + TypeInfo base = cl.superclassType(); + + if (!"enum".equals(kind)) { + if (base != null && !"java.lang.Object".equals(base.qualifiedTypeName())) { + stream.println(" extends " + base.fullName(classDeclTypeVars)); + } + } + + TypeInfo[] interfaces = cl.realInterfaceTypes(); + List<TypeInfo> usedInterfaces = new ArrayList<TypeInfo>(); + for (TypeInfo iface : interfaces) { + if (notStrippable.contains(iface.asClassInfo()) + && !iface.asClassInfo().isDocOnly()) { + usedInterfaces.add(iface); + } + } + if (usedInterfaces.size() > 0 && !cl.isAnnotation()) { + // can java annotations extend other ones? + if (cl.isInterface() || cl.isAnnotation()) { + stream.print(" extends "); + } else { + stream.print(" implements "); + } + String comma = ""; + for (TypeInfo iface: usedInterfaces) { + stream.print(comma + iface.fullName(classDeclTypeVars)); + comma = ", "; + } + stream.println(); + } + + stream.println("{"); + + FieldInfo[] enumConstants = cl.enumConstants(); + int N = enumConstants.length; + for (int i=0; i<N; i++) { + FieldInfo field = enumConstants[i]; + if (!field.constantLiteralValue().equals("null")){ + stream.println(field.name() + "(" + field.constantLiteralValue() + + (i==N-1 ? ");" : "),")); + }else{ + stream.println(field.name() + "(" + (i==N-1 ? ");" : "),")); + } + } + + for (ClassInfo inner: cl.getRealInnerClasses()) { + if (notStrippable.contains(inner) + && !inner.isDocOnly()){ + writeClass(stream, inner); + } + } + + + for (MethodInfo method: cl.constructors()) { + if (!method.isDocOnly()) { + writeMethod(stream, method, true); + } + } + + boolean fieldNeedsInitialization = false; + boolean staticFieldNeedsInitialization = false; + for (FieldInfo field: cl.allSelfFields()) { + if (!field.isDocOnly()) { + if (!field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) { + fieldNeedsInitialization = true; + } + if (field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) { + staticFieldNeedsInitialization = true; + } + } + } + + // The compiler includes a default public constructor that calls the super classes + // default constructor in the case where there are no written constructors. + // So, if we hide all the constructors, java may put in a constructor + // that calls a nonexistent super class constructor. So, if there are no constructors, + // and the super class doesn't have a default constructor, write in a private constructor + // that works. TODO -- we generate this as protected, but we really should generate + // it as private unless it also exists in the real code. + if ((cl.constructors().length == 0 && (cl.getNonWrittenConstructors().length != 0 + || fieldNeedsInitialization)) + && !cl.isAnnotation() + && !cl.isInterface() + && !cl.isEnum() ) { + //Errors.error(Errors.HIDDEN_CONSTRUCTOR, + // cl.position(), "No constructors " + + // "found and superclass has no parameterless constructor. A constructor " + + // "that calls an appropriate superclass constructor " + + // "was automatically written to stubs.\n"); + stream.println(cl.leafName() + + "() { " + superCtorCall(cl,null) + + "throw new" + " RuntimeException(\"Stub!\"); }"); + } + + for (MethodInfo method: cl.allSelfMethods()) { + if (cl.isEnum()) { + if (("values".equals(method.name()) + && "()".equals(method.signature())) + || ("valueOf".equals(method.name()) + && "(java.lang.String)".equals(method.signature()))) { + // skip these two methods on enums, because they're synthetic, + // although for some reason javadoc doesn't mark them as synthetic, + // maybe because they still want them documented + continue; + } + } + if (!method.isDocOnly()) { + writeMethod(stream, method, false); + } + } + //Write all methods that are hidden, but override abstract methods or interface methods. + //These can't be hidden. + for (MethodInfo method : cl.getHiddenMethods()){ + MethodInfo overriddenMethod = method.findRealOverriddenMethod(method.name(), method.signature(), notStrippable); + ClassInfo classContainingMethod = method.findRealOverriddenClass(method.name(), + method.signature()); + if (overriddenMethod != null && !overriddenMethod.isHidden() + && !overriddenMethod.isDocOnly() && + (overriddenMethod.isAbstract() || + overriddenMethod.containingClass().isInterface())) { + method.setReason("1:" + classContainingMethod.qualifiedName()); + cl.addMethod(method); + writeMethod(stream, method, false); + } + } + + for (MethodInfo element: cl.annotationElements()) { + if (!element.isDocOnly()) { + writeAnnotationElement(stream, element); + } + } + + for (FieldInfo field: cl.allSelfFields()) { + if (!field.isDocOnly()) { + writeField(stream, field); + } + } + + if (staticFieldNeedsInitialization) { + stream.print("static { "); + for (FieldInfo field: cl.allSelfFields()) { + if (!field.isDocOnly() && field.isStatic() && field.isFinal() + && !fieldIsInitialized(field) && field.constantValue() == null) { + stream.print(field.name() + " = " + field.type().defaultValue() + + "; "); + } + } + stream.println("}"); + } + + stream.println("}"); + } + + + static void writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor) { + String comma; + + stream.print(DroidDoc.scope(method) + " "); + if (method.isStatic()) { + stream.print("static "); + } + if (method.isFinal()) { + stream.print("final "); + } + if (method.isAbstract()) { + stream.print("abstract "); + } + if (method.isSynchronized()) { + stream.print("synchronized "); + } + if (method.isNative()) { + stream.print("native "); + } + if (false /*method.isStictFP()*/) { + stream.print("strictfp "); + } + + stream.print(method.typeArgumentsName(new HashSet()) + " "); + + if (!isConstructor) { + stream.print(method.returnType().fullName(method.typeVariables()) + " "); + } + String n = method.name(); + int pos = n.lastIndexOf('.'); + if (pos >= 0) { + n = n.substring(pos + 1); + } + stream.print(n + "("); + comma = ""; + int count = 1; + int size = method.parameters().length; + for (ParameterInfo param: method.parameters()) { + String fullTypeName = param.type().fullName(method.typeVariables()); + if (count == size && method.isVarArgs()) { + // TODO: note that this does not attempt to handle hypothetical + // vararg methods whose last parameter is a list of arrays, e.g. + // "Object[]...". + fullTypeName = param.type().qualifiedTypeName() + "..."; + } + stream.print(comma + fullTypeName + " " + param.name()); + comma = ", "; + count++; + } + stream.print(")"); + + comma = ""; + if (method.thrownExceptions().length > 0) { + stream.print(" throws "); + for (ClassInfo thrown: method.thrownExceptions()) { + stream.print(comma + thrown.qualifiedName()); + comma = ", "; + } + } + if (method.isAbstract() || method.isNative() || method.containingClass().isInterface()) { + stream.println(";"); + } else { + stream.print(" { "); + if (isConstructor) { + stream.print(superCtorCall(method.containingClass(), method.thrownExceptions())); + } + stream.println("throw new RuntimeException(\"Stub!\"); }"); + } + } + + static void writeField(PrintStream stream, FieldInfo field) { + stream.print(DroidDoc.scope(field) + " "); + if (field.isStatic()) { + stream.print("static "); + } + if (field.isFinal()) { + stream.print("final "); + } + if (field.isTransient()) { + stream.print("transient "); + } + if (field.isVolatile()) { + stream.print("volatile "); + } + + stream.print(field.type().fullName()); + stream.print(" "); + stream.print(field.name()); + + if (fieldIsInitialized(field)) { + stream.print(" = " + field.constantLiteralValue()); + } + + stream.println(";"); + } + + static boolean fieldIsInitialized(FieldInfo field) { + return (field.isFinal() && field.constantValue() != null) + || !field.type().dimension().equals("") + || field.containingClass().isInterface(); + } + + // Returns 'true' if the method is an @Override of a visible parent + // method implementation, and thus does not affect the API. + static boolean methodIsOverride(MethodInfo mi) { + // Abstract/static/final methods are always listed in the API description + if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) { + return false; + } + + // Find any relevant ancestor declaration and inspect it + MethodInfo om = mi.findSuperclassImplementation(notStrippable); + if (om != null) { + // Visibility mismatch is an API change, so check for it + if (mi.mIsPrivate == om.mIsPrivate + && mi.mIsPublic == om.mIsPublic + && mi.mIsProtected == om.mIsProtected) { + // Look only for overrides of an ancestor class implementation, + // not of e.g. an abstract or interface method declaration + if (!om.isAbstract()) { + // If the parent is hidden, we can't rely on it to provide + // the API + if (!om.isHidden()) { + // If the only "override" turns out to be in our own class + // (which sometimes happens in concrete subclasses of + // abstract base classes), it's not really an override + if (!mi.mContainingClass.equals(om.mContainingClass)) { + return true; + } + } + } + } + } + return false; + } + + static boolean canCallMethod(ClassInfo from, MethodInfo m) { + if (m.isPublic() || m.isProtected()) { + return true; + } + if (m.isPackagePrivate()) { + String fromPkg = from.containingPackage().name(); + String pkg = m.containingClass().containingPackage().name(); + if (fromPkg.equals(pkg)) { + return true; + } + } + return false; + } + + // call a constructor, any constructor on this class's superclass. + static String superCtorCall(ClassInfo cl, ClassInfo[] thrownExceptions) { + ClassInfo base = cl.realSuperclass(); + if (base == null) { + return ""; + } + HashSet<String> exceptionNames = new HashSet<String>(); + if (thrownExceptions != null ){ + for (ClassInfo thrown : thrownExceptions){ + exceptionNames.add(thrown.name()); + } + } + MethodInfo[] ctors = base.constructors(); + MethodInfo ctor = null; + //bad exception indicates that the exceptions thrown by the super constructor + //are incompatible with the constructor we're using for the sub class. + Boolean badException = false; + for (MethodInfo m: ctors) { + if (canCallMethod(cl, m)) { + if (m.thrownExceptions() != null){ + for (ClassInfo thrown : m.thrownExceptions()){ + if (!exceptionNames.contains(thrown.name())){ + badException = true; + } + } + } + if (badException){ + badException = false; + continue; + } + // if it has no args, we're done + if (m.parameters().length == 0) { + return ""; + } + ctor = m; + } + } + if (ctor != null) { + String result = ""; + result+= "super("; + ParameterInfo[] params = ctor.parameters(); + int N = params.length; + for (int i=0; i<N; i++) { + TypeInfo t = params[i].type(); + if (t.isPrimitive() && t.dimension().equals("")) { + String n = t.simpleTypeName(); + if (("byte".equals(n) + || "short".equals(n) + || "int".equals(n) + || "long".equals(n) + || "float".equals(n) + || "double".equals(n)) && t.dimension().equals("")) { + result += "0"; + } + else if ("char".equals(n)) { + result += "'\\0'"; + } + else if ("boolean".equals(n)) { + result += "false"; + } + else { + result += "<<unknown-" + n + ">>"; + } + } else { + //put null in each super class method. Cast null to the correct type + //to avoid collisions with other constructors. If the type is generic + //don't cast it + result += (!t.isTypeVariable() ? "(" + t.qualifiedTypeName() + t.dimension() + + ")" : "") + "null"; + } + if (i != N-1) { + result += ","; + } + } + result += "); "; + return result; + } else { + return ""; + } + } + + static void writeAnnotations(PrintStream stream, AnnotationInstanceInfo[] annotations) { + for (AnnotationInstanceInfo ann: annotations) { + if (!ann.type().isHidden()) { + stream.println(ann.toString()); + } + } + } + + static void writeAnnotationElement(PrintStream stream, MethodInfo ann) { + stream.print(ann.returnType().fullName()); + stream.print(" "); + stream.print(ann.name()); + stream.print("()"); + AnnotationValueInfo def = ann.defaultAnnotationElementValue(); + if (def != null) { + stream.print(" default "); + stream.print(def.valueString()); + } + stream.println(";"); + } + + static void writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses, + HashSet notStrippable) { + // extract the set of packages, sort them by name, and write them out in that order + Set<PackageInfo> allClassKeys = allClasses.keySet(); + PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]); + Arrays.sort(allPackages, PackageInfo.comparator); + + xmlWriter.println("<api>"); + for (PackageInfo pack : allPackages) { + writePackageXML(xmlWriter, pack, allClasses.get(pack), notStrippable); + } + xmlWriter.println("</api>"); + } + + static void writePackageXML(PrintStream xmlWriter, PackageInfo pack, List<ClassInfo> classList, + HashSet notStrippable) { + ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]); + Arrays.sort(classes, ClassInfo.comparator); + xmlWriter.println("<package name=\"" + pack.name() + "\"\n" + //+ " source=\"" + pack.position() + "\"\n" + + ">"); + for (ClassInfo cl : classes) { + writeClassXML(xmlWriter, cl, notStrippable); + } + xmlWriter.println("</package>"); + + + } + + static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet notStrippable) { + String scope = DroidDoc.scope(cl); + String deprecatedString = ""; + String declString = (cl.isInterface()) ? "interface" : "class"; + if (cl.isDeprecated()) { + deprecatedString = "deprecated"; + } else { + deprecatedString = "not deprecated"; + } + xmlWriter.println("<" + declString + " name=\"" + cl.name() + "\""); + if (!cl.isInterface() && !cl.qualifiedName().equals("java.lang.Object")) { + xmlWriter.println(" extends=\"" + ((cl.realSuperclass() == null) + ? "java.lang.Object" + : cl.realSuperclass().qualifiedName()) + "\""); + } + xmlWriter.println(" abstract=\"" + cl.isAbstract() + "\"\n" + + " static=\"" + cl.isStatic() + "\"\n" + + " final=\"" + cl.isFinal() + "\"\n" + + " deprecated=\"" + deprecatedString + "\"\n" + + " visibility=\"" + scope + "\"\n" + //+ " source=\"" + cl.position() + "\"\n" + + ">"); + + ClassInfo[] interfaces = cl.realInterfaces(); + Arrays.sort(interfaces, ClassInfo.comparator); + for (ClassInfo iface : interfaces) { + if (notStrippable.contains(iface)) { + xmlWriter.println("<implements name=\"" + iface.qualifiedName() + "\">"); + xmlWriter.println("</implements>"); + } + } + + MethodInfo[] constructors = cl.constructors(); + Arrays.sort(constructors, MethodInfo.comparator); + for (MethodInfo mi : constructors) { + writeConstructorXML(xmlWriter, mi); + } + + MethodInfo[] methods = cl.allSelfMethods(); + Arrays.sort(methods, MethodInfo.comparator); + for (MethodInfo mi : methods) { + if (!methodIsOverride(mi)) { + writeMethodXML(xmlWriter, mi); + } + } + + FieldInfo[] fields = cl.allSelfFields(); + Arrays.sort(fields, FieldInfo.comparator); + for (FieldInfo fi : fields) { + writeFieldXML(xmlWriter, fi); + } + xmlWriter.println("</" + declString + ">"); + + } + + static void writeMethodXML(PrintStream xmlWriter, MethodInfo mi) { + String scope = DroidDoc.scope(mi); + String deprecatedString = ""; + if (mi.isDeprecated()) { + deprecatedString = "deprecated"; + } else { + deprecatedString = "not deprecated"; + } + xmlWriter.println("<method name=\"" + mi.name() + "\"\n" + + ((mi.returnType() != null) + ? " return=\"" + mi.returnType().qualifiedTypeName() + "\"\n" + : "") + + " abstract=\"" + mi.isAbstract() + "\"\n" + + " native=\"" + mi.isNative() + "\"\n" + + " synchronized=\"" + mi.isSynchronized() + "\"\n" + + " static=\"" + mi.isStatic() + "\"\n" + + " final=\"" + mi.isFinal() + "\"\n" + + " deprecated=\""+ deprecatedString + "\"\n" + + " visibility=\"" + scope + "\"\n" + //+ " source=\"" + mi.position() + "\"\n" + + ">"); + + // write parameters in declaration order + for (ParameterInfo pi : mi.parameters()) { + writeParameterXML(xmlWriter, pi); + } + + // but write exceptions in canonicalized order + ClassInfo[] exceptions = mi.thrownExceptions(); + Arrays.sort(exceptions, ClassInfo.comparator); + for (ClassInfo pi : exceptions) { + xmlWriter.println("<exception name=\"" + pi.name() +"\" type=\"" + pi.qualifiedName() + + "\">"); + xmlWriter.println("</exception>"); + } + xmlWriter.println("</method>"); + } + + static void writeConstructorXML(PrintStream xmlWriter, MethodInfo mi) { + String scope = DroidDoc.scope(mi); + String deprecatedString = ""; + if (mi.isDeprecated()) { + deprecatedString = "deprecated"; + } else { + deprecatedString = "not deprecated"; + } + xmlWriter.println("<constructor name=\"" + mi.name() + "\"\n" + + " type=\"" + mi.containingClass().qualifiedName() + "\"\n" + + " static=\"" + mi.isStatic() + "\"\n" + + " final=\"" + mi.isFinal() + "\"\n" + + " deprecated=\"" + deprecatedString + "\"\n" + + " visibility=\"" + scope +"\"\n" + //+ " source=\"" + mi.position() + "\"\n" + + ">"); + + ClassInfo[] exceptions = mi.thrownExceptions(); + Arrays.sort(exceptions, ClassInfo.comparator); + for (ClassInfo pi : exceptions) { + xmlWriter.println("<exception name=\"" + pi.name() +"\" type=\"" + pi.qualifiedName() + + "\">"); + xmlWriter.println("</exception>"); + } + xmlWriter.println("</constructor>"); + } + + static void writeParameterXML(PrintStream xmlWriter, ParameterInfo pi) { + xmlWriter.println("<parameter name=\"" + pi.name() + "\" type=\"" + + pi.type().qualifiedTypeName() + "\">"); + xmlWriter.println("</parameter>"); + } + + static void writeFieldXML(PrintStream xmlWriter, FieldInfo fi) { + String scope = DroidDoc.scope(fi); + String deprecatedString = ""; + if (fi.isDeprecated()) { + deprecatedString = "deprecated"; + } else { + deprecatedString = "not deprecated"; + } + //need to make sure value is valid XML + String value = makeXMLcompliant(fi.constantLiteralValue()); + xmlWriter.println("<field name=\"" + fi.name() +"\"\n" + + " type=\"" + fi.type().qualifiedTypeName() + "\"\n" + + " transient=\"" + fi.isTransient() + "\"\n" + + " volatile=\"" + fi.isVolatile() + "\"\n" + + (fieldIsInitialized(fi) ? " value=\"" + value + "\"\n" : "") + + " static=\"" + fi.isStatic() + "\"\n" + + " final=\"" + fi.isFinal() + "\"\n" + + " deprecated=\"" + deprecatedString + "\"\n" + + " visibility=\"" + scope + "\"\n" + //+ " source=\"" + fi.position() + "\"\n" + + ">"); + xmlWriter.println("</field>"); + } + + static String makeXMLcompliant(String s) { + String returnString = ""; + returnString = s.replaceAll("&", "&"); + returnString = returnString.replaceAll("<", "<"); + returnString = returnString.replaceAll(">", ">"); + returnString = returnString.replaceAll("\"", """); + returnString = returnString.replaceAll("'", "&pos;"); + return returnString; + } +} + diff --git a/tools/droiddoc/src/TagInfo.java b/tools/droiddoc/src/TagInfo.java new file mode 100644 index 0000000..d25c500 --- /dev/null +++ b/tools/droiddoc/src/TagInfo.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; + +public class TagInfo +{ + private String mName; + private String mText; + private String mKind; + private SourcePositionInfo mPosition; + + TagInfo(String n, String k, String t, SourcePositionInfo sp) + { + mName = n; + mText = t; + mKind = k; + mPosition = sp; + } + + String name() + { + return mName; + } + + String text() + { + return mText; + } + + String kind() + { + return mKind; + } + + SourcePositionInfo position() { + return mPosition; + } + + void setKind(String kind) { + mKind = kind; + } + + public void makeHDF(HDF data, String base) + { + data.setValue(base + ".name", name()); + data.setValue(base + ".text", text()); + data.setValue(base + ".kind", kind()); + } + + public static void makeHDF(HDF data, String base, TagInfo[] tags) + { + makeHDF(data, base, tags, null, 0, 0); + } + + public static void makeHDF(HDF data, String base, InheritedTags tags) + { + makeHDF(data, base, tags.tags(), tags.inherited(), 0, 0); + } + + private static int makeHDF(HDF data, String base, TagInfo[] tags, + InheritedTags inherited, int j, int depth) + { + int i; + int len = tags.length; + if (len == 0 && inherited != null) { + j = makeHDF(data, base, inherited.tags(), inherited.inherited(), j, depth+1); + } else { + for (i=0; i<len; i++, j++) { + TagInfo t = tags[i]; + if (inherited != null && t.name().equals("@inheritDoc")) { + j = makeHDF(data, base, inherited.tags(), + inherited.inherited(), j, depth+1); + } else { + if (t.name().equals("@inheritDoc")) { + Errors.error(Errors.BAD_INHERITDOC, t.mPosition, + "@inheritDoc on class/method that is not inherited"); + } + t.makeHDF(data, base + "." + j); + } + } + } + return j; + } +} + diff --git a/tools/droiddoc/src/TextTagInfo.java b/tools/droiddoc/src/TextTagInfo.java new file mode 100644 index 0000000..dcdfdd9 --- /dev/null +++ b/tools/droiddoc/src/TextTagInfo.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +public class TextTagInfo extends TagInfo { + TextTagInfo(String n, String k, String t, SourcePositionInfo p) { + super(n, k, DroidDoc.escape(t), p); + } +} diff --git a/tools/droiddoc/src/ThrowsTagInfo.java b/tools/droiddoc/src/ThrowsTagInfo.java new file mode 100644 index 0000000..318a57d --- /dev/null +++ b/tools/droiddoc/src/ThrowsTagInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +public class ThrowsTagInfo extends ParsedTagInfo +{ + static final Pattern PATTERN = Pattern.compile( + "(\\S+)\\s+(.*)", + Pattern.DOTALL); + private ClassInfo mException; + + public ThrowsTagInfo(String name, String kind, String text, + ContainerInfo base, SourcePositionInfo sp) + { + super(name, kind, text, base, sp); + + Matcher m = PATTERN.matcher(text); + if (m.matches()) { + setCommentText(m.group(2)); + String className = m.group(1); + if (base instanceof ClassInfo) { + mException = ((ClassInfo)base).findClass(className); + } + if (mException == null) { + mException = Converter.obtainClass(className); + } + } + } + + public ThrowsTagInfo(String name, String kind, String text, + ClassInfo exception, String exceptionComment, + ContainerInfo base, SourcePositionInfo sp) + { + super(name, kind, text, base, sp); + mException = exception; + setCommentText(exceptionComment); + } + + public ClassInfo exception() + { + return mException; + } + + public TypeInfo exceptionType() + { + if (mException != null) { + return mException.asTypeInfo(); + } else { + return null; + } + } + + public static void makeHDF(HDF data, String base, ThrowsTagInfo[] tags) + { + for (int i=0; i<tags.length; i++) { + TagInfo.makeHDF(data, base + '.' + i + ".comment", + tags[i].commentTags()); + if (tags[i].exceptionType() != null) { + tags[i].exceptionType().makeHDF(data, base + "." + i + ".type"); + } + } + } + + +} + diff --git a/tools/droiddoc/src/TodoFile.java b/tools/droiddoc/src/TodoFile.java new file mode 100644 index 0000000..ebef27e --- /dev/null +++ b/tools/droiddoc/src/TodoFile.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; + +public class TodoFile { + + public static final String MISSING = "No description text"; + + public static boolean areTagsUseful(InheritedTags tags) { + while (tags != null) { + if (areTagsUseful(tags.tags())) { + return true; + } + tags = tags.inherited(); + } + return false; + } + + public static boolean areTagsUseful(TagInfo[] tags) { + for (TagInfo t: tags) { + if ("Text".equals(t.name()) && t.text().trim().length() != 0) { + return true; + } + if ("@inheritDoc".equals(t.name())) { + return true; + } + } + return false; + } + + public static void setHDF(HDF data, String base, SourcePositionInfo pos, String name, + String descr) { + data.setValue(base + ".pos", pos.toString()); + data.setValue(base + ".name", name); + data.setValue(base + ".descr", descr); + } + + static class PackageStats { + String name; + public int total; + public int errors; + } + + public static String percent(int a, int b) { + return ""+Math.round((((b-a)/(float)b))*100) + "%"; + } + + public static void writeTodoFile(String filename) { + HDF data = DroidDoc.makeHDF(); + DroidDoc.setPageTitle(data, "Missing Documentation"); + TreeMap<String,PackageStats> packageStats = new TreeMap<String,PackageStats>(); + + ClassInfo[] classes = Converter.rootClasses(); + Arrays.sort(classes); + + int classIndex = 0; + + for (ClassInfo cl: classes) { + if (cl.isHidden()) { + continue; + } + + String classBase = "classes." + classIndex; + + String base = classBase + ".errors."; + int errors = 0; + int total = 1; + + if (!areTagsUseful(cl.inlineTags())) { + setHDF(data, base + errors, cl.position(), "<class comment>", MISSING); + errors++; + } + + + for (MethodInfo m: cl.constructors()) { + boolean good = true; + total++; + if (m.checkLevel()) { + if (!areTagsUseful(m.inlineTags())) { + setHDF(data, base + errors, m.position(), m.name() + m.prettySignature(), + MISSING); + good = false; + } + } + if (!good) { + errors++; + } + } + + for (MethodInfo m: cl.selfMethods()) { + boolean good = true; + total++; + if (m.checkLevel()) { + if (!areTagsUseful(m.inlineTags())) { + setHDF(data, base + errors, m.position(), m.name() + m.prettySignature(), + MISSING); + good = false; + } + } + if (!good) { + errors++; + } + } + + + for (FieldInfo f: cl.enumConstants()) { + boolean good = true; + total++; + if (f.checkLevel()) { + if (!areTagsUseful(f.inlineTags())) { + setHDF(data, base + errors, f.position(), f.name(), MISSING); + good = false; + } + } + if (!good) { + errors++; + } + } + + for (FieldInfo f: cl.selfFields()) { + boolean good = true; + total++; + if (f.checkLevel()) { + if (!areTagsUseful(f.inlineTags())) { + setHDF(data, base + errors, f.position(), f.name(), MISSING); + good = false; + } + } + if (!good) { + errors++; + } + } + + if (errors > 0) { + data.setValue(classBase + ".qualified", cl.qualifiedName()); + data.setValue(classBase + ".errorCount", ""+errors); + data.setValue(classBase + ".totalCount", ""+total); + data.setValue(classBase + ".percentGood", percent(errors, total)); + } + + PackageInfo pkg = cl.containingPackage(); + String pkgName = pkg != null ? pkg.name() : ""; + PackageStats ps = packageStats.get(pkgName); + if (ps == null) { + ps = new PackageStats(); + ps.name = pkgName; + packageStats.put(pkgName, ps); + } + ps.total += total; + ps.errors += errors; + + classIndex++; + } + + int allTotal = 0; + int allErrors = 0; + + int i = 0; + for (PackageStats ps: packageStats.values()) { + data.setValue("packages." + i + ".name", ""+ps.name); + data.setValue("packages." + i + ".errorCount", ""+ps.errors); + data.setValue("packages." + i + ".totalCount", ""+ps.total); + data.setValue("packages." + i + ".percentGood", percent(ps.errors, ps.total)); + + allTotal += ps.total; + allErrors += ps.errors; + + i++; + } + + data.setValue("all.errorCount", ""+allErrors); + data.setValue("all.totalCount", ""+allTotal); + data.setValue("all.percentGood", percent(allErrors, allTotal)); + + ClearPage.write(data, "todo.cs", filename, true); + } +} + diff --git a/tools/droiddoc/src/TypeInfo.java b/tools/droiddoc/src/TypeInfo.java new file mode 100644 index 0000000..68cdd8e --- /dev/null +++ b/tools/droiddoc/src/TypeInfo.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +import org.clearsilver.HDF; +import org.clearsilver.CS; +import java.util.*; +import java.io.*; + +public class TypeInfo +{ + public TypeInfo(boolean isPrimitive, String dimension, + String simpleTypeName, String qualifiedTypeName, + ClassInfo cl) + { + mIsPrimitive = isPrimitive; + mDimension = dimension; + mSimpleTypeName = simpleTypeName; + mQualifiedTypeName = qualifiedTypeName; + mClass = cl; + } + + public ClassInfo asClassInfo() + { + return mClass; + } + + public boolean isPrimitive() + { + return mIsPrimitive; + } + + public String dimension() + { + return mDimension; + } + + public String simpleTypeName() + { + return mSimpleTypeName; + } + + public String qualifiedTypeName() + { + return mQualifiedTypeName; + } + + public String fullName() + { + if (mFullName != null) { + return mFullName; + } else { + return fullName(new HashSet()); + } + } + + public static String typeArgumentsName(TypeInfo[] args, HashSet<String> typeVars) + { + String result = "<"; + for (int i=0; i<args.length; i++) { + result += args[i].fullName(typeVars); + if (i != args.length-1) { + result += ", "; + } + } + result += ">"; + return result; + } + + public String fullName(HashSet<String> typeVars) + { + if (mIsTypeVariable) { + if (typeVars.contains(mQualifiedTypeName)) { + // don't recurse forever with the parameters. This handles + // Enum<K extends Enum<K>> + return mQualifiedTypeName + mDimension; + } + typeVars.add(mQualifiedTypeName); + } +/* + if (mFullName != null) { + return mFullName; + } +*/ + mFullName = mQualifiedTypeName; + if (mTypeArguments != null && mTypeArguments.length > 0) { + mFullName += typeArgumentsName(mTypeArguments, typeVars); + } + else if (mSuperBounds != null && mSuperBounds.length > 0) { + mFullName += " super " + mSuperBounds[0].fullName(typeVars); + for (int i=1; i<mSuperBounds.length; i++) { + mFullName += " & " + mSuperBounds[i].fullName(typeVars); + } + } + else if (mExtendsBounds != null && mExtendsBounds.length > 0) { + mFullName += " extends " + mExtendsBounds[0].fullName(typeVars); + for (int i=1; i<mExtendsBounds.length; i++) { + mFullName += " & " + mExtendsBounds[i].fullName(typeVars); + } + } + mFullName += mDimension; + return mFullName; + } + + public TypeInfo[] typeArguments() + { + return mTypeArguments; + } + + public void makeHDF(HDF data, String base) + { + makeHDFRecursive(data, base, false, false, new HashSet<String>()); + } + + public void makeQualifiedHDF(HDF data, String base) + { + makeHDFRecursive(data, base, true, false, new HashSet<String>()); + } + + public void makeHDF(HDF data, String base, boolean isLastVararg, + HashSet<String> typeVariables) + { + makeHDFRecursive(data, base, false, isLastVararg, typeVariables); + } + + public void makeQualifiedHDF(HDF data, String base, HashSet<String> typeVariables) + { + makeHDFRecursive(data, base, true, false, typeVariables); + } + + private void makeHDFRecursive(HDF data, String base, boolean qualified, + boolean isLastVararg, HashSet<String> typeVars) + { + String label = qualified ? qualifiedTypeName() : simpleTypeName(); + label += (isLastVararg) ? "..." : dimension(); + data.setValue(base + ".label", label); + ClassInfo cl = asClassInfo(); + if (mIsTypeVariable || mIsWildcard) { + // could link to an @param tag on the class to describe this + // but for now, just don't make it a link + } + else if (!isPrimitive() && cl != null && cl.isIncluded()) { + data.setValue(base + ".link", cl.htmlPage()); + } + + if (mIsTypeVariable) { + if (typeVars.contains(qualifiedTypeName())) { + // don't recurse forever with the parameters. This handles + // Enum<K extends Enum<K>> + return; + } + typeVars.add(qualifiedTypeName()); + } + if (mTypeArguments != null) { + TypeInfo.makeHDF(data, base + ".typeArguments", mTypeArguments, qualified, typeVars); + } + if (mSuperBounds != null) { + TypeInfo.makeHDF(data, base + ".superBounds", mSuperBounds, qualified, typeVars); + } + if (mExtendsBounds != null) { + TypeInfo.makeHDF(data, base + ".extendsBounds", mExtendsBounds, qualified, typeVars); + } + } + + public static void makeHDF(HDF data, String base, TypeInfo[] types, boolean qualified, + HashSet<String> typeVariables) + { + final int N = types.length; + for (int i=0; i<N; i++) { + types[i].makeHDFRecursive(data, base + "." + i, qualified, false, typeVariables); + } + } + + public static void makeHDF(HDF data, String base, TypeInfo[] types, boolean qualified) + { + makeHDF(data, base, types, qualified, new HashSet<String>()); + } + + void setTypeArguments(TypeInfo[] args) + { + mTypeArguments = args; + } + + void setBounds(TypeInfo[] superBounds, TypeInfo[] extendsBounds) + { + mSuperBounds = superBounds; + mExtendsBounds = extendsBounds; + } + + void setIsTypeVariable(boolean b) + { + mIsTypeVariable = b; + } + + void setIsWildcard(boolean b) + { + mIsWildcard = b; + } + + static HashSet<String> typeVariables(TypeInfo[] params) + { + return typeVariables(params, new HashSet()); + } + + static HashSet<String> typeVariables(TypeInfo[] params, HashSet<String> result) + { + for (TypeInfo t: params) { + if (t.mIsTypeVariable) { + result.add(t.mQualifiedTypeName); + } + } + return result; + } + + + public boolean isTypeVariable() + { + return mIsTypeVariable; + } + + public String defaultValue() { + if (mIsPrimitive) { + if ("boolean".equals(mSimpleTypeName)) { + return "false"; + } else { + return "0"; + } + } else { + return "null"; + } + } + + public String toString(){ + String returnString = ""; + returnString += "Primitive?: " + mIsPrimitive + " TypeVariable?: " + + mIsTypeVariable + " Wildcard?: " + mIsWildcard + " Dimension: " + mDimension + + " QualifedTypeName: " + mQualifiedTypeName; + + if (mTypeArguments != null){ + returnString += "\nTypeArguments: "; + for (TypeInfo tA : mTypeArguments){ + returnString += tA.qualifiedTypeName() + "(" + tA + ") "; + } + } + if (mSuperBounds != null){ + returnString += "\nSuperBounds: "; + for (TypeInfo tA : mSuperBounds){ + returnString += tA.qualifiedTypeName() + "(" + tA + ") "; + } + } + if (mExtendsBounds != null){ + returnString += "\nExtendsBounds: "; + for (TypeInfo tA : mExtendsBounds){ + returnString += tA.qualifiedTypeName() + "(" + tA + ") "; + } + } + return returnString; + } + + private boolean mIsPrimitive; + private boolean mIsTypeVariable; + private boolean mIsWildcard; + private String mDimension; + private String mSimpleTypeName; + private String mQualifiedTypeName; + private ClassInfo mClass; + private TypeInfo[] mTypeArguments; + private TypeInfo[] mSuperBounds; + private TypeInfo[] mExtendsBounds; + private String mFullName; +} |