AI 145983: am: CL 145911 ADT #1778786: tool to generate stubbed jar file.

This is only a preliminary CL. More will follow but this is
  a good start, with the following caveats:
  What it does:
  - take an input jar, a list of includes, a list of excludes.
  - generate actual Java source for the filtered classes.
  What it doesn't do yet:
  - some more work on filtering inner elements (methods, etc.)
  - properly generate inner classes.
  - hide synthetic fields.
  - some classes body are missing
  - directly generate a stubbed bytecode/jar rather than source.
  I'll likely want to keep the source generator for debugging
  purposes or if we want to integrate with a build system instead.
  - classpath will be changed in the final CL to refer to the external
  ASM lib rather than the project. I need the source for debugging
  rigth now.
  - will review comments before submitting.
  Original author: raphael
  Merged from: //branches/cupcake/...

Automated import of CL 145983
This commit is contained in:
Raphael Moll
2009-04-13 18:21:16 -07:00
committed by The Android Open Source Project
parent ca7264c7b7
commit 1297169e09
26 changed files with 2337 additions and 0 deletions

9
tools/mkstubs/.classpath Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="tests"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry combineaccessrules="false" kind="src" path="/asm3"/>
<classpathentry kind="output" path="bin"/>
</classpath>

18
tools/mkstubs/.project Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MkStubs</name>
<comment></comment>
<projects>
<project>asm3</project>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

28
tools/mkstubs/Android.mk Normal file
View File

@@ -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)

View File

@@ -0,0 +1 @@
Main-Class: com.android.mkstubs.Main

View File

@@ -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<String,ClassReader> parseInputJar(String inputJarPath) throws IOException {
TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
ZipFile zip = new ZipFile(inputJarPath);
Enumeration<? extends ZipEntry> 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<String, ClassReader> classes,
ArrayList<String> inclusions,
ArrayList<String> exclusions) {
ArrayList<String> inPrefix = new ArrayList<String>();
HashSet <String> inFull = new HashSet <String>();
ArrayList<String> exPrefix = new ArrayList<String>();
HashSet <String> exFull = new HashSet <String>();
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<String> keys = classes.keySet();
for(Iterator<String> 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();
}
}
}
}

View File

@@ -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<String, ClassReader> classes,
List<String> exclusions) throws IOException {
for (Entry<String, ClassReader> 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<String> 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*/);
}
}

View File

@@ -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<String> mExclusions;
public FilterClassAdapter(ClassVisitor writer, List<String> 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 = <init>
* 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
}
}

View File

@@ -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<String> mInclusions = new ArrayList<String>();
private ArrayList<String> mExclusions = new ArrayList<String>();
public Params(String inputJarPath, String outputJarPath) {
mInputJarPath = inputJarPath;
mOutputJarPath = outputJarPath;
}
public String getInputJarPath() {
return mInputJarPath;
}
public String getOutputJarPath() {
return mOutputJarPath;
}
public ArrayList<String> getExclusions() {
return mExclusions;
}
public ArrayList<String> 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<String, ClassReader> 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<String, ClassReader> classes) {
for(String className : classes.keySet()) {
System.out.println("Found " + className);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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.
}
}

View File

@@ -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 = "<init>".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<SignatureSourcer> 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
}
}

View File

@@ -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();
}
}

View File

