/*
* JSwiff is an open source Java API for Macromedia Flash file generation
* and manipulation
*
* Copyright (C) 2004-2006 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.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.swfparser.CodeUtil;
import org.swfparser.util.PrintfFormat;
import org.swfparser.util.UnsignedByte;
import org.apache.log4j.Logger;
/**
* <p>
* This class implements a container for action records. It is used in actions
* which contain other actions (e.g. <code>DefineFunction</code> or
* <code>With</code>).
* </p>
*
* <p>
* Build nested action blocks bottom-up, i.e. the inner blocks first. For
* example if you have a <code>With</code>action inside a
* <code>DefineFunction2</code> action block, first add actions to the action
* block of <code>With</code>, then add <code>With</code> to the action block
* of <code>DefineFunction2</code>. Finally, add <code>DefineFunction2</code>
* to the top level action block.
* </p>
*/
public class ActionBlock implements Serializable, ActionBlockReader {
private static Logger logger = Logger.getLogger(ActionBlock.class);
private static final String ACTION_BLOCK_READER_BEAN = "actionBlockReader";
/** Label name pointing to the end of the current action block. */
public static String LABEL_END = "__end";
/**
* Label name pointing outside the block (usually an error). Use this only
* for error checking!
*/
public static String LABEL_OUT = "__out";
protected static int instCounter = 0; // instance counter used for labels
protected List actions = new ArrayList();
protected Map labelMap = new HashMap();
protected Map inverseLabelMap = new HashMap();
private boolean skipGarbage = false;
public void setSkipGarbage(boolean skipGarbage) {
this.skipGarbage = skipGarbage;
}
public byte[] getData() {
return null;
}
public static ActionBlockReader getInstance() {
return (ActionBlockReader) CodeUtil.getApplicationContext().getBean(ACTION_BLOCK_READER_BEAN);
}
/**
* Creates a new Block action.
*/
public ActionBlock() {
// nothing to do
}
/**
* Reads an action block from a bit stream.
*
* @param stream the source bit stream
*
* @throws IOException if an I/O error has occured
*/
/* (non-Javadoc)
* @see com.jswiff.swfrecords.actions.ActionBlockReader#read(com.jswiff.io.InputBitStream)
*/
public void read(InputBitStream stream) throws IOException {
int startOffset = (int) stream.getOffset();
boolean hasEndAction = false;
while (stream.available() > 0) {
Action record = ActionReader.readRecord(stream);
if (record.code != ActionConstants.END) {
actions.add(record);
} else {
hasEndAction = true;
break;
}
}
if (actions.size() == 0) {
return;
}
// end offset (relative to start offset, end action ignored)
int relativeEndOffset = (int) stream.getOffset() - startOffset -
(hasEndAction ? 1 : 0);
// correct offsets, setting to relative to first action (not to start of stream)
// also, populate the label map with integers containing the corresponding offsets
int labelCounter = 0;
Map actionMap = new HashMap(); // contains offset->action mapping
for (int i = 0; i < actions.size(); i++) {
Action action = (Action) actions.get(i);
int newOffset = action.getOffset() - startOffset;
action.setOffset(newOffset);
actionMap.put(new Integer(newOffset), action);
// collect labels from Jump and If actions
if (
(action.getCode() == ActionConstants.IF) ||
(action.getCode() == ActionConstants.JUMP)) {
Branch branchAction = (Branch) action;
// temporarily put the offset into the label map
// later on, the offset will be replaced with the corresponding action instance
int branchOffset = getBranchOffset(branchAction);
String branchLabel;
if (branchOffset < relativeEndOffset) {
Integer branchOffsetObj = new Integer(branchOffset);
// check if branch target isn't already assigned a label
String oldLabel = (String) inverseLabelMap.get(
branchOffsetObj);
if (oldLabel == null) {
branchLabel = "L_" + instCounter + "_" + labelCounter++;
labelMap.put(branchLabel, branchOffsetObj);
inverseLabelMap.put(branchOffsetObj, branchLabel);
} else {
branchLabel = oldLabel;
}
} else if (branchOffset == relativeEndOffset) {
branchLabel = LABEL_END;
} else {
branchLabel = LABEL_OUT;
}
branchAction.setBranchLabel(branchLabel);
}
}
// now replace offsets from label map with corresponding actions
Set keys = labelMap.keySet();
for (Iterator i = keys.iterator(); i.hasNext();) {
String label = (String) i.next();
Object branchOffset = labelMap.get(label);
Action action = (Action) actionMap.get(branchOffset);
if (action != null) {
// action == null when label == LABEL_OUT
action.setLabel(label);
labelMap.put(label, action);
}
}
instCounter++;
}
/**
* Resets the instance counter. This counter is used to create action labels
* when parsing an SWF file.
*/
public static void resetInstanceCounter() {
instCounter = 0;
}
/* (non-Javadoc)
* @see com.jswiff.swfrecords.actions.ActionBlockReader#getActions()
*/
public List getActions() {
return actions;
}
/* (non-Javadoc)
* @see com.jswiff.swfrecords.actions.ActionBlockReader#getSize()
*/
public int getSize() {
int size = 0;
for (Iterator i = actions.iterator(); i.hasNext();) {
size += ((Action) i.next()).getSize();
}
return size;
}
/* (non-Javadoc)
* @see com.jswiff.swfrecords.actions.ActionBlockReader#addAction(com.jswiff.swfrecords.actions.Action)
*/
public void addAction(Action action) {
// add action to list
actions.add(action);
}
/* (non-Javadoc)
* @see com.jswiff.swfrecords.actions.ActionBlockReader#removeAction(com.jswiff.swfrecords.actions.Action)
*/
public boolean removeAction(Action action) {
return actions.remove(action);
}
/* (non-Javadoc)
* @see com.jswiff.swfrecords.actions.ActionBlockReader#removeAction(int)
*/
public Action removeAction(int index) {
return (Action) actions.remove(index);
}
/* (non-Javadoc)
* @see com.jswiff.swfrecords.actions.ActionBlockReader#write(com.jswiff.io.OutputBitStream, boolean)
*/
public void write(OutputBitStream stream, boolean writeEndAction) throws IOException {
// logger.debug("Writing action block ("+actions.size()+")");
// two passes
// first pass: correct offsets and populate labelMap
int currentOffset = 0;
for (Iterator i = actions.iterator(); i.hasNext();) {
Action action = (Action) i.next();
action.setOffset(currentOffset);
currentOffset += action.getSize();
// logger.debug("PASS1:action="+action+" ("+action.getLabel()+")");
// if action has label, add (label->action) mapping to labelMap
String label = action.getLabel();
if (label != null) {
labelMap.put(label, action);
}
}
// second pass: replace branch labels with branch offsets and write
// actions
for (Iterator i = actions.iterator(); i.hasNext();) {
Action action = (Action) i.next();
logger.debug("Writing action with code " + new PrintfFormat("%02X").sprintf(action.getCode()) + " (" + ActionConstants.getActionName(action.getCode()) + ")");
switch (action.getCode()) {
case ActionConstants.JUMP:
case ActionConstants.IF:
logger.debug("branch label = " + ((Branch) action).getBranchLabel());
replaceBranchLabelWithRelOffset((Branch) action); // replace
// branch
// label
// with
// offset
// relative
// to
// subsequent
// action
break;
}
action.write(stream);
}
// now write END action if needed
if (writeEndAction) {
stream.writeUI8((short) 0); // ActionEndFlag
}
}
/*
* Returns the action corresponding to a specific label. Labels are used for
* jumps within this action block.
*/
private Action getAction(String label) {
Object action = labelMap.get(label);
logger.debug("action = "+action);
if (action instanceof Action) {
return (Action) action;
}
throw new IllegalArgumentException(
"Label '" + label + "' points at non-existent action!");
}
/*
* Returns the absolute branch offset of an action.
*/
protected int getBranchOffset(Branch action) {
int branchOffset = 0;
branchOffset = action.getBranchOffset();
// convert from relative to absolute offset
branchOffset += (action.getOffset() + action.getSize());
return branchOffset;
}
/*
* Returns the absolute offset corresponding to a given label.
*/
protected int getOffset(String label) {
if (label.equals(LABEL_END)) {
return getSize();
}
Action action = getAction(label);
if (action == null) {
throw new IllegalArgumentException("Label " + label + " not defined!");
}
return action.getOffset();
}
private void replaceBranchLabelWithRelOffset(Branch action) {
// replace branch label with offset relative to subsequent action
// get absolute offset corresponding to branch label
short branchOffset = (short) getOffset(action.getBranchLabel());
// compute offset relative to subsequent action
branchOffset -= (action.getOffset() + action.getSize());
// set branch offset
action.setBranchOffset(branchOffset);
}
}