package php.runtime.wrap;
import php.runtime.Memory;
import php.runtime.annotation.Reflection;
import php.runtime.common.HintType;
import php.runtime.env.CompileScope;
import php.runtime.env.Environment;
import php.runtime.exceptions.CriticalException;
import php.runtime.ext.support.Extension;
import php.runtime.lang.BaseWrapper;
import php.runtime.lang.IObject;
import php.runtime.memory.support.MemoryOperation;
import php.runtime.memory.support.MemoryUtils;
import php.runtime.reflection.*;
import php.runtime.reflection.support.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Reflection.Signature
public class ClassWrapper {
protected final Extension extension;
protected final CompileScope scope;
protected final Class<?> nativeClass;
public ClassWrapper(CompileScope scope, Class<?> clazz) {
this(null, scope, clazz);
}
public ClassWrapper(Extension extension, CompileScope scope, Class<?> clazz) {
this.extension = extension;
this.scope = scope;
this.nativeClass = clazz;
}
public Extension getExtension() {
return extension;
}
public CompileScope getScope() {
return scope;
}
public Class<?> getNativeClass() {
return nativeClass;
}
protected void onWrapName(ClassEntity classEntity) {
classEntity.setName(ReflectionUtils.getClassName(nativeClass));
classEntity.setInternalName(nativeClass.getName().replace('.', '/'));
}
protected void onWrapConstants(ClassEntity classEntity) {
for(Field field : nativeClass.getDeclaredFields()){
int mod = field.getModifiers();
if (field.isAnnotationPresent(Reflection.Ignore.class))
continue;
if (Modifier.isFinal(mod) && Modifier.isStatic(mod) && !Modifier.isPrivate(mod)){
try {
field.setAccessible(true);
classEntity.addConstant(new ConstantEntity(
field.getName(), MemoryUtils.valueOf(field.get(null)), true
));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
protected void onWrapProperty(ClassEntity classEntity, Reflection.Arg arg) {
PropertyEntity entity = new PropertyEntity(classEntity.getContext());
entity.setClazz(classEntity);
entity.setModifier(arg.modifier());
entity.setName(arg.value());
entity.setStatic(false);
if (arg.optional().exists()){
entity.setDefaultValue(MemoryUtils.valueOf(arg.optional().value(), arg.optional().type()));
} else {
entity.setDefaultValue(null);
}
classEntity.addProperty(entity);
}
protected void onWrapCompileProperty(ClassEntity classEntity, Field field, Reflection.Property property) {
CompilePropertyEntity entity = new CompilePropertyEntity(classEntity.getContext(), field);
entity.setClazz(classEntity);
entity.setHiddenInDebugInfo(property.hiddenInDebugInfo());
if (property.value().isEmpty()) {
entity.setName(field.getName());
} else {
entity.setName(property.value());
}
classEntity.addProperty(entity);
}
protected void onWrapProperties(ClassEntity classEntity) {
Reflection.Signature signature = nativeClass.getAnnotation(Reflection.Signature.class);
if (signature != null){
for(Reflection.Arg arg : signature.value()){
onWrapProperty(classEntity, arg);
}
}
for(Field field : nativeClass.getDeclaredFields()) {
Reflection.Property property = field.getAnnotation(Reflection.Property.class);
if (property != null) {
onWrapCompileProperty(classEntity, field, property);
}
}
Reflection.WrapInterface interfaces = nativeClass.getAnnotation(Reflection.WrapInterface.class);
if (interfaces != null && interfaces.wrapFields() && BaseWrapper.class.isAssignableFrom(nativeClass)) {
Class<?> bindClass = MemoryOperation.getClassOfWrapper(
(Class<? extends php.runtime.lang.BaseWrapper<Object>>) nativeClass
);
for (Class _interface : interfaces.value()) {
for (Field field : _interface.getDeclaredFields()) {
try {
Field _field = bindClass.getDeclaredField(field.getName());
int mods = _field.getModifiers();
if ((/*Modifier.isProtected(mods) || */Modifier.isPublic(mods)) && !Modifier.isStatic(mods)) {
if (interfaces.skipConflicts()
&& MemoryOperation.get(_field.getType(), _field.getGenericType()) == null) {
continue;
}
PropertyEntity entity = new WrapCompilePropertyEntity(classEntity.getContext(), _field);
entity.setName(_field.getName());
classEntity.addProperty(entity);
}
} catch (NoSuchFieldException e) {
throw new CriticalException(e);
}
}
}
}
}
protected void onWrapArgument(ParameterEntity param, Reflection.Arg arg) {
param.setReference(arg.reference());
param.setType(arg.type());
param.setName(arg.value());
param.setNullable(arg.nullable());
if (arg.typeEnum() != Enum.class) {
param.setTypeEnum(arg.typeEnum());
}
param.setDefaultValue(null);
if (arg.optional().exists()
|| !arg.optional().value().isEmpty()
|| (arg.type() != HintType.STRING && !arg.optional().value().isEmpty())){
param.setDefaultValue(MemoryUtils.valueOf(arg.optional().value(), arg.optional().type()));
}
if (!arg.typeClass().isEmpty())
param.setTypeClass(arg.typeClass());
else if (arg.nativeType() != IObject.class) {
param.setTypeClass(ReflectionUtils.getClassName(arg.nativeType()));
}
}
protected PropertyEntity getPropertyOfMethod(MethodEntity entity, String name) {
PropertyEntity propertyEntity;
if (entity.isStatic()) {
throw new CriticalException("Cannot use static methods for Getters and Setters");
} else {
propertyEntity = entity.getClazz().findProperty(name);
}
if (propertyEntity == null || propertyEntity.getClazz() != entity.getClazz()) {
PropertyEntity prototype = propertyEntity;
propertyEntity = new PropertyEntity(entity.getContext());
propertyEntity.setClazz(entity.getClazz());
propertyEntity.setTrace(entity.getTrace());
propertyEntity.setPrototype(prototype);
propertyEntity.setName(name);
propertyEntity.setStatic(entity.isStatic());
entity.getClazz().addProperty(propertyEntity).check(null);
}
return propertyEntity;
}
protected void onWrapGetterSetterProperty(MethodEntity entity, Method method) {
if (method.isAnnotationPresent(Reflection.Getter.class)) {
Reflection.Getter getter = method.getAnnotation(Reflection.Getter.class);
String name = getter.value();
if (name.isEmpty()) {
name = method.getName();
if (name.startsWith("__get")) {
name = name.substring(5);
} else if (name.startsWith("get")) {
name = name.substring(3);
}
name = name.substring(0, 1).toLowerCase() + name.substring(1);
}
PropertyEntity propertyEntity = getPropertyOfMethod(
entity, name
);
method.setAccessible(true);
propertyEntity.setGetter(entity);
propertyEntity.setHiddenInDebugInfo(getter.hiddenInDebugInfo());
if (entity.isPublic()) {
entity.setModifier(php.runtime.common.Modifier.PROTECTED);
}
}
if (method.isAnnotationPresent(Reflection.Setter.class)) {
String name = method.getAnnotation(Reflection.Setter.class).value();
if (name.isEmpty()) {
name = method.getName();
if (name.startsWith("__set")) {
name = name.substring(5);
} else if (name.startsWith("set")) {
name = name.substring(3);
}
name = name.substring(0, 1).toLowerCase() + name.substring(1);
}
PropertyEntity propertyEntity = getPropertyOfMethod(
entity, name
);
method.setAccessible(true);
propertyEntity.setSetter(entity);
if (entity.isPublic()) {
entity.setModifier(php.runtime.common.Modifier.PROTECTED);
}
}
}
protected Reflection.Signature getSignature(Method method) {
Reflection.Signature signature = method.getAnnotation(Reflection.Signature.class);
if (signature == null) {
try {
Class<?> parent = method.getDeclaringClass().getSuperclass();
if (parent != null) {
method = parent.getDeclaredMethod(method.getName(), method.getParameterTypes());
if (method != null) {
signature = getSignature(method);
if (signature != null) {
return signature;
}
}
}
} catch (NoSuchMethodException e) {
return null;
}
}
return signature;
}
protected void onWrapMethod(ClassEntity classEntity, Method method) {
MethodEntity entity = new MethodEntity(extension, method);
entity.setClazz(classEntity);
entity.setNativeMethod(method);
entity.setAbstract(Modifier.isAbstract(method.getModifiers()));
entity.setFinal(false);
// entity.setFinal(Modifier.isFinal(method.getModifiers())); Disable native final.
entity.setStatic(Modifier.isStatic(method.getModifiers()));
if (classEntity.isInterface()){
entity.setAbstractable(true);
entity.setAbstract(false);
}
if (entity.isAbstract())
entity.setAbstractable(true);
if (method.isAnnotationPresent(Reflection.Final.class)) {
entity.setFinal(true);
}
if (method.isAnnotationPresent(Reflection.Abstract.class)) {
entity.setAbstract(true);
entity.setAbstractable(true);
}
entity.setInternalName(method.getName());
Reflection.Name name = method.getAnnotation(Reflection.Name.class);
entity.setName(name == null ? method.getName() : name.value());
Reflection.Signature sign = getSignature(method);
/*if (sign == null) {
sign = getClass().getAnnotation(Reflection.Signature.class);
}*/
ParameterEntity[] params = new ParameterEntity[sign.value().length];
int i = 0;
for (Reflection.Arg arg : sign.value()){
ParameterEntity param = new ParameterEntity(classEntity.getContext());
onWrapArgument(param, arg);
params[i++] = param;
}
entity.setParameters(params);
classEntity.addMethod(entity, null);
onWrapGetterSetterProperty(entity, method);
}
protected MethodEntity onWrapWrapCompileMethod(ClassEntity classEntity, Method method, Method interfaceMethod, boolean skipConflicts) {
MethodEntity _entity = classEntity.findMethod(method.getName().toLowerCase());
WrapCompileMethodEntity entity;
if (_entity instanceof WrapCompileMethodEntity) {
entity = (WrapCompileMethodEntity) _entity;
} else {
entity = new WrapCompileMethodEntity(classEntity.getExtension());
}
entity.addMethod(method, skipConflicts);
if (_entity == null) {
entity.setClazz(classEntity);
classEntity.addMethod(entity, null);
}
onWrapGetterSetterProperty(entity, interfaceMethod);
return entity;
}
protected void onWrapCompileMethod(ClassEntity classEntity, Method method) {
String name = method.getName();
MethodEntity _entity = classEntity.findMethod(name.toLowerCase());
CompileMethodEntity entity;
if (_entity instanceof CompileMethodEntity) {
entity = (CompileMethodEntity) _entity;
} else {
entity = new CompileMethodEntity(classEntity.getExtension());
}
if (classEntity.isInterface()){
entity.setAbstractable(true);
entity.setAbstract(false);
}
entity.addMethod(method, false);
if (_entity == null) {
entity.setClazz(classEntity);
classEntity.addMethod(entity, null);
}
onWrapGetterSetterProperty(entity, method);
}
protected void onWrapMethods(ClassEntity classEntity) {
Reflection.WrapInterface interfaces = nativeClass.getAnnotation(Reflection.WrapInterface.class);
Set<Class<?>> wrapInterfaces = new HashSet<Class<?>>();
if (interfaces != null) {
wrapInterfaces.addAll(Arrays.asList(interfaces.value()));
}
for (Class<?> cls : nativeClass.getDeclaredClasses()) {
if (cls.getSimpleName().equals("WrappedInterface") && cls.getDeclaringClass() == nativeClass) {
if (!cls.isInterface()) {
throw new CriticalException("WrappedInterface class must be interface");
}
if (!BaseWrapper.class.isAssignableFrom(nativeClass)) {
throw new CriticalException("To use WrappedInterface, your class must be inheritances from the BaseWrapper class");
}
wrapInterfaces.add(cls);
break;
}
}
if (!wrapInterfaces.isEmpty() && BaseWrapper.class.isAssignableFrom(nativeClass)) {
Class<?> bindClass = MemoryOperation.getClassOfWrapper(
(Class<? extends php.runtime.lang.BaseWrapper<Object>>) nativeClass
);
for (Class _interface : wrapInterfaces) {
for (Method method : _interface.getDeclaredMethods()) {
try {
if (method.isAnnotationPresent(Reflection.Property.class)) {
String name = method.getName();
MethodEntity getterEntity;
try {
getterEntity = onWrapWrapCompileMethod(
classEntity, bindClass.getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1)), method,
false
);
} catch (NoSuchMethodException e) {
if (method.getReturnType() == Boolean.TYPE || method.getReturnType() == Boolean.class) {
getterEntity = onWrapWrapCompileMethod(
classEntity, bindClass.getMethod("is" + name.substring(0, 1).toUpperCase() + name.substring(1)), method,
false
);
} else {
throw e;
}
}
MethodEntity setterEntity = null;
try {
setterEntity = onWrapWrapCompileMethod(
classEntity, bindClass.getMethod("set" + name.substring(0, 1).toUpperCase() + name.substring(1), method.getReturnType()), method,
false
);
} catch (NoSuchMethodException e) {
// nop
}
String propertyName = method.getAnnotation(Reflection.Property.class).value();
if (propertyName.isEmpty()) {
propertyName = name;
}
PropertyEntity propertyEntity = getPropertyOfMethod(getterEntity, propertyName);
propertyEntity.setGetter(getterEntity);
propertyEntity.setSetter(setterEntity);
if (setterEntity != null && method.isAnnotationPresent(Reflection.Nullable.class)) {
ParameterEntity setterArg = setterEntity.getParameters(1)[0];
setterArg.setNullable(true);
}
if (_interface.isInterface()) {
getterEntity.setAbstractable(false);
getterEntity.setAbstract(false);
if (getterEntity.isPublic()) {
getterEntity.setModifier(php.runtime.common.Modifier.PROTECTED);
}
if (setterEntity != null) {
setterEntity.setAbstractable(false);
setterEntity.setAbstract(false);
if (setterEntity.isPublic()) {
setterEntity.setModifier(php.runtime.common.Modifier.PROTECTED);
}
}
}
classEntity.addProperty(propertyEntity).check(null);
} else {
MethodEntity entity = onWrapWrapCompileMethod(
classEntity, bindClass.getDeclaredMethod(method.getName(), method.getParameterTypes()), method,
interfaces != null && interfaces.skipConflicts()
);
if (_interface.isInterface()) {
entity.setAbstractable(false);
entity.setAbstract(false);
}
}
} catch (NoSuchMethodException e) {
boolean _throw = true;
if (interfaces != null && interfaces.skipConflicts()) {
for (Class<?> aClass : interfaces.value()) {
if (aClass == _interface) {
_throw = false;
}
}
}
if (_throw) {
throw new CriticalException(e);
}
}
}
}
}
for (Method method : nativeClass.getDeclaredMethods()) {
Reflection.Signature signature = getSignature(method);
if (signature != null
|| method.isAnnotationPresent(Reflection.Getter.class)
|| method.isAnnotationPresent(Reflection.Setter.class)){
Class<?>[] types = method.getParameterTypes();
if (method.getReturnType() == Memory.class
&& types.length == 2 && types[0] == Environment.class && types[1] == Memory[].class) {
onWrapMethod(classEntity, method);
} else {
onWrapCompileMethod(classEntity, method);
}
}
}
}
protected void onWrapExtend(ClassEntity classEntity) {
if (nativeClass.isAnnotationPresent(Reflection.BaseType.class)) {
return;
}
Class<?> extend = nativeClass.getSuperclass();
if (extend != null && !extend.isAnnotationPresent(Reflection.Ignore.class)){
String name = ReflectionUtils.getClassName(extend);
ClassEntity entity = scope.fetchUserClass(name);
if (extend.isAssignableFrom(IObject.class) && entity == null)
throw new IllegalArgumentException("Class '"+name+"' not registered for '" + classEntity.getName() + "'");
if (entity == null)
return;
ClassEntity.ExtendsResult result = classEntity.setParent(entity, false);
result.check(null);
classEntity.updateParentMethods();
}
}
protected void onWrapImplement(ClassEntity classEntity) {
for (Class<?> interface_ : nativeClass.getInterfaces()){
if (interface_.isAnnotationPresent(Reflection.Ignore.class)) continue;
if (interface_.getPackage().getName().startsWith("java.")) continue;
String name = ReflectionUtils.getClassName(interface_);
ClassEntity entity = scope.fetchUserClass(name);
/*if (entity == null || !entity.isInterface())
throw new IllegalArgumentException("Interface '"+name+"' not registered");*/
if (entity == null)
return;
ClassEntity.ImplementsResult result = classEntity.addInterface(entity);
result.check(null);
}
}
public void onWrap(ClassEntity classEntity) {
int mod = nativeClass.getModifiers();
classEntity.setInternal(true);
classEntity.setNativeClazz(nativeClass);
classEntity.setExtension(extension);
classEntity.setType(nativeClass.isInterface() ? ClassEntity.Type.INTERFACE : ClassEntity.Type.CLASS);
if (nativeClass.isAnnotationPresent(Reflection.Trait.class)) {
classEntity.setType(ClassEntity.Type.TRAIT);
}
classEntity.setAbstract(Modifier.isAbstract(mod));
classEntity.setFinal(Modifier.isFinal(mod));
classEntity.setNotRuntime(nativeClass.isAnnotationPresent(Reflection.NotRuntime.class));
if (nativeClass.isAnnotationPresent(Reflection.Final.class)) {
classEntity.setFinal(true);
}
if (nativeClass.isAnnotationPresent(Reflection.Abstract.class)) {
classEntity.setAbstract(true);
}
this.onWrapName(classEntity);
if (!classEntity.isTrait()) {
this.onWrapConstants(classEntity);
}
this.onWrapProperties(classEntity);
// ---
classEntity.setNativeClazz(nativeClass);
// ---
this.onWrapMethods(classEntity);
if (this.scope != null)
this.onWrapExtend(classEntity);
this.onWrapImplement(classEntity);
classEntity.doneDeclare();
}
}