/*
* ExceptionHandler.java
* Transform
*
* Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Flagstone Software Ltd. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.flagstone.transform.action;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.coder.Context;
import com.flagstone.transform.coder.SWFDecoder;
import com.flagstone.transform.coder.SWFEncoder;
import com.flagstone.transform.coder.SWFFactory;
import com.flagstone.transform.exception.IllegalArgumentRangeException;
/**
* The ExceptionHandler class is used to represent try..catch blocks in
* Actionscript.
*
* <p>
* When an exception is thrown, the object can be assigned to either one of the
* Flash Player's 256 internal registers or to a variable in memory.
* </p>
*
* <p>
* The ExceptionHandler class contains three lists of actions supporting the
* standard syntax for an exception with try, catch and finally blocks. Both the
* catch and finally blocks are optional when defining an exception, the
* corresponding arguments in constructors and methods may be set to empty.
* </p>
*/
public final class ExceptionHandler implements Action {
/**
* The Builder class is used to generate a new ExceptionHandler object
* using a small set of convenience methods.
*/
public static final class Builder {
/** The register where the thrown object will be stored. */
private transient int register;
/** The name of the variable where the thrown object will be stored. */
private transient String variable;
/** The list of actions that make up the try block. */
private final transient List<Action> tryActions =
new ArrayList<Action>();
/** The list of actions that make up the catch block. */
private final transient List<Action> catchActions =
new ArrayList<Action>();
/** The list of actions that make up the finally block. */
private final transient List<Action> finalActions =
new ArrayList<Action>();
/**
* Set the register where the thrown object will be stored.
*
* @param index the register number. Must be in the range 0..255.
* @return this object.
*/
public Builder setRegister(final int index) {
if ((index < 0) || (index > HIGHEST_REGISTER)) {
throw new IllegalArgumentRangeException(0,
HIGHEST_REGISTER, index);
}
variable = "";
register = index;
return this;
}
/**
* Set the name of the variable where thrown object will be assigned.
* @param name the name of the actionsctipt variable.
* @return this object.
*/
public Builder setVariable(final String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException();
}
variable = name;
register = 0;
return this;
}
/**
* Add an action to the try block of the exception handler.
* @param action the action to the executed in the try block.
* @return this object.
*/
public Builder addToTry(final Action action) {
if (action == null) {
throw new IllegalArgumentException();
}
tryActions.add(action);
return this;
}
/**
* Add an action to the catch block of the exception handler.
* @param action the action to the executed in the catch block.
* @return this object.
*/
public Builder addToCatch(final Action action) {
if (action == null) {
throw new IllegalArgumentException();
}
catchActions.add(action);
return this;
}
/**
* Add an action to the final block of the exception handler.
* @param action the action to the executed in the final block.
* @return this object.
*/
public Builder addToFinal(final Action action) {
if (action == null) {
throw new IllegalArgumentException();
}
finalActions.add(action);
return this;
}
/**
* Generate an ExceptionHandler using the parameters defined in the
* Builder.
* @return an initialized ExceptionHandler object.
*/
public ExceptionHandler build() {
return new ExceptionHandler(this);
}
}
/** Format string used in toString() method. */
private static final String FORMAT = "ExceptionHandler: { variable=%s;"
+ " register=%d try=%s; catch=%s; final=%s}";
/** Bit mask used to read the containsVariable field. */
private static final int VARIABLE_MASK = 0x04;
/** Bit mask used to read the containsVariable field. */
private static final int FINAL_MASK = 0x02;
/** Bit mask used to read the containsVariable field. */
private static final int CATCH_MASK = 0x01;
/** Length of an empty exception handler with no actions. */
private static final int EMPTY_LENGTH = 8;
/** Number of registers in the FLash Player. */
private static final int HIGHEST_REGISTER = 255;
/** The number of the register that contains the thrown object. */
private final transient int register;
/** The name of the variable that the thrown object will be assigned to. */
private final transient String variable;
/** Set of actions where the exception might be thrown. */
private final transient List<Action> tryActions;
/** Set of actions used to process the exception. */
private final transient List<Action> catchActions;
/** Final set of actions executed, whether or not an exception occurred. */
private final transient List<Action> finalActions;
/** The length of the action, minus the header, when it is encoded. */
private transient int length;
/** Holds the length of the try block when it is encoded. */
private transient int tryLength;
/** Holds the length of the catch block when it is encoded. */
private transient int catchLength;
/** Holds the length of the final block when it is encoded. */
private transient int finalLength;
/**
* Creates and initialises an ExceptionHandler using parameters defined
* in the Builder.
*
* @param builder a Builder object containing the parameters to generate
* the ExceptionHandler.
*/
public ExceptionHandler(final Builder builder) {
register = builder.register;
variable = builder.variable;
tryActions = new ArrayList<Action>(builder.tryActions);
catchActions = new ArrayList<Action>(builder.catchActions);
finalActions = new ArrayList<Action>(builder.finalActions);
}
/**
* Creates and initialises an ExceptionHandler action using values encoded
* in the Flash binary format.
*
* @param coder
* an SWFDecoder object that contains the encoded Flash data.
*
* @param context
* a Context object used to manage the decoders for different
* type of object and to pass information on how objects are
* decoded.
*
* @throws IOException
* if an error occurs while decoding the data.
*/
public ExceptionHandler(final SWFDecoder coder, final Context context)
throws IOException {
length = coder.readUnsignedShort();
final int flags = coder.readByte();
final boolean containsVariable = (flags & VARIABLE_MASK) >> 2 == 1;
final boolean containsFinal = (flags & FINAL_MASK) >> 1 == 1;
final boolean containsCatch = (flags & CATCH_MASK) == 1;
tryLength = coder.readUnsignedShort();
catchLength = coder.readUnsignedShort();
finalLength = coder.readUnsignedShort();
if (length == EMPTY_LENGTH) {
length += tryLength;
length += catchLength;
length += finalLength;
}
if (containsVariable) {
variable = coder.readString();
register = 0;
} else {
variable = "";
register = coder.readByte();
}
tryActions = new ArrayList<Action>();
catchActions = new ArrayList<Action>();
finalActions = new ArrayList<Action>();
final SWFFactory<Action> decoder = context.getRegistry()
.getActionDecoder();
coder.mark();
while (coder.bytesRead() < tryLength) {
decoder.getObject(tryActions, coder, context);
}
coder.unmark();
if (containsCatch) {
coder.mark();
while (coder.bytesRead() < catchLength) {
decoder.getObject(catchActions, coder, context);
}
coder.unmark();
}
if (containsFinal) {
coder.mark();
while (coder.bytesRead() < finalLength) {
decoder.getObject(finalActions, coder, context);
}
coder.unmark();
}
}
/**
* Creates a new exception handler with the thrown object assigned to a
* local variable.
*
* @param name
* the name of the variable that the thrown object will be
* assigned to. Must not be null.
* @param tryArray
* actions that will be executed in the try block of the
* exception. Must not be null.
* @param catchArray
* actions that will be executed in the catch block of the
* exception, if one is defined. This may be empty if no
* catch block is required - the exception will be handled by
* another catch block higher in the exception tree.
* @param finallyArray
* actions that will be executed in the finally block of the
* exception, if one is defined. This may be empty if no
* finally block is required.
*/
public ExceptionHandler(final String name, final List<Action> tryArray,
final List<Action> catchArray, final List<Action> finallyArray) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException();
}
variable = name;
register = 0;
if (tryArray == null) {
throw new IllegalArgumentException();
}
tryActions = tryArray;
if (catchArray == null) {
throw new IllegalArgumentException();
}
catchActions = catchArray;
if (finallyArray == null) {
throw new IllegalArgumentException();
}
finalActions = finallyArray;
}
/**
* Constructs a new exception handler with the thrown object assigned to one
* of the Flash Player's internal registers.
*
* @param index
* the number of the register that the thrown object will be
* assigned to. Must be in the range 0..255.
* @param tryArray
* actions that will be executed in the try block of the
* exception. Must not be null.
* @param catchArray
* actions that will be executed in the catch block of the
* exception, if one is defined. This may be empty if no
* catch block is required - the exception will be handled by
* another catch block higher in the exception tree.
* @param finallyArray
* actions that will be executed in the finally block of the
* exception, if one is defined. This may be empty is no
* finally block is required.
*/
public ExceptionHandler(final int index, final List<Action> tryArray,
final List<Action> catchArray, final List<Action> finallyArray) {
if ((index < 0) || (index > HIGHEST_REGISTER)) {
throw new IllegalArgumentRangeException(0, HIGHEST_REGISTER, index);
}
variable = "";
register = index;
if (tryArray == null) {
throw new IllegalArgumentException();
}
tryActions = tryArray;
if (catchArray == null) {
throw new IllegalArgumentException();
}
catchActions = catchArray;
if (finallyArray == null) {
throw new IllegalArgumentException();
}
finalActions = finallyArray;
}
/**
* Creates and initialises an ExceptionHandler action using the values
* copied from another ExceptionHandler.
*
* @param object
* an ExceptionHandler object from which the values will be
* copied. References to immutable objects will be shared.
*/
public ExceptionHandler(final ExceptionHandler object) {
variable = object.variable;
register = object.register;
tryActions = new ArrayList<Action>(object.tryActions);
catchActions = new ArrayList<Action>(object.catchActions);
finalActions = new ArrayList<Action>(object.finalActions);
}
/**
* Returns the name of the variable which the exception object is assigned
* to.
*
* @return the name of the function. Returns null if the exception object
* will be assigned to a register.
*/
public String getVariable() {
return variable;
}
/**
* Returns the index of the register that the exception object is assigned
* to.
*
* @return the number of register. Returns 0 if the exception object will be
* assigned to a local variable.
*/
public int getRegister() {
return register;
}
/**
* Returns the list of actions executed in the try block.
*
* @return the list of actions for the try block.
*/
public List<Action> getTryActions() {
return new ArrayList<Action>(tryActions);
}
/**
* Returns the list of actions executed in the catch block.
*
* @return the list of actions for the catch block.
*/
public List<Action> getCatchActions() {
return new ArrayList<Action>(catchActions);
}
/**
* Returns the list of actions executed in the finally block.
*
* @return the list of actions for the finally block.
*/
public List<Action> getFinalActions() {
return new ArrayList<Action>(finalActions);
}
/** {@inheritDoc} */
public ExceptionHandler copy() {
return this;
}
/** {@inheritDoc} */
@Override
public String toString() {
return String.format(FORMAT, variable, register, tryActions,
catchActions, finalActions);
}
/** {@inheritDoc} */
public int prepareToEncode(final Context context) {
length = EMPTY_LENGTH; // assume thrown object is stored in register.
if (register == 0) {
length += context.strlen(variable) - 1;
}
tryLength = 0;
catchLength = 0;
finalLength = 0;
for (final Action action : tryActions) {
tryLength += action.prepareToEncode(context);
}
for (final Action action : catchActions) {
catchLength += action.prepareToEncode(context);
}
for (final Action action : finalActions) {
finalLength += action.prepareToEncode(context);
}
length += tryLength;
length += catchLength;
length += finalLength;
return Coder.ACTION_HEADER + length;
}
/** {@inheritDoc} */
public void encode(final SWFEncoder coder, final Context context)
throws IOException {
coder.writeByte(ActionTypes.EXCEPTION_HANDLER);
coder.writeShort(length);
int flags = 0;
if (register == 0) {
flags |= VARIABLE_MASK;
}
if (finalLength > 0) {
flags |= FINAL_MASK;
}
if (catchLength > 0) {
flags |= CATCH_MASK;
}
coder.writeByte(flags);
coder.writeShort(tryLength);
coder.writeShort(catchLength);
coder.writeShort(finalLength);
if (register == 0) {
coder.writeString(variable);
} else {
coder.writeByte(register);
}
for (final Action action : tryActions) {
action.encode(coder, context);
}
for (final Action action : catchActions) {
action.encode(coder, context);
}
for (final Action action : finalActions) {
action.encode(coder, context);
}
}
}