diff --git a/tools/mkstubs/.classpath b/tools/mkstubs/.classpath new file mode 100644 index 000000000..a6b4c4717 --- /dev/null +++ b/tools/mkstubs/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/mkstubs/.project b/tools/mkstubs/.project new file mode 100644 index 000000000..bef013ee0 --- /dev/null +++ b/tools/mkstubs/.project @@ -0,0 +1,18 @@ + + + MkStubs + + + asm3 + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/tools/mkstubs/Android.mk b/tools/mkstubs/Android.mk new file mode 100644 index 000000000..a12bf1845 --- /dev/null +++ b/tools/mkstubs/Android.mk @@ -0,0 +1,28 @@ +# +# Copyright (C) 2009 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under,src) + +LOCAL_JAR_MANIFEST := manifest.txt +LOCAL_STATIC_JAVA_LIBRARIES := \ + asm-3.1 + +LOCAL_MODULE := mkstubs + +include $(BUILD_HOST_JAVA_LIBRARY) + diff --git a/tools/mkstubs/manifest.txt b/tools/mkstubs/manifest.txt new file mode 100644 index 000000000..c0ca8e610 --- /dev/null +++ b/tools/mkstubs/manifest.txt @@ -0,0 +1 @@ +Main-Class: com.android.mkstubs.Main diff --git a/tools/mkstubs/src/com/android/mkstubs/AsmAnalyzer.java b/tools/mkstubs/src/com/android/mkstubs/AsmAnalyzer.java new file mode 100644 index 000000000..c023cf2f5 --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/AsmAnalyzer.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs; + +import org.objectweb.asm.ClassReader; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * + */ +class AsmAnalyzer { + + /** + * Parses a JAR file and returns a list of all classes founds using a map + * class name => ASM ClassReader. Class names are in the form "android.view.View". + */ + Map parseInputJar(String inputJarPath) throws IOException { + TreeMap classes = new TreeMap(); + + ZipFile zip = new ZipFile(inputJarPath); + Enumeration entries = zip.entries(); + ZipEntry entry; + while (entries.hasMoreElements()) { + entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + ClassReader cr = new ClassReader(zip.getInputStream(entry)); + String className = classReaderToAsmName(cr); + classes.put(className, cr); + } + } + + return classes; + } + + /** + * Utility that returns the fully qualified ASM class name for a ClassReader. + * E.g. it returns something like android/view/View. + */ + static String classReaderToAsmName(ClassReader classReader) { + if (classReader == null) { + return null; + } else { + return classReader.getClassName(); + } + } + + public void filter( + Map classes, + ArrayList inclusions, + ArrayList exclusions) { + + ArrayList inPrefix = new ArrayList(); + HashSet inFull = new HashSet (); + ArrayList exPrefix = new ArrayList(); + HashSet exFull = new HashSet (); + + for (String in : inclusions) { + if (in.endsWith("*")) { + inPrefix.add(in.substring(0, in.length() - 1)); + } else { + inFull.add(in); + } + } + + for (String ex : exclusions) { + if (ex.endsWith("*")) { + exPrefix.add(ex.substring(0, ex.length() - 1)); + } else { + exFull.add(ex); + } + } + + + Set keys = classes.keySet(); + for(Iterator it = keys.iterator(); it.hasNext(); ) { + String key = it.next(); + + + // Check if it can be included. + boolean keep = inFull.contains(key); + if (!keep) { + // Check for a prefix inclusion + for (String prefix : inPrefix) { + if (key.startsWith(prefix)) { + keep = true; + break; + } + } + } + + if (keep) { + // check for a full exclusion + keep = !exFull.contains(key); + } + if (keep) { + // or check for prefix exclusion + for (String prefix : exPrefix) { + if (key.startsWith(prefix)) { + keep = false; + break; + } + } + } + + // remove if we don't keep it + if (!keep) { + System.out.println("- Remove class " + key); + it.remove(); + } + } + } + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/AsmGenerator.java b/tools/mkstubs/src/com/android/mkstubs/AsmGenerator.java new file mode 100644 index 000000000..3446b0053 --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/AsmGenerator.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs; + +import com.android.mkstubs.sourcer.JavaSourcer; +import com.android.mkstubs.sourcer.Output; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * + */ +class AsmGenerator { + + /** + * Generate source for the stubbed classes, mostly for debug purposes. + * @throws IOException + */ + public void generateSource(File baseDir, + Map classes, + List exclusions) throws IOException { + + for (Entry entry : classes.entrySet()) { + ClassReader cr = entry.getValue(); + + String name = classNameToJavaPath(cr.getClassName()); + + FileWriter fw = null; + try { + fw = createWriter(baseDir, name); + dumpClass(fw, cr, exclusions); + } finally { + fw.close(); + } + } + } + + FileWriter createWriter(File baseDir, String name) throws IOException { + File f = new File(baseDir, name); + f.getParentFile().mkdirs(); + + System.out.println("Writing " + f.getPath()); + + return new FileWriter(f); + } + + /** + * Utility method that converts a fully qualified java name into a JAR entry path + * e.g. for the input "android.view.View" it returns "android/view/View.java" + */ + String classNameToJavaPath(String className) { + return className.replace('.', '/').concat(".java"); + } + + /** + * Generate a source equivalent to the stubbed version of the class reader, + * minus all exclusions + */ + void dumpClass(Writer fw, ClassReader cr, List exclusions) { + System.out.println("Dump " + cr.getClassName()); + + ClassVisitor javaWriter = new JavaSourcer(new Output(fw)); + ClassVisitor filter = new FilterClassAdapter(javaWriter, exclusions); + cr.accept(filter, 0 /*flags*/); + } + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java b/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java new file mode 100644 index 000000000..71c55f8f6 --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.List; + +/** + * A class visitor that filters out all the referenced exclusions + */ +class FilterClassAdapter extends ClassAdapter { + + private final List mExclusions; + + public FilterClassAdapter(ClassVisitor writer, List exclusions) { + super(writer); + mExclusions = exclusions; + } + + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + + // TODO filter super type + // TODO filter interfaces + + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitEnd() { + super.visitEnd(); + } + + /** + * Visits a field. + * + * {@inheritDoc} + * + * Examples: + * name = mArg + * desc = Ljava/Lang/String; + * signature = null (not a template) or template type + */ + @Override + public FieldVisitor visitField(int access, String name, String desc, + String signature, Object value) { + // exclude private fields + if ((access & Opcodes.ACC_PRIVATE) != 0) { + return null; + } + + // TODO filter on name + + return super.visitField(access, name, desc, signature, value); + } + + /** + * Visits a method. + * + * {@inheritDoc} + * + * Examples: + * name = + * desc = ()V + * signature = null (not a template) or template type + */ + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + + // exclude private methods + if ((access & Opcodes.ACC_PRIVATE) != 0) { + return null; + } + + // TODO filter exceptions: error if filtered exception is being used + + // TODO filter on name; error if filtered desc or signatures is being used + + return super.visitMethod(access, name, desc, signature, exceptions); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + + // Filter on desc type + return super.visitAnnotation(desc, visible); + } + + @Override + public void visitAttribute(Attribute attr) { + // pass + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + + // exclude private methods + if ((access & Opcodes.ACC_PRIVATE) != 0) { + return; + } + + // TODO filter on name + + super.visitInnerClass(name, outerName, innerName, access); + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + // TODO Auto-generated method stub + } + + @Override + public void visitSource(String source, String debug) { + // pass + } +} diff --git a/tools/mkstubs/src/com/android/mkstubs/Main.java b/tools/mkstubs/src/com/android/mkstubs/Main.java new file mode 100644 index 000000000..017b2f15f --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/Main.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs; + +import org.objectweb.asm.ClassReader; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; + + +/** + * + */ +public class Main { + + static class Params { + private String mInputJarPath; + private String mOutputJarPath; + private ArrayList mInclusions = new ArrayList(); + private ArrayList mExclusions = new ArrayList(); + + public Params(String inputJarPath, String outputJarPath) { + mInputJarPath = inputJarPath; + mOutputJarPath = outputJarPath; + } + + public String getInputJarPath() { + return mInputJarPath; + } + + public String getOutputJarPath() { + return mOutputJarPath; + } + + public ArrayList getExclusions() { + return mExclusions; + } + + public ArrayList getInclusions() { + return mInclusions; + } + } + + /** + * @param args + */ + public static void main(String[] args) { + + Main m = new Main(); + try { + Params p = m.processArgs(args); + m.process(p); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private Params processArgs(String[] args) throws IOException { + + if (args.length < 2) { + usage(); + } + + Params p = new Params(args[0], args[1]); + + for (int i = 2; i < args.length; i++) { + String s = args[i]; + if (s.startsWith("@")) { + addStringsFromFile(p, s.substring(1)); + } else if (s.startsWith("-")) { + p.getExclusions().add(s.substring(1)); + } else if (s.startsWith("+")) { + p.getInclusions().add(s.substring(1)); + } + } + + return p; + } + + private void addStringsFromFile(Params p, String inputFile) + throws IOException { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(inputFile)); + String line; + while ((line = br.readLine()) != null) { + line = line.trim(); + if (line.length() == 0) { + continue; + } + char mode = line.charAt(0); + line = line.substring(1).trim(); + + if (line.length() > 0) { + // Keep all class names in ASM path-like format, e.g. android/view/View + line = line.replace('.', '/'); + if (mode == '-') { + p.getExclusions().add(line); + } else if (mode == '+') { + p.getInclusions().add(line); + } + } + } + } finally { + br.close(); + } + } + + private void usage() { + System.out.println("Usage: mkstub input.jar output.jar [excluded-class @excluded-classes-file ...]"); + + System.out.println("Include syntax:\n" + + "+com.package.* : whole package, with glob\n" + + "+com.package.Class[$Inner] or ...Class*: whole classes with optional glob\n" + + "Inclusion is not supported at method/field level.\n\n"); + + System.out.println("Exclude syntax:\n" + + "-com.package.* : whole package, with glob\n" + + "-com.package.Class[$Inner] or ...Class*: whole classes with optional glob\n" + + "-com.package.Class#method: whole method or field\n" + + "-com.package.Class#method(IILjava/lang/String;)V: specific method with signature.\n\n"); + System.exit(1); + } + + private void process(Params p) throws IOException { + AsmAnalyzer aa = new AsmAnalyzer(); + Map classes = aa.parseInputJar(p.getInputJarPath()); + + aa.filter(classes, p.getInclusions(), p.getExclusions()); + + AsmGenerator gen = new AsmGenerator(); + + // dump as Java source files, mostly for debugging + File dst_src_dir = new File(p.getOutputJarPath() + File.separator + "sources"); + dst_src_dir.mkdir(); + gen.generateSource(dst_src_dir, classes, p.getExclusions()); + + } + + /** @deprecated debug only */ + private void displayClasses(Map classes) { + for(String className : classes.keySet()) { + System.out.println("Found " + className); + } + } + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/sourcer/AccessSourcer.java b/tools/mkstubs/src/com/android/mkstubs/sourcer/AccessSourcer.java new file mode 100644 index 000000000..757dcea15 --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/sourcer/AccessSourcer.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.objectweb.asm.Opcodes; + +/** + * + */ +class AccessSourcer { + + private final Output mOutput; + + public static int IS_CLASS = 1; + public static int IS_FIELD = 2; + public static int IS_METHOD = 4; + + private enum Flag { + ACC_PUBLIC(Opcodes.ACC_PUBLIC , IS_CLASS | IS_FIELD | IS_METHOD), + ACC_PRIVATE(Opcodes.ACC_PRIVATE , IS_CLASS | IS_FIELD | IS_METHOD), + ACC_PROTECTED(Opcodes.ACC_PROTECTED , IS_CLASS | IS_FIELD | IS_METHOD), + ACC_STATIC(Opcodes.ACC_STATIC , IS_FIELD | IS_METHOD), + ACC_FINAL(Opcodes.ACC_FINAL , IS_CLASS | IS_FIELD | IS_METHOD), + ACC_SUPER(Opcodes.ACC_SUPER , IS_CLASS), + ACC_SYNCHRONIZED(Opcodes.ACC_SYNCHRONIZED , IS_METHOD), + ACC_VOLATILE(Opcodes.ACC_VOLATILE , IS_FIELD), + ACC_BRIDGE(Opcodes.ACC_BRIDGE , IS_METHOD), + ACC_VARARGS(Opcodes.ACC_VARARGS , IS_METHOD), + ACC_TRANSIENT(Opcodes.ACC_TRANSIENT , IS_FIELD), + ACC_NATIVE(Opcodes.ACC_NATIVE , IS_METHOD), + ACC_INTERFACE(Opcodes.ACC_INTERFACE , IS_CLASS), + ACC_ABSTRACT(Opcodes.ACC_ABSTRACT , IS_CLASS | IS_METHOD), + ACC_STRICT(Opcodes.ACC_STRICT , IS_METHOD), + ACC_SYNTHETIC(Opcodes.ACC_SYNTHETIC , IS_CLASS | IS_FIELD | IS_METHOD), + ACC_ANNOTATION(Opcodes.ACC_ANNOTATION , IS_CLASS), + ACC_ENUM(Opcodes.ACC_ENUM , IS_CLASS), + ACC_DEPRECATED(Opcodes.ACC_DEPRECATED , IS_CLASS | IS_FIELD | IS_METHOD) + ; + + private final int mValue; + private final int mFilter; + + private Flag(int value, int filter) { + mValue = value; + mFilter = filter; + } + + public int getValue() { + return mValue; + } + + public int getFilter() { + return mFilter; + } + + /** Transforms "ACC_PUBLIC" into "public" */ + @Override + public String toString() { + return super.toString().substring(4).toLowerCase(); + } + } + + + public AccessSourcer(Output output) { + mOutput = output; + } + + /** + * Generates a list of access keywords, e.g. "public final". + * + * @param access The access mode, e.g. 33 or 18 + * @param filter One of {@link #IS_CLASS}, {@link #IS_FIELD} or {@link #IS_METHOD}, which + * indicates the validity context. + */ + public void write(int access, int filter) { + + boolean need_sep = false; + + for (Flag f : Flag.values()) { + if ((f.getFilter() & filter) != 0 && (access & f.getValue()) != 0) { + if (need_sep) { + mOutput.write(" "); + } + mOutput.write(f.toString()); + need_sep = true; + } + } + + } +} diff --git a/tools/mkstubs/src/com/android/mkstubs/sourcer/AnnotationSourcer.java b/tools/mkstubs/src/com/android/mkstubs/sourcer/AnnotationSourcer.java new file mode 100644 index 000000000..bbf1cb257 --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/sourcer/AnnotationSourcer.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.objectweb.asm.AnnotationVisitor; + +/** + * + */ +class AnnotationSourcer implements AnnotationVisitor { + + private final String mOpenChar; + private final String mCloseChar; + private final Output mOutput; + private boolean mNeedClose; + + public AnnotationSourcer(Output output) { + this(output, false /*isArray*/); + } + + public AnnotationSourcer(Output output, boolean isArray) { + mOutput = output; + mOpenChar = isArray ? "[" : "("; + mCloseChar = isArray ? "]" : ")"; + } + + public void visit(String name, Object value) { + startOpen(); + + if (name != null) { + mOutput.write("%s=", name); + } + if (value != null) { + mOutput.write(name.toString()); + } + } + + private void startOpen() { + if (!mNeedClose) { + mNeedClose = true; + mOutput.write(mOpenChar); + } + } + + public void visitEnd() { + if (mNeedClose) { + mOutput.write(mCloseChar); + } + mOutput.write("\n"); + } + + public AnnotationVisitor visitAnnotation(String name, String desc) { + startOpen(); + + mOutput.write("@%s", name); + return this; + } + + public AnnotationVisitor visitArray(String name) { + startOpen(); + return new AnnotationSourcer(mOutput, true /*isArray*/); + } + + public void visitEnum(String name, String desc, String value) { + mOutput.write("/* annotation enum not supported: %s */\n", name); + } + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/sourcer/FieldSourcer.java b/tools/mkstubs/src/com/android/mkstubs/sourcer/FieldSourcer.java new file mode 100644 index 000000000..4f9c229cc --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/sourcer/FieldSourcer.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.signature.SignatureReader; + +/** + * + */ +class FieldSourcer implements FieldVisitor { + + private final Output mOutput; + private final int mAccess; + private final String mName; + private final String mDesc; + private final String mSignature; + + public FieldSourcer(Output output, int access, String name, String desc, String signature) { + mOutput = output; + mAccess = access; + mName = name; + mDesc = desc; + mSignature = signature; + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + mOutput.write("@%s", desc); + return new AnnotationSourcer(mOutput); + } + + public void visitAttribute(Attribute attr) { + mOutput.write("%s /* non-standard attribute */ ", attr.type); + } + + public void visitEnd() { + // Need to write type and field name after the annotations and attributes. + + AccessSourcer as = new AccessSourcer(mOutput); + as.write(mAccess, AccessSourcer.IS_FIELD); + + if (mSignature == null) { + mOutput.write(" %s", mOutput.decodeDesc(mDesc)); + } else { + mOutput.write(" "); + SignatureReader sigReader = new SignatureReader(mSignature); + SignatureSourcer sigSourcer = new SignatureSourcer(); + sigReader.acceptType(sigSourcer); + mOutput.write(sigSourcer.toString()); + } + + mOutput.write(" %s", mName); + + mOutput.write(";\n"); + } + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/sourcer/JavaSourcer.java b/tools/mkstubs/src/com/android/mkstubs/sourcer/JavaSourcer.java new file mode 100644 index 000000000..fb06c680d --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/sourcer/JavaSourcer.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.signature.SignatureReader; + +/** + * A class visitor that rewrites a java source + */ +public class JavaSourcer implements ClassVisitor { + + private final Output mOutput; + private final AccessSourcer mAccessSourcer; + private String mClassName; + + public JavaSourcer(Output output) { + mOutput = output; + mAccessSourcer = new AccessSourcer(mOutput); + } + + /* Examples: + * name = com/foo/MyClass + * signature = null (if not generic) + * superName = java/lang/Object + * interfaces = [ java/lang/Runnable ... ] + */ + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + + String pkg = name.substring(0, name.lastIndexOf('/')).replace('/', '.'); + mClassName = name.substring(name.lastIndexOf('/') + 1); + + mOutput.write("package %s;\n", pkg); + + // dump access keywords. Note: do not dump "super" here + mAccessSourcer.write(access & ~Opcodes.ACC_SUPER, AccessSourcer.IS_CLASS); + + // write class name + mOutput.write(" class %s", mClassName); + + if (signature != null) { + // write template formal definition and super type + SignatureReader sigReader = new SignatureReader(signature); + SignatureSourcer sigSourcer = new SignatureSourcer(); + sigReader.accept(sigSourcer); + + if (sigSourcer.hasFormalsContent()) { + mOutput.write(sigSourcer.formalsToString()); + } + + mOutput.write(" extends %s", sigSourcer.getSuperClass().toString()); + + } else { + // write non-generic super type + mOutput.write(" extends %s", superName.replace('/', '.')); + } + + // write interfaces defined, if any + if (interfaces != null && interfaces.length > 0) { + mOutput.write(" implements "); + boolean need_sep = false; + for (String i : interfaces) { + if (need_sep) { + mOutput.write(", "); + } + mOutput.write(i.replace('/', '.')); + need_sep = true; + } + } + + // open class body + mOutput.write(" {\n"); + } + + public void visitEnd() { + mOutput.write("}\n"); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + mOutput.write("@%s", desc); + return new AnnotationSourcer(mOutput); + } + + public void visitAttribute(Attribute attr) { + mOutput.write("%s /* non-standard class attribute */ ", attr.type); + } + + + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + // skip synthetic fields + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + return null; + } + + return new FieldSourcer(mOutput, access, name, desc, signature); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + + // Visit the method and dump its stub. + return new MethodSourcer(mOutput, mClassName, access, name, desc, signature, exceptions); + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + // Skip inner classes. This just indicates there's an inner class definition but + // they are visited at the top level as separate classes. + } + + public void visitOuterClass(String owner, String name, String desc) { + // Skip outer classes. + } + + public void visitSource(String source, String debug) { + // Skip source information. + } + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/sourcer/MethodSourcer.java b/tools/mkstubs/src/com/android/mkstubs/sourcer/MethodSourcer.java new file mode 100644 index 000000000..d474e4b7c --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/sourcer/MethodSourcer.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; + +import java.util.ArrayList; + +/** + * + */ +class MethodSourcer implements MethodVisitor { + + private final Output mOutput; + private final int mAccess; + private final String mClassName; + private final String mName; + private final String mDesc; + private final String mSignature; + private final String[] mExceptions; + private boolean mNeedDeclaration; + private boolean mIsConstructor; + + public MethodSourcer(Output output, String className, int access, String name, + String desc, String signature, String[] exceptions) { + mOutput = output; + mClassName = className; + mAccess = access; + mName = name; + mDesc = desc; + mSignature = signature; + mExceptions = exceptions; + + mNeedDeclaration = true; + mIsConstructor = "".equals(name); + } + + private void writeHeader() { + if (!mNeedDeclaration) { + return; + } + + AccessSourcer as = new AccessSourcer(mOutput); + as.write(mAccess, AccessSourcer.IS_METHOD); + + // preprocess the signature to get the return type and the arguments + SignatureSourcer sigSourcer = null; + if (mSignature != null) { + SignatureReader sigReader = new SignatureReader(mSignature); + sigSourcer = new SignatureSourcer(); + sigReader.accept(sigSourcer); + + if (sigSourcer.hasFormalsContent()) { + // dump formal template parameter definitions + mOutput.write(" %s", sigSourcer.formalsToString()); + } + } + + // output return type (constructor have no return type) + if (!mIsConstructor) { + // The signature overrides desc, if present + if (sigSourcer == null || sigSourcer.getReturnType() == null) { + mOutput.write(" %s", Type.getReturnType(mDesc).getClassName()); + + } else { + mOutput.write(" %s", sigSourcer.getReturnType().toString()); + } + } + + // output name + mOutput.write(" %s(", mIsConstructor ? mClassName : mName); + + // output arguments. The signature overrides desc, if present + if (mSignature == null) { + Type[] types = Type.getArgumentTypes(mDesc); + + for(int i = 0; i < types.length; i++) { + if (i > 0) { + mOutput.write(", "); + } + mOutput.write("%s arg%d", types[i].getClassName(), i); + } + } else { + ArrayList params = sigSourcer.getParameters(); + + for(int i = 0; i < params.size(); i++) { + if (i > 0) { + mOutput.write(", "); + } + mOutput.write("%s arg%d", params.get(i).toString(), i); + } + } + mOutput.write(")"); + + // output throwable exceptions + if (mExceptions != null && mExceptions.length > 0) { + mOutput.write(" throws "); + + for (int i = 0; i < mExceptions.length; i++) { + if (i > 0) { + mOutput.write(", "); + } + mOutput.write(mExceptions[i].replace('/', '.')); + } + } + + mOutput.write(" {\n"); + + mNeedDeclaration = false; + } + + public void visitCode() { + writeHeader(); + + // write the stub itself + mOutput.write("throw new RuntimeException(\"Stub\");"); + } + + public void visitEnd() { + writeHeader(); + mOutput.write("\n}\n"); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + mOutput.write("@%s", desc); + return new AnnotationSourcer(mOutput); + } + + public AnnotationVisitor visitAnnotationDefault() { + // pass + return null; + } + + public void visitAttribute(Attribute attr) { + mOutput.write("%s /* non-standard method attribute */ ", attr.type); + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + // pass + } + + public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) { + // pass + } + + public void visitIincInsn(int var, int increment) { + // pass + } + + public void visitInsn(int opcode) { + // pass + } + + public void visitIntInsn(int opcode, int operand) { + // pass + } + + public void visitJumpInsn(int opcode, Label label) { + // pass + } + + public void visitLabel(Label label) { + // pass + } + + public void visitLdcInsn(Object cst) { + // pass + } + + public void visitLineNumber(int line, Label start) { + // pass + } + + public void visitLocalVariable(String name, String desc, String signature, + Label start, Label end, int index) { + // pass + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + // pass + } + + public void visitMaxs(int maxStack, int maxLocals) { + // pass + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + // pass + } + + public void visitMultiANewArrayInsn(String desc, int dims) { + // pass + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + // pass + return null; + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { + // pass + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + // pass + } + + public void visitTypeInsn(int opcode, String type) { + // pass + } + + public void visitVarInsn(int opcode, int var) { + // pass + } + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/sourcer/Output.java b/tools/mkstubs/src/com/android/mkstubs/sourcer/Output.java new file mode 100644 index 000000000..837cf9516 --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/sourcer/Output.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.objectweb.asm.Type; + +import java.io.IOException; +import java.io.Writer; + +/** + * + */ +public class Output { + + private final Writer mWriter; + + public Output(Writer writer) { + mWriter = writer; + } + + public void write(String format, Object... args) { + try { + mWriter.write(String.format(format, args)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void write(char c) { + write(Character.toString(c)); + } + + public void write(StringBuilder sb) { + write(sb.toString()); + } + + + public String decodeDesc(String desc) { + return Type.getType(desc).getClassName(); + } + + +} diff --git a/tools/mkstubs/src/com/android/mkstubs/sourcer/SignatureSourcer.java b/tools/mkstubs/src/com/android/mkstubs/sourcer/SignatureSourcer.java new file mode 100644 index 000000000..6202528c4 --- /dev/null +++ b/tools/mkstubs/src/com/android/mkstubs/sourcer/SignatureSourcer.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; + +import java.util.ArrayList; + +/** + * Note: most of the implementation is a duplicate of + * ASM's SignatureWriter with some slight variations. + *

