// --------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// --------------------------------------------------------------------------
package ognl;

import ognl.enhance.UnsupportedCompilationException;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Enumeration;

/**
 * This is an abstract class with static methods that define the operations of OGNL.
 *
 * @author Luke Blanshard (blanshlu@netscape.net)
 * @author Drew Davidson (drew@ognl.org)
 */
public abstract class OgnlOps implements NumericTypes
{

    /**
     * Compares two objects for equality, even if it has to convert one of them to the other type.
     * If both objects are numeric they are converted to the widest type and compared. If one is
     * non-numeric and one is numeric the non-numeric is converted to double and compared to the
     * double numeric value. If both are non-numeric and Comparable and the types are compatible
     * (i.e. v1 is of the same or superclass of v2's type) they are compared with
     * Comparable.compareTo(). If both values are non-numeric and not Comparable or of incompatible
     * classes this will throw and IllegalArgumentException.
     *
     * @param v1
     *            First value to compare
     * @param v2
     *            second value to compare
     * @return integer describing the comparison between the two objects. A negative number
     *         indicates that v1 < v2. Positive indicates that v1 > v2. Zero indicates v1 == v2.
     * @throws IllegalArgumentException
     *             if the objects are both non-numeric yet of incompatible types or do not implement
     *             Comparable.
     */
    public static int compareWithConversion(Object v1, Object v2)
    {
        int result;

        if (v1 == v2) {
            result = 0;
        } else {
            int t1 = getNumericType(v1), t2 = getNumericType(v2), type = getNumericType(t1, t2, true);

            switch(type) {
            case BIGINT:
                result = bigIntValue(v1).compareTo(bigIntValue(v2));
                break;

            case BIGDEC:
                result = bigDecValue(v1).compareTo(bigDecValue(v2));
                break;

            case NONNUMERIC:
                if ((t1 == NONNUMERIC) && (t2 == NONNUMERIC)) {
                    if ((v1 instanceof Comparable) && v1.getClass().isAssignableFrom(v2.getClass())) {
                        result = ((Comparable) v1).compareTo(v2);
                        break;
                    } else {
                        throw new IllegalArgumentException("invalid comparison: " + v1.getClass().getName() + " and "
                                + v2.getClass().getName());
                    }
                }
                // else fall through
            case FLOAT:
            case DOUBLE:
                double dv1 = doubleValue(v1),
                dv2 = doubleValue(v2);

                return (dv1 == dv2) ? 0 : ((dv1 < dv2) ? -1 : 1);

            default:
                long lv1 = longValue(v1),
                lv2 = longValue(v2);

                return (lv1 == lv2) ? 0 : ((lv1 < lv2) ? -1 : 1);
            }
        }
        return result;
    }

    /**
     * Returns true if object1 is equal to object2 in either the sense that they are the same object
     * or, if both are non-null if they are equal in the equals() sense.
     *
     * @param object1
     *            First object to compare
     * @param object2
     *            Second object to compare
     * @return true if v1 == v2
     */
    public static boolean isEqual(Object object1, Object object2)
    {
        boolean result = false;

        if (object1 == object2) {
            result = true;
        } else {
            if ((object1 != null) && object1.getClass().isArray()) {
                if ((object2 != null) && object2.getClass().isArray() && (object2.getClass() == object1.getClass())) {
                    result = (Array.getLength(object1) == Array.getLength(object2));
                    if (result) {
                        for(int i = 0, icount = Array.getLength(object1); result && (i < icount); i++) {
                            result = isEqual(Array.get(object1, i), Array.get(object2, i));
                        }
                    }
                }
            } else {
                // Check for converted equivalence first, then equals() equivalence
                result = (object1 != null) && (object2 != null)
                        && (object1.equals(object2) || (compareWithConversion(object1, object2) == 0));
            }
        }
        return result;
    }

    public static boolean booleanValue(boolean value)
    {
        return value;
    }

    public static boolean booleanValue(int value)
    {
        return value > 0;
    }

    public static boolean booleanValue(float value)
    {
        return value > 0;
    }

    public static boolean booleanValue(long value)
    {
        return value > 0;
    }

    public static boolean booleanValue(double value)
    {
        return value > 0;
    }

    /**
     * Evaluates the given object as a boolean: if it is a Boolean object, it's easy; if it's a
     * Number or a Character, returns true for non-zero objects; and otherwise returns true for
     * non-null objects.
     *
     * @param value
     *            an object to interpret as a boolean
     * @return the boolean value implied by the given object
     */
    public static boolean booleanValue(Object value)
    {
        if (value == null)
            return false;
        Class c = value.getClass();

        if (c == Boolean.class)
            return ((Boolean) value).booleanValue();

        if ( c == String.class )
            return Boolean.parseBoolean(String.valueOf(value));

        if (c == Character.class)
            return ((Character) value).charValue() != 0;
        if (value instanceof Number)
            return ((Number) value).doubleValue() != 0;

        return true; // non-null
    }

    /**
     * Evaluates the given object as a long integer.
     *
     * @param value
     *            an object to interpret as a long integer
     * @return the long integer value implied by the given object
     * @throws NumberFormatException
     *             if the given object can't be understood as a long integer
     */
    public static long longValue(Object value)
        throws NumberFormatException
    {
        if (value == null) return 0L;
        Class c = value.getClass();
        if (c.getSuperclass() == Number.class) return ((Number) value).longValue();
        if (c == Boolean.class) return ((Boolean) value).booleanValue() ? 1 : 0;
        if (c == Character.class) return ((Character) value).charValue();
        return Long.parseLong(stringValue(value, true));
    }

    /**
     * Evaluates the given object as a double-precision floating-point number.
     *
     * @param value
     *            an object to interpret as a double
     * @return the double value implied by the given object
     * @throws NumberFormatException
     *             if the given object can't be understood as a double
     */
    public static double doubleValue(Object value)
        throws NumberFormatException
    {
        if (value == null) return 0.0;
        Class c = value.getClass();
        if (c.getSuperclass() == Number.class) return ((Number) value).doubleValue();
        if (c == Boolean.class) return ((Boolean) value).booleanValue() ? 1 : 0;
        if (c == Character.class) return ((Character) value).charValue();
        String s = stringValue(value, true);

        return (s.length() == 0) ? 0.0 : Double.parseDouble(s);
    }

    /**
     * Evaluates the given object as a BigInteger.
     *
     * @param value
     *            an object to interpret as a BigInteger
     * @return the BigInteger value implied by the given object
     * @throws NumberFormatException
     *             if the given object can't be understood as a BigInteger
     */
    public static BigInteger bigIntValue(Object value)
        throws NumberFormatException
    {
        if (value == null) return BigInteger.valueOf(0L);
        Class c = value.getClass();
        if (c == BigInteger.class) return (BigInteger) value;
        if (c == BigDecimal.class) return ((BigDecimal) value).toBigInteger();
        if (c.getSuperclass() == Number.class) return BigInteger.valueOf(((Number) value).longValue());
        if (c == Boolean.class) return BigInteger.valueOf(((Boolean) value).booleanValue() ? 1 : 0);
        if (c == Character.class) return BigInteger.valueOf(((Character) value).charValue());
        return new BigInteger(stringValue(value, true));
    }

    /**
     * Evaluates the given object as a BigDecimal.
     *
     * @param value
     *            an object to interpret as a BigDecimal
     * @return the BigDecimal value implied by the given object
     * @throws NumberFormatException
     *             if the given object can't be understood as a BigDecimal
     */
    public static BigDecimal bigDecValue(Object value)
        throws NumberFormatException
    {
        if (value == null) return BigDecimal.valueOf(0L);
        Class c = value.getClass();
        if (c == BigDecimal.class) return (BigDecimal) value;
        if (c == BigInteger.class) return new BigDecimal((BigInteger) value);
        if (c == Boolean.class) return BigDecimal.valueOf(((Boolean) value).booleanValue() ? 1 : 0);
        if (c == Character.class) return BigDecimal.valueOf(((Character) value).charValue());
        return new BigDecimal(stringValue(value, true));
    }

    /**
     * Evaluates the given object as a String and trims it if the trim flag is true.
     *
     * @param value
     *            an object to interpret as a String
     * @return the String value implied by the given object as returned by the toString() method, or
     *         "null" if the object is null.
     */
    public static String stringValue(Object value, boolean trim)
    {
        String result;

        if (value == null) {
            result = OgnlRuntime.NULL_STRING;
        } else {
            result = value.toString();
            if (trim) {
                result = result.trim();
            }
        }
        return result;
    }

    /**
     * Evaluates the given object as a String.
     *
     * @param value
     *            an object to interpret as a String
     * @return the String value implied by the given object as returned by the toString() method, or
     *         "null" if the object is null.
     */
    public static String stringValue(Object value)
    {
        return stringValue(value, false);
    }

