0001: /*
0002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
0003: * notice. All rights reserved.
0004: */
0005: package com.tc.object.dna.impl;
0006:
0007: import com.tc.exception.TCRuntimeException;
0008: import com.tc.io.TCByteArrayOutputStream;
0009: import com.tc.io.TCDataInput;
0010: import com.tc.io.TCDataOutput;
0011: import com.tc.logging.TCLogger;
0012: import com.tc.logging.TCLogging;
0013: import com.tc.object.LiteralValues;
0014: import com.tc.object.ObjectID;
0015: import com.tc.object.dna.api.DNAEncoding;
0016: import com.tc.object.loaders.ClassProvider;
0017: import com.tc.object.loaders.NamedClassLoader;
0018: import com.tc.properties.TCPropertiesImpl;
0019: import com.tc.util.Assert;
0020:
0021: import gnu.trove.TObjectIntHashMap;
0022:
0023: import java.io.ByteArrayInputStream;
0024: import java.io.IOException;
0025: import java.io.UnsupportedEncodingException;
0026: import java.lang.reflect.Array;
0027: import java.lang.reflect.Constructor;
0028: import java.lang.reflect.Field;
0029: import java.lang.reflect.InvocationTargetException;
0030: import java.lang.reflect.Method;
0031: import java.math.BigDecimal;
0032: import java.math.BigInteger;
0033: import java.util.Currency;
0034: import java.util.zip.DeflaterOutputStream;
0035: import java.util.zip.InflaterInputStream;
0036:
0037: /**
0038: * Utility for encoding/decoding DNA
0039: */
0040: public class DNAEncodingImpl implements DNAEncoding {
0041:
0042: // XXX: These warning thresholds should be done in a non-static way so they can be made configurable
0043: // and architecture sensitive.
0044: private static final int WARN_THRESHOLD = 8 * 1000 * 1000;
0045: private static final int BOOLEAN_WARN = WARN_THRESHOLD / 1;
0046: private static final int BYTE_WARN = WARN_THRESHOLD / 1;
0047: private static final int CHAR_WARN = WARN_THRESHOLD / 2;
0048: private static final int DOUBLE_WARN = WARN_THRESHOLD / 8;
0049: private static final int FLOAT_WARN = WARN_THRESHOLD / 4;
0050: private static final int INT_WARN = WARN_THRESHOLD / 4;
0051: private static final int LONG_WARN = WARN_THRESHOLD / 8;
0052: private static final int SHORT_WARN = WARN_THRESHOLD / 2;
0053: private static final int REF_WARN = WARN_THRESHOLD / 4;
0054:
0055: static final byte LOGICAL_ACTION_TYPE = 1;
0056: static final byte PHYSICAL_ACTION_TYPE = 2;
0057: static final byte ARRAY_ELEMENT_ACTION_TYPE = 3;
0058: static final byte ENTIRE_ARRAY_ACTION_TYPE = 4;
0059: static final byte LITERAL_VALUE_ACTION_TYPE = 5;
0060: static final byte PHYSICAL_ACTION_TYPE_REF_OBJECT = 6;
0061: static final byte SUB_ARRAY_ACTION_TYPE = 7;
0062:
0063: private static final LiteralValues literalValues = new LiteralValues();
0064: private static final TCLogger logger = TCLogging
0065: .getLogger(DNAEncodingImpl.class);
0066:
0067: private static final byte TYPE_ID_REFERENCE = 1;
0068: private static final byte TYPE_ID_BOOLEAN = 2;
0069: private static final byte TYPE_ID_BYTE = 3;
0070: private static final byte TYPE_ID_CHAR = 4;
0071: private static final byte TYPE_ID_DOUBLE = 5;
0072: private static final byte TYPE_ID_FLOAT = 6;
0073: private static final byte TYPE_ID_INT = 7;
0074: private static final byte TYPE_ID_LONG = 10;
0075: private static final byte TYPE_ID_SHORT = 11;
0076: private static final byte TYPE_ID_STRING = 12;
0077: private static final byte TYPE_ID_STRING_BYTES = 13;
0078: private static final byte TYPE_ID_ARRAY = 14;
0079: private static final byte TYPE_ID_JAVA_LANG_CLASS = 15;
0080: private static final byte TYPE_ID_JAVA_LANG_CLASS_HOLDER = 16;
0081: private static final byte TYPE_ID_BIG_INTEGER = 17;
0082: private static final byte TYPE_ID_STACK_TRACE_ELEMENT = 18;
0083: private static final byte TYPE_ID_BIG_DECIMAL = 19;
0084: private static final byte TYPE_ID_JAVA_LANG_CLASSLOADER = 20;
0085: private static final byte TYPE_ID_JAVA_LANG_CLASSLOADER_HOLDER = 21;
0086: private static final byte TYPE_ID_ENUM = 22;
0087: private static final byte TYPE_ID_ENUM_HOLDER = 23;
0088: private static final byte TYPE_ID_CURRENCY = 24;
0089: private static final byte TYPE_ID_STRING_COMPRESSED = 25;
0090: // private static final byte TYPE_ID_URL = 26;
0091:
0092: private static final byte ARRAY_TYPE_PRIMITIVE = 1;
0093: private static final byte ARRAY_TYPE_NON_PRIMITIVE = 2;
0094:
0095: private final ClassProvider classProvider;
0096: private final byte policy;
0097:
0098: private static final ClassProvider FAILURE_PROVIDER = new FailureClassProvider();
0099: private static final ClassProvider LOCAL_PROVIDER = new LocalClassProvider();
0100:
0101: private static final boolean STRING_COMPRESSION_ENABLED = TCPropertiesImpl
0102: .getProperties().getBoolean(
0103: "l1.transactionmanager.strings.compress.enabled");
0104: private static final boolean STRING_COMPRESSION_LOGGING_ENABLED = TCPropertiesImpl
0105: .getProperties()
0106: .getBoolean(
0107: "l1.transactionmanager.strings.compress.logging.enabled");
0108: private static final int STRING_COMPRESSION_MIN_SIZE = TCPropertiesImpl
0109: .getProperties().getInt(
0110: "l1.transactionmanager.strings.compress.minSize");
0111:
0112: /**
0113: * Used in the Applicators. The policy is set to APPLICATOR.
0114: */
0115: public DNAEncodingImpl(ClassProvider classProvider) {
0116: this .classProvider = classProvider;
0117: this .policy = APPLICATOR;
0118: }
0119:
0120: public DNAEncodingImpl(byte policy) {
0121: this .policy = policy;
0122: // you only want this version on the server where you won't be expanding java.lang.Class instances
0123: if (policy == STORAGE) {
0124: this .classProvider = FAILURE_PROVIDER;
0125: } else if (policy == SERIALIZER) {
0126: this .classProvider = LOCAL_PROVIDER;
0127: } else {
0128: throw new AssertionError("Policy not valid : " + policy
0129: + " : For APPLICATORS use the other contructor !");
0130: }
0131: }
0132:
0133: public byte getPolicy() {
0134: return this .policy;
0135: }
0136:
0137: public void encodeClassLoader(Object value, TCDataOutput output) {
0138: output.writeByte(TYPE_ID_JAVA_LANG_CLASSLOADER);
0139: writeString(classProvider
0140: .getLoaderDescriptionFor((ClassLoader) value), output);
0141: }
0142:
0143: /**
0144: * The reason that we use reflection here is that Enum is a jdk 1.5 construct and this project is jdk 1.4 compliance.
0145: */
0146: private Object getEnumName(Object enumObject) {
0147: try {
0148: Method m = enumObject.getClass().getMethod("name",
0149: new Class[0]);
0150: m.setAccessible(true);
0151: Object val;
0152: val = m.invoke(enumObject, new Object[0]);
0153: return val;
0154: } catch (IllegalArgumentException e) {
0155: throw new TCRuntimeException(e);
0156: } catch (IllegalAccessException e) {
0157: throw new TCRuntimeException(e);
0158: } catch (InvocationTargetException e) {
0159: throw new TCRuntimeException(e);
0160: } catch (SecurityException e) {
0161: throw new TCRuntimeException(e);
0162: } catch (NoSuchMethodException e) {
0163: throw new TCRuntimeException(e);
0164: }
0165: }
0166:
0167: public void encode(Object value, TCDataOutput output) {
0168: if (value == null) {
0169: // Normally Null values should have already been converted to null ObjectID, but this is not true when there are
0170: // multiple versions of the same class in the cluster sharign data.
0171: value = ObjectID.NULL_ID;
0172: }
0173:
0174: // final Class valueClass = value.getClass();
0175: // final int type = literalValues.valueFor(valueClass.getName());
0176: final int type = literalValues.valueFor(value);
0177:
0178: switch (type) {
0179: case LiteralValues.CURRENCY:
0180: output.writeByte(TYPE_ID_CURRENCY);
0181: writeString(((Currency) value).getCurrencyCode(), output);
0182: break;
0183: case LiteralValues.ENUM:
0184: output.writeByte(TYPE_ID_ENUM);
0185: Class enumClass = getEnumDeclaringClass(value);
0186: writeString(enumClass.getName(), output);
0187: writeString(classProvider
0188: .getLoaderDescriptionFor(enumClass), output);
0189:
0190: Object name = getEnumName(value);
0191: writeString((String) name, output);
0192: break;
0193: case LiteralValues.ENUM_HOLDER:
0194: output.writeByte(TYPE_ID_ENUM_HOLDER);
0195: writeEnumInstance((EnumInstance) value, output);
0196: break;
0197: case LiteralValues.JAVA_LANG_CLASSLOADER:
0198: encodeClassLoader(value, output);
0199: break;
0200: case LiteralValues.JAVA_LANG_CLASSLOADER_HOLDER:
0201: output.writeByte(TYPE_ID_JAVA_LANG_CLASSLOADER_HOLDER);
0202: writeClassLoaderInstance((ClassLoaderInstance) value,
0203: output);
0204: break;
0205: case LiteralValues.JAVA_LANG_CLASS:
0206: output.writeByte(TYPE_ID_JAVA_LANG_CLASS);
0207: Class c = (Class) value;
0208: writeString(c.getName(), output);
0209: writeString(classProvider.getLoaderDescriptionFor(c),
0210: output);
0211: break;
0212: case LiteralValues.JAVA_LANG_CLASS_HOLDER:
0213: output.writeByte(TYPE_ID_JAVA_LANG_CLASS_HOLDER);
0214: writeClassInstance((ClassInstance) value, output);
0215: break;
0216: case LiteralValues.BOOLEAN:
0217: output.writeByte(TYPE_ID_BOOLEAN);
0218: output.writeBoolean(((Boolean) value).booleanValue());
0219: break;
0220: case LiteralValues.BYTE:
0221: output.writeByte(TYPE_ID_BYTE);
0222: output.writeByte(((Byte) value).byteValue());
0223: break;
0224: case LiteralValues.CHARACTER:
0225: output.writeByte(TYPE_ID_CHAR);
0226: output.writeChar(((Character) value).charValue());
0227: break;
0228: case LiteralValues.DOUBLE:
0229: output.writeByte(TYPE_ID_DOUBLE);
0230: output.writeDouble(((Double) value).doubleValue());
0231: break;
0232: case LiteralValues.FLOAT:
0233: output.writeByte(TYPE_ID_FLOAT);
0234: output.writeFloat(((Float) value).floatValue());
0235: break;
0236: case LiteralValues.INTEGER:
0237: output.writeByte(TYPE_ID_INT);
0238: output.writeInt(((Integer) value).intValue());
0239: break;
0240: case LiteralValues.LONG:
0241: output.writeByte(TYPE_ID_LONG);
0242: output.writeLong(((Long) value).longValue());
0243: break;
0244: case LiteralValues.SHORT:
0245: output.writeByte(TYPE_ID_SHORT);
0246: output.writeShort(((Short) value).shortValue());
0247: break;
0248: case LiteralValues.STRING:
0249: String s = (String) value;
0250: if (STRING_COMPRESSION_ENABLED
0251: && s.length() >= STRING_COMPRESSION_MIN_SIZE) {
0252: output.writeByte(TYPE_ID_STRING_COMPRESSED);
0253: writeCompressedString(s, output);
0254: } else {
0255: output.writeByte(TYPE_ID_STRING);
0256: writeString(s, output);
0257: }
0258: break;
0259: case LiteralValues.STRING_BYTES:
0260: UTF8ByteDataHolder utfBytes = (UTF8ByteDataHolder) value;
0261: if (utfBytes.isCompressed()) {
0262: output.writeByte(TYPE_ID_STRING_COMPRESSED);
0263: output.writeInt(utfBytes.getUnCompressedStringLength());
0264: writeByteArray(utfBytes.getBytes(), output);
0265: } else {
0266: output.writeByte(TYPE_ID_STRING_BYTES);
0267: writeByteArray(utfBytes.getBytes(), output);
0268: }
0269: break;
0270: case LiteralValues.OBJECT_ID:
0271: output.writeByte(TYPE_ID_REFERENCE);
0272: output.writeLong(((ObjectID) value).toLong());
0273: break;
0274: case LiteralValues.STACK_TRACE_ELEMENT:
0275: output.writeByte(TYPE_ID_STACK_TRACE_ELEMENT);
0276: StackTraceElement ste = (StackTraceElement) value;
0277: writeStackTraceElement(ste, output);
0278: break;
0279: case LiteralValues.BIG_INTEGER:
0280: output.writeByte(TYPE_ID_BIG_INTEGER);
0281: writeByteArray(((BigInteger) value).toByteArray(), output);
0282: break;
0283: case LiteralValues.BIG_DECIMAL:
0284: output.writeByte(TYPE_ID_BIG_DECIMAL);
0285: writeByteArray(((BigDecimal) value).toString().getBytes(),
0286: output);
0287: break;
0288: case LiteralValues.ARRAY:
0289: encodeArray(value, output);
0290: break;
0291: // case LiteralValues.URL:
0292: // {
0293: // URL url = (URL)value;
0294: // output.writeByte(TYPE_ID_URL);
0295: // output.writeString(url.getProtocol());
0296: // output.writeString(url.getHost());
0297: // output.writeInt(url.getPort());
0298: // output.writeString(url.getFile());
0299: // output.writeString(url.getRef());
0300: // }
0301: // break;
0302: default:
0303: throw Assert
0304: .failure("Illegal type (" + type + "):" + value);
0305: }
0306:
0307: // unreachable
0308: }
0309:
0310: private void writeStackTraceElement(StackTraceElement ste,
0311: TCDataOutput output) {
0312: output.writeString(ste.getClassName());
0313: output.writeString(ste.getMethodName());
0314: output.writeString(ste.getFileName());
0315: output.writeInt(ste.getLineNumber());
0316: }
0317:
0318: private void writeEnumInstance(EnumInstance value,
0319: TCDataOutput output) {
0320: writeByteArray(value.getClassInstance().getName().getBytes(),
0321: output);
0322: writeByteArray(value.getClassInstance().getLoaderDef()
0323: .getBytes(), output);
0324: writeByteArray(((UTF8ByteDataHolder) value.getEnumName())
0325: .getBytes(), output);
0326: }
0327:
0328: private void writeClassLoaderInstance(ClassLoaderInstance value,
0329: TCDataOutput output) {
0330: writeByteArray(value.getLoaderDef().getBytes(), output);
0331: }
0332:
0333: private void writeClassInstance(ClassInstance value,
0334: TCDataOutput output) {
0335: writeByteArray(value.getName().getBytes(), output);
0336: writeByteArray(value.getLoaderDef().getBytes(), output);
0337: }
0338:
0339: private void writeString(String string, TCDataOutput output) {
0340: try {
0341: writeByteArray(string.getBytes("UTF-8"), output);
0342: } catch (UnsupportedEncodingException e) {
0343: throw new AssertionError(e);
0344: }
0345: }
0346:
0347: private void writeCompressedString(String string,
0348: TCDataOutput output) {
0349: try {
0350: TCByteArrayOutputStream byteArrayOS = new TCByteArrayOutputStream(
0351: 4096);
0352: // Stride is 512 bytes by default, should I increase ?
0353: DeflaterOutputStream dos = new DeflaterOutputStream(
0354: byteArrayOS);
0355: byte[] uncompressed = string.getBytes("UTF-8");
0356: dos.write(uncompressed);
0357: dos.close();
0358: byte[] compressed = byteArrayOS.getInternalArray();
0359: // XXX:: We are writting the original string's length so that we save a couple of copies when decompressing
0360: output.writeInt(uncompressed.length);
0361: writeByteArray(compressed, 0, byteArrayOS.size(), output);
0362: if (STRING_COMPRESSION_LOGGING_ENABLED) {
0363: logger.info("Compressed String of size : "
0364: + string.length() + " bytes : "
0365: + uncompressed.length + " to bytes : "
0366: + compressed.length);
0367: }
0368: } catch (Exception e) {
0369: throw new AssertionError(e);
0370: }
0371: }
0372:
0373: private void writeByteArray(byte[] bytes, int offset, int length,
0374: TCDataOutput output) {
0375: output.writeInt(length);
0376: output.write(bytes, offset, length);
0377: }
0378:
0379: private void writeByteArray(byte bytes[], TCDataOutput output) {
0380: output.writeInt(bytes.length);
0381: output.write(bytes);
0382: }
0383:
0384: /* This method is an optimized method for writing char array when no check is needed */
0385: // private void writeCharArray(char[] chars, TCDataOutput output) {
0386: // output.writeInt(chars.length);
0387: // for (int i = 0, n = chars.length; i < n; i++) {
0388: // output.writeChar(chars[i]);
0389: // }
0390: // }
0391: private byte[] readByteArray(TCDataInput input) throws IOException {
0392: int length = input.readInt();
0393: if (length >= BYTE_WARN) {
0394: logger
0395: .warn("Attempting to allocate a large byte array of size: "
0396: + length);
0397: }
0398: byte[] array = new byte[length];
0399: input.readFully(array);
0400: return array;
0401: }
0402:
0403: public Object decode(TCDataInput input) throws IOException,
0404: ClassNotFoundException {
0405: final byte type = input.readByte();
0406:
0407: switch (type) {
0408: case TYPE_ID_CURRENCY:
0409: return readCurrency(input, type);
0410: case TYPE_ID_ENUM:
0411: return readEnum(input, type);
0412: case TYPE_ID_ENUM_HOLDER:
0413: return readEnum(input, type);
0414: case TYPE_ID_JAVA_LANG_CLASSLOADER:
0415: return readClassLoader(input, type);
0416: case TYPE_ID_JAVA_LANG_CLASSLOADER_HOLDER:
0417: return readClassLoader(input, type);
0418: case TYPE_ID_JAVA_LANG_CLASS:
0419: return readClass(input, type);
0420: case TYPE_ID_JAVA_LANG_CLASS_HOLDER:
0421: return readClass(input, type);
0422: case TYPE_ID_BOOLEAN:
0423: return new Boolean(input.readBoolean());
0424: case TYPE_ID_BYTE:
0425: return new Byte(input.readByte());
0426: case TYPE_ID_CHAR:
0427: return new Character(input.readChar());
0428: case TYPE_ID_DOUBLE:
0429: return new Double(input.readDouble());
0430: case TYPE_ID_FLOAT:
0431: return new Float(input.readFloat());
0432: case TYPE_ID_INT:
0433: return new Integer(input.readInt());
0434: case TYPE_ID_LONG:
0435: return new Long(input.readLong());
0436: case TYPE_ID_SHORT:
0437: return new Short(input.readShort());
0438: case TYPE_ID_STRING:
0439: return readString(input, type);
0440: case TYPE_ID_STRING_COMPRESSED:
0441: return readCompressedString(input);
0442: case TYPE_ID_STRING_BYTES:
0443: return readString(input, type);
0444: case TYPE_ID_REFERENCE:
0445: return new ObjectID(input.readLong());
0446: case TYPE_ID_ARRAY:
0447: return decodeArray(input);
0448: case TYPE_ID_STACK_TRACE_ELEMENT:
0449: return readStackTraceElement(input);
0450: case TYPE_ID_BIG_INTEGER:
0451: byte[] b1 = readByteArray(input);
0452: return new BigInteger(b1);
0453: case TYPE_ID_BIG_DECIMAL:
0454: // char[] chars = readCharArray(input); // Unfortunately this is 1.5 specific
0455: byte[] b2 = readByteArray(input);
0456: return new BigDecimal(new String(b2));
0457: // case TYPE_ID_URL:
0458: // {
0459: // String protocol = input.readString();
0460: // String host = input.readString();
0461: // int port = input.readInt();
0462: // String file = input.readString();
0463: // String ref = input.readString();
0464: // if (ref != null) {
0465: // file = file+"#"+ref;
0466: // }
0467: // return new URL(protocol, host, port, file);
0468: // }
0469: default:
0470: throw Assert.failure("Illegal type (" + type + ")");
0471: }
0472:
0473: // unreachable
0474: }
0475:
0476: // private char[] readCharArray(TCDataInput input) throws IOException {
0477: // int length = input.readInt();
0478: // if (length >= CHAR_WARN) {
0479: // logger.warn("Attempting to allocate a large char array of size: " + length);
0480: // }
0481: // char[] array = new char[length];
0482: // for (int i = 0, n = array.length; i < n; i++) {
0483: // array[i] = input.readChar();
0484: // }
0485: // return array;
0486: // }
0487:
0488: private Object readStackTraceElement(TCDataInput input)
0489: throws IOException, ClassNotFoundException {
0490: String className = input.readString();
0491: String methodName = input.readString();
0492: String fileName = input.readString();
0493: int lineNumber = input.readInt();
0494: return createStackTraceElement(className, fileName, methodName,
0495: lineNumber);
0496: }
0497:
0498: /*
0499: * This method uses reflection as 1.4 doesnt have a public constructor for stack trace element and 1.5 removed the
0500: * private no arg constructor. XXX::This is an ugly hack that I would like to getaway from
0501: */
0502: private Object createStackTraceElement(String className,
0503: String fileName, String methodName, int lineNumber)
0504: throws ClassNotFoundException, IOException {
0505: Class clazz = Class.forName("java.lang.StackTraceElement");
0506: Constructor constructors[] = clazz.getDeclaredConstructors();
0507: for (int i = 0; i < constructors.length; i++) {
0508: Class[] types = constructors[i].getParameterTypes();
0509: if (types.length == 0) {
0510: // This is 1.4
0511: return createStackTraceElementJDK14(clazz,
0512: constructors[i], className, fileName,
0513: methodName, lineNumber);
0514: } else if (types.length == 4 && types[0] == String.class
0515: && types[1] == String.class
0516: && types[2] == String.class
0517: && types[3] == int.class) {
0518: // This is 1.5
0519: return createStackTraceElementJDK15(clazz,
0520: constructors[i], className, fileName,
0521: methodName, lineNumber);
0522: }
0523: }
0524: throw new ClassNotFoundException(
0525: "java.lang.StackTraceElement : Both known constructors not found !");
0526: }
0527:
0528: private Object createStackTraceElementJDK14(Class clazz,
0529: Constructor constructor, String className, String fileName,
0530: String methodName, int lineNumber) throws IOException {
0531: try {
0532: constructor.setAccessible(true);
0533: Object i = constructor.newInstance(new Object[0]);
0534: Field[] fields = clazz.getDeclaredFields();
0535: byte set = 0x00;
0536: for (int j = 0; j < fields.length; j++) {
0537: fields[j].setAccessible(true);
0538: if ("declaringClass".equalsIgnoreCase(fields[j]
0539: .getName())) {
0540: fields[j].set(i, className);
0541: set |= 0x01;
0542: } else if ("methodName".equalsIgnoreCase(fields[j]
0543: .getName())) {
0544: fields[j].set(i, methodName);
0545: set |= 0x02;
0546: } else if ("fileName".equalsIgnoreCase(fields[j]
0547: .getName())) {
0548: fields[j].set(i, fileName);
0549: set |= 0x04;
0550: } else if ("lineNumber".equalsIgnoreCase(fields[j]
0551: .getName())) {
0552: fields[j].setInt(i, lineNumber);
0553: set |= 0x08;
0554: }
0555: }
0556: Assert.assertTrue(set == 0x0F);
0557: return i;
0558: } catch (Exception ex) {
0559: IOException ioe = new IOException();
0560: ioe.initCause(ex);
0561: throw ioe;
0562: }
0563: }
0564:
0565: private Object createStackTraceElementJDK15(Class clazz,
0566: Constructor constructor, String className, String fileName,
0567: String methodName, int lineNumber) throws IOException {
0568: try {
0569: Object params[] = new Object[4];
0570: params[0] = className;
0571: params[1] = methodName;
0572: params[2] = fileName;
0573: params[3] = new Integer(lineNumber);
0574: return constructor.newInstance(params);
0575: } catch (Exception ex) {
0576: IOException ioe = new IOException();
0577: ioe.initCause(ex);
0578: throw ioe;
0579: }
0580: }
0581:
0582: public void encodeArray(Object value, TCDataOutput output) {
0583: encodeArray(value, output, value == null ? -1 : Array
0584: .getLength(value));
0585: }
0586:
0587: public void encodeArray(Object value, TCDataOutput output,
0588: int length) {
0589: output.writeByte(TYPE_ID_ARRAY);
0590:
0591: if (value == null) {
0592: output.writeInt(-1);
0593: return;
0594: } else {
0595: output.writeInt(length);
0596: }
0597:
0598: Class type = value.getClass().getComponentType();
0599:
0600: if (type.isPrimitive()) {
0601: output.writeByte(ARRAY_TYPE_PRIMITIVE);
0602: switch (primitiveClassMap.get(type)) {
0603: case TYPE_ID_BOOLEAN:
0604: encodeBooleanArray((boolean[]) value, output, length);
0605: break;
0606: case TYPE_ID_BYTE:
0607: encodeByteArray((byte[]) value, output, length);
0608: break;
0609: case TYPE_ID_CHAR:
0610: encodeCharArray((char[]) value, output, length);
0611: break;
0612: case TYPE_ID_SHORT:
0613: encodeShortArray((short[]) value, output, length);
0614: break;
0615: case TYPE_ID_INT:
0616: encodeIntArray((int[]) value, output, length);
0617: break;
0618: case TYPE_ID_LONG:
0619: encodeLongArray((long[]) value, output, length);
0620: break;
0621: case TYPE_ID_FLOAT:
0622: encodeFloatArray((float[]) value, output, length);
0623: break;
0624: case TYPE_ID_DOUBLE:
0625: encodeDoubleArray((double[]) value, output, length);
0626: break;
0627: default:
0628: throw Assert.failure("unknown primitive array type: "
0629: + type);
0630: }
0631: } else {
0632: output.writeByte(ARRAY_TYPE_NON_PRIMITIVE);
0633: encodeObjectArray((Object[]) value, output, length);
0634: }
0635: }
0636:
0637: private void encodeByteArray(byte[] value, TCDataOutput output,
0638: int length) {
0639: output.writeByte(TYPE_ID_BYTE);
0640:
0641: for (int i = 0; i < length; i++) {
0642: output.write(value[i]);
0643: }
0644: }
0645:
0646: private void encodeObjectArray(Object[] value, TCDataOutput output,
0647: int length) {
0648: for (int i = 0; i < length; i++) {
0649: encode(value[i], output);
0650: }
0651: }
0652:
0653: private void encodeDoubleArray(double[] value, TCDataOutput output,
0654: int length) {
0655: output.writeByte(TYPE_ID_DOUBLE);
0656: for (int i = 0; i < length; i++) {
0657: output.writeDouble(value[i]);
0658: }
0659: }
0660:
0661: private void encodeFloatArray(float[] value, TCDataOutput output,
0662: int length) {
0663: output.writeByte(TYPE_ID_FLOAT);
0664: for (int i = 0; i < length; i++) {
0665: output.writeFloat(value[i]);
0666: }
0667: }
0668:
0669: private void encodeLongArray(long[] value, TCDataOutput output,
0670: int length) {
0671: output.writeByte(TYPE_ID_LONG);
0672: for (int i = 0; i < length; i++) {
0673: output.writeLong(value[i]);
0674: }
0675: }
0676:
0677: private void encodeIntArray(int[] value, TCDataOutput output,
0678: int length) {
0679: output.writeByte(TYPE_ID_INT);
0680: for (int i = 0; i < length; i++) {
0681: output.writeInt(value[i]);
0682: }
0683: }
0684:
0685: private void encodeShortArray(short[] value, TCDataOutput output,
0686: int length) {
0687: output.writeByte(TYPE_ID_SHORT);
0688: for (int i = 0; i < length; i++) {
0689: output.writeShort(value[i]);
0690: }
0691: }
0692:
0693: private void encodeCharArray(char[] value, TCDataOutput output,
0694: int length) {
0695: output.writeByte(TYPE_ID_CHAR);
0696: for (int i = 0; i < length; i++) {
0697: output.writeChar(value[i]);
0698: }
0699: }
0700:
0701: private void encodeBooleanArray(boolean[] value,
0702: TCDataOutput output, int length) {
0703: output.writeByte(TYPE_ID_BOOLEAN);
0704: for (int i = 0; i < length; i++) {
0705: output.writeBoolean(value[i]);
0706: }
0707: }
0708:
0709: private void checkSize(Class type, int threshold, int len) {
0710: if (len >= threshold) {
0711: logger.warn("Attempt to read a " + type + " array of len: "
0712: + len + "; threshold=" + threshold);
0713: }
0714: }
0715:
0716: private Object decodeArray(TCDataInput input) throws IOException,
0717: ClassNotFoundException {
0718: final int len = input.readInt();
0719: if (len < 0) {
0720: return null;
0721: }
0722:
0723: final byte arrayType = input.readByte();
0724: switch (arrayType) {
0725: case ARRAY_TYPE_PRIMITIVE:
0726: return decodePrimitiveArray(len, input);
0727: case ARRAY_TYPE_NON_PRIMITIVE:
0728: return decodeNonPrimitiveArray(len, input);
0729: default:
0730: throw Assert.failure("unknown array type: " + arrayType);
0731: }
0732:
0733: // unreachable
0734: }
0735:
0736: private Object[] decodeNonPrimitiveArray(int len, TCDataInput input)
0737: throws IOException, ClassNotFoundException {
0738: checkSize(Object.class, REF_WARN, len);
0739: Object[] rv = new Object[len];
0740: for (int i = 0, n = rv.length; i < n; i++) {
0741: rv[i] = decode(input);
0742: }
0743:
0744: return rv;
0745: }
0746:
0747: private Object decodePrimitiveArray(int len, TCDataInput input)
0748: throws IOException {
0749: byte type = input.readByte();
0750:
0751: switch (type) {
0752: case TYPE_ID_BOOLEAN:
0753: checkSize(Boolean.TYPE, BOOLEAN_WARN, len);
0754: return decodeBooleanArray(len, input);
0755: case TYPE_ID_BYTE:
0756: checkSize(Byte.TYPE, BYTE_WARN, len);
0757: return decodeByteArray(len, input);
0758: case TYPE_ID_CHAR:
0759: checkSize(Character.TYPE, CHAR_WARN, len);
0760: return decodeCharArray(len, input);
0761: case TYPE_ID_DOUBLE:
0762: checkSize(Double.TYPE, DOUBLE_WARN, len);
0763: return decodeDoubleArray(len, input);
0764: case TYPE_ID_FLOAT:
0765: checkSize(Float.TYPE, FLOAT_WARN, len);
0766: return decodeFloatArray(len, input);
0767: case TYPE_ID_INT:
0768: checkSize(Integer.TYPE, INT_WARN, len);
0769: return decodeIntArray(len, input);
0770: case TYPE_ID_LONG:
0771: checkSize(Long.TYPE, LONG_WARN, len);
0772: return decodeLongArray(len, input);
0773: case TYPE_ID_SHORT:
0774: checkSize(Short.TYPE, SHORT_WARN, len);
0775: return decodeShortArray(len, input);
0776: default:
0777: throw Assert.failure("unknown prim type: " + type);
0778: }
0779:
0780: // unreachable
0781: }
0782:
0783: private short[] decodeShortArray(int len, TCDataInput input)
0784: throws IOException {
0785: short[] rv = new short[len];
0786: for (int i = 0, n = rv.length; i < n; i++) {
0787: rv[i] = input.readShort();
0788: }
0789: return rv;
0790: }
0791:
0792: private long[] decodeLongArray(int len, TCDataInput input)
0793: throws IOException {
0794: long[] rv = new long[len];
0795: for (int i = 0, n = rv.length; i < n; i++) {
0796: rv[i] = input.readLong();
0797: }
0798: return rv;
0799: }
0800:
0801: private int[] decodeIntArray(int len, TCDataInput input)
0802: throws IOException {
0803: int[] rv = new int[len];
0804: for (int i = 0, n = rv.length; i < n; i++) {
0805: rv[i] = input.readInt();
0806: }
0807: return rv;
0808: }
0809:
0810: private float[] decodeFloatArray(int len, TCDataInput input)
0811: throws IOException {
0812: float[] rv = new float[len];
0813: for (int i = 0, n = rv.length; i < n; i++) {
0814: rv[i] = input.readFloat();
0815: }
0816: return rv;
0817: }
0818:
0819: private double[] decodeDoubleArray(int len, TCDataInput input)
0820: throws IOException {
0821: double[] rv = new double[len];
0822: for (int i = 0, n = rv.length; i < n; i++) {
0823: rv[i] = input.readDouble();
0824: }
0825: return rv;
0826: }
0827:
0828: private char[] decodeCharArray(int len, TCDataInput input)
0829: throws IOException {
0830: char[] rv = new char[len];
0831: for (int i = 0, n = rv.length; i < n; i++) {
0832: rv[i] = input.readChar();
0833: }
0834: return rv;
0835: }
0836:
0837: private byte[] decodeByteArray(int len, TCDataInput input)
0838: throws IOException {
0839: byte[] rv = new byte[len];
0840: if (len != 0) {
0841: int read = input.read(rv, 0, len);
0842: if (read != len) {
0843: throw new IOException("read " + read
0844: + " bytes, expected " + len);
0845: }
0846: }
0847: return rv;
0848: }
0849:
0850: private boolean[] decodeBooleanArray(int len, TCDataInput input)
0851: throws IOException {
0852: boolean[] rv = new boolean[len];
0853: for (int i = 0, n = rv.length; i < n; i++) {
0854: rv[i] = input.readBoolean();
0855: }
0856: return rv;
0857: }
0858:
0859: /**
0860: * The reason that we use reflection here is because Enum is a jdk 1.5 construct and this project is jdk 1.4
0861: * compliance.
0862: */
0863: private Class getEnumDeclaringClass(Object enumObj) {
0864: try {
0865: Method m = enumObj.getClass().getMethod(
0866: "getDeclaringClass", new Class[0]);
0867: Object enumDeclaringClass = m
0868: .invoke(enumObj, new Object[0]);
0869: return (Class) enumDeclaringClass;
0870: } catch (SecurityException e) {
0871: throw new TCRuntimeException(e);
0872: } catch (NoSuchMethodException e) {
0873: throw new TCRuntimeException(e);
0874: } catch (IllegalArgumentException e) {
0875: throw new TCRuntimeException(e);
0876: } catch (IllegalAccessException e) {
0877: throw new TCRuntimeException(e);
0878: } catch (InvocationTargetException e) {
0879: throw new TCRuntimeException(e);
0880: }
0881: }
0882:
0883: /**
0884: * The reason that we use reflection here is because Enum is a jdk 1.5 construct and this project is jdk 1.4
0885: * compliance.
0886: */
0887: private Object enumValueOf(Class enumType, String enumName) {
0888: try {
0889: Method m = enumType.getMethod("valueOf", new Class[] {
0890: Class.class, String.class });
0891: Object enumObj = m.invoke(null, new Object[] { enumType,
0892: enumName });
0893: return enumObj;
0894: } catch (SecurityException e) {
0895: throw new TCRuntimeException(e);
0896: } catch (NoSuchMethodException e) {
0897: throw new TCRuntimeException(e);
0898: } catch (IllegalArgumentException e) {
0899: throw new TCRuntimeException(e);
0900: } catch (IllegalAccessException e) {
0901: throw new TCRuntimeException(e);
0902: } catch (InvocationTargetException e) {
0903: throw new TCRuntimeException(e);
0904: }
0905: }
0906:
0907: private Object readCurrency(TCDataInput input, byte type)
0908: throws IOException {
0909: byte[] data = readByteArray(input);
0910: String currencyCode = new String(data, "UTF-8");
0911: return Currency.getInstance(currencyCode);
0912: }
0913:
0914: private Object readEnum(TCDataInput input, byte type)
0915: throws IOException, ClassNotFoundException {
0916: UTF8ByteDataHolder name = new UTF8ByteDataHolder(
0917: readByteArray(input));
0918: UTF8ByteDataHolder def = new UTF8ByteDataHolder(
0919: readByteArray(input));
0920: byte[] data = readByteArray(input);
0921:
0922: if ((policy == SERIALIZER && type == TYPE_ID_ENUM)
0923: || policy == APPLICATOR) {
0924: Class enumType = new ClassInstance(name, def)
0925: .asClass(classProvider);
0926:
0927: String enumName = new String(data, "UTF-8");
0928: return enumValueOf(enumType, enumName);
0929: } else {
0930: ClassInstance clazzInstance = new ClassInstance(name, def);
0931: UTF8ByteDataHolder enumName = new UTF8ByteDataHolder(data);
0932: return new EnumInstance(clazzInstance, enumName);
0933: }
0934: }
0935:
0936: private Object readClassLoader(TCDataInput input, byte type)
0937: throws IOException {
0938: UTF8ByteDataHolder def = new UTF8ByteDataHolder(
0939: readByteArray(input));
0940:
0941: if ((policy == SERIALIZER && type == TYPE_ID_JAVA_LANG_CLASSLOADER)
0942: || policy == APPLICATOR) {
0943: return new ClassLoaderInstance(def)
0944: .asClassLoader(classProvider);
0945: } else {
0946: return new ClassLoaderInstance(def);
0947: }
0948: }
0949:
0950: private Object readClass(TCDataInput input, byte type)
0951: throws IOException, ClassNotFoundException {
0952: UTF8ByteDataHolder name = new UTF8ByteDataHolder(
0953: readByteArray(input));
0954: UTF8ByteDataHolder def = new UTF8ByteDataHolder(
0955: readByteArray(input));
0956:
0957: if ((policy == SERIALIZER && type == TYPE_ID_JAVA_LANG_CLASS)
0958: || policy == APPLICATOR) {
0959: return new ClassInstance(name, def).asClass(classProvider);
0960: } else {
0961: return new ClassInstance(name, def);
0962: }
0963: }
0964:
0965: private Object readString(TCDataInput input, byte type)
0966: throws IOException {
0967: byte[] data = readByteArray(input);
0968: if ((policy == SERIALIZER && type == TYPE_ID_STRING)
0969: || policy == APPLICATOR) {
0970: return new String(data, "UTF-8");
0971: } else {
0972: return new UTF8ByteDataHolder(data);
0973: }
0974: }
0975:
0976: private Object readCompressedString(TCDataInput input)
0977: throws IOException {
0978: int stringLength = input.readInt();
0979: byte[] data = readByteArray(input);
0980: if (policy == APPLICATOR) {
0981: return inflateCompressedString(data, stringLength);
0982: } else {
0983: UTF8ByteDataHolder utfBytes = new UTF8ByteDataHolder(data,
0984: stringLength);
0985: return utfBytes;
0986: }
0987: }
0988:
0989: public static String inflateCompressedString(byte[] data, int length) {
0990: try {
0991: ByteArrayInputStream bais = new ByteArrayInputStream(data);
0992: InflaterInputStream iis = new InflaterInputStream(bais);
0993: byte uncompressed[] = new byte[length];
0994: int read;
0995: int offset = 0;
0996: while (length > 0
0997: && (read = iis.read(uncompressed, offset, length)) != -1) {
0998: offset += read;
0999: length -= read;
1000: }
1001: iis.close();
1002: Assert.assertEquals(0, length);
1003: return new String(uncompressed, "UTF-8");
1004: } catch (IOException e) {
1005: throw new AssertionError(e);
1006: }
1007: }
1008:
1009: private static class FailureClassProvider implements ClassProvider {
1010:
1011: public Class getClassFor(String className, String loaderDesc) {
1012: throw new AssertionError();
1013: }
1014:
1015: public String getLoaderDescriptionFor(Class clazz) {
1016: throw new AssertionError();
1017: }
1018:
1019: public ClassLoader getClassLoader(String loaderDesc) {
1020: throw new AssertionError();
1021: }
1022:
1023: public String getLoaderDescriptionFor(ClassLoader loader) {
1024: throw new AssertionError();
1025: }
1026:
1027: public void registerNamedLoader(NamedClassLoader loader) {
1028: throw new AssertionError();
1029: }
1030: }
1031:
1032: private static class LocalClassProvider implements ClassProvider {
1033:
1034: private static final String LOADER_ID = LocalClassProvider.class
1035: .getName()
1036: + "::CLASSPROVIDER";
1037:
1038: // This method assumes the Class is visible in this VM and can be loaded by the same class loader as this
1039: // object. Only used in SERIALIZER policy
1040: public Class getClassFor(String className, String loaderDesc) {
1041: Assert.assertEquals(LOADER_ID, loaderDesc);
1042: try {
1043: return Class.forName(className);
1044: } catch (ClassNotFoundException e) {
1045: throw new AssertionError(e);
1046: }
1047: }
1048:
1049: public String getLoaderDescriptionFor(Class clazz) {
1050: return LOADER_ID;
1051: }
1052:
1053: public ClassLoader getClassLoader(String loaderDesc) {
1054: Assert.assertEquals(LOADER_ID, loaderDesc);
1055: return ClassLoader.getSystemClassLoader();
1056: }
1057:
1058: public String getLoaderDescriptionFor(ClassLoader loader) {
1059: return LOADER_ID;
1060: }
1061:
1062: public void registerNamedLoader(NamedClassLoader loader) {
1063: // do nothing
1064: }
1065: }
1066:
1067: private static final TObjectIntHashMap primitiveClassMap = new TObjectIntHashMap();
1068:
1069: static {
1070: primitiveClassMap.put(java.lang.Boolean.TYPE, TYPE_ID_BOOLEAN);
1071: primitiveClassMap.put(java.lang.Byte.TYPE, TYPE_ID_BYTE);
1072: primitiveClassMap.put(java.lang.Character.TYPE, TYPE_ID_CHAR);
1073: primitiveClassMap.put(java.lang.Double.TYPE, TYPE_ID_DOUBLE);
1074: primitiveClassMap.put(java.lang.Float.TYPE, TYPE_ID_FLOAT);
1075: primitiveClassMap.put(java.lang.Integer.TYPE, TYPE_ID_INT);
1076: primitiveClassMap.put(java.lang.Long.TYPE, TYPE_ID_LONG);
1077: primitiveClassMap.put(java.lang.Short.TYPE, TYPE_ID_SHORT);
1078: }
1079:
1080: }
|