1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package com.google.devtools.build.android.desugar; 15 16 import java.io.IOException; 17 import java.io.InputStream; 18 import org.objectweb.asm.Attribute; 19 import org.objectweb.asm.ClassReader; 20 import org.objectweb.asm.ClassVisitor; 21 import org.objectweb.asm.ClassWriter; 22 import org.objectweb.asm.Opcodes; 23 import org.objectweb.asm.commons.ClassRemapper; 24 import org.objectweb.asm.commons.Remapper; 25 26 /** Utility class to prefix or unprefix class names of core library classes */ 27 class CoreLibraryRewriter { 28 private final String prefix; 29 30 public CoreLibraryRewriter(String prefix) { 31 this.prefix = prefix; 32 } 33 34 /** 35 * Factory method that returns either a normal ClassReader if prefix is empty, or a ClassReader 36 * with a ClassRemapper that prefixes class names of core library classes if prefix is not empty. 37 */ 38 public ClassReader reader(InputStream content) throws IOException { 39 if (prefix.isEmpty()) { 40 return new ClassReader(content); 41 } else { 42 return new PrefixingClassReader(content, prefix); 43 } 44 } 45 46 /** 47 * Factory method that returns a ClassVisitor that delegates to a ClassWriter, removing prefix 48 * from core library class names if it is not empty. 49 */ 50 public UnprefixingClassWriter writer(int flags) { 51 return new UnprefixingClassWriter(flags); 52 } 53 54 static boolean shouldPrefix(String typeName) { 55 return (typeName.startsWith("java/") || typeName.startsWith("sun/")) && !except(typeName); 56 } 57 58 private static boolean except(String typeName) { 59 if (typeName.startsWith("java/lang/invoke/")) { 60 return true; 61 } 62 63 switch (typeName) { 64 // Autoboxed types 65 case "java/lang/Boolean": 66 case "java/lang/Byte": 67 case "java/lang/Character": 68 case "java/lang/Double": 69 case "java/lang/Float": 70 case "java/lang/Integer": 71 case "java/lang/Long": 72 case "java/lang/Number": 73 case "java/lang/Short": 74 75 // Special types 76 case "java/lang/Class": 77 case "java/lang/Object": 78 case "java/lang/String": 79 case "java/lang/Throwable": 80 return true; 81 82 default: // fall out 83 } 84 85 return false; 86 } 87 88 /** Removes prefix from class names */ 89 public String unprefix(String typeName) { 90 if (prefix.isEmpty() || !typeName.startsWith(prefix)) { 91 return typeName; 92 } 93 return typeName.substring(prefix.length()); 94 } 95 96 /** ClassReader that prefixes core library class names as they are read */ 97 private static class PrefixingClassReader extends ClassReader { 98 private final String prefix; 99 100 PrefixingClassReader(InputStream content, String prefix) throws IOException { 101 super(content); 102 this.prefix = prefix; 103 } 104 105 @Override 106 public void accept(ClassVisitor cv, Attribute[] attrs, int flags) { 107 cv = 108 new ClassRemapper( 109 cv, 110 new Remapper() { 111 @Override 112 public String map(String typeName) { 113 return prefix(typeName); 114 } 115 }); 116 super.accept(cv, attrs, flags); 117 } 118 119 @Override 120 public String getClassName() { 121 return prefix(super.getClassName()); 122 } 123 124 @Override 125 public String getSuperName() { 126 String result = super.getSuperName(); 127 return result != null ? prefix(result) : null; 128 } 129 130 @Override 131 public String[] getInterfaces() { 132 String[] result = super.getInterfaces(); 133 for (int i = 0, len = result.length; i < len; ++i) { 134 result[i] = prefix(result[i]); 135 } 136 return result; 137 } 138 139 /** Prefixes core library class names with prefix. */ 140 private String prefix(String typeName) { 141 if (shouldPrefix(typeName)) { 142 return prefix + typeName; 143 } 144 return typeName; 145 } 146 } 147 148 /** 149 * ClassVisitor that delegates to a ClassWriter, but removes a prefix as each class is written. 150 * The unprefixing is optimized out if prefix is empty. 151 */ 152 public class UnprefixingClassWriter extends ClassVisitor { 153 private final ClassWriter writer; 154 155 UnprefixingClassWriter(int flags) { 156 super(Opcodes.ASM6); 157 this.writer = new ClassWriter(flags); 158 this.cv = this.writer; 159 if (!prefix.isEmpty()) { 160 this.cv = 161 new ClassRemapper( 162 this.cv, 163 new Remapper() { 164 @Override 165 public String map(String typeName) { 166 return unprefix(typeName); 167 } 168 }); 169 } 170 } 171 172 byte[] toByteArray() { 173 return writer.toByteArray(); 174 } 175 } 176 } 177