package php.runtime.reflection;
import php.runtime.Memory;
import php.runtime.annotation.Reflection;
import php.runtime.common.Callback;
import php.runtime.common.CallbackW;
import php.runtime.common.Messages;
import php.runtime.env.Context;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.exceptions.CriticalException;
import php.runtime.exceptions.support.ErrorType;
import php.runtime.ext.support.Extension;
import php.runtime.ext.support.compile.CompileFunction;
import php.runtime.lang.IObject;
import php.runtime.memory.support.MemoryOperation;
import php.runtime.reflection.support.ReflectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
public class CompileMethodEntity extends MethodEntity {
protected CompileMethod function;
public CompileMethodEntity(Extension extension) {
super((Context) null);
function = new CompileMethod();
setExtension(extension);
}
@Override
public void setName(String name) {
super.setName(name);
function.name = name + "()";
}
public boolean addMethod(Method method, boolean skipConflicts) {
return addMethod(method, skipConflicts, null);
}
public boolean addMethod(Method method, boolean skipConflicts, CallbackW<MemoryOperation, Class<?>, Type> unknownTypeFetcher) {
if (skipConflicts && function.find(method.getParameterTypes().length) != null) {
return false;
}
method.setAccessible(true);
CompileMethod.Method compileMethod = function.addMethod(method);
compileMethod.setUnknownTypeFetcher(unknownTypeFetcher);
int mods = method.getModifiers();
String name = method.getName();
if (method.isAnnotationPresent(Reflection.Name.class)) {
name = method.getAnnotation(Reflection.Name.class).value();
}
setName(name);
setStatic(Modifier.isStatic(mods));
setDeprecated(method.getAnnotation(Deprecated.class) != null);
if (Modifier.isProtected(mods)) {
setModifier(php.runtime.common.Modifier.PROTECTED);
} else if (Modifier.isPrivate(mods)) {
setModifier(php.runtime.common.Modifier.PRIVATE);
} else {
setModifier(php.runtime.common.Modifier.PUBLIC);
}
setReturnReference(method.getAnnotation(Reflection.Reference.class) != null);
setFinal(method.isAnnotationPresent(Reflection.Final.class));
setAbstract(Modifier.isAbstract(mods));
setInternalName(method.getName());
ParameterEntity[] parameters = new ParameterEntity[method.getParameterTypes().length];
Annotation[][] annotations = method.getParameterAnnotations();
int i = 0;
for (Class<?> el : method.getParameterTypes()) {
if (el == Environment.class || el == TraceInfo.class) {
continue;
}
ParameterEntity param = new ParameterEntity(context);
param.setName("arg" + i);
param.setTrace(TraceInfo.UNKNOWN);
Annotation[] argAnnotations = annotations[i];
if (ReflectionUtils.getAnnotation(argAnnotations, Reflection.Nullable.class) != null) {
param.setNullable(true);
}
parameters[i++] = param;
}
if (i < parameters.length) {
parameters = Arrays.copyOf(parameters, i);
}
if (this.parameters == null || this.parameters.length < parameters.length) {
this.parameters = parameters;
}
try {
compileMethod.setParameters(parameters);
} catch (CriticalException e) {
if (skipConflicts) {
function.delete(parameters.length);
return false;
}
throw e;
}
return true;
}
@Override
public void unsetArguments(Memory[] arguments) {
super.unsetArguments(arguments);
}
@Override
public Memory invokeDynamic(IObject _this, Environment env, Memory... arguments) throws Throwable {
return invokeDynamic((Object)_this, env, arguments);
}
public Memory invokeDynamic(Object _this, Environment env, Memory... arguments) throws Throwable {
try {
if (isAbstract){
env.error(ErrorType.E_ERROR, "Cannot call abstract method %s", getSignatureString(false));
return Memory.NULL;
}
if (_this == null && !isStatic){
_this = clazz.newMock(env);
if (_this == null)
env.error(ErrorType.E_ERROR, Messages.ERR_STATIC_METHOD_CALLED_DYNAMICALLY.fetch(
getClazz().getName() + "::" + getName())
);
}
CompileMethod.Method method = function.find(arguments == null ? 0 : arguments.length);
if (method == null){
env.warning(env.trace(), Messages.ERR_EXPECT_LEAST_PARAMS.fetch(
name, function.getMinArgs(), arguments == null ? 0 : arguments.length
));
return Memory.NULL;
} else {
if (arguments != null && arguments.length > method.argsCount && !method.isVarArg()) {
env.error(env.trace(), ErrorType.E_ERROR, Messages.ERR_EXPECT_EXACTLY_PARAMS,
name, method.argsCount, arguments.length
);
return Memory.NULL;
}
}
Class<?>[] types = method.parameterTypes;
Object[] passed = new Object[ types.length ];
int i = 0;
int j = 0;
for(Class<?> clazz : types) {
boolean isRef = method.references[i];
boolean mutableValue = method.mutableValues[i];
MemoryOperation<?> operation = method.argumentOperations[i];
if (clazz == Memory.class) {
passed[i] = isRef ? arguments[j] : (mutableValue ? arguments[j].toValue() : arguments[j].toImmutable());
j++;
} else if (operation != null) {
if (operation instanceof InjectMemoryOperation) {
passed[i] = operation.convert(env, trace, null);
} else {
passed[i] = operation.convert(env, trace, arguments[j]);
j++;
}
} else if (i == types.length - 1 && types[i] == Memory[].class){
Memory[] arg = new Memory[arguments.length - i + 1];
if (!isRef){
for(int k = 0; k < arg.length; k++)
arg[i] = arguments[i].toImmutable();
} else {
System.arraycopy(arguments, j, arg, 0, arg.length);
}
passed[i] = arg;
break;
} else {
env.error(trace, ErrorType.E_CORE_ERROR, name + "(): Cannot call this method dynamically");
passed[i] = Memory.NULL;
}
i++;
}
try {
return method.returnOperation.unconvertNoThow(env, trace, method.method.invoke(_this, passed));
} finally {
i = 0;
if (this != this.getClazz().methodConstruct) {
for (Object o : passed) {
MemoryOperation operation = method.argumentOperations[i];
if (operation != null) {
operation.releaseConverted(env, trace, o);
}
i++;
}
}
}
} catch (InvocationTargetException e){
return env.__throwException(e);
} catch (Throwable e) {
throw e;
} finally {
unsetArguments(arguments);
}
}
@Override
public void setPrototype(MethodEntity prototype) {
if (prototype instanceof CompileMethodEntity) {
function.mergeFunction(((CompileMethodEntity) prototype).function);
}
super.setPrototype(prototype);
}
@Override
public ParameterEntity[] getParameters(int count) {
CompileMethod.Method result = function.find(count);
if (result == null) {
return new ParameterEntity[0];
}
return result.parameters;
}
abstract public static class InjectMemoryOperation extends MemoryOperation {
@Override
public Class<?>[] getOperationClasses() {
return new Class<?>[0];
}
@Override
public Memory unconvert(Environment env, TraceInfo trace, Object arg) throws Throwable {
throw new CriticalException("Unsupported unconvert");
}
}
public static class ParameterEntity extends php.runtime.reflection.ParameterEntity {
public ParameterEntity(Context context) {
super(context);
}
}
public static class CompileMethod extends CompileFunction {
public CompileMethod() {
super(null);
}
@Override
public CompileMethod.Method addMethod(java.lang.reflect.Method method) {
return (Method) super.addMethod(method);
}
@Override
public CompileMethod.Method addMethod(java.lang.reflect.Method method, boolean asImmutable) {
return (Method) super.addMethod(method, asImmutable);
}
@Override
public CompileMethod.Method find(int paramCount) {
return (Method) super.find(paramCount);
}
@Override
protected CompileFunction.Method createMethod(java.lang.reflect.Method method, int count, boolean asImmutable) {
return new Method(method, count, asImmutable);
}
public class Method extends CompileFunction.Method {
protected MemoryOperation[] argumentOperations;
protected MemoryOperation returnOperation;
protected CallbackW<MemoryOperation, Class<?>, Type> unknownTypeFetcher;
protected ParameterEntity[] parameters;
public Method(java.lang.reflect.Method method, int argsCount, boolean _asImmutable) {
super(method, argsCount, _asImmutable);
}
public void setUnknownTypeFetcher(CallbackW<MemoryOperation, Class<?>, Type> unknownTypeFetcher) {
this.unknownTypeFetcher = unknownTypeFetcher;
}
public void setParameters(ParameterEntity[] parameters) {
this.parameters = parameters;
returnOperation = MemoryOperation.get(resultType, method.getGenericReturnType());
if (returnOperation == null && unknownTypeFetcher != null) {
returnOperation = unknownTypeFetcher.call(resultType, null);
}
if (returnOperation == null) {
throw new CriticalException("Unsupported type for binding - " + resultType + " in " + method.getDeclaringClass().getName() + "." + method.getName());
}
argumentOperations = new MemoryOperation[parameterTypes.length];
MemoryOperation op;
for (int i = 0; i < argumentOperations.length; i++) {
Class<?> parameterType = parameterTypes[i];
Type genericTypes = method.getGenericParameterTypes()[i];
op = MemoryOperation.get(parameterType, genericTypes);
argumentOperations[i] = op;
if (op != null) {
if (i <= parameters.length - 1) {
op.applyTypeHinting(parameters[i]);
}
} else {
if (parameterType == Environment.class) {
argumentOperations[i] = new InjectMemoryOperation() {
@Override
public Object convert(Environment env, TraceInfo trace, Memory arg) throws Throwable {
return env;
}
};
} else if (parameterType == TraceInfo.class) {
argumentOperations[i] = new InjectMemoryOperation() {
@Override
public Object convert(Environment env, TraceInfo trace, Memory arg) throws Throwable {
return trace;
}
};
} else {
if (unknownTypeFetcher != null) {
op = unknownTypeFetcher.call(parameterType, genericTypes);
argumentOperations[i] = op;
if (op != null) {
if (i <= parameters.length - 1) {
op.applyTypeHinting(parameters[i]);
}
continue;
}
}
throw new CriticalException("Unsupported type for binding - " + parameterType);
}
}
}
}
}
}
}