/* * 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.types; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.tuscany.sca.binding.corba.provider.exceptions.RequestConfigurationException; import org.apache.tuscany.sca.binding.corba.provider.meta.CorbaArray; import org.apache.tuscany.sca.binding.corba.provider.meta.CorbaUnionElement; import org.apache.tuscany.sca.binding.corba.provider.meta.CorbaUnionElementType; /** * @version $Rev$ $Date$ * Creator of types tree. */ public class TypeTreeCreator { /** * Helps to determine if type is a primitive. */ private static List<Class<?>> primitives = new ArrayList<Class<?>>(); static { primitives.add(boolean.class); primitives.add(byte.class); primitives.add(short.class); primitives.add(int.class); primitives.add(long.class); primitives.add(double.class); primitives.add(float.class); primitives.add(char.class); primitives.add(String.class); primitives.add(Boolean.class); primitives.add(Byte.class); primitives.add(Short.class); primitives.add(Integer.class); primitives.add(Long.class); primitives.add(Double.class); primitives.add(Float.class); primitives.add(Character.class); } /** * Creates class for given string argument. * * @param name name of type * @return type */ private static Class<?> createClassFromString(String name) { Class<?> result = null; try { if (name.length() == 1) { // primitives switch (name.charAt(0)) { case 'Z': result = boolean.class; break; case 'C': result = char.class; break; case 'B': result = byte.class; break; case 'S': result = short.class; break; case 'I': result = int.class; break; case 'J': result = long.class; break; case 'F': result = float.class; break; case 'D': result = double.class; break; } } else { // class name = name.substring(1, name.length() - 1); result = Class.forName(name); } } catch (Exception e) { e.printStackTrace(); } return result; } /** * Reduces dimension of array. * * @param forClass array class * @return reduced array */ private static Class<?> reduceArrayDimension(Class<?> forClass) { String name = forClass.getName(); try { String reduced = name.substring(1, name.length()); if (reduced.startsWith("[")) { // reduced class is still an array return Class.forName(reduced); } else { // reduced class may be primitive or class return createClassFromString(reduced); } } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } /** * Return given array without first element * * @param array * @return */ private static int[] removeFirstElement(int[] array) { int[] result = new int[array.length - 1]; System.arraycopy(array, 1, result, 0, result.length); return result; } /** * Converts objects annotations to structure which will be used by this * class * * @param notes * @return */ private static AnnotationAttributes createAnnotationAttributes(Annotation[] notes) { AnnotationAttributes attrs = new AnnotationAttributes(); for (int i = 0; notes != null && i < notes.length; i++) { if (notes[i].annotationType().equals(CorbaArray.class)) { attrs.setCorbaArray(true); attrs.setCorbaArrayLength(((CorbaArray)notes[i]).value()); } } return attrs; } /** * Creates tree for given type. * * @param forClass * @return type tree */ public static TypeTree createTypeTree(Class<?> forClass, Annotation[] notes) throws RequestConfigurationException { TypeTree tree = new TypeTree(); TypeTreeNode rootNode = null; AnnotationAttributes attrs = createAnnotationAttributes(notes); rootNode = inspectClassHierarchy(forClass, attrs, tree); tree.setRootNode(rootNode); return tree; } /** * Recurrent method which builds type tree. * * @param forClass * @param tree * @return */ private static TypeTreeNode inspectClassHierarchy(Class<?> forClass, AnnotationAttributes attributes, TypeTree tree) throws RequestConfigurationException { TypeTreeNode node = null; node = createTypeNode(forClass, attributes); NodeType nodeType = node.getNodeType(); TypeTreeNode[] children = null; if (nodeType.equals(NodeType.primitive)) { // stop condition for recurrent method } else if (nodeType.equals(NodeType.array)) { Class<?> reduced = reduceArrayDimension(node.getJavaClass()); children = new TypeTreeNode[1]; int[] newLengths = removeFirstElement(attributes.getCorbaArrayLength()); attributes.setCorbaArrayLength(newLengths); children[0] = inspectClassHierarchy(reduced, attributes, tree); } else if (nodeType.equals(NodeType.sequence)) { // reducing sequence dimension Class<?> reduced = reduceArrayDimension(node.getJavaClass()); children = new TypeTreeNode[1]; children[0] = inspectClassHierarchy(reduced, attributes, tree); // System.arraycopy(src, srcPos, dest, destPos, length) } else if (nodeType.equals(NodeType.struct) || nodeType.equals(NodeType.exception)) { // inspect types for every structure member Field[] fields = node.getJavaClass().getFields(); children = new TypeTreeNode[fields.length]; for (int i = 0; i < fields.length; i++) { Class<?> field = fields[i].getType(); AnnotationAttributes fAttrs = createAnnotationAttributes(fields[i].getAnnotations()); TypeTreeNode child = inspectClassHierarchy(field, fAttrs, tree); child.setName(fields[i].getName()); children[i] = child; } } else if (nodeType.equals(NodeType.idl_enum)) { } else if (nodeType.equals(NodeType.union)) { // inspect types for every structure member Field[] fields = node.getJavaClass().getDeclaredFields(); children = new TypeTreeNode[fields.length]; for (int i = 0; i < fields.length; i++) { Class<?> field = fields[i].getType(); AnnotationAttributes fAttrs = createAnnotationAttributes(fields[i].getAnnotations()); TypeTreeNode child = inspectClassHierarchy(field, fAttrs, tree); child.setName(fields[i].getName()); children[i] = child; } } else if (nodeType.equals(NodeType.reference)) { // TODO: CORBA references } node.setChildren(children); return node; } /** * Creating and configuring TypeTreeNode for given class. * * @param forClass class * @return node * @throws RequestConfigurationException */ private static TypeTreeNode createTypeNode(Class<?> forClass, AnnotationAttributes attributes) throws RequestConfigurationException { TypeTreeNode node = new TypeTreeNode(); if (forClass.isArray() && !attributes.isCorbaArray()) { node.setNodeType(NodeType.sequence); node.setJavaClass(forClass); } else if (forClass.isArray() && attributes.isCorbaArray()) { node.setNodeType(NodeType.array); node.setJavaClass(forClass); try { // set the actual array size for further use by ArrayTypeHelper node.setAttributes(attributes.getCorbaArrayLength()[0]); } catch (ArrayIndexOutOfBoundsException e) { RequestConfigurationException exc = new RequestConfigurationException("Annotated array size doesn't match declared arrays size"); throw exc; } } else if (primitives.contains(forClass)) { node.setNodeType(NodeType.primitive); node.setJavaClass(forClass); node.setChildren(null); } else if (forClass.isInterface()) { node.setNodeType(NodeType.reference); node.setJavaClass(forClass); node.setChildren(null); } else if (isStructType(forClass)) { node.setNodeType(NodeType.struct); node.setJavaClass(forClass); } else if (isEnumType(forClass)) { node.setNodeType(NodeType.idl_enum); node.setJavaClass(forClass); } else if (isUserException(forClass)) { node.setNodeType(NodeType.exception); node.setJavaClass(forClass); } else if (isUnionType(forClass)) { node.setNodeType(NodeType.union); node.setJavaClass(forClass); node.setAttributes(getUnionAttributes(forClass)); } else { RequestConfigurationException e = new RequestConfigurationException("User defined type which cannot be handled: " + forClass .getCanonicalName()); throw e; } return node; } /** * Tells whether given class is structure * * @param forClass * @return */ private static boolean isStructType(Class<?> forClass) { int classMods = forClass.getModifiers(); if (!Modifier.isFinal(classMods)) { return false; } boolean areCtorsValid = false; Class<?>[] fieldsTypes = null; Constructor<?>[] ctors = forClass.getConstructors(); /* * Do we have 2 ctors and one of them is null ctor? */ if (ctors.length != 2) { return false; } for (int i = 0; i < ctors.length; i++) { Class<?>[] params = ctors[i].getParameterTypes(); if (params.length == 0) { areCtorsValid = true; } else { fieldsTypes = params; } } if (!areCtorsValid) { return false; } /* * Are constructor args declared as class fields? */ Field[] fields = forClass.getFields(); Set<Class<?>> fieldsSet = new HashSet<Class<?>>(Arrays.asList(fieldsTypes)); for (int i = 0; i < fields.length && !fieldsSet.isEmpty(); i++) { int mods = fields[i].getModifiers(); if (Modifier.isPublic(mods) && !Modifier.isStatic(mods) && !Modifier.isFinal(mods)) { fieldsSet.remove(fields[i].getType()); } } return fieldsSet.isEmpty(); } /** * Tells whether given class is enum * * @param forClass * @return */ private static boolean isEnumType(Class<?> forClass) { boolean isValueMethod = false; boolean isFromIntMethod = false; /* * enum type should have value and from_int methods */ try { Method valueMet = forClass.getMethod("value", new Class[] {}); int modValueMet = valueMet.getModifiers(); if (valueMet.getReturnType().equals(int.class) && Modifier.isPublic(modValueMet)) { isValueMethod = true; } Method fromIntMet = forClass.getMethod("from_int", new Class[] {int.class}); int modFromIntMet = fromIntMet.getModifiers(); if ((fromIntMet.getReturnType().equals(forClass) && Modifier.isPublic(modFromIntMet) && Modifier .isStatic(modFromIntMet))) { isFromIntMethod = true; } } catch (NoSuchMethodException e) { } if (!isFromIntMethod || !isValueMethod) { return false; } /* * enum type should also contain minimum one pair of fields: EnumType * field and int _field */ int enumCount = 0; Field[] fields = forClass.getFields(); for (int i = 0; i < fields.length; i++) { if (fields[i].getType().equals(forClass)) { int modifiers = fields[i].getModifiers(); if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && Modifier.isFinal(modifiers)) { try { Field field = forClass.getField("_" + fields[i].getName()); if (field.getType().equals(int.class)) { enumCount++; } } catch (NoSuchFieldException e) { } } } } return enumCount > 0; } /** * Tells whether given class is CORBA user exception * * @param forClass * @return */ private static boolean isUserException(Class<?> forClass) { do { if (forClass.equals(Exception.class)) { return true; } else { forClass = forClass.getSuperclass(); } } while (forClass != null && !forClass.equals(Object.class)); return false; } /** * Tells whether given class is CORBA union. This method validates usage of * unions annotations. * * @param forClass * @return * @throws RequestConfigurationException */ private static boolean isUnionType(Class<?> forClass) throws RequestConfigurationException { int classMods = forClass.getModifiers(); if (!Modifier.isFinal(classMods)) { return false; } boolean atLeastOneOption = false; boolean discriminatorPresent = false; for (int i = 0; i < forClass.getDeclaredFields().length; i++) { CorbaUnionElement note = forClass.getDeclaredFields()[i].getAnnotation(CorbaUnionElement.class); if (note != null) { int fieldMod = forClass.getDeclaredFields()[i].getModifiers(); if (Modifier.isPrivate(fieldMod) && !Modifier.isFinal(fieldMod) && !Modifier.isStatic(fieldMod)) { if (note.type().equals(CorbaUnionElementType.discriminator)) { if (discriminatorPresent) { throw new RequestConfigurationException( "More than one discriminators declared on: " + forClass); } discriminatorPresent = true; } else { atLeastOneOption = true; } } else { throw new RequestConfigurationException( "Annotated union field should be private, not final and no static on class: " + forClass); } } } if (atLeastOneOption && !discriminatorPresent) { throw new RequestConfigurationException("No discriminator annotation found on: " + forClass); } else if (!atLeastOneOption && discriminatorPresent) { throw new RequestConfigurationException("No union option found on: " + forClass); } else if (discriminatorPresent && atLeastOneOption) { return true; } else { return false; } } /** * Gets union attributes - discriminator field name, option fields etc. This * method relies that previously scanned class is valid (method isUnionType) * * @param forClass * @return * @throws RequestConfigurationException */ private static UnionAttributes getUnionAttributes(Class<?> forClass) throws RequestConfigurationException { UnionAttributes attributes = new UnionAttributes(); for (int i = 0; i < forClass.getDeclaredFields().length; i++) { CorbaUnionElement note = forClass.getDeclaredFields()[i].getAnnotation(CorbaUnionElement.class); if (note != null) { if (note.type().equals(CorbaUnionElementType.discriminator)) { attributes.setDiscriminatorName(forClass.getDeclaredFields()[i].getName()); } else if (note.type().equals(CorbaUnionElementType.defaultOption)) { attributes.setDefaultOptionName(forClass.getDeclaredFields()[i].getName()); } else if (note.type().equals(CorbaUnionElementType.option)) { if (attributes.getOptionsMapping().containsKey(note.optionNumber())) { throw new RequestConfigurationException("In " + forClass + ": field \"" + forClass.getDeclaredFields()[i].getName() + "\" uses already used option id: " + note.optionNumber()); } else { attributes.getOptionsMapping().put(note.optionNumber(), forClass.getDeclaredFields()[i].getName()); } } } } return attributes; } }