    /**
     * Returns a constant from the NumericTypes interface that represents the numeric type of the
     * given object.
     *
     * @param value
     *            an object that needs to be interpreted as a number
     * @return the appropriate constant from the NumericTypes interface
     */
    public static int getNumericType(Object value)
    {
        if (value != null) {
            Class c = value.getClass();
            if (c == Integer.class) return INT;
            if (c == Double.class) return DOUBLE;
            if (c == Boolean.class) return BOOL;
            if (c == Byte.class) return BYTE;
            if (c == Character.class) return CHAR;
            if (c == Short.class) return SHORT;
            if (c == Long.class) return LONG;
            if (c == Float.class) return FLOAT;
            if (c == BigInteger.class) return BIGINT;
            if (c == BigDecimal.class) return BIGDEC;
        }
        return NONNUMERIC;
    }

    public static Object toArray(char value, Class toType)
    {
        return toArray(new Character(value), toType);
    }

    public static Object toArray(byte value, Class toType)
    {
        return toArray(new Byte(value), toType);
    }

    public static Object toArray(int value, Class toType)
    {
        return toArray(new Integer(value), toType);
    }

    public static Object toArray(long value, Class toType)
    {
        return toArray(new Long(value), toType);
    }

    public static Object toArray(float value, Class toType)
    {
        return toArray(new Float(value), toType);
    }

    public static Object toArray(double value, Class toType)
    {
        return toArray(new Double(value), toType);
    }

    public static Object toArray(boolean value, Class toType)
    {
        return toArray(new Boolean(value), toType);
    }

    public static Object convertValue(char value, Class toType)
    {
        return convertValue(new Character(value), toType);
    }

    public static Object convertValue(byte value, Class toType)
    {
        return convertValue(new Byte(value), toType);
    }

    public static Object convertValue(int value, Class toType)
    {
        return convertValue(new Integer(value), toType);
    }

    public static Object convertValue(long value, Class toType)
    {
        return convertValue(new Long(value), toType);
    }

    public static Object convertValue(float value, Class toType)
    {
        return convertValue(new Float(value), toType);
    }

    public static Object convertValue(double value, Class toType)
    {
        return convertValue(new Double(value), toType);
    }

    public static Object convertValue(boolean value, Class toType)
    {
        return convertValue(new Boolean(value), toType);
    }

    ////////////////////////////////////////////////////////////////

    public static Object convertValue(char value, Class toType, boolean preventNull)
    {
        return convertValue(new Character(value), toType, preventNull);
    }

    public static Object convertValue(byte value, Class toType, boolean preventNull)
    {
        return convertValue(new Byte(value), toType, preventNull);
    }

    public static Object convertValue(int value, Class toType, boolean preventNull)
    {
        return convertValue(new Integer(value), toType, preventNull);
    }

    public static Object convertValue(long value, Class toType, boolean preventNull)
    {
        return convertValue(new Long(value), toType, preventNull);
    }

    public static Object convertValue(float value, Class toType, boolean preventNull)
    {
        return convertValue(new Float(value), toType, preventNull);
    }

    public static Object convertValue(double value, Class toType, boolean preventNull)
    {
        return convertValue(new Double(value), toType, preventNull);
    }

    public static Object convertValue(boolean value, Class toType, boolean preventNull)
    {
        return convertValue(new Boolean(value), toType, preventNull);
    }

    /////////////////////////////////////////////////////////////////

    public static Object toArray(char value, Class toType, boolean preventNull)
    {
        return toArray(new Character(value), toType, preventNull);
    }

    public static Object toArray(byte value, Class toType, boolean preventNull)
    {
        return toArray(new Byte(value), toType, preventNull);
    }

    public static Object toArray(int value, Class toType, boolean preventNull)
    {
        return toArray(new Integer(value), toType, preventNull);
    }

    public static Object toArray(long value, Class toType, boolean preventNull)
    {
        return toArray(new Long(value), toType, preventNull);
    }

    public static Object toArray(float value, Class toType, boolean preventNull)
    {
        return toArray(new Float(value), toType, preventNull);
    }

    public static Object toArray(double value, Class toType, boolean preventNull)
    {
        return toArray(new Double(value), toType, preventNull);
    }

    public static Object toArray(boolean value, Class toType, boolean preventNull)
    {
        return toArray(new Boolean(value), toType, preventNull);
    }

    /**
     * Returns the value converted numerically to the given class type This method also detects when
     * arrays are being converted and converts the components of one array to the type of the other.
     *
     * @param value
     *            an object to be converted to the given type
     * @param toType
     *            class type to be converted to
     * @return converted value of the type given, or value if the value cannot be converted to the
     *         given type.
     */
    public static Object convertValue(Object value, Class toType)
    {
        return convertValue(value, toType, false);
    }

    public static Object toArray(Object value, Class toType)
    {
        return toArray(value, toType, false);
    }

    public static Object toArray(Object value, Class toType, boolean preventNulls)
    {
        if (value == null)
            return null;

        Object result = null;

        if (value.getClass().isArray() && toType.isAssignableFrom(value.getClass().getComponentType()))
            return value;

        if (!value.getClass().isArray()) {

            if (toType == Character.TYPE)
                return stringValue(value).toCharArray();

            if (value instanceof Collection)
                return ((Collection)value).toArray((Object[])Array.newInstance(toType, 0));

            Object arr =  Array.newInstance(toType, 1);
            Array.set(arr, 0, convertValue(value, toType, preventNulls));

            return arr;
        }

        result = Array.newInstance(toType, Array.getLength(value));
        for(int i = 0, icount = Array.getLength(value); i < icount; i++) {
            Array.set(result, i, convertValue(Array.get(value, i), toType));
        }

        if (result == null && preventNulls)
            return value;

        return result;
    }

    public static Object convertValue(Object value, Class toType, boolean preventNulls)
    {
        Object result = null;

        if (value != null && toType.isAssignableFrom(value.getClass()))
            return value;

        if (value != null) {
            /* If array -> array then convert components of array individually */
            if (value.getClass().isArray() && toType.isArray()) {
                Class componentType = toType.getComponentType();

                result = Array.newInstance(componentType, Array.getLength(value));
                for(int i = 0, icount = Array.getLength(value); i < icount; i++) {
                    Array.set(result, i, convertValue(Array.get(value, i), componentType));
                }
            } else if (value.getClass().isArray() && !toType.isArray()) {

                return convertValue(Array.get(value, 0), toType);
            } else if (!value.getClass().isArray() && toType.isArray()){

                if (toType.getComponentType() == Character.TYPE) {

                    result = stringValue(value).toCharArray();
                } else if (toType.getComponentType() == Object.class) {
                    if (value instanceof Collection) {
                        Collection vc = (Collection) value;
                        return vc.toArray(new Object[0]);
                    } else
                        return new Object[] { value };
                }
            } else {
                if ((toType == Integer.class) || (toType == Integer.TYPE)) {
                    result = new Integer((int) longValue(value));
                }
                if ((toType == Double.class) || (toType == Double.TYPE)) result = new Double(doubleValue(value));
                if ((toType == Boolean.class) || (toType == Boolean.TYPE))
                    result = booleanValue(value) ? Boolean.TRUE : Boolean.FALSE;
                if ((toType == Byte.class) || (toType == Byte.TYPE)) result = new Byte((byte) longValue(value));
                if ((toType == Character.class) || (toType == Character.TYPE))
                    result = new Character((char) longValue(value));
                if ((toType == Short.class) || (toType == Short.TYPE)) result = new Short((short) longValue(value));
                if ((toType == Long.class) || (toType == Long.TYPE)) result = new Long(longValue(value));
                if ((toType == Float.class) || (toType == Float.TYPE)) result = new Float(doubleValue(value));
                if (toType == BigInteger.class) result = bigIntValue(value);
                if (toType == BigDecimal.class) result = bigDecValue(value);
                if (toType == String.class) result = stringValue(value);
            }
        } else {
            if (toType.isPrimitive()) {
                result = OgnlRuntime.getPrimitiveDefaultValue(toType);
            } else if (preventNulls && toType == Boolean.class) {
                result = Boolean.FALSE;
            } else if (preventNulls && Number.class.isAssignableFrom(toType)){
                result = OgnlRuntime.getNumericDefaultValue(toType);
            }
        }

        if (result == null && preventNulls)
            return value;

        if (value != null && result == null) {

            throw new IllegalArgumentException("Unable to convert type " + value.getClass().getName() + " of " + value + " to type of " + toType.getName());
        }

        return result;
    }

    /**
     * Converts the specified value to a primitive integer value.
     *
     *

     *
Null values will cause a -1 to be returned.

     *
{@link Number} instances have their intValue() methods invoked.

     *
All other types result in calling Integer.parseInt(value.toString());

     *

     *
     * @param value
     *          The object to get the value of.
     * @return A valid integer.
     */
    public static int getIntValue(Object value)
    {
        try
        {
            if (value == null)
                return -1;

            if (Number.class.isInstance(value)) {

                return ((Number)value).intValue();
            }

            String str = String.class.isInstance(value) ? (String)value : value.toString();

            return Integer.parseInt(str);
        }
        catch (Throwable t)
        {
            throw new RuntimeException("Error converting " + value + " to integer:", t);
        }
    }

    /**
     * Returns the constant from the NumericTypes interface that best expresses the type of a
     * numeric operation on the two given objects.
     *
     * @param v1
     *            one argument to a numeric operator
     * @param v2
     *            the other argument
     * @return the appropriate constant from the NumericTypes interface
     */
    public static int getNumericType(Object v1, Object v2)
    {
        return getNumericType(v1, v2, false);
    }

