ApiDump.java :  » UnTagged » dalvik » apidump » Android Open Source

Android Open Source » UnTagged » dalvik 
dalvik » apidump » ApiDump.java
/*
 * Copyright (C) 2010 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.
 */

package apidump;

import com.google.inject.TypeLiteral;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * Writes a plain text report describing the API of a set of packages.
 */
public final class ApiDump {

    private static final Comparator<TypeLiteral<?>> ORDER_TYPES = new Comparator<TypeLiteral<?>>() {
        public int compare(TypeLiteral<?> a, TypeLiteral<?> b) {
            return a.toString().compareTo(b.toString());
        }
    };

    private static final Comparator<Class<?>> ORDER_CLASSES = new Comparator<Class<?>>() {
        public int compare(Class<?> a, Class<?> b) {
            return a.getName().compareTo(b.getName());
        }
    };

    /**
     * Order members by member type (fields, constructors, then methods), name, and parameters.
     */
    private static final Comparator<QualifiedMember> ORDER_MEMBERS = new Comparator<QualifiedMember>() {
        public int compare(QualifiedMember a, QualifiedMember b) {
            int mt = a.rankByType() - b.rankByType();
            if (mt != 0) {
                return mt;
            }

            int n = a.member.getName().compareTo(b.member.getName());
            if (n != 0) {
                return n;
            }

            if (a.isField()) {
                return 0;
            }

            List<TypeLiteral<?>> aParameters = a.getParameterTypes();
            List<TypeLiteral<?>> bParameters = b.getParameterTypes();

            for (int i = 0; i < aParameters.size() && i < bParameters.size(); i++) {
                int t = ORDER_TYPES.compare(aParameters.get(i), bParameters.get(i));
                if (t != 0) {
                    return t;
                }
            }

            return aParameters.size() - bParameters.size();
        }
    };

    private static final TypeLiteral<Object> OBJECT = new TypeLiteral<Object>() {};
    private final Set<QualifiedMember> MEMBERS_TO_SUPPRESS = computeMembersToSuppress();

    private final TreeSet<Class<?>> classes = new TreeSet<Class<?>>(ORDER_CLASSES);
    private final boolean grepFormat;
    private final boolean includeInherited;
    private final PrintStream out;

    public ApiDump(boolean grepFormat, boolean includeInherited, PrintStream out) {
        this.grepFormat = grepFormat;
        this.includeInherited = includeInherited;
        this.out = out;
    }

