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.
14package com.google.devtools.build.android.desugar;
15
16import java.io.IOException;
17import java.io.InputStream;
18import org.objectweb.asm.Attribute;
19import org.objectweb.asm.ClassReader;
20import org.objectweb.asm.ClassVisitor;
21import org.objectweb.asm.ClassWriter;
22import org.objectweb.asm.Opcodes;
23import org.objectweb.asm.commons.ClassRemapper;
24import org.objectweb.asm.commons.Remapper;
25
26/** Utility class to prefix or unprefix class names of core library classes */
27class 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