    /**
     * Returns the constant from the NumericTypes interface that best expresses the type of an
     * operation, which can be either numeric or not, on the two given types.
     *
     * @param t1
     *            type of one argument to an operator
     * @param t2
     *            type of the other argument
     * @param canBeNonNumeric
     *            whether the operator can be interpreted as non-numeric
     * @return the appropriate constant from the NumericTypes interface
     */
    public static int getNumericType(int t1, int t2, boolean canBeNonNumeric)
    {
        if (t1 == t2) return t1;

        if (canBeNonNumeric && (t1 == NONNUMERIC || t2 == NONNUMERIC || t1 == CHAR || t2 == CHAR)) return NONNUMERIC;

        if (t1 == NONNUMERIC) t1 = DOUBLE; // Try to interpret strings as doubles...
        if (t2 == NONNUMERIC) t2 = DOUBLE; // Try to interpret strings as doubles...

        if (t1 >= MIN_REAL_TYPE) {
            if (t2 >= MIN_REAL_TYPE) return Math.max(t1, t2);
            if (t2 < INT) return t1;
            if (t2 == BIGINT) return BIGDEC;
            return Math.max(DOUBLE, t1);
        } else if (t2 >= MIN_REAL_TYPE) {
            if (t1 < INT) return t2;
            if (t1 == BIGINT) return BIGDEC;
            return Math.max(DOUBLE, t2);
        } else return Math.max(t1, t2);
    }

    /**
     * Returns the constant from the NumericTypes interface that best expresses the type of an
     * operation, which can be either numeric or not, on the two given objects.
     *
     * @param v1
     *            one argument to an operator
     * @param v2
     *            the other argument
     * @param canBeNonNumeric
     *            whether the operator can be interpreted as non-numeric
     * @return the appropriate constant from the NumericTypes interface
     */
    public static int getNumericType(Object v1, Object v2, boolean canBeNonNumeric)
    {
        return getNumericType(getNumericType(v1), getNumericType(v2), canBeNonNumeric);
    }

    /**
     * Returns a new Number object of an appropriate type to hold the given integer value. The type
     * of the returned object is consistent with the given type argument, which is a constant from
     * the NumericTypes interface.
     *
     * @param type
     *            the nominal numeric type of the result, a constant from the NumericTypes interface
     * @param value
     *            the integer value to convert to a Number object
     * @return a Number object with the given value, of type implied by the type argument
     */
    public static Number newInteger(int type, long value)
    {
        switch(type) {
        case BOOL:
        case CHAR:
        case INT:
            return new Integer((int) value);

        case FLOAT:
            if ((long) (float) value == value) { return new Float((float) value); }
            // else fall through:
        case DOUBLE:
            if ((long) (double) value == value) { return new Double((double) value); }
            // else fall through:
        case LONG:
            return new Long(value);

        case BYTE:
            return new Byte((byte) value);

        case SHORT:
            return new Short((short) value);

        default:
            return BigInteger.valueOf(value);
        }
    }

    /**
     * Returns a new Number object of an appropriate type to hold the given real value. The type of
     * the returned object is always either Float or Double, and is only Float if the given type tag
     * (a constant from the NumericTypes interface) is FLOAT.
     *
     * @param type
     *            the nominal numeric type of the result, a constant from the NumericTypes interface
     * @param value
     *            the real value to convert to a Number object
     * @return a Number object with the given value, of type implied by the type argument
     */
    public static Number newReal(int type, double value)
    {
        if (type == FLOAT) return new Float((float) value);
        return new Double(value);
    }

    public static Object binaryOr(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2);
        if (type == BIGINT || type == BIGDEC) return bigIntValue(v1).or(bigIntValue(v2));
        return newInteger(type, longValue(v1) | longValue(v2));
    }

    public static Object binaryXor(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2);
        if (type == BIGINT || type == BIGDEC) return bigIntValue(v1).xor(bigIntValue(v2));
        return newInteger(type, longValue(v1) ^ longValue(v2));
    }

    public static Object binaryAnd(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2);
        if (type == BIGINT || type == BIGDEC) return bigIntValue(v1).and(bigIntValue(v2));
        return newInteger(type, longValue(v1) & longValue(v2));
    }

    public static boolean equal(Object v1, Object v2)
    {
        if (v1 == null) return v2 == null;
        if (v1 == v2 || isEqual(v1, v2)) return true;
        if (v1 instanceof Number && v2 instanceof Number)
            return ((Number) v1).doubleValue() == ((Number) v2).doubleValue();
        return false;
    }

    public static boolean less(Object v1, Object v2)
    {
        return compareWithConversion(v1, v2) < 0;
    }

    public static boolean greater(Object v1, Object v2)
    {
        return compareWithConversion(v1, v2) > 0;
    }

    public static boolean in(Object v1, Object v2)
        throws OgnlException
    {
        if (v2 == null) // A null collection is always treated as empty
            return false;

        ElementsAccessor elementsAccessor = OgnlRuntime.getElementsAccessor(OgnlRuntime.getTargetClass(v2));

        for(Enumeration e = elementsAccessor.getElements(v2); e.hasMoreElements();) {
            Object o = e.nextElement();

            if (equal(v1, o))
                return true;
        }

        return false;
    }

    public static Object shiftLeft(Object v1, Object v2)
    {
        int type = getNumericType(v1);
        if (type == BIGINT || type == BIGDEC) return bigIntValue(v1).shiftLeft((int) longValue(v2));
        return newInteger(type, longValue(v1) << (int) longValue(v2));
    }

    public static Object shiftRight(Object v1, Object v2)
    {
        int type = getNumericType(v1);
        if (type == BIGINT || type == BIGDEC) return bigIntValue(v1).shiftRight((int) longValue(v2));
        return newInteger(type, longValue(v1) >> (int) longValue(v2));
    }

    public static Object unsignedShiftRight(Object v1, Object v2)
    {
        int type = getNumericType(v1);
        if (type == BIGINT || type == BIGDEC) return bigIntValue(v1).shiftRight((int) longValue(v2));
        if (type <= INT) return newInteger(INT, ((int) longValue(v1)) >>> (int) longValue(v2));
        return newInteger(type, longValue(v1) >>> (int) longValue(v2));
    }

    public static Object add(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2, true);
        switch(type) {
        case BIGINT:
            return bigIntValue(v1).add(bigIntValue(v2));
        case BIGDEC:
            return bigDecValue(v1).add(bigDecValue(v2));
        case FLOAT:
        case DOUBLE:
            return newReal(type, doubleValue(v1) + doubleValue(v2));
        case NONNUMERIC:
            int t1 = getNumericType(v1),
            t2 = getNumericType(v2);

            if (((t1 != NONNUMERIC) && (v2 == null)) || ((t2 != NONNUMERIC) && (v1 == null))) {
                throw new NullPointerException("Can't add values " + v1 + " , " + v2);
            }

            return stringValue(v1) + stringValue(v2);
        default:
            return newInteger(type, longValue(v1) + longValue(v2));
        }
    }

    public static Object subtract(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2);
        switch(type) {
        case BIGINT:
            return bigIntValue(v1).subtract(bigIntValue(v2));
        case BIGDEC:
            return bigDecValue(v1).subtract(bigDecValue(v2));
        case FLOAT:
        case DOUBLE:
            return newReal(type, doubleValue(v1) - doubleValue(v2));
        default:
            return newInteger(type, longValue(v1) - longValue(v2));
        }
    }

    public static Object multiply(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2);
        switch(type) {
        case BIGINT:
            return bigIntValue(v1).multiply(bigIntValue(v2));
        case BIGDEC:
            return bigDecValue(v1).multiply(bigDecValue(v2));
        case FLOAT:
        case DOUBLE:
            return newReal(type, doubleValue(v1) * doubleValue(v2));
        default:
            return newInteger(type, longValue(v1) * longValue(v2));
        }
    }

    public static Object divide(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2);
        switch(type) {
        case BIGINT:
            return bigIntValue(v1).divide(bigIntValue(v2));
        case BIGDEC:
            return bigDecValue(v1).divide(bigDecValue(v2), BigDecimal.ROUND_HALF_EVEN);
        case FLOAT:
        case DOUBLE:
            return newReal(type, doubleValue(v1) / doubleValue(v2));
        default:
            return newInteger(type, longValue(v1) / longValue(v2));
        }
    }

    public static Object remainder(Object v1, Object v2)
    {
        int type = getNumericType(v1, v2);
        switch(type) {
        case BIGDEC:
        case BIGINT:
            return bigIntValue(v1).remainder(bigIntValue(v2));
        default:
            return newInteger(type, longValue(v1) % longValue(v2));
        }
    }

    public static Object negate(Object value)
    {
        int type = getNumericType(value);
        switch(type) {
        case BIGINT:
            return bigIntValue(value).negate();
        case BIGDEC:
            return bigDecValue(value).negate();
        case FLOAT:
        case DOUBLE:
            return newReal(type, -doubleValue(value));
        default:
            return newInteger(type, -longValue(value));
        }
    }

    public static Object bitNegate(Object value)
    {
        int type = getNumericType(value);
        switch(type) {
        case BIGDEC:
        case BIGINT:
            return bigIntValue(value).not();
        default:
            return newInteger(type, ~longValue(value));
        }
    }

    public static String getEscapeString(String value)
    {
        StringBuffer result = new StringBuffer();

        for(int i = 0, icount = value.length(); i < icount; i++) {
            result.append(getEscapedChar(value.charAt(i)));
        }
        return new String(result);
    }

    public static String getEscapedChar(char ch)
    {
        String result;

        switch(ch) {
        case '\b':
            result = "\b";
            break;
        case '\t':
            result = "\\t";
            break;
        case '\n':
            result = "\\n";
            break;
        case '\f':
            result = "\\f";
            break;
        case '\r':
            result = "\\r";
            break;
        case '\"':
            result = "\\\"";
            break;
        case '\'':
            result = "\\\'";
            break;
        case '\\':
            result = "\\\\";
            break;
        default:
            if (Character.isISOControl(ch)) {

                String hc = Integer.toString((int) ch, 16);
                int hcl = hc.length();

                result = "\\u";
                if (hcl < 4) {
                    if (hcl == 3) {
                        result = result + "0";
                    } else {
                        if (hcl == 2) {
                            result = result + "00";
                        } else {
                            result = result + "000";
                        }
                    }
                }

                result = result + hc;
            } else {
                result = new String(ch + "");
            }
            break;
        }
        return result;
    }

    public static Object returnValue(Object ignore, Object returnValue)
    {
        return returnValue;
    }

    /**
     * Utility method that converts incoming exceptions to {@link RuntimeException}
     * instances - or casts them if they already are.
     *
     * @param t
     *      The exception to cast.
     * @return The exception cast to a {@link RuntimeException}.
     */
    public static RuntimeException castToRuntime(Throwable t)
    {
        if (RuntimeException.class.isInstance(t))
            return (RuntimeException)t;

        if (OgnlException.class.isInstance(t))
            throw new UnsupportedCompilationException("Error evluating expression: " + t.getMessage(), t);

        return new RuntimeException(t);
    }
}


