/**
*
* Copyright 2004-2005 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.interop.rmi.iiop;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.apache.geronimo.interop.SystemException;
import org.apache.geronimo.interop.util.ArrayUtil;
import org.apache.geronimo.interop.util.ExceptionUtil;
import org.apache.geronimo.interop.util.JavaClass;
import org.apache.geronimo.interop.util.JavaType;
import org.apache.geronimo.interop.util.SystemUtil;
import org.apache.geronimo.interop.util.ThreadContext;
import org.omg.CORBA.TCKind;
/**
** A wrapper over java.lang.Class to help improve performance of using
** the Java reflection API for valuetype marshalling. We keep as much
** derived information as possible for optimal performance.
**/
public class ValueType
{
public static ValueType getInstance(Class forClass)
{
ValueType vt = (ValueType)_valueTypeMap.get(forClass);
if (vt == null)
{
synchronized (_valueTypeMap)
{
vt = (ValueType)_valueTypeMap.get(forClass);
if (vt == null)
{
vt = new ValueType();
_valueTypeMap.put(forClass, vt);
vt.init(forClass);
}
}
}
return vt;
}
public static ValueType getInstanceByID(String id)
{
// TODO: handle multiple class loaders???
ValueType vt = (ValueType)_idTypeMap.get(id);
if (vt == null)
{
synchronized (_idTypeMap)
{
vt = (ValueType)_idTypeMap.get(id);
if (vt == null)
{
Class theClass = getClass(id);
vt = getInstance(theClass);
_idTypeMap.put(id, vt);
}
}
}
return vt;
}
// -----------------------------------------------------------------------
// public data
// -----------------------------------------------------------------------
public Class _class;
public org.apache.geronimo.interop.rmi.iiop.ObjectHelper helper;
// -----------------------------------------------------------------------
// private data
// -----------------------------------------------------------------------
private static HashMap _valueTypeMap = new HashMap();
private static HashMap _initMap = new HashMap();
private static HashMap _idTypeMap = new HashMap();
private static final boolean JDK14 = SystemUtil.isJDK14();
private static Method _allocateNewObject;
private static Object[] _allocateNewObjectArgs;
private static Method _newInstance;
private ObjectStreamClass _objectStreamClass;
private Method _readExternal;
private Method _readObject;
private Method _readResolve;
private Method _writeExternal;
private Method _writeObject;
private Method _writeReplace;
// -----------------------------------------------------------------------
// package-private data
// -----------------------------------------------------------------------
static final int NULL_VALUE_TAG = 0;
static final int NO_TYPE_VALUE_TAG = 0x7fffff00;
static final int SINGLE_TYPE_VALUE_TAG = 0x7fffff02;
static final int TRUNCATABLE_NO_TYPE_VALUE_TAG = 0x7fffff08;
static final int TRUNCATABLE_SINGLE_TYPE_VALUE_TAG = 0x7fffff0a;
static final int TYPE_LIST_VALUE_TAG = 0x7fffff06;
static final int INDIRECTION_TAG = 0xffffffff;
static final int CASE_ARRAY = 1;
static final int CASE_CLASS = 2;
static final int CASE_IDL_ENTITY = 3;
static final int CASE_IDL_OBJECT = 4;
static final int CASE_STRING = 5;
static final ValueType OBJECT_VALUE_TYPE = getInstance(Object.class);
static final ValueType STRING_VALUE_TYPE = getInstance(String.class);
static final org.omg.CORBA.TypeCode TC_NULL = new TypeCode(TCKind.tk_null);
static TypeCode TC_ABSTRACT_BASE;
String id; // CORBA Repository ID
TypeCode tc;
ValueType parent;
ValueTypeField[] fields; // just the serializable fields.
ValueType element; // if array, this is ValueType for elements.
boolean hasParentState;
boolean hasReadObject;
boolean hasReadOrWriteObject;
boolean hasWriteObject;
boolean hasReadResolve;
boolean hasWriteReplace;
boolean isAbstractInterface;
boolean isAny;
boolean isAnyOrObjectRefOrAbstractInterface;
boolean isArray;
boolean isExternalizable;
boolean isIDLEntity;
boolean isObjectRef;
int primitiveArray;
int readWriteCase;
boolean requiresCustomSerialization;
boolean skipCustomFlags; // TODO: init this
// -----------------------------------------------------------------------
// static initializer
// -----------------------------------------------------------------------
static
{
TC_ABSTRACT_BASE = new TypeCode(TCKind.tk_abstract_interface);
TC_ABSTRACT_BASE.id("IDL:omg.org/CORBA/AbstractBase:1.0");
TC_ABSTRACT_BASE.name("");
try
{
if (JDK14)
{
_newInstance = java.io.ObjectStreamClass.class.getDeclaredMethod("newInstance", new Class[] {});
_newInstance.setAccessible(true);
}
else
{
_allocateNewObject = java.io.ObjectInputStream.class.getDeclaredMethod("allocateNewObject", new Class[] { Class.class, Class.class });
_allocateNewObject.setAccessible(true);
}
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
// -----------------------------------------------------------------------
// public methods
// -----------------------------------------------------------------------
public Object newInstance()
{
try
{
if (JDK14)
{
if (_class == Object.class)
{
return new Object();
}
else
{
return _newInstance.invoke(_objectStreamClass, ArrayUtil.EMPTY_OBJECT_ARRAY);
}
}
else
{
return _allocateNewObject.invoke(null, _allocateNewObjectArgs);
}
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
public String toString()
{
return "ValueType:" + JavaType.getName(_class);
}
public void readObject(Object _this, org.apache.geronimo.interop.rmi.iiop.ObjectInputStream input)
{
try
{
_readObject.invoke(_this, input.thisAsObjectArray);
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
public void writeObject(Object _this, org.apache.geronimo.interop.rmi.iiop.ObjectOutputStream output)
{
try
{
_writeObject.invoke(_this, output.thisAsObjectArray);
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
public Object readResolve(Object _this)
{
try
{
return _readResolve.invoke(_this, ArrayUtil.EMPTY_OBJECT_ARRAY);
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
public Object writeReplace(Object _this)
{
try
{
return _writeReplace.invoke(_this, ArrayUtil.EMPTY_OBJECT_ARRAY);
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
public void readExternal(Object _this, org.apache.geronimo.interop.rmi.iiop.ObjectInputStream input)
{
try
{
_readExternal.invoke(_this, input.thisAsObjectArray);
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
public void writeExternal(Object _this, org.apache.geronimo.interop.rmi.iiop.ObjectOutputStream output)
{
try
{
_writeExternal.invoke(_this, output.thisAsObjectArray);
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
}
// -----------------------------------------------------------------------
// protected methods
// -----------------------------------------------------------------------
protected void init(Class theClass)
{
boolean recursive = false;
if (_initMap.get(theClass) != null)
{
// recursive = true;
return; // Already initializing (recursive 'init' call).
}
_initMap.put(theClass, Boolean.TRUE);
try
{
_class = theClass;
_objectStreamClass = ObjectStreamClass.lookup(_class);
if (org.omg.CORBA.Object.class.isAssignableFrom(theClass)
|| javax.ejb.EJBHome.class.isAssignableFrom(theClass)
|| javax.ejb.EJBObject.class.isAssignableFrom(theClass)
|| java.rmi.Remote.class.isAssignableFrom(theClass))
{
helper = ObjectRefHelper.getInstance(theClass);
isObjectRef = true;
readWriteCase = CASE_IDL_OBJECT;
}
else if (org.omg.CORBA.portable.IDLEntity.class.isAssignableFrom(theClass))
{
helper = IDLEntityHelper.getInstance(theClass);
isIDLEntity = true;
readWriteCase = CASE_IDL_ENTITY;
}
else if (theClass == String.class)
{
helper = StringHelper.SINGLETON;
readWriteCase = CASE_STRING;
}
else if (theClass.isArray())
{
Class elementClass = theClass.getComponentType();
element = getInstance(elementClass);
isArray = true;
if (elementClass.isPrimitive())
{
primitiveArray = PrimitiveType.get(elementClass);
helper = PrimitiveType.getArrayHelper(elementClass);
}
else
{
helper = new ArrayHelper(elementClass);
}
readWriteCase = CASE_ARRAY;
}
else if (theClass == Class.class)
{
readWriteCase = CASE_CLASS;
}
if (_allocateNewObject != null)
{
Class bc = _class;
while (Serializable.class.isAssignableFrom(bc) && (bc.getSuperclass() != null))
{
bc = bc.getSuperclass();
}
_allocateNewObjectArgs = new Object[] { _class, bc };
}
isAny = _class == java.lang.Object.class
|| _class == java.io.Externalizable.class
|| _class == java.io.Serializable.class;
isExternalizable = java.io.Externalizable.class.isAssignableFrom(_class);
if (isExternalizable)
{
_readExternal = _class.getDeclaredMethod("readExternal", new Class[] { ObjectInput.class } );
_writeExternal = _class.getDeclaredMethod("writeExternal", new Class[] { ObjectOutput.class } );
}
// SG: Hopefully we got all the info that is needed
if(recursive)
{
return;
}
java.lang.Class tmpClass = _class;
ArrayList fieldList = new ArrayList();
Field[] javaFields = tmpClass.getDeclaredFields();
// TODO: suppress sort for IDL-generated valuetypes
Arrays.sort(javaFields, FieldComparator.SINGLETON);
// Create vector of non-static, non-transient fields.
// Ensure that all fields are readable/writable using reflection.
int nf = javaFields.length;
for (int f = 0; f < nf; f++)
{
Field javaField = javaFields[f];
int modifiers = javaField.getModifiers();
if ((modifiers & (Modifier.STATIC | Modifier.TRANSIENT)) != 0)
{
continue;
}
if (! javaField.isAccessible())
{
javaField.setAccessible(true);
}
ValueTypeField field = new ValueTypeField(javaField);
fieldList.add(field);
}
fields = (ValueTypeField[])fieldList.toArray(new ValueTypeField[fieldList.size()]);
// Check methods for readObject/writeObject. Also check for
// abstract interfaces.
Method[] methods = _class.getDeclaredMethods();
int countThrowsRemoteException = 0;
int nm = methods.length;
for (int m = 0; m < nm; m++)
{
Method method = methods[m];
Class[] types = method.getParameterTypes();
if (types.length == 1
&& types[0] == java.io.ObjectInputStream.class
&& (method.getModifiers() & Modifier.PRIVATE) != 0
&& method.getName().equals("readObject"))
{
_readObject = method;
if (! _readObject.isAccessible())
{
_readObject.setAccessible(true);
}
}
if (types.length == 1
&& types[0] == java.io.ObjectOutputStream.class
&& (method.getModifiers() & Modifier.PRIVATE) != 0
&& method.getName().equals("writeObject"))
{
_writeObject = method;
if (! _writeObject.isAccessible())
{
_writeObject.setAccessible(true);
}
}
if (types.length == 0
&& method.getReturnType() == java.lang.Object.class
&& method.getName().equals("writeReplace"))
{
_writeReplace = method;
if (! _writeReplace.isAccessible())
{
_writeReplace.setAccessible(true);
}
}
if (types.length == 0
&& method.getReturnType() == java.lang.Object.class
&& method.getName().equals("readResolve"))
{
_readResolve = method;
if (! _readResolve.isAccessible())
{
_readResolve.setAccessible(true);
}
}
Class[] exceptions = method.getExceptionTypes();
for (int i = 0; i < exceptions.length; i++)
{
Class exception = exceptions[i];
if (exception.isAssignableFrom(java.rmi.RemoteException.class))
{
// TODO: check Java to IDL wording for this
countThrowsRemoteException++;
break;
}
}
}
hasReadOrWriteObject = _readObject != null || _writeObject != null;
hasReadObject = _readObject != null;
hasWriteObject = _writeObject != null;
hasWriteReplace = _writeReplace != null;
hasReadResolve = _readResolve != null;
isAbstractInterface = ! isObjectRef
&& _class.isInterface()
&& countThrowsRemoteException == methods.length;
Class superclass = _class.getSuperclass();
if((superclass != null) && (superclass != java.lang.Object.class) && (!isIDLEntity ))
{
parent = getInstance(superclass);
}
hasParentState = parent != null
&& (parent.fields.length > 0
|| parent.isExternalizable
|| parent.hasReadOrWriteObject
|| parent.hasParentState);
requiresCustomSerialization = hasWriteObject || isExternalizable;
initRepositoryID();
initTypeCode();
isAnyOrObjectRefOrAbstractInterface = isAny || isObjectRef || isAbstractInterface;
}
catch (Exception ex)
{
throw ExceptionUtil.getRuntimeException(ex);
}
finally
{
if(!recursive)
{
_initMap.remove(theClass);
}
}
}
protected void initRepositoryID()
{
final String sixteenZeros = "0000000000000000";
final int requiredLength = 16;
/* if (isAny)
{
id = "#ANY-TODO#";
return;
}*/
if (isArray && primitiveArray != 0)
{
id = "RMI:" + _class.getName() + ":" + sixteenZeros;
return;
}
if (_class == String.class)
{
id = "IDL:omg.org/CORBA/WStringValue:1.0";
return;
}
if (isObjectRef)
{
id = "RMI:" + _class.getName() + ":" + sixteenZeros;
return;
}
if (_class == java.lang.Class.class)
{
id = "RMI:javax.rmi.CORBA.ClassDesc:2BABDA04587ADCCC:CFBF02CF5294176B";
return;
}
if (_class == java.math.BigInteger.class)
{
id = "RMI:java.math.BigInteger:E2F79B6E7A470003:8CFC9F1FA93BFB1D";
return;
}
if (_objectStreamClass == null)
{
id = "???";
return;
}
long structuralUID = computeStructuralUID(this);
long serialVersionUID = _objectStreamClass.getSerialVersionUID();
String structuralUIDString = Long.toHexString(structuralUID).toUpperCase();
String serialVersionUIDString = Long.toHexString(serialVersionUID).toUpperCase();
int currentLength;
int lengthNeeded;
currentLength = structuralUIDString.length();
if (currentLength < requiredLength)
{
lengthNeeded = requiredLength - currentLength;
structuralUIDString = sixteenZeros.substring(0, lengthNeeded) + structuralUIDString;
}
currentLength = serialVersionUIDString.length();
if (currentLength < requiredLength)
{
lengthNeeded = requiredLength - currentLength;
serialVersionUIDString = sixteenZeros.substring(0, lengthNeeded) + serialVersionUIDString;
}
id = "RMI:" + _class.getName() + ":" + structuralUIDString + ":" + serialVersionUIDString;
}
protected void initTypeCode()
{
if (isObjectRef)
{
tc = new TypeCode(TCKind.tk_objref);
tc.id(id);
tc.name("");
}
else if (isArray || isIDLEntity || _class == String.class)
{
tc = new TypeCode(TCKind.tk_value_box);
tc.id(id);
tc.name("");
if (_class == String.class)
{
tc.content_type(new TypeCode(TCKind.tk_wstring));
}
else if (isArray)
{
TypeCode seqTC = new TypeCode(TCKind.tk_sequence);
if (primitiveArray != 0)
{
seqTC.content_type(PrimitiveType.getTypeCode(primitiveArray));
}
else
{
seqTC.content_type(element.tc);
}
tc.content_type(seqTC);
}
else if (isIDLEntity)
{
// TODO tc.content_type(helper.type());
}
}
else
{
tc = new TypeCode(TCKind.tk_value);
tc.id(id);
tc.name("");
// TODO: value modifier
if (requiresCustomSerialization)
{
tc.type_modifier((short)1);
}
else if (isAbstractInterface)
{
tc.type_modifier((short)2);
}
else
{
tc.type_modifier((short)0);
}
if (parent == null)
{
tc.concrete_base_type(TC_NULL);
}
else
{
// TODO: check validity of this
tc.concrete_base_type(TC_NULL);
// tc.concrete_base_type(getTypeCode(parent));
}
// TODO: member fields
tc.member_count(0);
}
}
static long computeStructuralUID(ValueType vt)
{
Class c = vt._class;
ObjectStreamClass osc = vt._objectStreamClass;
ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);
long h = 0;
try
{
if (! java.io.Serializable.class.isAssignableFrom(c)
|| c.isInterface())
{
return 0;
}
if (java.io.Externalizable.class.isAssignableFrom(c))
{
return 1;
}
MessageDigest md = MessageDigest.getInstance("SHA");
DigestOutputStream mdo = new DigestOutputStream(devnull, md);
DataOutputStream data = new DataOutputStream(mdo);
if (vt.parent != null)
{
data.writeLong(computeStructuralUID(vt.parent));
}
if (vt.hasWriteObject)
{
data.writeInt(2);
}
else
{
data.writeInt(1);
}
List fieldList = new ArrayList(vt.fields.length);
for (int i = 0; i < vt.fields.length; i++)
{
fieldList.add(vt.fields[i].javaField);
}
Field[] fields = (Field[])fieldList.toArray(new Field[fieldList.size()]);
Arrays.sort(fields, FieldByNameComparator.SINGLETON);
for (int i = 0; i < vt.fields.length; i++)
{
Field f = fields[i];
data.writeUTF(f.getName());
data.writeUTF(JavaClass.getSignature(f.getType()));
}
data.flush();
byte[] hasharray = md.digest();
for (int i = 0; i < Math.min(8, hasharray.length); i++)
{
h += (long)(hasharray[i] & 255) << (i * 8);
}
return h;
}
catch (Exception ex)
{
throw new SystemException(ex);
}
}
/**
** Map an RMI/IDL Repository ID to a java.lang.Class.
**/
static Class getClass(String id)
{
if (id.startsWith("RMI:"))
{
int endClass = id.indexOf(':', 4);
if (endClass == -1)
{
throw new org.omg.CORBA.INV_IDENT(id);
}
String className = id.substring(4, endClass);
if (className.equals("javax.rmi.CORBA.ClassDesc"))
{
return Class.class;
}
else
{
return loadClass(className);
}
}
else if (id.equals("IDL:omg.org/CORBA/WStringValue:1.0"))
{
return java.lang.String.class;
}
else if (id.startsWith("IDL:omg.org/"))
{
int endClass = id.indexOf(':', 12);
if (endClass == -1)
{
throw new org.omg.CORBA.INV_IDENT(id);
}
String className = "org.omg" + id.substring( "IDL:omg.org".length(), endClass).replace('/', '.');
return loadClass(className);
}
else if (id.startsWith("IDL:"))
{
int endClass = id.indexOf(':', 4);
if (endClass == -1)
{
throw new org.omg.CORBA.INV_IDENT(id);
}
String className = id.substring(4, endClass).replace('/', '.');
return loadClass(className);
}
else
{
throw new org.omg.CORBA.INV_IDENT(id);
}
}
public Class getTheClass()
{
return _class;
}
static Class loadClass(String className)
{
return ThreadContext.loadClass(className);
}
}