Home | History | Annotate | Download | only in desugar
      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