package php.runtime.reflection;
import php.runtime.Memory;
import php.runtime.annotation.Reflection;
import php.runtime.common.HintType;
import php.runtime.common.Messages;
import php.runtime.common.Modifier;
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.lang.Closure;
import php.runtime.lang.IObject;
import php.runtime.memory.ObjectMemory;
import php.runtime.reflection.helper.ClosureEntity;
import php.runtime.reflection.support.AbstractFunctionEntity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@Reflection.Signature
public class MethodEntity extends AbstractFunctionEntity {
protected ClassEntity clazz;
protected ClassEntity trait;
protected Extension extension;
protected MethodEntity prototype;
protected Method nativeMethod;
protected boolean isAbstract;
protected boolean isFinal;
protected boolean isStatic;
protected Modifier modifier;
protected boolean dynamicSignature = false;
protected String signature;
protected Closure cachedClosure;
protected boolean contextDepends = false;
public MethodEntity(Context context) {
super(context);
}
public MethodEntity(FunctionEntity entity){
super(entity.getContext());
setParameters(entity.getParameters());
setReturnReference(entity.isReturnReference());
setDeprecated(entity.isDeprecated());
setAbstract(false);
setAbstractable(false);
setModifier(Modifier.PUBLIC);
}
public MethodEntity(MethodEntity entity){
super(entity.getContext());
setParameters(entity.parameters);
setReturnReference(entity.isReturnReference());
setDeprecated(entity.isDeprecated());
setAbstract(false);
setAbstractable(false);
setModifier(Modifier.PUBLIC);
}
public MethodEntity(Extension extension, Method method){
this((Context)null);
this.extension = extension;
this.usesStackTrace = true;
Reflection.Signature signature = method.getAnnotation(Reflection.Signature.class);
if (signature == null) {
signature = getClass().getAnnotation(Reflection.Signature.class);
}
do {
if (signature != null)
break;
try {
Class<?> cls = method.getDeclaringClass().getSuperclass();
if (cls == null)
break;
signature = cls.getMethod(method.getName(), method.getParameterTypes()).getAnnotation(Reflection.Signature.class);
} catch (Exception e) {
throw new CriticalException(e);
}
} while (true);
if (signature == null)
throw new IllegalArgumentException("Method is not annotated with @Reflection.Signature");
Class<?>[] types = method.getParameterTypes();
if (types.length != 2 || types[0] != Environment.class || types[1] != Memory[].class){
throw new IllegalArgumentException(
"Invalid method signature - " + method.toGenericString()
);
}
int modifiers = method.getModifiers();
isFinal = method.isAnnotationPresent(Reflection.Final.class);
isStatic = java.lang.reflect.Modifier.isStatic(modifiers);
isAbstract = java.lang.reflect.Modifier.isAbstract(modifiers);
modifier = Modifier.PUBLIC;
if (java.lang.reflect.Modifier.isProtected(modifiers))
modifier = Modifier.PROTECTED;
else if (java.lang.reflect.Modifier.isPrivate(modifiers))
modifier = Modifier.PRIVATE;
nativeMethod = method;
}
/*
ClassNode classNode = clazz.getClassNode();
for(Object m : classNode.methods) {
MethodNode method = (MethodNode) m;
if (method.name.equals(getInternalName()) ){
return cachedMethodNode = method;
}
}
throw new CriticalException("Cannot find MethodNode for method - " + name + "(" + getSignatureString(true) + ")");
*/
public Closure getClosure(Environment env, final IObject object) {
if (cachedClosure != null)
return cachedClosure;
final MethodEntity bind = this;
final ClosureEntity closureEntity1 = new ClosureEntity(this.getContext());
closureEntity1.setParent(env.scope.fetchUserClass(Closure.class));
closureEntity1.parameters = this.parameters;
closureEntity1.setReturnReference(this.isReturnReference());
MethodEntity m = new MethodEntity(this);
m.setClazz(closureEntity1);
m.setName("__invoke");
closureEntity1.addMethod(m, null);
closureEntity1.doneDeclare();
Closure tmp = new Closure(env, closureEntity1, new ObjectMemory(env.getLateObject()), clazz.getName(), new Memory[0]){
@Override
public Memory __invoke(Environment e, Memory... args) {
try {
if (object == null)
return bind.invokeStatic(e, args);
else
return bind.invokeDynamic(object, e, args);
} catch (RuntimeException e1){
throw e1;
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
@Override
public Memory getOrCreateStatic(String name) {
return Memory.NULL;
}
@Override
public ClassEntity getReflection() {
return closureEntity1;
}
};
try {
m.setNativeMethod(tmp.getClass().getDeclaredMethod("__invoke", Environment.class, Memory[].class));
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return cachedClosure = tmp;
}
public boolean isDynamicSignature() {
return dynamicSignature;
}
public void setDynamicSignature(boolean dynamicSignature) {
this.dynamicSignature = dynamicSignature;
}
public Extension getExtension() {
return extension;
}
public Method getNativeMethod() {
return nativeMethod;
}
public void setNativeMethod(Method nativeMethod) {
this.nativeMethod = nativeMethod;
if (nativeMethod != null) {
nativeMethod.setAccessible(true);
}
}
public Memory invokeDynamic(IObject _this, Environment environment, Memory... arguments) throws Throwable {
try {
if (isAbstract){
environment.error(ErrorType.E_ERROR, "Cannot call abstract method %s", getSignatureString(false));
return Memory.NULL;
}
if (_this == null && !isStatic){
_this = clazz.newMock(environment);
if (_this == null)
environment.error(ErrorType.E_ERROR, Messages.ERR_STATIC_METHOD_CALLED_DYNAMICALLY.fetch(
getClazz().getName() + "::" + getName())
);
}
if (isEmpty) {
return Memory.NULL;
}
return (Memory) nativeMethod.invoke(_this, environment, arguments);
} catch (InvocationTargetException e){
return environment.__throwException(e);
} catch (Throwable e) {
throw e;
} finally {
unsetArguments(arguments);
}
}
public Memory invokeStatic(Environment environment, TraceInfo trace, Memory... arguments) throws Throwable {
return invokeDynamic(null, environment, arguments);
}
final public Memory invokeStatic(Environment environment, Memory... arguments) throws Throwable {
return invokeStatic(environment, TraceInfo.UNKNOWN, arguments);
}
@Override
public void setName(String name) {
super.setName(name);
}
public MethodEntity getPrototype() {
return prototype;
}
public void setPrototype(MethodEntity prototype) {
this.prototype = prototype;
this.contextDepends = prototype != null && (prototype.contextDepends || prototype.isPrivate());
}
public ClassEntity getClazz() {
return clazz;
}
public String getClazzName(){
return clazz.getName();
}
public void setClazz(ClassEntity clazz) {
this.clazz = clazz;
}
public boolean isAbstract() {
return isAbstract;
}
public void setAbstract(boolean anAbstract) {
isAbstract = anAbstract;
}
public Modifier getModifier() {
return modifier;
}
public boolean isContextDepends() {
return contextDepends;
}
public boolean isPublic(){
return modifier == Modifier.PUBLIC;
}
public boolean isProtected(){
return modifier == Modifier.PROTECTED;
}
public boolean isPrivate(){
return modifier == Modifier.PRIVATE;
}
public void setModifier(Modifier modifier) {
this.modifier = modifier;
}
public boolean isFinal() {
return isFinal;
}
public void setFinal(boolean aFinal) {
isFinal = aFinal;
}
public boolean isStatic() {
return isStatic;
}
public void setStatic(boolean aStatic) {
isStatic = aStatic;
}
@Override
public boolean isNamespace(){
return false;
}
public boolean isDeprecated(){
return false; // TODO
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MethodEntity)) return false;
if (!super.equals(o)) return false;
MethodEntity that = (MethodEntity) o;
return hashCode() == that.hashCode();
}
/*@Override
public int hashCode() {
return hashCode(clazz.getLowerName(), lowerName);
}*/
public static int hashCode(String classLowerName, String methodLowerName){
return classLowerName.hashCode() + methodLowerName.hashCode();
}
public String getSignatureString(boolean withArgs){
String ownerName = getClazz().getName();
if (getTrait() != null)
ownerName = getTrait().getName();
StringBuilder sb = new StringBuilder(ownerName + "::" + getName() + "(");
int i = 0;
if (parameters != null && withArgs)
for(ParameterEntity param : parameters){
sb.append(param.getSignatureString());
if (i != parameters.length - 1)
sb.append(", ");
i++;
}
sb.append(")");
return sb.toString();
}
public String getSignature() {
if (signature != null)
return signature;
StringBuilder sb = new StringBuilder();
int i = 0;
if (parameters != null)
for(ParameterEntity param : parameters){
if (param.getDefaultValue() == null)
sb.append(
param.getType() == null ? HintType.ANY : param.getType()
).append("|").append(param.isReference ? "&" : "");
}
return signature = sb.toString();
}
public boolean equalsBySignature(MethodEntity method, boolean strong){
if (strong)
return getSignature().equals(method.getSignature());
else {
int cnt1 = parameters != null ? parameters.length : 0;
int cnt2 = method.parameters != null ? method.parameters.length : 0;
return cnt1 == cnt2;
}
}
public boolean equalsBySignature(MethodEntity method){
return equalsBySignature(method, true);
}
public boolean equalsByHintTypingSignature(MethodEntity method){
if (parameters == null || method.parameters == null)
return true;
int i = 0;
for(ParameterEntity param : parameters){
if (i >= method.parameters.length)
break;
ParameterEntity other = method.parameters[i];
if (param.getTypeClass() != null){
if (other.getTypeClass() == null || !other.getTypeClassLower().equals(param.getTypeClassLower()))
return false;
} else if (param.getType() != HintType.ANY){
if (other.getType() != param.getType())
return false;
}
i++;
}
return true;
}
public boolean isOwned(ClassEntity entity){
return clazz.getId() == entity.getId();
}
public int canAccess(Environment env) {
return canAccess(env, null);
}
/**
* 0 - success
* 1 - invalid protected
* 2 - invalid private
* @param env
* @return
*/
public int canAccess(Environment env, ClassEntity context) {
switch (modifier){
case PUBLIC: return 0;
case PRIVATE:
ClassEntity cl = context == null ? env.getLastClassOnStack() : context;
if (cl == null)
return 2;
if (cl.getId() == this.clazz.getId())
return 0;
MethodEntity tmp = prototype;
while (tmp != null){
if (cl.getId() == tmp.clazz.getId())
return 0;
tmp = tmp.prototype;
}
return 2;
case PROTECTED:
ClassEntity originClass;
ClassEntity clazz = originClass = context == null ? env.getLateStaticClass() : context;
if (clazz == null)
return 1;
long id = this.clazz.getId();
do {
if (clazz.getId() == id)
return 0;
clazz = clazz.parent;
} while (clazz != null);
if (this.clazz.isInstanceOf(originClass)) {
return 0;
}
return 1;
}
return 2;
}
public ClassEntity getTrait() {
return trait;
}
public void setTrait(ClassEntity trait) {
this.trait = trait;
}
public int getRequiredParamCount() {
int cnt = 0;
if (parameters != null)
for(ParameterEntity e : parameters) {
if (e.getDefaultValue() != null)
break;
}
return cnt;
}
public MethodEntity duplicateForInject() {
MethodEntity methodEntity = new MethodEntity(this.context);
methodEntity.setExtension(getExtension());
methodEntity.setAbstract(isAbstract);
methodEntity.setFinal(isFinal);
methodEntity.setDynamicSignature(isDynamicSignature());
methodEntity.setModifier(modifier);
methodEntity.setName(name);
methodEntity.setStatic(isStatic);
methodEntity.setAbstractable(isAbstractable());
methodEntity.setDocComment(getDocComment());
methodEntity.setParameters(parameters);
methodEntity.setReturnReference(isReturnReference());
methodEntity.setEmpty(isEmpty);
methodEntity.setImmutable(isImmutable);
methodEntity.setResult(result);
methodEntity.setUsesStackTrace(isUsesStackTrace());
methodEntity.setDeprecated(isDeprecated());
methodEntity.setInternalName(getInternalName());
if (trace == null || trace == TraceInfo.UNKNOWN)
methodEntity.setTrace(getClazz().getTrace());
else
methodEntity.setTrace(trace);
return methodEntity;
}
}