/* Copyright (C) 2007-2009 by Claas Wilke (claaswilke@gmx.net) This file is part of the Ecore 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.modelinstancetype.ecore.internal.util; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.osgi.util.NLS; import org.dresdenocl.model.IModel; import org.dresdenocl.model.ModelAccessException; import org.dresdenocl.model.ModelConstants; import org.dresdenocl.modelinstancetype.ecore.internal.modelinstance.EcoreModelInstanceFactory; import org.dresdenocl.modelinstancetype.ecore.internal.msg.EcoreModelInstanceTypeMessages; import org.dresdenocl.modelinstancetype.exception.TypeNotFoundInModelException; import org.dresdenocl.modelinstancetype.types.ComplexType; import org.dresdenocl.pivotmodel.PrimitiveType; import org.dresdenocl.pivotmodel.PrimitiveTypeKind; import org.dresdenocl.pivotmodel.Type; /** * <p> * This class provides some static methods to convert {@link EClass}es to * qualified names and vice versa. * </p> * * @author Claas Wilke * */ public class EcoreModelInstanceTypeUtility { /** * An array containing all Java {@link Class}es that can be mapped to the * {@link PrimitiveTypeKind#BOOLEAN}. */ private static final Class<?> BOOLEAN_CLASSES[] = new Class<?>[] { boolean.class, Boolean.class }; /** * An array containing all Java {@link Class}es that can be mapped to the * {@link PrimitiveTypeKind#INTEGER}. */ private static final Class<?> INTEGER_CLASSES[] = new Class<?>[] { BigDecimal.class, BigInteger.class, byte.class, Byte.class, int.class, Integer.class, long.class, Long.class, short.class, Short.class }; /** * An array containing all Java {@link Class}es that can be mapped to the * {@link PrimitiveTypeKind#REAL}. */ private static final Class<?> REAL_CLASSES[] = new Class<?>[] { double.class, Double.class, float.class, Float.class }; /** * An array containing all Java {@link Class}es that can be mapped to the * {@link PrimitiveTypeKind#STRING}. */ private static final Class<?> STRING_CLASSES[] = new Class<?>[] { char.class, Character.class, String.class }; /** Already found and cached {@link Class} references. */ private static Map<Type, Class<?>> cachedClasses = new WeakHashMap<Type, Class<?>>(); /** Already found and cached {@link Type} references. */ private Map<Class<?>, Type> cachedTypes = new WeakHashMap<Class<?>, Type>(); /** The IModel this {@link EcoreModelInstanceTypeUtility} belongs to. */ private IModel model; /** * <p> * Creates a new {@link EcoreModelInstanceTypeUtility}. * </p> * * @param model * The IModel this {@link EcoreModelInstanceTypeUtility} belongs * to. */ public EcoreModelInstanceTypeUtility(IModel model) { this.model = model; } /** * <p> * This method checks, if a given {@link java.lang.reflect.Type} conforms to * a given {@link Type}. * </p> * * @param reflectionType * The {@link java.lang.reflect.Type}. * @param type * The {@link Type}. * @return <code>true</code>, if the types conform. Else <code>false</code>. */ public static boolean conformsTypeToType( java.lang.reflect.Type reflectionType, Type type) { boolean result; /* If the reflective type is a class, compare the qualified names. */ if (reflectionType instanceof Class<?>) { Class<?> clazz; clazz = (Class<?>) reflectionType; if (clazz.isArray()) { result = conformsTypeToType(clazz.getComponentType(), type); } else { List<String> reflectionTypeQualifiedNameList; List<String> typeQualifiedNameList; reflectionTypeQualifiedNameList = toQualifiedNameList(clazz .getCanonicalName()); typeQualifiedNameList = type.getQualifiedNameList(); if (type instanceof PrimitiveType) { result = Arrays.asList( new String[] { ((PrimitiveType) type).getKind() .getName() }).equals( reflectionTypeQualifiedNameList); } else { if (typeQualifiedNameList.size() > 0 && typeQualifiedNameList.get(0).equals( ModelConstants.ROOT_PACKAGE_NAME)) { typeQualifiedNameList.remove(0); } // no else. if (typeQualifiedNameList.size() > reflectionTypeQualifiedNameList .size()) { result = false; } else { /* * Check that the qualified name of the class's name * ends with the qualified name of the type. */ int offset; offset = reflectionTypeQualifiedNameList.size() - typeQualifiedNameList.size(); result = true; for (int index = 0; index < typeQualifiedNameList .size(); index++) { result &= typeQualifiedNameList.get(index).equals( reflectionTypeQualifiedNameList.get(index + offset)); if (!result) { break; } // no else. } // end for. } // end else. } // end else. } } /* If the type is an array, compare its component type with the type. */ else if (reflectionType instanceof GenericArrayType) { GenericArrayType genericArrayType; genericArrayType = (GenericArrayType) reflectionType; result = conformsTypeToType(genericArrayType .getGenericComponentType(), type); } /* This is the case, if the type is a collection. */ else if (reflectionType instanceof ParameterizedType) { ParameterizedType parameterizedType; parameterizedType = (ParameterizedType) reflectionType; /* Check if exactly one generic parameter is set. */ if (parameterizedType.getActualTypeArguments().length == 1) { result = conformsTypeToType(parameterizedType .getActualTypeArguments()[0], type); } /* * Else a ParameterizedType can contain more than one classes. Thus, * the result is not unambiguous. */ else { result = false; } } else if (reflectionType instanceof TypeVariable<?>) { /* * A TypeVariable can contain more than one classes. Thus, the * result is not unambiguous. */ result = false; } else if (reflectionType instanceof WildcardType) { /* * A WildcardType can contain more than one classes. Thus, the * result is not unambiguous. */ result = false; } /* No Type of the Java standard library. Cannot compare. */ else { result = false; } return result; } /** * <p> * Converts a given {@link List} of {@link String}s that represent a * {@link Type}'s qualified name into a Java canonical name. * </p> * * @param qualifiedNameList * The given qualified name {@link List}. * @return The converte canonical name. */ public static String toCanonicalName(List<String> qualifiedNameList) { StringBuffer resultBuffer; resultBuffer = new StringBuffer(); /* Copy the list to avoid side effects. */ qualifiedNameList = new ArrayList<String>(qualifiedNameList); /* Probably remove the root package from the name. */ if (qualifiedNameList.get(0).equals(ModelConstants.ROOT_PACKAGE_NAME)) { qualifiedNameList.remove(0); } // no else. for (String aPackageName : qualifiedNameList) { if (resultBuffer.length() > 0) { resultBuffer.append("."); } // no else. resultBuffer.append(aPackageName); } return resultBuffer.toString(); } /** * <p> * Converts a given Java canonical name into a {@link List} of * {@link String}s representing a qualified name of an {@link IModel}'s * {@link Type}. * </p> * * @param canonicalName * The canonical name that shall be converted. * @return The qualified name as a {@link List} of {@link String}s. */ public static List<String> toQualifiedNameList(String canonicalName) { List<String> result; /* Check for primitive types. */ result = toPrimitiveQualifiedName(canonicalName); if (result == null) { result = new ArrayList<String>(Arrays.asList(canonicalName .split("[.]"))); result.add(0, ModelConstants.ROOT_PACKAGE_NAME); } // no else. return result; } /** * <p> * Tries to find a {@link Class} for a given {@link Type} and a given base * {@link Class} which itself or one of its super {@link Class}es (and * interfaces) may correspond to the {@link Type}. * </p> * * @param baseClass * A base {@link Class} which itself or one of its super * {@link Class}es (and interfaces) may correspond to the * {@link Type}. * @param type * The {@link Type} for which the class shall be found. * * @return The found {@link Class} or <code>null</code>. */ public static Class<?> findClassOfType(Class<?> baseClass, Type type) { /* Probably use a cached result. */ Class<?> result; result = cachedClasses.get(type); /* Else compute the result. */ if (result == null) { result = findSuperClassConformingToName(baseClass, baseClass, toCanonicalName(type.getQualifiedNameList()), new HashSet<Class<?>>()); cachedClasses.put(type, result); } // no else. return result; } /** * <p> * A helper method that returns the {@link Type} in a given {@link IModel} * that correspond to a given {@link Class}. * </p> * * @param aClass * The {@link Class} for that the {@link Type} shall be returned. * @return The found {@link Type}. * @throws TypeNotFoundInModelException * Thrown, if a given {@link Object} cannot be adapted to a * {@link Type} in the {@link IModel}. */ public Type findTypeOfClassInModel(Class<?> aClass) throws TypeNotFoundInModelException { Type result; /* Probably use a cached result. */ result = this.cachedTypes.get(aClass); /* Else compute the result. */ if (result == null) { try { List<String> typePath; typePath = EcoreModelInstanceTypeUtility .toQualifiedNameList(aClass.getCanonicalName()); /* * The problem with Ecore models is that Ecore models do not * contain the complete package hierarchy of the implementation * class. Thus, remove package per package from the path and * search for a type again. */ while (result == null && typePath.size() >= 2) { result = model.findType(typePath); typePath.remove(0); } // end while. } catch (ModelAccessException e) { result = null; } /* Probably throw an exception. */ if (result == null) { String msg; msg = EcoreModelInstanceTypeMessages.EcoreModelInstanceFactory_TypeNotFoundInModel; msg = NLS.bind(msg, aClass); throw new TypeNotFoundInModelException(msg); } /* Cache the result. */ this.cachedTypes.put(aClass, result); } // no else. return result; } /** * <p> * A helper method that tries to find at least one {@link Type} in the * {@link IModel} that a given {@link EObject} implements. * </p> * * @param eObject * The {@link EObject} for that {@link Type}s shall be found. * @return A {@link Set} of found {@link Type}s. * @throw {@link TypeNotFoundInModelException} Thrown, if the given * {@link EObject} cannot be adapted to any {@link Type} of the * {@link IModel} of this {@link EcoreModelInstanceFactory}. */ public Type findTypeOfEObjectInModel(EObject eObject) throws TypeNotFoundInModelException { Type result; Set<Type> resultSet; Class<?> objectClass; objectClass = eObject.getClass(); /* Probably use a cached result. */ result = this.cachedTypes.get(objectClass); /* Else compute the result. */ if (result == null) { resultSet = findTypesOfClassInModel(objectClass); /* * Probably remove the EObject Type from the TypeSet. Each element * is an EObject. This reference cause problems when in invoking the * isTypeOf method. */ try { Type eObjectType = this.model.findType(Arrays .asList(new String[] { "EObject" })); if (resultSet.size() > 1 && eObjectType != null && resultSet.contains(eObjectType)) { resultSet.remove(eObjectType); } // no else. } catch (ModelAccessException e) { /* * Unimportant. If EObject is not part of the model. It was not * found. Thus it is not part of the type Set. */ } /* Probably create a complex type. */ if (resultSet.size() == 1) { result = resultSet.iterator().next(); } else { result = new ComplexType(resultSet); } /* Cache result. */ this.cachedTypes.put(objectClass, result); } // no else. return result; } /** * <p> * A helper method that removes {@link Type}s from a given {@link Set} that * are transitively described by other {@link Type}s of the {@link Set} * because they are super {@link Type}s. * </p> * * @param types * The {@link Set} from which super {@link Type}s shall be * removed. * @return The {@link Set} without redundant super {@link Type}s. */ private static Set<Type> removeRedundantModelTypes(Set<Type> types) { List<Type> typeList; Set<Type> result; typeList = new ArrayList<Type>(types); result = new HashSet<Type>(); for (int index1 = 0; index1 < typeList.size(); index1++) { Type type1; boolean isRedundant; type1 = typeList.get(index1); isRedundant = false; /* Check if any other type is a sub type of type 1. */ for (int index2 = 0; index2 < typeList.size(); index2++) { Type type2; type2 = typeList.get(index2); if (index1 != index2 && type2.conformsTo(type1)) { isRedundant = true; break; } // no else. } if (!isRedundant) { result.add(type1); } // no else. } return result; } /** * <p> * A helper method that converts a given canonical name of a java * {@link Class} into the qualified name (as a {@link List} of * {@link String} s) if the given {@link Class} can be mapped to a * {@link PrimitiveType} of any {@link PrimitiveTypeKind}. Else * <code>null</code> is returned. * </p> * * @param canonicalName * The canonical name that shall be converted. * @return The qualified name of a {@link PrimitiveType} or * <code>null</code>. */ private static List<String> toPrimitiveQualifiedName(String canonicalName) { List<String> result; result = null; /* Check for the void type. */ if (canonicalName.equalsIgnoreCase(PrimitiveTypeKind.VOID.toString())) { result = new ArrayList<String>(); result.add(PrimitiveTypeKind.VOID.toString()); } /* Probably check for a Boolean type. */ if (result == null) { for (Class<?> clazz : BOOLEAN_CLASSES) { if (canonicalName.equals(clazz.getCanonicalName())) { result = new ArrayList<String>(); result.add(PrimitiveTypeKind.BOOLEAN.toString()); break; } // no else. } } // no else. /* Probably check for an Integer type. */ if (result == null) { for (Class<?> clazz : INTEGER_CLASSES) { if (canonicalName.equals(clazz.getCanonicalName())) { result = new ArrayList<String>(); result.add(PrimitiveTypeKind.INTEGER.toString()); break; } // no else. } } // no else. /* Probably check for a Real type. */ if (result == null) { for (Class<?> clazz : REAL_CLASSES) { if (canonicalName.equals(clazz.getCanonicalName())) { result = new ArrayList<String>(); result.add(PrimitiveTypeKind.REAL.toString()); break; } // no else. } } // no else. /* Probably check for a String type. */ if (result == null) { for (Class<?> clazz : STRING_CLASSES) { if (canonicalName.equals(clazz.getCanonicalName())) { result = new ArrayList<String>(); result.add(PrimitiveTypeKind.STRING.toString()); break; } // no else. } } // no else. return result; } /** * <p> * Tries to find a super {@link Class} of the given {@link Class} that * conforms to a given (probably partly) canonical name. * </p> * * @param baseClass * The {@link Class} whose super {@link Class}es (including * interfaces) are checked. * @param currentClass * The current {@link Class} during the recursive check (the same * as baseClass if called externally). * @param canonicalName * The (probably partly) canonical name of the {@link Class} that * shall be found. * @param alreadyCheckedClasses * {@link Class} that have already been checked (necessary to * avoid cycles). * @return The found {@link Class} or <code>null</code>. */ private static Class<?> findSuperClassConformingToName(Class<?> baseClass, Class<?> currentClass, String canonicalName, Set<Class<?>> alreadyCheckedClasses) { Class<?> result; result = null; if (currentClass.getCanonicalName().matches( ".*" + canonicalName.replaceAll("\\.", ".*") + ".*") && currentClass.isAssignableFrom(baseClass)) { result = currentClass; } else { /* Do not check the class again. */ alreadyCheckedClasses.add(currentClass); if (currentClass.getSuperclass() != null && !alreadyCheckedClasses.contains(currentClass .getSuperclass())) { result = findSuperClassConformingToName(baseClass, currentClass .getSuperclass(), canonicalName, alreadyCheckedClasses); } // no else. if (result == null) { for (Class<?> interfaze : currentClass.getInterfaces()) { if (!alreadyCheckedClasses.contains(interfaze)) { result = findSuperClassConformingToName(baseClass, interfaze, canonicalName, alreadyCheckedClasses); if (result != null) { break; } // no else. } // no else. } // end for. } // no else. } // end else. return result; } /** * <p> * A helper method that tries to find a {@link Type} in the {@link IModel} * that corresponds to the given class. * </p> * * <p> * Probably a {@link Class} could be related to different {@link Type}s in * the {@link IModel} that do not know each other. E.g., multiple interface * inheritance. In these cases, the result will be a {@link Set} containing * all {@link Type}s, the {@link Class} could be related to. * </p> * * <p> * The search strategy will work as follows: * </p> * <ul> * <li>If the {@link Class} itself is represented by a {@link Type} in the * {@link IModel}, only this {@link Type} will be returned.</li> * <li>Else the method will collect all {@link Type}s that are related to * implemented interfaces of the {@link Class} and probably also the * {@link Type} of its super {@link Class}.</li> * </ul> * * @param aClass * The {@link Class} for that the {@link Type}s shall be * returned. * @return The found {@link Type}s as an array. * @throws TypeNotFoundInModelException * Thrown, if a given {@link Object} cannot be adapted to a * {@link Type} in the {@link IModel}. */ private Set<Type> findTypesOfClassInModel(Class<?> clazz) throws TypeNotFoundInModelException { Set<Type> result; result = new HashSet<Type>(); /* Check that the given class is not null. */ if (clazz != null) { /* Try to find the type corresponding to the class itself. */ try { result.add(findTypeOfClassInModel(clazz)); } /* * Else search for the interfaces in the model and for the super * type. */ catch (TypeNotFoundInModelException e) { /* Add the types of the implemented interfaces. */ for (Class<?> anInterface : clazz.getInterfaces()) { try { result.addAll(findTypesOfClassInModel(anInterface)); } catch (TypeNotFoundInModelException e2) { /* Continue probably the class will implement a type. */ } } /* Add recursively found types for the super class. */ try { result .addAll(findTypesOfClassInModel(clazz .getSuperclass())); } catch (TypeNotFoundInModelException e2) { /* * Continue probably one of the interfaces will implement a * type. */ } /* * Remove types, that are already represented by sub types in * the model. */ result = removeRedundantModelTypes(result); } // end else. } /* * Check if any implemented type has been found. Else throw an * exception. */ if (result.size() == 0) { String msg; msg = EcoreModelInstanceTypeMessages.EcoreModelInstanceFactory_TypeNotFoundInModel; msg = NLS.bind(msg, clazz); throw new TypeNotFoundInModelException(msg); } return result; } }