package com.oracle.svm.hosted.substitute;
import static com.oracle.svm.core.util.UserError.guarantee;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
import org.graalvm.compiler.core.common.SuppressFBWarnings;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionType;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.AnnotateOriginal;
import com.oracle.svm.core.annotate.Delete;
import com.oracle.svm.core.annotate.Inject;
import com.oracle.svm.core.annotate.InjectAccessors;
import com.oracle.svm.core.annotate.KeepOriginal;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.annotate.TargetElement;
import com.oracle.svm.core.configure.ConfigurationFiles;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.OptionUtils;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.core.util.json.JSONParser;
import com.oracle.svm.core.util.json.JSONParserException;
import com.oracle.svm.hosted.ImageClassLoader;
import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport;
import jdk.vm.ci.meta.MetaAccessProvider;
public class DeclarativeSubstitutionProcessor extends AnnotationSubstitutionProcessor {
public static class Options {
@Option(help = "Comma-separated list of resource file names with declarative substitutions", type = OptionType.User)
public static final HostedOptionKey<String[]> SubstitutionResources = new HostedOptionKey<>(null);
}
private final Map<Class<?>, ClassDescriptor> classDescriptors;
private final Map<Executable, MethodDescriptor> methodDescriptors;
private final Map<Field, FieldDescriptor> fieldDescriptors;
public DeclarativeSubstitutionProcessor(ImageClassLoader imageClassLoader, MetaAccessProvider metaAccess, ClassInitializationSupport classInitializationSupport) {
super(imageClassLoader, metaAccess, classInitializationSupport);
classDescriptors = new HashMap<>();
methodDescriptors = new HashMap<>();
fieldDescriptors = new HashMap<>();
for (String substitutionFileName : OptionUtils.flatten(",", ConfigurationFiles.Options.SubstitutionFiles.getValue())) {
try {
loadFile(new FileReader(substitutionFileName));
} catch (FileNotFoundException ex) {
throw UserError.abort("Substitution file %s not found.", substitutionFileName);
} catch (IOException | JSONParserException ex) {
throw UserError.abort("Could not parse substitution file %s: %s", substitutionFileName, ex.getMessage());
}
}
for (String substitutionResourceName : OptionUtils.flatten(",", Options.SubstitutionResources.getValue())) {
try {
InputStream substitutionStream = imageClassLoader.findResourceAsStreamByName(substitutionResourceName);
if (substitutionStream == null) {
throw UserError.abort("Substitution resource not found: %s", substitutionResourceName);
}
loadFile(new InputStreamReader(substitutionStream));
} catch (IOException | JSONParserException ex) {
throw UserError.abort("Could not parse substitution resource %s: %s", substitutionResourceName, ex.getMessage());
}
}
}
private void loadFile(Reader reader) throws IOException {
Set<Class<?>> annotatedClasses = new HashSet<>(imageClassLoader.findAnnotatedClasses(TargetClass.class, false));
JSONParser parser = new JSONParser(reader);
@SuppressWarnings("unchecked")
List<Object> allDescriptors = (List<Object>) parser.parse();
for (Object classDescriptorData : allDescriptors) {
if (classDescriptorData == null) {
continue;
}
ClassDescriptor classDescriptor = new ClassDescriptor(classDescriptorData);
Class<?> annotatedClass = imageClassLoader.findClassOrFail(classDescriptor.annotatedClass());
if (annotatedClasses.contains(annotatedClass)) {
throw UserError.abort("Target class already registered using explicit @TargetClass annotation: %s", annotatedClass);
} else if (classDescriptors.containsKey(annotatedClass)) {
throw UserError.abort("Target class already registered using substitution file: %s", annotatedClass);
}
classDescriptors.put(annotatedClass, classDescriptor);
for (Object methodDescriptorData : classDescriptor.methods()) {
if (methodDescriptorData == null) {
continue;
}
MethodDescriptor methodDescriptor = new MethodDescriptor(methodDescriptorData);
Executable annotatedMethod;
if (methodDescriptor.parameterTypes() != null) {
annotatedMethod = findMethod(annotatedClass, methodDescriptor.annotatedName(), methodDescriptor.parameterTypes());
} else {
annotatedMethod = findMethod(annotatedClass, methodDescriptor.annotatedName(), true);
}
methodDescriptors.put(annotatedMethod, methodDescriptor);
}
for (Object fieldDescriptorData : classDescriptor.fields()) {
FieldDescriptor fieldDescriptor = new FieldDescriptor(fieldDescriptorData);
Field annotatedField = findField(annotatedClass, fieldDescriptor.annotatedName());
fieldDescriptors.put(annotatedField, fieldDescriptor);
}
}
}
private static Executable findMethod(Class<?> declaringClass, String methodName, boolean fatalIfNotUnique) {
Executable result = null;
if (methodName.equals(TargetElement.CONSTRUCTOR_NAME)) {
for (Constructor<?> c : declaringClass.getDeclaredConstructors()) {
if (result != null) {
guarantee(!fatalIfNotUnique, "two constructors found: %s, %s ", result, c);
return null;
}
result = c;
}
guarantee(!fatalIfNotUnique || result != null, "no constructor found: %s", declaringClass);
} else {
for (Method m : declaringClass.getDeclaredMethods()) {
if (m.getName().equals(methodName)) {
if (result != null) {
guarantee(!fatalIfNotUnique, "two methods with same name found: %s, %s ", result, m);
return null;
}
result = m;
}
}
guarantee(!fatalIfNotUnique || result != null, "method not found: %s, method name %s", declaringClass, methodName);
}
return result;
}
private Executable findMethod(Class<?> declaringClass, String methodName, String[] parameterTypeNames) {
Class<?>[] parameterTypes = new Class<?>[parameterTypeNames.length];
for (int i = 0; i < parameterTypes.length; i++) {
parameterTypes[i] = imageClassLoader.findClassOrFail(parameterTypeNames[i]);
}
try {
if (methodName.equals(TargetElement.CONSTRUCTOR_NAME)) {
return declaringClass.getDeclaredConstructor(parameterTypes);
} else {
return declaringClass.getDeclaredMethod(methodName, parameterTypes);
}
} catch (NoSuchMethodException e) {
throw VMError.shouldNotReachHere("method not found: " + declaringClass + ", method name " + methodName + ", parameter types " + Arrays.toString(parameterTypeNames));
}
}
private static Field findField(Class<?> declaringClass, String fieldName) {
try {
return declaringClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
throw VMError.shouldNotReachHere(e);
}
}
@Override
protected List<Class<?>> findTargetClasses() {
List<Class<?>> result = super.findTargetClasses();
result.addAll(classDescriptors.keySet());
return result;
}
@Override
protected <T extends Annotation> T lookupAnnotation(AnnotatedElement element, Class<T> annotationClass) {
Annotation result;
if (element instanceof Class && classDescriptors.containsKey(element)) {
ClassDescriptor classDescriptor = classDescriptors.get(element);
if (annotationClass == Platforms.class) {
result = classDescriptor.platforms() != null ? classDescriptor.new PlatformsImpl(imageClassLoader) : null;
} else if (annotationClass == TargetClass.class) {
result = classDescriptor.new TargetClassImpl();
} else if (annotationClass == Delete.class) {
result = classDescriptor.delete() ? new DeleteImpl() : null;
} else if (annotationClass == Substitute.class) {
result = classDescriptor.substitute() ? new SubstituteImpl() : null;
} else {
throw VMError.shouldNotReachHere("Unexpected annotation type: " + annotationClass.getName());
}
} else if (element instanceof Executable && methodDescriptors.containsKey(element)) {
MethodDescriptor methodDescriptor = methodDescriptors.get(element);
if (annotationClass == Platforms.class) {
result = methodDescriptor.platforms() != null ? methodDescriptor.new PlatformsImpl(imageClassLoader) : null;
} else if (annotationClass == TargetElement.class) {
result = methodDescriptor.new TargetElementImpl();
} else if (annotationClass == Delete.class) {
result = methodDescriptor.delete() ? new DeleteImpl() : null;
} else if (annotationClass == Substitute.class) {
result = methodDescriptor.substitute() ? new SubstituteImpl() : null;
} else if (annotationClass == AnnotateOriginal.class) {
result = methodDescriptor.annotateOriginal() ? new AnnotateOriginalImpl() : null;
} else if (annotationClass == KeepOriginal.class) {
result = methodDescriptor.keepOriginal() ? new KeepOriginalImpl() : null;
} else if (annotationClass == Alias.class) {
result = methodDescriptor.alias() ? new AliasImpl() : null;
} else {
throw VMError.shouldNotReachHere("Unexpected annotation type: " + annotationClass.getName());
}
} else if (element instanceof Field && fieldDescriptors.containsKey(element)) {
FieldDescriptor fieldDescriptor = fieldDescriptors.get(element);
if (annotationClass == Platforms.class) {
result = fieldDescriptor.platforms() != null ? fieldDescriptor.new PlatformsImpl(imageClassLoader) : null;
} else if (annotationClass == TargetElement.class) {
result = fieldDescriptor.new TargetElementImpl();
} else if (annotationClass == Delete.class) {
result = fieldDescriptor.delete() ? new DeleteImpl() : null;
} else if (annotationClass == Alias.class) {
result = fieldDescriptor.alias() ? new AliasImpl() : null;
} else if (annotationClass == Inject.class) {
result = fieldDescriptor.inject() ? new InjectImpl() : null;
} else if (annotationClass == RecomputeFieldValue.class) {
result = fieldDescriptor.kind() != null ? fieldDescriptor.new RecomputeFieldValueImpl() : null;
} else if (annotationClass == InjectAccessors.class) {
result = fieldDescriptor.injectAccessors() != null ? fieldDescriptor.new InjectAccessorsImpl(imageClassLoader) : null;
} else {
throw VMError.shouldNotReachHere("Unexpected annotation type: " + annotationClass.getName());
}
} else {
result = super.lookupAnnotation(element, annotationClass);
}
return annotationClass.cast(result);
}
}
class AnnotationImpl implements Annotation {
@Override
public Class<? extends Annotation> annotationType() {
throw VMError.shouldNotReachHere();
}
@Override
public int hashCode() {
throw VMError.shouldNotReachHere();
}
@Override
public boolean equals(Object obj) {
throw VMError.shouldNotReachHere();
}
}
@SuppressWarnings("all")
class DeleteImpl extends AnnotationImpl implements Delete {
@Override
public String value() {
return "";
}
}
@SuppressWarnings("all")
class SubstituteImpl extends AnnotationImpl implements Substitute {
@Override
public boolean polymorphicSignature() {
return false;
}
}
@SuppressWarnings("all")
class AliasImpl extends AnnotationImpl implements Alias {
}
@SuppressWarnings("all")
class AnnotateOriginalImpl extends AnnotationImpl implements AnnotateOriginal {
}
@SuppressWarnings("all")
class KeepOriginalImpl extends AnnotationImpl implements KeepOriginal {
}
@SuppressWarnings("all")
class InjectImpl extends AnnotationImpl implements Inject {
}
class DataObject {
final Map<String, Object> data;
@SuppressWarnings("unchecked")
DataObject(Object data) {
this.data = (Map<String, Object>) data;
}
@SuppressWarnings("unchecked")
<T> T get(String propertyName, T defaultValue) {
if (data.containsKey(propertyName)) {
return (T) data.get(propertyName);
} else {
return defaultValue;
}
}
}
@SuppressFBWarnings(value = "UwF", justification = "Fields written by GSON using reflection")
class PlatformsDescriptor extends DataObject {
PlatformsDescriptor(Object data) {
super(data);
}
List<String> platforms() {
return get("platforms", null);
}
@SuppressWarnings("all")
@SuppressFBWarnings(value = "NP", justification = "Fields written by GSON using reflection")
class PlatformsImpl extends AnnotationImpl implements Platforms {
private final ImageClassLoader loader;
PlatformsImpl(ImageClassLoader loader) {
this.loader = loader;
}
@Override
public Class<? extends Platform>[] value() {
List<String> platforms = platforms();
Class<?>[] result = new Class<?>[platforms.size()];
for (int i = 0; i < result.length; i++) {
result[i] = loader.findClassOrFail(platforms.get(i));
}
return cast(result);
}
}
@SuppressWarnings("unchecked")
static <T> T cast(Object o) {
return (T) o;
}
}
@SuppressWarnings("unchecked")
@SuppressFBWarnings(value = "UwF", justification = "Fields written by GSON using reflection")
class ClassDescriptor extends PlatformsDescriptor {
ClassDescriptor(Object data) {
super(data);
}
String annotatedClass() {
return get("annotatedClass", null);
}
boolean delete() {
return get("delete", false);
}
boolean substitute() {
return get("substitute", false);
}
List<Object> methods() {
return get("methods", Collections.emptyList());
}
List<Object> fields() {
return get("fields", Collections.emptyList());
}
@SuppressWarnings("all")
class TargetClassImpl extends AnnotationImpl implements TargetClass {
@Override
public Class<?> value() {
return TargetClass.class;
}
@Override
public String className() {
return get("originalClass", null);
}
@Override
public Class<? extends Function<TargetClass, String>> classNameProvider() {
return TargetClass.NoClassNameProvider.class;
}
@Override
public String[] innerClass() {
return new String[0];
}
@Override
public Class<? extends BooleanSupplier>[] onlyWith() {
return (Class<? extends BooleanSupplier>[]) new Class<?>[]{TargetClass.AlwaysIncluded.class};
}
}
}
@SuppressWarnings("unchecked")
@SuppressFBWarnings(value = "UwF", justification = "Fields written by GSON using reflection")
class ElementDescriptor extends PlatformsDescriptor {
ElementDescriptor(Object data) {
super(data);
}
String annotatedName() {
return get("annotatedName", null);
}
@SuppressWarnings("all")
class TargetElementImpl extends AnnotationImpl implements TargetElement {
@Override
public String name() {
return get("originalName", "");
}
@Override
public Class<? extends Predicate<Class<?>>>[] onlyWith() {
return (Class<? extends Predicate<Class<?>>>[]) new Class<?>[]{TargetClass.AlwaysIncluded.class};
}
}
}
@SuppressFBWarnings(value = "UwF", justification = "Fields written by GSON using reflection")
class MethodDescriptor extends ElementDescriptor {
MethodDescriptor(Object data) {
super(data);
}
String[] parameterTypes() {
return get("parameterTypes", null);
}
boolean delete() {
return get("delete", false);
}
boolean substitute() {
return get("substitute", false);
}
boolean annotateOriginal() {
return get("annotateOriginal", false);
}
boolean keepOriginal() {
return get("keepOriginal", false);
}
boolean alias() {
return get("alias", false);
}
}
@SuppressFBWarnings(value = "UwF", justification = "Fields written by GSON using reflection")
class FieldDescriptor extends ElementDescriptor {
FieldDescriptor(Object data) {
super(data);
}
boolean delete() {
return get("delete", false);
}
boolean alias() {
return get("alias", false);
}
boolean inject() {
return get("inject", false);
}
RecomputeFieldValue.Kind kind() {
return RecomputeFieldValue.Kind.valueOf(get("kind", null));
}
String injectAccessors() {
return get("injectAccessors", null);
}
@SuppressWarnings("all")
class RecomputeFieldValueImpl extends AnnotationImpl implements RecomputeFieldValue {
@Override
public Kind kind() {
Kind result = get("kind", null);
assert result != null;
return result;
}
@Override
public Class<?> declClass() {
return RecomputeFieldValue.class;
}
@Override
public String declClassName() {
return get("declClassName", "");
}
@Override
public String name() {
return get("name", "");
}
@Override
public boolean isFinal() {
return get("isFinal", false);
}
}
@SuppressWarnings("all")
class InjectAccessorsImpl extends AnnotationImpl implements InjectAccessors {
private final ImageClassLoader loader;
InjectAccessorsImpl(ImageClassLoader loader) {
this.loader = loader;
}
@Override
public Class<?> value() {
assert injectAccessors() != null;
return loader.findClassOrFail(injectAccessors());
}
}
}