diff options
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java new file mode 100644 index 0000000000..f3c546c1c3 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/io/CoreLibraryRewriter.java @@ -0,0 +1,201 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// 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.google.devtools.build.android.desugar.io; + +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; + +/** Utility class to prefix or unprefix class names of core library classes */ +public class CoreLibraryRewriter { + private final String prefix; + + public CoreLibraryRewriter(String prefix) { + this.prefix = prefix; + } + + /** + * Factory method that returns either a normal ClassReader if prefix is empty, or a ClassReader + * with a ClassRemapper that prefixes class names of core library classes if prefix is not empty. + */ + public ClassReader reader(InputStream content) throws IOException { + if (prefix.isEmpty()) { + return new ClassReader(content); + } else { + return new PrefixingClassReader(content, prefix); + } + } + + /** + * Factory method that returns a ClassVisitor that delegates to a ClassWriter, removing prefix + * from core library class names if it is not empty. + */ + public UnprefixingClassWriter writer(int flags) { + return new UnprefixingClassWriter(flags); + } + + static boolean shouldPrefix(String typeName) { + return (typeName.startsWith("java/") || typeName.startsWith("sun/")) && !except(typeName); + } + + private static boolean except(String typeName) { + if (typeName.startsWith("java/lang/invoke/")) { + return true; + } + + switch (typeName) { + // Autoboxed types + case "java/lang/Boolean": + case "java/lang/Byte": + case "java/lang/Character": + case "java/lang/Double": + case "java/lang/Float": + case "java/lang/Integer": + case "java/lang/Long": + case "java/lang/Number": + case "java/lang/Short": + + // Special types + case "java/lang/Class": + case "java/lang/Object": + case "java/lang/String": + case "java/lang/Throwable": + return true; + + default: // fall out + } + + return false; + } + + public String getPrefix() { + return prefix; + } + + /** Removes prefix from class names */ + public String unprefix(String typeName) { + if (prefix.isEmpty() || !typeName.startsWith(prefix)) { + return typeName; + } + return typeName.substring(prefix.length()); + } + + /** ClassReader that prefixes core library class names as they are read */ + private static class PrefixingClassReader extends ClassReader { + private final String prefix; + + PrefixingClassReader(InputStream content, String prefix) throws IOException { + super(content); + this.prefix = prefix; + } + + @Override + public void accept(ClassVisitor cv, Attribute[] attrs, int flags) { + cv = + new ClassRemapper( + cv, + new Remapper() { + @Override + public String map(String typeName) { + return prefix(typeName); + } + }); + super.accept(cv, attrs, flags); + } + + @Override + public String getClassName() { + return prefix(super.getClassName()); + } + + @Override + public String getSuperName() { + String result = super.getSuperName(); + return result != null ? prefix(result) : null; + } + + @Override + public String[] getInterfaces() { + String[] result = super.getInterfaces(); + for (int i = 0, len = result.length; i < len; ++i) { + result[i] = prefix(result[i]); + } + return result; + } + + /** Prefixes core library class names with prefix. */ + private String prefix(String typeName) { + if (shouldPrefix(typeName)) { + return prefix + typeName; + } + return typeName; + } + } + + /** + * ClassVisitor that delegates to a ClassWriter, but removes a prefix as each class is written. + * The unprefixing is optimized out if prefix is empty. + */ + public class UnprefixingClassWriter extends ClassVisitor { + private final ClassWriter writer; + + private String finalClassName; + + UnprefixingClassWriter(int flags) { + super(Opcodes.ASM6); + this.writer = new ClassWriter(flags); + this.cv = this.writer; + if (!prefix.isEmpty()) { + this.cv = + new ClassRemapper( + this.writer, + new Remapper() { + @Override + public String map(String typeName) { + return unprefix(typeName); + } + }); + } + } + + /** Returns the (unprefixed) name of the class once written. */ + @Nullable + public String getClassName() { + return finalClassName; + } + + public byte[] toByteArray() { + return writer.toByteArray(); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + finalClassName = unprefix(name); + super.visit(version, access, name, signature, superName, interfaces); + } + } +} |