public class Environment {
    // TODO make me thread safe
    private static final Map<String, Class<?>> DEFAULT_CLASSES = new HashMap<>();
    private final Map<String, Class<?>> classes = new LinkedHashMap<>(DEFAULT_CLASSES);
    private final Map<String, Variable> variables = new LinkedHashMap<>();

    static {
        for (String name : ReflectionUtil.JAVA_LANG_CLASSES) {
            try {
                DEFAULT_CLASSES.put(name, Class.forName("java.lang." + name));
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException("class java.lang." + name + " not found");
            }
        }
    }

    public void importClass(Class<?> _class) {
        final String name = _class.getCanonicalName();
        // Validate the type of class
        if (_class.isPrimitive()) {
            throw new UnsupportedOperationException("Can't import a primitive type: " + name);
        }
        if (_class.isArray()) {
            throw new UnsupportedOperationException("Can't import an array class: " + name);
        }
        if (_class.isAnonymousClass()) {
            throw new UnsupportedOperationException("Can't import an anonymous class: " + name);
        }
        // Check for existing import under the same simple name
        final String simpleName = _class.getSimpleName();
        final Class<?> existing = classes.get(simpleName);
        // ignore cases where the classes are the same as redundant imports are allowed
        if (existing != null && _class != existing) {
            throw new UnsupportedOperationException("Class " + name + " clashes with existing import " + existing.getCanonicalName());
        }
        // Add the class to the imports
        classes.put(simpleName, _class);
    }

    public Class<?> findClass(Identifier name) {
        return classes.get(name.getSource());
    }

    public Class<?> getClass(Identifier name) {
        final Class<?> _class = findClass(name);
        if (_class == null) {
            throw new UnsupportedOperationException("Class " + name.getSource() + " does not exist");
        }
        return _class;
    }

    public Collection<Class<?>> getClasses() {
        return classes.values();
    }

    public boolean hasClass(Identifier name) {
        return classes.containsKey(name.getSource());
    }

    public boolean hasClass(Class<?> _class) {
        return classes.containsValue(_class);
    }

    public void declareVariable(Identifier name, LiteralType type, Value value) {
        if (hasVariable(name)) {
            throw new UnsupportedOperationException("Variable " + name.getSource() + " is already declared");
        }
        variables.put(name.getSource(), new Variable(name, type, value));
    }

    public LiteralType getVariableType(Identifier name) {
        return findVariable(name).getType();
    }

    public Value getVariable(Identifier name) {
        return findVariable(name).getValue();
    }

    public Collection<Variable> getVariables() {
        return variables.values();
    }

    public void setVariable(Identifier name, Value value) {
        findVariable(name).setValue(value);
    }

    public boolean hasVariable(Identifier name) {
        return variables.containsKey(name.getSource());
    }

    private Variable findVariable(Identifier name) {
        final String nameString = name.getSource();
        final Variable variable = variables.get(nameString);
        if (variable == null) {
            throw new UnsupportedOperationException("Variable " + nameString + " does not exist");
        }
        return variable;
    }

    public static class Variable {
        private final Identifier name;
        private final LiteralType type;
        private Value value;

        private Variable(Identifier name, LiteralType type, Value value) {
            this.name = name;
            this.type = type;
            this.value = value;
        }

        public Identifier getName() {
            return name;
        }

        public LiteralType getType() {
            return type;
        }

        public boolean initialized() {
            return value != null;
        }

        public Value getValue() {
            if (!initialized()) {
                throw new UnsupportedOperationException("Variable " + name.getSource() + " has not been initialized");
            }
            return value;
        }

