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 class LiteralReferenceType extends SingleReferenceType implements LiteralType, TypeArgument {
    public static final LiteralReferenceType THE_STRING = LiteralReferenceType.of(String.class);
    public static final LiteralReferenceType THE_OBJECT = LiteralReferenceType.of(Object.class);
    public static final LiteralReferenceType THE_CLONEABLE = LiteralReferenceType.of(Cloneable.class);
    public static final LiteralReferenceType THE_SERIALIZABLE = LiteralReferenceType.of(Serializable.class);
    private static final Map<Class<?>, PrimitiveType> UNBOXING_CONVERSIONS = new HashMap<>();
    private final Class<?> type;
    private PrimitiveType unbox;
    private boolean unboxCached = false;
    private java.lang.reflect.TypeVariable<?>[] parameters = null;

    static {
        UNBOXING_CONVERSIONS.put(Boolean.class, PrimitiveType.THE_BOOLEAN);
        UNBOXING_CONVERSIONS.put(Byte.class, PrimitiveType.THE_BYTE);
        UNBOXING_CONVERSIONS.put(Short.class, PrimitiveType.THE_SHORT);
        UNBOXING_CONVERSIONS.put(Character.class, PrimitiveType.THE_CHAR);
        UNBOXING_CONVERSIONS.put(Integer.class, PrimitiveType.THE_INT);
        UNBOXING_CONVERSIONS.put(Long.class, PrimitiveType.THE_LONG);
        UNBOXING_CONVERSIONS.put(Float.class, PrimitiveType.THE_FLOAT);
        UNBOXING_CONVERSIONS.put(Double.class, PrimitiveType.THE_DOUBLE);
    }

    protected LiteralReferenceType(Class<?> type) {
        this.type = type;
    }

    @Override
    public String getName() {
        return type.getCanonicalName();
    }

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

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

    public boolean isRaw() {
        return getTypeParameters().length > 0;
    }

    public boolean isInterface() {
        return type.isInterface();
    }

    public boolean isEnum() {
        return type.isEnum();
    }

    public boolean isAbstract() {
        return Modifier.isAbstract(type.getModifiers());
    }

    public boolean isPublic() {
        return Modifier.isPublic(type.getModifiers());
    }

    public boolean isStatic() {
        return Modifier.isStatic(type.getModifiers());
    }

    public boolean isInnerClassOf(LiteralReferenceType enclosing) {
        if (enclosing == null) {
            // Enclosing as null means that it should not be an inner class
            return isStatic() || type.getEnclosingClass() == null;
        }
        return !isStatic() && type.getEnclosingClass() == enclosing.getTypeClass();
    }

    @Override
    public Class<?> getTypeClass() {
        return type;
    }

    protected java.lang.reflect.TypeVariable<?>[] getTypeParameters() {
        if (this.parameters == null) {
            // If this is an array, we need to get to the base component type
            Class<?> base = type;
            while (base.isArray()) {
                base = base.getComponentType();
            }
            this.parameters = base.getTypeParameters();
        }
        return parameters;
    }

    public boolean isBox() {
        if (!unboxCached) {
            unbox = UNBOXING_CONVERSIONS.get(type);
            unboxCached = true;
        }
        return unbox != null;
    }

    public PrimitiveType unbox() {
        if (isBox()) {
            return unbox;
        }
        throw new UnsupportedOperationException(type.getCanonicalName() + " is not a box type");
    }

    public LiteralType tryUnbox() {
        if (isBox()) {
            return unbox;
        }
        return this;
    }

    @Override
    public LiteralReferenceType getErasure() {
        return this;
    }

    @Override
    public Set<LiteralReferenceType> getDirectSuperTypes() {
        final Set<LiteralReferenceType> superTypes = new HashSet<>();
        if (isArray()) {
            // Find the number of dimensions of the array and the base component type
            int dimensions = 0;
            ComponentType componentType = this;
            do {
                if (!(componentType instanceof ReferenceType)) {
                    break;
                }
                componentType = ((ReferenceType) componentType).getComponentType();
                dimensions++;
            } while (componentType.isArray());
            if (componentType.equals(LiteralReferenceType.THE_OBJECT)) {
                // For an object component type we use the actual array direct super types
                superTypes.add(LiteralReferenceType.THE_OBJECT.asArray(dimensions - 1));
                superTypes.add(LiteralReferenceType.THE_SERIALIZABLE.asArray(dimensions - 1));
                superTypes.add(LiteralReferenceType.THE_CLONEABLE.asArray(dimensions - 1));
            } else {
                // Add all the component direct super types as arrays of the same dimension
                if (componentType instanceof LiteralReferenceType) {
                    for (LiteralReferenceType superType : ((LiteralReferenceType) componentType).getDirectSuperTypes()) {
                        superTypes.add(superType.asArray(dimensions));
                    }
                }
            }
        } else {
            // Add the direct super class and the directly implemented interfaces
            if (isInterface()) {
                // Interfaces have object as an implicit direct super class
                superTypes.add(LiteralReferenceType.THE_OBJECT);
            } else {
                // This will always return something unless this class is object
                final LiteralReferenceType superClass = getDirectSuperClass();
                if (superClass != null) {
                    superTypes.add(superClass);
                }
            }
            Collections.addAll(superTypes, getDirectlyImplementedInterfaces());
        }
        return superTypes;
    }

    @Override
    public LinkedHashSet<LiteralReferenceType> getSuperTypes() {
        final LinkedHashSet<LiteralReferenceType> result = new LinkedHashSet<>();
        final Queue<LiteralReferenceType> queue = new ArrayDeque<>();
        queue.add(capture());
        final boolean raw = isRaw();
        while (!queue.isEmpty()) {
            LiteralReferenceType child = queue.remove();
            if (raw) {
                child = child.getErasure();
            }
            if (result.add(child)) {
                queue.addAll(child.getDirectSuperTypes());
            }
        }
        return result;
    }

    @Override
    public LiteralReferenceType substituteTypeVariables(Substitutions substitution) {
        return this;
    }

    @Override
    public Set<TypeVariable> getTypeVariables() {
        return new HashSet<>();
    }

    public LiteralReferenceType getDirectSuperClass() {
        final java.lang.reflect.Type superClass = type.getGenericSuperclass();
        if (superClass != null) {
            final LiteralReferenceType wrappedClass = (LiteralReferenceType) TypeCache.wrapType(superClass);
            return isRaw() ? wrappedClass.getErasure() : wrappedClass;
        }
        return null;
    }

    public LiteralReferenceType[] getDirectlyImplementedInterfaces() {
        final java.lang.reflect.Type[] interfaces = type.getGenericInterfaces();
        final boolean raw = isRaw();
        final LiteralReferenceType[] wrapped = new LiteralReferenceType[interfaces.length];
        for (int i = 0; i < interfaces.length; i++) {
            final LiteralReferenceType wrappedInterface = (LiteralReferenceType) TypeCache.wrapType(interfaces[i]);
            wrapped[i] = raw ? wrappedInterface.getErasure() : wrappedInterface;
        }
        return wrapped;
    }

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

    @Override
    public ComponentType getComponentType() {
        final Class<?> componentType = type.getComponentType();
        if (componentType == null) {
            throw new UnsupportedOperationException("Not an array type");
        }
        return (ComponentType) TypeCache.wrapClass(componentType);
    }

    @Override
    public LiteralReferenceType asArray(int dimensions) {
        return of(ReflectionUtil.asArrayType(type, dimensions));
    }

    @Override
    public Object newArray(int length) {
        return Array.newInstance(type, length);
    }

    @Override
    public Object newArray(int[] lengths) {
        return Array.newInstance(type, lengths);
    }

    @Override
    public boolean contains(TypeArgument other) {
        return equals(other);
    }

    @Override
    public boolean convertibleTo(Type to) {
        // Literal class types might be convertible to a primitive if they can be unboxed
        if (to.isPrimitive()) {
            return isBox() && unbox().convertibleTo(to);
        }
        // If the target literal type is parametrized, this type must have a parent with
        // the same erasure and compatible type arguments
        if (to instanceof ParametrizedType) {
            final ParametrizedType parametrized = (ParametrizedType) to;
            final LiteralReferenceType erasure = parametrized.getErasure();
            for (LiteralReferenceType superType : getSuperTypes()) {
                if (superType.getErasure().equals(erasure)) {
                    return !(superType instanceof ParametrizedType)
                            || parametrized.argumentsContain(((ParametrizedType) superType).getArguments());
                }
            }
            return false;
        }
        // Else they can be converted to another literal class if they are a subtype
        if (to instanceof LiteralReferenceType) {
            final LiteralReferenceType target = (LiteralReferenceType) to;
            return target.type.isAssignableFrom(type);
        }
        // They can also be converted to a type variable lower bound
        if (to instanceof TypeVariable) {
            final TypeVariable target = (TypeVariable) to;
            return convertibleTo(target.getLowerBound());
        }
        // They can also be converted to an intersection if they can be converted to each member
        if (to instanceof IntersectionType) {
            final IntersectionType target = (IntersectionType) to;
            for (SingleReferenceType type : target.getTypes()) {
                if (!convertibleTo(type)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public boolean isUncheckedConversion(Type to) {
        if (!(to instanceof ParametrizedType)) {
            return false;
        }
        final ParametrizedType parametrized = (ParametrizedType) to;
        final LiteralReferenceType erasure = parametrized.getErasure();
        for (LiteralReferenceType superType : getSuperTypes()) {
            if (superType.getErasure().equals(erasure)) {
                return !(superType instanceof ParametrizedType);
            }
        }
        return false;
    }

    public LiteralReferenceType getDeclaredInnerClass(String name, TypeArgument[] typeArguments) {
        for (Class<?> _class : type.getDeclaredClasses()) {
            final int modifiers = _class.getModifiers();
            // Ignore non-public classes and synthetic classes generated by the compiler
            // Class must be non-static to be an inner class
            if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers) || _class.isSynthetic()) {
                continue;
            }
            // Check name match
            if (!_class.getSimpleName().equals(name)) {
                continue;
            }
            // If this enclosing class is parametrized, than it is an owner and the inner class is also parametrized
            return typeArguments.length > 0 || this instanceof ParametrizedType ?
                    ParametrizedType.of((ParametrizedType) this, _class, Arrays.asList(typeArguments)) :
                    LiteralReferenceType.of(_class);
        }
        return null;
    }

    public ClassVariable getDeclaredField(String name) {
        // Array length field must be handled specially because the reflection API doesn't declare it
        if (isArray() && "length".equals(name)) {
            return ArrayLengthVariable.of(this);
        }
        // Only one field can match the name, not overloads are possible
        for (Field field : type.getDeclaredFields()) {
            // Ignore non-public fields and synthetic fields generated by the compiler
            if (!Modifier.isPublic(field.getModifiers()) || field.isSynthetic()) {
                continue;
            }
            // Check name match
            if (!field.getName().equals(name)) {
                continue;
            }
            return InstanceVariable.of(this, field);
        }
        return null;
    }

    public Set<ConstructorCallable> getDeclaredConstructors(TypeArgument[] typeArguments) {
        final Set<ConstructorCallable> constructors = new HashSet<>();
        for (Constructor<?> constructor : type.getDeclaredConstructors()) {
            // Ignore non-public constructors and synthetic constructors generated by the compiler
            if (!Modifier.isPublic(constructor.getModifiers()) || constructor.isSynthetic()) {
                continue;
            }
            // Try to create a callable for the constructor, might fail if the type arguments are out of bounds
            final ConstructorCallable callable = ConstructorCallable.of(this, constructor, typeArguments);
            if (callable != null) {
                constructors.add(callable);
            }
        }
        return constructors;
    }

    public Set<? extends Callable> getDeclaredMethods(String name, TypeArgument[] typeArguments) {
        // Array clone method must be handled specially because the reflection API doesn't declare it
        if (isArray() && "clone".equals(name)) {
            return Collections.singleton(ArrayCloneCallable.of(this));
        }
        final Set<MethodCallable> methods = new HashSet<>();
        for (Method method : type.getDeclaredMethods()) {
            // Ignore non-public methods and synthetic methods generated by the compiler
            if (!Modifier.isPublic(method.getModifiers()) || method.isSynthetic()) {
                continue;
            }
            // Check name match
            if (!method.getName().equals(name)) {
                continue;
            }
            // Try to create a callable for the method, might fail if the type arguments are out of bounds
            final MethodCallable callable = MethodCallable.of(this, method, typeArguments);
            if (callable != null) {
                methods.add(callable);
            }
        }
        return methods;
    }

    @Override
    public LiteralReferenceType getInnerClass(String name, TypeArgument[] typeArguments) {
        final LinkedHashSet<LiteralReferenceType> superTypes = getSuperTypes();
        for (LiteralReferenceType type : superTypes) {
            final LiteralReferenceType innerClass = type.getDeclaredInnerClass(name, typeArguments);
            if (innerClass != null) {
                return innerClass;
            }
        }
        throw new UnsupportedOperationException("No inner class for signature " + name +
                (typeArguments.length > 0 ? '<' + StringUtil.toString(typeArguments, ", ") + '>' : "") +
                " in " + getName());
    }

    @Override
    public ClassVariable getField(String name) {
        final LinkedHashSet<LiteralReferenceType> superTypes = getSuperTypes();
        for (LiteralReferenceType type : superTypes) {
            final ClassVariable field = type.getDeclaredField(name);
            if (field != null) {
                return field;
            }
        }
        throw new UnsupportedOperationException("No field named " + name + " in " + getName());
    }

    @Override
    public ConstructorCallable getConstructor(TypeArgument[] typeArguments, Type[] arguments) {
        // Get the declared constructors and look for applicable candidates with and without using vararg
        final Set<ConstructorCallable> regularCandidates = new HashSet<>();
        final Set<ConstructorCallable> varargCandidates = new HashSet<>();
        final Set<ConstructorCallable> constructors = getDeclaredConstructors(typeArguments);
        for (ConstructorCallable constructor : constructors) {
            if (constructor.isApplicable(arguments)) {
                regularCandidates.add(constructor);
            }
            if (!constructor.supportsVararg()) {
                continue;
            }
            final ConstructorCallable varargConstructor = constructor.useVararg();
            if (varargConstructor.isApplicable(arguments)) {
                varargCandidates.add(varargConstructor);
            }
        }
        // Resolve overloads
        ConstructorCallable callable = reduceCallableCandidates(regularCandidates, arguments);
        // If the regular callables don't work, try with vararg
        if (callable == null) {
            callable = reduceCallableCandidates(varargCandidates, arguments);
        }
        if (callable != null) {
            return callable.requiresUncheckedConversion(arguments) ? callable.eraseReturnType() : callable;
        }
        throw new UnsupportedOperationException("No constructor for signature: " +
                (typeArguments.length > 0 ? '<' + StringUtil.toString(typeArguments, ", ") + '>' : "") +
                type.getSimpleName() + "(" + StringUtil.toString(Arrays.asList(arguments), ", ") + ") in " + getName());
    }

    @Override
    public Callable getMethod(String name, TypeArgument[] typeArguments, Type[] arguments) {
        // Get the declared methods and look for applicable candidates with and without using vararg
        final Set<Callable> regularCandidates = new HashSet<>();
        final Set<Callable> varargCandidates = new HashSet<>();
        final LinkedHashSet<LiteralReferenceType> superTypes = getSuperTypes();
        for (LiteralReferenceType type : superTypes) {
            // Check that the type is accessible
            if (!type.isPublic()) {
                // TODO: proper access control, for now exclude non-public types
                continue;
            }
            for (Callable method : type.getDeclaredMethods(name, typeArguments)) {
                if (method.isApplicable(arguments)) {
                    regularCandidates.add(method);
                }
                if (!method.supportsVararg()) {
                    continue;
                }
                final Callable varargMethod = method.useVararg();
                if (varargMethod.isApplicable(arguments)) {
                    varargCandidates.add(varargMethod);
                }
            }
        }
        // Resolve overloads
        Callable callable = reduceCallableCandidates(regularCandidates, arguments);
        // If the regular callables don't work, try with vararg
        if (callable == null) {
            callable = reduceCallableCandidates(varargCandidates, arguments);
        }
        if (callable != null) {
            return callable.requiresUncheckedConversion(arguments) ? callable.eraseReturnType() : callable;
        }
        throw new UnsupportedOperationException("No method for signature: " +
                (typeArguments.length > 0 ? '<' + StringUtil.toString(typeArguments, ", ") + '>' : "") +
                name + "(" + StringUtil.toString(Arrays.asList(arguments), ", ") + ") in " + getName());
    }

    private static <C extends Callable> C reduceCallableCandidates(Set<C> candidates, Type[] arguments) {
        // Reduce the regular candidates to keep only the most applicable ones
        for (Iterator<C> iterator = candidates.iterator(); iterator.hasNext(); ) {
            final C candidate1 = iterator.next();
            for (C candidate2 : candidates) {
                // Don't compare with itself
                if (candidate1 == candidate2) {
                    continue;
                }
                // Remove candidate1 is candidate2 is more applicable
                if (candidate2.isMoreApplicableThan(candidate1, arguments)) {
                    iterator.remove();
                    break;
                }
            }
        }
        return candidates.size() != 1 ? null : candidates.iterator().next();
    }

    public Substitutions getSubstitutions() {
        return Substitutions.NONE;
    }

    @Override
    public boolean equals(Object other) {
        return this == other || (other instanceof LiteralReferenceType) && this.type == ((LiteralReferenceType) other).type;
    }

    @Override
    public int hashCode() {
        return type.getName().hashCode();
    }

    public static LiteralReferenceType of(Class<?> type) {
        checkOwner(null, type);
        return new LiteralReferenceType(type);
    }

    protected static ParametrizedType checkOwner(ParametrizedType paramOwner, Class<?> inner) {
        return checkOwner(paramOwner, inner, Collections.<TypeArgument>emptyList());
    }

    protected static ParametrizedType checkOwner(ParametrizedType paramOwner, Class<?> inner, List<TypeArgument> arguments) {
        final LiteralReferenceType owner;
        final Class<?> enclosingClass = inner.getEnclosingClass();
        if (paramOwner != null) {
            // If the owner is given, check that it matches what is actually declared in the code
            if (!paramOwner.getTypeClass().equals(enclosingClass)) {
                throw new UnsupportedOperationException("Mismatch between given owner and actual one: " + paramOwner + " and " + enclosingClass);
            }
            // Also check that the inner type is selectable (that is, not static)
            if (Modifier.isStatic(inner.getModifiers())) {
                throw new UnsupportedOperationException("Inner type " + inner.getSimpleName() + " cannot be used in a static context");
            }
            owner = paramOwner;
        } else if (enclosingClass != null) {
            // If it's not given, check if enclosing class is applicable: the inner class cannot be static
            if (!Modifier.isStatic(inner.getModifiers())) {
                owner = LiteralReferenceType.of(enclosingClass);
            } else {
                // The enclosing class isn't an owner type
                owner = null;
            }
        } else {
            // No enclosing class
            owner = null;
        }
        if (owner != null) {
            // Now that we found an owner, make sure we don't have a partially raw owner/inner combination
            // Owner and inner cannot have one be raw and one not raw
            final boolean ownerParam = owner instanceof ParametrizedType;
            final boolean ownerRaw = owner.isRaw();
            final boolean innerParam = !arguments.isEmpty();
            final boolean innerRaw = inner.getTypeParameters().length > 0 && arguments.isEmpty();
            if ((ownerParam || ownerRaw) && (innerParam || innerRaw) && ownerRaw != innerRaw) {
                throw new UnsupportedOperationException("Cannot have a mix of raw and generic outer and inner classes");
            }
            // We only care of the owner if it is parametrized, as the type arguments are accessible by inner types
            // We want to access those, the rest doesn't matter
            return ownerParam ? (ParametrizedType) owner : null;
        }
        return null;
    }
} 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 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.name.TypeArgumentName;
import ca.sapon.jici.parser.statement.Statement;
import ca.sapon.jici.util.StringUtil;
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.LiteralReferenceType;
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 QualifiedConstructorCall implements Statement, Expression {
    private final Expression target;
    private final Identifier inner;
    private final List<TypeArgumentName> typeArguments;
    private final List<TypeArgumentName> classTypeArguments;
    private final boolean diamondOperator;
    private final List<Expression> arguments;
    private final int classTypeEnd;
    private int start;
    private int end;
    private Callable callable = null;

    public QualifiedConstructorCall(Expression target, Identifier inner, List<TypeArgumentName> typeArguments, List<TypeArgumentName> classTypeArguments, boolean diamondOperator,
                                    List<Expression> arguments, int classTypeEnd, int start, int end) {
        if (diamondOperator && !classTypeArguments.isEmpty()) {
            throw new IllegalArgumentException("Cannot use the diamond operator and have class type arguments at the same time");
        }
        this.target = target;
        this.inner = inner;
        this.typeArguments = typeArguments;
        this.classTypeArguments = classTypeArguments;
        this.diamondOperator = diamondOperator;
        this.arguments = arguments;
        this.classTypeEnd = classTypeEnd;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {