989 lines
40 KiB
Java
989 lines
40 KiB
Java
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Set;
|
|
import java.util.Comparator;
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.PrintStream;
|
|
|
|
public class Stubs {
|
|
private static HashSet<ClassInfo> notStrippable;
|
|
public static void writeStubs(String stubsDir, Boolean writeXML, String xmlFile,
|
|
HashSet<String> stubPackages) {
|
|
// figure out which classes we need
|
|
notStrippable = new HashSet();
|
|
ClassInfo[] all = Converter.allClasses();
|
|
File xml = new File(xmlFile);
|
|
xml.getParentFile().mkdirs();
|
|
PrintStream xmlWriter = null;
|
|
if (writeXML) {
|
|
try {
|
|
xmlWriter = new PrintStream(xml);
|
|
} catch (FileNotFoundException e) {
|
|
Errors.error(Errors.IO_ERROR, new SourcePositionInfo(xmlFile, 0, 0),
|
|
"Cannot open file for write.");
|
|
}
|
|
}
|
|
// If a class is public or protected, not hidden, and marked as included,
|
|
// then we can't strip it
|
|
for (ClassInfo cl: all) {
|
|
if (cl.checkLevel() && cl.isIncluded()) {
|
|
cantStripThis(cl, notStrippable, "0:0");
|
|
}
|
|
}
|
|
|
|
// complain about anything that looks includeable but is not supposed to
|
|
// be written, e.g. hidden things
|
|
for (ClassInfo cl: notStrippable) {
|
|
if (!cl.isHidden()) {
|
|
MethodInfo[] methods = cl.selfMethods();
|
|
for (MethodInfo m: methods) {
|
|
if (m.isHidden()) {
|
|
Errors.error(Errors.UNAVAILABLE_SYMBOL,
|
|
m.position(), "Reference to hidden method "
|
|
+ m.name());
|
|
} else if (m.isDeprecated()) {
|
|
// don't bother reporting deprecated methods
|
|
// unless they are public
|
|
Errors.error(Errors.DEPRECATED,
|
|
m.position(), "Method "
|
|
+ cl.qualifiedName() + "." + m.name()
|
|
+ " is deprecated");
|
|
}
|
|
|
|
ClassInfo returnClass = m.returnType().asClassInfo();
|
|
if (returnClass != null && returnClass.isHidden()) {
|
|
Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(),
|
|
"Method " + cl.qualifiedName() + "." + m.name()
|
|
+ " returns unavailable type " + returnClass.name());
|
|
}
|
|
|
|
ParameterInfo[] params = m.parameters();
|
|
for (ParameterInfo p: params) {
|
|
TypeInfo t = p.type();
|
|
if (!t.isPrimitive()) {
|
|
if (t.asClassInfo().isHidden()) {
|
|
Errors.error(Errors.UNAVAILABLE_SYMBOL,
|
|
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");
|
|
}
|
|
}
|
|
|
|
// write out the stubs
|
|
HashMap<PackageInfo, List<ClassInfo>> packages = new HashMap<PackageInfo, List<ClassInfo>>();
|
|
for (ClassInfo cl: notStrippable) {
|
|
if (!cl.isDocOnly()) {
|
|
if (stubPackages == null || stubPackages.contains(cl.containingPackage().name())) {
|
|
writeClassFile(stubsDir, cl);
|
|
if (packages.containsKey(cl.containingPackage())) {
|
|
packages.get(cl.containingPackage()).add(cl);
|
|
} else {
|
|
ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>();
|
|
classes.add(cl);
|
|
packages.put(cl.containingPackage(), classes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// write out the XML
|
|
if (writeXML && xmlWriter != null) {
|
|
writeXML(xmlWriter, packages, notStrippable);
|
|
}
|
|
|
|
if (xmlWriter != null) {
|
|
xmlWriter.close();
|
|
}
|
|
|
|
}
|
|
|
|
public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) {
|
|
|
|
if (!notStrippable.add(cl)) {
|
|
// slight optimization: if it already contains cl, it already contains
|
|
// all of cl's parents
|
|
return;
|
|
}
|
|
cl.setReasonIncluded(why);
|
|
|
|
// cant strip annotations
|
|
/*if (cl.annotations() != null){
|
|
for (AnnotationInstanceInfo ai : cl.annotations()){
|
|
if (ai.type() != null){
|
|
cantStripThis(ai.type(), notStrippable, "1:" + cl.qualifiedName());
|
|
}
|
|
}
|
|
}*/
|
|
// cant strip any public fields or their generics
|
|
if (cl.allSelfFields() != null){
|
|
for (FieldInfo fInfo : cl.allSelfFields()){
|
|
if (fInfo.type() != null){
|
|
if (fInfo.type().asClassInfo() != null){
|
|
cantStripThis(fInfo.type().asClassInfo(), notStrippable,
|
|
"2:" + cl.qualifiedName());
|
|
}
|
|
if (fInfo.type().typeArguments() != null){
|
|
for (TypeInfo tTypeInfo : fInfo.type().typeArguments()){
|
|
if (tTypeInfo.asClassInfo() != null){
|
|
cantStripThis(tTypeInfo.asClassInfo(), notStrippable,
|
|
"3:" + cl.qualifiedName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//cant strip any of the type's generics
|
|
if (cl.asTypeInfo() != null){
|
|
if (cl.asTypeInfo().typeArguments() != null){
|
|
for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()){
|
|
if (tInfo.asClassInfo() != null){
|
|
cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//cant strip any of the annotation elements
|
|
//cantStripThis(cl.annotationElements(), notStrippable);
|
|
// take care of methods
|
|
cantStripThis(cl.allSelfMethods(), notStrippable);
|
|
cantStripThis(cl.allConstructors(), notStrippable);
|
|
// blow the outer class open if this is an inner class
|
|
if(cl.containingClass() != null){
|
|
cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName());
|
|
}
|
|
// blow open super class and interfaces
|
|
ClassInfo supr = cl.realSuperclass();
|
|
if (supr != null) {
|
|
if (supr.isHidden()) {
|
|
// cl is a public class declared as extending a hidden superclass.
|
|
// this is not a desired practice but it's happened, so we deal
|
|
// with it by stripping off the superclass relation for purposes of
|
|
// generating the doc & stub information, and proceeding normally.
|
|
cl.init(cl.asTypeInfo(), cl.realInterfaces(), cl.realInterfaceTypes(),
|
|
cl.innerClasses(), cl.allConstructors(), cl.allSelfMethods(),
|
|
cl.annotationElements(), cl.allSelfFields(), cl.enumConstants(),
|
|
cl.containingPackage(), cl.containingClass(),
|
|
null, null, cl.annotations());
|
|
Errors.error(Errors.HIDDEN_SUPERCLASS,
|
|
cl.position(), "Public class " + cl.qualifiedName()
|
|
+ " stripped of unavailable superclass "
|
|
+ supr.qualifiedName());
|
|
} else {
|
|
cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name()
|
|
+ cl.qualifiedName());
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void cantStripThis(MethodInfo[] mInfos , HashSet<ClassInfo> notStrippable) {
|
|
//for each method, blow open the parameters, throws and return types. also blow open their generics
|
|
if (mInfos != null){
|
|
for (MethodInfo mInfo : mInfos){
|
|
if (mInfo.getTypeParameters() != null){
|
|
for (TypeInfo tInfo : mInfo.getTypeParameters()){
|
|
if (tInfo.asClassInfo() != null){
|
|
cantStripThis(tInfo.asClassInfo(), notStrippable, "8:" +
|
|
mInfo.realContainingClass().qualifiedName() + ":" +
|
|
mInfo.name());
|
|
}
|
|
}
|
|
}
|
|
if (mInfo.parameters() != null){
|
|
for (ParameterInfo pInfo : mInfo.parameters()){
|
|
if (pInfo.type() != null && pInfo.type().asClassInfo() != null){
|
|
cantStripThis(pInfo.type().asClassInfo(), notStrippable,
|
|
"9:"+ mInfo.realContainingClass().qualifiedName()
|
|
+ ":" + mInfo.name());
|
|
if (pInfo.type().typeArguments() != null){
|
|
for (TypeInfo tInfoType : pInfo.type().typeArguments()){
|
|
if (tInfoType.asClassInfo() != null){
|
|
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;
|
|
}
|
|
|
|
String filename = stubsDir + '/' + javaFileName(cl);
|
|
File file = new File(filename);
|
|
ClearPage.ensureDirectory(file);
|
|
|
|
PrintStream stream = null;
|
|
try {
|
|
stream = new PrintStream(file);
|
|
writeClassFile(stream, cl);
|
|
}
|
|
catch (FileNotFoundException e) {
|
|
System.err.println("error writing file: " + filename);
|
|
}
|
|
finally {
|
|
if (stream != null) {
|
|
stream.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void writeClassFile(PrintStream stream, ClassInfo cl) {
|
|
PackageInfo pkg = cl.containingPackage();
|
|
if (pkg != null) {
|
|
stream.println("package " + pkg.name() + ";");
|
|
}
|
|
writeClass(stream, cl);
|
|
}
|
|
|
|
static void writeClass(PrintStream stream, ClassInfo cl) {
|
|
writeAnnotations(stream, cl.annotations());
|
|
|
|
stream.print(DroidDoc.scope(cl) + " ");
|
|
if (cl.isAbstract() && !cl.isAnnotation() && !cl.isInterface()) {
|
|
stream.print("abstract ");
|
|
}
|
|
if (cl.isStatic()){
|
|
stream.print("static ");
|
|
}
|
|
if (cl.isFinal() && !cl.isEnum()) {
|
|
stream.print("final ");
|
|
}
|
|
if (false) {
|
|
stream.print("strictfp ");
|
|
}
|
|
|
|
HashSet<String> classDeclTypeVars = new HashSet();
|
|
String leafName = cl.asTypeInfo().fullName(classDeclTypeVars);
|
|
int bracket = leafName.indexOf('<');
|
|
if (bracket < 0) bracket = leafName.length() - 1;
|
|
int period = leafName.lastIndexOf('.', bracket);
|
|
if (period < 0) period = -1;
|
|
leafName = leafName.substring(period+1);
|
|
|
|
String kind = cl.kind();
|
|
stream.println(kind + " " + leafName);
|
|
|
|
TypeInfo base = cl.superclassType();
|
|
|
|
if (!"enum".equals(kind)) {
|
|
if (base != null && !"java.lang.Object".equals(base.qualifiedTypeName())) {
|
|
stream.println(" extends " + base.fullName(classDeclTypeVars));
|
|
}
|
|
}
|
|
|
|
TypeInfo[] interfaces = cl.realInterfaceTypes();
|
|
List<TypeInfo> usedInterfaces = new ArrayList<TypeInfo>();
|
|
for (TypeInfo iface : interfaces) {
|
|
if (notStrippable.contains(iface.asClassInfo())
|
|
&& !iface.asClassInfo().isDocOnly()) {
|
|
usedInterfaces.add(iface);
|
|
}
|
|
}
|
|
if (usedInterfaces.size() > 0 && !cl.isAnnotation()) {
|
|
// can java annotations extend other ones?
|
|
if (cl.isInterface() || cl.isAnnotation()) {
|
|
stream.print(" extends ");
|
|
} else {
|
|
stream.print(" implements ");
|
|
}
|
|
String comma = "";
|
|
for (TypeInfo iface: usedInterfaces) {
|
|
stream.print(comma + iface.fullName(classDeclTypeVars));
|
|
comma = ", ";
|
|
}
|
|
stream.println();
|
|
}
|
|
|
|
stream.println("{");
|
|
|
|
FieldInfo[] enumConstants = cl.enumConstants();
|
|
int N = enumConstants.length;
|
|
for (int i=0; i<N; i++) {
|
|
FieldInfo field = enumConstants[i];
|
|
if (!field.constantLiteralValue().equals("null")){
|
|
stream.println(field.name() + "(" + field.constantLiteralValue()
|
|
+ (i==N-1 ? ");" : "),"));
|
|
}else{
|
|
stream.println(field.name() + "(" + (i==N-1 ? ");" : "),"));
|
|
}
|
|
}
|
|
|
|
for (ClassInfo inner: cl.getRealInnerClasses()) {
|
|
if (notStrippable.contains(inner)
|
|
&& !inner.isDocOnly()){
|
|
writeClass(stream, inner);
|
|
}
|
|
}
|
|
|
|
|
|
for (MethodInfo method: cl.constructors()) {
|
|
if (!method.isDocOnly()) {
|
|
writeMethod(stream, method, true);
|
|
}
|
|
}
|
|
|
|
boolean fieldNeedsInitialization = false;
|
|
boolean staticFieldNeedsInitialization = false;
|
|
for (FieldInfo field: cl.allSelfFields()) {
|
|
if (!field.isDocOnly()) {
|
|
if (!field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
|
|
fieldNeedsInitialization = true;
|
|
}
|
|
if (field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
|
|
staticFieldNeedsInitialization = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The compiler includes a default public constructor that calls the super classes
|
|
// default constructor in the case where there are no written constructors.
|
|
// So, if we hide all the constructors, java may put in a constructor
|
|
// that calls a nonexistent super class constructor. So, if there are no constructors,
|
|
// and the super class doesn't have a default constructor, write in a private constructor
|
|
// that works. TODO -- we generate this as protected, but we really should generate
|
|
// it as private unless it also exists in the real code.
|
|
if ((cl.constructors().length == 0 && (cl.getNonWrittenConstructors().length != 0
|
|
|| fieldNeedsInitialization))
|
|
&& !cl.isAnnotation()
|
|
&& !cl.isInterface()
|
|
&& !cl.isEnum() ) {
|
|
//Errors.error(Errors.HIDDEN_CONSTRUCTOR,
|
|
// cl.position(), "No constructors " +
|
|
// "found and superclass has no parameterless constructor. A constructor " +
|
|
// "that calls an appropriate superclass constructor " +
|
|
// "was automatically written to stubs.\n");
|
|
stream.println(cl.leafName()
|
|
+ "() { " + superCtorCall(cl,null)
|
|
+ "throw new" + " RuntimeException(\"Stub!\"); }");
|
|
}
|
|
|
|
for (MethodInfo method: cl.allSelfMethods()) {
|
|
if (cl.isEnum()) {
|
|
if (("values".equals(method.name())
|
|
&& "()".equals(method.signature()))
|
|
|| ("valueOf".equals(method.name())
|
|
&& "(java.lang.String)".equals(method.signature()))) {
|
|
// skip these two methods on enums, because they're synthetic,
|
|
// although for some reason javadoc doesn't mark them as synthetic,
|
|
// maybe because they still want them documented
|
|
continue;
|
|
}
|
|
}
|
|
if (!method.isDocOnly()) {
|
|
writeMethod(stream, method, false);
|
|
}
|
|
}
|
|
//Write all methods that are hidden, but override abstract methods or interface methods.
|
|
//These can't be hidden.
|
|
for (MethodInfo method : cl.getHiddenMethods()){
|
|
MethodInfo overriddenMethod = method.findRealOverriddenMethod(method.name(), method.signature(), notStrippable);
|
|
ClassInfo classContainingMethod = method.findRealOverriddenClass(method.name(),
|
|
method.signature());
|
|
if (overriddenMethod != null && !overriddenMethod.isHidden()
|
|
&& !overriddenMethod.isDocOnly() &&
|
|
(overriddenMethod.isAbstract() ||
|
|
overriddenMethod.containingClass().isInterface())) {
|
|
method.setReason("1:" + classContainingMethod.qualifiedName());
|
|
cl.addMethod(method);
|
|
writeMethod(stream, method, false);
|
|
}
|
|
}
|
|
|
|
for (MethodInfo element: cl.annotationElements()) {
|
|
if (!element.isDocOnly()) {
|
|
writeAnnotationElement(stream, element);
|
|
}
|
|
}
|
|
|
|
for (FieldInfo field: cl.allSelfFields()) {
|
|
if (!field.isDocOnly()) {
|
|
writeField(stream, field);
|
|
}
|
|
}
|
|
|
|
if (staticFieldNeedsInitialization) {
|
|
stream.print("static { ");
|
|
for (FieldInfo field: cl.allSelfFields()) {
|
|
if (!field.isDocOnly() && field.isStatic() && field.isFinal()
|
|
&& !fieldIsInitialized(field) && field.constantValue() == null) {
|
|
stream.print(field.name() + " = " + field.type().defaultValue()
|
|
+ "; ");
|
|
}
|
|
}
|
|
stream.println("}");
|
|
}
|
|
|
|
stream.println("}");
|
|
}
|
|
|
|
|
|
static void writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor) {
|
|
String comma;
|
|
|
|
stream.print(DroidDoc.scope(method) + " ");
|
|
if (method.isStatic()) {
|
|
stream.print("static ");
|
|
}
|
|
if (method.isFinal()) {
|
|
stream.print("final ");
|
|
}
|
|
if (method.isAbstract()) {
|
|
stream.print("abstract ");
|
|
}
|
|
if (method.isSynchronized()) {
|
|
stream.print("synchronized ");
|
|
}
|
|
if (method.isNative()) {
|
|
stream.print("native ");
|
|
}
|
|
if (false /*method.isStictFP()*/) {
|
|
stream.print("strictfp ");
|
|
}
|
|
|
|
stream.print(method.typeArgumentsName(new HashSet()) + " ");
|
|
|
|
if (!isConstructor) {
|
|
stream.print(method.returnType().fullName(method.typeVariables()) + " ");
|
|
}
|
|
String n = method.name();
|
|
int pos = n.lastIndexOf('.');
|
|
if (pos >= 0) {
|
|
n = n.substring(pos + 1);
|
|
}
|
|
stream.print(n + "(");
|
|
comma = "";
|
|
int count = 1;
|
|
int size = method.parameters().length;
|
|
for (ParameterInfo param: method.parameters()) {
|
|
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(DroidDoc.scope(field) + " ");
|
|
if (field.isStatic()) {
|
|
stream.print("static ");
|
|
}
|
|
if (field.isFinal()) {
|
|
stream.print("final ");
|
|
}
|
|
if (field.isTransient()) {
|
|
stream.print("transient ");
|
|
}
|
|
if (field.isVolatile()) {
|
|
stream.print("volatile ");
|
|
}
|
|
|
|
stream.print(field.type().fullName());
|
|
stream.print(" ");
|
|
stream.print(field.name());
|
|
|
|
if (fieldIsInitialized(field)) {
|
|
stream.print(" = " + field.constantLiteralValue());
|
|
}
|
|
|
|
stream.println(";");
|
|
}
|
|
|
|
static boolean fieldIsInitialized(FieldInfo field) {
|
|
return (field.isFinal() && field.constantValue() != null)
|
|
|| !field.type().dimension().equals("")
|
|
|| field.containingClass().isInterface();
|
|
}
|
|
|
|
// Returns 'true' if the method is an @Override of a visible parent
|
|
// method implementation, and thus does not affect the API.
|
|
static boolean methodIsOverride(MethodInfo mi) {
|
|
// Abstract/static/final methods are always listed in the API description
|
|
if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) {
|
|
return false;
|
|
}
|
|
|
|
// Find any relevant ancestor declaration and inspect it
|
|
MethodInfo om = mi.findSuperclassImplementation(notStrippable);
|
|
if (om != null) {
|
|
// Visibility mismatch is an API change, so check for it
|
|
if (mi.mIsPrivate == om.mIsPrivate
|
|
&& mi.mIsPublic == om.mIsPublic
|
|
&& mi.mIsProtected == om.mIsProtected) {
|
|
// Look only for overrides of an ancestor class implementation,
|
|
// not of e.g. an abstract or interface method declaration
|
|
if (!om.isAbstract()) {
|
|
// If the parent is hidden, we can't rely on it to provide
|
|
// the API
|
|
if (!om.isHidden()) {
|
|
// If the only "override" turns out to be in our own class
|
|
// (which sometimes happens in concrete subclasses of
|
|
// abstract base classes), it's not really an override
|
|
if (!mi.mContainingClass.equals(om.mContainingClass)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static boolean canCallMethod(ClassInfo from, MethodInfo m) {
|
|
if (m.isPublic() || m.isProtected()) {
|
|
return true;
|
|
}
|
|
if (m.isPackagePrivate()) {
|
|
String fromPkg = from.containingPackage().name();
|
|
String pkg = m.containingClass().containingPackage().name();
|
|
if (fromPkg.equals(pkg)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// call a constructor, any constructor on this class's superclass.
|
|
static String superCtorCall(ClassInfo cl, ClassInfo[] thrownExceptions) {
|
|
ClassInfo base = cl.realSuperclass();
|
|
if (base == null) {
|
|
return "";
|
|
}
|
|
HashSet<String> exceptionNames = new HashSet<String>();
|
|
if (thrownExceptions != null ){
|
|
for (ClassInfo thrown : thrownExceptions){
|
|
exceptionNames.add(thrown.name());
|
|
}
|
|
}
|
|
MethodInfo[] ctors = base.constructors();
|
|
MethodInfo ctor = null;
|
|
//bad exception indicates that the exceptions thrown by the super constructor
|
|
//are incompatible with the constructor we're using for the sub class.
|
|
Boolean badException = false;
|
|
for (MethodInfo m: ctors) {
|
|
if (canCallMethod(cl, m)) {
|
|
if (m.thrownExceptions() != null){
|
|
for (ClassInfo thrown : m.thrownExceptions()){
|
|
if (!exceptionNames.contains(thrown.name())){
|
|
badException = true;
|
|
}
|
|
}
|
|
}
|
|
if (badException){
|
|
badException = false;
|
|
continue;
|
|
}
|
|
// if it has no args, we're done
|
|
if (m.parameters().length == 0) {
|
|
return "";
|
|
}
|
|
ctor = m;
|
|
}
|
|
}
|
|
if (ctor != null) {
|
|
String result = "";
|
|
result+= "super(";
|
|
ParameterInfo[] params = ctor.parameters();
|
|
int N = params.length;
|
|
for (int i=0; i<N; i++) {
|
|
TypeInfo t = params[i].type();
|
|
if (t.isPrimitive() && t.dimension().equals("")) {
|
|
String n = t.simpleTypeName();
|
|
if (("byte".equals(n)
|
|
|| "short".equals(n)
|
|
|| "int".equals(n)
|
|
|| "long".equals(n)
|
|
|| "float".equals(n)
|
|
|| "double".equals(n)) && t.dimension().equals("")) {
|
|
result += "0";
|
|
}
|
|
else if ("char".equals(n)) {
|
|
result += "'\\0'";
|
|
}
|
|
else if ("boolean".equals(n)) {
|
|
result += "false";
|
|
}
|
|
else {
|
|
result += "<<unknown-" + n + ">>";
|
|
}
|
|
} else {
|
|
//put null in each super class method. Cast null to the correct type
|
|
//to avoid collisions with other constructors. If the type is generic
|
|
//don't cast it
|
|
result += (!t.isTypeVariable() ? "(" + t.qualifiedTypeName() + t.dimension() +
|
|
")" : "") + "null";
|
|
}
|
|
if (i != N-1) {
|
|
result += ",";
|
|
}
|
|
}
|
|
result += "); ";
|
|
return result;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
static void writeAnnotations(PrintStream stream, AnnotationInstanceInfo[] annotations) {
|
|
for (AnnotationInstanceInfo ann: annotations) {
|
|
if (!ann.type().isHidden()) {
|
|
stream.println(ann.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
static void writeAnnotationElement(PrintStream stream, MethodInfo ann) {
|
|
stream.print(ann.returnType().fullName());
|
|
stream.print(" ");
|
|
stream.print(ann.name());
|
|
stream.print("()");
|
|
AnnotationValueInfo def = ann.defaultAnnotationElementValue();
|
|
if (def != null) {
|
|
stream.print(" default ");
|
|
stream.print(def.valueString());
|
|
}
|
|
stream.println(";");
|
|
}
|
|
|
|
static void writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses,
|
|
HashSet notStrippable) {
|
|
// extract the set of packages, sort them by name, and write them out in that order
|
|
Set<PackageInfo> allClassKeys = allClasses.keySet();
|
|
PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
|
|
Arrays.sort(allPackages, PackageInfo.comparator);
|
|
|
|
xmlWriter.println("<api>");
|
|
for (PackageInfo pack : allPackages) {
|
|
writePackageXML(xmlWriter, pack, allClasses.get(pack), notStrippable);
|
|
}
|
|
xmlWriter.println("</api>");
|
|
}
|
|
|
|
static void writePackageXML(PrintStream xmlWriter, PackageInfo pack, List<ClassInfo> classList,
|
|
HashSet notStrippable) {
|
|
ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
|
|
Arrays.sort(classes, ClassInfo.comparator);
|
|
xmlWriter.println("<package name=\"" + pack.name() + "\"\n"
|
|
//+ " source=\"" + pack.position() + "\"\n"
|
|
+ ">");
|
|
for (ClassInfo cl : classes) {
|
|
writeClassXML(xmlWriter, cl, notStrippable);
|
|
}
|
|
xmlWriter.println("</package>");
|
|
|
|
|
|
}
|
|
|
|
static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet notStrippable) {
|
|
String scope = DroidDoc.scope(cl);
|
|
String deprecatedString = "";
|
|
String declString = (cl.isInterface()) ? "interface" : "class";
|
|
if (cl.isDeprecated()) {
|
|
deprecatedString = "deprecated";
|
|
} else {
|
|
deprecatedString = "not deprecated";
|
|
}
|
|
xmlWriter.println("<" + declString + " name=\"" + cl.name() + "\"");
|
|
if (!cl.isInterface() && !cl.qualifiedName().equals("java.lang.Object")) {
|
|
xmlWriter.println(" extends=\"" + ((cl.realSuperclass() == null)
|
|
? "java.lang.Object"
|
|
: cl.realSuperclass().qualifiedName()) + "\"");
|
|
}
|
|
xmlWriter.println(" abstract=\"" + cl.isAbstract() + "\"\n"
|
|
+ " static=\"" + cl.isStatic() + "\"\n"
|
|
+ " final=\"" + cl.isFinal() + "\"\n"
|
|
+ " deprecated=\"" + deprecatedString + "\"\n"
|
|
+ " visibility=\"" + scope + "\"\n"
|
|
//+ " source=\"" + cl.position() + "\"\n"
|
|
+ ">");
|
|
|
|
ClassInfo[] interfaces = cl.realInterfaces();
|
|
Arrays.sort(interfaces, ClassInfo.comparator);
|
|
for (ClassInfo iface : interfaces) {
|
|
if (notStrippable.contains(iface)) {
|
|
xmlWriter.println("<implements name=\"" + iface.qualifiedName() + "\">");
|
|
xmlWriter.println("</implements>");
|
|
}
|
|
}
|
|
|
|
MethodInfo[] constructors = cl.constructors();
|
|
Arrays.sort(constructors, MethodInfo.comparator);
|
|
for (MethodInfo mi : constructors) {
|
|
writeConstructorXML(xmlWriter, mi);
|
|
}
|
|
|
|
MethodInfo[] methods = cl.allSelfMethods();
|
|
Arrays.sort(methods, MethodInfo.comparator);
|
|
for (MethodInfo mi : methods) {
|
|
if (!methodIsOverride(mi)) {
|
|
writeMethodXML(xmlWriter, mi);
|
|
}
|
|
}
|
|
|
|
FieldInfo[] fields = cl.allSelfFields();
|
|
Arrays.sort(fields, FieldInfo.comparator);
|
|
for (FieldInfo fi : fields) {
|
|
writeFieldXML(xmlWriter, fi);
|
|
}
|
|
xmlWriter.println("</" + declString + ">");
|
|
|
|
}
|
|
|
|
static void writeMethodXML(PrintStream xmlWriter, MethodInfo mi) {
|
|
String scope = DroidDoc.scope(mi);
|
|
|
|
String deprecatedString = "";
|
|
if (mi.isDeprecated()) {
|
|
deprecatedString = "deprecated";
|
|
} else {
|
|
deprecatedString = "not deprecated";
|
|
}
|
|
xmlWriter.println("<method name=\"" + mi.name() + "\"\n"
|
|
+ ((mi.returnType() != null)
|
|
? " return=\"" + 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 = DroidDoc.scope(mi);
|
|
String deprecatedString = "";
|
|
if (mi.isDeprecated()) {
|
|
deprecatedString = "deprecated";
|
|
} else {
|
|
deprecatedString = "not deprecated";
|
|
}
|
|
xmlWriter.println("<constructor name=\"" + mi.name() + "\"\n"
|
|
+ " type=\"" + mi.containingClass().qualifiedName() + "\"\n"
|
|
+ " static=\"" + mi.isStatic() + "\"\n"
|
|
+ " final=\"" + mi.isFinal() + "\"\n"
|
|
+ " deprecated=\"" + deprecatedString + "\"\n"
|
|
+ " visibility=\"" + scope +"\"\n"
|
|
//+ " source=\"" + mi.position() + "\"\n"
|
|
+ ">");
|
|
|
|
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 = DroidDoc.scope(fi);
|
|
String deprecatedString = "";
|
|
if (fi.isDeprecated()) {
|
|
deprecatedString = "deprecated";
|
|
} else {
|
|
deprecatedString = "not deprecated";
|
|
}
|
|
//need to make sure value is valid XML
|
|
String value = makeXMLcompliant(fi.constantLiteralValue());
|
|
|
|
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;
|
|
}
|
|
}
|