package com.oracle.truffle.polyglot;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.graalvm.polyglot.Value;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.polyglot.HostLanguage.HostContext;
final class HostAdapterFactory {
@TruffleBoundary
static AdapterResult getAdapterClassFor(PolyglotEngineImpl engine, HostContext hostContext, Class<?>[] types, Object classOverrides) {
assert types.length > 0;
HostClassCache hostClassCache = engine.getHostClassCache();
HostClassLoader hostClassLoader = hostContext.getClassloader();
if (classOverrides == null) {
if (types.length == 1) {
HostClassDesc classDesc = HostClassDesc.forClass(engine, types[0]);
return classDesc.getAdapter(hostContext);
} else {
Map<List<Class<?>>, AdapterResult> map = hostContext.adapterCache.get(getTypeForCache(types));
List<Class<?>> cacheKey = Arrays.asList(types);
AdapterResult result = map.get(cacheKey);
if (result == null) {
result = makeAdapterClassFor(hostClassCache, types, hostClassLoader, classOverrides);
if (result.isSuccess()) {
AdapterResult prev = map.putIfAbsent(cacheKey, result);
if (prev != null) {
result = prev;
}
}
}
return result;
}
}
return HostAdapterFactory.makeAdapterClassFor(hostClassCache, types, hostClassLoader, classOverrides);
}
@TruffleBoundary
static AdapterResult makeAdapterClassFor(HostClassCache hostClassCache, Class<?>[] types, ClassLoader classLoader, Object classOverrides) {
return makeAdapterClassForCommon(hostClassCache, types, classLoader, classOverrides);
}
@TruffleBoundary
static AdapterResult makeAdapterClassFor(HostClassCache hostClassCache, Class<?> type, ClassLoader classLoader) {
return makeAdapterClassForCommon(hostClassCache, new Class<?>[]{type}, classLoader, null);
}
private static AdapterResult makeAdapterClassForCommon(HostClassCache hostClassCache, Class<?>[] types, ClassLoader classLoader, Object classOverrides) {
assert types.length > 0;
CompilerAsserts.neverPartOfCompilation();
Class<?> superClass = null;
final List<Class<?>> interfaces = new ArrayList<>();
for (final Class<?> t : types) {
if (!t.isInterface()) {
if (superClass != null) {
throw PolyglotEngineException.illegalArgument(
String.format("Can not extend multiple classes %s and %s. At most one of the specified types can be a class, the rest must all be interfaces.",
t.getCanonicalName(), superClass.getCanonicalName()));
} else if (Modifier.isFinal(t.getModifiers())) {
throw PolyglotEngineException.illegalArgument(String.format("Can not extend final class %s.", t.getCanonicalName()));
} else {
superClass = t;
}
} else {
if (interfaces.size() >= 65535) {
throw PolyglotEngineException.illegalArgument("interface limit exceeded");
}
interfaces.add(t);
}
if (!Modifier.isPublic(t.getModifiers())) {
throw PolyglotEngineException.illegalArgument(String.format("Class not public: %s.", t.getCanonicalName()));
}
if (!HostInteropReflect.isExtensibleType(t) || !hostClassCache.allowsImplementation(t)) {
throw PolyglotEngineException.illegalArgument("Implementation not allowed for " + t);
}
}
superClass = superClass != null ? superClass : Object.class;
ClassLoader commonLoader = getCommonClassLoader(classLoader, superClass);
if (!classLoaderCanSee(commonLoader, types)) {
throw PolyglotEngineException.illegalArgument("Could not determine a class loader that can see all types: " + Arrays.toString(types));
}
Class<?> adapterClass;
try {
adapterClass = generateAdapterClassFor(superClass, interfaces, commonLoader, hostClassCache, classOverrides);
} catch (PolyglotEngineException ex) {
return new AdapterResult(ex);
} catch (IllegalArgumentException ex) {
return new AdapterResult(PolyglotEngineException.illegalArgument(ex));
}
HostClassDesc classDesc = hostClassCache.forClass(adapterClass);
HostMethodDesc constructor = classDesc.lookupConstructor();
HostMethodDesc.SingleMethod valueConstructor = null;
if (constructor != null) {
for (HostMethodDesc.SingleMethod overload : constructor.getOverloads()) {
if (overload.getParameterCount() == 1 && overload.getParameterTypes()[0] == Value.class) {
valueConstructor = overload;
break;
}
}
return new AdapterResult(adapterClass, constructor, valueConstructor);
} else {
return new AdapterResult(PolyglotEngineException.illegalArgument("No accessible constructor: " + superClass.getCanonicalName()));
}
}
private static Class<?> generateAdapterClassFor(Class<?> superClass, List<Class<?>> interfaces, ClassLoader commonLoader, HostClassCache hostClassCache, Object classOverrides) {
boolean classOverride = classOverrides != null;
HostAdapterBytecodeGenerator bytecodeGenerator = new HostAdapterBytecodeGenerator(superClass, interfaces, commonLoader, hostClassCache, classOverride);
HostAdapterClassLoader generatedClassLoader = bytecodeGenerator.createAdapterClassLoader();
return generatedClassLoader.generateClass(commonLoader, classOverrides);
}
@TruffleBoundary
static Object getSuperAdapter(HostObject adapter) {
assert isAdapterInstance(adapter.obj);
return new HostAdapterSuperMembers(adapter);
}
@TruffleBoundary
static String getSuperMethodName(String methodName) {
assert !methodName.startsWith(HostAdapterBytecodeGenerator.SUPER_PREFIX);
return HostAdapterBytecodeGenerator.SUPER_PREFIX.concat(methodName);
}
@TruffleBoundary
static boolean isAdapterInstance(Object adapter) {
return HostAdapterClassLoader.isAdapterInstance(adapter);
}
private static boolean classLoaderCanSee(ClassLoader loader, Class<?> clazz) {
if (clazz.getClassLoader() == loader) {
return true;
}
try {
return Class.forName(clazz.getName(), false, loader) == clazz;
} catch (final ClassNotFoundException e) {
return false;
}
}
private static boolean classLoaderCanSee(ClassLoader loader, Class<?>[] classes) {
for (Class<?> c : classes) {
if (!classLoaderCanSee(loader, c)) {
return false;
}
}
return true;
}
private static ClassLoader getCommonClassLoader(ClassLoader classLoader, Class<?> superclass) {
if (superclass != Object.class) {
if (HostAdapterClassLoader.isGeneratedClass(superclass)) {
return superclass.getClassLoader();
}
}
return classLoader;
}
private static Class<?> getTypeForCache(Class<?>[] types) {
return types[0];
}
static final class AdapterResult {
private final Class<?> adapterClass;
private final HostMethodDesc constructor;
private final HostMethodDesc.SingleMethod valueConstructor;
private final PolyglotEngineException exception;
AdapterResult(Class<?> adapterClass, HostMethodDesc constructor, HostMethodDesc.SingleMethod valueConstructor) {
this.adapterClass = Objects.requireNonNull(adapterClass);
this.constructor = constructor;
this.valueConstructor = valueConstructor;
this.exception = null;
}
AdapterResult(PolyglotEngineException exception) {
this.adapterClass = null;
this.constructor = null;
this.valueConstructor = null;
this.exception = exception;
}
Class<?> getAdapterClass() {
return adapterClass;
}
HostMethodDesc getConstructor() {
return constructor;
}
HostMethodDesc.SingleMethod getValueConstructor() {
return valueConstructor;
}
boolean isSuccess() {
return constructor != null;
}
boolean isAutoConvertible() {
return valueConstructor != null;
}
PolyglotEngineException throwException() {
throw exception;
}
}
}