+ * Note: When processing a method's signature, the signature order is the + * reverse of the source order, e.g. it is (parameters)return-type where + * we want to generate "return-type method-name (parameters)". + * So in this case the return-type and parameters are not output directly + * but are instead accumulated in internal variables. + */ +class SignatureSourcer implements SignatureVisitor { + + /** + * Buffer used to construct the signature. + */ + private final StringBuilder mBuf = new StringBuilder(); + + /** + * Buffer used to construct the formals signature. + */ + private final StringBuilder mFormalsBuf = new StringBuilder(); + + /** + * Indicates if the signature is currently processing formal type parameters. + */ + private boolean mWritingFormals; + + /** + * Stack used to keep track of class types that have arguments. Each element + * of this stack is a boolean encoded in one bit. The top of the stack is + * the lowest order bit. Pushing false = *2, pushing true = *2+1, popping = + * /2. + */ + private int mArgumentStack; + + private SignatureSourcer mReturnType; + + private SignatureSourcer mSuperClass; + + private ArrayList mParameters = new ArrayList(); + + + + /** + * Constructs a new {@link SignatureWriter} object. + */ + public SignatureSourcer() { + } + + private StringBuilder getBuf() { + if (mWritingFormals) { + return mFormalsBuf; + } else { + return mBuf; + } + } + + /** + * Contains the whole signature type when called by + * {@link SignatureReader#acceptType(SignatureVisitor)} or just the formals if + * called by {@link SignatureReader#accept(SignatureVisitor)}. + */ + @Override + public String toString() { + return mBuf.toString(); + } + + /** + * Will be non-null if a return type was processed + * by {@link SignatureReader#accept(SignatureVisitor)} + */ + public SignatureSourcer getReturnType() { + return mReturnType; + } + + /** + * Will be non-empty if a parameters were processed + * by {@link SignatureReader#accept(SignatureVisitor)} + */ + public ArrayList getParameters() { + return mParameters; + } + + /** + * True if the signature contains formal type parameters, which are available + * via {@link #formalsToString()} after calling {@link SignatureReader#accept(SignatureVisitor)} + */ + public boolean hasFormalsContent() { + return mFormalsBuf.length() > 0; + } + + public String formalsToString() { + return mFormalsBuf.toString(); + } + + /** + * Will be non-null if a super class was processed + * by {@link SignatureReader#accept(SignatureVisitor)} + */ + public SignatureSourcer getSuperClass() { + return mSuperClass; + } + + // ------------------------------------------------------------------------ + // Implementation of the SignatureVisitor interface + // ------------------------------------------------------------------------ + + public void visitFormalTypeParameter(final String name) { + if (!mWritingFormals) { + mWritingFormals = true; + getBuf().append('<'); + } else { + getBuf().append(", "); + } + getBuf().append(name); + getBuf().append(" extends "); + } + + public SignatureVisitor visitClassBound() { + // we don't differentiate between visiting a sub class or interface type + return this; + } + + public SignatureVisitor visitInterfaceBound() { + // we don't differentiate between visiting a sub class or interface type + return this; + } + + public SignatureVisitor visitSuperclass() { + endFormals(); + SignatureSourcer sourcer = new SignatureSourcer(); + assert mSuperClass == null; + mSuperClass = sourcer; + return sourcer; + } + + public SignatureVisitor visitInterface() { + return this; + } + + public SignatureVisitor visitParameterType() { + endFormals(); + SignatureSourcer sourcer = new SignatureSourcer(); + mParameters.add(sourcer); + return sourcer; + } + + public SignatureVisitor visitReturnType() { + endFormals(); + SignatureSourcer sourcer = new SignatureSourcer(); + assert mReturnType == null; + mReturnType = sourcer; + return sourcer; + } + + public SignatureVisitor visitExceptionType() { + getBuf().append('^'); + return this; + } + + public void visitBaseType(final char descriptor) { + getBuf().append(Type.getType(Character.toString(descriptor)).getClassName()); + } + + public void visitTypeVariable(final String name) { + getBuf().append(name.replace('/', '.')); + } + + public SignatureVisitor visitArrayType() { + getBuf().append('['); + return this; + } + + public void visitClassType(final String name) { + getBuf().append(name.replace('/', '.')); + mArgumentStack *= 2; + } + + public void visitInnerClassType(final String name) { + endArguments(); + getBuf().append('.'); + getBuf().append(name.replace('/', '.')); + mArgumentStack *= 2; + } + + public void visitTypeArgument() { + if (mArgumentStack % 2 == 0) { + ++mArgumentStack; + getBuf().append('<'); + } else { + getBuf().append(", "); + } + getBuf().append('*'); + } + + public SignatureVisitor visitTypeArgument(final char wildcard) { + if (mArgumentStack % 2 == 0) { + ++mArgumentStack; + getBuf().append('<'); + } else { + getBuf().append(", "); + } + if (wildcard != '=') { + if (wildcard == '+') { + getBuf().append("? extends "); + } else if (wildcard == '-') { + getBuf().append("? super "); + } else { + // can this happen? + getBuf().append(wildcard); + } + } + return this; + } + + public void visitEnd() { + endArguments(); + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Ends the formal type parameters section of the signature. + */ + private void endFormals() { + if (mWritingFormals) { + getBuf().append('>'); + mWritingFormals = false; + } + } + + /** + * Ends the type arguments of a class or inner class type. + */ + private void endArguments() { + if (mArgumentStack % 2 != 0) { + getBuf().append('>'); + } + mArgumentStack /= 2; + } +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/AsmGeneratorTest.java b/tools/mkstubs/tests/com/android/mkstubs/AsmGeneratorTest.java new file mode 100644 index 000000000..dd079d5e1 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/AsmGeneratorTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs; + + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.ClassReader; + +import java.io.StringWriter; +import java.util.ArrayList; + +/** + * + */ +public class AsmGeneratorTest { + + private AsmGenerator mGen; + + @Before + public void setUp() throws Exception { + mGen = new AsmGenerator(); + } + + @After + public void tearDown() throws Exception { + } + + + @Test + public void testDumpClass() throws Exception { + StringWriter sw = new StringWriter(); + ClassReader cr = new ClassReader("data/TestBaseClass"); + + mGen.dumpClass(sw, cr, new ArrayList()); + + String s = sw.toString(); + Assert.assertNotNull(s); + } +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/FilterClassAdapterTest.java b/tools/mkstubs/tests/com/android/mkstubs/FilterClassAdapterTest.java new file mode 100644 index 000000000..d27c1d510 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/FilterClassAdapterTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs; + + +import org.junit.After; +import org.junit.Before; + +public class FilterClassAdapterTest { + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/sourcer/AccessSourcerTest.java b/tools/mkstubs/tests/com/android/mkstubs/sourcer/AccessSourcerTest.java new file mode 100644 index 000000000..58022a980 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/sourcer/AccessSourcerTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.Opcodes; + +import java.io.StringWriter; + +public class AccessSourcerTest { + + private StringWriter mWriter; + private AccessSourcer mSourcer; + + + @Before + public void setUp() throws Exception { + mWriter = new StringWriter(); + mSourcer = new AccessSourcer(new Output(mWriter)); + } + + @After + public void tearDown() throws Exception { + mWriter = null; + mSourcer = null; + } + + @Test + public void testAbstractPublic() throws Exception { + mSourcer.write(Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC, AccessSourcer.IS_CLASS); + + String s = mWriter.toString(); + Assert.assertEquals("public abstract", s); + } + + @Test + public void testPrivateFinalStatic() throws Exception { + mSourcer.write(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, + AccessSourcer.IS_METHOD); + + String s = mWriter.toString(); + Assert.assertEquals("private static final", s); + } + +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/sourcer/FieldSourcerTest.java b/tools/mkstubs/tests/com/android/mkstubs/sourcer/FieldSourcerTest.java new file mode 100644 index 000000000..a45a47b51 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/sourcer/FieldSourcerTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.Opcodes; + +import java.io.StringWriter; + +/** + * + */ +public class FieldSourcerTest { + + private StringWriter mWriter; + + @Before + public void setUp() throws Exception { + mWriter = new StringWriter(); + } + + @After + public void tearDown() throws Exception { + mWriter = null; + } + + @Test + public void testStringField() throws Exception { + + FieldSourcer fs = new FieldSourcer(new Output(mWriter), + Opcodes.ACC_PUBLIC, // access + "mArg", // name + "Ljava/lang/String;", // desc + null // signature + ); + fs.visitEnd(); + + String s = mWriter.toString(); + Assert.assertEquals("public java.lang.String mArg;\n", s); + } + + @Test + public void testTemplateTypeField() throws Exception { + + FieldSourcer fs = new FieldSourcer(new Output(mWriter), + Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, // access + "mList", // name + "Ljava/util/ArrayList;", // desc + "Ljava/util/ArrayList;" // signature + ); + fs.visitEnd(); + + String s = mWriter.toString(); + Assert.assertEquals("private final java.util.ArrayList mList;\n", s); + } + +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/sourcer/JavaSourcerTest.java b/tools/mkstubs/tests/com/android/mkstubs/sourcer/JavaSourcerTest.java new file mode 100644 index 000000000..250da2a85 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/sourcer/JavaSourcerTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.ClassReader; + +import java.io.StringWriter; + +/** + * + */ +public class JavaSourcerTest extends TestHelper { + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + @Test + public void testBaseClassSource() throws Exception { + StringWriter sw = new StringWriter(); + ClassReader cr = new ClassReader("data/TestBaseClass"); + + JavaSourcer jw = new JavaSourcer(new Output(sw)); + cr.accept(jw, 0); + + assertSourceEquals( + "package data;\n" + + "public class TestBaseClass extends java.lang.Object implements java.lang.Runnable {\n" + + "\n" + + " private final java.lang.String mArg;\n" + + " \n" + + " public TestBaseClass() {\n" + + " throw new RuntimeException(\"Stub\");" + + " }\n" + + " public TestBaseClass(java.lang.String arg0) {\n" + + " throw new RuntimeException(\"Stub\");" + + " }\n" + + " public java.lang.String getArg() {\n" + + " throw new RuntimeException(\"Stub\");" + + " }\n" + + " public void run() {\n" + + " throw new RuntimeException(\"Stub\");" + + " }\n" + + "}", + sw.toString()); + } + + @Test + public void testInnerClassSource() throws Exception { + StringWriter sw = new StringWriter(); + ClassReader cr = new ClassReader("data/TestInnerClass"); + + JavaSourcer jw = new JavaSourcer(new Output(sw)); + cr.accept(jw, 0); + + assertSourceEquals( + "package data;\n" + + "public class TestInnerClass extends java.lang.Object {\n" + + " private final java.lang.String mArg;\n" + + " public TestInnerClass() {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public TestInnerClass(java.lang.String arg0) {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public java.lang.String getArg() {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public data.TestInnerClass$InnerPubClass getInnerPubClass() {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + "}", + sw.toString()); + } + + @Test + public void testTemplateClassSource() throws Exception { + StringWriter sw = new StringWriter(); + ClassReader cr = new ClassReader("data/TestTemplateClass"); + + JavaSourcer jw = new JavaSourcer(new Output(sw)); + cr.accept(jw, 0); + + assertSourceEquals( + "package data;\n" + + "public class TestTemplateClass extends java.lang.Object {\n" + + " private final java.util.Map mMap_T_U;\n" + + " public java.util.Map, java.util.Map>> mMap_T_S_U;\n" + + " public TestTemplateClass() {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public java.util.Map getMap_T_U() {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public java.util.Map, java.util.Map>> getMap_T_S_U() {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public void draw(java.util.List arg0) {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public static > void sort(java.util.List arg0) {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + " public void getMap(java.util.List arg0, java.util.Map arg1, java.util.Map> arg2) {\n" + + " throw new RuntimeException(\"Stub\");\n" + + " }\n" + + "}", + sw.toString()); + } + +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/sourcer/MethodSourcerTest.java b/tools/mkstubs/tests/com/android/mkstubs/sourcer/MethodSourcerTest.java new file mode 100644 index 000000000..ca3d2da23 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/sourcer/MethodSourcerTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.Opcodes; + +import java.io.StringWriter; + +/** + * + */ +public class MethodSourcerTest extends TestHelper { + + private StringWriter mWriter; + private Output mOutput; + + @Before + public void setUp() throws Exception { + mWriter = new StringWriter(); + mOutput = new Output(mWriter); + } + + @After + public void tearDown() throws Exception { + mWriter = null; + } + + @Test + public void testVoid() { + MethodSourcer m = new MethodSourcer(mOutput, + "foo", //classname + Opcodes.ACC_PUBLIC, //access + "testVoid", //name + "()V", //desc + null, //signature + null); //exception + m.visitEnd(); + + assertSourceEquals( + "public void testVoid() { }", + mWriter.toString()); + } + + @Test + public void testVoidThrow() { + MethodSourcer m = new MethodSourcer(mOutput, + "foo", //classname + Opcodes.ACC_PUBLIC, //access + "testVoid", //name + "()V", //desc + null, //signature + new String[] { "java/lang/Exception" }); //exception + m.visitEnd(); + + assertSourceEquals( + "public void testVoid() throws java.lang.Exception { }", + mWriter.toString()); + } + + @Test + public void testReturnMap() { + MethodSourcer m = new MethodSourcer(mOutput, + "foo", //classname + Opcodes.ACC_PUBLIC, //access + "getMap_T_U", //name + "()Ljava/util/Map;", //desc + "()Ljava/util/Map;", //signature + null); //exception + m.visitEnd(); + + assertSourceEquals( + "public java.util.Map getMap_T_U() { }", + mWriter.toString()); + } + +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/sourcer/SignatureSourcerTest.java b/tools/mkstubs/tests/com/android/mkstubs/sourcer/SignatureSourcerTest.java new file mode 100644 index 000000000..5d9f26ff5 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/sourcer/SignatureSourcerTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.signature.SignatureReader; + +import java.util.ArrayList; + +/** + * + */ +public class SignatureSourcerTest { + + private SignatureSourcer mSourcer; + + @Before + public void setUp() throws Exception { + mSourcer = new SignatureSourcer(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testReturnMapNoArgs() { + SignatureReader reader = new SignatureReader( + "()Ljava/util/Map;Ljava/util/Map;>;>;"); + reader.accept(mSourcer); + String result = mSourcer.getReturnType().toString(); + + Assert.assertEquals( + "java.util.Map, java.util.Map>>", + result); + } + + @Test + public void testReturnVoid() { + SignatureReader reader = new SignatureReader( + "(Ljava/util/List<+Lorg/w3c/dom/css/Rect;>;)V"); + reader.accept(mSourcer); + String result = mSourcer.getReturnType().toString(); + + Assert.assertEquals( + "void", + result); + } + + @Test + public void testSimpleArg() { + SignatureReader reader = new SignatureReader( + "(Ljava/util/List<+Lorg/w3c/dom/css/Rect;>;)V"); + reader.accept(mSourcer); + + ArrayList params = mSourcer.getParameters(); + Assert.assertNotNull(params); + + String[] array = toStringArray(params); + + Assert.assertArrayEquals( + new String[] { "java.util.List" }, + array); + } + + @Test + public void testFormalParameters1() { + SignatureReader reader = new SignatureReader("()V"); + reader.accept(mSourcer); + + Assert.assertTrue(mSourcer.hasFormalsContent()); + + String result = mSourcer.formalsToString(); + Assert.assertEquals( + "", + result); + } + + @Test + public void testFormalParameters2() { + SignatureReader reader = new SignatureReader(";>(Ljava/util/List;)V"); + reader.accept(mSourcer); + + Assert.assertTrue(mSourcer.hasFormalsContent()); + + String result = mSourcer.formalsToString(); + Assert.assertEquals( + ">", + result); + } + + @Test + public void testManyArgs() { + SignatureReader reader = new SignatureReader( + "(Ljava/util/List;Ljava/util/Map;Ljava/util/Map;>;)V"); + reader.accept(mSourcer); + + Assert.assertTrue(mSourcer.hasFormalsContent()); + String formals = mSourcer.formalsToString(); + Assert.assertEquals( + "", + formals); + + String result = mSourcer.getReturnType().toString(); + Assert.assertEquals( + "void", + result); + + ArrayList params = mSourcer.getParameters(); + Assert.assertNotNull(params); + + String[] array = toStringArray(params); + + Assert.assertArrayEquals( + new String[] { "java.util.List", + "java.util.Map", + "java.util.Map>" }, + array); + } + + private String[] toStringArray(ArrayList params) { + String[] array = new String[params.size()]; + for (int i = 0; i < params.size(); i++) { + array[i] = params.get(i).toString(); + } + return array; + } +} diff --git a/tools/mkstubs/tests/com/android/mkstubs/sourcer/TestHelper.java b/tools/mkstubs/tests/com/android/mkstubs/sourcer/TestHelper.java new file mode 100644 index 000000000..57bdee023 --- /dev/null +++ b/tools/mkstubs/tests/com/android/mkstubs/sourcer/TestHelper.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 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 com.android.mkstubs.sourcer; + +import org.junit.Assert; + +/** + * + */ +abstract class TestHelper { + + /** + * Test source equality after normalizing all whitespace. + */ + public void assertSourceEquals(String expected, String actual) { + String en = expected.replaceAll("[\\s]+", " ").trim(); + String an = actual.replaceAll( "[\\s]+", " ").trim(); + + Assert.assertEquals( + String.format("Source comparison failure: expected:<%s> but was:<%s>", expected, actual), + en, an); + } +} diff --git a/tools/mkstubs/tests/data/TestBaseClass.java b/tools/mkstubs/tests/data/TestBaseClass.java new file mode 100644 index 000000000..2b1b43832 --- /dev/null +++ b/tools/mkstubs/tests/data/TestBaseClass.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009 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 data; + + +/** + * + */ +public class TestBaseClass implements Runnable { + + private final String mArg; + + public TestBaseClass() { + throw new RuntimeException("Stub"); + } + + public TestBaseClass(String arg) { + mArg = arg; + } + + public String getArg() { + return mArg; + } + + @SuppressWarnings("unused") + public void run() { + } +} diff --git a/tools/mkstubs/tests/data/TestInnerClass.java b/tools/mkstubs/tests/data/TestInnerClass.java new file mode 100644 index 000000000..e19b96936 --- /dev/null +++ b/tools/mkstubs/tests/data/TestInnerClass.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 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 data; + + +/** + * + */ +public class TestInnerClass { + + private final String mArg; + + private class InnerPrivClass { + } + + public class InnerPubClass { + } + + private static final class InnerStaticClass { + } + + public TestInnerClass() { + mArg = null; + } + + public TestInnerClass(String arg) { + mArg = arg; + } + + public String getArg() { + return mArg; + } + + public InnerPubClass getInnerPubClass() { + return new InnerPubClass(); + } + +} diff --git a/tools/mkstubs/tests/data/TestTemplateClass.java b/tools/mkstubs/tests/data/TestTemplateClass.java new file mode 100644 index 000000000..bcc04c0f6 --- /dev/null +++ b/tools/mkstubs/tests/data/TestTemplateClass.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 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 data; + +import org.w3c.dom.css.Rect; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + */ +public class TestTemplateClass { + + private final Map mMap_T_U = null; + + public Map, Map>> mMap_T_S_U = null; + + public TestTemplateClass() { + } + + public Map getMap_T_U() { + return mMap_T_U; + } + + public Map, Map>> getMap_T_S_U() { + return mMap_T_S_U; + } + + public void draw(List shape) { + } + + public static > void sort(List list) { + } + + public void getMap(List list, Map tu, Map> xy) { + } +}