/*
 * 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("&", "&amp;");
        returnString = returnString.replaceAll("<", "&lt;");
        returnString = returnString.replaceAll(">", "&gt;");
        returnString = returnString.replaceAll("\"", "&quot;");
        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;
    }
}
