/*
* Copyright 2016 the original author or authors.
*
* 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 org.gradle.initialization;
import org.gradle.api.GradleException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
/**
* Enriches class loading with empty interfaces for certain types that have been removed,
* but which are baked into the bytecode generated by the Groovy compiler.
*/
public class DefaultLegacyTypesSupport implements LegacyTypesSupport {
private static final Type OBJECT_TYPE = Type.getType(Object.class);
private final Set<String> classesToMixInGroovyObject = readClassNames("converted-types.txt");
private final Set<String> syntheticClasses = readClassNames("removed-types.txt");
@Override
public Set<String> getClassesToMixInGroovyObject() {
return classesToMixInGroovyObject;
}
@Override
public Set<String> getSyntheticClasses() {
return syntheticClasses;
}
private Set<String> readClassNames(String resourceName) {
Set<String> classNames = new HashSet<String>();
URL resource = LegacyTypesSupport.class.getResource(resourceName);
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream()));
try {
String line;
while ((line = reader.readLine()) != null) {
classNames.add(line.trim());
}
} finally {
reader.close();
}
} catch (IOException e) {
throw new GradleException("Could not load class names from '" + resource + "'.", e);
}
return classNames;
}
@Override
public byte[] generateSyntheticClass(String name) {
ClassWriter visitor = new ClassWriter(0);
visitor.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT, name.replace('.', '/'), null, OBJECT_TYPE.getInternalName(), null);
visitor.visitEnd();
return visitor.toByteArray();
}
/**
* Injects the interfaces into an arbitrary classloader via
* {@link ClassLoader#defineClass(String, byte[], int, int)}.
*/
@Override
public void injectEmptyInterfacesIntoClassLoader(ClassLoader classLoader) {
try {
java.lang.reflect.Method defineClassMethod =
ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
for (String name : syntheticClasses) {
byte[] bytes = generateSyntheticClass(name);
defineClassMethod.invoke(classLoader, name, bytes, 0, bytes.length);
}
defineClassMethod.setAccessible(false);
} catch (Exception e) {
throw new GradleException("Could not inject synthetic classes.", e);
}
}
}