//--------------------------------------------------------------------------
//  Copyright (c) 2004, Drew Davidson and Luke Blanshard
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//
//  Redistributions of source code must retain the above copyright notice,
//  this list of conditions and the following disclaimer.
//  Redistributions in binary form must reproduce the above copyright
//  notice, this list of conditions and the following disclaimer in the
//  documentation and/or other materials provided with the distribution.
//  Neither the name of the Drew Davidson nor the names of its contributors
//  may be used to endorse or promote products derived from this software
//  without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
//  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
//  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
//  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
//  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
//  DAMAGE.
//--------------------------------------------------------------------------
package ognl.enhance;

public class EnhancedClassLoader extends ClassLoader
{
	/*===================================================================
		Constructors
	  ===================================================================*/
    public EnhancedClassLoader(ClassLoader parentClassLoader)
    {
        super(parentClassLoader);
    }

	/*===================================================================
		Overridden methods
	  ===================================================================*/
    public Class defineClass(String enhancedClassName, byte[] byteCode)
    {
        return defineClass(enhancedClassName, byteCode, 0, byteCode.length);
    }
}


// --------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// --------------------------------------------------------------------------
package ognl;

import ognl.enhance.LocalReference;

import java.util.*;

/**
 * This class defines the execution context for an OGNL expression
 *
 * @author Luke Blanshard (blanshlu@netscape.net)
 * @author Drew Davidson (drew@ognl.org)
 */
public class OgnlContext extends Object implements Map
{

    public static final String ROOT_CONTEXT_KEY = "root";
    public static final String THIS_CONTEXT_KEY = "this";
    public static final String TRACE_EVALUATIONS_CONTEXT_KEY = "_traceEvaluations";
    public static final String LAST_EVALUATION_CONTEXT_KEY = "_lastEvaluation";
    public static final String KEEP_LAST_EVALUATION_CONTEXT_KEY = "_keepLastEvaluation";

    @Deprecated
    public static final String TYPE_CONVERTER_CONTEXT_KEY = "_typeConverter";

    private static final String PROPERTY_KEY_PREFIX = "ognl";
    private static boolean DEFAULT_TRACE_EVALUATIONS = false;
    private static boolean DEFAULT_KEEP_LAST_EVALUATION = false;

    private static final Map<Object, Object> RESERVED_KEYS = new HashMap<>(6);

    private Object _root;
    private Object _currentObject;
    private Node _currentNode;
    private boolean _traceEvaluations = DEFAULT_TRACE_EVALUATIONS;
    private Evaluation _rootEvaluation;
    private Evaluation _currentEvaluation;
    private Evaluation _lastEvaluation;
    private boolean _keepLastEvaluation = DEFAULT_KEEP_LAST_EVALUATION;

    private final Map _values;

    private final ClassResolver _classResolver;
    private final TypeConverter _typeConverter;
    private final MemberAccess _memberAccess;

    static {
        String s;

        RESERVED_KEYS.put(ROOT_CONTEXT_KEY, null);
        RESERVED_KEYS.put(THIS_CONTEXT_KEY, null);
        RESERVED_KEYS.put(TRACE_EVALUATIONS_CONTEXT_KEY, null);
        RESERVED_KEYS.put(LAST_EVALUATION_CONTEXT_KEY, null);
        RESERVED_KEYS.put(KEEP_LAST_EVALUATION_CONTEXT_KEY, null);

        try {
            if ((s = System.getProperty(PROPERTY_KEY_PREFIX + ".traceEvaluations")) != null) {
                DEFAULT_TRACE_EVALUATIONS = Boolean.valueOf(s.trim());
            }
            if ((s = System.getProperty(PROPERTY_KEY_PREFIX + ".keepLastEvaluation")) != null) {
                DEFAULT_KEEP_LAST_EVALUATION = Boolean.valueOf(s.trim());
            }
        } catch (SecurityException ex) {
            // restricted access environment, just keep defaults
        }
    }

    private final List<Class<?>> _typeStack = new ArrayList<>(3);     // size 3 should be enough stack for most expressions
    private final List<Class<?>> _accessorStack = new ArrayList<>(3); // size 3 should be enough stack for most expressions

    private int _localReferenceCounter = 0;
    private Map<String, LocalReference> _localReferenceMap = null;

    /**
     * Constructs a new OgnlContext with the given class resolver, type converter and member access.
     * If any of these parameters is null the default will be used.
     */
    public OgnlContext(ClassResolver classResolver, TypeConverter typeConverter, MemberAccess memberAccess)
    {
        // No 'values' map has been specified, so we create one of the default size: 23 entries
        this(memberAccess, classResolver, typeConverter, new HashMap(23));
    }

    /**
     * @deprecated use on of the constructors which require {@link MemberAccess}, this one can be removed soon
     */
    @Deprecated
    public OgnlContext(Map values)
    {
        // nulls will prevent overriding the default class resolver, type converter and member access
        this(null, null, null, values);
    }

    public OgnlContext(MemberAccess memberAccess, ClassResolver classResolver, TypeConverter typeConverter, Map values)
    {
        super();
        this._values = values;
        if (classResolver != null) {
            this._classResolver = classResolver;
        } else {
            this._classResolver = new DefaultClassResolver();
        }
        if (typeConverter != null) {
            this._typeConverter = typeConverter;
        } else {
            this._typeConverter = new DefaultTypeConverter();
        }
        if (memberAccess != null) {
            this._memberAccess = memberAccess;
        } else {
            throw new RuntimeException("MemberAccess implementation must be provided!");
        }
    }

    public void setValues(Map value)
    {
        for (Object k : value.keySet()) {
            _values.put(k, value.get(k));
        }
    }

    public Map getValues()
    {
        return _values;
    }

    @Deprecated
    public void setClassResolver(ClassResolver ignore) {
        // no-op
    }

    public ClassResolver getClassResolver()
    {
        return _classResolver;
    }

    @Deprecated
    public void setTypeConverter(TypeConverter ignore)
    {
        // no-op
    }

    public TypeConverter getTypeConverter()
    {
        return _typeConverter;
    }

    @Deprecated
    public void setMemberAccess(MemberAccess ignore)
    {
        // no-op
    }

    public MemberAccess getMemberAccess()
    {
        return _memberAccess;
    }

    public void setRoot(Object value)
    {
        _root = value;
        _accessorStack.clear();
        _typeStack.clear();
        _currentObject = value;

        if (_currentObject != null)
        {
            setCurrentType(_currentObject.getClass());
        }
    }

    public Object getRoot()
    {
        return _root;
    }

    public boolean getTraceEvaluations()
    {
        return _traceEvaluations;
    }

    public void setTraceEvaluations(boolean value)
    {
        _traceEvaluations = value;
    }

    public Evaluation getLastEvaluation()
    {
        return _lastEvaluation;
    }

    public void setLastEvaluation(Evaluation value)
    {
        _lastEvaluation = value;
    }

