package net.sourceforge.javautil.bytecode.api.type;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.sourceforge.javautil.bytecode.BytecodeCompiler;
import net.sourceforge.javautil.bytecode.BytecodeException;
import net.sourceforge.javautil.bytecode.api.IBytecodeField;
import net.sourceforge.javautil.bytecode.api.BytecodeFieldDeclared;
import net.sourceforge.javautil.bytecode.api.IBytecodeReferenceable;
import net.sourceforge.javautil.bytecode.api.BytecodeResolutionPool;
import net.sourceforge.javautil.bytecode.api.IBytecodeResolvable;
import net.sourceforge.javautil.bytecode.api.MethodDescriptor;
import net.sourceforge.javautil.bytecode.api.TypeDescriptor;
import net.sourceforge.javautil.bytecode.api.TypeMemberAccess;
import net.sourceforge.javautil.bytecode.api.IBytecodeResolvable.ClassType;
import net.sourceforge.javautil.bytecode.api.TypeMemberAccess.Scope;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeBlock;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeConstructor;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeConstructorBase;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeContextMethod;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeMethodConcrete;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeMethod;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeMethod.ArgumentMatch;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeMethod.Util;
import net.sourceforge.javautil.bytecode.api.type.method.invocation.MethodInvocation;
import net.sourceforge.javautil.common.StringUtil;
/**
* A base for all {@link ClassType#Class} types.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public abstract class JavaClass<C extends BytecodeContextType> extends AbstractType<C> {
protected final List<BytecodeConstructorBase> constructors = new ArrayList<BytecodeConstructorBase>();
public JavaClass(BytecodeCompiler compiler, String name, TypeMemberAccess access) {
super(compiler, name, access, ClassType.Class);
}
@Override public Set<IBytecodeConstructor> getDeclaredConstructors() {
return new LinkedHashSet<IBytecodeConstructor>(constructors);
}
/**
* This will resolve the {@link TypeDescriptor}.
*
* @see #addInstanceField(String, TypeDescriptor, Scope, boolean)
*/
public IBytecodeField addInstanceField (String name, Class type, Scope scope, boolean isFinal) {
return this.addInstanceField(name, TypeDescriptor.getFor(type), scope, isFinal);
}
/**
* @param name The name of the instance field
* @param type The type of field
* @param scope The scope for the field
* @param isFinal True if the field is final, otherwise false
* @return The new field declaration
*/
public IBytecodeField addInstanceField (String name, TypeDescriptor type, Scope scope, boolean isFinal) {
this.fields.put(name, new BytecodeFieldDeclaration(
name, new TypeMemberAccess(scope, false, false, isFinal), this, type)
);
return this.fields.get(name);
}
public BytecodeConstructorBase addConstructor (Scope scope, boolean varArgs, Class<?>... parameterTypes) {
TypeDescriptor[] parameters = new TypeDescriptor[parameterTypes.length];
for (int i=0; i<parameters.length; i++) {
parameters[i] = TypeDescriptor.getFor(parameterTypes[i]);
}
return this.addConstructor(scope, varArgs, parameters);
}
/**
* @param scope The scope for the constructor
* @param varArgs True if the last argument is an array and can be used as var args
* @param parameters The parameters for the constructor
* @return The new constructor
*/
public BytecodeConstructorBase addConstructor (Scope scope, boolean varArgs, TypeDescriptor... parameters) {
this.constructors.add(new BytecodeConstructorBase(this, new TypeMemberAccess(scope, false, false, false),
varArgs, parameters));
return this.constructors.get(constructors.size()-1);
}
/**
* @param name The name of the method
* @param scope The scope for the method
* @param isStatic True if the method is static, otherwise false
* @param isFinal True if the method is final, otherwise false
* @param descriptor The descriptor for the method return type and parameters
* @return The new method
*/
public BytecodeMethodConcrete addMethod (String name, Scope scope, boolean isStatic, boolean isFinal, MethodDescriptor descriptor) {
return this.addMethod(name, new TypeMemberAccess(scope, false, isStatic, isFinal), descriptor);
}
public BytecodeMethodConcrete addMethod (String name, TypeMemberAccess access, MethodDescriptor descriptor) {
BytecodeMethodConcrete method = new BytecodeMethodConcrete(this, name, access, descriptor);
this.methods.add(method);
return method;
}
public BytecodeMethodConcrete implementMethod (IBytecodeMethod abstractMethod, BytecodeBlock code) {
return this.addMethod(abstractMethod.getName(), abstractMethod.getAccess(), abstractMethod.getDescriptor())
.setMethodBody(code);
}
public BytecodeMethodConcrete createJavaBeanProperty (final String fieldName, boolean getterOnly) {
String name = StringUtil.capitalize(fieldName);
return this.createJavaBeanProperty(fieldName, "get" + name, getterOnly ? null : "set" + name);
}
public BytecodeMethodConcrete createJavaBeanProperty (final String fieldName, final String getterName, final String setterName) {
final BytecodeFieldDeclaration field = this.fields.get(fieldName);
final boolean isStatic = field.getAccess().isStatic();
BytecodeMethodConcrete getter = this.addMethod(getterName,
new TypeMemberAccess(Scope.Public, false, isStatic, false),
new MethodDescriptor(false, field.getType(), new TypeDescriptor[0])).setMethodBody(new BytecodeBlock() {
@Override protected void writeInstructions(BytecodeContextMethod context) {
context.returnValue(isStatic ? context.getStaticField(fieldName) : context.getThisField(fieldName));
}
});
if (setterName != null) {
this.addMethod(setterName,
new TypeMemberAccess(Scope.Public, false, isStatic, false),
new MethodDescriptor(false, TypeDescriptor.VOID, new TypeDescriptor[0], field.getType())).setMethodBody(new BytecodeBlock() {
@Override protected void writeInstructions(BytecodeContextMethod context) {
context.setArrayIndexValue(isStatic ? context.getStaticField(fieldName) : context.getThisField(fieldName), context.getDeclaredParameter(0));
}
});
}
return getter;
}
@Override public IBytecodeConstructor findConstructor(BytecodeResolutionPool pool, TypeDescriptor... parameters) {
IBytecodeConstructor c = null;
for (IBytecodeConstructor cc : this.constructors) {
ArgumentMatch am = cc.compareArguments(pool, parameters);
if (am == ArgumentMatch.FUNCTIONAL && c == null) c = cc;
else if (am == ArgumentMatch.EXACT) { c = cc; break; }
}
return c;
}
/**
* Resolves the class name.
*
* @see #setSuperType(IBytecodeResolvable)
*/
public JavaClass setSuperType(String superClassName) {
return this.setSuperType(compiler.getPool().resolve(superClassName));
}
/**
* Resolves the super class.
*
* @see #setSuperType(IBytecodeResolvable)
*/
public JavaClass setSuperType(Class superClass) {
return this.setSuperType(compiler.getPool().resolve(superClass.getName()));
}
/**
* @param superType The super type for this class
* @return This for chaining
*/
public JavaClass setSuperType(IBytecodeResolvable superType) { this.superType = superType; return this; }
/**
* This will resolve the type
*
* @see #addImplements(IBytecodeResolvable)
*/
public JavaClass addImplements (String interfaceClassName) {
return this.addImplements(compiler.getPool().resolve(interfaceClassName));
}
/**
* This will resolve the type
*
* @see #addImplements(IBytecodeResolvable)
*/
public JavaClass addImplements (Class<?> iface) {
return this.addImplements(compiler.getPool().resolve(iface.getName()));
}
/**
* @param iface The interface to add
*/
public JavaClass addImplements (IBytecodeResolvable iface) {
if (iface.getClassType() != ClassType.Interface)
throw new BytecodeException("Not an interface: " + iface);
this.interfaces.add(iface);
return this;
}
@Override protected void internalClone(AbstractType target) {
super.internalClone(target);
((JavaClass)target).constructors.addAll(this.constructors);
}
@Override protected void writeInternal(C context) {
if (this.constructors.size() == 0) {
context.getWriter().writeMethod(context, new DefaultConstructor());
} else {
for (BytecodeConstructorBase constructor : this.constructors) {
context.getWriter().writeMethod(context, constructor);
}
}
super.writeInternal(context);
}
/**
* A default constructor.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public class DefaultConstructor extends BytecodeConstructorBase {
public DefaultConstructor() {
super(JavaClass.this, new TypeMemberAccess(Scope.Public, false, false, false), false);
this.setMethodBody(new BytecodeBlock() {
@Override protected void writeInstructions(BytecodeContextMethod context) {
context.createSuperConstructorInvocation().load(context);
context.returnVoid();
}
});
}
}
}