@@ -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.
* <p/>
* 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<SignatureSourcer> mParameters = new ArrayList<SignatureSourcer>();
/**
* 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<SignatureSourcer> 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;
}
}

View File

@@ -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>());
String s = sw.toString();
Assert.assertNotNull(s);
}
}

View File

@@ -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 {
}
}

View File

@@ -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);
}
}

View File

@@ -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<Ljava/lang/String;>;" // signature
);
fs.visitEnd();
String s = mWriter.toString();
Assert.assertEquals("private final java.util.ArrayList<java.lang.String> mList;\n", s);
}
}

View File

@@ -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<T extends java.io.InputStream, U extends java.lang.Object> extends java.lang.Object {\n" +
" private final java.util.Map<T, U> mMap_T_U;\n" +
" public java.util.Map<java.util.ArrayList<T>, java.util.Map<java.lang.String, java.util.ArrayList<U>>> mMap_T_S_U;\n" +
" public TestTemplateClass() {\n" +
" throw new RuntimeException(\"Stub\");\n" +
" }\n" +
" public java.util.Map<T, U> getMap_T_U() {\n" +
" throw new RuntimeException(\"Stub\");\n" +
" }\n" +
" public java.util.Map<java.util.ArrayList<T>, java.util.Map<java.lang.String, java.util.ArrayList<U>>> getMap_T_S_U() {\n" +
" throw new RuntimeException(\"Stub\");\n" +
" }\n" +
" public void draw(java.util.List<? extends org.w3c.dom.css.Rect> arg0) {\n" +
" throw new RuntimeException(\"Stub\");\n" +
" }\n" +
" public static <T extends java.lang.Comparable<? super T>> void sort(java.util.List<T> arg0) {\n" +
" throw new RuntimeException(\"Stub\");\n" +
" }\n" +
" public <X extends T, Y extends java.lang.Object> void getMap(java.util.List<T> arg0, java.util.Map<T, U> arg1, java.util.Map<X, java.util.Set<? super Y>> arg2) {\n" +
" throw new RuntimeException(\"Stub\");\n" +
" }\n" +
"}",
sw.toString());
}
}

View File

@@ -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<TT;TU;>;", //signature
null); //exception
m.visitEnd();
assertSourceEquals(
"public java.util.Map<T, U> getMap_T_U() { }",
mWriter.toString());
}
}

View File

@@ -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/ArrayList<TT;>;Ljava/util/Map<Ljava/lang/String;Ljava/util/ArrayList<TU;>;>;>;");
reader.accept(mSourcer);
String result = mSourcer.getReturnType().toString();
Assert.assertEquals(
"java.util.Map<java.util.ArrayList<T>, java.util.Map<java.lang.String, java.util.ArrayList<U>>>",
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<SignatureSourcer> params = mSourcer.getParameters();
Assert.assertNotNull(params);
String[] array = toStringArray(params);
Assert.assertArrayEquals(
new String[] { "java.util.List<? extends org.w3c.dom.css.Rect>" },
array);
}
@Test
public void testFormalParameters1() {
SignatureReader reader = new SignatureReader("<X:TT;Y:Ljava/lang/Object;>()V");
reader.accept(mSourcer);
Assert.assertTrue(mSourcer.hasFormalsContent());
String result = mSourcer.formalsToString();
Assert.assertEquals(
"<X extends T, Y extends java.lang.Object>",
result);
}
@Test
public void testFormalParameters2() {
SignatureReader reader = new SignatureReader("<T::Ljava/lang/Comparable<-TT;>;>(Ljava/util/List<TT;>;)V");
reader.accept(mSourcer);
Assert.assertTrue(mSourcer.hasFormalsContent());
String result = mSourcer.formalsToString();
Assert.assertEquals(
"<T extends java.lang.Comparable<? super T>>",
result);
}
@Test
public void testManyArgs() {
SignatureReader reader = new SignatureReader(
"<X:TT;Y:Ljava/lang/Object;>(Ljava/util/List<TT;>;Ljava/util/Map<TT;TU;>;Ljava/util/Map<TX;Ljava/util/Set<-TY;>;>;)V");
reader.accept(mSourcer);
Assert.assertTrue(mSourcer.hasFormalsContent());
String formals = mSourcer.formalsToString();
Assert.assertEquals(
"<X extends T, Y extends java.lang.Object>",
formals);
String result = mSourcer.getReturnType().toString();
Assert.assertEquals(
"void",
result);
ArrayList<SignatureSourcer> params = mSourcer.getParameters();
Assert.assertNotNull(params);
String[] array = toStringArray(params);
Assert.assertArrayEquals(
new String[] { "java.util.List<T>",
"java.util.Map<T, U>",
"java.util.Map<X, java.util.Set<? super Y>>" },
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;
}
}

View File

@@ -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);
}
}

View File

@@ -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() {
}
}

View File

@@ -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();
}
}

View File

@@ -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<T extends InputStream, U> {
private final Map<T, U> mMap_T_U = null;
public Map<ArrayList<T>, Map<String, ArrayList<U>>> mMap_T_S_U = null;
public TestTemplateClass() {
}
public Map<T, U> getMap_T_U() {
return mMap_T_U;
}
public Map<ArrayList<T>, Map<String, ArrayList<U>>> getMap_T_S_U() {
return mMap_T_S_U;
}
public void draw(List<? extends Rect> shape) {
}
public static <T extends Comparable<? super T>> void sort(List<T> list) {
}
public <X extends T, Y> void getMap(List<T> list, Map<T, U> tu, Map<X, Set<? super Y>> xy) {
}
}