    /**
     * This method can be called when the last evaluation has been used and can be returned for
     * reuse in the free pool maintained by the runtime. This is not a necessary step, but is useful
     * for keeping memory usage down. This will recycle the last evaluation and then set the last
     * evaluation to null.
     *
     * @deprecated since 3.2
     */
    @Deprecated
    public void recycleLastEvaluation()
    {
        OgnlRuntime.getEvaluationPool().recycleAll(_lastEvaluation);
        _lastEvaluation = null;
    }

    /**
     * Returns true if the last evaluation that was done on this context is retained and available
     * through getLastEvaluation(). The default is true.
     */
    public boolean getKeepLastEvaluation()
    {
        return _keepLastEvaluation;
    }

    /**
     * Sets whether the last evaluation that was done on this context is retained and available
     * through getLastEvaluation(). The default is true.
     */
    public void setKeepLastEvaluation(boolean value)
    {
        _keepLastEvaluation = value;
    }

    public void setCurrentObject(Object value)
    {
        _currentObject = value;
    }

    public Object getCurrentObject()
    {
        return _currentObject;
    }

    public void setCurrentAccessor(Class type)
    {
        _accessorStack.add(type);
    }

    public Class getCurrentAccessor()
    {
        if (_accessorStack.isEmpty())
            return null;

        return _accessorStack.get(_accessorStack.size() - 1);
    }

    public Class getPreviousAccessor()
    {
        if (_accessorStack.isEmpty())
            return null;

        if (_accessorStack.size() > 1)
            return _accessorStack.get(_accessorStack.size() - 2);
        else
            return null;
    }

    public Class getFirstAccessor()
    {
        if (_accessorStack.isEmpty())
            return null;

        return _accessorStack.get(0);
    }

    /**
     * Gets the current class type being evaluated on the stack, as set by {@link #setCurrentType(Class)}.
     *
     * @return The current object type, may be null.
     */
    public Class getCurrentType()
    {
        if (_typeStack.isEmpty())
            return null;

       return (Class) _typeStack.get(_typeStack.size() - 1);
    }

    public void setCurrentType(Class type)
    {
        _typeStack.add(type);
    }

    /**
     * Represents the last known object type on the evaluation stack, will be the value of
     * the last known {@link #getCurrentType()}.
     *
     * @return The previous type of object on the stack, may be null.
     */
    public Class getPreviousType()
    {
        if (_typeStack.isEmpty())
            return null;

        if (_typeStack.size() > 1)
            return _typeStack.get(_typeStack.size() - 2);
        else
            return null;
    }

    public void setPreviousType(Class type)
    {
        if (_typeStack.isEmpty() || _typeStack.size() < 2)
            return;

        _typeStack.set(_typeStack.size() - 2, type);
    }

    public Class getFirstType()
    {
        if (_typeStack.isEmpty())
            return null;

        return _typeStack.get(0);
    }

    public void setCurrentNode(Node value)
    {
        _currentNode = value;
    }

    public Node getCurrentNode()
    {
        return _currentNode;
    }

    /**
     * Gets the current Evaluation from the top of the stack. This is the Evaluation that is in
     * process of evaluating.
     */
    public Evaluation getCurrentEvaluation()
    {
        return _currentEvaluation;
    }

    public void setCurrentEvaluation(Evaluation value)
    {
        _currentEvaluation = value;
    }

    /**
     * Gets the root of the evaluation stack. This Evaluation contains the node representing the
     * root expression and the source is the root source object.
     */
    public Evaluation getRootEvaluation()
    {
        return _rootEvaluation;
    }

    public void setRootEvaluation(Evaluation value)
    {
        _rootEvaluation = value;
    }

    /**
     * Returns the Evaluation at the relative index given. This should be zero or a negative number
     * as a relative reference back up the evaluation stack. Therefore getEvaluation(0) returns the
     * current Evaluation.
     */
    public Evaluation getEvaluation(int relativeIndex)
    {
        Evaluation result = null;

        if (relativeIndex <= 0) {
            result = _currentEvaluation;
            while((++relativeIndex < 0) && (result != null)) {
                result = result.getParent();
            }
        }
        return result;
    }

    /**
     * Pushes a new Evaluation onto the stack. This is done before a node evaluates. When evaluation
     * is complete it should be popped from the stack via popEvaluation().
     */
    public void pushEvaluation(Evaluation value)
    {
        if (_currentEvaluation != null) {
            _currentEvaluation.addChild(value);
        } else {
            setRootEvaluation(value);
        }
        setCurrentEvaluation(value);
    }

    /**
     * Pops the current Evaluation off of the top of the stack. This is done after a node has
     * completed its evaluation.
     */
    public Evaluation popEvaluation()
    {
        Evaluation result;

        result = _currentEvaluation;
        setCurrentEvaluation(result.getParent());
        if (_currentEvaluation == null) {
            setLastEvaluation(getKeepLastEvaluation() ? result : null);
            setRootEvaluation(null);
            setCurrentNode(null);
        }
        return result;
    }

    public int incrementLocalReferenceCounter()
    {
        return ++_localReferenceCounter;
    }

    public void addLocalReference(String key, LocalReference reference)
    {
        if (_localReferenceMap == null)
        {
            _localReferenceMap = new LinkedHashMap<>();
        }

        _localReferenceMap.put(key, reference);
    }

    public Map getLocalReferences()
    {
        return _localReferenceMap;
    }

    /* ================= Map interface ================= */
    public int size()
    {
        return _values.size();
    }

    public boolean isEmpty()
    {
        return _values.isEmpty();
    }

    public boolean containsKey(Object key)
    {
        return _values.containsKey(key);
    }

    public boolean containsValue(Object value)
    {
        return _values.containsValue(value);
    }

    public Object get(Object key)
    {
        Object result;

        if (RESERVED_KEYS.containsKey(key)) {
            if (key.equals(OgnlContext.THIS_CONTEXT_KEY)) {
                result = getCurrentObject();
            } else {
                if (key.equals(OgnlContext.ROOT_CONTEXT_KEY)) {
                    result = getRoot();
                } else {
                    if (key.equals(OgnlContext.TRACE_EVALUATIONS_CONTEXT_KEY)) {
                        result = getTraceEvaluations() ? Boolean.TRUE : Boolean.FALSE;
                    } else {
                        if (key.equals(OgnlContext.LAST_EVALUATION_CONTEXT_KEY)) {
                            result = getLastEvaluation();
                        } else {
                            if (key.equals(OgnlContext.KEEP_LAST_EVALUATION_CONTEXT_KEY)) {
                                result = getKeepLastEvaluation() ? Boolean.TRUE : Boolean.FALSE;
                            } else {
                                throw new IllegalArgumentException("unknown reserved key '" + key + "'");
                            }
                        }
                    }
                }
            }
        } else {
            result = _values.get(key);
        }
        return result;
    }

    public Object put(Object key, Object value)
    {
        Object result;

        if (RESERVED_KEYS.containsKey(key)) {
            if (key.equals(OgnlContext.THIS_CONTEXT_KEY)) {
                result = getCurrentObject();
                setCurrentObject(value);
            } else {
                if (key.equals(OgnlContext.ROOT_CONTEXT_KEY)) {
                    result = getRoot();
                    setRoot(value);
                } else {
                    if (key.equals(OgnlContext.TRACE_EVALUATIONS_CONTEXT_KEY)) {
                        result = getTraceEvaluations() ? Boolean.TRUE : Boolean.FALSE;
                        setTraceEvaluations(OgnlOps.booleanValue(value));
                    } else {
                        if (key.equals(OgnlContext.LAST_EVALUATION_CONTEXT_KEY)) {
                            result = getLastEvaluation();
                            _lastEvaluation = (Evaluation) value;
                        } else {
                            if (key.equals(OgnlContext.KEEP_LAST_EVALUATION_CONTEXT_KEY)) {
                                result = getKeepLastEvaluation() ? Boolean.TRUE : Boolean.FALSE;
                                setKeepLastEvaluation(OgnlOps.booleanValue(value));
                            } else {
                                throw new IllegalArgumentException("unknown reserved key '" + key + "'");
                            }
                        }
                    }
                }
            }
        } else {
            result = _values.put(key, value);
        }

        return result;
    }

    public Object remove(Object key)
    {
        Object result;

        if (RESERVED_KEYS.containsKey(key)) {
            if (key.equals(OgnlContext.THIS_CONTEXT_KEY)) {
                result = getCurrentObject();
                setCurrentObject(null);
            } else {
                if (key.equals(OgnlContext.ROOT_CONTEXT_KEY)) {
                    result = getRoot();
                    setRoot(null);
                } else {
                    if (key.equals(OgnlContext.TRACE_EVALUATIONS_CONTEXT_KEY)) {
                        throw new IllegalArgumentException("can't remove "
                                + OgnlContext.TRACE_EVALUATIONS_CONTEXT_KEY + " from context");
                    } else {
                        if (key.equals(OgnlContext.LAST_EVALUATION_CONTEXT_KEY)) {
                            result = _lastEvaluation;
                            setLastEvaluation(null);
                        } else {
                            if (key.equals(OgnlContext.KEEP_LAST_EVALUATION_CONTEXT_KEY)) {
                                throw new IllegalArgumentException("can't remove "
                                        + OgnlContext.KEEP_LAST_EVALUATION_CONTEXT_KEY + " from context");
                            } else {
                                throw new IllegalArgumentException("unknown reserved key '" + key + "'");
                            }
                        }
                    }
                }
            }
        } else {
            result = _values.remove(key);
        }
        return result;
    }