        private void setValue(Value value) {
            this.value = value;
        }
    }
} public class EvaluatorException extends SourceException {
    private static final long serialVersionUID = 1;

    public EvaluatorException(Throwable cause, SourceIndexed indexed) {
        this("EvaluatorException", cause, indexed);
    }

    public EvaluatorException(String error, Throwable cause, SourceIndexed indexed) {
        super(getMessage(error, cause), cause, null, indexed.getStart(), indexed.getEnd());
    }

    public EvaluatorException(String error, SourceIndexed indexed) {
        this(error, indexed.getStart(), indexed.getEnd());
    }

    public EvaluatorException(String error, int start, int end) {
        super(error, null, start, end);
    }

    private static String getMessage(String error, Throwable cause) {
        if (cause == null) {
            return error;
        }
        String causeMessage = cause.getMessage();
        if (causeMessage != null) {
            causeMessage = causeMessage.trim();
            if (causeMessage.isEmpty()) {
                causeMessage = null;
            }
        }
        error += " (" + cause.getClass().getSimpleName();
        if (causeMessage != null) {
            error += ": " + causeMessage;
        }
        return getMessage(error, cause.getCause()) + ")";
    }
} public interface Callable {
    LiteralReferenceType getDeclaringType();

    String getName();

    TypeVariable[] getTypeParameters();

    Type[] getParameterTypes();

    Type getReturnType();

    boolean isStatic();

    boolean supportsVararg();

    boolean isVarargEnabled();

    Callable useVararg();

    boolean isApplicable(Type[] argumentTypes);

    boolean isMoreApplicableThan(Callable other, Type[] argumentTypes);

    boolean requiresUncheckedConversion(Type[] argumentTypes);

    Callable eraseReturnType();

    Value call(Value target, Value... arguments);

    String toString();
} public interface ReferenceType extends Type {
    ComponentType getComponentType();

    LiteralReferenceType getInnerClass(String name, TypeArgument[] typeArguments);

    ClassVariable getField(String name);

    ConstructorCallable getConstructor(TypeArgument[] typeArguments, Type[] arguments);

    Callable getMethod(String name, TypeArgument[] typeArguments, Type[] arguments);

    Set<LiteralReferenceType> getDirectSuperTypes();

    Set<LiteralReferenceType> getSuperTypes();

    ReferenceType getErasure();

    boolean isUncheckedConversion(Type to);

    @Override
    ReferenceType capture();
} public interface Type {
    String getName();

    ValueKind getKind();

    boolean isVoid();

    boolean isNull();

    boolean isPrimitive();

    boolean isNumeric();

    boolean isIntegral();

    boolean isBoolean();

    boolean isArray();

    boolean isReference();

    boolean isReifiable();

    boolean convertibleTo(Type to);

    Type capture();

    @Override
    String toString();

    @Override
    boolean equals(Object other);

    @Override
    int hashCode();
} public interface TypeArgument extends Type {
    TypeArgument substituteTypeVariables(Substitutions substitution);

    Set<TypeVariable> getTypeVariables();

    boolean contains(TypeArgument other);

    TypeArgument asArray(int dimensions);
} public class WildcardType implements TypeArgument, BoundedType {
    public static final WildcardType THE_UNBOUNDED = of(Collections.<SingleReferenceType>emptySet(), Collections.<SingleReferenceType>emptySet());
    private final IntersectionType lowerBound;
    private final IntersectionType upperBound;

    private WildcardType(IntersectionType lowerBound, IntersectionType upperBound) {
        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
    }

    @Override
    public IntersectionType getLowerBound() {
        return lowerBound;
    }

    @Override
    public IntersectionType getUpperBound() {
        return upperBound;
    }

    public boolean isUnbounded() {
        return lowerBound.equals(IntersectionType.EVERYTHING) && upperBound.equals(IntersectionType.NOTHING);
    }

    @Override
    public String getName() {
        String name = "?";
        if (!lowerBound.equals(IntersectionType.EVERYTHING)) {
            name += " super " + lowerBound;
        }
        if (!upperBound.equals(IntersectionType.NOTHING)) {
            name += " extends " + upperBound;
        }
        return name;
    }

    @Override
    public ValueKind getKind() {
        return ValueKind.OBJECT;
    }

    @Override
    public boolean isVoid() {
        return false;
    }

    @Override
    public boolean isNull() {
        return false;
    }

    @Override
    public boolean isPrimitive() {
        return false;
    }

    @Override
    public boolean isNumeric() {
        return false;
    }

    @Override
    public boolean isIntegral() {
        return false;
    }

    @Override
    public boolean isBoolean() {
        return false;
    }

    @Override
    public boolean isArray() {
        return upperBound.isArray();
    }

    @Override
    public boolean isReference() {
        return true;
    }

    @Override
    public boolean isReifiable() {
        return isUnbounded();
    }

    @Override
    public boolean convertibleTo(Type to) {
        throw new UnsupportedOperationException("Wildcard type needs to be capture converted for this operation");
    }

    @Override
    public WildcardType capture() {
        return this;
    }

    @Override
    public TypeArgument asArray(int dimensions) {
        IntersectionType arrayLowerBound = lowerBound;
        if (!arrayLowerBound.equals(IntersectionType.EVERYTHING)) {
            arrayLowerBound = arrayLowerBound.asArray(dimensions);
        }
        return new WildcardType(arrayLowerBound, upperBound.asArray(dimensions));
    }

    @Override
    public WildcardType substituteTypeVariables(Substitutions substitution) {
        // Apply recursively on lower and upper bounds
        return new WildcardType(lowerBound.substituteTypeVariables(substitution), upperBound.substituteTypeVariables(substitution));
    }

    @Override
    public Set<TypeVariable> getTypeVariables() {
        final Set<TypeVariable> typeVariables = lowerBound.getTypeVariables();
        typeVariables.addAll(upperBound.getTypeVariables());
        return typeVariables;
    }

    @Override
    public boolean contains(TypeArgument other) {
        if (other instanceof WildcardType) {
            final WildcardType otherWildcard = (WildcardType) other;
            return otherWildcard.upperBound.convertibleTo(upperBound) && lowerBound.convertibleTo(otherWildcard.lowerBound);
        }
        return other.convertibleTo(upperBound) && lowerBound.convertibleTo(other);
    }

    @Override
    public String toString() {
        return getName();
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof WildcardType)) {
            return false;
        }
        final WildcardType that = (WildcardType) other;
        return this.lowerBound.equals(that.lowerBound) && this.upperBound.equals(that.upperBound);
    }

    @Override
    public int hashCode() {
        int result = lowerBound.hashCode();
        result = 31 * result + upperBound.hashCode();
        return result;
    }

    public static WildcardType of(Set<SingleReferenceType> lowerBound, Set<SingleReferenceType> upperBound) {
        final IntersectionType lower = lowerBound.isEmpty() ? IntersectionType.EVERYTHING : IntersectionType.of(lowerBound);
        final IntersectionType upper = upperBound.isEmpty() ? IntersectionType.NOTHING : IntersectionType.of(upperBound);
        return of(lower, upper);
    }

    public static WildcardType of(IntersectionType lowerBound, IntersectionType upperBound) {
        upperBound.checkIfValidUpperBound();
        return new WildcardType(lowerBound, upperBound);
    }
} public interface Value {
    boolean asBoolean();

    byte asByte();

    short asShort();

    char asChar();

    int asInt();

    long asLong();

    float asFloat();

    double asDouble();

    Object asObject();

    String asString();

    <T> T as();

    ValueKind getKind();

    boolean isPrimitive();

    Class<?> getTypeClass();

    String toString();
} public class Identifier extends Token {
    private Identifier(String source, int index) {
        super(TokenID.IDENTIFIER, source, index);
    }

    public static Identifier from(String source, int index) {
        return new Identifier(source, index);
    }
} public interface Expression extends SourceIndexed {
    Type getType(Environment environment);

    Value getValue(Environment environment);

    void setStart(int start);

    void setEnd(int end);

    String toString();
} public class AmbiguousReference implements Reference {
    private final List<Identifier> name;
    private int start;
    private int end;
    private Reference reference = null;

    public AmbiguousReference(List<Identifier> name) {
        this.name = name;
        start = name.get(0).getStart();
        end = name.get(name.size() - 1).getEnd();
    }

    private Reference getReference(Environment environment) {
        if (reference != null) {
            return reference;
        }
        final Expression resolved = disambiguate(environment, name);
        if (!(resolved instanceof Reference)) {
            throw new EvaluatorException("Not a reference", resolved);
        }
        return reference = (Reference) resolved;
    }

    @Override
    public Type getType(Environment environment) {
        return getReference(environment).getType(environment);
    }

    @Override
    public Type getTargetType(Environment environment) {
        return getReference(environment).getTargetType(environment);
    }

    @Override
    public Value getValue(Environment environment) {
        return getReference(environment).getValue(environment);
    }

    @Override
    public void setValue(Environment environment, Value value) {
        getReference(environment).setValue(environment, value);
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return StringUtil.toString(name, ".");
    }

    public static Expression disambiguate(Environment environment, List<Identifier> name) {
        Expression resolved;
        final List<Identifier> accesses;
        final Identifier identifier = name.get(0);
        // variable followed by one or more field accesses
        // class followed by one or more field accesses
        if (environment.hasVariable(identifier)) {
            resolved = new VariableAccess(identifier);
            accesses = name.subList(1, name.size());
        } else {
            Class<?> _class = environment.findClass(identifier);
            if (_class != null) {
                resolved = new StaticAccess(_class, identifier.getStart(), identifier.getEnd());
                accesses = name.subList(1, name.size());
            } else {
                accesses = new ArrayList<>(name);
                _class = ReflectionUtil.disambiguateClass(accesses);
                final int start = identifier.getStart();
                if (_class == null) {
                    throw new EvaluatorException("Unknown identifier " + StringUtil.toString(name, "."), start, name.get(name.size() - 1).getEnd());
                }
                resolved = new StaticAccess(_class, start, accesses.isEmpty() ? name.get(name.size() - 1).getEnd() : accesses.get(0).getStart());
            }
        }
        for (Identifier access : accesses) {
            resolved = new FieldAccess(resolved, access);
        }
        return resolved;
    }

    public static class StaticAccess implements Expression {
        private final Class<?> _class;
        private int start;
        private int end;
        private Type type = null;

        private StaticAccess(Class<?> _class, int start, int end) {
            this._class = _class;
            this.start = start;
            this.end = end;
        }

        @Override
        public Type getType(Environment environment) {
            if (type == null) {
                type = TypeCache.wrapClass(_class);
            }
            return type;
        }

        @Override
        public Value getValue(Environment environment) {
            return ObjectValue.of(null);
        }

        @Override
        public int getStart() {
            return start;
        }

        @Override
        public int getEnd() {
            return end;
        }

        @Override
        public void setStart(int start) {
            this.start = start;
        }

        @Override
        public void setEnd(int end) {
            this.end = end;
        }

        @Override
        public String toString() {
            return _class.getCanonicalName();
        }
    }
} public class VariableAccess implements Reference {
    private final Identifier name;
    private int start;
    private int end;
    private Type type = null;

    public VariableAccess(Identifier name) {
        this.name = name;
        start = name.getStart();
        end = name.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        return getTargetType(environment);
    }

    @Override
    public Type getTargetType(Environment environment) {
        if (type != null) {
            return type;
        }
        try {
            type = environment.getVariableType(name);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), name);
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        try {
            return environment.getVariable(name);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), name);
        }
    }

    @Override
    public void setValue(Environment environment, Value value) {
        try {
            environment.setVariable(name, value);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), name);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return name.toString();
    }
} public class TypeArgumentName implements SourceIndexed {
    private final TypeName bound;
    private final BoundKind kind;
    private final int start;
    private TypeArgument type = null;

    public TypeArgumentName(int index) {
        this(null, BoundKind.NONE, index);
    }

    public TypeArgumentName(TypeName bound, BoundKind kind, int start) {
        this.bound = bound;
        this.kind = kind;
        this.start = start;
    }

    public TypeArgument getType(Environment environment) {
        if (type == null) {
            if (kind == BoundKind.NONE) {
                type = WildcardType.THE_UNBOUNDED;
            } else {
                final LiteralType bound = this.bound.getType(environment);
                if (!(bound instanceof TypeArgument)) {
                    throw new EvaluatorException("Name must be a type argument", this);
                }
                switch (kind) {
                    case EXACT:
                        type = (TypeArgument) bound;
                        break;
                    case LOWER:
                        type = WildcardType.of(Collections.singleton((SingleReferenceType) bound), Collections.<SingleReferenceType>emptySet());
                        break;
                    case UPPER:
                        type = WildcardType.of(Collections.<SingleReferenceType>emptySet(), Collections.singleton((SingleReferenceType) bound));
                        break;
                }
            }
        }
        return type;
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return bound == null ? start : bound.getEnd();
    }

    @Override
    public String toString() {
        switch (kind) {
            case EXACT:
                return bound.toString();
            case NONE:
                return "?";
            case LOWER:
                return "? super " + bound.toString();
            case UPPER:
                return "? extends " + bound.toString();
            default:
                throw new UnsupportedOperationException(kind.name());
        }
    }

    public enum BoundKind {
        EXACT,
        NONE,
        LOWER,
        UPPER
    }
} public interface Statement extends SourceIndexed {
    void execute(Environment environment);

    String toString();
} public final class StringUtil {
    private static final int[] DIGIT_VALUES = new int[256];

    static {
        for (int i = 0; i < DIGIT_VALUES.length; i++) {
            DIGIT_VALUES[i] = -1;
        }
        for (int c = '0', i = 0; c <= '9'; c++, i++) {
            DIGIT_VALUES[c] = i;
        }
        for (int c = 'a', i = 10; c <= 'z'; c++, i++) {
            DIGIT_VALUES[c] = i;
        }
        for (int c = 'A', i = 10; c <= 'Z'; c++, i++) {
            DIGIT_VALUES[c] = i;
        }
    }

    private StringUtil() {
    }

    public static boolean isWhitespace(char c) {
        return c == ' ' || c == '\t' || c == '\f' || isLineTerminator(c);
    }

    public static boolean isLineTerminator(char c) {
        return c == '\n' || c == '\r';
    }

    public static String toString(Object[] elements, String separator) {
        return toString(Arrays.asList(elements), separator);
    }

    public static String toString(Collection<?> elements, String separator) {
        final int size = elements.size() - 1;
        if (size >= 0) {
            final StringBuilder builder = new StringBuilder();
            final Iterator<?> iterator = elements.iterator();
            for (int i = 0; i < size; i++) {
                builder.append(iterator.next());
                builder.append(separator);
            }
            return builder.append(iterator.next()).toString();
        }
        return "";
    }

    public static String repeat(String string, int number) {
        final StringBuilder builder = new StringBuilder();
        repeat(string, number, builder);
        return builder.toString();
    }

    public static void repeat(String string, int number, StringBuilder to) {
        for (int i = 0; i < number; i++) {
            to.append(string);
        }
    }

    public static String removeAll(String string, char remove) {
        final int length = string.length();
        final char[] chars = new char[length];
        int count = 0;
        for (int i = 0; i < length; i++) {
            final char c = string.charAt(i);
            if (c != remove) {
                chars[count++] = c;
            }
        }
        return String.valueOf(chars, 0, count);
    }

    public static int findRadix(String source) {
        if (source.length() < 2) {
            return 10;
        }
        if (source.charAt(0) == '0') {
            switch (source.charAt(1)) {
                case 'b':
                case 'B':
                    return 2;
                case 'x':
                case 'X':
                    return 16;
                default:
                    return 8;
            }
        }
        return 10;
    }

    public static String removeRadixIdentifier(String source, int radix) {
        switch (radix) {
            case 2:
            case 16:
                return source.substring(2);
            case 8:
                return source.substring(1);
            case 10:
                return source;
            default:
                throw new IllegalArgumentException("No known radix identifier for " + radix);
        }
    }

    public static String reduceSign(String source) {
        boolean sign = false;
        int i = 0;
        for (; i < source.length(); i++) {
            final char c = source.charAt(i);
            if (c == '+') {
                continue;
            }
            if (c == '-') {
                sign ^= true;
                continue;
            }
            if (!isWhitespace(c)) {
                break;
            }
        }
        return (sign ? '-' : "") + source.substring(i);
    }

    public static int getDigitValue(char digit, int radix) {
        if (digit >= 256) {
            return -1;
        }
        final int value = DIGIT_VALUES[digit];
        return value >= radix ? -1 : value;
    }

    public static boolean equalsNoCaseASCII(char a, char b) {
        return (a & ~32) == (b & ~32);
    }

    public static char decodeJavaEscape(char escape) {
        switch (escape) {
            case 'b':
                return '\b';
            case 't':
                return '\t';
            case 'n':
                return '\n';
            case 'f':
                return '\f';
            case 'r':
                return '\r';
            case '"':
                return '\"';
            case '\'':
                return '\'';
            case '\\':
                return '\\';
            case '0':
                return '\0';
            case '1':
                return '\1';
            case '2':
                return '\2';
            case '3':
                return '\3';
            case '4':
                return '\4';
            case '5':
                return '\6';
            case '6':
                return '\6';
            case '7':
                return '\7';
            default:
                throw new IllegalArgumentException("'" + escape + "' is neither b, t, n, f, r, \", ', \\, 0, 1, 2, 3, 4, 5, 6 or 7");
        }
    }

    public static char decodeUnicodeEscape(String escape) {
        // format: XXXX where X is a hexadecimal digit
        if (escape.length() == 4) {
            int digit = getDigitValue(escape.charAt(3), 16);
            if (digit >= 0) {
                int value = digit;
                digit = getDigitValue(escape.charAt(2), 16);
                if (digit >= 0) {
                    value += digit << 4;
                    digit = getDigitValue(escape.charAt(1), 16);
                    if (digit >= 0) {
                        value += digit << 8;
                        digit = getDigitValue(escape.charAt(0), 16);
                        if (digit >= 0) {
                            value += digit << 12;
                            return (char) value;
                        }
                    }
                }
            }
        }
        throw new IllegalArgumentException("Expected 4 hexadecimal digits, got: " + escape);
    }

    public static String escapeCharacter(char offender) {
        final String string;
        if (Character.isWhitespace(offender)) {
            string = Character.getName(offender);
        } else {
            string = String.valueOf(offender);
        }
        return '\'' + string + '\'';
    }
}
import ca.sapon.jici.evaluator.type.Type;
import ca.sapon.jici.evaluator.type.TypeArgument;
import ca.sapon.jici.evaluator.type.WildcardType;
import ca.sapon.jici.evaluator.value.Value;
import ca.sapon.jici.lexer.Identifier;
import ca.sapon.jici.parser.expression.Expression;
import ca.sapon.jici.parser.expression.reference.AmbiguousReference;
import ca.sapon.jici.parser.expression.reference.VariableAccess;
import ca.sapon.jici.parser.name.TypeArgumentName;
import ca.sapon.jici.parser.statement.Statement;
import ca.sapon.jici.util.StringUtil;
import java.util.Collections;
import java.util.List;
import ca.sapon.jici.evaluator.Environment;
import ca.sapon.jici.evaluator.EvaluatorException;
import ca.sapon.jici.evaluator.member.Callable;
import ca.sapon.jici.evaluator.type.ReferenceType;
/*
 * This file is part of JICI, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2015-2016 Aleksi Sapon <http://sapon.ca/jici/>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package ca.sapon.jici.parser.expression.call;



public class MethodCall implements Expression, Statement {
    private final Expression object;
    private final Identifier method;
    private final List<TypeArgumentName> typeArguments;
    private final List<Expression> arguments;
    private int start;
    private int end;
    private Callable callable = null;

    public MethodCall(Expression object, Identifier method, List<Expression> arguments, int end) {
        this(object, method, Collections.<TypeArgumentName>emptyList(), arguments, method.getStart(), end);
    }

    public MethodCall(Expression object, Identifier method, List<TypeArgumentName> typeArguments, List<Expression> arguments, int start, int end) {
        this.object = object;
        this.method = method;
        this.typeArguments = typeArguments;
        this.arguments = arguments;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {