blob: 87180f6501955bf1858bef6dbb08c8d595f642e4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 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
*******************************************************************************/
/*
* $RCSfile: ExpressionProcesser.java,v $
* $Revision: 1.11 $ $Date: 2005/05/18 23:11:26 $
*/
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.AmbiguousMethodException;
import org.eclipse.jem.internal.proxy.common.MethodHelper;
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.toString(); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* 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)
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 {
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 {
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 {
int remove = expressionStack.size()-1;
while (count-- > 0) {
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;
}
}
/*
* 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_")); //$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
processSyntaxException(new NoExpressionValueException());
} else
processSyntaxException(new NoExpressionValueException());
} 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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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) {
processSyntaxException(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.
if (argumentCount > 0) {
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) {
System.out.print(ctor);
System.out.print(')');
}
value = ctor.newInstance(args);
} else {
// No args, just do default ctor.
if (traceOn) {
System.out.print("Default ctor)"); //$NON-NLS-1$
}
value = type.newInstance();
}
pushExpressionValue(value, type);
} catch (RuntimeException e) {
processException(e);
} catch (NoExpressionValueException e) {
processSyntaxException(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);
}
} 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) {
processSyntaxException(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$
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.
Method reflectMethod;
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);
}
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(e);
} catch (NoExpressionValueException e) {
processSyntaxException(e);
} catch (IllegalAccessException e) {
processException(e);
} catch (InvocationTargetException e) {
processException(e);
} catch (NoSuchMethodException e) {
processException(e);
} catch (AmbiguousMethodException e) {
processException(e);
}
} finally {
if (traceOn)
printTraceEnd();
}
}
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) {
processSyntaxException(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) {
processSyntaxException(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.
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;
// 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.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.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();
}
}
}