/* * Copyright (C) 2009 by Claas Wilke (claaswilke@gmx.net) This file is part of * the Java Meta Model of Dresden OCL2 for Eclipse. Dresden OCL2 for Eclipse is * free software: you can redistribute it and/or modify it under the terms of * the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. Dresden OCL2 for Eclipse is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with Dresden OCL2 for Eclipse. If * not, see <http://www.gnu.org/licenses/>. */ package org.dresdenocl.metamodels.java.internal.model; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import org.apache.log4j.Logger; import org.eclipse.emf.common.util.BasicEList; import org.dresdenocl.essentialocl.EssentialOclPlugin; import org.dresdenocl.essentialocl.types.CollectionType; import org.dresdenocl.metamodels.java.JavaMetaModelPlugin; import org.dresdenocl.pivotmodel.EnumerationLiteral; import org.dresdenocl.pivotmodel.Namespace; import org.dresdenocl.pivotmodel.Parameter; import org.dresdenocl.pivotmodel.ParameterDirectionKind; import org.dresdenocl.pivotmodel.PrimitiveTypeKind; import org.dresdenocl.pivotmodel.Type; /** * <p> * A factory to create Pivot Model types for this meta model. * </p> * * @author Claas Wilke */ public class JavaAdapterFactory { /** The {@link Logger} for this class. */ private static final Logger LOGGER = JavaMetaModelPlugin .getLogger(JavaAdapterFactory.class); /** * A {@link Map} containing all {@link EnumerationLiteral}s already adapted. */ private Map<Enum<?>, EnumerationLiteral> adaptedEnumLiterals; /** A {@link Map} containing all {@link Field}s already adapted. */ private Map<Field, JavaField> adaptedFields; /** A {@link Map} containing all {@link Methods}s already adapted. */ private Map<Method, JavaOperation> adaptedMethods; /** A {@link Map} containing all {@link Namespace}s already adapted. */ private Map<String, Namespace> adaptedNamespaces; /** A {@link Map} containing all {@link Parameter}s already adapted. */ private Map<String, Parameter> adaptedParameters; /** * A {@link Map} containing all {@link Class}es already adapted referenced * by their {@link Class} {@link Object}. */ private Map<Class<?>, Type> adaptedTypes; /** * <p> * Creates a new {@link JavaAdapterFactory}. * </p> */ public JavaAdapterFactory() { this.adaptedTypes = new WeakHashMap<Class<?>, Type>(); this.adaptedFields = new WeakHashMap<Field, JavaField>(); this.adaptedMethods = new WeakHashMap<Method, JavaOperation>(); this.adaptedParameters = new HashMap<String, Parameter>(); this.adaptedNamespaces = new HashMap<String, Namespace>(); this.adaptedEnumLiterals = new WeakHashMap<Enum<?>, EnumerationLiteral>(); } /** * <p> * A helper method which checks whether or not a given {@link Class} * represents a {@link Type} with multiple values (an array or a * collection). * * @param aType * The {@link Class} which shall be checked. * @return True if the given {@link Class} has multiple values. */ protected static boolean isMultiple(Class<?> aType) { boolean result; /* Check if the field's type is an array. */ if (aType.isArray()) { result = true; } /* Else check if the fields type is a collection. */ else if (Collection.class.isAssignableFrom(aType)) { result = true; } /* Else check if the fields type is a map. */ else if (Map.class.isAssignableFrom(aType)) { result = true; } else { result = false; } return result; } /** * <p> * A helper method which checks whether or not a given {@link Class} * represents a {@link Type} with ordered values. * * @param aType * The {@link Class} which shall be checked. * @return True if the given {@link Class} contains ordered values. */ protected static boolean isOrdered(Class<?> aType) { boolean result; /* Normally a Java field is ordered. */ result = true; /* Sets are not ordered. */ if (Set.class.isAssignableFrom(aType)) { result = false; } /* Maps are not ordered as well. */ else if (Map.class.isAssignableFrom(aType)) { result = false; } // no else. return result; } /** * <p> * A helper method which checks whether or not a given {@link Class} * represents a {@link Type} with unique values. * * @param aType * The {@link Class} which shall be checked. * @return True if the given {@link Class} contains unique values. */ protected static boolean isUnique(Class<?> aType) { boolean result; /* By default the result is true. */ result = true; /* Arrays, Collections (except Sets), and Maps can have multiple values. */ if (aType.isArray()) { result = false; } else if (Collection.class.isAssignableFrom(aType)) { if (Set.class.isAssignableFrom(aType)) { result = true; } else { result = false; } } else if (Map.class.isAssignableFrom(aType)) { result = false; } return result; } /** * <p> * Creates a new {@link JavaEnumerationLiteral} instance. * </p> * * @param aLiteral * The adapted {@link Object} of this * {@link JavaEnumerationLiteral}. * @return A new {@link JavaEnumerationLiteral} instance. */ public EnumerationLiteral createEnumerationLiteral(Enum<?> aLiteral) { /* Eventually log the entry of this method. */ if (LOGGER.isDebugEnabled()) { String msg; msg = "createEnumerationLiteral("; msg += "aLiteral = " + aLiteral; msg += ") - enter"; LOGGER.debug(msg); //$NON-NLS-1$ } // no else. EnumerationLiteral result; /* Eventually use a cached result. */ if (this.adaptedEnumLiterals.containsKey(aLiteral)) { result = this.adaptedEnumLiterals.get(aLiteral); } /* Else create the result. */ else { result = new JavaEnumerationLiteral(aLiteral, this); /* Cache the result. */ this.adaptedEnumLiterals.put(aLiteral, result); } /* Eventually log the exit of this method. */ if (LOGGER.isDebugEnabled()) { LOGGER.debug("createEnumerationLiteral() - exit"); //$NON-NLS-1$ } // no else. return result; } /** * <p> * Creates a {@link List} of new input {@link JavaParameter} instances. * </p> * * @param method * The {@link Method} whose inpute parameters shall be created. * @return A new {@link List} of {@link JavaParameter} instances. */ public List<Parameter> createInputParameters(Method method) { /* Eventually log the entry of this method. */ if (LOGGER.isDebugEnabled()) { String msg; msg = "createInputParameters("; msg += "method = " + method; msg += ") - enter"; LOGGER.debug(msg); //$NON-NLS-1$ } // no else. List<Parameter> result; Class<?>[] classes; java.lang.reflect.Type[] genericTypes; classes = method.getParameterTypes(); genericTypes = method.getGenericParameterTypes(); result = new BasicEList<Parameter>(); for (int index = 0; index < classes.length; index++) { result.add(this.createParameter(classes[index], genericTypes[index], ParameterDirectionKind.IN, method, index + 1)); } /* Eventually log the exit of this method. */ if (LOGGER.isDebugEnabled()) { LOGGER.debug("createInputParameters() - exit"); //$NON-NLS-1$ } // no else. return result; } /** * <p> * Creates a {@link Namespace} for its given qualified name as a * {@link List} of {@link Namespace} names. * </p> * * @param qualifiedPath * The qualified name of the {@link Namespace} which shall be * created as a {@link List} of {@link Namespace} names. * @return The created {@link Namespace}. */ public Namespace createNamespace(List<String> qualifiedPath) { Namespace result; String qualifiedName; qualifiedName = this.toQualifiedName(qualifiedPath); /* Eventually use a cached result. */ if (this.adaptedNamespaces.containsKey(qualifiedName)) { result = this.adaptedNamespaces.get(qualifiedName); } /* Else create a new result. */ else { result = new JavaPackage(qualifiedPath, this); this.adaptedNamespaces.put(qualifiedName, result); } return result; } /** * <p> * Creates a new {@link JavaClass} instance. * </p> * * @param aClass * The related {@link Class} of this {@link JavaClass}. * @return A new {@link JavaClass} instance. */ public Type createType(Class<?> aClass) { /* Eventually log the entry of this method. */ if (LOGGER.isDebugEnabled()) { String msg; msg = "createType("; msg += "aClass = " + aClass; msg += ") - enter"; LOGGER.debug(msg); //$NON-NLS-1$ } // no else. Type result; /* Check if the class has been adapted already. */ if (this.adaptedTypes.containsKey(aClass)) { result = this.adaptedTypes.get(aClass); } /* Check if the given type is the void type. */ else if (aClass.equals(void.class)) { result = EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getOclVoid(); } /* Else create the result. */ else { /* Check if the class is a primitive type. */ if (!JavaPrimitiveType.getPrimitiveTypeKind(aClass).equals( PrimitiveTypeKind.UNKNOWN)) { result = new JavaPrimitiveType(aClass, this); } /* Else check if the class is an Enumeration. */ else if (aClass.isEnum()) { result = new JavaEnumeration(aClass, this); } /* Else create a regular type. */ else { result = new JavaClass(aClass, this); } /* * Navigate to the root name space to register this packages * transitively with the root name space (eventually unknown sub * name spaces are adapted). */ Namespace aNamespace; aNamespace = result.getNamespace(); while (aNamespace != null) { aNamespace = aNamespace.getNestingNamespace(); } /* Store the result. */ adaptedTypes.put(aClass, result); } /* Eventually log the exit of this method. */ if (LOGGER.isDebugEnabled()) { LOGGER.debug("createType() - exit"); //$NON-NLS-1$ } // no else. return result; } /** * <p> * Creates a new {@link JavaOperation} instance. * </p> * * @param aField * The related {@link Method} of this {@link JavaOperation}. * @return A new {@link JavaOperation} instance. */ public JavaOperation createOperation(Method aMethod) { /* Eventually log the entry of this method. */ if (LOGGER.isDebugEnabled()) { String msg; msg = "createMethod("; msg += "aMethod = " + aMethod; msg += ") - enter"; LOGGER.debug(msg); //$NON-NLS-1$ } // no else. JavaOperation result; /* Check if the class has been adapted already. */ if (this.adaptedMethods.containsKey(aMethod)) { result = this.adaptedMethods.get(aMethod); } /* Else create the result. */ else { result = new JavaOperation(aMethod, this); /* Cache the result. */ this.adaptedMethods.put(aMethod, result); } /* Eventually log the exit of this method. */ if (LOGGER.isDebugEnabled()) { LOGGER.debug("createMethod() - exit"); //$NON-NLS-1$ } // no else. return result; } /** * <p> * Creates a new {@link JavaParameter} instance. * </p> * * @param aClass * The {@link Class} that shall be adapted. * @param genericType * The generic {@link java.lang.reflect.Type} of the * {@link JavaParameter} or <code>null</code>. * @param parameterKind * The {@link ParameterDirectionKind} of the * {@link JavaParameter}. * @param method * The {@link Method} of the {@link JavaParameter}. * @param parameterNumber * A number used to create a unique parameter name. * @return A new {@link JavaParameter} instance. */ public Parameter createParameter(Class<?> aClass, java.lang.reflect.Type genericType, ParameterDirectionKind parameterKind, Method method, int parameterNumber) { /* Eventually log the entry of this method. */ if (LOGGER.isDebugEnabled()) { String msg; msg = "createParameter("; msg += "aClass = " + aClass; msg += ", genericType = " + genericType; msg += ", parameterKind = " + parameterKind; msg += ", method = " + method; msg += ", parameterNumber = " + parameterNumber; msg += ") - enter"; LOGGER.debug(msg); //$NON-NLS-1$ } // no else. Parameter result; String key; /* Generate a key to cache the parameter. */ key = aClass.toString(); key += genericType.toString(); key += parameterKind.toString(); key += method.toString(); key += parameterNumber; /* Eventually use a cached parameter. */ if (this.adaptedParameters.containsKey(key)) { result = this.adaptedParameters.get(key); } /* Else create the parameters. */ else { result = new JavaParameter(aClass, genericType, parameterKind, method, parameterNumber, this); /* Cache the result. */ this.adaptedParameters.put(key, result); } /* Eventually log the exit of this method. */ if (LOGGER.isDebugEnabled()) { LOGGER.debug("createParameter() - exit"); //$NON-NLS-1$ } // no else. return result; } /** * <p> * Creates a new {@link JavaField} instance. * </p> * * @param aField * The related {@link Field} of this {@link JavaField}. * @return A new {@link JavaField} instance. */ public JavaField createProperty(Field aField) { /* Eventually log the entry of this method. */ if (LOGGER.isDebugEnabled()) { String msg; msg = "createProperty("; msg += "aField = " + aField; msg += ") - enter"; LOGGER.debug(msg); //$NON-NLS-1$ } // no else. JavaField result; /* Check if the class has been adapted already. */ if (this.adaptedFields.containsKey(aField)) { result = this.adaptedFields.get(aField); } /* Else create the result. */ else { result = new JavaField(aField, this); adaptedFields.put(aField, result); } /* Eventually log the exit of this method. */ if (LOGGER.isDebugEnabled()) { LOGGER.debug("createProperty() - exit"); //$NON-NLS-1$ } // no else. return result; } /** * <p> * Returns all {@link Namespace} that are contained in a given * {@link Namespace}. * </p> * * @param aNamespace * The {@link Namespace} whose children shall be returned. * @return All found adapted {@link Namespace} located in this * {@link Namespace}. */ public List<Namespace> getNestedNamespaces(Namespace aNamespace) { List<Namespace> result; result = new ArrayList<Namespace>(); /* Iterate through all adapted name spaces. */ for (String aQualifiedName : this.adaptedNamespaces.keySet()) { Namespace anAdaptedNamespace; anAdaptedNamespace = this.adaptedNamespaces.get(aQualifiedName); if (anAdaptedNamespace.getNestingNamespace() != null && anAdaptedNamespace.getNestingNamespace().equals( aNamespace)) { result.add(anAdaptedNamespace); } // no else. } // end for. return result; } /** * <p> * Returns all {@link Type}s that are contained in a given {@link Namespace} * . * </p> * * @param aNamespace * The {@link Namespace} whose children shall be returned. * @return All found adapted {@link Type}s located in this {@link Namespace} * . */ public List<Type> getOwnedTypes(Namespace aNamespace) { List<Type> result = new BasicEList<Type>(); /* Iterate through all adapted name spaces. */ for (Class<?> aClass : this.adaptedTypes.keySet()) { Type anAdaptedType; anAdaptedType = this.adaptedTypes.get(aClass); /* Special handling of the root name space. */ if (aNamespace.getNestingNamespace() == null && anAdaptedType.getNamespace().getNestingNamespace() == null) { result.add(anAdaptedType); } /* Handling for other name spaces. */ else if (anAdaptedType.getNamespace().equals(aNamespace)) { result.add(anAdaptedType); } // no else. } // end for. return result; } /** * <p> * A helper method which adapts a given Java {@link Class} as a pivot model * {@link Type}. * * @param aClass * The Java {@link Class} which shall be adapted. * @param aGenericType * The generic {@link java.lang.reflect.Type} to the given * {@link Class} or null, if the {@link Class} does not represent * a generic type. * @return The adapted {@link Type}. */ protected Type adaptJavaType(Class<?> aClass, java.lang.reflect.Type aGenericType) { Type result; /* If the type is an array convert the array to a collection. */ if (aClass.isArray()) { if (aGenericType instanceof GenericArrayType) { GenericArrayType genericArrayType; genericArrayType = (GenericArrayType) aGenericType; java.lang.reflect.Type componentType = genericArrayType .getGenericComponentType(); Type elementType; if (componentType instanceof ParameterizedType) { ParameterizedType paramterizedType; paramterizedType = (ParameterizedType) componentType; elementType = this.adaptParameterizedType(paramterizedType); elementType = this.adaptCollectionClass( (Class<?>) paramterizedType.getRawType(), elementType); } else if (componentType instanceof Class<?>) { Class<?> componentClass; componentClass = (Class<?>) componentType; if (componentClass.isArray()) { elementType = this.adaptArrayClass(componentClass); } else { elementType = this.createType(componentClass); } } else { elementType = this.createType(Object.class); } result = EssentialOclPlugin.getOclLibraryProvider() .getOclLibrary().getSequenceType(elementType); } else { result = this.adaptArrayClass(aClass); } } /* Else if the type is a collection get its generic type. */ else if (Collection.class.isAssignableFrom(aClass)) { Type genericType; if (aGenericType instanceof ParameterizedType) { genericType = adaptParameterizedType((ParameterizedType) aGenericType); } else { genericType = this.createType(Object.class); } result = this.adaptCollectionClass(aClass, genericType); } else { result = this.createType(aClass); } return result; } /** * <p> * Adapts a given array {@link Class} to a {@link CollectionType}. * </p> * * @param aClass * The array {@link Class} that shall be converted into a * {@link CollectionType}. * @return A {@link CollectionType} (probably nesting further * {@link CollectionType}s). */ private Type adaptArrayClass(Class<?> aClass) { Type result; Class<?> elementClass; /* Get the class of the elements. */ elementClass = aClass.getComponentType(); Type elementType; /* Recall method recursively, if element type is an array again. */ if (elementClass.isArray()) { elementType = this.adaptArrayClass(elementClass); } else { elementType = this.createType(elementClass); } result = EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getSequenceType(elementType); return result; } /** * <p> * Adapts a given {@link Class} to a {@link CollectionType} having the given * {@link Type} as its element {@link Type}. * </p> * * @param aClass * The {@link Class} that shall be converted into a * {@link CollectionType}. * @param genericType * The element {@link Type} of the collection. * @return A {@link CollectionType} (probably nesting further * {@link CollectionType}s). */ private Type adaptCollectionClass(Class<?> aClass, Type genericType) { Type result; if (List.class.isAssignableFrom(aClass)) { result = EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getSequenceType(genericType); } else if (Set.class.isAssignableFrom(aClass)) { result = EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getSetType(genericType); } else { result = EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getCollectionType(genericType); } return result; } /** * <p> * Converts a given {@link ParameterizedType} to a {@link CollectionType}. * </p> * * @param parameterizedType * The {@link ParameterizedType} that shall be converted to a * {@link CollectionType}. * @return The converted {@link CollectionType}. */ private Type adaptParameterizedType(ParameterizedType parameterizedType) { Type result; java.lang.reflect.Type arguments[]; java.lang.reflect.Type argument; arguments = parameterizedType.getActualTypeArguments(); /* Use the first generic type of the collection as type. */ argument = arguments[0]; if (argument instanceof Class<?>) { Class<?> elementClass; elementClass = (Class<?>) argument; result = this.createType(elementClass); } else if (argument instanceof TypeVariable<?>) { result = this.createType((Class<?>) ((TypeVariable<?>) argument) .getBounds()[0]); } else if (argument instanceof ParameterizedType) { Type genericType; genericType = this .adaptParameterizedType((ParameterizedType) argument); result = this.adaptCollectionClass( (Class<?>) ((ParameterizedType) argument).getRawType(), genericType); } else if (argument instanceof GenericArrayType) { GenericArrayType genericArrayType; genericArrayType = (GenericArrayType) argument; java.lang.reflect.Type componentType = genericArrayType .getGenericComponentType(); Type elementType; if (componentType instanceof ParameterizedType) { elementType = this .adaptParameterizedType((ParameterizedType) componentType); } else if (componentType instanceof Class<?>) { Class<?> componentClass; componentClass = (Class<?>) componentType; if (componentClass.isArray()) { elementType = this.adaptArrayClass(componentClass); } else { elementType = this.createType(componentClass); } } else { elementType = this.createType(Object.class); } result = EssentialOclPlugin.getOclLibraryProvider().getOclLibrary() .getSequenceType(elementType); } else { LOGGER.warn("Adapted Generic Type " + argument + " to java.lang.Object."); result = this.createType(Object.class); } return result; } /** * <p> * A helper method that converts a given qualified path (a {@link List} of * {@link String}s representing {@link Namespace}s) into a String containing * this names separated by <code>::</code>. * </p> * * @param qualifiedPath * The path which shall be converted. * @return The converted qualifiedName */ private String toQualifiedName(List<String> qualifiedPath) { String result; /* Copy the list to avoid side effects. */ qualifiedPath = new ArrayList<String>(qualifiedPath); result = qualifiedPath.remove(0); while (qualifiedPath.size() > 0) { result += "::" + qualifiedPath.remove(0); } return result; } }