    public boolean isVisible(int modifiers) {
        return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers);
    }

    private void dump() {
        for (Class<?> type : classes) {
            dumpType(type);
        }
    }

    private void dumpType(Class<?> type) {
        if (!isVisible(type.getModifiers())) {
            return;
        }

        dumpClassDeclaration(type);

        Set<Class<?>> visited = new TreeSet<Class<?>>(ORDER_CLASSES);
        Set<QualifiedMember> members = new TreeSet<QualifiedMember>(ORDER_MEMBERS);
        getMembersRecursive(TypeLiteral.get(type), visited, members, true);

        for (QualifiedMember member : members) {
            if (!MEMBERS_TO_SUPPRESS.contains(member)) {
                dumpMemberDeclaration(type, member);
            }
        }

        if (!grepFormat) {
            out.print("}\n");
        }
    }

    /**
     * Writes a type declaration like this:
     *
     * public class java.util.HashMap<K, V>
     *     extends java.util.AbstractMap<K, V>
     *     implements java.io.Serializable, java.lang.Cloneable, java.util.Map<K, V> {
     */
    private void dumpClassDeclaration(Class<?> rawType) {
        TypeLiteral<?> type = TypeLiteral.get(rawType);
        
        if (grepFormat) {
            out.print(rawType.getName());
            out.print("\t");
        }

        String supertypeListSeparator = grepFormat ? " " : "\n    ";

        dumpModifiers(rawType.getModifiers(), rawType.isEnum(),
                rawType.isEnum() || rawType.isInterface());
        out.print(" " + typeType(rawType));
        out.print(" " + type);

        TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
        if (typeParameters.length > 0) {
            out.print("<" + join(", ", Arrays.asList(typeParameters)) + ">");
        }

        // TODO: skip private supertypes
        Class<?> rawSuperclass = rawType.getSuperclass();
        if (rawSuperclass != null && rawSuperclass != Object.class) {
            out.print(supertypeListSeparator);
            out.print("extends " + type.getSupertype(rawSuperclass));
        }

        Set<TypeLiteral<?>> allImplementedInterfaces = new TreeSet<TypeLiteral<?>>(ORDER_TYPES);
        getImplementedInterfaces(TypeLiteral.get(rawType), allImplementedInterfaces);
        if (!allImplementedInterfaces.isEmpty()) {
            out.print(supertypeListSeparator);
            out.print("implements " + join(", ", allImplementedInterfaces));
        }

        if (grepFormat) {
            out.print(" {}\n");
        } else {
            out.print(" {\n");
        }
    }

    private static String join(String delimiter, Iterable<?> args) {
        Iterator<?> i = args.iterator();
        if (!i.hasNext()) {
            return "";
        }
        StringBuilder result = new StringBuilder();
        result.append(i.next());
        while (i.hasNext()) {
            result.append(delimiter);
            result.append(i.next());
        }
        return result.toString();
    }

    /**
     * Prints a member declaration like these:
     *
     *   public HashMap(int, float);
     *   protected void finalize() throws java.lang.Throwable;
     * 
     * @param ofType the type this member is being dumped for. For inherited
     *     methods, this may differ from the member's declaring type which could
     *     be a supertype of this parameter.
     */
    private void dumpMemberDeclaration(Class<?> ofType, QualifiedMember member) {
        if (!isVisible(member.member.getModifiers())) {
            return;
        }
        
        if (grepFormat) {
            out.print(ofType.getName());
            out.print(".");
            out.print(member.member instanceof Constructor ? "<init>" : member.member.getName());
            out.print("\t");
        } else {
            out.print("  ");
        }

        Class<?> declaringClass = member.type.getRawType();
        dumpModifiers(member.member.getModifiers(), declaringClass.isEnum(),
                declaringClass.isEnum() || declaringClass.isInterface());

        if (member.isField()) {
            Field field = (Field) member.member;
            out.print(" " + member.type.getFieldType((Field) member.member));
            out.print(" " + field.getName());
            out.print(";\n");
            return;
        }

        List<TypeLiteral<?>> parameters = member.getParameterTypes();
        List<TypeLiteral<?>> exceptions = member.getExceptionTypes();
        TypeVariable<?>[] typeParameters;
        boolean isVarArgs;
        String name;
        if (member.member instanceof Constructor) {
            Constructor constructor = (Constructor) member.member;
            typeParameters = constructor.getTypeParameters();
            isVarArgs = constructor.isVarArgs();
            name = constructor.getName();
        } else if (member.member instanceof Method) {
            Method method = (Method) member.member;
            typeParameters = method.getTypeParameters();
            isVarArgs = method.isVarArgs();
            name = method.getName();
        } else {
            throw new AssertionError();
        }

        if (typeParameters.length > 0) {
            out.print(" <" + join(", ", Arrays.asList(typeParameters)) + ">");
        }

        if (member.member instanceof Method) {
            out.print(" " + member.type.getReturnType((Method) member.member));
        }

        out.print(" " + name);
        out.print("(");
        for (int i = 0; i < parameters.size(); i++) {
            TypeLiteral<?> parameter = parameters.get(i);
            if (i > 0) {
                out.print(", ");
            }
            out.print(parameterToString(parameter, (i == parameters.size() - 1 && isVarArgs)));
        }
        out.print(")");

        Set<TypeLiteral<?>> deduplicatedExceptions = deduplicateExceptions(exceptions);
        if (!deduplicatedExceptions.isEmpty()) {
            out.print(" throws " + join(", ", deduplicatedExceptions));
        }
        out.print(";\n");
    }

    /**
     * Removes unnecessary exceptions. Some members declare redundant exceptions,
     * for example:
     *     public void foo() throws IOException, FileNotFoundException;
     * could be simplified to:
     *     public void foo() throws IOException;
     */
    private Set<TypeLiteral<?>> deduplicateExceptions(List<TypeLiteral<?>> exceptions) {
        Set<TypeLiteral<?>> result = new TreeSet<TypeLiteral<?>>(ORDER_TYPES);

        eachException:
        for (TypeLiteral<?> exception : exceptions) {
            Class<?> rawException = exception.getRawType();
            if (RuntimeException.class.isAssignableFrom(rawException)
                    || Error.class.isAssignableFrom(rawException)) {
                continue;
            }
            for (Iterator<TypeLiteral<?>> i = result.iterator(); i.hasNext(); ) {
                TypeLiteral<?> existing = i.next();
                if (existing.getRawType().isAssignableFrom(rawException)) {
                    continue eachException;
                }
                if (rawException.isAssignableFrom(existing.getRawType())) {
                    i.remove();
                }
            }
            result.add(exception);
        }

        return result;
    }

    private void dumpModifiers(int modifiers, boolean omitFinal, boolean omitAbstract) {
        if (Modifier.isPublic(modifiers)) {
            out.print("public");
        } else if (Modifier.isProtected(modifiers)) {
            out.print("protected");
        } else if (Modifier.isPrivate(modifiers)) {
            out.print("private");
        } else {
            out.print("package");
        }
        if (Modifier.isStatic(modifiers)) {
            out.print(" static");
        }
        if (!omitFinal && Modifier.isFinal(modifiers)) {
            out.print(" final");
        }
        if (!omitAbstract && Modifier.isAbstract(modifiers)) {
            out.print(" abstract");
        }
    }

    private static String parameterToString(TypeLiteral<?> type, boolean isVarArgs) {
        String toString = type.toString();
        if (isVarArgs) {
            if (!toString.endsWith("[]")) {
                throw new IllegalArgumentException();
            }
            return toString.substring(0, toString.length() - 2) + "...";
        }
        return toString;
    }

    private String typeType(Class<?> type) {
        if (type.isEnum()) {
            return "enum";
        } else if (type.isAnnotation()) {
            return "@interface";
        } else if (type.isInterface()) {
            return "interface";
        } else {
            return "class";
        }
    }

    private void getImplementedInterfaces(TypeLiteral<?> type, Set<TypeLiteral<?>> sink) {
        // TODO: omit private interfaces and private superclasses

        Class<?> rawType = type.getRawType();
        for (Class<?> rawInterface : rawType.getInterfaces()) {
            TypeLiteral<?> implemented = type.getSupertype(rawInterface);
            if (sink.add(implemented)) {
                getImplementedInterfaces(implemented, sink);
            }
        }
        Class<?> rawSuperclass = rawType.getSuperclass();
        if (rawSuperclass != null) {
            getImplementedInterfaces(type.getSupertype(rawSuperclass), sink);
        }
    }

    private void getMembersRecursive(TypeLiteral<?> type, Set<Class<?>> visited,
            Set<QualifiedMember> sink, boolean direct) {
        // fields and constructors aren't inherited
        Class<?> rawType = type.getRawType();
        if (direct) {
            for (Constructor constructor : rawType.getDeclaredConstructors()) {
                sink.add(new QualifiedMember(type, constructor));
            }
            for (Field field : rawType.getDeclaredFields()) {
                sink.add(new QualifiedMember(type, field));
            }
        }

        for (Method method : rawType.getDeclaredMethods()) {
            QualifiedMember member = new QualifiedMember(type, method);
            if (method.isSynthetic()
                    || (!direct && Modifier.isStatic(method.getModifiers()))) {
                continue;
            }
            /*
             * Don't add a method when an override is already present. That could break covariant
             * return types.
             */
            if (!sink.contains(member)) {
                sink.add(member);
            }
        }
        
        if (includeInherited) {
            Class<?> rawSuperclass = rawType.getSuperclass();
            if (rawSuperclass != null) {
                if (visited.add(rawSuperclass)) {
                    getMembersRecursive(type.getSupertype(rawSuperclass), visited, sink, false);
                }
            }

            for (Class<?> rawInterface : rawType.getInterfaces()) {
                if (visited.add(rawInterface)) {
                    getMembersRecursive(type.getSupertype(rawInterface), visited, sink, false);
                }
            }
        }
    }

    private void addPackages(List<String> packages) throws IOException {
        ClassPathScanner scanner = new ClassPathScanner();
        System.err.println("Scanning " + scanner.getClassPath());

        for (String packageName : packages) {
            Set<Class<?>> types = scanner.scan(packageName).getTopLevelClassesRecursive();
            if (types.isEmpty()) {
                System.err.println("No classes found in " + packageName);
                continue;
            }

            for (Class<?> type : types) {
                getTypesRecursive(type, this.classes);
            }
        }
    }

    private void getTypesRecursive(Class<?> type, Set<Class<?>> sink) {
        if (sink.add(type)) {
            for (Class<?> inner : type.getClasses()) {
                getTypesRecursive(inner, sink);
            }
        }
    }

    /**
     * Don't print members inherited by every class, unless it's clone(). We
     * have to print clone() because it's eligible for covariant return classes,
     * and subclasses may expose a different signature.
     */
    private Set<QualifiedMember> computeMembersToSuppress() {
        TreeSet<QualifiedMember> result = new TreeSet<QualifiedMember>(ORDER_MEMBERS);
        getMembersRecursive(OBJECT, new TreeSet<Class<?>>(ORDER_CLASSES), result, true);
        try {
            result.remove(new QualifiedMember(OBJECT, Object.class.getDeclaredMethod("clone")));
        } catch (NoSuchMethodException e) {
            throw new AssertionError();
        }
        return Collections.unmodifiableSet(result);
    }

    static class QualifiedMember {
        private final TypeLiteral<?> type;
        private final Member member;

        QualifiedMember(TypeLiteral<?> type, Member member) {
            this.type = type;
            this.member = member;
        }

        public boolean isField() {
            return member instanceof Field;
        }

        public List<TypeLiteral<?>> getParameterTypes() {
            return type.getParameterTypes(member);
        }

        public List<TypeLiteral<?>> getExceptionTypes() {
            return type.getExceptionTypes(member);
        }

        private int rankByType() {
            if (member instanceof Field) {
                return 0;
            } else if (member instanceof Constructor) {
                return 1;
            } else if (member instanceof Method) {
                return 2;
            } else {
                throw new AssertionError();
            }
        }

        @Override public boolean equals(Object o) {
            return o instanceof QualifiedMember
                    && ((QualifiedMember) o).type.equals(type)
                    && ((QualifiedMember) o).member.equals(member);
        }

        @Override public int hashCode() {
            return type.hashCode() ^ member.hashCode();
        }
    }

    public static void main(String[] args) throws IOException {
        if (args.length == 0) {
            System.out.println("Usage: ApiDump [options] <package names...>");
            System.out.println();
            System.out.println("Options:");
            System.out.println("  --grep-format: include type name on every line");
        }
        
        boolean grepFormat = false;
        boolean includeInherited = false;
        
        List<String> argsList = new ArrayList<String>();
        argsList.addAll(Arrays.asList(args));

        for (Iterator<String> i = argsList.iterator(); i.hasNext(); ) {
            String arg = i.next();
            if (arg.equals("--grep-format")) {
                grepFormat = true;
                i.remove();
            } else if (arg.equals("--include-inherited")) {
                includeInherited = true;
                i.remove();
            }
        }

        ApiDump dump = new ApiDump(grepFormat, includeInherited, System.out);
        dump.addPackages(argsList);
        dump.dump();
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.