Initial import of Doclava project
Change-Id: Ia5ae56f1700fce98e0ae6954fa2df617ec0537bb
diff --git a/src/com/google/doclava/AnnotationInstanceInfo.java b/src/com/google/doclava/AnnotationInstanceInfo.java
new file mode 100644
index 0000000..bb498d7
--- /dev/null
+++ b/src/com/google/doclava/AnnotationInstanceInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+public 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;
+ }
+
+ @Override
+ 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/src/com/google/doclava/AnnotationValueInfo.java b/src/com/google/doclava/AnnotationValueInfo.java
new file mode 100644
index 0000000..2022a59
--- /dev/null
+++ b/src/com/google/doclava/AnnotationValueInfo.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+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/src/com/google/doclava/AttrTagInfo.java b/src/com/google/doclava/AttrTagInfo.java
new file mode 100644
index 0000000..909cacf
--- /dev/null
+++ b/src/com/google/doclava/AttrTagInfo.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+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;
+ }
+
+ @Override
+ public String name() {
+ return NAME_COMMAND.equals(mCommand) ? mAttrName : null;
+ }
+
+ public Comment description() {
+ return DESCRIPTION_COMMAND.equals(mCommand) ? mDescrComment : null;
+ }
+
+ @Override
+ public void makeHDF(Data data, String base) {
+ super.makeHDF(data, base);
+ }
+
+ public void setAttribute(AttributeInfo info) {
+ mAttrInfo = info;
+ }
+
+ public static void makeReferenceHDF(Data 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/src/com/google/doclava/AttributeInfo.java b/src/com/google/doclava/AttributeInfo.java
new file mode 100644
index 0000000..25d424f
--- /dev/null
+++ b/src/com/google/doclava/AttributeInfo.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+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, SourcePositionInfo.UNKNOWN);
+ }
+ return mComment;
+ }
+
+ public String anchor() {
+ return "attr_" + name();
+ }
+
+ public String htmlPage() {
+ return mClass.htmlPage() + "#" + anchor();
+ }
+
+ public void makeHDF(Data 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.prettySignature());
+ }
+ }
+
+ public boolean checkLevel() {
+ return attrField.checkLevel();
+ }
+}
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
new file mode 100644
index 0000000..77863fd
--- /dev/null
+++ b/src/com/google/doclava/ClassInfo.java
@@ -0,0 +1,1716 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.doclava.apicheck.ApiInfo;
+
+import com.sun.javadoc.*;
+import java.util.*;
+
+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());
+ }
+ };
+
+ /**
+ * Constructs a stub representation of a class.
+ */
+ public ClassInfo(String qualifiedName) {
+ super("", SourcePositionInfo.UNKNOWN);
+
+ mQualifiedName = qualifiedName;
+ if (qualifiedName.lastIndexOf('.') != -1) {
+ mName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
+ } else {
+ mName = 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 = new ArrayList<ClassInfo>();
+ for (ClassInfo cl : interfaces) {
+ mRealInterfaces.add(cl);
+ }
+ 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 =
+ Doclava.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();
+ }
+ }
+
+ @Override
+ 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 = new ClassInfo[mRealInterfaces.size()];
+ mRealInterfaces.toArray(mInterfaces);
+ }
+ Arrays.sort(mInterfaces, ClassInfo.qualifiedComparator);
+ }
+ return mInterfaces;
+ }
+
+ public ClassInfo[] realInterfaces() {
+ ClassInfo[] classInfos = new ClassInfo[mRealInterfaces.size()];
+ return mRealInterfaces.toArray(classInfos);
+ }
+
+ 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().isDeprecated();
+ 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() {
+ // Should we also do the interfaces?
+ return comment().deprecatedTags();
+ }
+
+ 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.getHashableName();
+ all.put(key, method);
+ }
+ }
+ }
+
+ ClassInfo superclass = superclass();
+ if (superclass != null) {
+ MethodInfo[] inhereted = superclass.methods();
+ for (MethodInfo method : inhereted) {
+ String key = method.getHashableName();
+ all.put(key, method);
+ }
+ }
+
+ MethodInfo[] methods = selfMethods();
+ for (MethodInfo method : methods) {
+ String key = method.getHashableName();
+ all.put(key, method);
+ }
+
+ mMethods = all.values().toArray(new MethodInfo[all.size()]);
+ Arrays.sort(mMethods, MethodInfo.comparator);
+ }
+ 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;
+ }
+
+ private 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
+ if (mAllSelfMethods != null) {
+ for (int i = 0; i < mAllSelfMethods.length; i++) {
+ MethodInfo m = mAllSelfMethods[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) {
+ mApiCheckMethods.put(method.getHashableName(), method);
+
+ if (mAllSelfMethods == null) {
+ mAllSelfMethods = new MethodInfo[] { method };
+ return;
+ }
+
+ MethodInfo[] methods = new MethodInfo[mAllSelfMethods.length + 1];
+ int i = 0;
+ for (MethodInfo m : mAllSelfMethods) {
+ methods[i++] = m;
+ }
+ methods[i] = method;
+ mAllSelfMethods = methods;
+ }
+
+ public void setContainingPackage(PackageInfo pkg) {
+ mContainingPackage = pkg;
+ }
+
+ 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 = Doclava.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.prettySignature(), htmlPage + "#" + m.anchor(),
+ "constructor in " + qualifiedName));
+ }
+ }
+
+ // protected constructors
+ if (Doclava.checkLevel(Doclava.SHOW_PROTECTED)) {
+ for (MethodInfo m : ctors) {
+ if (m.isProtected() && m.checkLevel()) {
+ keywords.add(new KeywordEntry(m.prettySignature(),
+ htmlPage + "#" + m.anchor(), "constructor in " + qualifiedName));
+ }
+ }
+ }
+
+ // package private constructors
+ if (Doclava.checkLevel(Doclava.SHOW_PACKAGE)) {
+ for (MethodInfo m : ctors) {
+ if (m.isPackagePrivate() && m.checkLevel()) {
+ keywords.add(new KeywordEntry(m.prettySignature(),
+ htmlPage + "#" + m.anchor(), "constructor in " + qualifiedName));
+ }
+ }
+ }
+
+ // private constructors
+ if (Doclava.checkLevel(Doclava.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 (Doclava.checkLevel(Doclava.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 (Doclava.checkLevel(Doclava.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 (Doclava.checkLevel(Doclava.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(Data 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(Data 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(Data 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());
+ data.setValue(base + ".since", getSince());
+ setFederatedReferences(data, base);
+ }
+
+ /**
+ * Turns into the main class page
+ */
+ public void makeHDF(Data 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);
+ }
+ data.setValue("class.since", getSince());
+ setFederatedReferences(data, "class");
+
+ // 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++;
+ }
+
+ // hide special cases
+ if ("java.lang.Object".equals(qualified) || "java.io.Serializable".equals(qualified)) {
+ data.setValue("class.subclasses.hidden", "1");
+ } else {
+ data.setValue("class.subclasses.hidden", "0");
+ }
+
+ // 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 (Doclava.checkLevel(Doclava.SHOW_PROTECTED)) {
+ i = 0;
+ for (MethodInfo ctor : ctors) {
+ if (ctor.isProtected()) {
+ ctor.makeHDF(data, "class.ctors.protected." + i);
+ i++;
+ }
+ }
+ }
+
+ // package private constructors
+ if (Doclava.checkLevel(Doclava.SHOW_PACKAGE)) {
+ i = 0;
+ for (MethodInfo ctor : ctors) {
+ if (ctor.isPackagePrivate()) {
+ ctor.makeHDF(data, "class.ctors.package." + i);
+ i++;
+ }
+ }
+ }
+
+ // private constructors
+ if (Doclava.checkLevel(Doclava.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 (Doclava.checkLevel(Doclava.SHOW_PROTECTED)) {
+ i = 0;
+ for (MethodInfo method : methods) {
+ if (method.isProtected()) {
+ method.makeHDF(data, "class.methods.protected." + i);
+ i++;
+ }
+ }
+ }
+
+ // package private methods
+ if (Doclava.checkLevel(Doclava.SHOW_PACKAGE)) {
+ i = 0;
+ for (MethodInfo method : methods) {
+ if (method.isPackagePrivate()) {
+ method.makeHDF(data, "class.methods.package." + i);
+ i++;
+ }
+ }
+ }
+
+ // private methods
+ if (Doclava.checkLevel(Doclava.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(Data 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);
+ }
+
+ if (cl.mIsIncluded) {
+ data.setValue(base + ".included", "true");
+ } else {
+ Doclava.federationTagger.tagAll(new ClassInfo[] {cl});
+ if (!cl.getFederatedReferences().isEmpty()) {
+ FederatedSite site = cl.getFederatedReferences().iterator().next();
+ data.setValue(base + ".link", site.linkFor(cl.htmlPage()));
+ data.setValue(base + ".federated", site.name());
+ }
+ }
+
+ // 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++;
+ }
+ }
+ }
+
+ @Override
+ 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 != null && 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, boolean varargs) {
+ 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, varargs)) {
+ return method;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public MethodInfo findMethod(String name, String[] params, String[] dimensions, boolean varargs) {
+ // first look on our class, and our superclasses
+
+ // for methods
+ MethodInfo rv;
+ rv = matchMethod(methods(), name, params, dimensions, varargs);
+
+ if (rv != null) {
+ return rv;
+ }
+
+ // for constructors
+ rv = matchMethod(constructors(), name, params, dimensions, varargs);
+ if (rv != null) {
+ return rv;
+ }
+
+ // then recursively look at our containing class
+ ClassInfo containing = containingClass();
+ if (containing != null) {
+ return containing.findMethod(name, params, dimensions, varargs);
+ }
+
+ return null;
+ }
+
+ public boolean supportsMethod(MethodInfo method) {
+ for (MethodInfo m : methods()) {
+ if (m.getHashableName().equals(method.getHashableName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ 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 String scope() {
+ if (isPublic()) {
+ return "public";
+ } else if (isProtected()) {
+ return "protected";
+ } else if (isPackagePrivate()) {
+ return "";
+ } else if (isPrivate()) {
+ return "private";
+ } else {
+ throw new RuntimeException("invalid scope for object " + this);
+ }
+ }
+
+ public void setHiddenMethods(MethodInfo[] mInfo) {
+ mHiddenMethods = mInfo;
+ }
+
+ public MethodInfo[] getHiddenMethods() {
+ return mHiddenMethods;
+ }
+
+ @Override
+ 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 List<ClassInfo> mRealInterfaces = new ArrayList<ClassInfo>();
+ 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;
+
+ // TODO: Temporary members from apicheck migration.
+ private HashMap<String, MethodInfo> mApiCheckMethods = new HashMap<String, MethodInfo>();
+ private HashMap<String, FieldInfo> mApiCheckFields = new HashMap<String, FieldInfo>();
+ private HashMap<String, ConstructorInfo> mApiCheckConstructors
+ = new HashMap<String, ConstructorInfo>();
+
+ /**
+ * Returns true if {@code cl} implements the interface {@code iface} either by either being that
+ * interface, implementing that interface or extending a type that implements the interface.
+ */
+ private boolean implementsInterface(ClassInfo cl, String iface) {
+ if (cl.qualifiedName().equals(iface)) {
+ return true;
+ }
+ for (ClassInfo clImplements : cl.interfaces()) {
+ if (implementsInterface(clImplements, iface)) {
+ return true;
+ }
+ }
+ if (cl.mSuperclass != null && implementsInterface(cl.mSuperclass, iface)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ public void addInterface(ClassInfo iface) {
+ mRealInterfaces.add(iface);
+ }
+
+ public void addConstructor(ConstructorInfo cInfo) {
+ mApiCheckConstructors.put(cInfo.getHashableName(), cInfo);
+
+ }
+
+ public void addField(FieldInfo fInfo) {
+ mApiCheckFields.put(fInfo.name(), fInfo);
+
+ }
+
+ public void setSuperClass(ClassInfo superclass) {
+ mSuperclass = superclass;
+ }
+
+ public Map<String, ConstructorInfo> allConstructorsMap() {
+ return mApiCheckConstructors;
+ }
+
+ public Map<String, FieldInfo> allFields() {
+ return mApiCheckFields;
+ }
+
+ /**
+ * Returns all methods defined directly in this class. For a list of all
+ * methods supported by this class, see {@link #methods()}.
+ */
+
+ public Map<String, MethodInfo> allMethods() {
+ return mApiCheckMethods;
+ }
+
+ /**
+ * Returns the class hierarchy for this class, starting with this class.
+ */
+ public Iterable<ClassInfo> hierarchy() {
+ List<ClassInfo> result = new ArrayList<ClassInfo>(4);
+ for (ClassInfo c = this; c != null; c = c.mSuperclass) {
+ result.add(c);
+ }
+ return result;
+ }
+
+ public String superclassName() {
+ if (mSuperclass == null) {
+ if (mQualifiedName.equals("java.lang.Object")) {
+ return null;
+ }
+ throw new UnsupportedOperationException("Superclass not set for " + qualifiedName());
+ }
+ return mSuperclass.mQualifiedName;
+ }
+
+ public void setAnnotations(AnnotationInstanceInfo[] annotations) {
+ mAnnotations = annotations;
+ }
+
+ public boolean isConsistent(ClassInfo cl) {
+ boolean consistent = true;
+
+ if (isInterface() != cl.isInterface()) {
+ Errors.error(Errors.CHANGED_CLASS, cl.position(), "Class " + cl.qualifiedName()
+ + " changed class/interface declaration");
+ consistent = false;
+ }
+ for (ClassInfo iface : mRealInterfaces) {
+ if (!implementsInterface(cl, iface.mQualifiedName)) {
+ Errors.error(Errors.REMOVED_INTERFACE, cl.position(), "Class " + qualifiedName()
+ + " no longer implements " + iface);
+ }
+ }
+ for (ClassInfo iface : cl.mRealInterfaces) {
+ if (!implementsInterface(this, iface.mQualifiedName)) {
+ Errors.error(Errors.ADDED_INTERFACE, cl.position(), "Added interface " + iface
+ + " to class " + qualifiedName());
+ consistent = false;
+ }
+ }
+
+ for (MethodInfo mInfo : mApiCheckMethods.values()) {
+ if (cl.mApiCheckMethods.containsKey(mInfo.getHashableName())) {
+ if (!mInfo.isConsistent(cl.mApiCheckMethods.get(mInfo.getHashableName()))) {
+ consistent = false;
+ }
+ } else {
+ /*
+ * This class formerly provided this method directly, and now does not. Check our ancestry
+ * to see if there's an inherited version that still fulfills the API requirement.
+ */
+ MethodInfo mi = ClassInfo.overriddenMethod(mInfo, cl);
+ if (mi == null) {
+ mi = ClassInfo.interfaceMethod(mInfo, cl);
+ }
+ if (mi == null) {
+ Errors.error(Errors.REMOVED_METHOD, mInfo.position(), "Removed public method "
+ + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ }
+ for (MethodInfo mInfo : cl.mApiCheckMethods.values()) {
+ if (!mApiCheckMethods.containsKey(mInfo.getHashableName())) {
+ /*
+ * Similarly to the above, do not fail if this "new" method is really an override of an
+ * existing superclass method.
+ */
+ MethodInfo mi = ClassInfo.overriddenMethod(mInfo, this);
+ if (mi == null) {
+ Errors.error(Errors.ADDED_METHOD, mInfo.position(), "Added public method "
+ + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ }
+
+ for (ConstructorInfo mInfo : mApiCheckConstructors.values()) {
+ if (cl.mApiCheckConstructors.containsKey(mInfo.getHashableName())) {
+ if (!mInfo.isConsistent(cl.mApiCheckConstructors.get(mInfo.getHashableName()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_METHOD, mInfo.position(), "Removed public constructor "
+ + mInfo.prettySignature());
+ consistent = false;
+ }
+ }
+ for (ConstructorInfo mInfo : cl.mApiCheckConstructors.values()) {
+ if (!mApiCheckConstructors.containsKey(mInfo.getHashableName())) {
+ Errors.error(Errors.ADDED_METHOD, mInfo.position(), "Added public constructor "
+ + mInfo.prettySignature());
+ consistent = false;
+ }
+ }
+
+ for (FieldInfo mInfo : mApiCheckFields.values()) {
+ if (cl.mApiCheckFields.containsKey(mInfo.name())) {
+ if (!mInfo.isConsistent(cl.mApiCheckFields.get(mInfo.name()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_FIELD, mInfo.position(), "Removed field "
+ + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ for (FieldInfo mInfo : cl.mApiCheckFields.values()) {
+ if (!mApiCheckFields.containsKey(mInfo.name())) {
+ Errors.error(Errors.ADDED_FIELD, mInfo.position(), "Added public field "
+ + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+
+ if (mIsAbstract != cl.mIsAbstract) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_ABSTRACT, cl.position(), "Class " + cl.qualifiedName()
+ + " changed abstract qualifier");
+ }
+
+ if (mIsFinal != cl.mIsFinal) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, cl.position(), "Class " + cl.qualifiedName()
+ + " changed final qualifier");
+ }
+
+ if (mIsStatic != cl.mIsStatic) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_STATIC, cl.position(), "Class " + cl.qualifiedName()
+ + " changed static qualifier");
+ }
+
+ if (!scope().equals(cl.scope())) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SCOPE, cl.position(), "Class " + cl.qualifiedName()
+ + " scope changed from " + scope() + " to " + cl.scope());
+ }
+
+ if (!isDeprecated() == cl.isDeprecated()) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_DEPRECATED, cl.position(), "Class " + cl.qualifiedName()
+ + " has changed deprecation state");
+ }
+
+ if (superclassName() != null) {
+ if (cl.superclassName() == null || !superclassName().equals(cl.superclassName())) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SUPERCLASS, cl.position(), "Class " + qualifiedName()
+ + " superclass changed from " + superclassName() + " to " + cl.superclassName());
+ }
+ } else if (cl.superclassName() != null) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SUPERCLASS, cl.position(), "Class " + qualifiedName()
+ + " superclass changed from " + "null to " + cl.superclassName());
+ }
+
+ return consistent;
+ }
+
+ // Find a superclass implementation of the given method.
+ public static MethodInfo overriddenMethod(MethodInfo candidate, ClassInfo newClassObj) {
+ if (newClassObj == null) {
+ return null;
+ }
+ for (MethodInfo mi : newClassObj.mApiCheckMethods.values()) {
+ if (mi.matches(candidate)) {
+ // found it
+ return mi;
+ }
+ }
+
+ // not found here. recursively search ancestors
+ return ClassInfo.overriddenMethod(candidate, newClassObj.mSuperclass);
+ }
+
+ // Find a superinterface declaration of the given method.
+ public static MethodInfo interfaceMethod(MethodInfo candidate, ClassInfo newClassObj) {
+ if (newClassObj == null) {
+ return null;
+ }
+ for (ClassInfo interfaceInfo : newClassObj.interfaces()) {
+ for (MethodInfo mi : interfaceInfo.mApiCheckMethods.values()) {
+ if (mi.matches(candidate)) {
+ return mi;
+ }
+ }
+ }
+ return ClassInfo.interfaceMethod(candidate, newClassObj.mSuperclass);
+ }
+
+ public boolean hasConstructor(MethodInfo constructor) {
+ String name = constructor.getHashableName();
+ for (ConstructorInfo ctor : mApiCheckConstructors.values()) {
+ if (name.equals(ctor.getHashableName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setTypeInfo(TypeInfo typeInfo) {
+ mTypeInfo = typeInfo;
+ }
+}
diff --git a/src/com/google/doclava/ClearPage.java b/src/com/google/doclava/ClearPage.java
new file mode 100644
index 0000000..4bb0784
--- /dev/null
+++ b/src/com/google/doclava/ClearPage.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+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"); }
+ */
+
+ private static ArrayList<String> mTemplateDirs = new ArrayList<String>();
+ private static boolean mTemplateDirSet = false;
+
+ private static ArrayList<String> mBundledTemplateDirs = new ArrayList<String>();
+
+ 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);
+ }
+
+ public static List<String> getTemplateDirs() {
+ return mTemplateDirs;
+ }
+
+ public static void addBundledTemplateDir(String dir) {
+ mTemplateDirSet = true;
+ mBundledTemplateDirs.add(dir);
+ }
+
+ public static List<String> getBundledTemplateDirs() {
+ return mBundledTemplateDirs;
+ }
+
+ 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(Data data, String templ, String filename, JSilver cs) {
+ write(data, templ, filename, false, cs);
+ }
+
+ public static void write(Data data, String templ, String filename) {
+ write(data, templ, filename, false, Doclava.jSilver);
+ }
+
+ public static void write(Data data, String templ, String filename, boolean fullPath) {
+ write(data, templ, filename, false, Doclava.jSilver);
+ }
+
+ public static void write(Data data, String templ, String filename, boolean fullPath, JSilver cs) {
+ 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");
+ }
+
+ File file = new File(outputFilename(filename));
+
+ ensureDirectory(file);
+
+ OutputStreamWriter stream = null;
+ try {
+ stream = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
+ String rendered = cs.render(templ, data);
+ 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 (!Doclava.htmlExtension.equals(".html") && htmlFile.endsWith(".html")) {
+ return htmlFile.substring(0, htmlFile.length() - 5) + Doclava.htmlExtension;
+ } else {
+ return htmlFile;
+ }
+ }
+
+}
diff --git a/src/com/google/doclava/CodeTagInfo.java b/src/com/google/doclava/CodeTagInfo.java
new file mode 100644
index 0000000..1a4a864
--- /dev/null
+++ b/src/com/google/doclava/CodeTagInfo.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+public class CodeTagInfo extends TagInfo {
+ private static String encode(String t) {
+ t = t.replace("&", "&");
+ t = t.replace("<", "<");
+ t = t.replace(">", ">");
+ return t;
+ }
+
+ public CodeTagInfo(String text, SourcePositionInfo sp) {
+ // TODO: the correct behavior is to escape the text,
+ // but we'll have to update the Android sources before making the switch.
+ //super("@code", "@code", encode(text), sp);
+ super("@code", "@code", text, sp);
+ }
+}
diff --git a/src/com/google/doclava/Comment.java b/src/com/google/doclava/Comment.java
new file mode 100644
index 0000000..178c17a
--- /dev/null
+++ b/src/com/google/doclava/Comment.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import 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",
+ "@sdkCurrent", "@inheritDoc", "@more", "@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(text, pos));
+ } else if (name.equals("@code")) {
+ mInlineTagsList.add(new CodeTagInfo(text, pos));
+ } else if (name.equals("@hide") || name.equals("@pending") || 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 != -1) {
+ return mHidden != 0;
+ } else {
+ if (Doclava.checkLevel(Doclava.SHOW_HIDDEN)) {
+ mHidden = 0;
+ return false;
+ }
+ boolean b = mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0;
+ mHidden = b ? 1 : 0;
+ return b;
+ }
+ }
+
+ public boolean isDocOnly() {
+ if (mDocOnly != -1) {
+ return mDocOnly != 0;
+ } else {
+ boolean b = (mText != null) && (mText.indexOf("@doconly") >= 0);
+ mDocOnly = b ? 1 : 0;
+ return b;
+ }
+ }
+
+ public boolean isDeprecated() {
+ if (mDeprecated != -1) {
+ return mDeprecated != 0;
+ } else {
+ boolean b = (mText != null) && (mText.indexOf("@deprecated") >= 0);
+ mDeprecated = b ? 1 : 0;
+ return b;
+ }
+ }
+
+ private void init() {
+ if (!mInitialized) {
+ initImpl();
+ }
+ }
+
+ private void initImpl() {
+ isHidden();
+ isDocOnly();
+ isDeprecated();
+
+ // Don't bother parsing text if we aren't generating documentation.
+ if (Doclava.generatingDocs()) {
+ parseRegex(mText);
+ parseBriefTags();
+ } else {
+ // Forces methods to be recognized by findOverriddenMethods in MethodInfo.
+ mInlineTagsList.add(new TextTagInfo("Text", "Text", mText,
+ SourcePositionInfo.add(mPosition, mText, 0)));
+ }
+
+ 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;
+ int mDeprecated = -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/src/com/google/doclava/ConstructorInfo.java b/src/com/google/doclava/ConstructorInfo.java
new file mode 100644
index 0000000..0f2c2fd
--- /dev/null
+++ b/src/com/google/doclava/ConstructorInfo.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.doclava.apicheck.AbstractMethodInfo;
+
+import java.util.*;
+
+public class ConstructorInfo implements AbstractMethodInfo {
+
+ private boolean mIsVarargs;
+ private String mName;
+ private String mType;
+ private boolean mIsStatic;
+ private boolean mIsFinal;
+ private String mDeprecated;
+ private boolean mIsDeprecated;
+ private String mScope;
+ private List<String> mExceptions;
+ private List<ParameterInfo> mParameters;
+ private SourcePositionInfo mSourcePosition;
+ private ClassInfo mClass;
+
+ public ConstructorInfo(String name, String type, boolean isStatic, boolean isFinal,
+ String deprecated, String scope, SourcePositionInfo pos, ClassInfo clazz) {
+ mName = name;
+ mType = type;
+ mIsStatic = isStatic;
+ mIsFinal = isFinal;
+ mDeprecated = deprecated;
+ mIsDeprecated = "deprecated".equals(deprecated);
+ mScope = scope;
+ mExceptions = new ArrayList<String>();
+ mParameters = new ArrayList<ParameterInfo>();
+ mSourcePosition = pos;
+ mClass = clazz;
+ }
+
+ public void setDeprecated(boolean deprecated) {
+ mIsDeprecated = deprecated;
+ }
+
+ public void addParameter(ParameterInfo pInfo) {
+ mParameters.add(pInfo);
+ }
+
+ public void addException(String exec) {
+ mExceptions.add(exec);
+ }
+
+ public String getHashableName() {
+ StringBuilder result = new StringBuilder();
+ result.append(name());
+ for (ParameterInfo pInfo : mParameters) {
+ result.append(":").append(pInfo.typeName());
+ }
+ return result.toString();
+ }
+
+ public SourcePositionInfo position() {
+ return mSourcePosition;
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ public String qualifiedName() {
+ String baseName = (mClass != null) ? (mClass.qualifiedName() + ".") : "";
+ return baseName + name();
+ }
+
+ public String prettySignature() {
+ String params = "";
+ for (ParameterInfo pInfo : mParameters) {
+ if (params.length() > 0) {
+ params += ", ";
+ }
+ params += pInfo.typeName();
+ }
+ return qualifiedName() + '(' + params + ')';
+ }
+
+ public boolean isConsistent(ConstructorInfo mInfo) {
+ boolean consistent = true;
+
+ if (mIsFinal != mInfo.mIsFinal) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, mInfo.position(), "Constructor " + mInfo.qualifiedName()
+ + " has changed 'final' qualifier");
+ }
+
+ if (mIsStatic != mInfo.mIsStatic) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, mInfo.position(), "Constructor " + mInfo.qualifiedName()
+ + " has changed 'static' qualifier");
+ }
+
+ if (!mScope.equals(mInfo.mScope)) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Constructor " + mInfo.qualifiedName()
+ + " changed scope from " + mScope + " to " + mInfo.mScope);
+ }
+
+ if (!mIsDeprecated == mInfo.mIsDeprecated) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Constructor "
+ + mInfo.qualifiedName() + " has changed deprecation state");
+ }
+
+ for (String exec : mExceptions) {
+ if (!mInfo.mExceptions.contains(exec)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Constructor "
+ + mInfo.qualifiedName() + " no longer throws exception " + exec);
+ consistent = false;
+ }
+ }
+
+ for (String exec : mInfo.mExceptions) {
+ if (!mExceptions.contains(exec)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Constructor "
+ + mInfo.qualifiedName() + " added thrown exception " + exec);
+ consistent = false;
+ }
+ }
+
+ return consistent;
+ }
+
+ @Override
+ public void setVarargs(boolean varargs) {
+ mIsVarargs = varargs;
+ }
+
+ public boolean isVarArgs() {
+ return mIsVarargs;
+ }
+
+}
diff --git a/src/com/google/doclava/ContainerInfo.java b/src/com/google/doclava/ContainerInfo.java
new file mode 100644
index 0000000..bde9506
--- /dev/null
+++ b/src/com/google/doclava/ContainerInfo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+public interface ContainerInfo {
+ public String qualifiedName();
+
+ public boolean checkLevel();
+}
diff --git a/src/com/google/doclava/Converter.java b/src/com/google/doclava/Converter.java
new file mode 100644
index 0000000..06b7acb
--- /dev/null
+++ b/src/com/google/doclava/Converter.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.sun.javadoc.*;
+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, boolean isVarArg) {
+ if (p == null) return null;
+ ParameterInfo pi =
+ new ParameterInfo(p.name(), p.typeName(), Converter.obtainType(p.type()), isVarArg,
+ Converter.convertSourcePosition(pos));
+ return pi;
+ }
+
+ private static ParameterInfo[] convertParameters(Parameter[] p, ExecutableMemberDoc m) {
+ SourcePosition pos = m.position();
+ int len = p.length;
+ ParameterInfo[] q = new ParameterInfo[len];
+ for (int i = 0; i < len; i++) {
+ boolean isVarArg = (m.isVarArgs() && i == len - 1);
+ q[i] = Converter.convertParameter(p[i], pos, isVarArg);
+ }
+ return q;
+ }
+
+ private static TypeInfo[] convertTypes(Type[] p) {
+ if (p == null) return null;
+ int len = p.length;
+ TypeInfo[] q = new TypeInfo[len];
+ for (int i = 0; i < len; i++) {
+ q[i] = Converter.obtainType(p[i]);
+ }
+ return q;
+ }
+
+ private Converter() {}
+
+ private static class ClassNeedingInit {
+ ClassNeedingInit(ClassDoc c, ClassInfo cl) {
+ this.c = c;
+ this.cl = cl;
+ }
+
+ ClassDoc c;
+ ClassInfo cl;
+ }
+
+ private static ArrayList<ClassNeedingInit> mClassesNeedingInit =
+ new ArrayList<ClassNeedingInit>();
+
+ static ClassInfo obtainClass(ClassDoc o) {
+ return (ClassInfo) mClasses.obtain(o);
+ }
+
+ private static Cache mClasses = new Cache() {
+ @Override
+ protected Object make(Object o) {
+ ClassDoc c = (ClassDoc) o;
+ ClassInfo cl =
+ new ClassInfo(c, c.getRawCommentText(), Converter.convertSourcePosition(c.position()), c
+ .isPublic(), c.isProtected(), c.isPackagePrivate(), c.isPrivate(), c.isStatic(), c
+ .isInterface(), c.isAbstract(), c.isOrdinaryClass(), c.isException(), c.isError(), c
+ .isEnum(), (c instanceof AnnotationTypeDoc), c.isFinal(), c.isIncluded(), c.name(), c
+ .qualifiedName(), c.qualifiedTypeName(), c.isPrimitive());
+ if (mClassesNeedingInit != null) {
+ mClassesNeedingInit.add(new ClassNeedingInit(c, cl));
+ }
+ return cl;
+ }
+
+ @Override
+ protected void made(Object o, Object r) {
+ if (mClassesNeedingInit == null) {
+ initClass((ClassDoc) o, (ClassInfo) r);
+ ((ClassInfo) r).init2();
+ }
+ }
+
+ @Override
+ ClassInfo[] all() {
+ return mCache.values().toArray(new ClassInfo[mCache.size()]);
+ }
+ };
+
+ private static MethodInfo[] getHiddenMethods(MethodDoc[] methods) {
+ if (methods == null) return null;
+ ArrayList<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() {
+ @Override
+ 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() {
+ @Override
+ protected Object make(Object o) {
+ FieldDoc f = (FieldDoc) o;
+ return new FieldInfo(f.name(), Converter.obtainClass(f.containingClass()), Converter
+ .obtainClass(f.containingClass()), f.isPublic(), f.isProtected(), f.isPackagePrivate(), f
+ .isPrivate(), f.isFinal(), f.isStatic(), f.isTransient(), f.isVolatile(),
+ f.isSynthetic(), Converter.obtainType(f.type()), f.getRawCommentText(),
+ f.constantValue(), Converter.convertSourcePosition(f.position()), Converter
+ .convertAnnotationInstances(f.annotations()));
+ }
+ };
+
+ private static PackageInfo obtainPackage(PackageDoc o) {
+ return (PackageInfo) mPackagees.obtain(o);
+ }
+
+ private static Cache mPackagees = new Cache() {
+ @Override
+ protected Object make(Object o) {
+ PackageDoc p = (PackageDoc) o;
+ return new PackageInfo(p, p.name(), Converter.convertSourcePosition(p.position()));
+ }
+ };
+
+ private static TypeInfo obtainType(Type o) {
+ return (TypeInfo) mTypes.obtain(o);
+ }
+
+ private static Cache mTypes = new Cache() {
+ @Override
+ protected Object make(Object o) {
+ Type t = (Type) o;
+ String simpleTypeName;
+ if (t instanceof ClassDoc) {
+ simpleTypeName = ((ClassDoc) t).name();
+ } else {
+ simpleTypeName = t.simpleTypeName();
+ }
+ TypeInfo ti =
+ new TypeInfo(t.isPrimitive(), t.dimension(), simpleTypeName, t.qualifiedTypeName(),
+ Converter.obtainClass(t.asClassDoc()));
+ return ti;
+ }
+
+ @Override
+ protected void made(Object o, Object r) {
+ Type t = (Type) o;
+ TypeInfo ti = (TypeInfo) r;
+ if (t.asParameterizedType() != null) {
+ ti.setTypeArguments(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()));
+ }
+ }
+
+ @Override
+ protected Object keyFor(Object o) {
+ Type t = (Type) o;
+ String keyString = o.getClass().getName() + "/" + o.toString() + "/";
+ if (t.asParameterizedType() != null) {
+ keyString += t.asParameterizedType().toString() + "/";
+ if (t.asParameterizedType().typeArguments() != null) {
+ for (Type ty : t.asParameterizedType().typeArguments()) {
+ keyString += ty.toString() + "/";
+ }
+ }
+ } else {
+ keyString += "NoParameterizedType//";
+ }
+ if (t.asTypeVariable() != null) {
+ keyString += t.asTypeVariable().toString() + "/";
+ if (t.asTypeVariable().bounds() != null) {
+ for (Type ty : t.asTypeVariable().bounds()) {
+ keyString += ty.toString() + "/";
+ }
+ }
+ } else {
+ keyString += "NoTypeVariable//";
+ }
+ if (t.asWildcardType() != null) {
+ keyString += t.asWildcardType().toString() + "/";
+ if (t.asWildcardType().superBounds() != null) {
+ for (Type ty : t.asWildcardType().superBounds()) {
+ keyString += ty.toString() + "/";
+ }
+ }
+ if (t.asWildcardType().extendsBounds() != null) {
+ for (Type ty : t.asWildcardType().extendsBounds()) {
+ keyString += ty.toString() + "/";
+ }
+ }
+ } else {
+ keyString += "NoWildCardType//";
+ }
+
+ return keyString;
+ }
+ };
+
+ public static TypeInfo obtainTypeFromString(String type) {
+ return (TypeInfo) mTypesFromString.obtain(type);
+ }
+
+ private static final Cache mTypesFromString = new Cache() {
+ @Override
+ protected Object make(Object o) {
+ String name = (String) o;
+ return new TypeInfo(name);
+ }
+
+ @Override
+ protected void made(Object o, Object r) {
+
+ }
+
+ @Override
+ protected Object keyFor(Object o) {
+ return o;
+ }
+ };
+
+ private static MemberInfo obtainMember(MemberDoc o) {
+ return (MemberInfo) mMembers.obtain(o);
+ }
+
+ private static Cache mMembers = new Cache() {
+ @Override
+ protected Object make(Object o) {
+ if (o instanceof MethodDoc) {
+ return Converter.obtainMethod((MethodDoc) o);
+ } else if (o instanceof ConstructorDoc) {
+ return Converter.obtainMethod((ConstructorDoc) o);
+ } else if (o instanceof FieldDoc) {
+ return Converter.obtainField((FieldDoc) o);
+ } else {
+ return null;
+ }
+ }
+ };
+
+ private static AnnotationInstanceInfo[] convertAnnotationInstances(AnnotationDesc[] orig) {
+ int len = orig.length;
+ AnnotationInstanceInfo[] out = new AnnotationInstanceInfo[len];
+ for (int i = 0; i < len; i++) {
+ out[i] = Converter.obtainAnnotationInstance(orig[i]);
+ }
+ return out;
+ }
+
+
+ private static AnnotationInstanceInfo obtainAnnotationInstance(AnnotationDesc o) {
+ return (AnnotationInstanceInfo) mAnnotationInstances.obtain(o);
+ }
+
+ private static Cache mAnnotationInstances = new Cache() {
+ @Override
+ protected Object make(Object o) {
+ AnnotationDesc a = (AnnotationDesc) o;
+ ClassInfo annotationType = Converter.obtainClass(a.annotationType());
+ AnnotationDesc.ElementValuePair[] ev = a.elementValues();
+ AnnotationValueInfo[] elementValues = new AnnotationValueInfo[ev.length];
+ for (int i = 0; i < ev.length; i++) {
+ elementValues[i] =
+ obtainAnnotationValue(ev[i].value(), Converter.obtainMethod(ev[i].element()));
+ }
+ return new AnnotationInstanceInfo(annotationType, elementValues);
+ }
+ };
+
+
+ private abstract static class Cache {
+ void put(Object key, Object value) {
+ mCache.put(key, value);
+ }
+
+ Object obtain(Object o) {
+ if (o == null) {
+ return null;
+ }
+ Object k = keyFor(o);
+ Object r = mCache.get(k);
+ if (r == null) {
+ r = make(o);
+ mCache.put(k, r);
+ made(o, r);
+ }
+ return r;
+ }
+
+ protected HashMap<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<AnnotationValue, AnnotationValueInfo>();
+ private static HashSet<AnnotationValue> mAnnotationValuesNeedingInit =
+ new HashSet<AnnotationValue>();
+
+ 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<AnnotationValue>();
+ for (AnnotationValue o : set) {
+ AnnotationValueInfo v = mAnnotationValues.get(o);
+ initAnnotationValue(o, v);
+ }
+ depth++;
+ }
+ mAnnotationValuesNeedingInit = null;
+ }
+}
diff --git a/src/com/google/doclava/DocFile.java b/src/com/google/doclava/DocFile.java
new file mode 100644
index 0000000..ab0a4fd
--- /dev/null
+++ b/src/com/google/doclava/DocFile.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+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();
+ FileInputStream is = new FileInputStream(f);
+ InputStreamReader reader = new InputStreamReader(is, "UTF-8");
+ 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) {
+ Data hdf = Doclava.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);
+
+ // write the page using the appropriate root template, based on the
+ // whichdoc value supplied by build
+ String fromWhichmodule = hdf.getValue("android.whichmodule", "");
+ if (fromWhichmodule.equals("online-pdk")) {
+ // leaving this in just for temporary compatibility with pdk doc
+ hdf.setValue("online-pdk", "true");
+ // add any conditional login for root template here (such as
+ // for custom left nav based on tab etc.
+ ClearPage.write(hdf, "docpage.cs", outfile);
+ } else {
+ if (outfile.indexOf("sdk/") != -1) {
+ hdf.setValue("sdk", "true");
+ if ((outfile.indexOf("index.html") != -1) || (outfile.indexOf("features.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("resources/") != -1) {
+ hdf.setValue("resources", "true");
+ ClearPage.write(hdf, "docpage.cs", outfile);
+ } else {
+ ClearPage.write(hdf, "nosidenavpage.cs", outfile);
+ }
+ }
+ } // writePage
+}
diff --git a/src/com/google/doclava/DocInfo.java b/src/com/google/doclava/DocInfo.java
new file mode 100644
index 0000000..d1fdae1
--- /dev/null
+++ b/src/com/google/doclava/DocInfo.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public abstract class DocInfo {
+ public DocInfo(String rawCommentText, SourcePositionInfo sp) {
+ mRawCommentText = rawCommentText;
+ mPosition = sp;
+ }
+
+ /**
+ * The relative path to a web page representing this item.
+ */
+ public abstract String htmlPage();
+
+ 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();
+
+ public void setSince(String since) {
+ mSince = since;
+ }
+
+ public String getSince() {
+ return mSince;
+ }
+
+ public final void addFederatedReference(FederatedSite source) {
+ mFederatedReferences.add(source);
+ }
+
+ public final Set<FederatedSite> getFederatedReferences() {
+ return mFederatedReferences;
+ }
+
+ public final void setFederatedReferences(Data data, String base) {
+ int pos = 0;
+ for (FederatedSite source : getFederatedReferences()) {
+ data.setValue(base + ".federated." + pos + ".url", source.linkFor(htmlPage()));
+ data.setValue(base + ".federated." + pos + ".name", source.name());
+ pos++;
+ }
+ }
+
+ private String mRawCommentText;
+ Comment mComment;
+ SourcePositionInfo mPosition;
+ private String mSince;
+ private Set<FederatedSite> mFederatedReferences = new LinkedHashSet<FederatedSite>();
+}
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
new file mode 100644
index 0000000..9489090
--- /dev/null
+++ b/src/com/google/doclava/Doclava.java
@@ -0,0 +1,1375 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
+import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
+import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import com.sun.javadoc.*;
+
+import java.util.*;
+import java.util.jar.JarFile;
+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;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class Doclava {
+ 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_CONSTANT_TYPE_FEATURE =
+ "android.annotation.SdkConstant.SdkConstantType.FEATURE";
+ 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 SinceTagger sinceTagger = new SinceTagger();
+ public static FederationTagger federationTagger = new FederationTagger();
+ private static boolean generatingDocs = true;
+
+ public static JSilver jSilver = null;
+
+ public static boolean checkLevel(int level) {
+ return (showLevel & level) == level;
+ }
+
+ /**
+ * Returns true if we are generating html reference documentation.
+ */
+ public static boolean generatingDocs() {
+ return generatingDocs;
+ }
+
+ 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 void main(String[] args) {
+ com.sun.tools.javadoc.Main.execute(args);
+ }
+
+ 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 offlineMode = 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")) {
+ Doclava.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<String>();
+ for (String pkg : a[1].split(":")) {
+ stubPackages.add(pkg);
+ }
+ } else if (a[0].equals("-sdkvalues")) {
+ sdkValuePath = a[1];
+ } else if (a[0].equals("-apixml")) {
+ apiFile = a[1];
+ } else if (a[0].equals("-nodocs")) {
+ generatingDocs = false;
+ } else if (a[0].equals("-since")) {
+ sinceTagger.addVersion(a[1], a[2]);
+ } else if (a[0].equals("-offlinemode")) {
+ offlineMode = true;
+ } else if (a[0].equals("-federate")) {
+ try {
+ String name = a[1];
+ URL federationURL = new URL(a[2]);
+ federationTagger.addSite(name, federationURL);
+ } catch (MalformedURLException e) {
+ System.err.println("Could not parse URL for federation: " + a[1]);
+ return false;
+ }
+ }
+ }
+
+
+ // Set up the data structures
+ Converter.makeInfo(r);
+
+ if (generatingDocs) {
+ ClearPage.addBundledTemplateDir("assets/customizations");
+ ClearPage.addBundledTemplateDir("assets/templates");
+
+ List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>();
+ List<String> templates = ClearPage.getTemplateDirs();
+ for (String tmpl : templates) {
+ resourceLoaders.add(new FileSystemResourceLoader(tmpl));
+ }
+
+ templates = ClearPage.getBundledTemplateDirs();
+ for (String tmpl : templates) {
+ resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl));
+ }
+
+ ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders);
+ jSilver = new JSilver(compositeResourceLoader);
+
+ if (!Doclava.readTemplateSettings()) {
+ return false;
+ }
+
+ long startTime = System.nanoTime();
+
+ // Apply @since tags from the XML file
+ sinceTagger.tagAll(Converter.rootClasses());
+
+ // Apply details of federated documentation
+ federationTagger.tagAll(Converter.rootClasses());
+
+ // Files for proofreading
+ if (proofreadFile != null) {
+ Proofread.initProofread(proofreadFile);
+ }
+ if (todoFile != null) {
+ TodoFile.writeTodoFile(todoFile);
+ }
+
+ // HTML Pages
+ if (ClearPage.htmlDir != null) {
+ writeHTMLPages();
+ }
+
+ writeAssets();
+
+ // Navigation tree
+ NavTree.writeNavTree(javadocDir);
+
+ // Packages Pages
+ writePackages(javadocDir + "packages" + htmlExtension);
+
+ // Classes
+ writeClassLists();
+ writeClasses();
+ writeHierarchy();
+ // writeKeywords();
+
+ // Lists for JavaScript
+ writeLists();
+ if (keepListFile != null) {
+ writeKeepList(keepListFile);
+ }
+
+ // Sample Code
+ for (SampleCode sc : sampleCodes) {
+ sc.write(offlineMode);
+ }
+
+ // Index page
+ writeIndex();
+
+ Proofread.finishProofread(proofreadFile);
+
+ if (sdkValuePath != null) {
+ writeSdkValues(sdkValuePath);
+ }
+
+ long time = System.nanoTime() - startTime;
+ System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to "
+ + ClearPage.outputDir);
+ }
+
+ // Stubs
+ if (stubsDir != null || apiFile != null) {
+ Stubs.writeStubsAndXml(stubsDir, apiFile, stubPackages);
+ }
+
+ Errors.printErrors();
+
+ return !Errors.hadError;
+ }
+
+ private static void writeIndex() {
+ Data data = makeHDF();
+ ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension);
+ }
+
+ private static boolean readTemplateSettings() {
+ Data data = makeHDF();
+
+ // The .html extension is hard-coded in several .cs files,
+ // and so you cannot currently set it as a property.
+ htmlExtension = ".html";
+ // 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(Data data, String title) {
+ String s = title;
+ if (Doclava.title.length() > 0) {
+ s += " - " + Doclava.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;
+ }
+ if (option.equals("-nodocs")) {
+ return 1;
+ }
+ if (option.equals("-since")) {
+ return 3;
+ }
+ if (option.equals("-offlinemode")) {
+ return 1;
+ }
+ if (option.equals("-federate")) {
+ return 3;
+ }
+ 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 Data makeHDF() {
+ Data data = jSilver.createData();
+
+ for (String[] p : mHDFData) {
+ data.setValue(p[0], p[1]);
+ }
+
+ return data;
+ }
+
+
+
+ public static Data makePackageHDF() {
+ Data 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 = 0;
+ ClassInfo[] classesToCheck = null;
+ while (pass < 5) {
+ switch (pass) {
+ case 0:
+ classesToCheck = pkg.ordinaryClasses();
+ break;
+ 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.err.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", "1");
+ data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0");
+ data.setValue("docs.packages." + i + ".name", s);
+ data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
+ data.setValue("docs.packages." + i + ".since", pkg.getSince());
+ TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
+ i++;
+ }
+
+ sinceTagger.writeVersionNames(data);
+ return data;
+ }
+
+ private static void writeDirectory(File dir, String relative, JSilver js) {
+ 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))) {
+ Data data = makeHDF();
+ String filename = templ.substring(0, len - 3) + htmlExtension;
+ ClearPage.write(data, templ, filename, js);
+ } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
+ String filename = templ.substring(0, len - 3) + htmlExtension;
+ DocFile.writePage(f.getAbsolutePath(), relative, filename);
+ } else {
+ ClearPage.copyFile(f, templ);
+ }
+ } else if (f.isDirectory()) {
+ writeDirectory(f, relative + f.getName() + "/", js);
+ }
+ }
+ }
+
+ public static void writeHTMLPages() {
+ File f = new File(ClearPage.htmlDir);
+ if (!f.isDirectory()) {
+ System.err.println("htmlDir not a directory: " + ClearPage.htmlDir);
+ }
+
+ ResourceLoader loader = new FileSystemResourceLoader(f);
+ JSilver js = new JSilver(loader);
+ writeDirectory(f, "", js);
+ }
+
+ public static void writeAssets() {
+ JarFile thisJar = JarUtils.jarForClass(Doclava.class, null);
+ if (thisJar != null) {
+ try {
+ List<String> templateDirs = ClearPage.getBundledTemplateDirs();
+ for (String templateDir : templateDirs) {
+ String assetsDir = templateDir + "/assets";
+ JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets");
+ }
+ } catch (IOException e) {
+ System.err.println("Error copying assets directory.");
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ List<String> templateDirs = ClearPage.getTemplateDirs();
+ for (String templateDir : templateDirs) {
+ File assets = new File(templateDir + "/assets");
+ if (assets.isDirectory()) {
+ writeDirectory(assets, "assets/", null);
+ }
+ }
+ }
+
+ public static void writeLists() {
+ Data 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.err.println("error writing file: " + filename);
+ } finally {
+ if (stream != null) {
+ stream.close();
+ }
+ }
+ }
+
+ private static PackageInfo[] sVisiblePackages = null;
+
+ public static PackageInfo[] choosePackages() {
+ if (sVisiblePackages != null) {
+ return sVisiblePackages;
+ }
+
+ 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);
+ }
+
+ ArrayList<PackageInfo> result = new ArrayList<PackageInfo>();
+
+ for (String s : sorted.keySet()) {
+ PackageInfo pkg = sorted.get(s);
+
+ if (pkg.isHidden()) {
+ continue;
+ }
+ Boolean allHidden = true;
+ int pass = 0;
+ ClassInfo[] classesToCheck = null;
+ while (pass < 5) {
+ switch (pass) {
+ case 0:
+ classesToCheck = pkg.ordinaryClasses();
+ break;
+ 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.err.println("Error reading package: " + pkg.name());
+ break;
+ }
+ for (ClassInfo cl : classesToCheck) {
+ if (!cl.isHidden()) {
+ allHidden = false;
+ break;
+ }
+ }
+ if (!allHidden) {
+ break;
+ }
+ pass++;
+ }
+ if (allHidden) {
+ continue;
+ }
+
+ result.add(pkg);
+ }
+
+ sVisiblePackages = result.toArray(new PackageInfo[result.size()]);
+ return sVisiblePackages;
+ }
+
+ public static void writePackages(String filename) {
+ Data data = makePackageHDF();
+
+ int i = 0;
+ for (PackageInfo pkg : choosePackages()) {
+ writePackage(pkg);
+
+ data.setValue("docs.packages." + i + ".name", pkg.name());
+ data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
+ TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
+
+ 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
+ Data data = makePackageHDF();
+
+ String name = pkg.name();
+
+ data.setValue("package.name", name);
+ data.setValue("package.since", pkg.getSince());
+ data.setValue("package.descr", "...description...");
+ pkg.setFederatedReferences(data, "package");
+
+ 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 = pkg.fullDescriptionHtmlPage();
+ setPageTitle(data, name + " Details");
+ ClearPage.write(data, "package-descr.cs", filename);
+
+ Proofread.writePackage(filename, pkg.inlineTags());
+ }
+
+ public static void writeClassLists() {
+ int i;
+ Data 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() + ")";
+ }
+ }
+ }
+ 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 = makeHDF();
+ *
+ * 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);
+ }
+ }
+ Data 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) {
+ Data data = makePackageHDF();
+ if (!cl.isHidden()) {
+ writeClass(cl, data);
+ }
+ }
+ }
+
+ public static void writeClass(ClassInfo cl, Data data) {
+ cl.makeHDF(data);
+
+ setPageTitle(data, cl.name());
+ ClearPage.write(data, "class.cs", cl.htmlPage());
+
+ Proofread.writeClass(cl.htmlPage(), cl);
+ }
+
+ public static void makeClassListHDF(Data 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 or @pending annotation.
+ */
+ private static boolean hasHideAnnotation(Doc doc) {
+ String comment = doc.getRawCommentText();
+ return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -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;
+ }
+ }
+
+ /**
+ * 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<String> features = 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());
+ } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) {
+ features.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);
+
+ Collections.sort(features);
+ writeValues(output + "/features.txt", features);
+
+ // 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/src/com/google/doclava/DoclavaDiff.java b/src/com/google/doclava/DoclavaDiff.java
new file mode 100644
index 0000000..e043246
--- /dev/null
+++ b/src/com/google/doclava/DoclavaDiff.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
+import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class is used to generate a web page highlighting the differences and
+ * similarities among various Java libraries.
+ *
+ */
+public final class DoclavaDiff {
+ private final String outputDir;
+ private final JSilver jSilver;
+ private final List<FederatedSite> sites = new ArrayList<FederatedSite>();
+
+ public static void main(String[] args) {
+ new DoclavaDiff(args).generateSite();
+ }
+
+ public DoclavaDiff(String[] args) {
+ // TODO: options parsing
+ try {
+ sites.add(new FederatedSite("Android", new URL("http://manatee/doclava/android")));
+ sites.add(new FederatedSite("GWT", new URL("http://manatee/doclava/gwt")));
+ //sites.add(new FederatedSite("Crore", new URL("http://manatee/doclava/crore")));
+ outputDir = "build";
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+
+ // TODO: accept external templates
+ List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>();
+ resourceLoaders.add(new FileSystemResourceLoader("assets/templates"));
+
+ ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders);
+ jSilver = new JSilver(compositeResourceLoader);
+ }
+
+ public void generateSite() {
+ Data data = generateHdf();
+ generateHtml("diff.cs", data, new File(outputDir + "/diff.html"));
+ }
+
+ /**
+ * Creates an HDF with this structure:
+ * <pre>
+ * sites.0.name = Android
+ * sites.0.url = http://developer.android.com/reference
+ * sites.1.name = GWT
+ * sites.1.url = http://gwt.googlecode.com
+ * packages.0.name = java.lang
+ * packages.0.sites.0.hasPackage = 1
+ * packages.0.sites.0.link = http://developer.android.com/reference/java/lang
+ * packages.0.sites.1.hasPackage = 0
+ * packages.0.classes.0.qualifiedName = java.lang.Object
+ * packages.0.classes.0.sites.0.hasClass = 1
+ * packages.0.classes.0.sites.0.link = http://developer.android.com/reference/java/lang/Object
+ * packages.0.classes.0.sites.1.hasClass = 0
+ * packages.0.classes.0.methods.0.signature = wait()
+ * packages.0.classes.0.methods.0.sites.0.hasMethod = 1
+ * packages.0.classes.0.methods.0.sites.0.link = http://developer.android.com/reference/java/lang/Object#wait
+ * packages.0.classes.0.methods.0.sites.1.hasMethod = 0
+ * </pre>
+ */
+ private Data generateHdf() {
+ Data data = jSilver.createData();
+
+ data.setValue("triangle.opened", "../assets/templates/assets/images/triangle-opened.png");
+ data.setValue("triangle.closed", "../assets/templates/assets/images/triangle-closed.png");
+
+ int i = 0;
+ for (FederatedSite site : sites) {
+ String base = "sites." + (i++);
+ data.setValue(base + ".name", site.name());
+ data.setValue(base + ".url", site.baseUrl().toString());
+ }
+
+ List<String> allPackages = knownPackages(sites);
+
+ int p = 0;
+ for (String pkg : allPackages) {
+ PackageInfo packageInfo = new PackageInfo(pkg);
+ String packageBase = "packages." + (p++);
+ data.setValue(packageBase + ".name", pkg);
+
+ int s = 0;
+ for (FederatedSite site : sites) {
+ String siteBase = packageBase + ".sites." + (s++);
+ if (site.apiInfo().getPackages().containsKey(pkg)) {
+ data.setValue(siteBase + ".hasPackage", "1");
+ data.setValue(siteBase + ".link", site.linkFor(packageInfo.htmlPage()));
+ } else {
+ data.setValue(siteBase + ".hasPackage", "0");
+ }
+ }
+
+ if (packageUniqueToSite(pkg, sites)) {
+ continue;
+ }
+
+ List<String> packageClasses = knownClassesForPackage(pkg, sites);
+ int c = 0;
+ for (String qualifiedClassName : packageClasses) {
+ String classBase = packageBase + ".classes." + (c++);
+ data.setValue(classBase + ".qualifiedName", qualifiedClassName);
+
+ s = 0;
+ for (FederatedSite site : sites) {
+ String siteBase = classBase + ".sites." + (s++);
+ ClassInfo classInfo = site.apiInfo().findClass(qualifiedClassName);
+ if (classInfo != null) {
+ data.setValue(siteBase + ".hasClass", "1");
+ data.setValue(siteBase + ".link", site.linkFor(classInfo.htmlPage()));
+ } else {
+ data.setValue(siteBase + ".hasClass", "0");
+ }
+ }
+
+ if (agreeOnClass(qualifiedClassName, sites)) {
+ continue;
+ }
+
+ if (classUniqueToSite(qualifiedClassName, sites)) {
+ continue;
+ }
+
+ int m = 0;
+ List<MethodInfo> methods = knownMethodsForClass(qualifiedClassName, sites);
+ for (MethodInfo method : methods) {
+ if (agreeOnMethod(qualifiedClassName, method, sites)) {
+ continue;
+ }
+
+ String methodBase = classBase + ".methods." + (m++);
+ data.setValue(methodBase + ".signature", method.prettySignature());
+ int k = 0;
+ for (FederatedSite site : sites) {
+ String siteBase = methodBase + ".sites." + (k++);
+ if (site.apiInfo().findClass(qualifiedClassName) == null) {
+ data.setValue(siteBase + ".hasMethod", "0");
+ continue;
+ }
+ Map<String,MethodInfo> siteMethods
+ = site.apiInfo().findClass(qualifiedClassName).allMethods();
+ if (siteMethods.containsKey(method.getHashableName())) {
+ data.setValue(siteBase + ".hasMethod", "1");
+ data.setValue(siteBase + ".link", site.linkFor(method.htmlPage()));
+ } else {
+ data.setValue(siteBase + ".hasMethod", "0");
+ }
+ }
+ }
+ }
+ }
+
+ return data;
+ }
+
+ /**
+ * Returns a list of all known packages from all sites.
+ */
+ private List<String> knownPackages(List<FederatedSite> sites) {
+ Set<String> allPackages = new LinkedHashSet<String>();
+ for (FederatedSite site : sites) {
+ Map<String, PackageInfo> packages = site.apiInfo().getPackages();
+ for (String pkg : packages.keySet()) {
+ allPackages.add(pkg);
+ }
+ }
+
+ List<String> packages = new ArrayList<String>(allPackages);
+ Collections.sort(packages);
+ return packages;
+ }
+
+ /**
+ * Returns all known classes from all sites for a given package.
+ */
+ private List<String> knownClassesForPackage(String pkg, List<FederatedSite> sites) {
+ Set<String> allClasses = new LinkedHashSet<String>();
+ for (FederatedSite site : sites) {
+ PackageInfo packageInfo = site.apiInfo().getPackages().get(pkg);
+ if (packageInfo == null) {
+ continue;
+ }
+ HashMap<String, ClassInfo> classes = packageInfo.allClasses();
+ for (Map.Entry<String, ClassInfo> entry : classes.entrySet()) {
+ allClasses.add(entry.getValue().qualifiedName());
+ }
+ }
+
+ List<String> classes = new ArrayList<String>(allClasses);
+ Collections.sort(classes);
+ return classes;
+ }
+
+ /**
+ * Returns all known methods from all sites for a given class.
+ */
+ private List<MethodInfo> knownMethodsForClass(String qualifiedClassName,
+ List<FederatedSite> sites) {
+
+ Map<String, MethodInfo> allMethods = new HashMap<String, MethodInfo>();
+ for (FederatedSite site : sites) {
+ ClassInfo classInfo = site.apiInfo().findClass(qualifiedClassName);
+ if (classInfo == null) {
+ continue;
+ }
+
+ for (Map.Entry<String, MethodInfo> entry: classInfo.allMethods().entrySet()) {
+ allMethods.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ List<MethodInfo> methods = new ArrayList<MethodInfo>();
+ methods.addAll(allMethods.values());
+ return methods;
+ }
+
+ /**
+ * Returns true if the list of sites all completely agree on the given
+ * package. All sites must possess the package, all classes it contains, and
+ * all methods of each class.
+ */
+ private boolean agreeOnPackage(String pkg, List<FederatedSite> sites) {
+ for (FederatedSite site : sites) {
+ if (site.apiInfo().getPackages().get(pkg) == null) {
+ return false;
+ }
+ }
+
+ List<String> classes = knownClassesForPackage(pkg, sites);
+ for (String clazz : classes) {
+ if (!agreeOnClass(clazz, sites)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the list of sites all agree on the given class. Each site
+ * must have the class and agree on its methods.
+ */
+ private boolean agreeOnClass(String qualifiedClassName, List<FederatedSite> sites) {
+ List<MethodInfo> methods = knownMethodsForClass(qualifiedClassName, sites);
+ for (MethodInfo method : methods) {
+ if (!agreeOnMethod(qualifiedClassName, method, sites)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the list of sites all contain the given method.
+ */
+ private boolean agreeOnMethod(String qualifiedClassName, MethodInfo method,
+ List<FederatedSite> sites) {
+
+ for (FederatedSite site : sites) {
+ ClassInfo siteClass = site.apiInfo().findClass(qualifiedClassName);
+ if (siteClass == null) {
+ return false;
+ }
+
+ if (!siteClass.supportsMethod(method)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the given package is known to exactly one of the given sites.
+ */
+ private boolean packageUniqueToSite(String pkg, List<FederatedSite> sites) {
+ int numSites = 0;
+ for (FederatedSite site : sites) {
+ if (site.apiInfo().getPackages().containsKey(pkg)) {
+ numSites++;
+ }
+ }
+ return numSites == 1;
+ }
+
+ /**
+ * Returns true if the given class is known to exactly one of the given sites.
+ */
+ private boolean classUniqueToSite(String qualifiedClassName, List<FederatedSite> sites) {
+ int numSites = 0;
+ for (FederatedSite site : sites) {
+ if (site.apiInfo().findClass(qualifiedClassName) != null) {
+ numSites++;
+ }
+ }
+ return numSites == 1;
+ }
+
+ private void generateHtml(String template, Data data, File file) {
+ ClearPage.ensureDirectory(file);
+
+ OutputStreamWriter stream = null;
+ try {
+ stream = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
+ String rendered = jSilver.render(template, data);
+ stream.write(rendered, 0, rendered.length());
+ } catch (IOException e) {
+ System.out.println("error: " + e.getMessage() + "; when writing file: " + file.getAbsolutePath());
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException ignored) {}
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/doclava/Errors.java b/src/com/google/doclava/Errors.java
new file mode 100644
index 0000000..6a257fe
--- /dev/null
+++ b/src/com/google/doclava/Errors.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class Errors {
+ public static boolean hadError = false;
+ private static boolean warningsAreErrors = false;
+ private static TreeSet<ErrorMessage> allErrors = new TreeSet<ErrorMessage>();
+
+ public static class ErrorMessage implements Comparable {
+ Error error;
+ SourcePositionInfo pos;
+ String msg;
+
+ ErrorMessage(Error e, SourcePositionInfo p, String m) {
+ error = e;
+ pos = p;
+ msg = m;
+ }
+
+ public int compareTo(Object o) {
+ ErrorMessage that = (ErrorMessage) o;
+ int r = this.pos.compareTo(that.pos);
+ if (r != 0) return r;
+ return this.msg.compareTo(that.msg);
+ }
+
+ @Override
+ public String toString() {
+ String whereText = this.pos == null ? "unknown: " : this.pos.toString() + ':';
+ return whereText + this.msg;
+ }
+
+ public Error error() {
+ return error;
+ }
+ }
+
+ public static void error(Error error, SourcePositionInfo where, String text) {
+ if (error.level == HIDDEN) {
+ return;
+ }
+
+ int level = (!warningsAreErrors && error.level == WARNING) ? WARNING : ERROR;
+ String which = level == WARNING ? " warning " : " error ";
+ String message = which + error.code + ": " + text;
+
+ if (where == null) {
+ where = new SourcePositionInfo("unknown", 0, 0);
+ }
+
+ allErrors.add(new ErrorMessage(error, where, message));
+
+ if (error.level == ERROR || (warningsAreErrors && error.level == WARNING)) {
+ hadError = true;
+ }
+ }
+
+ public static void clearErrors() {
+ hadError = false;
+ allErrors.clear();
+ }
+
+ public static void printErrors() {
+ printErrors(allErrors);
+ }
+
+ public static void printErrors(Set<ErrorMessage> errors) {
+ for (ErrorMessage m : errors) {
+ if (m.error.level == WARNING) {
+ System.err.println(m.toString());
+ }
+ }
+ for (ErrorMessage m : errors) {
+ if (m.error.level == ERROR) {
+ System.err.println(m.toString());
+ }
+ }
+ }
+
+ public static Set<ErrorMessage> getErrors() {
+ return allErrors;
+ }
+
+ 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 String toString() {
+ return "Error #" + this.code;
+ }
+ }
+
+ public static final Error UNRESOLVED_LINK = new Error(1, WARNING);
+ public static final Error BAD_INCLUDE_TAG = new Error(2, WARNING);
+ public static final Error UNKNOWN_TAG = new Error(3, WARNING);
+ public static final Error UNKNOWN_PARAM_TAG_NAME = new Error(4, WARNING);
+ public static final Error UNDOCUMENTED_PARAMETER = new Error(5, HIDDEN);
+ public static final Error BAD_ATTR_TAG = new Error(6, ERROR);
+ public static final Error BAD_INHERITDOC = new Error(7, HIDDEN);
+ public static final Error HIDDEN_LINK = new Error(8, WARNING);
+ public static final Error HIDDEN_CONSTRUCTOR = new Error(9, WARNING);
+ public static final Error UNAVAILABLE_SYMBOL = new Error(10, ERROR);
+ public static final Error HIDDEN_SUPERCLASS = new Error(11, WARNING);
+ public static final Error DEPRECATED = new Error(12, HIDDEN);
+ public static final Error DEPRECATION_MISMATCH = new Error(13, WARNING);
+ public static final Error MISSING_COMMENT = new Error(14, WARNING);
+ public static final Error IO_ERROR = new Error(15, HIDDEN);
+ public static final Error NO_SINCE_DATA = new Error(16, HIDDEN);
+ public static final Error NO_FEDERATION_DATA = new Error(17, WARNING);
+
+ public static final Error PARSE_ERROR = new Error(18, ERROR); // Apicheck error code 1
+ public static final Error ADDED_PACKAGE = new Error(19, WARNING); // Apicheck error code 2
+ public static final Error ADDED_CLASS = new Error(20, WARNING); // Apicheck error code 3
+ public static final Error ADDED_METHOD = new Error(21, WARNING); // Apicheck error code 4
+ public static final Error ADDED_FIELD = new Error(22, WARNING); // Apicheck error code 5
+ public static final Error ADDED_INTERFACE = new Error(23, WARNING); // Apicheck error code 6
+ public static final Error REMOVED_PACKAGE = new Error(24, WARNING); // Apicheck error code 7
+ public static final Error REMOVED_CLASS = new Error(25, WARNING); // Apicheck error code 8
+ public static final Error REMOVED_METHOD = new Error(26, WARNING); // Apicheck error code 9
+ public static final Error REMOVED_FIELD = new Error(27, WARNING); // Apicheck error code 10
+ public static final Error REMOVED_INTERFACE = new Error(28, WARNING); // Apicheck error code 11
+ public static final Error CHANGED_STATIC = new Error(29, WARNING); // Apicheck error code 12
+ public static final Error CHANGED_FINAL = new Error(30, WARNING); // Apicheck error code 13
+ public static final Error CHANGED_TRANSIENT = new Error(31, WARNING); // Apicheck error code 14
+ public static final Error CHANGED_VOLATILE = new Error(32, WARNING); // Apicheck error code 15
+ public static final Error CHANGED_TYPE = new Error(33, WARNING); // Apicheck error code 16
+ public static final Error CHANGED_VALUE = new Error(34, WARNING); // Apicheck error code 17
+ public static final Error CHANGED_SUPERCLASS = new Error(35, WARNING); // Apicheck error code 18
+ public static final Error CHANGED_SCOPE = new Error(36, WARNING); // Apicheck error code 19
+ public static final Error CHANGED_ABSTRACT = new Error(37, WARNING); // Apicheck error code 20
+ public static final Error CHANGED_THROWS = new Error(38, WARNING); // Apicheck error code 21
+ public static final Error CHANGED_NATIVE = new Error(39, HIDDEN); // Apicheck error code 22
+ public static final Error CHANGED_CLASS = new Error(40, WARNING); // Apicheck error code 23
+ public static final Error CHANGED_DEPRECATED = new Error(41, WARNING); // Apicheck error code 24
+ public static final Error CHANGED_SYNCHRONIZED = new Error(42, ERROR); // Apicheck error code 25
+
+ public static final 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, NO_SINCE_DATA,
+ NO_FEDERATION_DATA, PARSE_ERROR, ADDED_PACKAGE, ADDED_CLASS, ADDED_METHOD, ADDED_FIELD,
+ ADDED_INTERFACE, REMOVED_PACKAGE, REMOVED_CLASS, REMOVED_METHOD, REMOVED_FIELD,
+ REMOVED_INTERFACE, CHANGED_STATIC, CHANGED_FINAL, CHANGED_TRANSIENT, CHANGED_VOLATILE,
+ CHANGED_TYPE, CHANGED_VALUE, CHANGED_SUPERCLASS, CHANGED_SCOPE, CHANGED_ABSTRACT,
+ CHANGED_THROWS, CHANGED_NATIVE, CHANGED_CLASS, CHANGED_DEPRECATED, CHANGED_SYNCHRONIZED};
+
+ 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/src/com/google/doclava/FederatedSite.java b/src/com/google/doclava/FederatedSite.java
new file mode 100644
index 0000000..2aa12cf
--- /dev/null
+++ b/src/com/google/doclava/FederatedSite.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.doclava.apicheck.ApiCheck;
+import com.google.doclava.apicheck.ApiInfo;
+import com.google.doclava.apicheck.ApiParseException;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * A remote source of documentation that can be linked against. A federated
+ * site represents a library that has packages, classes, and members that may
+ * be referenced or shared across codebases.
+ */
+public final class FederatedSite {
+ private final String name;
+ private final URL baseUrl;
+ private final ApiInfo apiInfo;
+
+ public FederatedSite(String name, URL baseUrl) throws ApiParseException {
+ this.name = name;
+ this.baseUrl = baseUrl;
+
+ try {
+ URL xmlUrl = new URL(baseUrl + "/xml/current.xml");
+ this.apiInfo = new ApiCheck().parseApi(xmlUrl);
+ } catch (MalformedURLException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ public String linkFor(String htmlPage) {
+ return baseUrl + "/" + htmlPage;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public ApiInfo apiInfo() {
+ return apiInfo;
+ }
+
+ public URL baseUrl() {
+ return baseUrl;
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/doclava/FederationTagger.java b/src/com/google/doclava/FederationTagger.java
new file mode 100644
index 0000000..2b54d9e
--- /dev/null
+++ b/src/com/google/doclava/FederationTagger.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.doclava.apicheck.ApiCheck;
+import com.google.doclava.apicheck.ApiInfo;
+import com.google.doclava.apicheck.ApiParseException;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Cross-references documentation among different libraries. A FederationTagger
+ * is populated with a list of {@link FederatedSite} objects which are linked
+ * against when overlapping content is discovered.
+ */
+public final class FederationTagger {
+ private final List<FederatedSite> federatedSites = new ArrayList<FederatedSite>();
+
+ /**
+ * Adds a Doclava documentation site for federation. Accepts the base URL of
+ * the remote API.
+ */
+ public void addSite(String name, URL site) {
+ try {
+ federatedSites.add(new FederatedSite(name, site));
+ } catch (ApiParseException e) {
+ String error = "Could not add site for federation: " + site;
+ if (e.getMessage() != null) {
+ error += ": " + e.getMessage();
+ }
+ Errors.error(Errors.NO_FEDERATION_DATA, null, error);
+ }
+ }
+
+ public void tagAll(ClassInfo[] classDocs) {
+ for (FederatedSite site : federatedSites) {
+ applyFederation(site, classDocs);
+ }
+ }
+
+ private void applyFederation(FederatedSite federationSource, ClassInfo[] classDocs) {
+ for (ClassInfo classDoc : classDocs) {
+ PackageInfo packageSpec
+ = federationSource.apiInfo().getPackages().get(classDoc.containingPackage().name());
+
+ if (packageSpec == null) {
+ continue;
+ }
+
+ ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name());
+
+ if (classSpec == null) {
+ continue;
+ }
+
+ federateMethods(federationSource, classSpec, classDoc);
+ federateConstructors(federationSource, classSpec, classDoc);
+ federateFields(federationSource, classSpec, classDoc);
+ federateClass(federationSource, classDoc);
+ federatePackage(federationSource, classDoc.containingPackage());
+ }
+ }
+
+ private void federateMethods(FederatedSite site, ClassInfo federatedClass, ClassInfo localClass) {
+ for (MethodInfo method : localClass.methods()) {
+ for (ClassInfo superclass : federatedClass.hierarchy()) {
+ if (superclass.allMethods().containsKey(method.getHashableName())) {
+ method.addFederatedReference(site);
+ break;
+ }
+ }
+ }
+ }
+
+ private void federateConstructors(FederatedSite site, ClassInfo federatedClass,
+ ClassInfo localClass) {
+ for (MethodInfo constructor : localClass.constructors()) {
+ if (federatedClass.hasConstructor(constructor)) {
+ constructor.addFederatedReference(site);
+ }
+ }
+ }
+
+ private void federateFields(FederatedSite site, ClassInfo federatedClass, ClassInfo localClass) {
+ for (FieldInfo field : localClass.fields()) {
+ if (federatedClass.allFields().containsKey(field.name())) {
+ field.addFederatedReference(site);
+ }
+ }
+ }
+
+ private void federateClass(FederatedSite source, ClassInfo doc) {
+ doc.addFederatedReference(source);
+ }
+
+ private void federatePackage(FederatedSite source, PackageInfo pkg) {
+ pkg.addFederatedReference(source);
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/doclava/FieldInfo.java b/src/com/google/doclava/FieldInfo.java
new file mode 100644
index 0000000..112b6cf
--- /dev/null
+++ b/src/com/google/doclava/FieldInfo.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+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 String qualifiedName() {
+ String parentQName
+ = (containingClass() != null) ? (containingClass().qualifiedName() + ".") : "";
+ return parentQName + name();
+ }
+
+ 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 void setDeprecated(boolean deprecated) {
+ mDeprecatedKnown = true;
+ mIsDeprecated = deprecated;
+ }
+
+ public boolean isDeprecated() {
+ if (!mDeprecatedKnown) {
+ boolean commentDeprecated = comment().isDeprecated();
+ 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(Data 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 + ".since", getSince());
+ 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", Doclava.escape(dec));
+ data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
+ } else {
+ data.setValue(base + ".constantValue.str", Doclava.escape(str));
+ data.setValue(base + ".constantValue.isString", "1");
+ }
+ }
+
+ setFederatedReferences(data, base);
+ }
+
+ @Override
+ public boolean isExecutable() {
+ return false;
+ }
+
+ public boolean isTransient() {
+ return mIsTransient;
+ }
+
+ public boolean isVolatile() {
+ return mIsVolatile;
+ }
+
+ // Check the declared value with a typed comparison, not a string comparison,
+ // to accommodate toolchains with different fp -> string conversions.
+ private boolean valueEquals(FieldInfo other) {
+ if ((mConstantValue == null) != (other.mConstantValue == null)) {
+ return false;
+ }
+
+ // Null values are considered equal
+ if (mConstantValue == null) {
+ return true;
+ }
+
+ // TODO: This method is called through from an XML comparison only right now,
+ // and mConstantValue is always a String. Get rid of this assertion.
+ if (!(mConstantValue instanceof String && other.mConstantValue instanceof String)) {
+ throw new AssertionError("Bad type for field value");
+ }
+
+ String mValue = (String)mConstantValue;
+ String oValue = (String)other.mConstantValue;
+ // Type mismatch means nonequal
+ if (!mType.equals(other.mType)) {
+ return false;
+ }
+
+ // Floating point gets an implementation-type comparison; all others just use the string
+ // If float/double parse fails, fall back to string comparison -- it means that it's a
+ // canonical droiddoc-generated constant expression that represents a NaN.
+ try {
+ if (mType.equals("float")) {
+ float val = Float.parseFloat(mValue);
+ float otherVal = Float.parseFloat(oValue);
+ return (val == otherVal);
+ } else if (mType.equals("double")) {
+ double val = Double.parseDouble(mValue);
+ double otherVal = Double.parseDouble(oValue);
+ return (val == otherVal);
+ }
+ } catch (NumberFormatException e) {
+ // fall through
+ }
+
+ return mValue.equals(oValue);
+ }
+
+ public boolean isConsistent(FieldInfo fInfo) {
+ boolean consistent = true;
+ if (!mType.equals(fInfo.mType)) {
+ Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
+ + " has changed type");
+ consistent = false;
+ } else if (!this.valueEquals(fInfo)) {
+ Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
+ + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
+ consistent = false;
+ }
+
+ if (!scope().equals(fInfo.scope())) {
+ Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
+ + " changed scope from " + this.scope() + " to " + fInfo.scope());
+ consistent = false;
+ }
+
+ if (mIsStatic != fInfo.mIsStatic) {
+ Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
+ + " has changed 'static' qualifier");
+ consistent = false;
+ }
+
+ if (mIsFinal != fInfo.mIsFinal) {
+ Errors.error(Errors.CHANGED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
+ + " has changed 'final' qualifier");
+ consistent = false;
+ }
+
+ if (mIsTransient != fInfo.mIsTransient) {
+ Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
+ + " has changed 'transient' qualifier");
+ consistent = false;
+ }
+
+ if (mIsVolatile != fInfo.mIsVolatile) {
+ Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
+ + " has changed 'volatile' qualifier");
+ consistent = false;
+ }
+
+ if (isDeprecated() != fInfo.isDeprecated()) {
+ Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
+ + " has changed deprecation state");
+ consistent = false;
+ }
+
+ return consistent;
+ }
+
+ boolean mIsTransient;
+ boolean mIsVolatile;
+ boolean mDeprecatedKnown;
+ boolean mIsDeprecated;
+ TypeInfo mType;
+ Object mConstantValue;
+}
diff --git a/src/com/google/doclava/Hierarchy.java b/src/com/google/doclava/Hierarchy.java
new file mode 100755
index 0000000..0887b63
--- /dev/null
+++ b/src/com/google/doclava/Hierarchy.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.HashMap;
+import java.util.TreeSet;
+import java.util.Set;
+
+public class Hierarchy {
+ public static void makeHierarchy(Data 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.getChild("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, Data 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", "");
+ Data children = hdf.getChild("derived");
+ i = 0;
+ remainingDepth--;
+ for (String s : derived) {
+ String index = "" + i;
+ children.setValue(index, "");
+ recurse(nodes, s, children.getChild(index), totalDepth, remainingDepth);
+ i++;
+ }
+ }
+
+ nodes.remove(name);
+ }
+}
diff --git a/src/com/google/doclava/InheritedTags.java b/src/com/google/doclava/InheritedTags.java
new file mode 100644
index 0000000..1915721
--- /dev/null
+++ b/src/com/google/doclava/InheritedTags.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+public interface InheritedTags {
+ TagInfo[] tags();
+
+ InheritedTags inherited();
+}
diff --git a/src/com/google/doclava/JarUtils.java b/src/com/google/doclava/JarUtils.java
new file mode 100644
index 0000000..a5925ec
--- /dev/null
+++ b/src/com/google/doclava/JarUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class JarUtils {
+ /**
+ * Returns the jar file used to load class clazz, or defaultJar if clazz was not loaded from a
+ * jar.
+ */
+ public static JarFile jarForClass(Class<?> clazz, JarFile defaultJar) {
+ String path = "/" + clazz.getName().replace('.', '/') + ".class";
+ URL jarUrl = clazz.getResource(path);
+ if (jarUrl == null) {
+ return defaultJar;
+ }
+
+ String url = jarUrl.toString();
+ int bang = url.indexOf("!");
+ String JAR_URI_PREFIX = "jar:file:";
+ if (url.startsWith(JAR_URI_PREFIX) && bang != -1) {
+ try {
+ return new JarFile(url.substring(JAR_URI_PREFIX.length(), bang));
+ } catch (IOException e) {
+ throw new IllegalStateException("Error loading jar file.", e);
+ }
+ } else {
+ return defaultJar;
+ }
+ }
+
+ /**
+ * Copies a directory from a jar file to an external directory.
+ */
+ public static void copyResourcesToDirectory(JarFile fromJar, String jarDir, String destDir)
+ throws IOException {
+ for (Enumeration<JarEntry> entries = fromJar.entries(); entries.hasMoreElements();) {
+ JarEntry entry = entries.nextElement();
+ if (entry.getName().startsWith(jarDir + "/") && !entry.isDirectory()) {
+ File dest = new File(destDir + "/" + entry.getName().substring(jarDir.length() + 1));
+ File parent = dest.getParentFile();
+ if (parent != null) {
+ parent.mkdirs();
+ }
+
+ FileOutputStream out = new FileOutputStream(dest);
+ InputStream in = fromJar.getInputStream(entry);
+
+ try {
+ byte[] buffer = new byte[8 * 1024];
+
+ int s = 0;
+ while ((s = in.read(buffer)) > 0) {
+ out.write(buffer, 0, s);
+ }
+ } catch (IOException e) {
+ throw new IOException("Could not copy asset from jar file", e);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ignored) {}
+ try {
+ out.close();
+ } catch (IOException ignored) {}
+ }
+ }
+ }
+
+ }
+
+ private JarUtils() {} // non-instantiable
+}
diff --git a/src/com/google/doclava/KeywordEntry.java b/src/com/google/doclava/KeywordEntry.java
new file mode 100644
index 0000000..28f2d47
--- /dev/null
+++ b/src/com/google/doclava/KeywordEntry.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+class KeywordEntry implements Comparable {
+ KeywordEntry(String label, String href, String comment) {
+ this.label = label;
+ this.href = href;
+ this.comment = comment;
+ }
+
+ public void makeHDF(Data 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/src/com/google/doclava/LinkReference.java b/src/com/google/doclava/LinkReference.java
new file mode 100644
index 0000000..8afba0b
--- /dev/null
+++ b/src/com/google/doclava/LinkReference.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import 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);
+
+ /**
+ * regex pattern to use when matching double-quoted reference text
+ */
+ private static final Pattern QUOTE_PATTERN = Pattern.compile("^\"([^\"]*)\"[ \n\r\t]*$");
+
+ /**
+ * 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;
+ boolean varargs = false;
+ 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 (mem.length() > i+2 && mem.charAt(i+1) == '.' && mem.charAt(i+2) == '.') {
+ if (typeend < 0) {
+ typeend = i;
+ }
+ varargs = true;
+ }
+ } else 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, varargs);
+ 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)
+ Matcher matcher = QUOTE_PATTERN.matcher(text);
+ if (!matcher.matches()) {
+ Errors.error(Errors.UNRESOLVED_LINK, pos, "unbalanced quoted link/see tag: " + text.trim());
+ result.makeError();
+ return result;
+ }
+ skipHref = true;
+ result.label = matcher.group(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()
+ + "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
+ }
+ 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/src/com/google/doclava/LiteralTagInfo.java b/src/com/google/doclava/LiteralTagInfo.java
new file mode 100644
index 0000000..1feb276
--- /dev/null
+++ b/src/com/google/doclava/LiteralTagInfo.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+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 text, SourcePositionInfo sp) {
+ super("Text", "Text", encode(text), sp);
+ }
+}
diff --git a/src/com/google/doclava/MemberInfo.java b/src/com/google/doclava/MemberInfo.java
new file mode 100644
index 0000000..9855e01
--- /dev/null
+++ b/src/com/google/doclava/MemberInfo.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+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 String scope() {
+ if (isPublic()) {
+ return "public";
+ } else if (isProtected()) {
+ return "protected";
+ } else if (isPackagePrivate()) {
+ return "";
+ } else if (isPrivate()) {
+ return "private";
+ } else {
+ throw new RuntimeException("invalid scope for object " + this);
+ }
+ }
+
+ public boolean isStatic() {
+ return mIsStatic;
+ }
+
+ public boolean isFinal() {
+ return mIsFinal;
+ }
+
+ public boolean isSynthetic() {
+ return mIsSynthetic;
+ }
+
+ @Override
+ public ContainerInfo parent() {
+ return mContainingClass;
+ }
+
+ public boolean checkLevel() {
+ return Doclava.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/src/com/google/doclava/MethodInfo.java b/src/com/google/doclava/MethodInfo.java
new file mode 100644
index 0000000..c7e2304
--- /dev/null
+++ b/src/com/google/doclava/MethodInfo.java
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.doclava.apicheck.AbstractMethodInfo;
+
+import java.util.*;
+
+public class MethodInfo extends MemberInfo implements AbstractMethodInfo {
+ 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().isDeprecated();
+ 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 void setDeprecated(boolean deprecated) {
+ mDeprecatedKnown = true;
+ mIsDeprecated = deprecated;
+ }
+
+ 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) {
+ // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
+ // the Java5-emitted base API description.
+ super(rawCommentText, name, signature, containingClass, realContainingClass, isPublic,
+ isProtected, isPackagePrivate, isPrivate, ((name.equals("values") && containingClass
+ .isEnum()) ? true : 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() {
+ return name() + prettyParameters();
+ }
+
+ /**
+ * Returns a printable version of the parameters of this method's signature.
+ */
+ public String prettyParameters() {
+ StringBuilder params = new StringBuilder("(");
+ for (ParameterInfo pInfo : mParameters) {
+ if (params.length() > 1) {
+ params.append(",");
+ }
+ params.append(pInfo.type().simpleTypeName());
+ }
+
+ params.append(")");
+ return params.toString();
+ }
+
+ /**
+ * Returns a name consistent with the {@link com.google.doclava.MethodInfo#getHashableName()}.
+ */
+ public String getHashableName() {
+ StringBuilder result = new StringBuilder();
+ result.append(name());
+ for (int p = 0; p < mParameters.length; p++) {
+ result.append(":");
+ if (p == mParameters.length - 1 && isVarArgs()) {
+ // TODO: note that this does not attempt to handle hypothetical
+ // vararg methods whose last parameter is a list of arrays, e.g.
+ // "Object[]...".
+ result.append(mParameters[p].type().fullNameNoDimension(typeVariables())).append("...");
+ } else {
+ result.append(mParameters[p].type().fullName(typeVariables()));
+ }
+ }
+ return result.toString();
+ }
+
+ 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, boolean varargs) {
+ if (mParamStrings == null) {
+ ParameterInfo[] mine = mParameters;
+ int len = mine.length;
+ if (len != params.length) {
+ return false;
+ }
+ for (int i = 0; i < len; i++) {
+ if (!mine[i].matchesDimension(dimensions[i], varargs)) {
+ return false;
+ }
+ TypeInfo myType = mine[i].type();
+ String qualifiedName = myType.qualifiedTypeName();
+ String realType = myType.isPrimitive() ? "" : myType.asClassInfo().qualifiedName();
+ String s = params[i];
+ int slen = s.length();
+ int qnlen = qualifiedName.length();
+
+ // Check for a matching generic name or best known type
+ if (!matchesType(qualifiedName, s) && !matchesType(realType, s)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks to see if a parameter from a method signature is
+ * compatible with a parameter given in a {@code @link} tag.
+ */
+ private boolean matchesType(String signatureParam, String callerParam) {
+ int signatureLength = signatureParam.length();
+ int callerLength = callerParam.length();
+ return ((signatureParam.equals(callerParam) || ((callerLength + 1) < signatureLength
+ && signatureParam.charAt(signatureLength - callerLength - 1) == '.'
+ && signatureParam.endsWith(callerParam))));
+ }
+
+ public void makeHDF(Data 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());
+ data.setValue(base + ".since", getSince());
+ 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);
+ }
+
+ setFederatedReferences(data, base);
+ }
+
+ 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return this.name();
+ }
+
+ public void setReason(String reason) {
+ mReasonOpened = reason;
+ }
+
+ public String getReason() {
+ return mReasonOpened;
+ }
+
+ public void addException(String exec) {
+ ClassInfo exceptionClass = new ClassInfo(exec);
+ List<ClassInfo> exceptions = new ArrayList<ClassInfo>(mThrownExceptions.length + 1);
+ exceptions.addAll(Arrays.asList(mThrownExceptions));
+ exceptions.add(exceptionClass);
+ mThrownExceptions = new ClassInfo[exceptions.size()];
+ exceptions.toArray(mThrownExceptions);
+ }
+
+ public void addParameter(ParameterInfo p) {
+ // Name information
+ ParameterInfo[] newParams;
+ int i = 0;
+
+ if (mParameters == null) {
+ newParams = new ParameterInfo[1];
+ } else {
+ newParams = new ParameterInfo[mParameters.length+1];
+ for (ParameterInfo info : mParameters) {
+ newParams[i++] = info;
+ }
+ }
+ newParams[i] = p;
+ mParameters = newParams;
+
+ // Type information
+ TypeInfo[] newTypes;
+ i = 0;
+
+ if (mTypeParameters == null) {
+ newTypes = new TypeInfo[1];
+ } else {
+ newTypes = new TypeInfo[mTypeParameters.length+1];
+ for (TypeInfo info : mTypeParameters) {
+ newTypes[i++] = info;
+ }
+ }
+ newTypes[i] = p.mType;
+ mTypeParameters = newTypes;
+ }
+
+ 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;
+
+ // TODO: merge with droiddoc version (above)
+ public String qualifiedName() {
+ String parentQName = (containingClass() != null)
+ ? (containingClass().qualifiedName() + ".") : "";
+ return parentQName + name();
+ }
+
+ @Override
+ public String signature() {
+ if (mSignature == null) {
+ StringBuilder params = new StringBuilder("(");
+ for (ParameterInfo pInfo : mParameters) {
+ if (params.length() > 1) {
+ params.append(", ");
+ }
+ params.append(pInfo.type().fullName());
+ }
+
+ params.append(")");
+ mSignature = params.toString();
+ }
+ return mSignature;
+ }
+
+ public boolean matches(MethodInfo other) {
+ return signature().equals(other.signature());
+ }
+
+ public boolean throwsException(ClassInfo exception) {
+ for (ClassInfo e : mThrownExceptions) {
+ if (e.qualifiedName().equals(exception.qualifiedName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isConsistent(MethodInfo mInfo) {
+ boolean consistent = true;
+ if (!this.mReturnType.equals(mInfo.mReturnType)) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " has changed return type from " + mReturnType + " to " + mInfo.mReturnType);
+ }
+
+ if (mIsAbstract != mInfo.mIsAbstract) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " has changed 'abstract' qualifier");
+ }
+
+ if (mIsNative != mInfo.mIsNative) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_NATIVE, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " has changed 'native' qualifier");
+ }
+
+ if (mIsFinal != mInfo.mIsFinal) {
+ // Compiler-generated methods vary in their 'final' qual between versions of
+ // the compiler, so this check needs to be quite narrow. A change in 'final'
+ // status of a method is only relevant if (a) the method is not declared 'static'
+ // and (b) the method's class is not itself 'final'.
+ if (!mIsStatic) {
+ if ((containingClass() == null) || (!containingClass().isFinal())) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " has changed 'final' qualifier");
+ }
+ }
+ }
+
+ if (mIsStatic != mInfo.mIsStatic) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_STATIC, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " has changed 'static' qualifier");
+ }
+
+ if (!scope().equals(mInfo.scope())) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SCOPE, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " changed scope from " + scope() + " to " + mInfo.scope());
+ }
+
+ if (!isDeprecated() == mInfo.isDeprecated()) {
+ Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " has changed deprecation state");
+ consistent = false;
+ }
+
+ if (mIsSynchronized != mInfo.mIsSynchronized) {
+ Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
+ + mInfo.mIsSynchronized);
+ consistent = false;
+ }
+
+ for (ClassInfo exception : thrownExceptions()) {
+ if (!mInfo.throwsException(exception)) {
+ // exclude 'throws' changes to finalize() overrides with no arguments
+ if (!name().equals("finalize") || (mParameters.length > 0)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " no longer throws exception " + exception.qualifiedName());
+ consistent = false;
+ }
+ }
+ }
+
+ for (ClassInfo exec : mInfo.thrownExceptions()) {
+ // exclude 'throws' changes to finalize() overrides with no arguments
+ if (!throwsException(exec)) {
+ if (!name().equals("finalize") || (mParameters.length > 0)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(), "Method " + mInfo.qualifiedName()
+ + " added thrown exception " + exec.qualifiedName());
+ consistent = false;
+ }
+ }
+ }
+
+ return consistent;
+ }
+}
diff --git a/src/com/google/doclava/NavTree.java b/src/com/google/doclava/NavTree.java
new file mode 100644
index 0000000..5c3e53d
--- /dev/null
+++ b/src/com/google/doclava/NavTree.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NavTree {
+
+ public static void writeNavTree(String dir) {
+ List<Node> children = new ArrayList<Node>();
+ for (PackageInfo pkg : Doclava.choosePackages()) {
+ children.add(makePackageNode(pkg));
+ }
+ Node node = new Node("Reference", dir + "packages.html", children, null);
+
+ StringBuilder buf = new StringBuilder();
+ if (false) {
+ // if you want a root node
+ buf.append("[");
+ node.render(buf);
+ buf.append("]");
+ } else {
+ // if you don't want a root node
+ node.renderChildren(buf);
+ }
+
+ Data data = Doclava.makeHDF();
+ data.setValue("reference_tree", buf.toString());
+ ClearPage.write(data, "navtree_data.cs", "navtree_data.js");
+ }
+
+ private static Node makePackageNode(PackageInfo pkg) {
+ List<Node> children = new ArrayList<Node>();
+
+ children.add(new Node("Description", pkg.fullDescriptionHtmlPage(), null, null));
+
+ addClassNodes(children, "Interfaces", pkg.interfaces());
+ addClassNodes(children, "Classes", pkg.ordinaryClasses());
+ addClassNodes(children, "Enums", pkg.enums());
+ addClassNodes(children, "Exceptions", pkg.exceptions());
+ addClassNodes(children, "Errors", pkg.errors());
+
+ return new Node(pkg.name(), pkg.htmlPage(), children, pkg.getSince());
+ }
+
+ private static void addClassNodes(List<Node> parent, String label, ClassInfo[] classes) {
+ List<Node> children = new ArrayList<Node>();
+
+ for (ClassInfo cl : classes) {
+ if (cl.checkLevel()) {
+ children.add(new Node(cl.name(), cl.htmlPage(), null, cl.getSince()));
+ }
+ }
+
+ if (children.size() > 0) {
+ parent.add(new Node(label, null, children, null));
+ }
+ }
+
+ private static class Node {
+ private String mLabel;
+ private String mLink;
+ List<Node> mChildren;
+ private String mSince;
+
+ Node(String label, String link, List<Node> children, String since) {
+ mLabel = label;
+ mLink = link;
+ mChildren = children;
+ mSince = since;
+ }
+
+ static void renderString(StringBuilder buf, String s) {
+ if (s == null) {
+ buf.append("null");
+ } else {
+ buf.append('"');
+ final int N = s.length();
+ for (int i = 0; i < N; i++) {
+ char c = s.charAt(i);
+ if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
+ buf.append(c);
+ } else {
+ buf.append("\\u");
+ for (int j = 0; i < 4; i++) {
+ char x = (char) (c & 0x000f);
+ if (x > 10) {
+ x = (char) (x - 10 + 'a');
+ } else {
+ x = (char) (x + '0');
+ }
+ buf.append(x);
+ c >>= 4;
+ }
+ }
+ }
+ buf.append('"');
+ }
+ }
+
+ void renderChildren(StringBuilder buf) {
+ List<Node> list = mChildren;
+ if (list == null || list.size() == 0) {
+ // We output null for no children. That way empty lists here can just
+ // be a byproduct of how we generate the lists.
+ buf.append("null");
+ } else {
+ buf.append("[ ");
+ final int N = list.size();
+ for (int i = 0; i < N; i++) {
+ list.get(i).render(buf);
+ if (i != N - 1) {
+ buf.append(", ");
+ }
+ }
+ buf.append(" ]\n");
+ }
+ }
+
+ void render(StringBuilder buf) {
+ buf.append("[ ");
+ renderString(buf, mLabel);
+ buf.append(", ");
+ renderString(buf, mLink);
+ buf.append(", ");
+ renderChildren(buf);
+ buf.append(", ");
+ renderString(buf, mSince);
+ buf.append(" ]");
+ }
+ }
+}
diff --git a/src/com/google/doclava/PackageInfo.java b/src/com/google/doclava/PackageInfo.java
new file mode 100644
index 0000000..7651b5c
--- /dev/null
+++ b/src/com/google/doclava/PackageInfo.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import com.sun.javadoc.*;
+import java.util.*;
+
+public class PackageInfo extends DocInfo implements ContainerInfo {
+ public static final String DEFAULT_PACKAGE = "default package";
+
+ 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);
+ if (name.isEmpty()) {
+ mName = DEFAULT_PACKAGE;
+ } else {
+ mName = name;
+ }
+
+ mPackage = pkg;
+ }
+
+ public PackageInfo(String name) {
+ super("", null);
+ mName = name;
+ }
+
+ public PackageInfo(String name, SourcePositionInfo position) {
+ super("", position);
+
+ if (name.isEmpty()) {
+ mName = "default package";
+ } else {
+ mName = name;
+ }
+ }
+
+ public String htmlPage() {
+ String s = mName;
+ s = s.replace('.', '/');
+ s += "/package-summary.html";
+ s = Doclava.javadocDir + s;
+ return s;
+ }
+
+ public String fullDescriptionHtmlPage() {
+ String s = mName;
+ s = s.replace('.', '/');
+ s += "/package-descr.html";
+ s = Doclava.javadocDir + s;
+ return s;
+ }
+
+ @Override
+ public ContainerInfo parent() {
+ return null;
+ }
+
+ @Override
+ 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(Data data, String base) {
+ if (checkLevel()) {
+ data.setValue(base + ".link", htmlPage());
+ }
+ data.setValue(base + ".name", name());
+ data.setValue(base + ".since", getSince());
+ }
+
+ public void makeClassLinkListHDF(Data data, String base) {
+ makeLink(data, base);
+ ClassInfo.makeLinkListHDF(data, base + ".interfaces", interfaces());
+ ClassInfo.makeLinkListHDF(data, base + ".classes", ordinaryClasses());
+ ClassInfo.makeLinkListHDF(data, base + ".enums", enums());
+ ClassInfo.makeLinkListHDF(data, base + ".exceptions", exceptions());
+ ClassInfo.makeLinkListHDF(data, base + ".errors", errors());
+ data.setValue(base + ".since", getSince());
+ }
+
+ public ClassInfo[] interfaces() {
+ if (mInterfaces == null) {
+ mInterfaces =
+ ClassInfo.sortByName(filterHidden(Converter.convertClasses(mPackage.interfaces())));
+ }
+ return mInterfaces;
+ }
+
+ public ClassInfo[] ordinaryClasses() {
+ if (mOrdinaryClasses == null) {
+ mOrdinaryClasses =
+ ClassInfo.sortByName(filterHidden(Converter.convertClasses(mPackage.ordinaryClasses())));
+ }
+ return mOrdinaryClasses;
+ }
+
+ public ClassInfo[] enums() {
+ if (mEnums == null) {
+ mEnums = ClassInfo.sortByName(filterHidden(Converter.convertClasses(mPackage.enums())));
+ }
+ return mEnums;
+ }
+
+ public ClassInfo[] exceptions() {
+ if (mExceptions == null) {
+ mExceptions =
+ ClassInfo.sortByName(filterHidden(Converter.convertClasses(mPackage.exceptions())));
+ }
+ return mExceptions;
+ }
+
+ public ClassInfo[] errors() {
+ if (mErrors == null) {
+ mErrors = ClassInfo.sortByName(filterHidden(Converter.convertClasses(mPackage.errors())));
+ }
+ return mErrors;
+ }
+
+ // in hashed containers, treat the name as the key
+ @Override
+ public int hashCode() {
+ return mName.hashCode();
+ }
+
+ private String mName;
+ private PackageDoc mPackage;
+ private ClassInfo[] mInterfaces;
+ private ClassInfo[] mOrdinaryClasses;
+ private ClassInfo[] mEnums;
+ private ClassInfo[] mExceptions;
+ private ClassInfo[] mErrors;
+
+ // TODO: Leftovers from ApiCheck that should be better merged.
+ private HashMap<String, ClassInfo> mClasses = new HashMap<String, ClassInfo>();
+
+ public void addClass(ClassInfo cl) {
+ mClasses.put(cl.name(), cl);
+ }
+
+ public HashMap<String, ClassInfo> allClasses() {
+ return mClasses;
+ }
+
+ public boolean isConsistent(PackageInfo pInfo) {
+ boolean consistent = true;
+ for (ClassInfo cInfo : mClasses.values()) {
+ if (pInfo.mClasses.containsKey(cInfo.name())) {
+ if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_CLASS, cInfo.position(), "Removed public class "
+ + cInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ for (ClassInfo cInfo : pInfo.mClasses.values()) {
+ if (!mClasses.containsKey(cInfo.name())) {
+ Errors.error(Errors.ADDED_CLASS, cInfo.position(), "Added class " + cInfo.name()
+ + " to package " + pInfo.name());
+ consistent = false;
+ }
+ }
+ return consistent;
+ }
+}
diff --git a/src/com/google/doclava/ParamTagInfo.java b/src/com/google/doclava/ParamTagInfo.java
new file mode 100644
index 0000000..f8329bc
--- /dev/null
+++ b/src/com/google/doclava/ParamTagInfo.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+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;
+ }
+
+ @Override
+ public void makeHDF(Data 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(Data 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/src/com/google/doclava/ParameterInfo.java b/src/com/google/doclava/ParameterInfo.java
new file mode 100644
index 0000000..f4e39f0
--- /dev/null
+++ b/src/com/google/doclava/ParameterInfo.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.HashSet;
+
+public class ParameterInfo {
+ public ParameterInfo(String name, String typeName, TypeInfo type, boolean isVarArg,
+ SourcePositionInfo position) {
+ mName = name;
+ mTypeName = typeName;
+ mType = type;
+ mIsVarArg = isVarArg;
+ mPosition = position;
+ }
+
+ TypeInfo type() {
+ return mType;
+ }
+
+ String name() {
+ return mName;
+ }
+
+ String typeName() {
+ return mTypeName;
+ }
+
+ SourcePositionInfo position() {
+ return mPosition;
+ }
+
+ boolean isVarArg() {
+ return mIsVarArg;
+ }
+
+ public void makeHDF(Data 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(Data 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);
+ }
+ }
+
+ /**
+ * Returns true if this parameter's dimension information agrees
+ * with the represented callee's dimension information.
+ */
+ public boolean matchesDimension(String dimension, boolean varargs) {
+ if (varargs) {
+ dimension += "[]";
+ }
+ return mType.dimension().equals(dimension);
+ }
+
+ String mName;
+ String mTypeName;
+ TypeInfo mType;
+ boolean mIsVarArg;
+ SourcePositionInfo mPosition;
+}
diff --git a/src/com/google/doclava/ParsedTagInfo.java b/src/com/google/doclava/ParsedTagInfo.java
new file mode 100755
index 0000000..bcb9230
--- /dev/null
+++ b/src/com/google/doclava/ParsedTagInfo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import 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/src/com/google/doclava/Proofread.java b/src/com/google/doclava/Proofread.java
new file mode 100644
index 0000000..64a0a1e
--- /dev/null
+++ b/src/com/google/doclava/Proofread.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import 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/src/com/google/doclava/SampleCode.java b/src/com/google/doclava/SampleCode.java
new file mode 100644
index 0000000..91b1746
--- /dev/null
+++ b/src/com/google/doclava/SampleCode.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.*;
+import java.io.*;
+
+
+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(boolean offlineMode) {
+ File f = new File(mSource);
+ if (!f.isDirectory()) {
+ System.out.println("-samplecode not a directory: " + mSource);
+ return;
+ }
+ if (offlineMode)
+ writeIndexOnly(f, mDest, offlineMode);
+ else
+ 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, Doclava.htmlExtension), subdir);
+ files.add(name);
+ }
+ if (inList(out, TEMPLATED)) {
+ // copied and goes through the template
+ ClearPage.copyFile(f, out);
+ writePage(f, convertExtension(out, Doclava.htmlExtension), subdir);
+ files.add(name);
+ }
+ // else ignored
+ } else if (f.isDirectory()) {
+ writeDirectory(f, relative + name + "/");
+ dirs.add(name);
+ }
+ }
+
+ // write the index page
+ int i;
+
+ Data hdf = writeIndex(dir);
+ 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++;
+ }
+
+ ClearPage.write(hdf, "sampleindex.cs", relative + "/index" + Doclava.htmlExtension);
+ }
+
+ public void writeIndexOnly(File dir, String relative, Boolean offline) {
+ Data hdf = writeIndex(dir);
+ if (!offline) relative = "/" + relative;
+ ClearPage.write(hdf, "sampleindex.cs", relative + "index" + Doclava.htmlExtension);
+ }
+
+ public Data writeIndex(File dir) {
+ Data hdf = Doclava.makeHDF();
+
+ hdf.setValue("page.title", dir.getName() + " - " + mTitle);
+ hdf.setValue("projectTitle", mTitle);
+
+ 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);
+
+ return hdf;
+ }
+
+ 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 = Doclava.escape(data);
+
+ Data hdf = Doclava.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 + "\" />";
+
+ Data hdf = Doclava.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/src/com/google/doclava/SampleTagInfo.java b/src/com/google/doclava/SampleTagInfo.java
new file mode 100644
index 0000000..9b9e40a
--- /dev/null
+++ b/src/com/google/doclava/SampleTagInfo.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+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);
+ }
+
+ @Override
+ public void makeHDF(Data 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/src/com/google/doclava/Scoped.java b/src/com/google/doclava/Scoped.java
new file mode 100644
index 0000000..03e42f9
--- /dev/null
+++ b/src/com/google/doclava/Scoped.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+public interface Scoped {
+ boolean isPublic();
+
+ boolean isProtected();
+
+ boolean isPackagePrivate();
+
+ boolean isPrivate();
+
+ boolean isHidden();
+}
diff --git a/src/com/google/doclava/SeeTagInfo.java b/src/com/google/doclava/SeeTagInfo.java
new file mode 100644
index 0000000..40581cd
--- /dev/null
+++ b/src/com/google/doclava/SeeTagInfo.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+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;
+ }
+
+ @Override
+ public void makeHDF(Data 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(Data 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/src/com/google/doclava/SinceTagger.java b/src/com/google/doclava/SinceTagger.java
new file mode 100644
index 0000000..00e2029
--- /dev/null
+++ b/src/com/google/doclava/SinceTagger.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.doclava.apicheck.ApiCheck;
+import com.google.doclava.apicheck.ApiInfo;
+import com.google.doclava.apicheck.ApiParseException;
+
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Collections;
+
+
+/**
+ * Applies version information to the DroidDoc class model from apicheck XML files. Sample usage:
+ *
+ * <pre>
+ * ClassInfo[] classInfos = ...
+ *
+ * SinceTagger sinceTagger = new SinceTagger()
+ * sinceTagger.addVersion("frameworks/base/api/1.xml", "Android 1.0")
+ * sinceTagger.addVersion("frameworks/base/api/2.xml", "Android 1.5")
+ * sinceTagger.tagAll(...);
+ * </pre>
+ */
+public class SinceTagger {
+
+ private final Map<String, String> xmlToName = new LinkedHashMap<String, String>();
+
+ /**
+ * Specifies the apicheck XML file and the API version it holds. Calls to this method should be
+ * called in order from oldest version to newest.
+ */
+ public void addVersion(String file, String name) {
+ xmlToName.put(file, name);
+ }
+
+ public void tagAll(ClassInfo[] classDocs) {
+ // read through the XML files in order, applying their since information
+ // to the Javadoc models
+ for (Map.Entry<String, String> versionSpec : xmlToName.entrySet()) {
+ String xmlFile = versionSpec.getKey();
+ String versionName = versionSpec.getValue();
+
+ ApiInfo specApi;
+ try {
+ specApi = new ApiCheck().parseApi(xmlFile);
+ } catch (ApiParseException e) {
+ Errors.error(Errors.NO_SINCE_DATA, null, "Could not add since data for " + versionName);
+ continue;
+ }
+
+ applyVersionsFromSpec(versionName, specApi, classDocs);
+ }
+
+ if (!xmlToName.isEmpty()) {
+ warnForMissingVersions(classDocs);
+ }
+ }
+
+ public boolean hasVersions() {
+ return !xmlToName.isEmpty();
+ }
+
+ /**
+ * Writes an index of the version names to {@code data}.
+ */
+ public void writeVersionNames(Data data) {
+ int index = 1;
+ for (String version : xmlToName.values()) {
+ data.setValue("since." + index + ".name", version);
+ index++;
+ }
+ }
+
+ /**
+ * Applies the version information to {@code classDocs} where not already present.
+ *
+ * @param versionName the version name
+ * @param specApi the spec for this version. If a symbol is in this spec, it was present in the
+ * named version
+ * @param classDocs the doc model to update
+ */
+ private void applyVersionsFromSpec(String versionName, ApiInfo specApi, ClassInfo[] classDocs) {
+ for (ClassInfo classDoc : classDocs) {
+ PackageInfo packageSpec
+ = specApi.getPackages().get(classDoc.containingPackage().name());
+
+ if (packageSpec == null) {
+ continue;
+ }
+
+ ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name());
+
+ if (classSpec == null) {
+ continue;
+ }
+
+ versionPackage(versionName, classDoc.containingPackage());
+ versionClass(versionName, classDoc);
+ versionConstructors(versionName, classSpec, classDoc);
+ versionFields(versionName, classSpec, classDoc);
+ versionMethods(versionName, classSpec, classDoc);
+ }
+ }
+
+ /**
+ * Applies version information to {@code doc} where not already present.
+ */
+ private void versionPackage(String versionName, PackageInfo doc) {
+ if (doc.getSince() == null) {
+ doc.setSince(versionName);
+ }
+ }
+
+ /**
+ * Applies version information to {@code doc} where not already present.
+ */
+ private void versionClass(String versionName, ClassInfo doc) {
+ if (doc.getSince() == null) {
+ doc.setSince(versionName);
+ }
+ }
+
+ /**
+ * Applies version information from {@code spec} to {@code doc} where not already present.
+ */
+ private void versionConstructors(String versionName, ClassInfo spec, ClassInfo doc) {
+ for (MethodInfo constructor : doc.constructors()) {
+ if (constructor.getSince() == null
+ && spec.hasConstructor(constructor)) {
+ constructor.setSince(versionName);
+ }
+ }
+ }
+
+ /**
+ * Applies version information from {@code spec} to {@code doc} where not already present.
+ */
+ private void versionFields(String versionName, ClassInfo spec, ClassInfo doc) {
+ for (FieldInfo field : doc.fields()) {
+ if (field.getSince() == null && spec.allFields().containsKey(field.name())) {
+ field.setSince(versionName);
+ }
+ }
+ }
+
+ /**
+ * Applies version information from {@code spec} to {@code doc} where not already present.
+ */
+ private void versionMethods(String versionName, ClassInfo spec, ClassInfo doc) {
+ for (MethodInfo method : doc.methods()) {
+ if (method.getSince() != null) {
+ continue;
+ }
+
+ for (ClassInfo superclass : spec.hierarchy()) {
+ if (superclass.allMethods().containsKey(method.getHashableName())) {
+ method.setSince(versionName);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Warns if any symbols are missing version information. When configured properly, this will yield
+ * zero warnings because {@code apicheck} guarantees that all symbols are present in the most
+ * recent API.
+ */
+ private void warnForMissingVersions(ClassInfo[] classDocs) {
+ for (ClassInfo claz : classDocs) {
+ if (!checkLevelRecursive(claz)) {
+ continue;
+ }
+
+ if (claz.getSince() == null) {
+ Errors.error(Errors.NO_SINCE_DATA, claz.position(), "XML missing class "
+ + claz.qualifiedName());
+ }
+
+ for (FieldInfo field : missingVersions(claz.fields())) {
+ Errors.error(Errors.NO_SINCE_DATA, field.position(), "XML missing field "
+ + claz.qualifiedName() + "#" + field.name());
+ }
+
+ for (MethodInfo constructor : missingVersions(claz.constructors())) {
+ Errors.error(Errors.NO_SINCE_DATA, constructor.position(), "XML missing constructor "
+ + claz.qualifiedName() + "#" + constructor.getHashableName());
+ }
+
+ for (MethodInfo method : missingVersions(claz.methods())) {
+ Errors.error(Errors.NO_SINCE_DATA, method.position(), "XML missing method "
+ + claz.qualifiedName() + "#" + method.getHashableName());
+ }
+ }
+ }
+
+ /**
+ * Returns the DocInfos in {@code all} that are documented but do not have since tags.
+ */
+ private <T extends MemberInfo> Iterable<T> missingVersions(T[] all) {
+ List<T> result = Collections.emptyList();
+ for (T t : all) {
+ // if this member has version info or isn't documented, skip it
+ if (t.getSince() != null || t.isHidden() || !checkLevelRecursive(t.realContainingClass())) {
+ continue;
+ }
+
+ if (result.isEmpty()) {
+ result = new ArrayList<T>(); // lazily construct a mutable list
+ }
+ result.add(t);
+ }
+ return result;
+ }
+
+ /**
+ * Returns true if {@code claz} and all containing classes are documented. The result may be used
+ * to filter out members that exist in the API data structure but aren't a part of the API.
+ */
+ private boolean checkLevelRecursive(ClassInfo claz) {
+ for (ClassInfo c = claz; c != null; c = c.containingClass()) {
+ if (!c.checkLevel()) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/google/doclava/Sorter.java b/src/com/google/doclava/Sorter.java
new file mode 100644
index 0000000..2ee8f67
--- /dev/null
+++ b/src/com/google/doclava/Sorter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+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/src/com/google/doclava/SourcePositionInfo.java b/src/com/google/doclava/SourcePositionInfo.java
new file mode 100644
index 0000000..cf516c8
--- /dev/null
+++ b/src/com/google/doclava/SourcePositionInfo.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+public class SourcePositionInfo implements Comparable {
+ public static final SourcePositionInfo UNKNOWN = new SourcePositionInfo("(unknown)", 0, 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);
+ }
+
+ @Override
+ 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;
+ }
+
+ /**
+ * Build a SourcePositionInfo from the XML source= notation
+ */
+ public static SourcePositionInfo fromXml(String source) {
+ if (source != null) {
+ for (int i = 0; i < source.length(); i++) {
+ if (source.charAt(i) == ':') {
+ return new SourcePositionInfo(source.substring(0, i), Integer.parseInt(source
+ .substring(i + 1)), 0);
+ }
+ }
+ }
+
+ return UNKNOWN;
+ }
+
+ public String file;
+ public int line;
+ public int column;
+}
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
new file mode 100644
index 0000000..1fb67e8
--- /dev/null
+++ b/src/com/google/doclava/Stubs.java
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import 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.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+
+public class Stubs {
+ private static HashSet<ClassInfo> notStrippable;
+
+ public static void writeStubsAndXml(String stubsDir, String xmlFile,
+ HashSet<String> stubPackages) {
+ // figure out which classes we need
+ notStrippable = new HashSet<ClassInfo>();
+ ClassInfo[] all = Converter.allClasses();
+ PrintStream xmlWriter = null;
+ if (xmlFile != null) {
+ try {
+ File xml = new File(xmlFile);
+ xml.getParentFile().mkdirs();
+ 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, m.position(), "Parameter of hidden type "
+ + t.fullName() + " in " + cl.qualifiedName() + "." + m.name() + "()");
+ }
+ }
+ }
+ }
+
+ // 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");
+ }
+ }
+
+ 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())) {
+ // write out the stubs
+ if (stubsDir != null) {
+ writeClassFile(stubsDir, cl);
+ }
+ // build class list for xml file
+ if (xmlWriter != null) {
+ 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 (xmlWriter != null) {
+ writeXML(xmlWriter, packages, notStrippable);
+ 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) {
+ ClassInfo tcl = tInfoType.asClassInfo();
+ if (tcl.isHidden()) {
+ Errors
+ .error(Errors.UNAVAILABLE_SYMBOL, mInfo.position(),
+ "Parameter of hidden type " + tInfoType.fullName() + " in "
+ + mInfo.containingClass().qualifiedName() + '.' + mInfo.name()
+ + "()");
+ } else {
+ cantStripThis(tcl, 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;
+ }
+
+ // Work around the bogus "Array" class we invent for
+ // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
+ if (cl.containingPackage() != null
+ && cl.containingPackage().name().equals(PackageInfo.DEFAULT_PACKAGE)) {
+ 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(cl.scope() + " ");
+ 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(method.scope() + " ");
+ 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()) {
+ stream.print(comma + fullParameterTypeName(method, param.type(), count == size) + " "
+ + 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(field.scope() + " ");
+ 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);
+ // Work around the bogus "Array" class we invent for
+ // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
+ if (pack.name().equals(PackageInfo.DEFAULT_PACKAGE)) {
+ return;
+ }
+ 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 = cl.scope();
+ 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 = mi.scope();
+
+ String deprecatedString = "";
+ if (mi.isDeprecated()) {
+ deprecatedString = "deprecated";
+ } else {
+ deprecatedString = "not deprecated";
+ }
+ xmlWriter.println("<method name=\""
+ + mi.name()
+ + "\"\n"
+ + ((mi.returnType() != null) ? " return=\""
+ + makeXMLcompliant(fullParameterTypeName(mi, mi.returnType(), false)) + "\"\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
+ int numParameters = mi.parameters().length;
+ int count = 0;
+ for (ParameterInfo pi : mi.parameters()) {
+ count++;
+ writeParameterXML(xmlWriter, mi, pi, count == numParameters);
+ }
+
+ // 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 = mi.scope();
+ 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"
+ + ">");
+
+ int numParameters = mi.parameters().length;
+ int count = 0;
+ for (ParameterInfo pi : mi.parameters()) {
+ count++;
+ writeParameterXML(xmlWriter, mi, pi, count == numParameters);
+ }
+
+ 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, MethodInfo method, ParameterInfo pi,
+ boolean isLast) {
+ xmlWriter.println("<parameter name=\"" + pi.name() + "\" type=\""
+ + makeXMLcompliant(fullParameterTypeName(method, pi.type(), isLast)) + "\">");
+ xmlWriter.println("</parameter>");
+ }
+
+ static void writeFieldXML(PrintStream xmlWriter, FieldInfo fi) {
+ String scope = fi.scope();
+ String deprecatedString = "";
+ if (fi.isDeprecated()) {
+ deprecatedString = "deprecated";
+ } else {
+ deprecatedString = "not deprecated";
+ }
+ // need to make sure value is valid XML
+ String value = makeXMLcompliant(fi.constantLiteralValue());
+
+ String fullTypeName = makeXMLcompliant(fi.type().qualifiedTypeName()) + fi.type().dimension();
+
+ xmlWriter.println("<field name=\"" + fi.name() + "\"\n" + " type=\"" + fullTypeName + "\"\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;
+ }
+
+ static String fullParameterTypeName(MethodInfo method, TypeInfo type, boolean isLast) {
+ String fullTypeName = type.fullName(method.typeVariables());
+ if (isLast && 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 = type.fullNameNoDimension(method.typeVariables()) + "...";
+ }
+ return fullTypeName;
+ }
+}
diff --git a/src/com/google/doclava/TagInfo.java b/src/com/google/doclava/TagInfo.java
new file mode 100644
index 0000000..09d4e35
--- /dev/null
+++ b/src/com/google/doclava/TagInfo.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+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(Data data, String base) {
+ data.setValue(base + ".name", name());
+ data.setValue(base + ".text", text());
+ data.setValue(base + ".kind", kind());
+ }
+
+ public static void makeHDF(Data data, String base, TagInfo[] tags) {
+ makeHDF(data, base, tags, null, 0, 0);
+ }
+
+ public static void makeHDF(Data data, String base, InheritedTags tags) {
+ makeHDF(data, base, tags.tags(), tags.inherited(), 0, 0);
+ }
+
+ private static int makeHDF(Data 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/src/com/google/doclava/TextTagInfo.java b/src/com/google/doclava/TextTagInfo.java
new file mode 100644
index 0000000..35a486b
--- /dev/null
+++ b/src/com/google/doclava/TextTagInfo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+public class TextTagInfo extends TagInfo {
+ TextTagInfo(String n, String k, String t, SourcePositionInfo p) {
+ super(n, k, Doclava.escape(t), p);
+ }
+}
diff --git a/src/com/google/doclava/ThrowsTagInfo.java b/src/com/google/doclava/ThrowsTagInfo.java
new file mode 100644
index 0000000..5f49485
--- /dev/null
+++ b/src/com/google/doclava/ThrowsTagInfo.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+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(Data 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/src/com/google/doclava/TodoFile.java b/src/com/google/doclava/TodoFile.java
new file mode 100644
index 0000000..5cf4f1e
--- /dev/null
+++ b/src/com/google/doclava/TodoFile.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.*;
+
+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(Data 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) {
+ Data data = Doclava.makeHDF();
+ Doclava.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.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/src/com/google/doclava/TypeInfo.java b/src/com/google/doclava/TypeInfo.java
new file mode 100644
index 0000000..8572f59
--- /dev/null
+++ b/src/com/google/doclava/TypeInfo.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import java.util.*;
+
+public class TypeInfo {
+ public static final Set<String> PRIMITIVE_TYPES = Collections.unmodifiableSet(
+ new HashSet<String>(Arrays.asList("boolean", "byte", "char", "double", "float", "int",
+ "long", "short", "void")));
+
+ public TypeInfo(boolean isPrimitive, String dimension, String simpleTypeName,
+ String qualifiedTypeName, ClassInfo cl) {
+ mIsPrimitive = isPrimitive;
+ mDimension = dimension;
+ mSimpleTypeName = simpleTypeName;
+ mQualifiedTypeName = qualifiedTypeName;
+ mClass = cl;
+ }
+
+ public TypeInfo(String typeString) {
+ // VarArgs
+ if (typeString.endsWith("...")) {
+ typeString = typeString.substring(0, typeString.length() - 3);
+ }
+
+ // Generic parameters
+ int paramStartPos = typeString.indexOf('<');
+ if (paramStartPos > -1) {
+ ArrayList<TypeInfo> generics = new ArrayList<TypeInfo>();
+ int paramEndPos = typeString.lastIndexOf('>');
+
+ int entryStartPos = paramStartPos + 1;
+ int bracketNesting = 0;
+ for (int i = entryStartPos; i < paramEndPos; i++) {
+ char c = typeString.charAt(i);
+ if (c == ',' && bracketNesting == 0) {
+ String entry = typeString.substring(entryStartPos, i).trim();
+ TypeInfo info = new TypeInfo(entry);
+ generics.add(info);
+ entryStartPos = i + 1;
+ } else if (c == '<') {
+ bracketNesting++;
+ } else if (c == '>') {
+ bracketNesting--;
+ }
+ }
+
+ TypeInfo info = new TypeInfo(typeString.substring(entryStartPos, paramEndPos).trim());
+ generics.add(info);
+
+ mTypeArguments = new TypeInfo[generics.size()];
+ generics.toArray(mTypeArguments);
+
+ if (paramEndPos < typeString.length() - 1) {
+ typeString = typeString.substring(0,paramStartPos) + typeString.substring(paramEndPos + 1);
+ } else {
+ typeString = typeString.substring(0,paramStartPos);
+ }
+ }
+
+ // Dimensions
+ int pos = typeString.indexOf('[');
+ if (pos > -1) {
+ mDimension = typeString.substring(pos);
+ typeString = typeString.substring(0, pos);
+ } else {
+ mDimension = "";
+ }
+
+ if (PRIMITIVE_TYPES.contains(typeString)) {
+ mIsPrimitive = true;
+ mSimpleTypeName = typeString;
+ mQualifiedTypeName = typeString;
+ } else {
+ mQualifiedTypeName = typeString;
+ pos = typeString.lastIndexOf('.');
+ if (pos > -1) {
+ mSimpleTypeName = typeString.substring(pos + 1);
+ } else {
+ mSimpleTypeName = typeString;
+ }
+ }
+ }
+
+ 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<String>());
+ }
+ }
+
+ 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) {
+ mFullName = fullNameNoDimension(typeVars) + mDimension;
+ return mFullName;
+ }
+
+ public String fullNameNoDimension(HashSet<String> typeVars) {
+ String fullName = null;
+ if (mIsTypeVariable) {
+ if (typeVars.contains(mQualifiedTypeName)) {
+ // don't recurse forever with the parameters. This handles
+ // Enum<K extends Enum<K>>
+ return mQualifiedTypeName;
+ }
+ typeVars.add(mQualifiedTypeName);
+ }
+ /*
+ * if (fullName != null) { return fullName; }
+ */
+ fullName = mQualifiedTypeName;
+ if (mTypeArguments != null && mTypeArguments.length > 0) {
+ fullName += typeArgumentsName(mTypeArguments, typeVars);
+ } else if (mSuperBounds != null && mSuperBounds.length > 0) {
+ fullName += " super " + mSuperBounds[0].fullName(typeVars);
+ for (int i = 1; i < mSuperBounds.length; i++) {
+ fullName += " & " + mSuperBounds[i].fullName(typeVars);
+ }
+ } else if (mExtendsBounds != null && mExtendsBounds.length > 0) {
+ fullName += " extends " + mExtendsBounds[0].fullName(typeVars);
+ for (int i = 1; i < mExtendsBounds.length; i++) {
+ fullName += " & " + mExtendsBounds[i].fullName(typeVars);
+ }
+ }
+ return fullName;
+ }
+
+ public TypeInfo[] typeArguments() {
+ return mTypeArguments;
+ }
+
+ public void makeHDF(Data data, String base) {
+ makeHDFRecursive(data, base, false, false, new HashSet<String>());
+ }
+
+ public void makeQualifiedHDF(Data data, String base) {
+ makeHDFRecursive(data, base, true, false, new HashSet<String>());
+ }
+
+ public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
+ makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
+ }
+
+ public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) {
+ makeHDFRecursive(data, base, true, false, typeVariables);
+ }
+
+ private void makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg,
+ HashSet<String> typeVars) {
+ String label = qualified ? qualifiedTypeName() : simpleTypeName();
+ label += (isLastVararg) ? "..." : dimension();
+ data.setValue(base + ".label", label);
+ 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() && mClass != null) {
+ if (mClass.isIncluded()) {
+ data.setValue(base + ".link", mClass.htmlPage());
+ data.setValue(base + ".since", mClass.getSince());
+ } else {
+ Doclava.federationTagger.tagAll(new ClassInfo[] {mClass});
+ if (!mClass.getFederatedReferences().isEmpty()) {
+ FederatedSite site = mClass.getFederatedReferences().iterator().next();
+ data.setValue(base + ".link", site.linkFor(mClass.htmlPage()));
+ data.setValue(base + ".federated", site.name());
+ }
+ }
+ }
+
+ 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(Data 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(Data 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";
+ }
+ }
+
+ @Override
+ 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;
+}
diff --git a/src/com/google/doclava/apicheck/AbstractMethodInfo.java b/src/com/google/doclava/apicheck/AbstractMethodInfo.java
new file mode 100644
index 0000000..306dba8
--- /dev/null
+++ b/src/com/google/doclava/apicheck/AbstractMethodInfo.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava.apicheck;
+
+public interface AbstractMethodInfo {
+
+ public void addException(String exec);
+
+ public void addParameter(com.google.doclava.ParameterInfo p);
+
+ public void setDeprecated(boolean deprecated);
+
+ public void setVarargs(boolean varargs);
+ public boolean isVarArgs();
+}
diff --git a/src/com/google/doclava/apicheck/ApiCheck.java b/src/com/google/doclava/apicheck/ApiCheck.java
new file mode 100644
index 0000000..0ec19c1
--- /dev/null
+++ b/src/com/google/doclava/apicheck/ApiCheck.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava.apicheck;
+
+import com.google.doclava.AnnotationInstanceInfo;
+import com.google.doclava.ClassInfo;
+import com.google.doclava.ConstructorInfo;
+import com.google.doclava.Converter;
+import com.google.doclava.Errors;
+import com.google.doclava.MethodInfo;
+import com.google.doclava.PackageInfo;
+import com.google.doclava.ParameterInfo;
+import com.google.doclava.SourcePositionInfo;
+import com.google.doclava.TypeInfo;
+
+import com.google.doclava.FieldInfo;
+import com.google.doclava.Errors.ErrorMessage;
+
+import com.sun.javadoc.ClassDoc;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.Stack;
+
+public class ApiCheck {
+ // parse out and consume the -whatever command line flags
+ private static ArrayList<String[]> parseFlags(ArrayList<String> allArgs) {
+ ArrayList<String[]> ret = new ArrayList<String[]>();
+
+ int i;
+ for (i = 0; i < allArgs.size(); i++) {
+ // flags with one value attached
+ String flag = allArgs.get(i);
+ if (flag.equals("-error") || flag.equals("-warning") || flag.equals("-hide")) {
+ String[] arg = new String[2];
+ arg[0] = flag;
+ arg[1] = allArgs.get(++i);
+ ret.add(arg);
+ } else {
+ // we've consumed all of the -whatever args, so we're done
+ break;
+ }
+ }
+
+ // i now points to the first non-flag arg; strip what came before
+ for (; i > 0; i--) {
+ allArgs.remove(0);
+ }
+ return ret;
+ }
+
+ public static void main(String[] originalArgs) {
+ ApiCheck acheck = new ApiCheck();
+ Report report = acheck.checkApi(originalArgs);
+
+ Errors.printErrors(report.errors());
+ System.exit(report.code);
+ }
+
+ /**
+ * Compares two api xml files for consistency.
+ */
+ public Report checkApi(String[] originalArgs) {
+ // translate to an ArrayList<String> for munging
+ ArrayList<String> args = new ArrayList<String>(originalArgs.length);
+ for (String a : originalArgs) {
+ args.add(a);
+ }
+
+ ArrayList<String[]> flags = ApiCheck.parseFlags(args);
+ for (String[] a : flags) {
+ 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) {
+ System.err.println("Bad argument: " + a[0] + " " + a[1]);
+ return new Report(2, Errors.getErrors());
+ }
+ }
+ }
+
+ ApiInfo oldApi;
+ ApiInfo newApi;
+
+ try {
+ oldApi = parseApi(args.get(0));
+ newApi = parseApi(args.get(1));
+ } catch (ApiParseException e) {
+ e.printStackTrace();
+ System.err.println("Error parsing API");
+ return new Report(1, Errors.getErrors());
+ }
+
+ // only run the consistency check if we haven't had XML parse errors
+ if (!Errors.hadError) {
+ oldApi.isConsistent(newApi);
+ }
+
+ return new Report(Errors.hadError ? 1 : 0, Errors.getErrors());
+ }
+
+ public ApiInfo parseApi(String xmlFile) throws ApiParseException {
+ FileInputStream fileStream = null;
+ try {
+ fileStream = new FileInputStream(xmlFile);
+ return parseApi(fileStream);
+ } catch (IOException e) {
+ throw new ApiParseException("Could not open file for parsing: " + xmlFile, e);
+ } finally {
+ if (fileStream != null) {
+ try {
+ fileStream.close();
+ } catch (IOException ignored) {}
+ }
+ }
+ }
+
+ public ApiInfo parseApi(URL xmlURL) throws ApiParseException {
+ InputStream xmlStream = null;
+ try {
+ xmlStream = xmlURL.openStream();
+ return parseApi(xmlStream);
+ } catch (IOException e) {
+ throw new ApiParseException("Could not open stream for parsing: " + xmlURL,e);
+ } finally {
+ if (xmlStream != null) {
+ try {
+ xmlStream.close();
+ } catch (IOException ignored) {}
+ }
+ }
+ }
+
+ public ApiInfo parseApi(InputStream xmlStream) throws ApiParseException {
+ try {
+ XMLReader xmlreader = XMLReaderFactory.createXMLReader();
+ MakeHandler handler = new MakeHandler();
+ xmlreader.setContentHandler(handler);
+ xmlreader.setErrorHandler(handler);
+ xmlreader.parse(new InputSource(xmlStream));
+ ApiInfo apiInfo = handler.getApi();
+ apiInfo.resolveSuperclasses();
+ apiInfo.resolveInterfaces();
+ return apiInfo;
+ } catch (Exception e) {
+ throw new ApiParseException("Error parsing API", e);
+ }
+ }
+
+ private class MakeHandler extends DefaultHandler {
+
+ private ApiInfo mApi;
+ private PackageInfo mCurrentPackage;
+ private ClassInfo mCurrentClass;
+ private AbstractMethodInfo mCurrentMethod;
+ private Stack<ClassInfo> mClassScope = new Stack<ClassInfo>();
+
+
+ public MakeHandler() {
+ super();
+ mApi = new ApiInfo();
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) {
+ if (qName.equals("package")) {
+ mCurrentPackage =
+ new PackageInfo(attributes.getValue("name"), SourcePositionInfo.fromXml(attributes
+ .getValue("source")));
+ } else if (qName.equals("class") || qName.equals("interface")) {
+ // push the old outer scope for later recovery, then set
+ // up the new current class object
+ mClassScope.push(mCurrentClass);
+
+ ClassDoc classDoc = null;
+ String rawCommentText = "";
+ SourcePositionInfo position = SourcePositionInfo.fromXml(attributes.getValue("source"));
+ String visibility = attributes.getValue("visibility");
+ boolean isPublic = "public".equals(visibility);
+ boolean isProtected = "protected".equals(visibility);
+ boolean isPrivate = "private".equals(visibility);
+ boolean isPackagePrivate = !isPublic && !isPrivate && !isProtected;
+ boolean isStatic = Boolean.valueOf(attributes.getValue("static"));
+ boolean isInterface = qName.equals("interface");
+ boolean isAbstract = Boolean.valueOf(attributes.getValue("abstract"));
+ boolean isOrdinaryClass = qName.equals("class");
+ boolean isException = false; // TODO: check hierarchy for java.lang.Exception
+ boolean isError = false; // TODO: not sure.
+ boolean isEnum = false; // TODO: not sure.
+ boolean isAnnotation = false; // TODO: not sure.
+ boolean isFinal = Boolean.valueOf(attributes.getValue("final"));
+ boolean isIncluded = false;
+ String name = attributes.getValue("name");
+ String qualifiedName = qualifiedName(mCurrentPackage.name(), name, mCurrentClass);
+ String qualifiedTypeName = null; // TODO: not sure
+ boolean isPrimitive = false;
+
+ mCurrentClass =
+ new ClassInfo(classDoc, rawCommentText, position, isPublic, isProtected,
+ isPackagePrivate, isPrivate, isStatic, isInterface, isAbstract, isOrdinaryClass,
+ isException, isError, isEnum, isAnnotation, isFinal, isIncluded, name, qualifiedName,
+ qualifiedTypeName, isPrimitive);
+
+ mCurrentClass.setContainingPackage(mCurrentPackage);
+ String superclass = attributes.getValue("extends");
+ if (superclass == null && !isInterface && !"java.lang.Object".equals(qualifiedName)) {
+ throw new AssertionError("no superclass known for class " + name);
+ }
+
+ // Resolve superclass after .xml completely parsed.
+ mApi.mapClassToSuper(mCurrentClass, superclass);
+
+ TypeInfo typeInfo = Converter.obtainTypeFromString(qualifiedName) ;
+ mCurrentClass.setTypeInfo(typeInfo);
+ mCurrentClass.setAnnotations(new AnnotationInstanceInfo[] {});
+ } else if (qName.equals("method")) {
+ String rawCommentText = "";
+ TypeInfo[] typeParameters = new TypeInfo[0];
+ String name = attributes.getValue("name");
+ String signature = null; // TODO
+ ClassInfo containingClass = mCurrentClass;
+ ClassInfo realContainingClass = mCurrentClass;
+ String visibility = attributes.getValue("visibility");
+ boolean isPublic = "public".equals(visibility);
+ boolean isProtected = "protected".equals(visibility);
+ boolean isPrivate = "private".equals(visibility);
+ boolean isPackagePrivate = !isPublic && !isPrivate && !isProtected;
+ boolean isFinal = Boolean.valueOf(attributes.getValue("final"));
+ boolean isStatic = Boolean.valueOf(attributes.getValue("static"));
+ boolean isSynthetic = false; // TODO
+ boolean isAbstract = Boolean.valueOf(attributes.getValue("abstract"));
+ boolean isSynchronized = Boolean.valueOf(attributes.getValue("synchronized"));
+ boolean isNative = Boolean.valueOf(attributes.getValue("native"));
+ boolean isAnnotationElement = false; // TODO
+ String kind = qName;
+ String flatSignature = null; // TODO
+ MethodInfo overriddenMethod = null; // TODO
+ TypeInfo returnType = Converter.obtainTypeFromString(attributes.getValue("return"));
+ ParameterInfo[] parameters = new ParameterInfo[0];
+ ClassInfo[] thrownExceptions = new ClassInfo[0];
+ SourcePositionInfo position = SourcePositionInfo.fromXml(attributes.getValue("source"));
+ AnnotationInstanceInfo[] annotations = new AnnotationInstanceInfo[] {}; // TODO
+
+ mCurrentMethod =
+ new MethodInfo(rawCommentText, typeParameters, name, signature, containingClass,
+ realContainingClass, isPublic, isProtected, isPackagePrivate, isPrivate, isFinal,
+ isStatic, isSynthetic, isAbstract, isSynchronized, isNative, isAnnotationElement, kind,
+ flatSignature, overriddenMethod, returnType, parameters, thrownExceptions, position,
+ annotations);
+
+ mCurrentMethod.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
+ } else if (qName.equals("constructor")) {
+ mCurrentMethod =
+ new ConstructorInfo(attributes.getValue("name"), attributes.getValue("type"), Boolean
+ .valueOf(attributes.getValue("static")), Boolean.valueOf(attributes
+ .getValue("final")), attributes.getValue("deprecated"), attributes
+ .getValue("visibility"), SourcePositionInfo.fromXml(attributes.getValue("source")),
+ mCurrentClass);
+ } else if (qName.equals("field")) {
+ String visibility = attributes.getValue("visibility");
+ boolean isPublic = visibility.equals("public");
+ boolean isProtected = visibility.equals("protected");
+ boolean isPrivate = visibility.equals("private");
+ boolean isPackagePrivate = visibility.equals("");
+ String typeName = attributes.getValue("type");
+ TypeInfo type = Converter.obtainTypeFromString(typeName);
+
+ FieldInfo fInfo =
+ new FieldInfo(attributes.getValue("name"), mCurrentClass, mCurrentClass, isPublic,
+ isProtected, isPackagePrivate, isPrivate, Boolean.valueOf(attributes.getValue("final")),
+ Boolean.valueOf(attributes.getValue("static")), Boolean.valueOf(attributes.
+ getValue("transient")), Boolean.valueOf(attributes.getValue("volatile")), false,
+ type, "", attributes.getValue("value"), SourcePositionInfo
+ .fromXml(attributes.getValue("source")), new AnnotationInstanceInfo[] {});
+
+ fInfo.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
+ mCurrentClass.addField(fInfo);
+ } else if (qName.equals("parameter")) {
+ String name = attributes.getValue("name");
+ String typeName = attributes.getValue("type");
+ TypeInfo type = Converter.obtainTypeFromString(typeName);
+ boolean isVarArg = typeName.endsWith("...");
+ SourcePositionInfo position = null;
+
+ mCurrentMethod.addParameter(new ParameterInfo(name, typeName, type, isVarArg, position));
+ mCurrentMethod.setVarargs(isVarArg);
+ } else if (qName.equals("exception")) {
+ mCurrentMethod.addException(attributes.getValue("type"));
+ } else if (qName.equals("implements")) {
+ // Resolve interfaces after .xml completely parsed.
+ mApi.mapClassToInterface(mCurrentClass, attributes.getValue("name"));
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ if (qName.equals("method")) {
+ mCurrentClass.addMethod((MethodInfo) mCurrentMethod);
+ } else if (qName.equals("constructor")) {
+ mCurrentClass.addConstructor((ConstructorInfo) mCurrentMethod);
+ } else if (qName.equals("class") || qName.equals("interface")) {
+ mCurrentPackage.addClass(mCurrentClass);
+ mCurrentClass = mClassScope.pop();
+ } else if (qName.equals("package")) {
+ mApi.addPackage(mCurrentPackage);
+ }
+ }
+
+ public ApiInfo getApi() {
+ return mApi;
+ }
+
+ private String qualifiedName(String pkg, String className, ClassInfo parent) {
+ String parentQName = (parent != null) ? (parent.qualifiedName() + ".") : "";
+ return pkg + "." + parentQName + className;
+ }
+ }
+
+ public class Report {
+ private int code;
+ private Set<ErrorMessage> errors;
+
+ private Report(int code, Set<ErrorMessage> errors) {
+ this.code = code;
+ this.errors = errors;
+ }
+
+ public int code() {
+ return code;
+ }
+
+ public Set<ErrorMessage> errors() {
+ return errors;
+ }
+ }
+}
diff --git a/src/com/google/doclava/apicheck/ApiInfo.java b/src/com/google/doclava/apicheck/ApiInfo.java
new file mode 100644
index 0000000..758942a
--- /dev/null
+++ b/src/com/google/doclava/apicheck/ApiInfo.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava.apicheck;
+
+import com.google.doclava.ClassInfo;
+import com.google.doclava.Errors;
+import com.google.doclava.PackageInfo;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ApiInfo {
+
+ private HashMap<String, PackageInfo> mPackages
+ = new HashMap<String, PackageInfo>();
+ private HashMap<String, ClassInfo> mAllClasses
+ = new HashMap<String, ClassInfo>();
+ private Map<ClassInfo,String> mClassToSuper
+ = new HashMap<ClassInfo, String>();
+ private Map<ClassInfo, ArrayList<String>> mClassToInterface
+ = new HashMap<ClassInfo, ArrayList<String>>();
+
+
+ public ClassInfo findClass(String name) {
+ return mAllClasses.get(name);
+ }
+
+ protected void resolveInterfaces() {
+ for (ClassInfo cl : mAllClasses.values()) {
+ ArrayList<String> ifaces = mClassToInterface.get(cl);
+ if (ifaces == null) {
+ continue;
+ }
+ for (String iface : ifaces) {
+ cl.addInterface(mAllClasses.get(iface));
+ }
+ }
+ }
+
+ /**
+ * Checks to see if this api is consistent with a newer version.
+ */
+ public boolean isConsistent(ApiInfo otherApi) {
+ boolean consistent = true;
+ for (PackageInfo pInfo : mPackages.values()) {
+ if (otherApi.getPackages().containsKey(pInfo.name())) {
+ if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_PACKAGE, pInfo.position(), "Removed package " + pInfo.name());
+ consistent = false;
+ }
+ }
+ for (PackageInfo pInfo : otherApi.mPackages.values()) {
+ if (!mPackages.containsKey(pInfo.name())) {
+ Errors.error(Errors.ADDED_PACKAGE, pInfo.position(), "Added package " + pInfo.name());
+ consistent = false;
+ }
+ }
+ return consistent;
+ }
+
+ public HashMap<String, PackageInfo> getPackages() {
+ return mPackages;
+ }
+
+ protected void mapClassToSuper(ClassInfo classInfo, String superclass) {
+ mClassToSuper.put(classInfo, superclass);
+ }
+
+ protected void mapClassToInterface(ClassInfo classInfo, String iface) {
+ if (!mClassToInterface.containsKey(classInfo)) {
+ mClassToInterface.put(classInfo, new ArrayList<String>());
+ }
+ mClassToInterface.get(classInfo).add(iface);
+ }
+
+ protected void addPackage(PackageInfo pInfo) {
+ // track the set of organized packages in the API
+ mPackages.put(pInfo.name(), pInfo);
+
+ // accumulate a direct map of all the classes in the API
+ for (ClassInfo cl : pInfo.allClasses().values()) {
+ mAllClasses.put(cl.qualifiedName(), cl);
+ }
+ }
+
+ protected void resolveSuperclasses() {
+ for (ClassInfo cl : mAllClasses.values()) {
+ // java.lang.Object has no superclass
+ if (!cl.qualifiedName().equals("java.lang.Object")) {
+ String scName = mClassToSuper.get(cl);
+ if (scName == null) {
+ scName = "java.lang.Object";
+ }
+ ClassInfo superclass = mAllClasses.get(scName);
+ if (superclass == null) {
+ // Superclass not provided by this codebase. Inject a stub.
+ superclass = new ClassInfo(scName);
+ }
+ cl.setSuperClass(superclass);
+ }
+ }
+ }
+}
diff --git a/src/com/google/doclava/apicheck/ApiParseException.java b/src/com/google/doclava/apicheck/ApiParseException.java
new file mode 100644
index 0000000..cd8ad7f
--- /dev/null
+++ b/src/com/google/doclava/apicheck/ApiParseException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava.apicheck;
+
+public final class ApiParseException extends Exception {
+ public ApiParseException(String message, Exception cause) {
+ super(message, cause);
+ }
+
+ public ApiParseException() {
+
+ }
+
+ ApiParseException(String message) {
+ super(message);
+ }
+}