blob: 0c553ff593f9a11543503003387ac4e8c3a1241e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
/*
*/
package org.eclipse.jem.internal.proxy.initParser.tree;
import java.lang.reflect.*;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jem.internal.proxy.common.*;
import org.eclipse.jem.internal.proxy.initParser.InitializationStringEvaluationException;
import org.eclipse.jem.internal.proxy.initParser.InitializationStringParser;
/**
* Expression processing. This does the actual expression processing with the live objects.
* It is meant to be subclassed only to provide additional expression types. All of the
* current expressions cannot be overridden. This is because the stack is very sensitive to
* call order.
*
* @since 1.0.0
*/
public class ExpressionProcesser {
/**
* A variable reference for a field access.
*
* @since 1.1.0
*/
protected static class FieldAccessReference extends VariableReference {
private final Field field;
private final Object receiver;
/**
* Use this to construct a FieldAccessReference. This will do checks to make sure
* it is valid so that exceptions won't be thrown later when actually dereferenced.
*
* @param field
* @param receiver
* @return
* @throws IllegalArgumentException
*
* @since 1.1.0
*/
public static FieldAccessReference createFieldAccessReference(Field field, Object receiver) throws IllegalArgumentException {
// If static, then receiver is ignored.
if (!Modifier.isStatic(field.getModifiers())) {
if (!field.getDeclaringClass().isInstance(receiver))
throw new IllegalArgumentException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.CreateFieldAccessReference.FieldsTypesNotMatching_EXC_"), new Object[]{field.getType(), (receiver!=null ? receiver.getClass() : null)})); //$NON-NLS-1$
}
field.setAccessible(true); // Make it always accessible. Trust it.
return new FieldAccessReference(field, receiver);
}
protected FieldAccessReference(Field field, Object receiver) {
this.field = field;
this.receiver = receiver;
}
/* (non-Javadoc)
* @see org.eclipse.jem.internal.proxy.initParser.tree.ExpressionProcesser.VariableReference#dereference()
*/
public Object dereference() {
try {
return field.get(receiver);
} catch (IllegalArgumentException e) {
// Shouldn't occur. Already tested for this.
e.printStackTrace();
} catch (IllegalAccessException e) {
// Shouldn't occur. Already tested for this.
e.printStackTrace();
}
return null;
}
/* (non-Javadoc)
* @see org.eclipse.jem.internal.proxy.initParser.tree.ExpressionProcesser.VariableReference#set(java.lang.Object, java.lang.Class)
*/
public Object set(Object value, Class type) throws IllegalArgumentException, IllegalAccessException {
field.set(receiver, value);
return field.get(receiver); // Just in case some conversion happened. Technically it is not the value set but the retrieved when in an assignment.
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
return "FieldAccess{"+field.toString()+"} on "+(receiver != null ? receiver.toString() : "<static access>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
/**
* A variable reference for an Array access. It will reference only the last indexed entry of the array.
* For example if <code>x[3][4]</code> is the access, then what will be given to this reference will be
* the array entry at x[3][4], not the x array itself.
*
* @since 1.1.0
*/
protected static class ArrayAccessReference extends VariableReference {
private final Object array;
private final int index;
/**
* Use this to construct an array access reference. This will do checks to make sure
* it is valid so that exceptions won't be thrown later when actually dereferenced.
*
* @param array
* @param index
* @return
* @throws IllegalArgumentException
*
* @since 1.1.0
*/
public static ArrayAccessReference createArrayAccessReference(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
int len = Array.getLength(array);
if (index < 0 || len <= index)
throw new ArrayIndexOutOfBoundsException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.CreateArrayAccessReference.OutOfBounds_EXC_"), new Object[]{new Integer(index), new Integer(len)})); //$NON-NLS-1$
return new ArrayAccessReference(array, index);
}
/**
* Construct the reference with the array and the index of the entry being referenced.
* @param array
* @param index
*
* @since 1.1.0
*/
protected ArrayAccessReference(Object array, int index) {
this.array = array;
this.index = index;
}
/* (non-Javadoc)
* @see org.eclipse.jem.internal.proxy.initParser.tree.VariableReference#dereference()
*/
public Object dereference() {
return Array.get(array, index);
}
/* (non-Javadoc)
* @see org.eclipse.jem.internal.proxy.initParser.tree.VariableReference#set(java.lang.Object, java.lang.Class)
*/
public Object set(Object value, Class type) throws IllegalArgumentException {
Array.set(array, index, value);
return Array.get(array, index); // In case there was some conversion applied. Technically it is not the value set but the retrieved when in an assignment.
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
return "ArrayAccess["+index+"]: "+array.toString(); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* The expression result stack and the expression result type stack.
* The type stack is used to be expected type of the corresponding
* expression result. This is needed for converting to primitives
* and for finding correct method call from the argument types. In
* this case, it is not the true value, but the value expected, e.g.
* <code>Object getObject()</code> returns something of type Object.
* This needs to be maintained so that if it goes into another method
* we don't accidently return a more specific method instead of the
* one that takes Object as an argument.
*
* expressionStack has result of the expression.
* expressionTypeStack has the computed type of the expression i.e.
* the type that the expression returns, not the type of the value.
* These can be different because the expression (e.g. method) may
* return an Object, but the expression value will be some specific
* subclass. So the expressionTypeStack would have a <code>java.lang.Object.class</code>
* on it in that case.
* Note: if the expressionStack has a <code>null</code> on it, then the type stack
* may either have a specific type in it, or it may be <code>MethodHelper.NULL_TYPE</code>. It
* would be this if it was explicitly pushed in and not as the
* result of a computation. If the result of a computation, it would have the
* true value.
* Note: if the expressionStack has a <code>Void.type</code> on it, then that
* means the previous expression had no result. This is an error if trying to
* use the expression in another expression.
*
* @see org.eclipse.jem.internal.proxy.initParser.MethodHelper#NULL_TYPE
*/
private List expressionStack = new ArrayList(10);
private List expressionTypeStack = new ArrayList(10);
/**
* List of the expression proxies. The index into the list is the
* same as the expression proxy id.
*/
private ArrayList expressionProxies; // It is array list because we want to call ensureCapacity and that is not available on List.
/**
* An error has occurred. At this point all subcommands will simply make sure they flush the input stream
* correctly, but they do not process it.
*
* @since 1.0.0
*/
private boolean errorOccurred = false;
private boolean novalueException = false;
private Throwable exception = null; // Was there another kind of exception that was caught.
/**
* Process all other exceptions then the NoExpressionValueException. This can be called from usage code so that if there was an error
* in setting up for a call to the processer it can be logged.
*
* @param e
*
* @since 1.0.0
*/
public final void processException(Throwable e) {
// Process all other exceptions.
novalueException = false;
while (e.getCause() != null)
e = e.getCause();
if (traceOn) {
System.out.println();
System.out.print("***** >>>\tException: "); //$NON-NLS-1$
System.out.println(e);
}
throwException(e); // Treat as a throw to let try/catches expressions handle it.
}
/**
* This is a syntax exception. This means data coming across is corrupted in
* some way so no further processing should occur.
* @param e
*
* @since 1.1.0
*/
protected final void processSyntaxException(Throwable e) {
errorOccurred = true;
novalueException = false;
exception = e;
}
/**
* Process a NoExpressionValueException. Don't wrapper these.
* @param e
*
* @since 1.1.0
*/
protected final void processSyntaxException(NoExpressionValueException e) {
if (traceOn) {
// This can happen at any time, so make sure we are on a new line.
System.out.println();
printTrace("Expression has no value", false); //$NON-NLS-1$
}
try {
errorOccurred = true;
novalueException = true;
exception = e;
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Return whether there are any errors.
*
* @return <code>true</code> if no errors.
*
* @since 1.0.0
*/
public boolean noErrors() {
return !errorOccurred;
}
/**
* Return whether the error is a NoExpressionValueException or not.
* @return
*
* @since 1.1.0
*/
public boolean isNoExpressionValue() {
return novalueException;
}
/**
* Return the throwable if a Throwable was caught.
*
* @return The throwable, or <code>null</code> if not set.
*
* @since 1.0.0
*/
public Throwable getErrorThrowable() {
return exception;
}
/**
* Push the expression value and its expected type.
* @param o
* @param type
*
* @since 1.0.0
*/
protected final void pushExpressionValue(Object o, Class type) {
expressionStack.add(o);
expressionTypeStack.add(type);
}
/**
* Pop just the expression value. It is imperitive that the expression type
* is popped immediately following. Separated the methods so that we
* don't need to create an array to return two values. This will dereference
* any variable references.
*
* @return The value.
* @throws NoExpressionValueException
*
* @since 1.0.0
*/
protected final Object popExpression() throws NoExpressionValueException {
return popExpression(true);
}
/**
* Pop just the expression value. It is imperitive that the expression type
* is popped immediately following. Separated the methods so that we
* don't need to create an array to return two values.
*
* @param deReference If the top expression is a Reference, then dereference it.
* @return The value.
* @throws NoExpressionValueException
*
* @since 1.0.0
*/
protected final Object popExpression(boolean deReference) throws NoExpressionValueException {
try {
// Do not pop above the current subexpression pos, if any.
if (topSubexpression != -1)
if (expressionStack.size() == subexpressionStackPos[topSubexpression])
throw new NoExpressionValueException();
Object result = expressionStack.remove(expressionStack.size()-1);
if (deReference && result instanceof VariableReference)
result = ((VariableReference) result).dereference();
return result;
} catch (IndexOutOfBoundsException e) {
throw new NoExpressionValueException();
}
}
/**
* Get the expression at <code>fromTop</code> down from the top. This is
* need for when multi-operators happen and they are stored in reverse of
* what is needed. They would normally be stored left to right, with the
* rightmost one on top. But they need to be processed left to right, so
* to get the left most one requires digging down in the stack.
* <p>
* When done, <code>popExpressions(int count)</code> must be called to
* clean them out since they were processed.
* <p>
* This will not dereference the expression. It is the job of the caller to do this.
*
* @param fromTop <code>1</code> is the top one, <code>2</code> is the next one down.
* @return The entry from the top that was requested.
* @throws NoExpressionValueException
*
* @see IDEExpression#popExpressions(int)
* @since 1.0.0
*/
protected final Object getExpression(int fromTop) throws NoExpressionValueException {
try {
// Do not pull above the current subexpression pos, if any.
if (topSubexpression != -1)
if (expressionStack.size()-fromTop < subexpressionStackPos[topSubexpression])
throw new NoExpressionValueException();
return expressionStack.get(expressionStack.size()-fromTop);
} catch (IndexOutOfBoundsException e) {
throw new NoExpressionValueException();
}
}
/**
* Remove the top <code>count</code> items. This will not cause dereferencing to occur. It
* removes the corresponding type stack entries.
*
* @param count
* @throws NoExpressionValueException
*
* @since 1.0.0
*/
protected final void popExpressions(int count) throws NoExpressionValueException {
try {
// Do not pop above the current subexpression pos, if any.
int stop = topSubexpression != -1 ? subexpressionStackPos[topSubexpression] : -1;
int remove = expressionStack.size()-1;
while (count-- > 0) {
if (expressionStack.size() <= stop)
throw new NoExpressionValueException(); // Try to go above the current subexpression.
expressionStack.remove(remove);
expressionTypeStack.remove(remove--);
}
} catch (IndexOutOfBoundsException e) {
throw new NoExpressionValueException();
}
}
/**
* Pop just the expression type. It is imperitive that the expression type
* is popped immediately following popExpression. Separated the methods so that we
* don't need to create an array to return two values.
* <p>
* If the allowVoid is false and type is void, then a NoExpressionValueException will be thrown.
* This is for the case where the expression was trying to be used in a different
* expression. This will be set to void only on expressions that return no value (only
* method's do this for now).
*
* @param allowVoid Allow void types if <code>true</code>
* @return The type.
* @throws NoExpressionValueException
* @since 1.0.0
*/
protected final Class popExpressionType(boolean allowVoid) throws NoExpressionValueException {
try {
Class result = (Class) expressionTypeStack.remove(expressionTypeStack.size()-1);
if (!allowVoid && result == Void.TYPE)
throw new NoExpressionValueException(InitparserTreeMessages.getString("ExpressionProcesser.PopExpressionType.ExpressionVoid_EXC_")); //$NON-NLS-1$
return result;
} catch (IndexOutOfBoundsException e) {
throw new NoExpressionValueException();
}
}
/**
* Get the expression type at <code>fromTop</code> down from the top. This is
* need for when multi-operators happen and they are stored in reverse of
* what is needed. They would normally be stored left to right, with the
* rightmost one on top. But they need to be processed left to right, so
* to get the left most one requires digging down in the stack.
* <p>
* When done, <code>popExpressionTypes(int count)</code> must be called to
* clean them out since they were processed.
* @param fromTop <code>1</code> is the top one, <code>2</code> is the next one down.
* @param allowVoid Allow void types if <code>true</code>
* @return The type from the top that was requested.
* @throws ThrowableProxy
* @throws NoExpressionValueException
*
* @see IDEExpression#popExpressionTypes(int)
* @since 1.0.0
*/
protected final Class getExpressionType(int fromTop, boolean allowVoid) throws NoExpressionValueException {
try {
Class result = (Class) expressionTypeStack.get(expressionTypeStack.size()-fromTop);
if (!allowVoid && result == Void.TYPE)
throw new NoExpressionValueException();
return result;
} catch (IndexOutOfBoundsException e) {
throw new NoExpressionValueException();
}
}
/**
* Flag indicating expression should be ignored and not processed.
* This happens because of few cases, like conditional and, that
* if one returns false, the rest of the expressions in that conditional and
* expression should be ignored and not processed.
* <p>
* It is an Object that acts as an enum for the type of expression that initiated the ignore.
* If it is <code>null</code> then no one is ignoring.
* <p>
* All of the pushTo...Proxy methods must test this for this to work correctly.
* Each expression has some way of testing that their particular nesting of
* expressions is complete and they can turn off the ignore flag.
* <p>
* Only one type of ignore can exist at a time.
*/
protected Object ignoreExpression = null;
private List saveStates;
/**
* Are we tracing or not.
*/
protected final boolean traceOn;
private final long thresholdTime;
private long startExpressionStepTime;
private long startExpressionTime;
private long lastExpressionEndTime;
/**
* Trace head of this expression. So that traces from different expressions can be distinquished.
* It is simply an monotonically increasing counter. It is the header string for any trace output.
*/
protected final String traceHeader;
private int indent = 0; // Indented for certain block expressions.
/*
* Trace counter. It is incremented once for each expression and assigned to the traceId of the expression.
*/
private static int TRACE_COUNTER;
/**
* Create the Expression without tracing.
* @param registry
*
* @since 1.0.0
*/
public ExpressionProcesser() {
this(false, -1);
}
/**
* Create the expression, and set the tracing mode and threshold time. Use -1
* for default time of 100ms.
* @param traceOn
*
* @since 1.1.0
*/
public ExpressionProcesser(boolean traceOn, long threshold) {
this.traceOn = traceOn;
if (traceOn) {
traceHeader = "**"+(++TRACE_COUNTER)+':'; //$NON-NLS-1$
System.out.print(traceHeader);
System.out.println(" Start expression"); //$NON-NLS-1$
this.thresholdTime = threshold != -1 ? threshold : 100;
lastExpressionEndTime = startExpressionTime = System.currentTimeMillis();
} else {
traceHeader = null;
thresholdTime = 100;
}
}
/**
* Trace msg helper. Should only be called if traceOn is true. This method is only used to start a new trace message.
* The caller is must call printTraceEnd at the end.
*
* @param msg message to print
* @param ignore are we ignoring the expression, or is it being processed (this just alters the trace output slightly).
*
* @since 1.1.0
*/
protected void printTrace(String msg, boolean ignore) {
startExpressionStepTime = System.currentTimeMillis();
long sinceLastExpression = startExpressionStepTime - lastExpressionEndTime;
System.out.print(traceHeader);
if (sinceLastExpression > 0) {
System.out.print('(');
if (sinceLastExpression > thresholdTime)
System.out.print("***"); //$NON-NLS-1$
System.out.print(sinceLastExpression);
System.out.print("ms)"); //$NON-NLS-1$
}
System.out.print('\t');
if (!ignore)
System.out.print("\t"); //$NON-NLS-1$
else
System.out.print("##\t"); //$NON-NLS-1$
printIndent();
System.out.print(msg);
}
/**
* print the indent. It will not do a new line before nor after.
*
* @since 1.1.0
*/
protected void printIndent() {
for(int i=indent; i>0; i--) {
System.out.print(" "); //$NON-NLS-1$
}
}
protected void printTraceEnd() {
long stop = System.currentTimeMillis()-startExpressionStepTime;
if (stop > 0) {
System.out.print(" ("); //$NON-NLS-1$
if (stop > thresholdTime)
System.out.print("***"); //$NON-NLS-1$
System.out.print(stop);
System.out.print("ms)"); //$NON-NLS-1$
}
System.out.println();
lastExpressionEndTime = System.currentTimeMillis();
}
/**
* Do an indent (undent) according to indent flag.
* @param indent <code>true</code> to increment indent, or otherwise decrement.
*
* @since 1.1.0
*/
protected void indent(boolean indent) {
this.indent += (indent ? 1 : -1);
if (this.indent < 0)
this.indent = 0;
}
/**
* Print the object and type. It will not end with a newline char, so one will be needed afterwards.
*
* @param o
* @param t
*
* @since 1.1.0
*/
protected void printObjectAndType(Object o, Class t) {
System.out.print(' ');
System.out.print("Object-"); //$NON-NLS-1$
System.out.print(o);
System.out.print(" Type-"); //$NON-NLS-1$
System.out.print(t);
System.out.print(' ');
}
/**
* Close the exception processing
*
* @since 1.0.0
*/
public final void close() {
boolean firstClose = expressionStack != null;
if (firstClose && traceOn) {
printTrace("End expression", false); //$NON-NLS-1$
long totalTime = System.currentTimeMillis()-startExpressionTime;
System.out.print(" Total expression evaluation time: "); //$NON-NLS-1$
System.out.print(totalTime);
System.out.print("ms."); //$NON-NLS-1$
}
try {
expressionStack = null;
expressionTypeStack = null;
expressionProxies = null;
exception = null;
catchThrowable = null;
saveStates = null;
} finally {
if (firstClose && traceOn)
printTraceEnd();
}
}
/**
* Pull the value. The value will be placed into the array passed in.
* It will be stored as value[0] = value value[1] = valuetype(Class).
*
* @param value The value array to store the value and type into.
* @throws NoExpressionValueException
* @since 1.0.0
*/
public final void pullValue(Object[] value) throws NoExpressionValueException {
if (traceOn)
printTrace("Pull value:", false); //$NON-NLS-1$
try {
value[0] = popExpression();
value[1] = popExpressionType(false);
} finally {
if (traceOn) {
printObjectAndType(value[0], (Class) value[1]);
printTraceEnd();
}
}
close();
}
/**
* Pull the value of the expression proxy, dereferencing it if necessary. This is for resolution only purposes at the
* end of the expression being processed. Not meant for general access to the value of expression proxy. Use
* {@link ExpressionProcesser#getExpressionProxyValue(int, Object[])} instead for general access to the value.
*
* @param proxyid
* @param value
* @throws NoExpressionValueException
*
* @since 1.1.0
*/
public final void pullExpressionProxyValue(int proxyid, Object[] value) throws NoExpressionValueException {
getExpressionProxyValue(proxyid, value, true, true);
}
/**
* Get the expression proxy value. If the expression has not yet been evaluated it will
* return false. If it has it will return true.
* @param proxyid
* @param value put value into value[0] and the type into value[1].
* @return <code>true</code> if successful, or <code>false</code> if the expression proxy was never resolved or doesn't exist.
*
* @since 1.1.0
*/
public boolean getExpressionProxyValue(int proxyid, Object[] value) {
try {
return getExpressionProxyValue(proxyid, value, true, false);
} catch (NoExpressionValueException e) {
return false;
}
}
/**
* Get the expression proxy value, or the NoExpressionValueException if not resolved. This
* is useful for callers that need to handle the proxy itself for failed calls.
*
* @param proxyid
* @param value
* @throws NoExpressionValueException
*
* @since 1.1.0
*/
public void getExpressionProxy(int proxyid, Object[] value) throws NoExpressionValueException {
getExpressionProxyValue(proxyid, value, true, false);
}
/*
* Internal method use to actually get the value, but to distinquish between pull and get of the public interface.
* Get will process the errors as normal execution errors, while pull will throw the errors. finalTrace is when
* this is the final call to return the values to the client. We will trace the results in that case.
* Return true if successful.
*/
private boolean getExpressionProxyValue(int proxyid, Object[] value, boolean pull, boolean finalTrace) throws NoExpressionValueException {
// Note: This will throw the exceptions right away since this is called from outside to fill in the value and
// so we are holding such exceptions.
boolean doTrace = finalTrace && traceOn;
try {
if (expressionProxies != null && expressionProxies.size() > proxyid) {
InternalExpressionProxy proxy = (InternalExpressionProxy) expressionProxies.get(proxyid);
if (proxy != null && proxy.isSet()) {
value[0] = proxy.getValue();
if (value[0] instanceof VariableReference)
value[0] = ((VariableReference) value[0]).dereference(); // Here we want the final current value.
value[1] = proxy.getType();
if (doTrace)
if (value[1] != Void.TYPE) {
printTrace("Return Proxy #" + proxyid + " Resolved to", false); //$NON-NLS-1$ //$NON-NLS-2$
printObjectAndType(value[0], (Class) value[1]);
} else
printTrace("Return Proxy #" + proxyid + " Resolved to void.", false); //$NON-NLS-1$ //$NON-NLS-2$
return true;
} else {
if (doTrace)
printTrace("Return Proxy #" + proxyid + ": Not resolved", false); //$NON-NLS-1$ //$NON-NLS-2$
NoExpressionValueException e = new NoExpressionValueException(InitparserTreeMessages.getString("ExpressionProcesser.GetExpressionProxyValue.ExpressionProxyNotSet_EXC_"), proxy); //$NON-NLS-1$
if (pull)
throw e;
else
processSyntaxException(e);
return false;
}
} else {
if (doTrace)
printTrace("Return Proxy #" + proxyid + ": Never created.", false); //$NON-NLS-1$ //$NON-NLS-2$
NoExpressionValueException e = new NoExpressionValueException(InitparserTreeMessages.getString("ExpressionProcesser.GetExpressionProxyValue.ExpressionProxyDoesntExist_EXC_")); //$NON-NLS-1$
if (pull)
throw e;
else
processSyntaxException(e);
return false;
}
} finally {
if (doTrace)
printTraceEnd();
}
}
/**
* Push the expression (just a value) onto the stack.
*
* @param o
* @param t
*
* @since 1.0.0
*/
public final void pushExpression(Object o, Class t) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn) {
printTrace("Push: ", ignore); //$NON-NLS-1$
printObjectAndType(o, t);
}
try {
if (ignore)
return;
pushExpressionValue(o, t);
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Get the value of the expression proxy (from proxy id), and push the value onto the stack.
*
* @param proxyid The proxy id of the ExpressionProxy to push as a value.
*
* @since 1.0.0
*/
public final void pushExpressionProxy(int proxyid) {
boolean ignore =(ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("Push Expression Proxy #"+proxyid, ignore); //$NON-NLS-1$
try {
if (ignore)
return;
if (expressionProxies != null && expressionProxies.size() > proxyid) {
InternalExpressionProxy proxy = (InternalExpressionProxy) expressionProxies.get(proxyid);
if (proxy != null && proxy.isSet()) {
if (traceOn)
printObjectAndType(proxy.getValue(), proxy.getType());
pushExpressionValue(proxy.getValue(), proxy.getType()); // Can push a VariableReference. This is ok. When used it will then deref with the current value.
} else
processException(new NoExpressionValueException("Proxy id: "+proxyid)); //$NON-NLS-1$
} else
processException(new NoExpressionValueException("Proxy id: "+proxyid)); //$NON-NLS-1$
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push a cast onto stack. The type passed in is either a String (with classname to cast to) or the
* type to cast to.
* @param type To cast to. If <code>String</code> then convert to type (using something like <code>Class.forName()</code>) or it is a Class
*
* @since 1.0.0
*/
public final void pushCast(Class type) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("Cast to: "+type, ignore); //$NON-NLS-1$
try {
if (ignore)
return;
try {
Object exp = popExpression();
Class exptype = popExpressionType(false);
pushExpressionValue(castBean(type, exp, exptype), type);
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Cast a bean into the return type. If the return type is not primitive, then
* the bean is left alone, however it is checked to be an instance of
* the return type. If the return type is primitive, then the
* correct primitive wrapper is created from the bean (bean must be a number or character or boolean primitve so
* that cast will work).
* <p>
* However if can't be cast for primitive or if not an instance of the
* returntype for objects, a ClassCastException will be raised.
* <p>
* This is a helper method for expression processer to cast a bean. Since it is a helper method it doesn't
* check nor process the exception. It throws it. Callers must handle it as they see fit.
*
* @param returnType
* @param bean
* @param beanType The type that bean is supposed to be (e.g. even though it is a Number, it actually represents a primitive).
* @return The cast bean (either to the appropriate primitive wrapper type or bean)
*
* @throws ClassCastException
* @since 1.0.0
*/
protected final Object castBean(Class returnType, Object bean, Class beanType) throws ClassCastException {
// Cast uses true value and true class of bean, not expected type (i.e. not beanType).
if (bean == null)
if (!returnType.isPrimitive())
return bean; // bean is null, and return type is not primitive, so this is a valid cast.
else
throwClassCast(returnType, bean);
else if (returnType.equals(bean.getClass()))
return bean; // They are already the same.
else if (!returnType.isPrimitive()) {
if (!beanType.isPrimitive() && returnType.isInstance(bean))
return bean;
else
throwClassCast(returnType, bean); // Either bean type was wrappering primitive or not instanceof returntype.
} else {
if (!beanType.isPrimitive())
throwClassCast(returnType, bean); // bean type was not wrappering a primitive. Can't cast to primitive.
// It is return type of primitive. Now convert to correct primitive.
if (returnType == Boolean.TYPE)
if (bean instanceof Boolean)
return bean;
else
throwClassCast(returnType, bean);
else {
if (bean instanceof Number) {
if (returnType == Integer.TYPE)
if (bean instanceof Integer)
return bean;
else
return new Integer(((Number) bean).intValue());
else if (returnType == Byte.TYPE)
if (bean instanceof Byte)
return bean;
else
return new Byte(((Number) bean).byteValue());
else if (returnType == Character.TYPE)
if (bean instanceof Character)
return bean;
else
return new Character((char) ((Number) bean).intValue());
else if (returnType == Double.TYPE)
if (bean instanceof Double)
return bean;
else
return new Double(((Number) bean).doubleValue());
else if (returnType == Float.TYPE)
if (bean instanceof Float)
return bean;
else
return new Float(((Number) bean).floatValue());
else if (returnType == Long.TYPE)
if (bean instanceof Long)
return bean;
else
return new Long(((Number) bean).longValue());
else if (returnType == Short.TYPE)
if (bean instanceof Short)
return bean;
else
return new Short(((Number) bean).shortValue());
else
throwClassCast(returnType, bean);
} else if (bean instanceof Character) {
if (returnType == Character.TYPE)
return bean;
else if (returnType == Integer.TYPE)
return new Integer(((Character) bean).charValue());
else if (returnType == Byte.TYPE)
return new Byte((byte) ((Character) bean).charValue());
else if (returnType == Double.TYPE)
return new Double(((Character) bean).charValue());
else if (returnType == Float.TYPE)
return new Float(((Character) bean).charValue());
else if (returnType == Long.TYPE)
return new Long(((Character) bean).charValue());
else if (returnType == Short.TYPE)
return new Short((short) ((Character) bean).charValue());
else
throwClassCast(returnType, bean);
} else
throwClassCast(returnType, bean);
}
}
return null; // It should never get here;
}
private void throwClassCast(Class returnType, Object bean) throws ClassCastException {
throw new ClassCastException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.CannotCastXToY_EXC_"), new Object[] {bean != null ? bean.getClass().getName() : null, returnType.getName()})); //$NON-NLS-1$
}
/**
* Return the primitive type that the wrapper bean represents (i.e. Boolean instance returns Boolean.TYPE)
* <p>
* This is a helper method for expression processer to get the primitive type. Since it is a helper method it doesn't
* check nor process the exception. It throws it. Callers must handle it as they see fit.
* @param bean
* @return the primitive type class of the given bean.
* @throws IllegalArgumentException if bean is <code>null</code> or not of the type that can be converted to a primitive.
*
* @since 1.0.0
*/
protected final Class getPrimitiveType(Object bean) throws IllegalArgumentException {
if (bean instanceof Boolean)
return Boolean.TYPE;
else if (bean instanceof Integer)
return Integer.TYPE;
else if (bean instanceof Byte)
return Byte.TYPE;
else if (bean instanceof Character)
return Character.TYPE;
else if (bean instanceof Double)
return Double.TYPE;
else if (bean instanceof Float)
return Float.TYPE;
else if (bean instanceof Long)
return Long.TYPE;
else if (bean instanceof Short)
return Short.TYPE;
else
throw new IllegalArgumentException(bean != null ? bean.getClass().getName() : "null"); //$NON-NLS-1$
}
private static final Object IFELSE_IGNORE = "IF/ELSE IGNORE"; // Flag for if/else in ingore //$NON-NLS-1$
private int ifElseNesting = 0; // Nesting of if/else expressions.
private int ifElseIgnoreNestCount = 0; // When ignoring if/else expressions, ignore until this nest count.
private boolean ifElseSkipTruePart;
/**
* Push an if test expression.
* @param hasElseClause
*
* @since 1.0.0
*/
public final void pushIfElse() {
try {
boolean ignore = true;
try {
if (errorOccurred)
return;
// Slightly different here in that if an ignoring occurred we still need to process at least part of it so that
// we can get the expression grouping correct.
ifElseNesting++; // We have the test.
if (ignoreExpression != null)
return;
ignore = false;
} finally {
if (traceOn)
printTrace("If test condition", ignore); //$NON-NLS-1$
}
try {
Object condition = popExpression();
Class type = popExpressionType(false);
if (type != Boolean.TYPE)
throwClassCast(Boolean.TYPE, condition);
if (traceOn) {
System.out.print(" Test Result="+condition); //$NON-NLS-1$
printTraceEnd();
indent(true);
printTrace("Begin True Expression.", ignore); //$NON-NLS-1$
printTraceEnd();
indent(true);
}
if (((Boolean) condition).booleanValue()) {
// Condition was true.
// Do nothing. Let true condition be processed.
} else {
// Condition was false.
ifElseSkipTruePart = true; // Tell the true condition should be ignored.
ignoreExpression = IFELSE_IGNORE;
ifElseIgnoreNestCount = ifElseNesting;
}
// We don't put anything back on the stack because the condition test is not ever returned.
// The appropriate true or false condition evaluation will be left on the stack.
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push an if/else clause. It can be any clause of the if (true, or false clause).
* @param clauseType
*
* @since 1.0.0
*/
public final void pushIfElse(InternalIfElseOperandType clauseType) {
try {
boolean ignore = true;
if (errorOccurred)
return;
// Slightly different here in that if an ignoring occurred we still need to process at least part of it so that
// we can get the expression grouping correct.
switch (clauseType.getValue()) {
case InternalIfElseOperandType.TRUE_CLAUSE_VALUE:
if (traceOn) {
indent(false);
printTrace("Begin False Expression.", ignore); //$NON-NLS-1$
printTraceEnd();
indent(true);
}
if (ifElseSkipTruePart && ignoreExpression == IFELSE_IGNORE && ifElseIgnoreNestCount == ifElseNesting) {
// stop ignoring, we've ignored the true condition of interest.
ignoreExpression = null;
return; // However, leave because since this condition was ignored.
}
break;
case InternalIfElseOperandType.ELSE_CLAUSE_VALUE:
if (traceOn) {
indent(false);
indent(false);
printTrace("End IF/ELSE Expression.", ignore); //$NON-NLS-1$
printTraceEnd();
}
int currentNesting = ifElseNesting--;
if (ignoreExpression == IFELSE_IGNORE && ifElseIgnoreNestCount == currentNesting) {
// stop ignoring, we've ignored the false condition of interest.
ignoreExpression = null;
return; // However, leave because since this condition was ignored.
}
}
if (ignoreExpression != null)
return;
ignore = false;
try {
switch (clauseType.getValue()) {
case InternalIfElseOperandType.TRUE_CLAUSE_VALUE:
ifElseSkipTruePart = false; // Tell the false condition should be ignored.
ignoreExpression = IFELSE_IGNORE;
ifElseIgnoreNestCount = ifElseNesting;
break;
case InternalIfElseOperandType.ELSE_CLAUSE_VALUE:
// There's nothing to do, if it was ignored due to true, we wouldn't of gotton here.
// If it wasn't ignored, then the result of the false expression is on the stack, which is what it should be.
break;
}
} catch (RuntimeException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push the instanceof expression. The type passed in is either a String (with classname to test against) or the
* type to test against.
* @param type To test against.
* @since 1.0.0
*/
public final void pushInstanceof(Class type) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("Instanceof type: "+type, ignore); //$NON-NLS-1$
try {
if (ignore)
return;
try {
Object exp = popExpression();
Class exptype = popExpressionType(false);
pushExpressionValue(Boolean.valueOf(isInstance(type, exp, exptype)), Boolean.TYPE);
} catch (NoExpressionValueException e) {
processException(e);
} catch (RuntimeException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Test if instance of. It will make sure that primitive to non-primitive is not permitted.
* This is a true instance of, which means null IS NOT AN instance of any type. This is
* different then assignable from, in that case null can be assigned to any class type.
* <p>
* This is a helper method for expression processer to do isInstance. Since it is a helper method it doesn't
* check nor process exceptions.
* @param type
* @param bean
* @param beanType
* @return
*
* @since 1.0.0
*/
protected final boolean isInstance(Class type, Object bean, Class beanType) {
if (type.isPrimitive())
return beanType.isPrimitive() && type == beanType; // Can't use isInstance because for a primitive type isInstance returns false.
else
return type.isInstance(bean);
}
/**
* Push new instance from string.
* @param initializationString
* @param resultType expected result type. If it isn't of that type, a classcast will be processed.
* @param classloader classloader to use for finding classes, or <code>null</code> to use classloader of InitializationStringParser.class.
*
* @since 1.1.0
*/
public final void pushNewInstanceFromString(String initializationString, Class resultType, ClassLoader classloader) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("New instance from string: \""+initializationString+"\" Type="+resultType, ignore); //$NON-NLS-1$ //$NON-NLS-2$
try {
if (ignore)
return;
try {
InitializationStringParser parser = InitializationStringParser.createParser(initializationString, classloader);
Object newValue = parser.evaluate();
newValue = castBean(resultType, newValue, parser.getExpectedType());
pushExpressionValue(newValue, resultType);
} catch (RuntimeException e) {
processException(e);
} catch (InitializationStringEvaluationException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push prefix expression.
* @param operator
* @since 1.0.0
*/
public final void pushPrefix(PrefixOperator operator) {
try {
if (ignoreExpression != null || errorOccurred) {
if (traceOn)
printTrace("Prefix: \'"+operator+"\'", true); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
if (operator == PrefixOperator.PRE_PLUS)
return; // Do nothing. "+" doesn't affect the result of the current top expression.
if (traceOn)
printTrace("Prefix: \'"+operator+"\' ", false); //$NON-NLS-1$ //$NON-NLS-2$
try {
Object exp = popExpression();
Class exptype = popExpressionType(false);
if (!exptype.isPrimitive())
throwInvalidPrefix(operator, exp);
int primTypeEnum = getEnumForPrimitive(exptype);
switch (operator.getValue()) {
case PrefixOperator.PRE_MINUS_VALUE:
switch (primTypeEnum) {
case BOOLEAN:
throwInvalidPrefix(operator, exp);
case BYTE:
exp = new Integer(-((Number) exp).byteValue());
break;
case CHAR:
exp = new Integer(-((Character) exp).charValue());
break;
case DOUBLE:
exp = new Double(-((Number) exp).doubleValue());
break;
case FLOAT:
exp = new Float(-((Number) exp).floatValue());
break;
case INT:
exp = new Integer(-((Number) exp).intValue());
break;
case LONG:
exp = new Long(-((Number) exp).longValue());
break;
case SHORT:
exp = new Integer(-((Number) exp).shortValue());
break;
}
exptype = getPrimitiveType(exp); // It can actually change the type.
break;
case PrefixOperator.PRE_COMPLEMENT_VALUE:
switch (primTypeEnum) {
case BOOLEAN:
case DOUBLE:
case FLOAT:
throwInvalidPrefix(operator, exp);
case BYTE:
exp = new Integer(~((Number) exp).byteValue());
break;
case CHAR:
exp = new Integer(~((Character) exp).charValue());
break;
case INT:
exp = new Integer(~((Number) exp).intValue());
break;
case LONG:
exp = new Long(~((Number) exp).longValue());
break;
case SHORT:
exp = new Integer(~((Number) exp).shortValue());
break;
}
exptype = getPrimitiveType(exp); // It can actually change the type.
break;
case PrefixOperator.PRE_NOT_VALUE:
switch (primTypeEnum) {
case BOOLEAN:
exp = !((Boolean) exp).booleanValue() ? Boolean.TRUE : Boolean.FALSE;
break;
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
throwInvalidPrefix(operator, exp);
}
break;
}
if (traceOn)
printObjectAndType(exp, exptype);
pushExpressionValue(exp, exptype); // Push the result back on the stack.
} catch (IllegalArgumentException e) {
processSyntaxException(e);
} catch (NoExpressionValueException e) {
processException(e);
} catch (RuntimeException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Assign the right expression to the left expression.
* @since 1.1.0
*/
public final void pushAssignment() {
if (ignoreExpression != null || errorOccurred) {
if (traceOn) {
printTrace("Assignment", true); //$NON-NLS-1$
printTraceEnd();
}
return;
}
try {
// KLUDGE: The only reason leftValue/refType are outside of try/finally is because
// of tracing. pushExpression() does its own trace statements, so we need to end
// our trace before calling pushExpression.
Object leftValue;
Class refType;
try {
if (traceOn)
printTrace("Assignment: ", false); //$NON-NLS-1$
// The order on the stack is right then left operand.
// First the right operand
Object value = popExpression();
Class type = popExpressionType(false);
// Next the left operand, should be a reference.
VariableReference left = (VariableReference) popExpression(false); // Don't dereference it.
refType = popExpressionType(false);
if (traceOn)
printObjectAndType(left, refType);
leftValue = left.set(value, type);
} finally {
if (traceOn)
printTraceEnd();
}
// Now do assignment and return the value to the stack.
pushExpression(leftValue, refType); // The type of the result is the type of the reference.
} catch (IllegalArgumentException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
} catch (IllegalAccessException e) {
processException(e);
} catch (RuntimeException e) {
processException(e);
}
}
/**
* Assign the expression proxy to the top expression value.
*
* @param proxy
*
* @since 1.1.0
*/
public final void pushAssignment(InternalExpressionProxy proxy) {
boolean ignore = (ignoreExpression != null || errorOccurred);
try {
if (traceOn) {
printTrace("Assign to Proxy #"+proxy.getProxyID(), ignore); //$NON-NLS-1$
}
if (ignore)
return;
try {
assignToExpressionProxyFromTopStackEntry(proxy);
if (traceOn)
printObjectAndType(proxy.getValue(), proxy.getType());
} catch (NoExpressionValueException e) {
processException(e);
} catch (RuntimeException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Assign the top stack entry to the new expression proxy and allocate it for callback later.
* @param proxy
* @throws NoExpressionValueException
*
* @since 1.1.0
*/
protected void assignToExpressionProxyFromTopStackEntry(InternalExpressionProxy proxy) throws NoExpressionValueException {
Object value = getExpression(1);
Class type = getExpressionType(1, true);
if (value instanceof VariableReference)
value = ((VariableReference) value).dereference(); // Here we want the final current value.
proxy.setProxy(value, type);
allocateExpressionProxy(proxy);
}
/**
* Allocate an expression proxy. This is used to make an expression proxy known to the processor. The expression proxy must
* have been setProxy() at this point. This is used to assign from the top of the stack or to add from outside an evaluated proxy
* to be used later by others.
*
* @param proxy
*
* @since 1.1.0
*/
public void allocateExpressionProxy(InternalExpressionProxy proxy) {
int minSize = proxy.getProxyID()+1;
if (expressionProxies == null)
expressionProxies = new ArrayList(minSize+10); // Allow room to grow ten more.
else if (expressionProxies.size() < minSize)
expressionProxies.ensureCapacity(minSize+10); // Allow room to grow ten more.
int fill = minSize-expressionProxies.size(); // Number of "null" fill entries needed. Probably shouldn't occur, but to be safe.
if (fill > 0) {
while (--fill > 0)
expressionProxies.add(null);
expressionProxies.add(proxy);
} else
expressionProxies.set(proxy.getProxyID(), proxy); // Already large enough, replace entry.
}
/**
* The primitive enums.
* NOTE: Their order must not changed. They are in order of permitted widening.
*
*/
protected static final int
BOOLEAN = 0,
BYTE = 1,
SHORT = 2,
CHAR = 3,
INT = 4,
LONG = 5,
FLOAT = 6,
DOUBLE = 7;
/**
* Get the enum constant for the type of primitive passed in.
* <p>
* This is a helper method for expression processer to get the enum for the primitive type. Since it is a helper method it doesn't
* check nor process the exception. It throws it. Callers must handle it as they see fit.
* @param primitiveType
* @return
* @throws IllegalArgumentException if type is not a primitive.
*
* @see ExpressionProcesser#BOOLEAN
* @since 1.0.0
*/
protected final int getEnumForPrimitive(Class primitiveType) throws IllegalArgumentException {
if (primitiveType == Boolean.TYPE)
return BOOLEAN;
else if (primitiveType == Integer.TYPE)
return INT;
else if (primitiveType == Byte.TYPE)
return BYTE;
else if (primitiveType == Character.TYPE)
return CHAR;
else if (primitiveType == Double.TYPE)
return DOUBLE;
else if (primitiveType == Float.TYPE)
return FLOAT;
else if (primitiveType == Long.TYPE)
return LONG;
else if (primitiveType == Short.TYPE)
return SHORT;
else
throw new IllegalArgumentException(primitiveType != null ? primitiveType.getName() : "null"); //$NON-NLS-1$
}
private void throwInvalidPrefix(PrefixOperator operator, Object exp) throws IllegalArgumentException {
throw new IllegalArgumentException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.InvalidOperandOfPrefixOperator_EXC_"), new Object[] {exp != null ? exp.toString() : null, operator.toString()})); //$NON-NLS-1$
}
private static final Object INFIX_IGNORE = "INFIX IGNORE"; // Flag for infix in ingore //$NON-NLS-1$
private int infixNesting = 0; // Nesting of infix expressions.
private int infixIgnoreNestCount = 0; // When ignoring infix expressions, ignore until this nest count.
/**
* Push the infix expression onto the stack.
* @param operator
* @param operandType The operator type. Left, right, other.
* @since 1.0.0
*/
public final void pushInfix(InfixOperator operator, InternalInfixOperandType operandType) {
try {
boolean ignore = true;
try {
if (errorOccurred) {
return;
}
// Slightly different here in that if an ignored occurred we still need to process at least part of it so that
// we can get the expression grouping correct.
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
infixNesting++;
else if (operandType == InternalInfixOperandType.INFIX_LAST_OPERAND) {
int currentNest = infixNesting--;
if (ignoreExpression == INFIX_IGNORE && currentNest == infixIgnoreNestCount) {
// We were ignoring, and it was this expression that was being ignore.
// We have received the last operand of the nested expression that was being ignored,
// so we can stop ignoring. But we still leave since the value of the expression is on the
// top of the stack.
ignoreExpression = null;
return;
}
}
if (ignoreExpression != null)
return;
ignore = false;
} finally {
if (traceOn)
printTrace("Infix: "+operator, ignore); //$NON-NLS-1$
}
try {
Object right = null;
Class rightType = null;
if (operandType != InternalInfixOperandType.INFIX_LEFT_OPERAND) {
// We are not the left operand, so the stack has the right on the top, followed by the left.
right = popExpression();
rightType = popExpressionType(false);
}
Object value = popExpression();
Class valueType = popExpressionType(false);
switch (operator.getValue()) {
case InfixOperator.IN_AND_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidBitType(valueType, InfixOperator.IN_AND);
testValidBitType(rightType, InfixOperator.IN_AND);
if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) & getLong(right));
valueType = Long.TYPE;
} else {
// Else it is int. (even two shorts together produce an int).
value = new Integer(getInt(value) & getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_CONDITIONAL_AND_VALUE:
// This is tricky.
// First if this is left type, then just continue.
// Else if this other or last, then need to make it the new value.
if (operandType != InternalInfixOperandType.INFIX_LEFT_OPERAND) {
value = right;
valueType = rightType;
}
//If the value is now false, we need to ignore the rest.
if (valueType != Boolean.TYPE)
throwInvalidInfix(operator, value);
if (!((Boolean) value).booleanValue() && operandType != InternalInfixOperandType.INFIX_LAST_OPERAND)
startInfixIgnore(); // Start ignoring because we know the value of the expression at this point. It is false.
break;
case InfixOperator.IN_CONDITIONAL_OR_VALUE:
// This is tricky.
// First if this is left type, then just continue.
// Else if this other or last, then need to make it the new value.
if (operandType != InternalInfixOperandType.INFIX_LEFT_OPERAND) {
value = right;
valueType = rightType;
}
//If the value is now true, we need to ignore the rest.
if (valueType != Boolean.TYPE)
throwInvalidInfix(operator, value);
if (((Boolean) value).booleanValue() && operandType != InternalInfixOperandType.INFIX_LAST_OPERAND)
startInfixIgnore(); // Start ignoring because we know the value of the expression at this point. It is true.
break;
case InfixOperator.IN_DIVIDE_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_DIVIDE);
testValidArithmeticType(rightType, InfixOperator.IN_DIVIDE);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, the result will be double.
value = new Double(getDouble(value) / getDouble(right));
valueType = Double.TYPE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, the result will be float.
value = new Float(getFloat(value) / getFloat(right));
valueType = Float.TYPE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) / getLong(right));
valueType = Long.TYPE;
} else {
// Else it will result in an int, even if both sides are short.
value = new Integer(getInt(value) / getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_EQUALS_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
// We should never get extended operator for this, but we'll ignore the possibility.
if (valueType.isPrimitive() && rightType.isPrimitive()) {
// Primitives require more testing than just ==. boolean primitives
if (valueType == Boolean.TYPE || rightType == Boolean.TYPE) {
// If either side is a boolean, then the other side needs to be boolean for it to even try to be true.
if (valueType != Boolean.TYPE || valueType != Boolean.TYPE)
value = Boolean.FALSE;
else
value = (((Boolean) value).booleanValue() == ((Boolean) right).booleanValue()) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Now do number tests since not boolean primitive, only numbers are left
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, compare as double.
value = (getDouble(value) == getDouble(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, compare as float.
value = (getFloat(value) == getFloat(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the compare as long.
value = (getLong(value) == getLong(right)) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Else it will compare as int, even if both sides are short.
value = (getInt(value) == getInt(right)) ? Boolean.TRUE : Boolean.FALSE;
}
}
} else if (valueType.isPrimitive() || rightType.isPrimitive())
value = Boolean.FALSE; // Can't be true if one side prim and the other isn't
else {
// Just do object ==
value = (value == right) ? Boolean.TRUE : Boolean.FALSE;
}
valueType = Boolean.TYPE; // We know result will be a boolean.
break;
case InfixOperator.IN_GREATER_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_GREATER);
testValidArithmeticType(rightType, InfixOperator.IN_GREATER);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, compare will be double.
value = (getDouble(value) > getDouble(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, compare will be float.
value = (getFloat(value) > getFloat(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, compare will be long.
value = (getLong(value) > getLong(right)) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Else compare will be int, even if both sides are short.
value = (getInt(value) > getInt(right)) ? Boolean.TRUE : Boolean.FALSE;
}
valueType = Boolean.TYPE; // We know result will be a boolean.
break;
case InfixOperator.IN_GREATER_EQUALS_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_GREATER_EQUALS);
testValidArithmeticType(rightType, InfixOperator.IN_GREATER_EQUALS);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, compare will be double.
value = (getDouble(value) >= getDouble(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, compare will be float.
value = (getFloat(value) >= getFloat(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, compare will be long.
value = (getLong(value) >= getLong(right)) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Else compare will be int, even if both sides are short.
value = (getInt(value) >= getInt(right)) ? Boolean.TRUE : Boolean.FALSE;
}
valueType = Boolean.TYPE; // We know result will be a boolean.
break;
case InfixOperator.IN_LEFT_SHIFT_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidBitType(valueType, InfixOperator.IN_LEFT_SHIFT);
testValidBitType(rightType, InfixOperator.IN_LEFT_SHIFT);
if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) << getLong(right));
valueType = Long.TYPE;
} else {
// Else it is int. (even two shorts together produce an int).
value = new Integer(getInt(value) << getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_LESS_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_LESS);
testValidArithmeticType(rightType, InfixOperator.IN_LESS);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, compare will be double.
value = (getDouble(value) < getDouble(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, compare will be float.
value = (getFloat(value) < getFloat(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, compare will be long.
value = (getLong(value) < getLong(right)) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Else compare will be int, even if both sides are short.
value = (getInt(value) < getInt(right)) ? Boolean.TRUE : Boolean.FALSE;
}
valueType = Boolean.TYPE; // We know result will be a boolean.
break;
case InfixOperator.IN_LESS_EQUALS_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_LESS_EQUALS);
testValidArithmeticType(rightType, InfixOperator.IN_LESS_EQUALS);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, compare will be double.
value = (getDouble(value) <= getDouble(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, compare will be float.
value = (getFloat(value) <= getFloat(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, compare will be long.
value = (getLong(value) <= getLong(right)) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Else compare will be int, even if both sides are short.
value = (getInt(value) <= getInt(right)) ? Boolean.TRUE : Boolean.FALSE;
}
valueType = Boolean.TYPE; // We know result will be a boolean.
break;
case InfixOperator.IN_MINUS_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_MINUS);
testValidArithmeticType(rightType, InfixOperator.IN_MINUS);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, the result will be double.
value = new Double(getDouble(value) - getDouble(right));
valueType = Double.TYPE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, the result will be float.
value = new Float(getFloat(value) - getFloat(right));
valueType = Float.TYPE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) - getLong(right));
valueType = Long.TYPE;
} else {
// Else it will result in an int, even if both sides are short.
value = new Integer(getInt(value) - getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_NOT_EQUALS_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
// We should never get extended operator for this, but we'll ignore the possibility.
if (valueType.isPrimitive() && rightType.isPrimitive()) {
// Primitives require more testing than just ==. boolean primitives
if (valueType == Boolean.TYPE || rightType == Boolean.TYPE) {
// If either side is a boolean, then the other side needs to be boolean for it to even try to be true.
if (valueType != Boolean.TYPE || valueType != Boolean.TYPE)
value = Boolean.TRUE;
else
value = (((Boolean) value).booleanValue() != ((Boolean) right).booleanValue()) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Now do number tests since not boolean primitive, only numbers are left
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, compare as double.
value = (getDouble(value) != getDouble(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, compare as float.
value = (getFloat(value) != getFloat(right)) ? Boolean.TRUE : Boolean.FALSE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the compare as long.
value = (getLong(value) != getLong(right)) ? Boolean.TRUE : Boolean.FALSE;
} else {
// Else it will compare as int, even if both sides are short.
value = (getInt(value) != getInt(right)) ? Boolean.TRUE : Boolean.FALSE;
}
}
} else if (valueType.isPrimitive() || rightType.isPrimitive())
value = Boolean.TRUE; // Must be true if one side prim and the other isn't
else {
// Just do object !=
value = (value != right) ? Boolean.TRUE : Boolean.FALSE;
}
valueType = Boolean.TYPE; // We know result will be a boolean.
break;
case InfixOperator.IN_OR_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidBitType(valueType, InfixOperator.IN_OR);
testValidBitType(rightType, InfixOperator.IN_OR);
if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) | getLong(right));
valueType = Long.TYPE;
} else {
// Else it is int. (even two shorts together produce an int).
value = new Integer(getInt(value) | getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_PLUS_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND) {
if (valueType == String.class) {
// Special. left argument is a string, so we want to store a string buffer instead
// since we know we will be appending to it.
value = new StringBuffer((String) value);
}
break; // Do nothing with first operand
}
testValidPlusType(valueType, rightType);
if (valueType == String.class || rightType == String.class) {
// Special we have a string on one side. Need to do it as strings instead.
// We are going to be tricky in that we will store a StringBuffer on the stack (if not last operand)
// but call it a string.
StringBuffer sb = null;
if (valueType == String.class) {
sb = (StringBuffer) value; // We know that if the value (left) is string type, we've already converted it to buffer.
} else {
// The right is the one that introduces the string, so we change the value over to a string buffer.
sb = new StringBuffer(((String) right).length()+16); // We can't put the value in yet, need to get left into it.
appendToBuffer(sb, value, valueType); // Put the left value in now
value = sb;
valueType = String.class; // Make it a string class
}
appendToBuffer(sb, right, rightType);
// Now if we are the last operand, we should get rid of the buffer and put a true string back in.
if (operandType == InternalInfixOperandType.INFIX_LAST_OPERAND)
value = sb.toString();
} else if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, the result will be double.
value = new Double(getDouble(value) + getDouble(right));
valueType = Double.TYPE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, the result will be float.
value = new Float(getFloat(value) + getFloat(right));
valueType = Float.TYPE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) + getLong(right));
valueType = Long.TYPE;
} else {
// Else it will result in an int, even if both sides are short.
value = new Integer(getInt(value) + getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_REMAINDER_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_REMAINDER);
testValidArithmeticType(rightType, InfixOperator.IN_REMAINDER);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, the result will be double.
value = new Double(getDouble(value) % getDouble(right));
valueType = Double.TYPE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, the result will be float.
value = new Float(getFloat(value) % getFloat(right));
valueType = Float.TYPE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) % getLong(right));
valueType = Long.TYPE;
} else {
// Else it will result in an int, even if both sides are short.
value = new Integer(getInt(value) % getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_RIGHT_SHIFT_SIGNED_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidBitType(valueType, InfixOperator.IN_RIGHT_SHIFT_SIGNED);
testValidBitType(rightType, InfixOperator.IN_RIGHT_SHIFT_SIGNED);
if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) >> getLong(right));
valueType = Long.TYPE;
} else {
// Else it is int. (even two shorts together produce an int).
value = new Integer(getInt(value) >> getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_RIGHT_SHIFT_UNSIGNED_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidBitType(valueType, InfixOperator.IN_RIGHT_SHIFT_UNSIGNED);
testValidBitType(rightType, InfixOperator.IN_RIGHT_SHIFT_UNSIGNED);
if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) >>> getLong(right));
valueType = Long.TYPE;
} else {
// Else it is int. (even two shorts together produce an int).
value = new Integer(getInt(value) >>> getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_TIMES_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidArithmeticType(valueType, InfixOperator.IN_TIMES);
testValidArithmeticType(rightType, InfixOperator.IN_TIMES);
if (valueType == Double.TYPE || rightType == Double.TYPE) {
// If either side is double, the result will be double.
value = new Double(getDouble(value) * getDouble(right));
valueType = Double.TYPE;
} else if (valueType == Float.TYPE || rightType == Float.TYPE) {
// If either side is float, the result will be float.
value = new Float(getFloat(value) * getFloat(right));
valueType = Float.TYPE;
} else if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) * getLong(right));
valueType = Long.TYPE;
} else {
// Else it will result in an int, even if both sides are short.
value = new Integer(getInt(value) * getInt(right));
valueType = Integer.TYPE;
}
break;
case InfixOperator.IN_XOR_VALUE:
if (operandType == InternalInfixOperandType.INFIX_LEFT_OPERAND)
break; // Do nothing with first operand
testValidBitType(valueType, InfixOperator.IN_XOR);
testValidBitType(rightType, InfixOperator.IN_XOR);
if (valueType == Long.TYPE || rightType == Long.TYPE) {
// If either side is long, the result will be long.
value = new Long(getLong(value) ^ getLong(right));
valueType = Long.TYPE;
} else {
// Else it is int. (even two shorts together produce an int).
value = new Integer(getInt(value) ^ getInt(right));
valueType = Integer.TYPE;
}
break;
}
if (traceOn)
printObjectAndType(value, valueType);
pushExpressionValue(value, valueType); // Push the result back on the stack.
} catch (IllegalArgumentException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
} catch (RuntimeException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Start ignoring rest of the current infix expression.
*
* @since 1.1.0
*/
private void startInfixIgnore() {
ignoreExpression = INFIX_IGNORE;
infixIgnoreNestCount = infixNesting; // Ignore until we get back to the current nesting.
}
/**
* Get int value of the primitive wrapper bean passed in (must be either a <code>Number/code> or <code>Character</code>.
* Anything else will cause a class cast error.
* <p>
* This is a helper method for expression processer to get the int value of the object. Since it is a helper method it doesn't
* check nor process the exception. It throws it. Callers must handle it as they see fit.
* @param bean
* @return the int value of the number/character
* @throws ClassCastException
*
* @since 1.0.0
*/
protected final int getInt(Object bean) throws ClassCastException {
return (bean instanceof Number) ? ((Number) bean).intValue() : ((Character) bean).charValue();
}
/**
* Get float value of the primitive wrapper bean passed in (must be either a <code>Number/code> or <code>Character</code>.
* Anything else will cause a class cast error.
* <p>
* This is a helper method for expression processer to get the float value of the object. Since it is a helper method it doesn't
* check nor process the exception. It throws it. Callers must handle it as they see fit.
* @param bean
* @return float value of the Number/character
* @throws ClassCastException
* @since 1.0.0
*/
protected final float getFloat(Object bean) throws ClassCastException {
return (bean instanceof Number) ? ((Number) bean).floatValue() : ((Character) bean).charValue();
}
/**
* Get double value of the primitive wrapper bean passed in (must be either a <code>Number/code> or <code>Character</code>.
* Anything else will cause a class cast error.
* <p>
* This is a helper method for expression processer to get the float value of the object. Since it is a helper method it doesn't
* check nor process the exception. It throws it. Callers must handle it as they see fit.
*
* @param bean
* @return double value of the Number/Character.
* @throws ClassCastException
* @since 1.0.0
*/
protected final double getDouble(Object bean) throws ClassCastException {
return (bean instanceof Number) ? ((Number) bean).doubleValue() : ((Character) bean).charValue();
}
/**
* Get long value of the primitive wrapper bean passed in (must be either a <code>Number/code> or <code>Character</code>.
* Anything else will cause a class cast error.
* <p>
* This is a helper method for expression processer to get the float value of the object. Since it is a helper method it doesn't
* check nor process the exception. It throws it. Callers must handle it as they see fit.
*
* @param bean
* @return
* @throws ClassCastException
* @since 1.0.0
*/
protected final long getLong(Object bean) throws ClassCastException {
return (bean instanceof Number) ? ((Number) bean).longValue() : ((Character) bean).charValue();
}
private void throwInvalidInfix(InfixOperator operator, Object value) throws IllegalArgumentException {
throw new IllegalArgumentException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.InvalidOperandOfOperator_EXC_"), new Object[] {value != null ? value.toString() : null, operator.toString()})); //$NON-NLS-1$
}
private void testValidBitType(Class type, InfixOperator operator) {
if (!type.isPrimitive() || type == Boolean.TYPE || type == Double.TYPE|| type == Float.TYPE)
throwInvalidInfix(operator, type);
}
private void testValidArithmeticType(Class type, InfixOperator operator) {
if (!type.isPrimitive() || type == Boolean.TYPE)
throwInvalidInfix(operator, type);
}
private void testValidPlusType(Class left, Class right) {
// Plus is special in that string objects are also valid.
if (left == String.class || right == String.class)
return; // As long as one side is string. Anything is valid.
// If neither is string, then standard arithmetic test.
testValidArithmeticType(left, InfixOperator.IN_PLUS);
testValidArithmeticType(right, InfixOperator.IN_PLUS);
}
private void appendToBuffer(StringBuffer sb, Object value, Class valueType) {
if (value == null)
sb.append((Object)null);
else if (valueType == String.class)
sb.append((String) value);
else if (valueType.isPrimitive()) {
switch (getEnumForPrimitive(valueType)) {
case BOOLEAN:
sb.append(((Boolean) value).booleanValue());
break;
case BYTE:
sb.append(((Number) value).byteValue());
break;
case CHAR:
sb.append(((Character) value).charValue());
break;
case DOUBLE:
sb.append(((Number) value).doubleValue());
break;
case FLOAT:
sb.append(((Number) value).floatValue());
break;
case INT:
sb.append(((Number) value).intValue());
break;
case LONG:
sb.append(((Number) value).longValue());
break;
case SHORT:
sb.append(((Number) value).shortValue());
break;
}
} else {
// Just an object.
sb.append(value);
}
}
/**
* Push the array access expression.
*
* @param indexCount Number of dimensions being accessed
* @since 1.0.0
*/
public final void pushArrayAccess(int indexCount) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn) {
printTrace("Array Access["+indexCount+']', ignore); //$NON-NLS-1$
}
try {
if (ignore)
return;
try {
// We need to pop off the args. The topmost will be the rightmost index, and the bottom most will be the array itself.
int[] arguments = new int[indexCount];
// Fill the arg array in reverse order.
for(int i=indexCount-1; i >= 0; i--) {
Object index = popExpression();
Class indexType = popExpressionType(false);
if (indexType.isPrimitive() && (indexType == Integer.TYPE || indexType == Short.TYPE || indexType == Character.TYPE || indexType == Byte.TYPE)) {
arguments[i] = getInt(index);
} else
throwClassCast(Integer.TYPE, index);
}
Object array = popExpression();
Class arrayType = popExpressionType(false);
if (arrayType.isArray()) {
// First figure out how many dimensions are available. Stop when we hit indexcount because we won't be going further.
int dimcount = 0;
Class[] componentTypes = new Class[indexCount]; //
Class componentType = arrayType;
while (dimcount < indexCount && componentType.isArray()) {
componentTypes[dimcount++] = componentType = componentType.getComponentType();
}
if (dimcount < indexCount)
throw new IllegalArgumentException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.XIsGreaterThanNumberOfDimensionsInArray_EXC_"), new Object[] {new Integer(indexCount), new Integer(dimcount)})); //$NON-NLS-1$
// Now start accessing one index at a time, stop just before the last one. The last one will be turned into an ArrayAccessReference.
Object value = array; // Final value, start with full array.
int pullCount = indexCount-1;
for(int i=0; i<pullCount; i++) {
value = Array.get(value, arguments[i]);
}
ArrayAccessReference arrayValue = ArrayAccessReference.createArrayAccessReference(value, arguments[pullCount]);
if (traceOn)
printObjectAndType(arrayValue, componentTypes[pullCount]);
pushExpressionValue(arrayValue, componentTypes[pullCount]);
} else
throw new IllegalArgumentException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.NotAnArray_EXC_"), new Object[] {arrayType})); //$NON-NLS-1$
} catch (NoExpressionValueException e) {
processException(e);
} catch (RuntimeException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push the array creation request.
*
* @param arrayType The type of the array
* @param dimensionCount The number of dimensions being initialized. Zero if using an initializer.
* @since 1.0.0
*/
public final void pushArrayCreation(Class arrayType, int dimensionCount) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("Array Creation: "+arrayType.getName()+'['+dimensionCount+']', ignore); //$NON-NLS-1$
try {
if (ignore)
return;
try {
if (dimensionCount == 0) {
// The top value is the array itself, from the array initializer.
// So we do nothing.
} else {
// Strip off dimensionCounts from the array type, e.g.
// ArrayType is int[][][]
// Dimensioncount is 2
// Then we need to strip two componenttypes off of the array type
// wind up with int[]
// This is necessary because Array.new will add those dimensions back
// on through the dimension count.
Class componentType = arrayType;
for(int i=0; i < dimensionCount && componentType != null; i++)
componentType = componentType.getComponentType();
if (componentType == null)
throw new IllegalArgumentException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.ArraytypeHasFewerDimensionsThanRequested_EXC_"), new Object[] {arrayType, new Integer(dimensionCount)})); //$NON-NLS-1$
// We need to pull in the dimension initializers. They are stacked in reverse order.
int[] dimInit = new int[dimensionCount];
for(int i=dimensionCount-1; i >= 0; i--) {
Object index = popExpression();
Class dimType = popExpressionType(false);
if (dimType.isPrimitive() && (dimType == Integer.TYPE || dimType == Short.TYPE || dimType == Character.TYPE || dimType == Byte.TYPE)) {
dimInit[i] = getInt(index);
} else
throwClassCast(Integer.TYPE, index);
}
// Finally create the array.
Object array = Array.newInstance(componentType, dimInit);
pushExpressionValue(array, arrayType);
}
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push the array initializer request.
*
* @param arrayType The original type of the array to create.
* @param stripCount the count of how many dimensions to strip to get the type needed for this initializer.
* @param expressionCount
* @since 1.0.0
*/
public final void pushArrayInitializer(Class arrayType, int stripCount, int expressionCount) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("Initialize Array: "+arrayType.getName()+'{'+expressionCount+'}', ignore); //$NON-NLS-1$
try {
if (ignore)
return;
try {
if (!arrayType.isArray()) {
// It is not an array type.
throw new ClassCastException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.CannotCastXToY_EXC_"), new Object[] {arrayType, "array"})); //$NON-NLS-1$ //$NON-NLS-2$
}
// Strip off the number of dimensions specified.
while(stripCount-->0) {
arrayType = arrayType.getComponentType();
}
Object[] dimValues = null;
if (expressionCount > 0) {
// We need to pull in the initializers. They are stacked in reverse order.
dimValues = new Object[expressionCount];
for (int i = expressionCount - 1; i >= 0; i--) {
Object dimValue = dimValues[i] = popExpression();
Class dimType = popExpressionType(false);
if (arrayType.isPrimitive()) {
if (dimValue == null || !dimType.isPrimitive())
throwClassCast(arrayType, dimType);
// A little trickier. Can assign short to an int, but can't assign long to an int. Widening is permitted.
if (arrayType != dimType) {
int compEnum = getEnumForPrimitive(arrayType);
int dimEnum = getEnumForPrimitive(dimType);
if (compEnum == BOOLEAN || dimEnum == BOOLEAN)
throwClassCast(arrayType, dimType);
int dimValueAsInt = getInt(dimValue);
switch (compEnum) {
case BYTE :
// Can accept byte, short, char, or int as long as value is <= byte max. Can't accept long, double, float at all.
// Note: This isn't actually true. The max/min test is only valid if the value is a literal, not an expression,
// however, at this point in time we no longer know this. So we will simply allow it.
if (dimEnum > INT || dimValueAsInt > Byte.MAX_VALUE || dimValueAsInt < Byte.MIN_VALUE)
throwClassCast(arrayType, dimType);
// But need to be changed to appropriate type for the array.set to work.
dimValues[i] = new Byte((byte)dimValueAsInt);
break;
case SHORT :
// Can accept byte, short, char, or int as long as value is <= byte max. Can't accept long, double, float at all.
// Note: This isn't actually true. The max/min test is only valid if the value is a literal, not an expression,
// however, at this point in time we no longer know this. So we will simply allow it.
if (dimEnum > INT || dimValueAsInt > Short.MAX_VALUE || dimValueAsInt < Short.MIN_VALUE)
throwClassCast(arrayType, dimType);
// But need to be changed to appropriate type for the array.set to work.
dimValues[i] = new Short((short)dimValueAsInt);
break;
case CHAR :
// Can accept byte, short, char, or int as long as value is <= byte max. Can't accept long, double, float at all.
// Note: This isn't actually true. The max/min test is only valid if the value is a literal, not an expression,
// however, at this point in time we no longer know this. So we will simply allow it.
if (dimEnum > INT || dimValueAsInt > Character.MAX_VALUE || dimValueAsInt < Character.MIN_VALUE)
throwClassCast(arrayType, dimType);
// But need to be changed to appropriate type for the array.set to work.
dimValues[i] = new Character((char)dimValueAsInt);
break;
case INT :
// Can accept byte, short, char, or int. Can't accept long, double, float at all.
if (dimEnum > INT)
throwClassCast(arrayType, dimType);
// But need to be changed to appropriate type for the array.set to work.
dimValues[i] = new Integer(dimValueAsInt);
break;
case LONG :
// Can accept byte, short, char, int, or long. Can't accept double, float at all.
if (dimEnum > LONG)
throwClassCast(arrayType, dimType);
// But need to be changed to appropriate type for the array.set to work.
dimValues[i] = new Long(getLong(dimValue));
break;
case FLOAT :
// Can accept byte, short, char, int, long, or float. Can't accept double at all.
if (dimEnum > FLOAT)
throwClassCast(arrayType, dimType);
// But need to be changed to appropriate type for the array.set to work.
dimValues[i] = new Float(getFloat(dimValue));
break;
case DOUBLE :
// But need to be changed to appropriate type for the array.set to work.
dimValues[i] = new Double(getDouble(dimValue));
break;
}
}
// Compatible, so ok.
} else if (dimType != MethodHelper.NULL_TYPE && !arrayType.isAssignableFrom(dimType)) {
// If it is NULL_TYPE, then this is a pushed null. This is always assignable to a non-primitive.
// So we don't enter here in that case. However, a null that was returned from some expression
// won't have a NULL_TYPE, it will instead have the expected return type. That must be used
// in the assignment instead. That is because in java it uses the expected type to determine
// compatibility, not the actual type.
throwClassCast(arrayType, dimType);
}
}
}
// Now we finally create the array.
Object array = Array.newInstance(arrayType, new int[] {expressionCount});
for (int i = 0; i < expressionCount; i++) {
Array.set(array, i, dimValues[i]);
}
pushExpressionValue(array, array.getClass()); // Adjust to true array type, not the incoming type (which is one dimension too small).
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push the class instance creation request.
*
* @param type The type to create an instance of
* @param argumentCount The number of arguments (which are stored on the stack). * @throws NoExpressionValueException
* @since 1.0.0
*/
public final void pushClassInstanceCreation(Class type, int argumentCount) {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("Create Class: "+type+" (", ignore); //$NON-NLS-1$ //$NON-NLS-2$
try {
if (ignore)
return;
try {
// We need to pull in the arguments. They are stacked in reverse order.
Object value = null; // The new instance.
Object[] args = new Object[argumentCount];
Class[] argTypes = new Class[argumentCount];
for (int i = argumentCount - 1; i >= 0; i--) {
args[i] = popExpression();
argTypes[i] = popExpressionType(false);
}
// Now we need to find the appropriate constructor.
Constructor ctor;
ctor = MethodHelper.findCompatibleConstructor(type, argTypes);
if (traceOn) {
if (argumentCount == 0) {
// No args, just do default ctor.
System.out.print("Default ctor)"); //$NON-NLS-1$
} else {
System.out.print(ctor);
System.out.print(')');
}
}
value = ctor.newInstance(args);
pushExpressionValue(value, type);
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
} catch (InstantiationException e) {
processException(e);
} catch (IllegalAccessException e) {
processException(e);
} catch (InvocationTargetException e) {
processException(e);
} catch (NoSuchMethodException e) {
processException(e);
} catch (AmbiguousMethodException e) {
processException(e);
} catch (LinkageError e) {
processException(e);
} catch (Error e) {
if (e.getClass() == Error.class) {
// If exactly Error.class, then process it. This is hopefully just a "unresolved compilation error".
processException(new UnresolvedCompilationError(e));
} else
throw e; // All subclasses we throw on out.
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push the field access expression.
* @param field String for fieldname, or a java.lang.reflect.Field.
* @param fieldIsString <code>true</code> if field is a string name, and not a java.lang.reflect.Field.
* @param hasReceiver
*
* @since 1.0.0
*/
public final void pushFieldAccess(Object field, boolean fieldIsString, boolean hasReceiver) {
try {
if (ignoreExpression != null || errorOccurred) {
if (traceOn)
printTrace("Field Access", true); //$NON-NLS-1$
return;
}
if (traceOn)
printTrace("Field Access: ", false); //$NON-NLS-1$
try {
// Get the receiver off of the stack.
Object receiver = null;
Class receiverType = null;
if (hasReceiver) {
receiver = popExpression();
receiverType = popExpressionType(false);
}
// Find the field.
Field reflectField = fieldIsString ? receiverType.getField((String) field) : (Field) field;
// Access the field.
Object value = FieldAccessReference.createFieldAccessReference(reflectField, receiver);
Class valueType = reflectField.getType();
if (traceOn) {
System.out.print("Field: "); //$NON-NLS-1$
if (fieldIsString)
System.out.print("(looked up) "); //$NON-NLS-1$
System.out.print(reflectField);
System.out.print(">"); //$NON-NLS-1$
printObjectAndType(value, valueType);
}
pushExpressionValue(value, valueType);
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
} catch (NoSuchFieldException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push the method invocation expression.
* @param method
* @param methodIsString <code>true</code> if method is a string (so string name) or else it is a java.lang.reflect.Method.
* @param hasReceiver
* @param argCount
* @since 1.0.0
*/
public final void pushMethodInvocation(Object method, boolean methodIsString, boolean hasReceiver, int argCount) {
try {
if (ignoreExpression != null || errorOccurred) {
if (traceOn)
printTrace("Invoke", true); //$NON-NLS-1$
return;
}
if (traceOn)
printTrace("Invoke: ", false); //$NON-NLS-1$
Method reflectMethod = null;
try {
// We need to pull in the arguments. They are stacked in reverse order.
Object[] args = new Object[argCount];
Class[] argTypes = new Class[argCount];
for (int i = argCount - 1; i >= 0; i--) {
args[i] = popExpression();
argTypes[i] = popExpressionType(false);
}
// Now get receiver
Object receiver = null;
Class receiverType = null;
if (hasReceiver) {
receiver = popExpression();
receiverType = popExpressionType(false);
}
// Now we need to find the appropriate method. If it is a string then there must be a receiver, otherwise no way to know.
if (methodIsString) {
reflectMethod = MethodHelper.findCompatibleMethod(receiverType, (String) method, argTypes);
} else
reflectMethod = (Method) method;
if (traceOn && reflectMethod != null) {
System.out.print("Method: "); //$NON-NLS-1$
if (methodIsString)
System.out.print("(looked up) "); //$NON-NLS-1$
System.out.print(reflectMethod);
}
if (!Modifier.isStatic(reflectMethod.getModifiers()) && receiver == null)
throw new NullPointerException("No receiver for non-static method: "+reflectMethod.toString()); //$NON-NLS-1$
Object value = reflectMethod.invoke(receiver, args);
if (traceOn) {
System.out.print(" returns: "); //$NON-NLS-1$
printObjectAndType(value, reflectMethod.getReturnType());
}
pushExpressionValue(value, reflectMethod.getReturnType());
} catch (RuntimeException e) {
processException(fixUpMethodException(e, reflectMethod));
} catch (NoExpressionValueException e) {
processException(fixUpMethodException(e, reflectMethod));
} catch (IllegalAccessException e) {
processException(fixUpMethodException(e, reflectMethod));
} catch (InvocationTargetException e) {
processException(fixUpMethodException(e.getCause(), reflectMethod));
} catch (NoSuchMethodException e) {
processException(e);
} catch (AmbiguousMethodException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/*
* Many of the exceptions don't add in what method had the error, so we
* will do that here.
*/
private Throwable fixUpMethodException(Throwable e, Method method) {
if (method == null)
return e;
try {
String msg = e.getMessage();
Constructor eWithStringCtor = e.getClass().getConstructor(new Class[] {String.class});
String newMsg;
if (msg == null) {
newMsg = method.toString();
} else {
newMsg = method.toString()+": \""+msg+'\"'; //$NON-NLS-1$
}
Exception fixedupE = (Exception) eWithStringCtor.newInstance(new Object[] {newMsg});
fixedupE.setStackTrace(e.getStackTrace());
return fixedupE;
} catch (Exception e1) {
return e;
}
}
private static final Object CONDITIONAL_IGNORE = "CONDITIONAL IGNORE"; // Flag for conditional in ingore //$NON-NLS-1$
private int conditionalNesting = 0; // Nesting of conditional expressions.
private int conditionalIgnoreNestCount = 0; // When ignoring conditional expressions, ignore until this nest count.
private boolean skipTruePart;
/**
* Push a conditional expression. It can be any clause of the conditional (test, true, or false clause).
* @param expressionType
*
* @since 1.0.0
*/
public final void pushConditional(InternalConditionalOperandType expressionType) {
try {
boolean ignore = true;
try {
if (errorOccurred)
return;
// Slightly different here in that if an ignoring occurred we still need to process at least part of it so that
// we can get the expression grouping correct.
switch (expressionType.getValue()) {
case InternalConditionalOperandType.CONDITIONAL_TEST_VALUE:
conditionalNesting++;
break;
case InternalConditionalOperandType.CONDITIONAL_TRUE_VALUE:
if (skipTruePart && ignoreExpression == CONDITIONAL_IGNORE && conditionalIgnoreNestCount == conditionalNesting) {
// stop ignoring, we've ignored the true condition of interest.
ignoreExpression = null;
return; // However, leave because since this condition was ignored.
}
break;
case InternalConditionalOperandType.CONDITIONAL_FALSE_VALUE:
int currentNesting = conditionalNesting--;
if (ignoreExpression == CONDITIONAL_IGNORE && conditionalIgnoreNestCount == currentNesting) {
// stop ignoring, we've ignored the false condition of interest.
ignoreExpression = null;
return; // However, leave because since this condition was ignored.
}
}
if (ignoreExpression != null)
return;
ignore = false;
} finally {
if (traceOn)
printTrace("Conditional "+expressionType, ignore); //$NON-NLS-1$
}
try {
switch (expressionType.getValue()) {
case InternalConditionalOperandType.CONDITIONAL_TEST_VALUE:
Object condition = popExpression();
Class type = popExpressionType(false);
if (type != Boolean.TYPE)
throwClassCast(Boolean.TYPE, condition);
if (((Boolean) condition).booleanValue()) {
// Condition was true.
// Do nothing. Let true condition be processed.
} else {
// Condition was false.
skipTruePart = true; // Tell the true condition should be ignored.
ignoreExpression = CONDITIONAL_IGNORE;
conditionalIgnoreNestCount = conditionalNesting;
}
// We don't put anything back on the stack because the condition test is not ever returned.
// The appropriate true or false condition evaluation will be left on the stack.
break;
case InternalConditionalOperandType.CONDITIONAL_TRUE_VALUE:
skipTruePart = false; // Tell the false condition should be ignored.
ignoreExpression = CONDITIONAL_IGNORE;
conditionalIgnoreNestCount = conditionalNesting;
break;
case InternalConditionalOperandType.CONDITIONAL_FALSE_VALUE:
// There's nothing to do, if it was ignored due to true, we wouldn't of gotton here.
// If it wasn't ignored, then the result of the false expression is on the stack, which is what it should be.
break;
}
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
private static final Object BLOCK_IGNORE = "BLOCK IGNORE"; //$NON-NLS-1$
private int[] blocks; // Stack of block numbers currently evaluating.
private int topBlock = -1; // Top block index.
private int breakBlock = -1; // Block number we are breaking to.
/**
* Push a begin block.
* @param blockNumber
*
* @since 1.1.0
*/
public final void pushBlockBegin(int blockNumber) {
if (traceOn) {
printTrace("Begin Block #"+blockNumber, errorOccurred); //$NON-NLS-1$
indent(true);
}
try {
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (blocks == null)
blocks = new int[10];
if (++topBlock >= blocks.length) {
int[] newList = new int[blocks.length*2];
System.arraycopy(blocks, 0, newList, 0, blocks.length);
blocks = newList;
}
blocks[topBlock] = blockNumber;
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push a block end. The current block must be the given number, or it is an error.
*
* @param blockNumber
*
* @since 1.1.0
*/
public final void pushBlockEnd(int blockNumber) {
try {
if (traceOn) {
indent(false);
printTrace("End Block #"+blockNumber, errorOccurred); //$NON-NLS-1$
}
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (blocks == null || topBlock < 0 || blocks[topBlock] != blockNumber) {
processSyntaxException(new IllegalStateException(InitparserTreeMessages.getString("ExpressionProcesser.PushBlockEnd.ReceivedEndBlocksOutOfOrder_EXC_"))); //$NON-NLS-1$
} else {
topBlock--;
if (ignoreExpression == BLOCK_IGNORE && blockNumber == breakBlock) {
ignoreExpression = null;
}
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Skip all following until we hit the requested block number.
* @param blockNumber
*
* @since 1.1.0
*/
public final void pushBlockBreak(int blockNumber) {
try {
if (traceOn)
printTrace("Break Block #"+blockNumber, errorOccurred); //$NON-NLS-1$
if (errorOccurred)
return;
if (ignoreExpression == null) {
ignoreExpression = BLOCK_IGNORE; // Start ignoring expressions until we hit the block number end block.
breakBlock = blockNumber;
}
} finally {
if (traceOn)
printTraceEnd();
}
}
private static final Object TRY_THROW_IGNORE = "TRY THROW IGNORE"; //$NON-NLS-1$
private static final Object TRY_FINAL_IGNORE = "TRY FINAL IGNORE"; //$NON-NLS-1$
private int[] trys; // Stack of try numbers currently evaluating.
// Stack of trys in catch clause (i.e. starting executing a catch/final clause for the try). Corresponds with try from same index in trys. Contains the throwable for the catch.
// This is used to know we are executing a catch (entry not null) and for the rethrow short-hand to rethrow the same exception within the catch.
private Throwable[] trysInCatch;
private int topTry = -1; // Top try index.
private int breakTry = -1; // Try number we are breaking to.
private Throwable catchThrowable; // The throwable to check catches against.
/**
* Push a try statement.
* @param tryNumber
*
* @since 1.1.0
*/
public final void pushTryBegin(int tryNumber) {
try {
if (traceOn) {
printTrace("Begin Try #"+tryNumber, errorOccurred); //$NON-NLS-1$
indent(true);
}
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (trys == null) {
trys = new int[10];
trysInCatch = new Throwable[10];
}
if (++topTry >= trys.length) {
int[] newList = new int[trys.length*2];
System.arraycopy(trys, 0, newList, 0, trys.length);
trys = newList;
Throwable[] newCatches = new Throwable[trys.length];
System.arraycopy(trysInCatch, 0, newCatches, 0, trysInCatch.length);
trysInCatch = newCatches;
}
trys[topTry] = tryNumber;
trysInCatch[topTry] = null;
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Throw the top stack entry. It must be an exception.
*
*
* @since 1.1.0
*/
public final void pushThrowException() {
try {
boolean ignore = (ignoreExpression != null || errorOccurred);
if (traceOn)
printTrace("Throw exception: ", ignore); //$NON-NLS-1$
if (ignore)
return;
try {
Object t = popExpression();
popExpressionType(false);
if (traceOn) {
System.out.print(t);
}
throwException((Throwable) t);
} catch (NoExpressionValueException e) {
processException(e);
} catch (ClassCastException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Throw this exception (means throw through the expression being processed, not throw for this thread).
* @param exception
*
* @since 1.1.0
*/
protected final void throwException(Throwable exception) {
if (topTry == -1) {
// There are no tries, so treat this as a syntax error.
if (exception instanceof NoExpressionValueException)
processSyntaxException((NoExpressionValueException) exception);
else
processSyntaxException(exception);
} else if (trysInCatch[topTry] == null) {
// We are not in a catch clause of the top try. So do a throw ignore for toptry.
ignoreExpression = TRY_THROW_IGNORE;
breakTry = trys[topTry];
catchThrowable = exception;
} else {
// We are in a catch of the top try. So do a throw to finally instead.
ignoreExpression = TRY_FINAL_IGNORE;
trysInCatch[topTry] = FINAL_CATCH;
breakTry = trys[topTry];
catchThrowable = exception;
}
}
/**
* Push a catch clause
* @param tryNumber
* @param exceptionType
* @param expressionProxy
*
* @since 1.1.0
*/
public final void pushTryCatchClause(int tryNumber, Class exceptionType, InternalExpressionProxy expressionProxy) {
try {
if (traceOn) {
indent(false);
if (expressionProxy == null)
printTrace("Catch Try #"+tryNumber+" ("+exceptionType+')', errorOccurred); //$NON-NLS-1$ //$NON-NLS-2$
else
printTrace("Catch Try #"+tryNumber+" ("+exceptionType+") Return exception in proxy #"+expressionProxy.getProxyID(), errorOccurred); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
indent(true);
}
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (trys == null || topTry < 0 || trys[topTry] != tryNumber) {
processSyntaxException(new IllegalStateException(InitparserTreeMessages.getString("ExpressionProcesser.PushTryCatchClause.CatchReceivedOutOfOrder_EXC_"))); //$NON-NLS-1$
} else {
if (ignoreExpression == null) {
// Normal flow, no throw in progress, so just ignore now until the finally or end try reached.
ignoreExpression = TRY_FINAL_IGNORE;
breakTry = tryNumber;
} else if (ignoreExpression == TRY_THROW_IGNORE && tryNumber == breakTry) {
// We are here due to a throw occuring in this try block, see if for us, and if it is, stop ignoring.
// Else just continue ignoring.
if (exceptionType.isInstance(catchThrowable)) {
// For us, so just turn everything back on, except mark that we are in the catch phase.
ignoreExpression = null;
trysInCatch[topTry] = catchThrowable; // This is so that we know if we throw again that we should not catch anything.
breakTry = -1;
if (expressionProxy != null) {
expressionProxy.setProxy(catchThrowable, catchThrowable.getClass());
allocateExpressionProxy(expressionProxy);
}
if (traceOn) {
System.out.print(" Caught: "); //$NON-NLS-1$
System.out.print(catchThrowable);
}
catchThrowable = null;
}
}
}
} finally {
if (traceOn)
printTraceEnd();
}
}
// This is used only so that finally clause can indicate it was executing so that end expression knows this.
private static final Throwable FINAL_CATCH = new RuntimeException();
/**
* Push the try finally clause.
* @param tryNumber
*
* @since 1.1.0
*/
public final void pushTryFinallyClause(int tryNumber) {
try {
if (traceOn) {
indent(false);
printTrace("Finally Try #"+tryNumber, errorOccurred); //$NON-NLS-1$
indent(true);
}
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (trys == null || topTry < 0 || trys[topTry] != tryNumber) {
processSyntaxException(new IllegalStateException(InitparserTreeMessages.getString("ExpressionProcesser.PushTryFinallyClause.FinallyReceivedOutOfOrder_EXC_"))); //$NON-NLS-1$
} else {
if (tryNumber == breakTry && (ignoreExpression == TRY_THROW_IGNORE || ignoreExpression == TRY_FINAL_IGNORE)) {
// We are here due to a throw occuring in this try block or a catch was reached (in which case all intervening catch's were ignored).
// Now do a normal execution. If we are here due to a throw that wasn't cleared (either no catch or another throw occured within the catch)
// then we leave it uncleared so that try/end may rethrow it.
ignoreExpression = null;
trysInCatch[topTry] = FINAL_CATCH; // We are in the finally clause of a exception being thrown within this try.
breakTry = -1;
if (traceOn)
System.out.print(" Executing finally."); //$NON-NLS-1$
}
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Rethrow the caught exception. This is a shortcut for:
* } catch (Exception e) {
* ... do stuff ...
* throw e;
* }
* @param tryNumber
*
* @since 1.1.0
*/
public final void pushTryRethrow(int tryNumber) {
if (traceOn)
printTrace("Rethrow Try #"+tryNumber, errorOccurred || ignoreExpression != null); //$NON-NLS-1$
try {
if (errorOccurred)
return;
// We are not checking ignore because we need to make sure this is not called out of order.
if (trys == null || topTry < 0 || trys[topTry] != tryNumber) {
processSyntaxException(new IllegalStateException(InitparserTreeMessages.getString("ExpressionProcesser.PushTryRethrow.RethrowReceivedOutOfOrder_EXC_"))); //$NON-NLS-1$
} else if (ignoreExpression == null) {
if (trysInCatch[topTry] == null || trysInCatch[topTry] == FINAL_CATCH)
processSyntaxException(new IllegalStateException(InitparserTreeMessages.getString("ExpressionProcesser.PushTryRethrow.RetryReceivedOutOfExecutingCatchClause_EXC_"))); //$NON-NLS-1$
else {
throwException(trysInCatch[topTry]);
}
}
} finally {
if (traceOn)
printTraceEnd();
}
}
public final void pushTryEnd(int tryNumber) {
if (traceOn) {
indent(false);
printTrace("End Try #"+tryNumber, errorOccurred); //$NON-NLS-1$
}
try {
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (trys == null || topTry < 0 || trys[topTry] != tryNumber) {
processSyntaxException(new IllegalStateException(InitparserTreeMessages.getString("ExpressionProcesser.PushTryEnd.TryEndReceivedOutOfOrder_EXC_"))); //$NON-NLS-1$
} else {
boolean inCatch = trysInCatch[topTry] != null;
trysInCatch[topTry] = null;
topTry--;
if (inCatch || (tryNumber == breakTry && (ignoreExpression == TRY_THROW_IGNORE || ignoreExpression == TRY_FINAL_IGNORE))) {
// We are here due to a throw or normal flow through a catch. Either way if there is a throwable still pending, we rethrow.
ignoreExpression = null;
breakTry = -1;
if (catchThrowable != null)
throwException(catchThrowable);
}
}
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Class used to save the state at time of mark. It will
* be used to restore state if error.
*
* @since 1.1.0
*/
protected class SaveState {
public int markID;
// Block state
public int topBlock;
public int breakBlock;
// Subexpression state
public int topSubexpression;
// Try state
public int topTry;
public int breakTry;
public Throwable catchThrowable;
// Error state
public boolean errorOccurred;
public boolean novalueException;
public Throwable exception;
public Object ignoreExpression;
// Expression stack state
public int expressionStackPos;
// If/else state
public int ifElseNesting;
public int ifElseIgnoreNestCount;
public boolean ifElseSkipTruePart;
// Other
public int indent;
public int expressionProxyPos;
/**
* Construct and save the state.
*
* @param markNumber
*
* @since 1.1.0
*/
public SaveState(int markID) {
this.markID = markID;
ExpressionProcesser ep = ExpressionProcesser.this;
this.topBlock = ep.topBlock;
this.breakBlock = ep.breakBlock;
this.topSubexpression = ep.topSubexpression;
this.topTry = ep.topTry;
this.breakTry = ep.breakTry;
this.catchThrowable = ep.catchThrowable;
this.errorOccurred = ep.errorOccurred;
this.novalueException = ep.novalueException;
this.exception = ep.exception;
this.ignoreExpression = ep.ignoreExpression;
this.expressionStackPos = expressionStack.size()-1;
this.ifElseNesting = ep.ifElseNesting;
this.ifElseIgnoreNestCount = ep.ifElseIgnoreNestCount;
this.ifElseSkipTruePart = ep.ifElseSkipTruePart;
this.indent = ep.indent;
this.expressionProxyPos = expressionProxies != null ? expressionProxies.size()-1 : -1;
}
/**
* Restore the state.
*
*
* @since 1.1.0
*/
public void restoreState() {
ExpressionProcesser ep = ExpressionProcesser.this;
ep.topBlock = this.topBlock;
ep.breakBlock = this.breakBlock;
ep.topSubexpression = this.topSubexpression;
ep.topTry = this.topTry;
ep.breakTry = this.breakTry;
ep.catchThrowable = this.catchThrowable;
if (trysInCatch != null) {
for (int i = topTry + 1; i < ep.trysInCatch.length; i++) {
ep.trysInCatch[i] = null;
}
}
ep.errorOccurred = this.errorOccurred;
ep.novalueException = ep.novalueException;
ep.exception = this.exception;
ep.ignoreExpression = this.ignoreExpression;
// Pop stack down to saved state.
for (int i = expressionStack.size()-1; i > this.expressionStackPos; i--) {
expressionStack.remove(i);
expressionTypeStack.remove(i);
}
ep.ifElseNesting = this.ifElseNesting;
ep.ifElseIgnoreNestCount = this.ifElseIgnoreNestCount;
ep.ifElseSkipTruePart = this.ifElseSkipTruePart;
ep.indent = this.indent;
if (expressionProxies != null) {
for (int i = expressionProxies.size() - 1; i > this.expressionProxyPos; i--) {
expressionProxies.remove(i);
}
}
// These settings can't cross mark boundaries, so reset them to not set. This is in case we were in this state somewhere
// in the mark when the restore occurred.
ep.conditionalIgnoreNestCount = 0;
ep.conditionalNesting = 0;
ep.skipTruePart = false;
ep.infixIgnoreNestCount = 0;
ep.infixNesting = 0;
}
}
/**
* Create the save state with the given id.
* @param markID
* @return
*
* @since 1.1.0
*/
protected SaveState createSaveState(int markID) {
return new SaveState(markID);
}
/**
* Push the start of a mark.
* @param markNumber
*
* @since 1.1.0
*/
public final void pushMark(int markNumber) {
if (traceOn)
printTrace("Mark#"+markNumber, false); //$NON-NLS-1$
if (saveStates == null)
saveStates = new ArrayList();
saveStates.add(createSaveState(markNumber));
if (traceOn)
printTraceEnd();
}
/**
* Push the end mark. If there is no error, it will simply
* remove it and all save states in the map after it. If there
* is an error it will do this plus it will restore the state.
* <p>
* It is assumed that the calls are coming in correct order from
* the server so we won't check validity.
*
* @param markID
* @param restore
*
* @since 1.1.0
*/
public final void pushEndmark(int markID, boolean restore) {
if (traceOn)
printTrace("End Mark#"+markID+" Restored="+restore, false); //$NON-NLS-1$ //$NON-NLS-2$
try {
if (saveStates != null) {
// Start from the end (since that is where it most likely will be) and start
// search, removing the end one until we reach the markID.
for (int i = saveStates.size() - 1; i >= 0; i--) {
SaveState state = (SaveState) saveStates.remove(i);
if (state.markID == markID) {
// We found it.
if (restore)
state.restoreState();
return;
}
}
// But to be safe, if we got here, this is bad. We tried restore a mark we didn't have.
processSyntaxException(new IllegalStateException(MessageFormat.format(InitparserTreeMessages.getString("ExpressionProcesser.PushEndmark.EndMarkOnNonExistingID_EXC_"), new Object[]{new Integer(markID)}))); //$NON-NLS-1$
}
} finally {
if (traceOn)
printTraceEnd();
}
}
private int[] subexpressions; // Stack of subexpression numbers currently evaluating.
private int[] subexpressionStackPos; // Stack of the expression stack positions (next entry index) for currently evaluating expressions. The evaluation stack cannot be popped beyond the current top. And at end it will be cleaned up to the position.
private int topSubexpression = -1; // Top subexpression index.
/**
* Push a begin subexpression.
* @param subexpressionNumber
*
* @since 1.1.0
*/
public final void pushSubexpressionBegin(int subexpressionNumber) {
if (traceOn) {
printTrace("Begin Subexpression #"+subexpressionNumber, errorOccurred); //$NON-NLS-1$
indent(true);
}
try {
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (subexpressions == null) {
subexpressions = new int[10];
subexpressionStackPos = new int[10];
}
if (++topSubexpression >= subexpressions.length) {
int[] newList = new int[subexpressions.length*2];
System.arraycopy(subexpressions, 0, newList, 0, subexpressions.length);
subexpressions = newList;
newList = new int[subexpressionStackPos.length*2];
System.arraycopy(subexpressionStackPos, 0, newList, 0, subexpressionStackPos.length);
subexpressionStackPos = newList;
}
subexpressions[topSubexpression] = subexpressionNumber;
subexpressionStackPos[topSubexpression] = expressionStack.size();
} finally {
if (traceOn)
printTraceEnd();
}
}
/**
* Push a subexpression end. The current subexpression must be the given number, or it is an error.
*
* @param subexpressionNumber
*
* @since 1.1.0
*/
public final void pushSubexpressionEnd(int subexpressionNumber) {
try {
if (traceOn) {
indent(false);
printTrace("End Subexpression #"+subexpressionNumber, errorOccurred); //$NON-NLS-1$
}
if (errorOccurred)
return;
// We are not checking ignore because this is a structural concept instead of executable expressions, so we need to keep track of these.
if (subexpressions == null || topSubexpression < 0 || subexpressions[topSubexpression] != subexpressionNumber) {
processSyntaxException(new IllegalStateException(InitparserTreeMessages.getString("ExpressionProcesser.PushSubexpressionEnd.ReceivedEndSubexpressionsOutOfOrder_EXC_"))); //$NON-NLS-1$
} else {
try {
popExpressions(expressionStack.size()-subexpressionStackPos[topSubexpression]);
topSubexpression--;
} catch (NoExpressionValueException e) {
processException(e);
}
}
} finally {
if (traceOn)
printTraceEnd();
}
}
}