    public void putAll(Map t)
    {
        for (Object k : t.keySet()) {
            put(k, t.get(k));
        }
    }

    public void clear()
    {
        _values.clear();
        _typeStack.clear();
        _accessorStack.clear();

        _localReferenceCounter = 0;
        if (_localReferenceMap != null)
        {
            _localReferenceMap.clear();
        }

        setRoot(null);
        setCurrentObject(null);
        setRootEvaluation(null);
        setCurrentEvaluation(null);
        setLastEvaluation(null);
        setCurrentNode(null);
    }

    public Set keySet()
    {
        /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */
        return _values.keySet();
    }

    public Collection values()
    {
        /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */
        return _values.values();
    }

    public Set entrySet()
    {
        /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */
        return _values.entrySet();
    }

    public boolean equals(Object o)
    {
        return _values.equals(o);
    }

    public int hashCode()
    {
        return _values.hashCode();
    }
}


package ognl.enhance;

import javassist.*;
import ognl.*;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Responsible for managing/providing functionality related to compiling generated java source
 * expressions via bytecode enhancements for a given ognl expression.
 */
public class ExpressionCompiler implements OgnlExpressionCompiler {

    /**
     * Key used to store any java source string casting statements in the {@link OgnlContext} during
     * class compilation.
     */
    public static final String PRE_CAST = "_preCast";

    /**
     * {@link ClassLoader} instances.
     */
    protected Map _loaders = new HashMap();

    /**
     * Javassist class definition poool.
     */
    protected ClassPool _pool;

    protected int _classCounter = 0;

    /**
     * Default constructor, does nothing.
     */
    public ExpressionCompiler()
    {
    }

    /**
     * Used by {@link #castExpression(ognl.OgnlContext, ognl.Node, String)} to store the cast java
     * source string in to the current {@link OgnlContext}. This will either add to the existing
     * string present if it already exists or create a new instance and store it using the static key
     * of {@link #PRE_CAST}.
     *
     * @param context
     *          The current execution context.
     * @param cast
     *          The java source string to store in to the context.
     */
    public static void addCastString(OgnlContext context, String cast)
    {
        String value = (String) context.get(PRE_CAST);

        if (value != null)
            value = cast + value;
        else
            value = cast;

        context.put(PRE_CAST, value);
    }

    /**
     * Returns the appropriate casting expression (minus parens) for the specified class type.
     *
     *

     * For instance, if given an {@link Integer} object the string "java.lang.Integer"
     * would be returned. For an array of primitive ints "int[]" and so on..
     *

     *
     * @param type The class to cast a string expression for.
     * @return The converted raw string version of the class name.
     */
    public static String getCastString(Class type)
    {
        if (type == null)
            return null;

        return type.isArray() ? type.getComponentType().getName() + "[]" : type.getName();
    }

    /**
     * Convenience method called by many different property/method resolving AST types to get a root expression
     * resolving string for the given node.  The callers are mostly ignorant and rely on this method to properly
     * determine if the expression should be cast at all and take the appropriate actions if it should.
     *
     * @param expression
     *          The node to check and generate a root expression to if necessary.
     * @param root
     *          The root object for this execution.
     * @param context
     *          The current execution context.
     * @return Either an empty string or a root path java source string compatible with javassist compilations
     *          from the root object up to the specified {@link Node}.
     */
    public static String getRootExpression(Node expression, Object root, OgnlContext context)
    {
        String rootExpr = "";

        if (!shouldCast(expression))
            return rootExpr;

        if ((!ASTList.class.isInstance(expression)
             && !ASTVarRef.class.isInstance(expression)
             && !ASTStaticMethod.class.isInstance(expression)
             && !ASTStaticField.class.isInstance(expression)
             && !ASTConst.class.isInstance(expression)
             && !ExpressionNode.class.isInstance(expression)
             && !ASTCtor.class.isInstance(expression)
             && !ASTStaticMethod.class.isInstance(expression)
             && root != null) || (root != null && ASTRootVarRef.class.isInstance(expression))) {

            Class castClass = OgnlRuntime.getCompiler().getRootExpressionClass(expression, context);

            if (castClass.isArray() || ASTRootVarRef.class.isInstance(expression)
                || ASTThisVarRef.class.isInstance(expression))
            {
                rootExpr = "((" + getCastString(castClass) + ")$2)";

                if (ASTProperty.class.isInstance(expression) && !((ASTProperty) expression).isIndexedAccess())
                    rootExpr += ".";
            } else if ((ASTProperty.class.isInstance(expression)
                        && ((ASTProperty) expression).isIndexedAccess())
                       || ASTChain.class.isInstance(expression))
            {
                rootExpr = "((" + getCastString(castClass) + ")$2)";
            } else
            {
                rootExpr = "((" + getCastString(castClass) + ")$2).";
            }
        }

        return rootExpr;
    }

    /**
     * Used by {@link #getRootExpression(ognl.Node, Object, ognl.OgnlContext)} to determine if the expression
     * needs to be cast at all.
     *
     * @param expression
     *          The node to check against.
     * @return Yes if the node type should be cast - false otherwise.
     */
    public static boolean shouldCast(Node expression)
    {
        if (ASTChain.class.isInstance(expression))
        {
            Node child = expression.jjtGetChild(0);
            if (ASTConst.class.isInstance(child)
                || ASTStaticMethod.class.isInstance(child)
                || ASTStaticField.class.isInstance(child)
                || (ASTVarRef.class.isInstance(child) && !ASTRootVarRef.class.isInstance(child)))
                return false;
        }

        return !ASTConst.class.isInstance(expression);
    }

    public String castExpression(OgnlContext context, Node expression, String body)
    {
        // ok - so this looks really f-ed up ...and it is ..eh if you can do it better I'm all for it :)

        if (context.getCurrentAccessor() == null
            || context.getPreviousType() == null
            || context.getCurrentAccessor().isAssignableFrom(context.getPreviousType())
            || (context.getCurrentType() != null
                && context.getCurrentObject() != null
                && context.getCurrentType().isAssignableFrom(context.getCurrentObject().getClass())
                && context.getCurrentAccessor().isAssignableFrom(context.getPreviousType()))
            || body == null || body.trim().length() < 1
            || (context.getCurrentType() != null && context.getCurrentType().isArray()
                && (context.getPreviousType() == null || context.getPreviousType() != Object.class))
            || ASTOr.class.isInstance(expression)
            || ASTAnd.class.isInstance(expression)
            || ASTRootVarRef.class.isInstance(expression)
            || context.getCurrentAccessor() == Class.class
            || (context.get(ExpressionCompiler.PRE_CAST) != null && ((String) context.get(ExpressionCompiler.PRE_CAST)).startsWith("new"))
            || ASTStaticField.class.isInstance(expression)
            || ASTStaticMethod.class.isInstance(expression)
            || (OrderedReturn.class.isInstance(expression) && ((OrderedReturn) expression).getLastExpression() != null))
            return body;

/*         System.out.println("castExpression() with expression " + expression + " expr class: " + expression.getClass() + " currentType is: " + context.getCurrentType()
                      + " previousType: " + context.getPreviousType()
                      + "\n current Accessor: " + context.getCurrentAccessor()
                      + " previous Accessor: " + context.getPreviousAccessor()
                      + " current object " + context.getCurrentObject());*/

        ExpressionCompiler.addCastString(context, "((" + ExpressionCompiler.getCastString(context.getCurrentAccessor()) + ")");

        return ")" + body;
    }

    public String getClassName(Class clazz)
    {
        if (clazz.getName().equals("java.util.AbstractList$Itr"))
            return Iterator.class.getName();

        if (Modifier.isPublic(clazz.getModifiers()) && clazz.isInterface())
            return clazz.getName();

        return _getClassName(clazz, clazz.getInterfaces());
    }

    private String _getClassName(Class clazz, Class[] intf)
    {
        for (int i = 0; i < intf.length; i++)
        {
            if (intf[i].getName().indexOf("util.List") > 0)
                return intf[i].getName();
            else if (intf[i].getName().indexOf("Iterator") > 0)
                return intf[i].getName();
        }

        final Class superclazz = clazz.getSuperclass();
        if (superclazz != null)
        {
            final Class[] superclazzIntf = superclazz.getInterfaces();
            if (superclazzIntf.length > 0)
                return _getClassName(superclazz, superclazzIntf);
        }

        return clazz.getName();
    }

    public Class getSuperOrInterfaceClass(Method m, Class clazz)
    {
        Class[] intfs = clazz.getInterfaces();
        if (intfs != null && intfs.length > 0)
        {
            Class intClass;

            for (int i = 0; i < intfs.length; i++)
            {
                intClass = getSuperOrInterfaceClass(m, intfs[i]);

                if (intClass != null)
                    return intClass;

                if (Modifier.isPublic(intfs[i].getModifiers()) && containsMethod(m, intfs[i]))
                    return intfs[i];
            }
        }

        if (clazz.getSuperclass() != null)
        {
            Class superClass = getSuperOrInterfaceClass(m, clazz.getSuperclass());

            if (superClass != null)
                return superClass;
        }

        if (Modifier.isPublic(clazz.getModifiers()) && containsMethod(m, clazz))
            return clazz;

        return null;
    }

