package com.jswiff.swfrecords.actions;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.List;
import org.springframework.util.Assert;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.swfparser.BooleanOperation;
import org.swfparser.CodeUtil;
import org.swfparser.DualUse;
import org.swfparser.Operation;
import org.swfparser.Priority;
import org.swfparser.operation.NotOperation;
import com.jswiff.io.InputBitStream;
import com.jswiff.io.OutputBitStream;
/**
* This class contains a value which can be pushed to the stack. The default
* value is <code>undefined</code>, the type is <code>TYPE_UNDEFINED</code>.
* Use setters to change.
*/
public class StackValue implements Serializable, Operation, BooleanOperation/*, DualUse*/ {
/**
*
*/
private static final long serialVersionUID = 3688111214860987011L;
/** Indicates that the value to be pushed is a string. */
public static final short TYPE_STRING = 0;
/** Indicates that the value to be pushed is a floating point number. */
public static final short TYPE_FLOAT = 1;
/** Indicates that the value to be pushed is <code>null</code>. */
public static final short TYPE_NULL = 2;
/** Indicates that the value to be pushed is <code>undefined</code>. */
public static final short TYPE_UNDEFINED = 3;
/** Indicates that the value to be pushed is a register number. */
public static final short TYPE_REGISTER = 4;
/** Indicates that the value to be pushed is a boolean. */
public static final short TYPE_BOOLEAN = 5;
/**
* Indicates that the value to be pushed is double-precision floating point
* number.
*/
public static final short TYPE_DOUBLE = 6;
/** Indicates that the value to be pushed is an integer. */
public static final short TYPE_INTEGER = 7;
/**
* Indicates that the value to be pushed is an 8-bit constant pool index.
*/
public static final short TYPE_CONSTANT_8 = 8;
/**
* Indicates that the value to be pushed is a 16-bit constant pool index.
*/
public static final short TYPE_CONSTANT_16 = 9;
protected short type = TYPE_UNDEFINED;
private String string;
private float floatValue;
private short registerNumber;
private boolean booleanValue;
private double doubleValue;
private long integerValue;
private short constant8;
private int constant16;
private boolean isStatement = false;
/**
* Creates a new StackValue instance. Initial type is
* <code>TYPE_UNDEFINED</code>.
*/
public StackValue() {
// nothing to do
}
public StackValue(String string) {
this.string = string;
this.type = TYPE_STRING;
}
public StackValue(int intValue) {
this.integerValue = intValue;
this.type = TYPE_INTEGER;
}
public StackValue(boolean bValue) {
this.booleanValue = bValue;
this.type = TYPE_BOOLEAN;
}
/*
* Reads a PushEntry instance from a bit stream.
*/
StackValue(InputBitStream stream) throws IOException {
type = stream.readUI8();
switch (type) {
case TYPE_STRING:
string = stream.readString();
break;
case TYPE_FLOAT:
floatValue = stream.readFloat();
break;
case TYPE_REGISTER:
registerNumber = stream.readUI8();
break;
case TYPE_BOOLEAN:
booleanValue = (stream.readUI8() != 0);
break;
case TYPE_DOUBLE:
doubleValue = stream.readDouble();
break;
case TYPE_INTEGER:
integerValue = stream.readUI32();
break;
case TYPE_CONSTANT_8:
constant8 = stream.readUI8();
break;
case TYPE_CONSTANT_16:
constant16 = stream.readUI16();
break;
}
}
/**
* Sets the push value to a boolean, and the type to TYPE_BOOLEAN.
*
* @param value
* a boolean value
*/
public void setBoolean(boolean value) {
this.booleanValue = value;
type = TYPE_BOOLEAN;
}
/**
* Returns the boolean the push value is set to. If the value type is not
* TYPE_BOOLEAN, an IllegalStateException is thrown.
*
* @return push value as boolean
*/
public boolean getBoolean() {
return booleanValue;
}
/**
* Sets the push value to a 16-bit constant pool index, and the type to
* TYPE_BOOLEAN. Use 16-bit indexes when the constant pool contains more
* than 256 constants.
*
* @param value
* an 8-bit constant pool index
*/
public void setConstant16(int value) {
this.constant16 = value;
type = TYPE_CONSTANT_16;
}
/**
* Returns the 16-bit constant pool index the push value is set to. If the
* value type is not TYPE_CONSTANT_16, an IllegalStateException is thrown.
*
* @return push value as 16-bit constant pool index
*
* @throws IllegalStateException
* if type is not TYPE_CONSTANT_16
*/
public int getConstant16() {
if (type != TYPE_CONSTANT_16) {
throw new IllegalStateException("Value type is not TYPE_CONSTANT_16!");
}
return constant16;
}
/**
* Sets the push value to an 8-bit constant pool index, and the type to
* TYPE_BOOLEAN. Use 8-bit indexes when the constant pool contains less than
* 256 constants.
*
* @param value
* an 8-bit constant pool index
*/
public void setConstant8(short value) {
this.constant8 = value;
type = TYPE_CONSTANT_8;
}
/**
* Returns the 8-bit constant pool index the push value is set to. If the
* value type is not TYPE_CONSTANT_8, an IllegalStateException is thrown.
*
* @return push value as 8-bit constant pool index
*
* @throws IllegalStateException
* if type is not TYPE_CONSTANT_8
*/
public short getConstant8() {
if (type != TYPE_CONSTANT_8) {
throw new IllegalStateException("Value type is not TYPE_CONSTANT_8!");
}
return constant8;
}
/**
* Sets the push value to a double-precision number, and the type to
* TYPE_DOUBLE.
*
* @param value
* a double value
*/
public void setDouble(double value) {
this.doubleValue = value;
type = TYPE_DOUBLE;
}
/**
* Returns the double the push value is set to. If the value type is not
* TYPE_DOUBLE, an IllegalStateException is thrown.
*
* @return push value as double
*
* @throws IllegalStateException
* if type is not TYPE_DOUBLE
*/
public double getDouble() {
if (type != TYPE_DOUBLE) {
throw new IllegalStateException("Value type is not TYPE_DOUBLE!");
}
return doubleValue;
}
/**
* Sets the push value to a (single-precision) float, and the type to
* TYPE_FLOAT.
*
* @param value
* a float value
*/
public void setFloat(float value) {
this.floatValue = value;
type = TYPE_FLOAT;
}
/**
* Returns the float the push value is set to. If the value type is not
* TYPE_FLOAT, an IllegalStateException is thrown.
*
* @return push value as float
*
* @throws IllegalStateException
* if type is not TYPE_FLOAT
*/
public float getFloat() {
if (type != TYPE_FLOAT) {
throw new IllegalStateException("Value type is not TYPE_FLOAT!");
}
return floatValue;
}
/**
* Sets the push value to an integer, and the type to TYPE_INTEGER.
*
* @param value
* an integer value (of type <code>long</code>)
*/
public void setInteger(long value) {
this.integerValue = value;
type = TYPE_INTEGER;
}
/**
* Returns the integer the push value is set to. If the value type is not
* TYPE_INTEGER, an IllegalStateException is thrown.
*
* @return push value as integer
*
* @throws IllegalStateException
* if type is not TYPE_INTEGER
*/
public long getInteger() {
if (type != TYPE_INTEGER) {
throw new IllegalStateException("Value type is not TYPE_INTEGER!");
}
return integerValue;
}
/**
* Sets the type to <code>TYPE_NULL</code> (i.e. the push value is
* <code>null</code>).
*/
public void setNull() {
type = TYPE_NULL;
}
/**
* Checks if the push value is <code>null</code>.
*
* @return true if <code>null</code>, else false.
*/
public boolean isNull() {
return (type == TYPE_NULL);
}
/**
* Sets the push value to a register number, and the type to TYPE_REGISTER.
*
* @param value
* a register number
*/
public void setRegisterNumber(short value) {
this.registerNumber = value;
type = TYPE_REGISTER;
}
/**
* Returns the register number the push value is set to. If the value type
* is not TYPE_REGISTER, an IllegalStateException is thrown.
*
* @return push value as register number
*
* @throws IllegalStateException
* if type is not TYPE_REGISTER
*/
public short getRegisterNumber() {
if (type != TYPE_REGISTER) {
throw new IllegalStateException("Value type is not TYPE_REGISTER!");
}
return registerNumber;
}
/**
* Sets the push value to a string, and the type to TYPE_STRING
*
* @param value
* a string value
*/
public void setString(String value) {
this.string = value;
type = TYPE_STRING;
}
/**
* Returns the string the push value is set to. If the value type is not
* TYPE_STRING, an IllegalStateException is thrown
*
* @return push value as string
*
* @throws IllegalStateException
* if type is not TYPE_STRING
*/
public String getString() {
if (type != TYPE_STRING) {
throw new IllegalStateException("Value type is not TYPE_STRING!");
}
return string;
}
/**
* Returns the type of the push value. The type is one of the constants
* <code>TYPE_BOOLEAN, TYPE_CONSTANT_8, TYPE_CONSTANT_16, TYPE_DOUBLE,
* TYPE_FLOAT, TYPE_INTEGER, TYPE_NULL, TYPE_REGISTER, TYPE_STRING,
* TYPE_UNDEFINED</code>.
*
* @return type of push value
*/
public short getType() {
return type;
}
/**
* Sets the type to <code>TYPE_UNDEFINED</code> (i.e. the push value is
* <code>undefined</code>).
*/
public void setUndefined() {
type = TYPE_UNDEFINED;
}
/**
* Checks if the push value is <code>undefined</code>.
*
* @return true if <code>undefined</code>, else false.
*/
public boolean isUndefined() {
return (type == TYPE_UNDEFINED);
}
/**
* Returs a short description of the push value (type and value)
*
* @return type and value
*/
public String toString() {
String result = "StackValue(";
switch (type) {
case TYPE_STRING:
result += ("string: '" + string + "'");
break;
case TYPE_FLOAT:
result += ("float: " + floatValue);
break;
case TYPE_REGISTER:
result += ("register: " + registerNumber);
break;
case TYPE_BOOLEAN:
result += ("boolean: " + booleanValue);
break;
case TYPE_DOUBLE:
result += ("double: " + doubleValue);
break;
case TYPE_INTEGER:
result += ("integer: " + integerValue);
break;
case TYPE_CONSTANT_8:
result += ("constant8[" + constant8 + "]");
break;
case TYPE_CONSTANT_16:
result += ("constant16[" + constant16 + "]");
break;
case TYPE_UNDEFINED:
result += "undefined";
break;
case TYPE_NULL:
result += "null";
break;
}
result += ")";
return result;
}
int getSize() {
int size = 1; // type
switch (type) {
case TYPE_STRING:
try {
size += (string.getBytes("UTF-8").length + 1);
} catch (UnsupportedEncodingException e) {
// UTF-8 should be available. If not, we have a big problem
// anyway
}
break;
case TYPE_FLOAT:
size += 4;
break;
case TYPE_REGISTER:
size++;
break;
case TYPE_BOOLEAN:
size++;
break;
case TYPE_DOUBLE:
size += 8;
break;
case TYPE_INTEGER:
size += 4;
break;
case TYPE_CONSTANT_8:
size++;
break;
case TYPE_CONSTANT_16:
size += 2;
break;
}
return size;
}
void write(OutputBitStream outStream) throws IOException {
outStream.writeUI8(type);
switch (type) {
case TYPE_STRING:
outStream.writeString(string);
break;
case TYPE_FLOAT:
outStream.writeFloat(floatValue);
break;
case TYPE_REGISTER:
outStream.writeUI8(registerNumber);
break;
case TYPE_BOOLEAN:
outStream.writeUI8((short) (booleanValue ? 1 : 0));
break;
case TYPE_DOUBLE:
outStream.writeDouble(doubleValue);
break;
case TYPE_INTEGER:
outStream.writeUI32(integerValue);
break;
case TYPE_CONSTANT_8:
outStream.writeUI8(constant8);
break;
case TYPE_CONSTANT_16:
outStream.writeUI16(constant16);
break;
}
}
public int getArgsNumber() {
return 0;
}
public int getIntValue() {
int value = 0;
if (StackValue.TYPE_FLOAT == getType()) {
value = Float.valueOf(getFloat()).intValue();
}
if (StackValue.TYPE_DOUBLE == getType()) {
value = Double.valueOf(getDouble()).intValue();
}
if (StackValue.TYPE_INTEGER == getType()) {
value = (int) getInteger();
}
return value;
}
public double getDoubleValue() {
double value = 0;
if (StackValue.TYPE_FLOAT == getType()) {
value = Float.valueOf(getFloat()).doubleValue();
}
if (StackValue.TYPE_DOUBLE == getType()) {
value = Double.valueOf(getDouble()).doubleValue();
}
if (StackValue.TYPE_INTEGER == getType()) {
value = getInteger();
}
return value;
}
public String getStringValue(int level) {
String prefix = isStatement ? CodeUtil.getIndent(level) : "";
switch (type) {
case TYPE_NULL:
return "null";
case TYPE_STRING:
return prefix+formatString(string);
case TYPE_FLOAT:
return prefix+Float.valueOf(getFloat()).toString();
case TYPE_REGISTER:
return prefix+"r:" + getRegisterNumber();
case TYPE_BOOLEAN:
return prefix+(getBoolean() ? "true" : "false");
case TYPE_DOUBLE:
return prefix + ((getDouble() == 0) ? "0" : Double.valueOf(getDouble()).toString());
case TYPE_INTEGER:
return prefix+Long.valueOf(getInteger()).toString();
case TYPE_UNDEFINED:
return prefix+"undefined";
case TYPE_CONSTANT_8:
case TYPE_CONSTANT_16:
default:
return prefix+toString();
}
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(type).append(toString()).toHashCode();
}
@Override
public boolean equals(Object obj) {
// if (!(obj instanceof StackValue)) {
// return false;
// }
if (this == obj) {
return true;
}
if (obj instanceof StackValue) {
StackValue stackValue = (StackValue) obj;
boolean match = stackValue.getType() == type;
if (!match) {
return false;
}
switch (type) {
case TYPE_STRING:
match = string.equals(stackValue.getString());
break;
case TYPE_FLOAT:
match = (floatValue == stackValue.getFloat());
break;
case TYPE_REGISTER:
match = (registerNumber == stackValue.getRegisterNumber());
break;
case TYPE_BOOLEAN:
match = (booleanValue == stackValue.getBoolean());
break;
case TYPE_DOUBLE:
match = (doubleValue == stackValue.getDouble());
break;
case TYPE_INTEGER:
match = (integerValue == stackValue.getInteger());
break;
case TYPE_CONSTANT_8:
match = (constant8 == stackValue.getConstant8());
break;
case TYPE_CONSTANT_16:
match = (constant16 == stackValue.getConstant16());
break;
}
return match;
} else if (obj instanceof NotOperation) {
return obj.equals(this);
} else {
return false;
}
}
// public String formatString() {
// Assert.isTrue(type == TYPE_STRING);
// return formatString(string);
// }
public static String formatString(String v) {
return "\""+escapeString(v)+"\"";
}
private static String escapeString(String v) {
v = v.replaceAll("\\\\", "\\\\\\\\");
v = v.replaceAll("\n", "\\\\n");
v = v.replaceAll("\"", "\\\\\"");
v = v.replaceAll("\t", "\\\\t");
v = v.replaceAll("\r", "\\\\r");
return v;
}
public int getPriority() {
return Priority.HIGHEST;
}
public static void main(String[] args) {
System.out.println(formatString("\\ - \n"));
}
// public String getInvertedStringValue(int level) {
// if (TYPE_BOOLEAN == type) {
// return booleanValue ? "false" : "true";
// } else {
// return "!"+getStringValue(level);
// }
// }
public Operation getInvertedOperation() {
return new InvertedStackValue(this);
}
/**
* Inverted stack value operation class.
*
*/
private class InvertedStackValue implements BooleanOperation, DualUse {
private StackValue stackValue;
private boolean isStatement = false;
public InvertedStackValue(StackValue stackValue) {
super();
this.stackValue = stackValue;
}
public Operation getInvertedOperation() {
return stackValue;
}
public int getArgsNumber() {
return 0;
}
public int getPriority() {
return Priority.HIGHEST;
}
public String getStringValue(int level) {
String prefix = isStatement ? CodeUtil.getIndent(level) : "";
if (TYPE_BOOLEAN == stackValue.getType()) {
return prefix+ (stackValue.getBoolean() ? "false" : "true");
} else {
return prefix+"!"+getStringValue(level);
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof NotOperation) {
return obj.equals(this); // invert comparison
} else {
return super.equals(obj);
}
}
public List<Operation> getOperations() {
return Collections.EMPTY_LIST;
}
public void markAsStatement() {
isStatement = true;
}
}
public void markAsStatement() {
isStatement = true;
}
public List<Operation> getOperations() {
return Collections.EMPTY_LIST;
}
}