package scriptingLanguage.variables;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import scriptingLanguage.Interpreter;
import scriptingLanguage.Token;
import scriptingLanguage.Type;
import scriptingLanguage.errors.InterpreterException;
import scriptingLanguage.errors.NullAccessException;
import scriptingLanguage.errors.UndefinedResultException;
import scriptingLanguage.errors.UnexpectedTokenException;
import scriptingLanguage.errors.UnexpectedTypeException;
import scriptingLanguage.errors.VariableNotFoundException;
import scriptingLanguage.frames.AbstractFrame;
public class JavaObject<T> extends AbstractObject<T> {
public JavaObject(JavaClass<T> type) {
super((AbstractClass<T>) type);
}
public JavaObject(JavaClass<T> type, T source) {
super((AbstractClass<T>) type, source);
}
@Override
public Token eval(AbstractClass<?> caller, Token parameters, AbstractFrame frame) throws InterpreterException {
if (!parameters.getCarType().equals(Interpreter.identifierType))
throw new UnexpectedTokenException("Expected an identifier, got a " + parameters.getCarType().getName() + ". " + parameters.toString());
Object data = getSource();
String name = (String) parameters.getCar();
if ((parameters = parameters.getNextToken()).getCarType().equals(Interpreter.parenthesesType)) {
ArrayList<Token> args = Interpreter.evalFunctionArguments(caller, (Token) parameters.getCar(), frame);
Class<?> source = (Class<?>) (getSource() instanceof Class<?> ? getSource() : getSource().getClass());
try {
Class<?>[] params = new Class<?>[args.size()];
for (int i = 0; i < args.size(); i++)
params[i] = (Class<?>) (args.get(i).getCar() instanceof JavaObject ? ((JavaObject<?>) args.get(i).getCar()).getType().getSource() : getClass(((AbstractObject<?>) args.get(i).getCar()).getType().getSource()));
Method method = null, methods[] = source.getMethods();
try {
method = source.getMethod(name, params);
}
catch (NoSuchMethodException e) {
int accuracy = 0;
method = null;
for (Method m : methods) {
Class<?>[] types = m.getParameterTypes();
//TODO add support for var args
if (types.length != params.length) //These have different parameter lengths
continue;
boolean good = true;
int a = 0;
for (int i = 0; i < types.length; i++) {
if (types[i].equals(params[i]))
a += 2; //Same type
else if (types[i].isAssignableFrom(params[i]))
a++; //Different types, but subclass
else {
good = false;
break;
}
}
if (good && a > accuracy) {
accuracy = a;
method = m;
}
}
if (method == null)
throw new NoSuchMethodException();
}
Object[] arguments = new Object[args.size()];
for (int i = 0; i < args.size(); i++)
arguments[i] = ((JavaObject<?>) args.get(i).getCar()).getSource();
//If source is an instance of Class<?>, then this is a static class reference.
data = method.invoke(getSource() instanceof Class<?> ? null : getSource(), arguments);
}
catch (ClassCastException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
throw new VariableNotFoundException(frame.getInterpreter().evalFunctionName(name, args) + " is not defined in the current frame.");
}
}
else
try {
Class<?> source = (Class<?>) (getSource() instanceof Class<?> ? getSource() : getSource().getClass());
Field field = source.getField(name);
data = field.get(getSource() instanceof Class<?> ? null : getSource());
}
catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new VariableNotFoundException(name + " is not defined in the current frame.");
}
if (data == null)
return new Token();
AbstractClass<?> output = determineType(data, frame);
return new Token(output instanceof JavaClass ? new JavaObject<Object>((JavaClass<Object>) output, data) :
((PrimitiveClass<?>) output).makePrimitiveObject(data), (Type<Object>) output.getTokenType());
}
@Override
public AbstractObject<?> castData(AbstractClass<?> output, AbstractFrame frame) throws UnexpectedTypeException, UndefinedResultException, NullAccessException {
if (output.equals(Interpreter.stringClass))
return new JavaObject<String>(Interpreter.stringClass, toString());
if (!(output instanceof JavaClass))
throw new UnexpectedTypeException("Instances of the " + getType().getName() + " type cannot be cast to " + output.getName());
switch (getType().extend(output)) {
case -1:
throw new UnexpectedTypeException("Instances of the " + getType().getName() + " type cannot be cast to " + output.getName());
case 0: //They are the same type
return this;
default:
return new JavaObject<Object>((JavaClass<Object>) output, getSource());
}
}
@Override
public int extend(AbstractClass<?> type) throws NullAccessException {
return getType().extend(type) + 1;
}
@Override
public String toString() {
if (getSource() instanceof Array) {
String output = "";
int length = Array.getLength(getSource());
for (int i = 0; i < length; i++)
output = output + ", " + Array.get(getSource(), i).toString();
if (length > 0)
output = output.substring(2);
return "[" + output + "]";
}
return getSource().toString();
}
@Override
public Variable<T> clone() {
return new JavaObject<T>((JavaClass<T>) getType(), getSource());
}
}