/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.tuscany.sca.binding.ejb.java2idl;
import java.io.Externalizable;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
/**
* Common base class of ValueType and InterfaceType.
*/
public abstract class ContainerType extends ClassType {
/** Flags a method as overloaded. */
protected final byte M_OVERLOADED = 1;
/** Flags a method as the accessor of a read-write property. */
protected final byte M_READ = 2;
/** Flags a method as the mutator of a read-write property. */
protected final byte M_WRITE = 4;
/** Flags a method as the accessor of a read-only property. */
protected final byte M_READONLY = 8;
/** Flags a method as being inherited. */
protected final byte M_INHERITED = 16;
/**
* Flags a method as being the writeObject() method used for serialization.
*/
protected final byte M_WRITEOBJECT = 32;
/** Flags a field as being a constant (public final static). */
protected final byte F_CONSTANT = 1;
/**
* Flags a field as being the special public final static
* java.io.ObjectStreamField[] serialPersistentFields
* field.
*/
protected final byte F_SPFFIELD = 2;
/**
* Array of all java methods.
*/
protected Method[] methods;
/**
* Array with flags for all java methods.
*/
protected byte[] m_flags;
/**
* Index of the mutator for read-write attributes. Only entries
* i
where (m_flags[i]&M_READ) != 0
are used.
* These entries contain the index of the mutator method corresponding to
* the accessor method.
*/
protected int[] setters;
/**
* Array of all java fields.
*/
protected Field[] fields;
/**
* Array with flags for all java fields.
*/
protected byte[] f_flags;
/**
* The class hash code, as specified in "The Common Object Request Broker:
* Architecture and Specification" (01-02-33), section 10.6.2.
*/
protected long classHashCode = 0;
/**
* The repository ID. This is in the RMI hashed format, like
* "RMI:java.util.Hashtable:C03324C0EA357270:13BB0F25214AE4B8".
*/
protected String repositoryId;
/**
* The prefix and postfix of members repository ID. These are used to
* calculate member repository IDs and are like "RMI:java.util.Hashtable."
* and ":C03324C0EA357270:13BB0F25214AE4B8".
*/
protected String memberPrefix, memberPostfix;
/**
* Array of type of the interfaces implemented/extended here.
*/
protected InterfaceType[] interfaces;
/**
* Array of type of the abstract base valuetypes implemented/extended here.
*/
protected ValueType[] abstractBaseValuetypes;
/**
* Array of attributes.
*/
protected AttributeType[] attributes;
/**
* Array of Constants.
*/
protected ConstantType[] constants;
/**
* Array of operations.
*/
protected OperationType[] operations;
/**
* A cache for the fully qualified IDL name of the IDL module we belong to.
*/
private String idlModuleName = null;
protected ContainerType(Class cls) {
super(cls);
if (cls == java.lang.Object.class || cls == java.io.Serializable.class || cls == java.io.Externalizable.class)
throw new IllegalArgumentException("Cannot parse special class: " + cls.getName());
this.javaClass = cls;
}
protected void parse() {
parseInterfaces();
parseMethods();
parseFields();
calculateClassHashCode();
calculateRepositoryId();
parseAttributes();
parseConstants();
parseOperations();
fixupOverloadedOperationNames();
}
/**
* Return the interfaces.
*/
public InterfaceType[] getInterfaces() {
return (InterfaceType[])interfaces.clone();
}
/**
* Return the abstract base valuetypes.
*/
public ValueType[] getAbstractBaseValuetypes() {
return (ValueType[])abstractBaseValuetypes.clone();
}
/**
* Return the attributes.
*/
public AttributeType[] getAttributes() {
return (AttributeType[])attributes.clone();
}
/**
* Return the constants.
*/
public ConstantType[] getConstants() {
return (ConstantType[])constants.clone();
}
/**
* Return the operations.
*/
public OperationType[] getOperations() {
return (OperationType[])operations.clone();
}
/**
* Return the repository ID.
*/
public String getRepositoryId() {
return repositoryId;
}
/**
* Return a repository ID for a member.
*
* @param memberName The Java name of the member.
*/
public String getMemberRepositoryId(String memberName) {
return memberPrefix + escapeIRName(memberName) + memberPostfix;
}
/**
* Return the fully qualified IDL module name that this type should be
* placed in.
*/
public String getIDLModuleName() {
if (idlModuleName == null) {
String pkgName = javaClass.getPackage().getName();
StringBuffer b = new StringBuffer();
while (!"".equals(pkgName)) {
int idx = pkgName.indexOf('.');
String n = (idx == -1) ? pkgName : pkgName.substring(0, idx);
b.append("::").append(IDLUtil.javaToIDLName(n));
pkgName = (idx == -1) ? "" : pkgName.substring(idx + 1);
}
idlModuleName = b.toString();
}
return idlModuleName;
}
// Protected -----------------------------------------------------
/**
* Convert an integer to a 16-digit hex string.
*/
protected String toHexString(int i) {
String s = Integer.toHexString(i).toUpperCase();
if (s.length() < 8)
return "00000000".substring(0, 8 - s.length()) + s;
else
return s;
}
/**
* Convert a long to a 16-digit hex string.
*/
protected String toHexString(long l) {
String s = Long.toHexString(l).toUpperCase();
if (s.length() < 16)
return "0000000000000000".substring(0, 16 - s.length()) + s;
else
return s;
}
/**
* Check if a method is an accessor.
*/
protected boolean isReadMethod(Method m) {
Class returnType = m.getReturnType();
if (!m.getName().startsWith("get"))
if (!m.getName().startsWith("is") || !(returnType == boolean.class))
return false;
if (returnType == void.class)
return false;
if (m.getParameterTypes().length != 0)
return false;
return hasNonAppExceptions(m);
}
/**
* Check if a method is a mutator.
*/
protected boolean isWriteMethod(Method m) {
if (!m.getName().startsWith("set"))
return false;
if (m.getReturnType() != void.class)
return false;
if (m.getParameterTypes().length != 1)
return false;
return hasNonAppExceptions(m);
}
private static boolean isCheckedException(Class type) {
/*
* Is a checked exception
*/
if (!Throwable.class.isAssignableFrom(type))
return false;
if (Error.class.isAssignableFrom(type))
return false;
if (RuntimeException.class.isAssignableFrom(type))
return false;
return true;
}
/**
* Check if a method throws anything checked other than
* java.rmi.RemoteException and its subclasses.
*/
protected boolean hasNonAppExceptions(Method m) {
Class[] ex = m.getExceptionTypes();
for (int i = 0; i < ex.length; ++i) {
if (!isCheckedException(ex[i]))
continue;
if (!RemoteException.class.isAssignableFrom(ex[i]))
return false;
}
return true;
}
/**
* Analyse the fields of the class. This will fill in the
* fields
and f_flags
arrays.
*/
protected void parseFields() {
fields = javaClass.getDeclaredFields();
f_flags = new byte[fields.length];
for (int i = 0; i < fields.length; ++i) {
int mods = fields[i].getModifiers();
if (Modifier.isFinal(mods) && Modifier.isStatic(mods) && Modifier.isPublic(mods))
f_flags[i] |= F_CONSTANT;
}
}
/**
* Analyse the interfaces of the class. This will fill in the
* interfaces
array.
*/
protected void parseInterfaces() {
Class[] intfs = javaClass.getInterfaces();
ArrayList a = new ArrayList();
ArrayList b = new ArrayList();
for (int i = 0; i < intfs.length; ++i) {
// Ignore java.rmi.Remote
if (intfs[i] == java.rmi.Remote.class)
continue;
// Ignore java.io.Serializable
if (intfs[i] == java.io.Serializable.class)
continue;
// Ignore java.io.Externalizable
if (intfs[i] == java.io.Externalizable.class)
continue;
if (!Java2IDLUtil.isAbstractValueType(intfs[i])) {
a.add(InterfaceType.getInterfaceType(intfs[i]));
} else {
b.add(ValueType.getValueType(intfs[i]));
}
}
interfaces = new InterfaceType[a.size()];
interfaces = (InterfaceType[])a.toArray(interfaces);
abstractBaseValuetypes = new ValueType[b.size()];
abstractBaseValuetypes = (ValueType[])b.toArray(abstractBaseValuetypes);
}
/**
* Analyse the methods of the class. This will fill in the
* methods
and m_flags
arrays.
*/
protected void parseMethods() {
// The dynamic stub and skeleton strategy generation mechanism
// requires the inclusion of inherited methods in the type of
// remote interfaces. To speed things up, inherited methods are
// not considered in the type of a class or non-remote interface.
if (javaClass.isInterface() && java.rmi.Remote.class.isAssignableFrom(javaClass))
methods = javaClass.getMethods();
else
methods = javaClass.getDeclaredMethods();
m_flags = new byte[methods.length];
setters = new int[methods.length];
// Find read-write properties
for (int i = 0; i < methods.length; ++i)
setters[i] = -1; // no mutator here
for (int i = 0; i < methods.length; ++i) {
if (isReadMethod(methods[i]) && (m_flags[i] & M_READ) == 0) {
String attrName = attributeReadName(methods[i].getName());
Class iReturn = methods[i].getReturnType();
for (int j = i + 1; j < methods.length; ++j) {
if (isWriteMethod(methods[j]) && (m_flags[j] & M_WRITE) == 0
&& attrName.equals(attributeWriteName(methods[j].getName()))) {
Class[] jParams = methods[j].getParameterTypes();
if (jParams.length == 1 && jParams[0] == iReturn) {
m_flags[i] |= M_READ;
m_flags[j] |= M_WRITE;
setters[i] = j;
break;
}
}
}
} else if (isWriteMethod(methods[i]) && (m_flags[i] & M_WRITE) == 0) {
String attrName = attributeWriteName(methods[i].getName());
Class[] iParams = methods[i].getParameterTypes();
for (int j = i + 1; j < methods.length; ++j) {
if (isReadMethod(methods[j]) && (m_flags[j] & M_READ) == 0
&& attrName.equals(attributeReadName(methods[j].getName()))) {
Class jReturn = methods[j].getReturnType();
if (iParams.length == 1 && iParams[0] == jReturn) {
m_flags[i] |= M_WRITE;
m_flags[j] |= M_READ;
setters[j] = i;
break;
}
}
}
}
}
// Find read-only properties
for (int i = 0; i < methods.length; ++i)
if ((m_flags[i] & (M_READ | M_WRITE)) == 0 && isReadMethod(methods[i]))
m_flags[i] |= M_READONLY;
// Check for overloaded and inherited methods
for (int i = 0; i < methods.length; ++i) {
if ((m_flags[i] & (M_READ | M_WRITE | M_READONLY)) == 0) {
String iName = methods[i].getName();
for (int j = i + 1; j < methods.length; ++j) {
if (iName.equals(methods[j].getName())) {
m_flags[i] |= M_OVERLOADED;
m_flags[j] |= M_OVERLOADED;
}
}
}
if (methods[i].getDeclaringClass() != javaClass)
m_flags[i] |= M_INHERITED;
}
}
/**
* Convert an attribute read method name in Java format to an attribute name
* in Java format.
*/
protected String attributeReadName(String name) {
if (name.startsWith("get"))
name = name.substring(3);
else if (name.startsWith("is"))
name = name.substring(2);
else
throw new IllegalArgumentException("Not an accessor: " + name);
return name;
}
/**
* Convert an attribute write method name in Java format to an attribute
* name in Java format.
*/
protected String attributeWriteName(String name) {
if (name.startsWith("set"))
name = name.substring(3);
else
throw new IllegalArgumentException("Not an accessor: " + name);
return name;
}
/**
* Analyse constants. This will fill in the constants
array.
*/
protected void parseConstants() {
ArrayList a = new ArrayList();
for (int i = 0; i < fields.length; ++i) {
if ((f_flags[i] & F_CONSTANT) == 0)
continue;
Class type = fields[i].getType();
// Only map primitives and java.lang.String
if (!type.isPrimitive() && type != java.lang.String.class) {
// It is an RMI/IIOP violation for interfaces.
if (javaClass.isInterface())
throw new IDLViolationException("Field \"" + fields[i].getName()
+ "\" of interface \""
+ javaClass.getName()
+ "\" is a constant, but not of one "
+ "of the primitive types, or String.", "1.2.3");
continue;
}
String name = fields[i].getName();
Object value;
try {
value = fields[i].get(null);
} catch (Exception ex) {
throw new RuntimeException(ex.toString());
}
a.add(new ConstantType(name, type, value));
}
constants = new ConstantType[a.size()];
constants = (ConstantType[])a.toArray(constants);
}
/**
* Analyse attributes. This will fill in the attributes
* array.
*/
protected void parseAttributes() {
ArrayList a = new ArrayList();
for (int i = 0; i < methods.length; ++i) {
// if ((m_flags[i]&M_INHERITED) != 0)
// continue;
if ((m_flags[i] & (M_READ | M_READONLY)) != 0) {
// Read method of an attribute.
String name = attributeReadName(methods[i].getName());
if ((m_flags[i] & M_READONLY) != 0)
a.add(new AttributeType(name, methods[i]));
else
a.add(new AttributeType(name, methods[i], methods[setters[i]]));
}
}
attributes = new AttributeType[a.size()];
attributes = (AttributeType[])a.toArray(attributes);
}
/**
* Analyse operations. This will fill in the operations
* array. This implementation just creates an empty array; override in
* subclasses for a real type.
*/
protected void parseOperations() {
operations = new OperationType[0];
}
/**
* Fixup overloaded operation names. As specified in section 1.3.2.6.
*/
protected void fixupOverloadedOperationNames() {
for (int i = 0; i < methods.length; ++i) {
if ((m_flags[i] & M_OVERLOADED) == 0)
continue;
// Find the operation
OperationType oa = null;
for (int opIdx = 0; oa == null && opIdx < operations.length; ++opIdx)
if (operations[opIdx].getMethod().equals(methods[i]))
oa = operations[opIdx];
if (oa == null)
continue; // This method is not mapped.
// Calculate new IDL name
ParameterType[] parms = oa.getParameters();
StringBuffer b = new StringBuffer(oa.getIDLName());
if (parms.length == 0)
b.append("__");
for (int j = 0; j < parms.length; ++j) {
String s = parms[j].getTypeIDLName();
if (s.startsWith("::"))
s = s.substring(2);
b.append('_');
while (!"".equals(s)) {
int idx = s.indexOf("::");
b.append('_');
if (idx == -1) {
b.append(s);
s = "";
} else {
b.append(s.substring(0, idx));
s = s.substring(idx + 2);
}
}
}
// Set new IDL name
oa.setIDLName(b.toString());
}
}
/**
* Fixup names differing only in case. As specified in section 1.3.2.7.
*/
protected void fixupCaseNames() {
ArrayList entries = getContainedEntries();
boolean[] clash = new boolean[entries.size()];
String[] upperNames = new String[entries.size()];
for (int i = 0; i < entries.size(); ++i) {
IDLType aa = (IDLType)entries.get(i);
clash[i] = false;
upperNames[i] = aa.getIDLName().toUpperCase();
for (int j = 0; j < i; ++j) {
if (upperNames[i].equals(upperNames[j])) {
clash[i] = true;
clash[j] = true;
}
}
}
for (int i = 0; i < entries.size(); ++i) {
if (!clash[i])
continue;
IDLType aa = (IDLType)entries.get(i);
boolean noUpper = true;
String name = aa.getIDLName();
StringBuffer b = new StringBuffer(name);
b.append('_');
for (int j = 0; j < name.length(); ++j) {
if (!Character.isUpperCase(name.charAt(j)))
continue;
if (noUpper)
noUpper = false;
else
b.append('_');
b.append(j);
}
aa.setIDLName(b.toString());
}
}
/**
* Return a list of all the entries contained here.
*
* @param entries The list of entries contained here. Entries in this list
* must be subclasses of AbstractType
.
*/
abstract protected ArrayList getContainedEntries();
/**
* Return the class hash code, as specified in "The Common Object Request
* Broker: Architecture and Specification" (01-02-33), section 10.6.2.
*/
protected void calculateClassHashCode() {
// The simple cases
if (javaClass.isInterface())
classHashCode = 0;
else if (!Serializable.class.isAssignableFrom(javaClass))
classHashCode = 0;
else if (Externalizable.class.isAssignableFrom(javaClass))
classHashCode = 1;
else
// Go ask Util class for the hash code
classHashCode = IDLUtil.getClassHashCode(javaClass);
}
/**
* Escape non-ISO characters for an IR name.
*/
protected String escapeIRName(String name) {
StringBuffer b = new StringBuffer();
for (int i = 0; i < name.length(); ++i) {
char c = name.charAt(i);
if (c < 256)
b.append(c);
else
b.append("\\U").append(toHexString((int)c));
}
return b.toString();
}
/**
* Return the IR global ID of the given class or interface. This is
* described in section 1.3.5.7. The returned string is in the RMI hashed
* format, like "RMI:java.util.Hashtable:C03324C0EA357270:13BB0F25214AE4B8".
*/
protected void calculateRepositoryId() {
if (javaClass.isArray() || javaClass.isPrimitive())
throw new IllegalArgumentException("Not a class or interface.");
if (javaClass.isInterface() && org.omg.CORBA.Object.class.isAssignableFrom(javaClass)
&& org.omg.CORBA.portable.IDLEntity.class.isAssignableFrom(javaClass)) {
StringBuffer b = new StringBuffer("IDL:");
b.append(javaClass.getPackage().getName().replace('.', '/'));
b.append('/');
String base = javaClass.getName();
base = base.substring(base.lastIndexOf('.') + 1);
b.append(base).append(":1.0");
repositoryId = b.toString();
} else {
StringBuffer b = new StringBuffer("RMI:");
b.append(escapeIRName(javaClass.getName()));
memberPrefix = b.toString() + ".";
String hashStr = toHexString(classHashCode);
b.append(':').append(hashStr);
ObjectStreamClass osClass = ObjectStreamClass.lookup(javaClass);
if (osClass != null) {
long serialVersionUID = osClass.getSerialVersionUID();
String SVUID = toHexString(serialVersionUID);
if (classHashCode != serialVersionUID)
b.append(':').append(SVUID);
memberPostfix = ":" + hashStr + ":" + SVUID;
} else
memberPostfix = ":" + hashStr;
repositoryId = b.toString();
}
}
/**
* A simple aggregate of work-in-progress, and the thread doing the work.
*/
private static class Task {
ContainerType type;
Thread thread;
Task(ContainerType type, Thread thread) {
this.type = type;
this.thread = thread;
}
}
/**
* Instances of this class cache the most complex types. The types cached
* are:
*
InterfaceType
for interfaces.ValueType
for value types.ExceptionType
for exceptions.getType()
metohd, an unfinished type will be
* returned if the same thread is already working on this type.
*/
protected static class WorkCache {
/**
* The type constructor of our type class. This constructor takes a
* single argument of type Class
.
*/
private Constructor constructor;
/**
* This maps the classes of completely done parses to soft references of
* their type.
*/
private Map workDone;
/**
* This maps the classes of parses in progress to their type.
*/
private Map workInProgress;
/**
* Create a new work cache manager.
*
* @param containerType The class of the type type we cache here.
*/
WorkCache(Class containerType) {
// Find the constructor and initializer.
try {
constructor = containerType.getDeclaredConstructor(new Class[] {Class.class});
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Bad Class: " + ex.toString());
}
workDone = new WeakHashMap();
workInProgress = new HashMap();
}
/**
* Returns an type. If the calling thread is currently doing an type of
* this class, an unfinished type is returned.
*/
ContainerType getType(Class cls) {
ContainerType ret;
synchronized (this) {
ret = lookupDone(cls);
if (ret != null)
return ret;
// is it work-in-progress?
Task inProgress = (Task)workInProgress.get(cls);
if (inProgress != null) {
if (inProgress.thread == Thread.currentThread())
return inProgress.type; // return unfinished
// Do not wait for the other thread: We may deadlock
// Double work is better than deadlock...
}
ret = createTask(cls);
}
// Do the work
parse(cls, ret);
// We did it
synchronized (this) {
workInProgress.remove(cls);
workDone.put(cls, new SoftReference(ret));
notifyAll();
}
return ret;
}
/**
* Lookup an type in the fully done map.
*/
private ContainerType lookupDone(Class cls) {
SoftReference ref = (SoftReference)workDone.get(cls);
if (ref == null)
return null;
ContainerType ret = (ContainerType)ref.get();
if (ret == null)
workDone.remove(cls); // clear map entry if soft ref. was
// cleared.
return ret;
}
/**
* Create new work-in-progress.
*/
private ContainerType createTask(Class cls) {
try {
ContainerType type = (ContainerType)constructor.newInstance(new Object[] {cls});
workInProgress.put(cls, new Task(type, Thread.currentThread()));
return type;
} catch (Exception ex) {
// Ignore it
return null;
}
}
private void parse(Class cls, ContainerType ret) {
try {
ret.parse();
} finally {
synchronized (this) {
workInProgress.remove(cls);
}
}
}
}
}