diff options
author | kmb <kmb@google.com> | 2018-02-05 18:18:15 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-02-05 18:21:02 -0800 |
commit | 1324318ea0fe60350c0a5179818fc1c97d4ec854 (patch) | |
tree | 95e900cd38111f5b380fb703880b7b3fb3b91c27 /src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java | |
parent | 1eeba9c9f90e81fad1d1a82163c3b8b2f62f761e (diff) |
Basic tooling to desugar select core libraries
RELNOTES: None.
PiperOrigin-RevId: 184619885
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java new file mode 100644 index 0000000000..56e5f18b30 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java @@ -0,0 +1,152 @@ +// Copyright 2018 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; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.collect.ImmutableList; +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * Helper that keeps track of which core library classes and methods we want to rewrite. + */ +class CoreLibrarySupport { + + private final CoreLibraryRewriter rewriter; + private final ClassLoader targetLoader; + /** Internal name prefixes that we want to move to a custom package. */ + private final ImmutableList<String> renamedPrefixes; + /** Internal names of interfaces whose default and static interface methods we'll emulate. */ + private final ImmutableList<Class<?>> emulatedInterfaces; + + public CoreLibrarySupport(CoreLibraryRewriter rewriter, ClassLoader targetLoader, + ImmutableList<String> renamedPrefixes, ImmutableList<String> emulatedInterfaces) + throws ClassNotFoundException { + this.rewriter = rewriter; + this.targetLoader = targetLoader; + checkArgument( + renamedPrefixes.stream().allMatch(prefix -> prefix.startsWith("java/")), renamedPrefixes); + this.renamedPrefixes = renamedPrefixes; + ImmutableList.Builder<Class<?>> classBuilder = ImmutableList.builder(); + for (String itf : emulatedInterfaces) { + checkArgument(itf.startsWith("java/util/"), itf); + Class<?> clazz = targetLoader.loadClass((rewriter.getPrefix() + itf).replace('/', '.')); + checkArgument(clazz.isInterface(), itf); + classBuilder.add(clazz); + } + this.emulatedInterfaces = classBuilder.build(); + } + + public boolean isRenamedCoreLibrary(String internalName) { + String unprefixedName = rewriter.unprefix(internalName); + return renamedPrefixes.stream().anyMatch(prefix -> unprefixedName.startsWith(prefix)); + } + + public String renameCoreLibrary(String internalName) { + internalName = rewriter.unprefix(internalName); + return (internalName.startsWith("java/")) + ? "j$/" + internalName.substring(/* cut away "java/" prefix */ 5) + : internalName; + } + + public boolean isEmulatedCoreLibraryInvocation( + int opcode, String owner, String name, String desc, boolean itf) { + return getEmulatedCoreLibraryInvocationTarget(opcode, owner, name, desc, itf) != null; + } + + @Nullable + public Class<?> getEmulatedCoreLibraryInvocationTarget( + int opcode, String owner, String name, String desc, boolean itf) { + if (owner.contains("$$Lambda$") || owner.endsWith("$$CC")) { + return null; // regular desugaring handles invocations on generated classes, no emulation + } + Class<?> clazz = getEmulatedCoreClassOrInterface(owner); + if (clazz == null) { + return null; + } + + if (itf && opcode == Opcodes.INVOKESTATIC) { + return clazz; // static interface method + } + + Method callee = findInterfaceMethod(clazz, name, desc); + if (callee != null && callee.isDefault()) { + return callee.getDeclaringClass(); + } + return null; + } + + private Class<?> getEmulatedCoreClassOrInterface(String internalName) { + { + String unprefixedOwner = rewriter.unprefix(internalName); + if (!unprefixedOwner.startsWith("java/util/") || isRenamedCoreLibrary(unprefixedOwner)) { + return null; + } + } + + Class<?> clazz; + try { + clazz = targetLoader.loadClass(internalName.replace('/', '.')); + } catch (ClassNotFoundException e) { + throw (NoClassDefFoundError) new NoClassDefFoundError().initCause(e); + } + + if (emulatedInterfaces.stream().anyMatch(itf -> itf.isAssignableFrom(clazz))) { + return clazz; + } + return null; + } + + private static Method findInterfaceMethod(Class<?> clazz, String name, String desc) { + return collectImplementedInterfaces(clazz, new LinkedHashSet<>()) + .stream() + // search more subtypes before supertypes + .sorted(DefaultMethodClassFixer.InterfaceComparator.INSTANCE) + .map(itf -> findMethod(itf, name, desc)) + .filter(Objects::nonNull) + .findFirst() + .orElse((Method) null); + } + + + private static Method findMethod(Class<?> clazz, String name, String desc) { + for (Method m : clazz.getMethods()) { + if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) { + return m; + } + } + return null; + } + + private static Set<Class<?>> collectImplementedInterfaces(Class<?> clazz, Set<Class<?>> dest) { + if (clazz.isInterface()) { + if (!dest.add(clazz)) { + return dest; + } + } else if (clazz.getSuperclass() != null) { + collectImplementedInterfaces(clazz.getSuperclass(), dest); + } + + for (Class<?> itf : clazz.getInterfaces()) { + collectImplementedInterfaces(itf, dest); + } + return dest; + } +} |