/* * 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.corba.provider.util; import java.lang.reflect.Method; import java.rmi.RemoteException; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.tuscany.sca.interfacedef.DataType; import org.apache.tuscany.sca.interfacedef.Operation; import org.omg.CORBA.portable.IDLEntity; /** * @version $Rev$ $Date$ */ public final class OperationMapper { private static Set<Class<?>> getAllInterfaces(Class<?> intfClass) { Set<Class<?>> allInterfaces = new LinkedHashSet<Class<?>>(); LinkedList<Class<?>> stack = new LinkedList<Class<?>>(); stack.addFirst(intfClass); while (!stack.isEmpty()) { Class<?> intf = stack.removeFirst(); allInterfaces.add(intf); for (Class<?> i : intf.getInterfaces()) { stack.add(0, i); } } return allInterfaces; } /** * Maps Java methods to operation names * @param intfClass * @return */ @SuppressWarnings("unchecked") public static Map<Method, String> mapMethodToOperationName(Class<?> intfClass) { return iiopMap(intfClass, false); } /** * Maps operation names to Java methods * @param intfClass * @return */ @SuppressWarnings("unchecked") public static Map<String, Method> mapOperationNameToMethod(Class<?> intfClass) { return iiopMap(intfClass, true); } @SuppressWarnings("unchecked") private static Map iiopMap(Class<?> intfClass, boolean operationToMethod) { Method[] methods = getAllMethods(intfClass); // find every valid getter Map<Method, String> getterByMethod = new HashMap<Method, String>(methods.length); Map<String, Method> getterByName = new HashMap<String, Method>(methods.length); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; String methodName = method.getName(); // no arguments allowed if (method.getParameterTypes().length != 0) { continue; } // must start with get or is String verb; if (methodName.startsWith("get") && methodName.length() > 3 && method.getReturnType() != void.class) { verb = "get"; } else if (methodName.startsWith("is") && methodName.length() > 2 && method.getReturnType() == boolean.class) { verb = "is"; } else { continue; } // must only throw Remote or Runtime Exceptions boolean exceptionsValid = true; Class[] exceptionTypes = method.getExceptionTypes(); for (int j = 0; j < exceptionTypes.length; j++) { Class<?> exceptionType = exceptionTypes[j]; if (!RemoteException.class.isAssignableFrom(exceptionType) && !RuntimeException.class.isAssignableFrom(exceptionType) && !Error.class.isAssignableFrom(exceptionType)) { exceptionsValid = false; break; } } if (!exceptionsValid) { continue; } String propertyName; if (methodName.length() > verb.length() + 1 && Character.isUpperCase(methodName.charAt(verb.length() + 1))) { propertyName = methodName.substring(verb.length()); } else { propertyName = Character.toLowerCase(methodName.charAt(verb.length())) + methodName.substring(verb.length() + 1); } getterByMethod.put(method, propertyName); getterByName.put(propertyName, method); } Map<Method, String> setterByMethod = new HashMap<Method, String>(methods.length); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; String methodName = method.getName(); // must have exactally one arg if (method.getParameterTypes().length != 1) { continue; } // must return non void if (method.getReturnType() != void.class) { continue; } // must start with set if (!methodName.startsWith("set") || methodName.length() <= 3) { continue; } // must only throw Remote or Runtime Exceptions boolean exceptionsValid = true; Class<?>[] exceptionTypes = method.getExceptionTypes(); for (int j = 0; j < exceptionTypes.length; j++) { Class<?> exceptionType = exceptionTypes[j]; if (!RemoteException.class.isAssignableFrom(exceptionType) && !RuntimeException.class.isAssignableFrom(exceptionType) && !Error.class.isAssignableFrom(exceptionType)) { exceptionsValid = false; break; } } if (!exceptionsValid) { continue; } String propertyName; if (methodName.length() > 4 && Character.isUpperCase(methodName.charAt(4))) { propertyName = methodName.substring(3); } else { propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); } // must have a matching getter Method getter = (Method) getterByName.get(propertyName); if (getter == null) { continue; } // setter property must match getter return value if (!method.getParameterTypes()[0].equals(getter.getReturnType())) { continue; } setterByMethod.put(method, propertyName); } // index the methods by name... used to determine which methods are overloaded HashMap<String, List<Method>> overloadedMethods = new HashMap<String, List<Method>>(methods.length); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if (getterByMethod.containsKey(method) || setterByMethod.containsKey(method)) { continue; } String methodName = method.getName(); List<Method> methodList = overloadedMethods.get(methodName); if (methodList == null) { methodList = new LinkedList<Method>(); overloadedMethods.put(methodName, methodList); } methodList.add(method); } // index the methods by lower case name... used to determine which methods differ only by case Map<String, Set<String>> caseCollisionMethods = new HashMap<String, Set<String>>(methods.length); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if (getterByMethod.containsKey(method) || setterByMethod.containsKey(method)) { continue; } String lowerCaseMethodName = method.getName().toLowerCase(); Set<String> methodSet = caseCollisionMethods.get(lowerCaseMethodName); if (methodSet == null) { methodSet = new HashSet<String>(); caseCollisionMethods.put(lowerCaseMethodName, methodSet); } methodSet.add(method.getName()); } String className = getClassName(intfClass); Map iiopMap = new HashMap(methods.length); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; String iiopName = (String) getterByMethod.get(method); if (iiopName != null) { // if we have a leading underscore prepend with J if (iiopName.charAt(0) == '_') { iiopName = "J_get_" + iiopName.substring(1); } else { iiopName = "_get_" + iiopName; } } else { iiopName = (String) setterByMethod.get(method); if (iiopName != null) { // if we have a leading underscore prepend with J if (iiopName.charAt(0) == '_') { iiopName = "J_set_" + iiopName.substring(1); } else { iiopName = "_set_" + iiopName; } } else { iiopName = method.getName(); // if we have a leading underscore prepend with J if (iiopName.charAt(0) == '_') { iiopName = "J" + iiopName; } } } // if this name only differs by case add the case index to the end Set<String> caseCollisions = caseCollisionMethods.get(method.getName().toLowerCase()); if (caseCollisions != null && caseCollisions.size() > 1) { iiopName += upperCaseIndexString(iiopName); } // if this is an overloaded method append the parameter string List<Method> overloads = overloadedMethods.get(method.getName()); if (overloads != null && overloads.size() > 1) { iiopName += buildOverloadParameterString(method.getParameterTypes()); } // if we have a leading underscore prepend with J iiopName = replace(iiopName, '$', "U0024"); // if we have matched a keyword prepend with an underscore if (keywords.contains(iiopName.toLowerCase())) { iiopName = "_" + iiopName; } // if the name is the same as the class name, append an underscore if (iiopName.equalsIgnoreCase(className)) { iiopName += "_"; } if (operationToMethod) { iiopMap.put(iiopName, method); } else { iiopMap.put(method, iiopName); } } return iiopMap; } private static Method[] getAllMethods(Class<?> intfClass) { List<Method> methods = new LinkedList<Method>(); for (Iterator<Class<?>> iterator = getAllInterfaces(intfClass).iterator(); iterator.hasNext();) { Class<?> intf = iterator.next(); methods.addAll(Arrays.asList(intf.getDeclaredMethods())); } return (Method[]) methods.toArray(new Method[methods.size()]); } /** * Return the a string containing an underscore '_' index of each uppercase * character in the IIOP name. This is used for distinction of names that * only differ by case, since CORBA does not support case sensitive names. */ private static String upperCaseIndexString(String iiopName) { StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < iiopName.length(); i++) { char c = iiopName.charAt(i); if (Character.isUpperCase(c)) { stringBuffer.append('_').append(i); } } return stringBuffer.toString(); } /** * Replaces any occurances of the specified "oldChar" with the new string. * This is used to replace occurances if '$' in CORBA names since '$' is a * special character */ private static String replace(String source, char oldChar, String newString) { StringBuffer stringBuffer = new StringBuffer(source.length()); for (int i = 0; i < source.length(); i++) { char c = source.charAt(i); if (c == oldChar) { stringBuffer.append(newString); } else { stringBuffer.append(c); } } return stringBuffer.toString(); } /** * Return the a string containing a double underscore '__' list of parameter * types encoded using the Java to IDL rules. This is used for distinction * of methods that only differ by parameter lists. */ private static String buildOverloadParameterString(Class<?>[] parameterTypes) { String name = ""; if (parameterTypes.length == 0) { name += "__"; } else { for (int i = 0; i < parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; name += buildOverloadParameterString(parameterType); } } return name.replace('.', '_'); } /** * Returns a single parameter type encoded using the Java to IDL rules. */ private static String buildOverloadParameterString(Class<?> parameterType) { String name = "_"; int arrayDimensions = 0; while (parameterType.isArray()) { arrayDimensions++; parameterType = parameterType.getComponentType(); } // arrays start with org_omg_boxedRMI_ if (arrayDimensions > 0) { name += "_org_omg_boxedRMI"; } // IDLEntity types must be prefixed with org_omg_boxedIDL_ if (IDLEntity.class.isAssignableFrom(parameterType)) { name += "_org_omg_boxedIDL"; } // add package... some types have special mappings in corba String packageName = (String)specialTypePackages.get(parameterType.getName()); if (packageName == null) { packageName = getPackageName(parameterType.getName()); } if (packageName.length() > 0) { name += "_" + packageName; } // arrays now contain a dimension indicator if (arrayDimensions > 0) { name += "_" + "seq" + arrayDimensions; } // add the class name String className = (String)specialTypeNames.get(parameterType.getName()); if (className == null) { className = buildClassName(parameterType); } name += "_" + className; return name; } /** * Returns a string containing an encoded class name. */ private static String buildClassName(Class<?> type) { if (type.isArray()) { throw new IllegalArgumentException("type is an array: " + type); } // get the classname String typeName = type.getName(); int endIndex = typeName.lastIndexOf('.'); if (endIndex < 0) { return typeName; } StringBuffer className = new StringBuffer(typeName.substring(endIndex + 1)); // for innerclasses replace the $ separator with two underscores // we can't just blindly replace all $ characters since class names can // contain the $ character if (type.getDeclaringClass() != null) { String declaringClassName = getClassName(type.getDeclaringClass()); assert className.toString().startsWith(declaringClassName + "$"); className.replace(declaringClassName.length(), declaringClassName.length() + 1, "__"); } // if we have a leading underscore prepend with J if (className.charAt(0) == '_') { className.insert(0, "J"); } return className.toString(); } private static String getClassName(Class<?> type) { if (type.isArray()) { throw new IllegalArgumentException("type is an array: " + type); } // get the classname String typeName = type.getName(); int endIndex = typeName.lastIndexOf('.'); if (endIndex < 0) { return typeName; } return typeName.substring(endIndex + 1); } private static String getPackageName(String interfaceName) { int endIndex = interfaceName.lastIndexOf('.'); if (endIndex < 0) { return ""; } return interfaceName.substring(0, endIndex); } private static final Map<String, String> specialTypeNames; private static final Map<String, String> specialTypePackages; private static final Set<String> keywords; static { specialTypeNames = new HashMap<String, String>(); specialTypeNames.put("boolean", "boolean"); specialTypeNames.put("char", "wchar"); specialTypeNames.put("byte", "octet"); specialTypeNames.put("short", "short"); specialTypeNames.put("int", "long"); specialTypeNames.put("long", "long_long"); specialTypeNames.put("float", "float"); specialTypeNames.put("double", "double"); specialTypeNames.put("java.lang.Class", "ClassDesc"); specialTypeNames.put("java.lang.String", "WStringValue"); specialTypeNames.put("org.omg.CORBA.Object", "Object"); specialTypePackages = new HashMap<String, String>(); specialTypePackages.put("boolean", ""); specialTypePackages.put("char", ""); specialTypePackages.put("byte", ""); specialTypePackages.put("short", ""); specialTypePackages.put("int", ""); specialTypePackages.put("long", ""); specialTypePackages.put("float", ""); specialTypePackages.put("double", ""); specialTypePackages.put("java.lang.Class", "javax.rmi.CORBA"); specialTypePackages.put("java.lang.String", "CORBA"); specialTypePackages.put("org.omg.CORBA.Object", ""); keywords = new HashSet<String>(); keywords.add("abstract"); keywords.add("any"); keywords.add("attribute"); keywords.add("boolean"); keywords.add("case"); keywords.add("char"); keywords.add("const"); keywords.add("context"); keywords.add("custom"); keywords.add("default"); keywords.add("double"); keywords.add("enum"); keywords.add("exception"); keywords.add("factory"); keywords.add("false"); keywords.add("fixed"); keywords.add("float"); keywords.add("in"); keywords.add("inout"); keywords.add("interface"); keywords.add("long"); keywords.add("module"); keywords.add("native"); keywords.add("object"); keywords.add("octet"); keywords.add("oneway"); keywords.add("out"); keywords.add("private"); keywords.add("public"); keywords.add("raises"); keywords.add("readonly"); keywords.add("sequence"); keywords.add("short"); keywords.add("string"); keywords.add("struct"); keywords.add("supports"); keywords.add("switch"); keywords.add("true"); keywords.add("truncatable"); keywords.add("typedef"); keywords.add("union"); keywords.add("unsigned"); keywords.add("valuebase"); keywords.add("valuetype"); keywords.add("void"); keywords.add("wchar"); keywords.add("wstring"); } @SuppressWarnings("unchecked") public static Map<Operation, Method> mapOperationToMethod(List<Operation> operations, Class<?> forClass) { return (Map<Operation, Method>)createMethod2OperationMapping(operations, forClass, false); } @SuppressWarnings("unchecked") public static Map<Method, Operation> mapMethodToOperation(List<Operation> operations, Class<?> forClass) { return (Map<Method, Operation>)createMethod2OperationMapping(operations, forClass, true); } /** * Maps Java methods to Tuscany operations */ @SuppressWarnings("unchecked") private static Map createMethod2OperationMapping(List<Operation> operations, Class<?> forClass, boolean method2operation) { // for every operation find all methods with the same name, then // compare operations and methods parameters Map mapping = new HashMap(); for (Operation operation : operations) { List<DataType> inputTypes = operation.getInputType().getLogical(); Method[] methods = forClass.getMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].getName().equals(operation.getName()) && inputTypes.size() == methods[i] .getParameterTypes().length) { Class<?>[] parameterTypes = methods[i].getParameterTypes(); int j = 0; boolean parameterMatch = true; for (DataType dataType : inputTypes) { if (!dataType.getPhysical().equals(parameterTypes[j])) { parameterMatch = false; break; } j++; } if (parameterMatch) { // match found if (method2operation) { mapping.put(methods[i], operation); } else { mapping.put(operation, methods[i]); } break; } } } } return mapping; } }