    /**
     * Helper utility method used by compiler to help resolve class->method mappings
     * during method calls to {@link OgnlExpressionCompiler#getSuperOrInterfaceClass(java.lang.reflect.Method, Class)}.
     *
     * @param m
     *          The method to check for existance of.
     * @param clazz
     *          The class to check for the existance of a matching method definition to the method passed in.
     * @return True if the class contains the specified method, false otherwise.
     */
    public boolean containsMethod(Method m, Class clazz)
    {
        Method[] methods = clazz.getMethods();

        if (methods == null)
            return false;

        for (int i = 0; i < methods.length; i++)
        {
            if (methods[i].getName().equals(m.getName())
                && methods[i].getReturnType() == m.getReturnType())
            {
                Class[] parms = m.getParameterTypes();
                if (parms == null)
                    continue;

                Class[] mparms = methods[i].getParameterTypes();
                if (mparms == null || mparms.length != parms.length)
                    continue;

                boolean parmsMatch = true;
                for (int p = 0; p < parms.length; p++)
                {
                    if (parms[p] != mparms[p])
                    {
                        parmsMatch = false;
                        break;
                    }
                }

                if (!parmsMatch)
                    continue;

                Class[] exceptions = m.getExceptionTypes();
                if (exceptions == null)
                    continue;

                Class[] mexceptions = methods[i].getExceptionTypes();
                if (mexceptions == null || mexceptions.length != exceptions.length)
                    continue;

                boolean exceptionsMatch = true;
                for (int e = 0; e < exceptions.length; e++)
                {
                    if (exceptions[e] != mexceptions[e])
                    {
                        exceptionsMatch = false;
                        break;
                    }
                }

                if (!exceptionsMatch)
                    continue;

                return true;
            }
        }

        return false;
    }

    public Class getInterfaceClass(Class clazz)
    {
        if (clazz.getName().equals("java.util.AbstractList$Itr"))
            return Iterator.class;

        if (Modifier.isPublic(clazz.getModifiers())
            && clazz.isInterface() || clazz.isPrimitive())
            return clazz;

        return _getInterfaceClass(clazz, clazz.getInterfaces());
    }

    private Class _getInterfaceClass(Class clazz, Class[] intf)
    {
        for (int i = 0; i < intf.length; i++)
        {
            if (List.class.isAssignableFrom(intf[i]))
                return List.class;
            else if (Iterator.class.isAssignableFrom(intf[i]))
                return Iterator.class;
            else if (Map.class.isAssignableFrom(intf[i]))
                return Map.class;
            else if (Set.class.isAssignableFrom(intf[i]))
                return Set.class;
            else if (Collection.class.isAssignableFrom(intf[i]))
                return Collection.class;
        }

        final Class superclazz = clazz.getSuperclass();
        if (superclazz != null)
        {
            final Class[] superclazzIntf = superclazz.getInterfaces();
            if (superclazzIntf.length > 0)
                return _getInterfaceClass(superclazz, superclazzIntf);
        }

        return clazz;
    }

    public Class getRootExpressionClass(Node rootNode, OgnlContext context)
    {
        if (context.getRoot() == null)
            return null;

        Class ret = context.getRoot().getClass();

        if (context.getFirstAccessor() != null && context.getFirstAccessor().isInstance(context.getRoot()))
        {
            ret = context.getFirstAccessor();
        }

        return ret;
    }

    /* (non-Javadoc)
     * @see ognl.enhance.OgnlExpressionCompiler#compileExpression(ognl.OgnlContext, ognl.Node, java.lang.Object)
     */
    public void compileExpression(OgnlContext context, Node expression, Object root)
            throws Exception
    {
//        System.out.println("Compiling expr class " + expression.getClass().getName() + " and root " + root);

        if (expression.getAccessor() != null)
            return;

        String getBody, setBody;

        EnhancedClassLoader loader = getClassLoader(context);
        ClassPool pool = getClassPool(context, loader);

        CtClass newClass = pool.makeClass(expression.getClass().getName() + expression.hashCode() + _classCounter++ + "Accessor");
        newClass.addInterface(getCtClass(ExpressionAccessor.class));

        CtClass ognlClass = getCtClass(OgnlContext.class);
        CtClass objClass = getCtClass(Object.class);

        CtMethod valueGetter = new CtMethod(objClass, "get", new CtClass[]{ognlClass, objClass}, newClass);
        CtMethod valueSetter = new CtMethod(CtClass.voidType, "set", new CtClass[]{ognlClass, objClass, objClass}, newClass);

        CtField nodeMember = null; // will only be set if uncompilable exception is thrown

        CtClass nodeClass = getCtClass(Node.class);
        CtMethod setExpression = null;

        try {

            getBody = generateGetter(context, newClass, objClass, pool, valueGetter, expression, root);

        } catch (UnsupportedCompilationException uc)
        {
            //uc.printStackTrace();

            nodeMember = new CtField(nodeClass, "_node", newClass);
            newClass.addField(nodeMember);

            getBody = generateOgnlGetter(newClass, valueGetter, nodeMember);

            if (setExpression == null)
            {
                setExpression = CtNewMethod.setter("setExpression", nodeMember);
                newClass.addMethod(setExpression);
            }
        }

        try {

            setBody = generateSetter(context, newClass, objClass, pool,  valueSetter, expression, root);

        } catch (UnsupportedCompilationException uc)
        {

            //uc.printStackTrace();

            if (nodeMember == null)
            {
                nodeMember = new CtField(nodeClass, "_node", newClass);
                newClass.addField(nodeMember);
            }

            setBody = generateOgnlSetter(newClass, valueSetter, nodeMember);

            if (setExpression == null)
            {
                setExpression = CtNewMethod.setter("setExpression", nodeMember);
                newClass.addMethod(setExpression);
            }
        }

        try {
            newClass.addConstructor(CtNewConstructor.defaultConstructor(newClass));

            Class clazz = pool.toClass(newClass);
            newClass.detach();

            expression.setAccessor((ExpressionAccessor) clazz.newInstance());

            // need to set expression on node if the field was just defined.

            if (nodeMember != null)
            {
                expression.getAccessor().setExpression(expression);
            }

        } catch (Throwable t) {
            //t.printStackTrace();

            throw new RuntimeException("Error compiling expression on object " + root
                                       + " with expression node " + expression + " getter body: " + getBody
                                       + " setter body: " + setBody, t);
        }

    }

    protected String generateGetter(OgnlContext context, CtClass newClass, CtClass objClass, ClassPool pool,
                                    CtMethod valueGetter, Node expression, Object root)
            throws Exception
    {
        String pre = "";
        String post = "";
        String body;

        context.setRoot(root);

        // the ExpressionAccessor API has to reference the generic Object class for get/set operations, so this sets up that known
        // type beforehand

        context.remove(PRE_CAST);

        // Recursively generate the java source code representation of the top level expression

        String getterCode = expression.toGetSourceString(context, root);

        if (getterCode == null || getterCode.trim().length() <= 0
                                  && !ASTVarRef.class.isAssignableFrom(expression.getClass()))
            getterCode = "null";

        String castExpression = (String) context.get(PRE_CAST);

        if (context.getCurrentType() == null
            || context.getCurrentType().isPrimitive()
            || Character.class.isAssignableFrom(context.getCurrentType())
            || Object.class == context.getCurrentType())
        {
            pre = pre + " ($w) (";
            post = post + ")";
        }

        String rootExpr = !getterCode.equals("null") ? getRootExpression(expression, root, context) : "";

        String noRoot = (String) context.remove("_noRoot");
        if (noRoot != null)
            rootExpr = "";

        createLocalReferences(context, pool, newClass, objClass, valueGetter.getParameterTypes());

        if (OrderedReturn.class.isInstance(expression) && ((OrderedReturn) expression).getLastExpression() != null)
        {
            body = "{ "
                   + (ASTMethod.class.isInstance(expression) || ASTChain.class.isInstance(expression) ? rootExpr : "")
                   + (castExpression != null ? castExpression : "")
                   + ((OrderedReturn) expression).getCoreExpression()
                   + " return " + pre + ((OrderedReturn) expression).getLastExpression()
                   + post
                   + ";}";

        } else {

            body = "{  return "
                   + pre
                   + (castExpression != null ? castExpression : "")
                   + rootExpr
                   + getterCode
                   + post
                   + ";}";
        }

        if (body.indexOf("..") >= 0)
            body = body.replaceAll("\\.\\.", ".");

//        System.out.println("Getter Body: ===================================\n" + body);
        valueGetter.setBody(body);
        newClass.addMethod(valueGetter);

        return body;
    }

    public String createLocalReference(OgnlContext context, String expression, Class type)
    {
        String referenceName = "ref" + context.incrementLocalReferenceCounter();
        context.addLocalReference(referenceName, new LocalReferenceImpl(referenceName, expression, type));

        String castString = "";
        if (!type.isPrimitive())
            castString = "(" + ExpressionCompiler.getCastString(type) + ") ";

        return castString + referenceName + "($$)";
    }

