package org.develnext.jphp.core.compiler.jvm.statement;
import org.develnext.jphp.core.compiler.jvm.Constants;
import org.develnext.jphp.core.compiler.jvm.JPHPClassWriter;
import org.develnext.jphp.core.compiler.jvm.JvmCompiler;
import org.develnext.jphp.core.compiler.jvm.misc.LocalVariable;
import org.develnext.jphp.core.compiler.jvm.node.ClassNodeImpl;
import org.develnext.jphp.core.compiler.jvm.node.MethodNodeImpl;
import org.develnext.jphp.core.tokenizer.token.Token;
import org.develnext.jphp.core.tokenizer.token.expr.ValueExprToken;
import org.develnext.jphp.core.tokenizer.token.expr.value.FulledNameToken;
import org.develnext.jphp.core.tokenizer.token.expr.value.NameToken;
import org.develnext.jphp.core.tokenizer.token.expr.value.SelfExprToken;
import org.develnext.jphp.core.tokenizer.token.expr.value.StaticAccessExprToken;
import org.develnext.jphp.core.tokenizer.token.stmt.ClassStmtToken;
import org.develnext.jphp.core.tokenizer.token.stmt.ClassVarStmtToken;
import org.develnext.jphp.core.tokenizer.token.stmt.ConstStmtToken;
import org.develnext.jphp.core.tokenizer.token.stmt.MethodStmtToken;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import php.runtime.Memory;
import php.runtime.common.Function;
import php.runtime.common.Messages;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.exceptions.CriticalException;
import php.runtime.exceptions.FatalException;
import php.runtime.exceptions.support.ErrorType;
import php.runtime.invoke.cache.ConstantCallCache;
import php.runtime.invoke.cache.FunctionCallCache;
import php.runtime.invoke.cache.MethodCallCache;
import php.runtime.invoke.cache.PropertyCallCache;
import php.runtime.lang.BaseObject;
import php.runtime.reflection.*;
import php.runtime.reflection.helper.GeneratorEntity;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.*;
import static org.objectweb.asm.Opcodes.*;
public class ClassStmtCompiler extends StmtCompiler<ClassEntity> {
protected JPHPClassWriter cw;
public final ClassNode node;
public final ClassStmtToken statement;
public final List<TraceInfo> traceList = new ArrayList<TraceInfo>();
public final List<Memory> memoryConstants = new ArrayList<Memory>();
public final List<Collection<Memory>> memoryArrayConstants = new ArrayList<Collection<Memory>>();
private boolean external = false;
private boolean isSystem = false;
private boolean isInterfaceCheck = true;
private String functionName = "";
private boolean initDynamicExists = false;
private int callFuncCount = 0;
private int callMethCount = 0;
private int callConstCount = 0;
private int callPropCount = 0;
private GeneratorEntity generatorEntity;
protected List<ConstStmtToken.Item> dynamicConstants = new ArrayList<ConstStmtToken.Item>();
protected List<ClassVarStmtToken> dynamicProperties = new ArrayList<ClassVarStmtToken>();
private ClassStmtToken classContext;
public ClassStmtCompiler(JvmCompiler compiler, ClassStmtToken statement) {
super(compiler);
this.statement = statement;
this.node = new ClassNodeImpl();
}
public int getAndIncCallFuncCount() {
return callFuncCount++;
}
public int getAndIncCallMethCount() {
return callMethCount++;
}
public int getAndIncCallConstCount() {
return callConstCount++;
}
public int getAndIncCallPropCount() {
return callPropCount++;
}
public boolean isInitDynamicExists() {
return initDynamicExists;
}
public boolean isClosure() {
return functionName == null;
}
public String getFunctionName() {
return functionName;
}
public void setFunctionName(String functionName) {
this.functionName = functionName;
}
public GeneratorEntity getGeneratorEntity() {
return generatorEntity;
}
public void setGeneratorEntity(GeneratorEntity generatorEntity) {
this.generatorEntity = generatorEntity;
}
public boolean isSystem() {
return isSystem;
}
public void setSystem(boolean system) {
isSystem = system;
}
public void setInterfaceCheck(boolean check) {
isInterfaceCheck = check;
}
public boolean isExternal() {
return external;
}
public void setExternal(boolean external) {
this.external = external;
}
TraceInfo makeTraceInfo(int line, int position) {
return new TraceInfo(compiler.getContext(), line, 0, position, 0);
}
TraceInfo makeTraceInfo(Token token) {
return token.toTraceInfo(compiler.getContext());
}
int addTraceInfo(int line, int position) {
traceList.add(makeTraceInfo(line, position));
return traceList.size() - 1;
}
int addTraceInfo(Token token) {
traceList.add(makeTraceInfo(token));
return traceList.size() - 1;
}
int addMemoryConstant(Memory memory) {
memoryConstants.add(memory);
return memoryConstants.size() - 1;
}
int addMemoryArray(Collection<Memory> memories) {
memoryArrayConstants.add(memories);
return memoryArrayConstants.size() - 1;
}
@SuppressWarnings("unchecked")
protected void writeDestructor() {
if (entity.methodDestruct != null) {
MethodNode destructor = new MethodNodeImpl();
destructor.name = "finalize";
destructor.access = ACC_PUBLIC;
destructor.desc = Type.getMethodDescriptor(Type.getType(void.class));
MethodStmtCompiler methodCompiler = new MethodStmtCompiler(this, destructor);
ExpressionStmtCompiler expressionCompiler = new ExpressionStmtCompiler(methodCompiler, null);
methodCompiler.writeHeader();
LabelNode end = new LabelNode();
LabelNode l0 = writeLabel(destructor, statement.getMeta().getStartLine());
methodCompiler.addLocalVariable("~this", l0);
expressionCompiler.writeVarLoad("~this");
expressionCompiler.writeSysDynamicCall(null, "isFinalized", Boolean.TYPE);
destructor.instructions.add(new JumpInsnNode(IFEQ, end));
// --- if (!__finalized__) {
expressionCompiler.writeVarLoad("~this");
expressionCompiler.writePushDup();
expressionCompiler.writeSysDynamicCall(null, "doFinalize", void.class);
expressionCompiler.writePushEnvFromSelf();
expressionCompiler.writePushConstNull();
expressionCompiler.writeSysDynamicCall(
null, entity.methodDestruct.getInternalName(), Memory.class, Environment.class, Memory[].class
);
expressionCompiler.writePopAll(1);
// ---- }
destructor.instructions.add(end);
// call parent
// WARNING!!! It's commented for better performance, super.finalize empty in JDK 1.6, 1.7, 1.8
/*expressionCompiler.writeVarLoad("~this");
destructor.instructions.add(new MethodInsnNode(
INVOKEVIRTUAL,
Type.getInternalName(Object.class),
destructor.name,
destructor.desc
));*/
destructor.instructions.add(new InsnNode(Opcodes.RETURN));
methodCompiler.writeFooter();
node.methods.add(destructor);
}
}
protected void writeDefaultConstructors()
{
if (!isSystem && !isClosure() && entity.getParent() != null
&& entity.getParent().getNativeClass() != null
&& !BaseObject.class.isAssignableFrom(entity.getParent().getNativeClass())) {
for (Constructor el : entity.getParent().getNativeClass().getConstructors()) {
Class<?>[] parameterTypes = el.getParameterTypes();
if (parameterTypes.length == 2
&& parameterTypes[0] == Environment.class && parameterTypes[1] == ClassEntity.class) {
continue;
}
MethodNode constructor = new MethodNodeImpl();
constructor.name = Constants.INIT_METHOD;
constructor.access = el.getModifiers();
constructor.exceptions = new ArrayList();
MethodStmtCompiler methodCompiler = new MethodStmtCompiler(this, constructor);
ExpressionStmtCompiler expressionCompiler = new ExpressionStmtCompiler(methodCompiler, null);
LabelNode l0 = writeLabel(constructor, statement.getMeta().getStartLine());
methodCompiler.addLocalVariable("~this", l0);
Type[] argumentTypes = new Type[parameterTypes.length];
int i = 0;
for (Class type : parameterTypes) {
argumentTypes[i++] = Type.getType(type);
methodCompiler.addLocalVariable("arg" + i, l0, type);
}
constructor.desc = Type.getMethodDescriptor(
Type.getType(void.class), argumentTypes
);
methodCompiler.writeHeader();
expressionCompiler.writeVarLoad("~this");
for (i = 0; i < argumentTypes.length; i++) {
expressionCompiler.writeVarLoad("arg" + (i + 1));
}
constructor.instructions.add(new MethodInsnNode(
INVOKESPECIAL,
node.superName,
Constants.INIT_METHOD,
constructor.desc,
false
));
methodCompiler.writeFooter();
constructor.instructions.add(new InsnNode(RETURN));
node.methods.add(constructor);
}
}
}
@SuppressWarnings("unchecked")
protected void writeConstructor() {
MethodNode constructor = new MethodNodeImpl();
constructor.name = Constants.INIT_METHOD;
constructor.access = ACC_PUBLIC;
constructor.exceptions = new ArrayList();
MethodStmtCompiler methodCompiler = new MethodStmtCompiler(this, constructor);
ExpressionStmtCompiler expressionCompiler = new ExpressionStmtCompiler(methodCompiler, null);
methodCompiler.writeHeader();
LabelNode l0 = writeLabel(constructor, statement.getMeta().getStartLine());
methodCompiler.addLocalVariable("~this", l0);
if (isClosure() || generatorEntity != null) {
constructor.desc = Type.getMethodDescriptor(
Type.getType(void.class),
Type.getType(Environment.class),
Type.getType(ClassEntity.class),
Type.getType(Memory.class),
Type.getType(Memory[].class)
);
if (isClosure()) {
constructor.desc = Type.getMethodDescriptor(
Type.getType(void.class),
Type.getType(Environment.class),
Type.getType(ClassEntity.class),
Type.getType(Memory.class),
Type.getType(String.class),
Type.getType(Memory[].class)
);
}
methodCompiler.addLocalVariable("~env", l0, Environment.class);
methodCompiler.addLocalVariable("~class", l0, ClassEntity.class);
methodCompiler.addLocalVariable("~self", l0, Memory.class);
if (isClosure()) {
methodCompiler.addLocalVariable("~context", l0, String.class);
}
methodCompiler.addLocalVariable("~uses", l0, Memory[].class);
methodCompiler.writeHeader();
expressionCompiler.writeVarLoad("~this");
expressionCompiler.writeVarLoad("~env");
expressionCompiler.writeVarLoad("~class");
expressionCompiler.writeVarLoad("~self");
if (isClosure()) {
expressionCompiler.writeVarLoad("~context");
}
expressionCompiler.writeVarLoad("~uses");
constructor.instructions.add(new MethodInsnNode(
INVOKESPECIAL,
node.superName,
Constants.INIT_METHOD,
constructor.desc,
false
));
} else {
constructor.desc = Type.getMethodDescriptor(
Type.getType(void.class), Type.getType(Environment.class), Type.getType(ClassEntity.class)
);
methodCompiler.addLocalVariable("~env", l0, Environment.class);
methodCompiler.addLocalVariable("~class", l0, String.class);
expressionCompiler.writeVarLoad("~this");
expressionCompiler.writeVarLoad("~env");
expressionCompiler.writeVarLoad("~class");
constructor.instructions.add(new MethodInsnNode(
INVOKESPECIAL,
node.superName,
Constants.INIT_METHOD,
constructor.desc,
false
));
// PROPERTIES
for (ClassVarStmtToken property : statement.getProperties()) {
ExpressionStmtCompiler expressionStmtCompiler = new ExpressionStmtCompiler(methodCompiler, null);
Memory value = Memory.NULL;
if (property.getValue() != null)
value = expressionStmtCompiler.writeExpression(property.getValue(), true, true, false);
PropertyEntity prop = new PropertyEntity(compiler.getContext());
prop.setName(property.getVariable().getName());
prop.setModifier(property.getModifier());
prop.setStatic(property.isStatic());
prop.setDefaultValue(value);
prop.setDefault(property.getValue() != null);
prop.setTrace(property.toTraceInfo(compiler.getContext()));
if (property.getDocComment() != null) {
prop.setDocComment(new DocumentComment(property.getDocComment().getComment()));
}
ClassEntity.PropertyResult result = entity.addProperty(prop);
result.check(compiler.getEnvironment());
if (value == null && property.getValue() != null) {
if (property.getValue().isSingle() && ValueExprToken.isConstable(property.getValue().getSingle(), true))
dynamicProperties.add(property);
else
compiler.getEnvironment().error(
property.getVariable().toTraceInfo(compiler.getContext()),
ErrorType.E_COMPILE_ERROR,
Messages.ERR_EXPECTED_CONST_VALUE.fetch(entity.getName() + "::$" + property.getVariable().getName())
);
}
}
}
methodCompiler.writeFooter();
constructor.instructions.add(new InsnNode(RETURN));
node.methods.add(constructor);
}
protected void writeConstant(ConstStmtToken constant) {
MethodStmtCompiler methodStmtCompiler = new MethodStmtCompiler(this, (MethodStmtToken) null);
ExpressionStmtCompiler expressionStmtCompiler = new ExpressionStmtCompiler(methodStmtCompiler, null);
DocumentComment documentComment = null;
if (constant.getDocComment() != null)
documentComment = new DocumentComment(constant.getDocComment().getComment());
for (ConstStmtToken.Item el : constant.items) {
Memory value = expressionStmtCompiler.writeExpression(el.value, true, true, false);
ConstantEntity constantEntity = new ConstantEntity(el.getFulledName(), value, true);
constantEntity.setTrace(el.name.toTraceInfo(compiler.getContext()));
constantEntity.setDocComment(documentComment);
if (value != null && !value.isArray()) {
ConstantEntity c = entity.findConstant(el.getFulledName());
if (c != null && c.getClazz().getId() == entity.getId()) {
compiler.getEnvironment().error(
constant.toTraceInfo(compiler.getContext()),
ErrorType.E_ERROR,
Messages.ERR_CANNOT_REDEFINE_CLASS_CONSTANT,
entity.getName() + "::" + el.getFulledName()
);
return;
}
entity.addConstant(constantEntity);
} else {
if (ValueExprToken.isConstable(el.value.getSingle(), false)) {
dynamicConstants.add(el);
entity.addConstant(constantEntity);
} else
compiler.getEnvironment().error(
constant.toTraceInfo(compiler.getContext()),
Messages.ERR_EXPECTED_CONST_VALUE.fetch(entity.getName() + "::" + el.getFulledName())
);
}
}
}
@SuppressWarnings("unchecked")
protected void writeSystemInfo() {
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "$FN",
Type.getDescriptor(String.class),
null,
compiler.getSourceFile()
));
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_STATIC, "$TRC",
Type.getDescriptor(TraceInfo[].class),
null,
null
));
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_STATIC, "$MEM",
Type.getDescriptor(Memory[].class),
null,
null
));
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_STATIC, "$AMEM",
Type.getDescriptor(Memory[][].class),
null,
null
));
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_STATIC, "$CALL_FUNC_CACHE",
Type.getDescriptor(FunctionCallCache.class),
null,
null
));
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_STATIC, "$CALL_METH_CACHE",
Type.getDescriptor(MethodCallCache.class),
null,
null
));
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_STATIC, "$CALL_PROP_CACHE",
Type.getDescriptor(PropertyCallCache.class),
null,
null
));
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_STATIC, "$CALL_CONST_CACHE",
Type.getDescriptor(ConstantCallCache.class),
null,
null
));
if (functionName != null) {
node.fields.add(new FieldNode(
ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "$CL",
Type.getDescriptor(String.class),
null,
!functionName.isEmpty() ? functionName : entity.getName()
));
}
}
@SuppressWarnings("unchecked")
protected void writeInitEnvironment() {
if (!dynamicConstants.isEmpty() || !dynamicProperties.isEmpty()) {
initDynamicExists = true;
MethodNode node = new MethodNodeImpl();
node.access = ACC_STATIC + ACC_PUBLIC;
node.name = "__$initEnvironment";
node.desc = Type.getMethodDescriptor(
Type.getType(void.class), Type.getType(Environment.class)
);
if (entity.isTrait()) {
node.desc = Type.getMethodDescriptor(
Type.getType(void.class), Type.getType(Environment.class), Type.getType(String.class)
);
}
MethodStmtCompiler methodCompiler = new MethodStmtCompiler(this, node);
ExpressionStmtCompiler expressionCompiler = new ExpressionStmtCompiler(methodCompiler, null);
methodCompiler.writeHeader();
LabelNode l0 = expressionCompiler.makeLabel();
methodCompiler.addLocalVariable("~env", l0, Environment.class);
if (entity.isTrait())
methodCompiler.addLocalVariable("~class_name", l0, String.class);
LocalVariable l_class = methodCompiler.addLocalVariable("~class", l0, ClassEntity.class);
expressionCompiler.writePushEnv();
if (entity.isTrait()) {
expressionCompiler.writeVarLoad("~class_name");
expressionCompiler.writePushDupLowerCase();
} else {
expressionCompiler.writePushConstString(entity.getName());
expressionCompiler.writePushConstString(entity.getLowerName());
}
expressionCompiler.writePushConstBoolean(true);
expressionCompiler.writeSysDynamicCall(
Environment.class, "fetchClass", ClassEntity.class, String.class, String.class, Boolean.TYPE
);
expressionCompiler.writeVarStore(l_class, false, false);
// corrects defination of constants
final List<ConstStmtToken.Item> first = new ArrayList<ConstStmtToken.Item>();
final Set<String> usedNames = new HashSet<String>();
final List<ConstStmtToken.Item> other = new ArrayList<ConstStmtToken.Item>();
for (ConstStmtToken.Item el : dynamicConstants) {
Token tk = el.value.getSingle();
if (tk instanceof StaticAccessExprToken) {
StaticAccessExprToken access = (StaticAccessExprToken) tk;
boolean self = false;
if (access.getClazz() instanceof SelfExprToken)
self = true;
else if (access.getClazz() instanceof FulledNameToken
&& ((FulledNameToken) access.getClazz()).getName().equalsIgnoreCase(entity.getName())) {
self = true;
}
if (self) {
String name = ((NameToken) access.getField()).getName();
if (usedNames.contains(el.getFulledName()))
first.add(0, el);
else
first.add(el);
usedNames.add(name);
continue;
}
}
if (usedNames.contains(el.getFulledName()))
first.add(0, el);
else
other.add(el);
}
other.addAll(0, first);
for (ConstStmtToken.Item el : other) {
expressionCompiler.writeVarLoad(l_class);
expressionCompiler.writePushEnv();
expressionCompiler.writePushConstString(el.getFulledName());
expressionCompiler.writeExpression(el.value, true, false, true);
expressionCompiler.writePopBoxing(true);
expressionCompiler.writeSysDynamicCall(
ClassEntity.class, "addDynamicConstant", void.class,
Environment.class, String.class, Memory.class
);
}
for (ClassVarStmtToken el : dynamicProperties) {
expressionCompiler.writeVarLoad(l_class);
expressionCompiler.writePushEnv();
expressionCompiler.writePushConstString(el.getVariable().getName());
expressionCompiler.writeExpression(el.getValue(), true, false, true);
expressionCompiler.writePopBoxing(true);
expressionCompiler.writeSysDynamicCall(
ClassEntity.class,
el.isStatic() ? "addDynamicStaticProperty" : "addDynamicProperty",
void.class,
Environment.class, String.class, Memory.class
);
}
node.instructions.add(new InsnNode(RETURN));
methodCompiler.writeFooter();
this.node.methods.add(node);
}
}
@SuppressWarnings("unchecked")
protected void writeInitStatic() {
MethodNode node = new MethodNodeImpl();
node.access = ACC_STATIC;
node.name = Constants.STATIC_INIT_METHOD;
node.desc = Type.getMethodDescriptor(Type.getType(void.class));
MethodStmtCompiler methodCompiler = new MethodStmtCompiler(this, node);
ExpressionStmtCompiler expressionCompiler = new ExpressionStmtCompiler(methodCompiler, null);
methodCompiler.writeHeader();
// trace list
expressionCompiler.writePushSmallInt(traceList.size());
node.instructions.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(TraceInfo.class)));
expressionCompiler.stackPush(Memory.Type.REFERENCE);
int i = 0;
for (TraceInfo traceInfo : traceList) {
expressionCompiler.writePushDup();
expressionCompiler.writePushSmallInt(i);
expressionCompiler.writePushCreateTraceInfo(traceInfo.getStartLine(), traceInfo.getStartPosition());
node.instructions.add(new InsnNode(AASTORE));
expressionCompiler.stackPop();
expressionCompiler.stackPop();
i++;
}
expressionCompiler.writePutStatic("$TRC", TraceInfo[].class);
// memory constants
expressionCompiler.writePushSmallInt(memoryConstants.size());
node.instructions.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Memory.class)));
expressionCompiler.stackPush(Memory.Type.REFERENCE);
i = 0;
for (Memory memory : memoryConstants) {
expressionCompiler.writePushDup();
expressionCompiler.writePushSmallInt(i);
expressionCompiler.writePushMemory(memory);
expressionCompiler.writePopBoxing(true, false);
node.instructions.add(new InsnNode(AASTORE));
expressionCompiler.stackPop();
expressionCompiler.stackPop();
i++;
}
expressionCompiler.writePutStatic("$MEM", Memory[].class);
// memory array constants
expressionCompiler.writePushSmallInt(memoryArrayConstants.size());
node.instructions.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Memory[].class)));
expressionCompiler.stackPush(Memory.Type.REFERENCE);
i = 0;
for (Collection<Memory> memories : memoryArrayConstants) {
expressionCompiler.writePushDup();
expressionCompiler.writePushSmallInt(i);
expressionCompiler.writePushParameters(memories);
node.instructions.add(new InsnNode(AASTORE));
expressionCompiler.stackPop();
expressionCompiler.stackPop();
i++;
}
expressionCompiler.writePutStatic("$AMEM", Memory[][].class);
// cached calls
expressionCompiler.writePushNewObject(FunctionCallCache.class);
expressionCompiler.writePutStatic("$CALL_FUNC_CACHE", FunctionCallCache.class);
expressionCompiler.writePushNewObject(MethodCallCache.class);
expressionCompiler.writePutStatic("$CALL_METH_CACHE", MethodCallCache.class);
expressionCompiler.writePushNewObject(ConstantCallCache.class);
expressionCompiler.writePutStatic("$CALL_CONST_CACHE", ConstantCallCache.class);
expressionCompiler.writePushNewObject(PropertyCallCache.class);
expressionCompiler.writePutStatic("$CALL_PROP_CACHE", PropertyCallCache.class);
node.instructions.add(new InsnNode(RETURN));
methodCompiler.writeFooter();
this.node.methods.add(node);
}
protected void writeInterfaceMethod(MethodEntity method) {
MethodNode node = new MethodNodeImpl();
node.access = ACC_PUBLIC;
node.name = method.getName();
node.desc = Type.getMethodDescriptor(
Type.getType(Memory.class),
Type.getType(Environment.class),
Type.getType(Memory[].class)
);
MethodStmtCompiler methodCompiler = new MethodStmtCompiler(this, node);
ExpressionStmtCompiler expressionCompiler = new ExpressionStmtCompiler(methodCompiler, null);
methodCompiler.writeHeader();
LabelNode l0 = writeLabel(node, statement.getMeta().getStartLine());
methodCompiler.addLocalVariable("~this", l0);
methodCompiler.addLocalVariable("~env", l0);
methodCompiler.addLocalVariable("~args", l0);
expressionCompiler.writeVarLoad("~this");
expressionCompiler.writeVarLoad("~env");
expressionCompiler.writeVarLoad("~args");
String internalName = entity.findMethod(method.getLowerName()).getInternalName();
expressionCompiler.writeSysDynamicCall(null, internalName, Memory.class, Environment.class, Memory[].class);
node.instructions.add(new InsnNode(ARETURN));
methodCompiler.writeFooter();
this.node.methods.add(node);
}
protected void writeInterfaceMethods(Collection<MethodEntity> methods) {
for (MethodEntity method : methods) {
writeInterfaceMethod(method);
}
}
protected Set<ClassEntity> writeInterfaces(ClassEntity _interface) {
Set<ClassEntity> result = new HashSet<ClassEntity>();
writeInterfaces(_interface, result);
return result;
}
protected void writeInterfaces(ClassEntity _interface, Set<ClassEntity> used) {
if (used.add(_interface)) {
if (_interface != null && _interface.isInternal())
node.interfaces.add(_interface.getInternalName());
if (_interface != null) {
for (ClassEntity el : _interface.getInterfaces().values()) {
if (!_interface.isInternal() || !el.isInternal())
writeInterfaces(el, used);
}
}
}
}
protected ClassEntity fetchClass(String name) {
ClassEntity result = compiler.getModule().findClass(name);
if (result == null)
result = getCompiler().getEnvironment().fetchClass(name, true);
return result;
}
protected ClassEntity fetchClassAndCheck(String name) {
ClassEntity r = fetchClass(name);
if (r == null)
compiler.getEnvironment().error(
entity.getTrace(),
Messages.ERR_CLASS_NOT_FOUND.fetch(name)
);
return r;
}
@SuppressWarnings("unchecked")
protected void writeImplements() {
if (statement.getImplement() != null) {
Environment env = compiler.getEnvironment();
for (FulledNameToken name : statement.getImplement()) {
ClassEntity implement = fetchClass(name.getName());
Set<ClassEntity> needWriteInterfaceMethods = new HashSet<ClassEntity>();
if (implement == null) {
env.error(
name.toTraceInfo(compiler.getContext()),
Messages.ERR_INTERFACE_NOT_FOUND.fetch(name.toName())
);
} else {
if (implement.getType() != ClassEntity.Type.INTERFACE) {
env.error(
name.toTraceInfo(compiler.getContext()),
Messages.ERR_CANNOT_IMPLEMENT.fetch(entity.getName(), implement.getName())
);
}
if (!statement.isInterface())
needWriteInterfaceMethods = writeInterfaces(implement);
}
ClassEntity.ImplementsResult addResult = entity.addInterface(implement);
addResult.check(env);
for (ClassEntity el : needWriteInterfaceMethods) {
if (el.isInternal()) {
writeInterfaceMethods(el.getMethods().values());
}
}
}
}
}
protected void writeCopiedMethod(ClassStmtToken.Alias alias, String methodName, ClassEntity trait) {
final MethodEntity methodEntity = fetchClassAndCheck(alias.getTrait()).findMethod(methodName.toLowerCase());
String name = alias.getName();
if (name == null)
name = methodName;
MethodEntity origin = entity.findMethod(name.toLowerCase());
if (origin != null) {
if (origin.getClazz() == entity) {
if (origin.getTrait() != null) {
compiler.getEnvironment().error(
entity.getTrace(),
Messages.ERR_TRAIT_METHOD_COLLISION.fetch(
methodName, trait.getName(), origin.getTrait().getName(), entity.getName()
)
);
}
return;
}
}
if (methodEntity == null) {
compiler.getEnvironment().error(
entity.getTrace(),
Messages.ERR_METHOD_NOT_FOUND.fetch(alias.getTrait(), methodName)
);
return;
}
MethodEntity dup = methodEntity.duplicateForInject();
dup.setClazz(entity);
dup.setTrait(trait);
if (alias.getName() != null)
dup.setName(alias.getName());
if (alias.getModifier() != null)
dup.setModifier(alias.getModifier());
MethodNodeImpl methodNode = MethodNodeImpl.duplicate(methodEntity.getAdditionalData("methodNode", MethodNode.class, new Function<MethodNode>() {
@Override
public MethodNode call() {
ClassNode classNode = methodEntity.getClazz().getAdditionalData("classNode", ClassNode.class, new Function<ClassNode>() {
@Override
public ClassNode call() {
ClassReader classReader;
if (methodEntity.getClazz().getData() != null)
classReader = new ClassReader(methodEntity.getClazz().getData());
else {
try {
classReader = new ClassReader(methodEntity.getClazz().getName());
} catch (IOException e) {
throw new CriticalException(e);
}
}
ClassNode classNode = new ClassNode();
classReader.accept(classNode, 0);
return classNode;
}
});
for (Object m : classNode.methods) {
MethodNode method = (MethodNode) m;
if (method.name.equals(methodEntity.getInternalName())) {
return method;
}
}
throw new CriticalException("Cannot find MethodNode for method - " + methodEntity.getName() + "(" + methodEntity.getSignatureString(true) + ")");
}
}));
if (origin != null) {
dup.setPrototype(origin);
}
dup.setInternalName(dup.getName() + "$" + entity.nextMethodIndex());
methodNode.name = dup.getInternalName();
ClassEntity.SignatureResult result = entity.addMethod(dup, null);
result.check(compiler.getEnvironment());
node.methods.add(methodNode);
}
protected void writeCopiedMethod(MethodEntity methodEntity, ClassEntity trait) {
ClassStmtToken.Replacement replacement = statement.findReplacement(methodEntity.getName());
if (replacement != null && replacement.hasTrait(trait.getName())) {
return;
}
List<ClassStmtToken.Alias> aliases = statement.findAliases(methodEntity.getName());
if (aliases == null)
aliases = Arrays.asList(new ClassStmtToken.Alias(trait.getName(), null, methodEntity.getName()));
else {
boolean replaceExists = false;
boolean replaceAlias = false;
for (ClassStmtToken.Alias alias : aliases) {
if (replacement != null && alias.getTrait().equalsIgnoreCase(replacement.getOrigin())) {
replaceExists = true;
break;
}
if ((alias.getModifier() == null || alias.getModifier() == methodEntity.getModifier())
|| alias.getName() == null)
replaceAlias = true;
}
if (replacement != null && !replaceExists)
aliases.add(new ClassStmtToken.Alias(replacement.getOrigin(), null, methodEntity.getName()));
if (!replaceAlias)
aliases.add(new ClassStmtToken.Alias(trait.getName(), null, methodEntity.getName()));
}
for (ClassStmtToken.Alias alias : aliases) {
writeCopiedMethod(alias, methodEntity.getName(), trait);
}
}
protected void writeTraitProperties(ClassEntity trait, Collection<PropertyEntity> props) {
for (PropertyEntity el : props) {
PropertyEntity origin = entity.properties.get(el.getLowerName());
if (origin != null) {
// doesn't work with parent private properties of non-traits (see: the traits/property006.php test)
boolean isSkip = origin.getTrait() == null && !origin.getClazz().equals(entity) && origin.isPrivate();
if (origin.getTrait() != null && origin.getTrait().equals(trait))
isSkip = true;
if (!isSkip) {
Environment env = compiler.getEnvironment();
String ownerName = origin.getClazz().getName();
if (origin.getTrait() != null)
ownerName = origin.getTrait().getName();
boolean isFatal = origin.getModifier() != el.getModifier();
if (origin.getDefaultValue() != null && el.getDefaultValue() == null)
isFatal = true;
else if (origin.getDefaultValue() == null && el.getDefaultValue() != null)
isFatal = true;
else if (origin.getDefaultValue() == null) {
// nop
} else if (!origin.getDefaultValue().identical(el.getDefaultValue()))
isFatal = true;
if (isFatal) {
env.error(
entity.getTrace(),
Messages.ERR_TRAIT_SAME_PROPERTY.fetch(
ownerName,
trait.getName(), el.getName(), entity.getName()
)
);
} else {
env.error(
entity.getTrace(), ErrorType.E_STRICT,
Messages.ERR_TRAIT_SAME_PROPERTY_STRICT.fetch(
ownerName, trait.getName(), el.getName(), entity.getName()
)
);
}
}
}
if (origin != null && origin.getClazz().getId() == entity.getId())
continue;
PropertyEntity p = el.duplicate();
p.setClazz(entity);
p.setTrait(trait);
ClassEntity.PropertyResult r = entity.addProperty(p);
r.check(compiler.getEnvironment());
}
}
protected void writeTraitProperties(ClassEntity trait) {
writeTraitProperties(trait, trait.getProperties());
writeTraitProperties(trait, trait.getStaticProperties());
}
protected void writeTrait(ClassEntity trait) {
entity.addTrait(trait);
initDynamicExists = true;
writeTraitProperties(trait);
for (MethodEntity methodEntity : trait.getMethods().values()) {
writeCopiedMethod(methodEntity, trait);
}
}
protected void checkRequiredTrait(String trait) {
if (!entity.hasTrait(trait.toLowerCase())) {
compiler.getEnvironment().error(
entity.getTrace(),
Messages.ERR_TRAIT_WAS_NOT_ADDED.fetch(trait, entity.getName())
);
}
}
protected void checkAliasAndReplacementsTraits() {
if (statement.getAliases() != null)
for (List<ClassStmtToken.Alias> aliases : statement.getAliases().values()) {
for (ClassStmtToken.Alias alias : aliases) {
checkRequiredTrait(alias.getTrait());
}
}
if (statement.getReplacements() != null)
for (ClassStmtToken.Replacement replacement : statement.getReplacements().values()) {
checkRequiredTrait(replacement.getOrigin());
for (String e : replacement.getTraits())
checkRequiredTrait(e);
}
}
protected List<ClassEntity> fetchTraits() {
List<ClassEntity> r = new ArrayList<ClassEntity>();
for (NameToken one : statement.getUses()) {
ClassEntity trait = fetchClass(one.getName());
if (trait == null) {
compiler.getEnvironment().error(
one.toTraceInfo(compiler.getContext()),
Messages.ERR_TRAIT_NOT_FOUND.fetch(one.getName())
);
return null;
}
if (!trait.isTrait()) {
compiler.getEnvironment().error(
one.toTraceInfo(compiler.getContext()),
Messages.ERR_CANNOT_USE_NON_TRAIT.fetch(
entity.getName(), one.getName()
)
);
} else
r.add(trait);
}
return r;
}
protected void writeTraits(Collection<ClassEntity> traits) {
for (ClassEntity trait : traits) {
writeTrait(trait);
}
}
@Override
public ClassEntity compile() {
entity = new ClassEntity(compiler.getContext());
entity.setId(compiler.getScope().nextClassIndex());
entity.setFinal(statement.isFinal());
entity.setAbstract(statement.isAbstract());
entity.setName(statement.getFulledName());
if (statement.getDocComment() != null)
entity.setDocComment(new DocumentComment(statement.getDocComment().getComment()));
entity.setTrace(statement.toTraceInfo(compiler.getContext()));
entity.setType(statement.getClassType());
List<ClassEntity> traits = fetchTraits();
for (ClassEntity e : traits)
entity.addTrait(e);
checkAliasAndReplacementsTraits();
if (statement.getExtend() != null) {
ClassEntity parent = fetchClass(statement.getExtend().getName().getName());
if (parent == null)
compiler.getEnvironment().error(
statement.getExtend().toTraceInfo(compiler.getContext()),
Messages.ERR_CLASS_NOT_FOUND.fetch(statement.getExtend().getName().toName())
);
ClassEntity.ExtendsResult result = entity.setParent(parent, false);
if (isInterfaceCheck) {
result.check(compiler.getEnvironment());
}
}
if (!isSystem) {
if (entity.isUseJavaLikeNames()) {
entity.setInternalName(
entity.getName().replace('\\', '/')
);
} else {
entity.setInternalName(
compiler.getModule().getInternalName() + "_class" + compiler.getModule().getClasses().size()
);
}
}
if (compiler.getModule().findClass(entity.getLowerName()) != null
|| compiler.getEnvironment().isLoadedClass(entity.getLowerName())) {
throw new FatalException(
Messages.ERR_CANNOT_REDECLARE_CLASS.fetch(entity.getName()),
statement.getName().toTraceInfo(compiler.getContext())
);
}
if (!statement.isInterface()) {
node.access = ACC_SUPER + ACC_PUBLIC;
node.name = !isSystem /*&& !statement.isTrait()*/
? entity.getCompiledInternalName()
: statement.getFulledName(Constants.NAME_DELIMITER);
node.superName = entity.getParent() == null
? Type.getInternalName(BaseObject.class)
: entity.getParent().getInternalName();
node.sourceFile = compiler.getSourceFile();
/*if (!isSystem) {
AnnotationNode annotationNode = new AnnotationNode(Type.getInternalName(Reflection.Name.class));
annotationNode.values = Arrays.asList("value", entity.getName());
node.visibleAnnotations.add(annotationNode);
} */
writeSystemInfo();
writeConstructor();
writeDefaultConstructors();
}
// constants
if (statement.getConstants() != null)
for (ConstStmtToken constant : statement.getConstants()) {
writeConstant(constant);
}
if (statement.getMethods() != null) {
for (MethodStmtToken method : statement.getMethods()) {
ClassEntity.SignatureResult result = entity.addMethod(
compiler.compileMethod(this, method, external, generatorEntity), null);
result.check(compiler.getEnvironment());
}
}
writeTraits(traits);
ClassEntity.SignatureResult result = entity.updateParentMethods();
if (isInterfaceCheck) {
result.check(compiler.getEnvironment());
}
writeImplements();
entity.doneDeclare();
if (!statement.isInterface()) {
writeDestructor();
if (entity.getType() != ClassEntity.Type.INTERFACE) {
writeInitEnvironment();
}
writeInitStatic();
cw = new JPHPClassWriter(entity.isTrait());
node.accept(cw);
entity.setData(cw.toByteArray());
}
return entity;
}
public void setClassContext(ClassStmtToken classContext) {
this.classContext = classContext;
}
public ClassStmtToken getClassContext() {
return classContext;
}
}