You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by al...@apache.org on 2020/04/02 16:58:37 UTC
[flink] 01/06: [FLINK-11767] [tests] Introduce ClassRelocator
This is an automated email from the ASF dual-hosted git repository.
aljoscha pushed a commit to branch release-1.9
in repository https://gitbox.apache.org/repos/asf/flink.git
commit 0bc3d69e036ede36d11daa65bf6541e5c3a209c3
Author: Igal Shilman <ig...@data-artisans.com>
AuthorDate: Tue Mar 5 10:09:13 2019 +0100
[FLINK-11767] [tests] Introduce ClassRelocator
---
.../flink/api/common/typeutils/ClassRelocator.java | 193 +++++++++++++++++++++
1 file changed, 193 insertions(+)
diff --git a/flink-core/src/test/java/org/apache/flink/api/common/typeutils/ClassRelocator.java b/flink-core/src/test/java/org/apache/flink/api/common/typeutils/ClassRelocator.java
new file mode 100644
index 0000000..c2e2e57
--- /dev/null
+++ b/flink-core/src/test/java/org/apache/flink/api/common/typeutils/ClassRelocator.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.flink.api.common.typeutils;
+
+import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.commons.RemappingClassAdapter;
+import jdk.internal.org.objectweb.asm.commons.SimpleRemapper;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Class relocator.
+ */
+public final class ClassRelocator {
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface RelocateClass {
+ String value();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> Class<? extends T> relocate(
+ Class<?> originalClass) {
+
+ ClassRegistry remapping = new ClassRegistry(originalClass);
+ ClassRemapper classRenamer = new ClassRemapper(remapping);
+ Map<String, byte[]> newClassBytes = classRenamer.remap();
+
+ return (Class<? extends T>) patchClass(newClassBytes, remapping);
+ }
+
+ private static Class<?> patchClass(Map<String, byte[]> newClasses, ClassRegistry remapping) {
+ final ByteClassLoader renamingClassLoader = new ByteClassLoader(remapping.getRoot().getClassLoader(), newClasses);
+ try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(renamingClassLoader)) {
+ return renamingClassLoader.loadClass(remapping.getRootNewName());
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static final class ClassRegistry {
+ private static final AtomicInteger GENERATED_ID = new AtomicInteger(0);
+
+ private final Class<?> root;
+ private final Map<Class<?>, String> targetNames;
+
+ ClassRegistry(Class<?> root) {
+ this.root = root;
+ this.targetNames = definedClasses(root)
+ .filter(type -> type.getAnnotation(RelocateClass.class) != null)
+ .collect(Collectors.toMap(
+ Function.identity(),
+ type -> type.getAnnotation(RelocateClass.class).value()
+ ));
+
+ // it is also important to overwrite the top class since it is already loaded,
+ // and we need to force load it
+ targetNames.put(root, root.getName() + String.format("$generated%d$", GENERATED_ID.incrementAndGet()));
+ }
+
+ public Class<?> getRoot() {
+ return root;
+ }
+
+ String getRootNewName() {
+ return newNameFor(root);
+ }
+
+ String newNameFor(Class<?> oldClass) {
+ return targetNames.getOrDefault(oldClass, oldClass.getName());
+ }
+
+ Set<Class<?>> getDefinedClassesUnderRoot() {
+ return definedClasses(root)
+ .collect(Collectors.toSet());
+ }
+
+ private Map<String, String> remappingAsPathNames() {
+ return targetNames.entrySet()
+ .stream()
+ .collect(Collectors.toMap(
+ e -> pathName(e.getKey()),
+ e -> pathName(e.getValue())));
+ }
+
+ private static String pathName(String className) {
+ return className.replace('.', '/');
+ }
+
+ private static String pathName(Class<?> javaClass) {
+ return javaClass.getName().replace('.', '/');
+ }
+
+ private static Stream<Class<?>> definedClasses(Class<?> klass) {
+ Stream<Class<?>> nestedClass = Arrays.stream(klass.getClasses())
+ .flatMap(ClassRegistry::definedClasses);
+
+ return Stream.concat(
+ Stream.of(klass),
+ nestedClass);
+ }
+ }
+
+ private static final class ClassRemapper {
+ private final ClassRegistry renaming;
+
+ ClassRemapper(ClassRegistry renaming) {
+ this.renaming = renaming;
+ }
+
+ Map<String, byte[]> remap() {
+ final Map<String, String> renames = renaming.remappingAsPathNames();
+
+ return renaming.getDefinedClassesUnderRoot()
+ .stream()
+ .collect(Collectors.toMap(renaming::newNameFor, classToTransform -> {
+ ClassReader providerClassReader = classReaderFor(classToTransform);
+ ClassWriter transformedProvider = remap(renames, providerClassReader);
+ return transformedProvider.toByteArray();
+ }));
+ }
+
+ private static ClassWriter remap(Map<String, String> reMapping, ClassReader providerClassReader) {
+ ClassWriter cw = new ClassWriter(0);
+ RemappingClassAdapter remappingClassAdapter = new RemappingClassAdapter(cw, new SimpleRemapper(reMapping));
+ providerClassReader.accept(remappingClassAdapter, ClassReader.EXPAND_FRAMES);
+ return cw;
+ }
+
+ private static ClassReader classReaderFor(Class<?> providerClass) {
+ String classAsPath = providerClass.getName().replace('.', '/') + ".class";
+ InputStream in = providerClass.getClassLoader().getResourceAsStream(classAsPath);
+ try {
+ return new ClassReader(in);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static final class ByteClassLoader extends URLClassLoader {
+ private final Map<String, byte[]> remappedClasses;
+
+ private ByteClassLoader(ClassLoader parent, Map<String, byte[]> remappedClasses) {
+ super(new URL[0], parent);
+ this.remappedClasses = remappedClasses;
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] bytes = remappedClasses.remove(name);
+ if (bytes == null) {
+ return super.findClass(name);
+ }
+ return defineClass(name, bytes, 0, bytes.length);
+ }
+
+ }
+}