/*
* JSwiff is an open source Java API for Macromedia Flash file generation
* and manipulation
*
* Copyright (C) 2004-2005 Ralf Terdic (contact@jswiff.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.jswiff.swfrecords.actions;
import com.jswiff.io.InputBitStream;
import com.jswiff.io.OutputBitStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import org.apache.log4j.Logger;
/**
* <p>
* This action defines handlers for exceptional conditions (exceptions). After
* defining the mandatory try block, you can define a catch block and/or a
* finally block.
* </p>
*
* <p>
* Performed stack operations: none
* </p>
*
* <p>
* ActionScript equivalent: <code>try..catch..finally</code> keywords
* </p>
*
* @since SWF 7
*/
public final class Try extends Action {
private static Logger logger = Logger.getLogger(Try.class);
private boolean catchInRegister;
private String catchVariable;
private short catchRegister;
private ActionBlockReader tryBlock;
private ActionBlockReader catchBlock;
private ActionBlockReader finallyBlock;
/**
* Creates a new Try action. You can specify a name of a variable the caught
* object is put into.
*
* @param catchVar
* catch variable name
*/
public Try(String catchVar) {
this();
this.catchVariable = catchVar;
}
/**
* Creates a new Try action. You can specify the register number the caught
* object is put into.
*
* @param catchRegister
* catch register number
*/
public Try(short catchRegister) {
this();
this.catchRegister = catchRegister;
catchInRegister = true;
}
Try(InputBitStream stream, InputBitStream mainStream) throws IOException {
code = ActionConstants.TRY;
short flags = stream.readUI8(); // 5 reserved bits - ignore
catchInRegister = ((flags & 4) != 0);
boolean hasFinallyBlock = ((flags & 2) != 0);
boolean hasCatchBlock = ((flags & 1) != 0);
int trySize = stream.readUI16();
int catchSize = stream.readUI16();
int finallySize = stream.readUI16();
if (catchInRegister) {
catchRegister = stream.readUI8();
} else {
catchVariable = stream.readString();
}
// now read further actions from the main stream
// read try block
byte[] blockBuffer = mainStream.readBytes(trySize);
InputBitStream blockStream = new InputBitStream(blockBuffer);
blockStream.setANSI(stream.isANSI());
blockStream.setShiftJIS(stream.isShiftJIS());
// tryBlock = new ActionBlock(blockStream);
tryBlock = ActionBlock.getInstance();
tryBlock.setSkipGarbage(false);
tryBlock.read(blockStream);
removeTryJump();
// read catch block
if (hasCatchBlock) {
blockBuffer = mainStream.readBytes(catchSize);
blockStream = new InputBitStream(blockBuffer);
blockStream.setANSI(stream.isANSI());
blockStream.setShiftJIS(stream.isShiftJIS());
// catchBlock = new ActionBlock(blockStream);
catchBlock = ActionBlock.getInstance();
catchBlock.setSkipGarbage(false);
catchBlock.read(blockStream);
} else {
catchBlock = ActionBlock.getInstance();
}
// read finally block
if (hasFinallyBlock) {
blockBuffer = mainStream.readBytes(finallySize);
blockStream = new InputBitStream(blockBuffer);
blockStream.setANSI(stream.isANSI());
blockStream.setShiftJIS(stream.isShiftJIS());
// finallyBlock = new ActionBlock(blockStream);
finallyBlock = ActionBlock.getInstance();
finallyBlock.setSkipGarbage(false);
finallyBlock.read(blockStream);
} else {
finallyBlock = ActionBlock.getInstance();
}
}
private Try() {
code = ActionConstants.TRY;
tryBlock = ActionBlock.getInstance();
catchBlock = ActionBlock.getInstance();
finallyBlock = ActionBlock.getInstance();
}
/**
* Returns the catch action block. Use <code>addToCatchBlock()</code>
* method to add new action records to the catch action block.
*
* @return catch action block
*/
public ActionBlockReader getCatchBlock() {
return catchBlock;
}
/**
* Returns the number of the register the exception is catched into.
*
* @return catch variable name
*/
public short getCatchRegister() {
return catchRegister;
}
/**
* Returns the name of the variable the exception is catched into.
*
* @return catch variable name
*/
public String getCatchVariable() {
return catchVariable;
}
/**
* Returns the finally action block. Use <code>addToFinallyBlock()</code>
* method to add new action records to the finally action block.
*
* @return finally action block
*/
public ActionBlockReader getFinallyBlock() {
return finallyBlock;
}
/**
* Returns the size of this action record in bytes.
*
* @return size of this record
*
* @see Action#getSize()
*/
public int getSize() {
int size = 10 + tryBlock.getSize() + catchBlock.getSize() + finallyBlock.getSize();
if (catchInRegister) {
size++; // one byte
} else {
try {
size += (catchVariable.getBytes("UTF-8").length + 1); // unicode,
// null-terminated
} catch (UnsupportedEncodingException e) {
// UTF-8 should be available
}
}
return size;
}
/**
* <p>
* Returns the try action block. Use <code>addToTryBlock()</code> method
* to add new action records to the try action block.
* </p>
*
* <p>
* Note: if a catch block is present, the try block contains an implicit
* <code>Jump</code> at the end pointing at the first action after the
* catch block, causing it's actions to be skipped in case no exception is
* thrown.
* </p>
*
* @return try action block
*/
public ActionBlockReader getTryBlock() {
return tryBlock;
}
/**
* Adds a new action record to the catch block.
*
* @param action
* an action record
*/
public void addToCatch(Action action) {
catchBlock.addAction(action);
}
/**
* Adds a new action record to the finally block.
*
* @param action
* an action record
*/
public void addToFinally(Action action) {
finallyBlock.addAction(action);
}
/**
* Adds a new action record to the try block.
*
* @param action
* an action record
*/
public void addToTry(Action action) {
tryBlock.addAction(action);
}
/**
* Checks if the catched exception is put into a register or into a
* variable.
*
* @return <code>true</code> if exception is catched into a register, else
* <code>false</code>
*/
public boolean catchInRegister() {
return catchInRegister;
}
/**
* Checks if there is a catch block.
*
* @return <code>true</code> if there is a catch block, otherwise
* <code>false</code>
*/
public boolean hasCatchBlock() {
return (catchBlock.getActions().size() > 0);
}
/**
* Returns the finally action block. Can be used to add new actions to the
* finally block.
*
* @return the finally action block
*/
public boolean hasFinallyBlock() {
return (finallyBlock.getActions().size() > 0);
}
protected void writeData(OutputBitStream dataStream, OutputBitStream mainStream) throws IOException {
dataStream.writeUnsignedBits(0, 5); // 5 reserved bits
dataStream.writeBooleanBit(catchInRegister);
dataStream.writeBooleanBit(finallyBlock.getActions().size() > 0);
dataStream.writeBooleanBit(catchBlock.getActions().size() > 0);
dataStream.writeUI16(tryBlock.getSize());
dataStream.writeUI16(catchBlock.getSize());
dataStream.writeUI16(finallyBlock.getSize());
if (catchInRegister) {
dataStream.writeUI8(catchRegister);
} else {
dataStream.writeString(catchVariable);
}
tryBlock.write(mainStream, false);
if (catchBlock.getActions().size() > 0) {
// if there is a catch block, execute a jump to the finally block
new Jump((short) catchBlock.getSize()).write(mainStream);
}
catchBlock.write(mainStream, false);
finallyBlock.write(mainStream, false);
}
private void removeTryJump() {
// Removes the jump to the first action of the finally block.
// This jump is the last action of the try block when there is a catch
// block.
List actions = tryBlock.getActions();
if (actions.size() > 0) {
Action lastAction = (Action) actions.get(actions.size() - 1);
if (lastAction.getCode() == ActionConstants.JUMP) {
if (((Jump) lastAction).getBranchLabel().equals(ActionBlock.LABEL_OUT)) {
actions.remove(actions.size() - 1);
if (lastAction.getLabel()!=null) {
// there are actions pointing on this action, fix their branch labels
fixActionsWithJumpToTheLastAction(actions,lastAction.getLabel());
}
}
}
}
}
private void fixActionsWithJumpToTheLastAction(List<Action> actions,String label) {
logger.debug("Fixing actions pointing on "+label);
for (Action action : actions) {
if (action instanceof Branch) {
Branch branch = (Branch) action;
if (label.equals(branch.getBranchLabel())) {
logger.debug("Fixing "+branch+ " Setting branch pointer to "+ActionBlock.LABEL_END);
branch.setBranchLabel(ActionBlock.LABEL_END);
}
}
}
}
}