    void createLocalReferences(OgnlContext context, ClassPool pool, CtClass clazz, CtClass objClass, CtClass[] params)
            throws CannotCompileException, NotFoundException
    {
        Map referenceMap = context.getLocalReferences();
        if (referenceMap == null || referenceMap.size() < 1)
            return;

        Iterator it = referenceMap.values().iterator();

        while (it.hasNext())
        {
            LocalReference ref = (LocalReference) it.next();

            String widener = ref.getType().isPrimitive() ? " " : " ($w) ";

            String body = "{";
            body += " return  " + widener + ref.getExpression() + ";";
            body += "}";

            if (body.indexOf("..") >= 0)
                body = body.replaceAll("\\.\\.", ".");

//            System.out.println("adding method " + ref.getName() + " with body:\n" + body + " and return type: " + ref.getType());

            CtMethod method = new CtMethod(pool.get(getCastString(ref.getType())), ref.getName(), params, clazz);
            method.setBody(body);

            clazz.addMethod(method);

            it.remove();
        }
    }

    protected String generateSetter(OgnlContext context, CtClass newClass, CtClass objClass, ClassPool pool,
                                    CtMethod valueSetter, Node expression, Object root)
            throws Exception
    {
        if (ExpressionNode.class.isInstance(expression)
            || ASTConst.class.isInstance(expression))
            throw new UnsupportedCompilationException("Can't compile expression/constant setters.");

        context.setRoot(root);
        context.remove(PRE_CAST);

        String body;

        String setterCode = expression.toSetSourceString(context, root);
        String castExpression = (String) context.get(PRE_CAST);

        if (setterCode == null || setterCode.trim().length() < 1)
            throw new UnsupportedCompilationException("Can't compile null setter body.");

        if (root == null)
            throw new UnsupportedCompilationException("Can't compile setters with a null root object.");

        String pre = getRootExpression(expression, root, context);

        String noRoot = (String) context.remove("_noRoot");
        if (noRoot != null)
            pre = "";

        createLocalReferences(context, pool, newClass, objClass, valueSetter.getParameterTypes());

        body = "{"
               + (castExpression != null ? castExpression : "")
               + pre
               + setterCode + ";}";

        if (body.indexOf("..") >= 0)
            body = body.replaceAll("\\.\\.", ".");

//        System.out.println("Setter Body: ===================================\n" + body);

        valueSetter.setBody(body);
        newClass.addMethod(valueSetter);

        return body;
    }

    /**
     * Fail safe getter creation when normal compilation fails.
     *
     * @param clazz
     *          The javassist class the new method should be attached to.
     * @param valueGetter
     *          The method definition the generated code will be contained within.
     * @param node
     *          The root expression node.
     * @return The generated source string for this method, the method will still be
     *          added via the javassist API either way so this is really a convenience
     *          for exception reporting / debugging.
     * @throws Exception
     *          If a javassist error occurs.
     */
    protected String generateOgnlGetter(CtClass clazz, CtMethod valueGetter, CtField node)
            throws Exception
    {
        String body = "return " + node.getName() + ".getValue($1, $2);";

        valueGetter.setBody(body);
        clazz.addMethod(valueGetter);

        return body;
    }

    /**
     * Fail safe setter creation when normal compilation fails.
     *
     * @param clazz
     *          The javassist class the new method should be attached to.
     * @param valueSetter
     *          The method definition the generated code will be contained within.
     * @param node
     *          The root expression node.
     * @return The generated source string for this method, the method will still be
     *          added via the javassist API either way so this is really a convenience
     *          for exception reporting / debugging.
     * @throws Exception
     *          If a javassist error occurs.
     */
    protected String generateOgnlSetter(CtClass clazz, CtMethod valueSetter, CtField node)
            throws Exception
    {
        String body = node.getName() + ".setValue($1, $2, $3);";

        valueSetter.setBody(body);
        clazz.addMethod(valueSetter);

        return body;
    }

    /**
     * Creates a {@link ClassLoader} instance compatible with the javassist classloader and normal
     * OGNL class resolving semantics.
     *
     * @param context
     *          The current execution context.
     *
     * @return The created {@link ClassLoader} instance.
     */
    protected EnhancedClassLoader getClassLoader(OgnlContext context)
    {
        EnhancedClassLoader ret = (EnhancedClassLoader) _loaders.get(context.getClassResolver());

        if (ret != null)
            return ret;

        ClassLoader classLoader = new ContextClassLoader(OgnlContext.class.getClassLoader(), context);

        ret = new EnhancedClassLoader(classLoader);
        _loaders.put(context.getClassResolver(), ret);

        return ret;
    }

    /**
     * Loads a new class definition via javassist for the specified class.
     *
     * @param searchClass
     *          The class to load.
     * @return The javassist class equivalent.
     *
     * @throws NotFoundException When the class definition can't be found.
     */
    protected CtClass getCtClass(Class searchClass)
            throws NotFoundException
    {
        return _pool.get(searchClass.getName());
    }

    /**
     * Gets either a new or existing {@link ClassPool} for use in compiling javassist
     * classes.  A new class path object is inserted in to the returned {@link ClassPool} using
     * the passed in loader instance if a new pool needs to be created.
     *
     * @param context
     *          The current execution context.
     * @param loader
     *          The {@link ClassLoader} instance to use - as returned by {@link #getClassLoader(ognl.OgnlContext)}.
     * @return The existing or new {@link ClassPool} instance.
     */
    protected ClassPool getClassPool(OgnlContext context, EnhancedClassLoader loader)
    {
        if (_pool != null)
            return _pool;

        _pool = ClassPool.getDefault();
        _pool.insertClassPath(new LoaderClassPath(loader.getParent()));

        return _pool;
    }
}


/* Generated By:JavaCC: Do not edit this line. JJTOgnlParserState.java Version 4.1d1 */
package ognl;

public class JJTOgnlParserState {
  private java.util.List nodes;
  private java.util.List marks;

  private int sp;        // number of nodes on stack
  private int mk;        // current mark
  private boolean node_created;

  public JJTOgnlParserState() {
    nodes = new java.util.ArrayList();
    marks = new java.util.ArrayList();
    sp = 0;
    mk = 0;
  }

  /* Determines whether the current node was actually closed and
     pushed.  This should only be called in the final user action of a
     node scope.  */
  public boolean nodeCreated() {
    return node_created;
  }

  /* Call this to reinitialize the node stack.  It is called
     automatically by the parser's ReInit() method. */
  public void reset() {
    nodes.clear();
    marks.clear();
    sp = 0;
    mk = 0;
  }

  /* Returns the root node of the AST.  It only makes sense to call
     this after a successful parse. */
  public Node rootNode() {
    return (Node)nodes.get(0);
  }

  /* Pushes a node on to the stack. */
  public void pushNode(Node n) {
    nodes.add(n);
    ++sp;
  }

  /* Returns the node on the top of the stack, and remove it from the
     stack.  */
  public Node popNode() {
    if (--sp < mk) {
      mk = ((Integer)marks.remove(marks.size()-1)).intValue();
    }
    return (Node)nodes.remove(nodes.size()-1);
  }

  /* Returns the node currently on the top of the stack. */
  public Node peekNode() {
    return (Node)nodes.get(nodes.size()-1);
  }

  /* Returns the number of children on the stack in the current node
     scope. */
  public int nodeArity() {
    return sp - mk;
  }

  public void clearNodeScope(Node n) {
    while (sp > mk) {
      popNode();
    }
    mk = ((Integer)marks.remove(marks.size()-1)).intValue();
  }

  public void openNodeScope(Node n) {
    marks.add(new Integer(mk));
    mk = sp;
    n.jjtOpen();
  }

  /* A definite node is constructed from a specified number of
     children.  That number of nodes are popped from the stack and
     made the children of the definite node.  Then the definite node
     is pushed on to the stack. */
  public void closeNodeScope(Node n, int num) {
    mk = ((Integer)marks.remove(marks.size()-1)).intValue();
    while (num-- > 0) {
      Node c = popNode();
      c.jjtSetParent(n);
      n.jjtAddChild(c, num);
    }
    n.jjtClose();
    pushNode(n);
    node_created = true;
  }

  /* A conditional node is constructed if its condition is true.  All
     the nodes that have been pushed since the node was opened are
     made children of the conditional node, which is then pushed
     on to the stack.  If the condition is false the node is not
     constructed and they are left on the stack. */
  public void closeNodeScope(Node n, boolean condition) {
    if (condition) {
      int a = nodeArity();
      mk = ((Integer)marks.remove(marks.size()-1)).intValue();
      while (a-- > 0) {
        Node c = popNode();
        c.jjtSetParent(n);
        n.jjtAddChild(c, a);
      }
      n.jjtClose();
      pushNode(n);
      node_created = true;
    } else {
      mk = ((Integer)marks.remove(marks.size()-1)).intValue();
      node_created = false;
    }
  }
}
/* JavaCC - OriginalChecksum=61071c68a05e7c9104307c34a2e37165 (do not edit this line) */