/******************************************************************************* * Copyright (c) 2006-2013 The RCP Company and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * The RCP Company - initial API and implementation *******************************************************************************/ package com.rcpcompany.uibindings.internal.bindingDataTypes; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.runtime.IAdapterManager; import org.eclipse.core.runtime.Platform; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EEnumLiteral; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EPackage.Registry; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EcorePackage; import com.rcpcompany.uibindings.IBindingDataType; import com.rcpcompany.utils.logging.LogUtils; /** * Factory for {@link IBindingDataType} objects. * * @author Tonny Madsen, The RCP Company */ public final class BindingDataTypeFactory { private BindingDataTypeFactory() { // TODO Auto-generated constructor stub } private static final Map<Object, IBindingDataType> DATA_TYPE_MAPPING = new HashMap<Object, IBindingDataType>(); /** * Creates and returns a new {@link IBindingDataType binding data type} appropriate for the * specified element. * <p> * The result is cached and reused. * <p> * The context is used to get a more accurate result when the element is a structural feature - * or something that evaluates to that. * * @param context the context of the element * @param element the element to return a data type for * @return the data type object or <code>null</code> */ public static IBindingDataType create(Object context, Object element) { EClassifier classifier = null; IBindingDataType dt = null; if (element instanceof EClassifier) { if (DATA_TYPE_MAPPING.containsKey(element)) return DATA_TYPE_MAPPING.get(element); dt = new EClassifierBindingDataType((EClassifier) element); DATA_TYPE_MAPPING.put(element, dt); } else if (element instanceof EStructuralFeature) { final CSF csf = new CSF(); csf.eSF = (EStructuralFeature) element; /* * Features are special: for these we use the context so you can specify a specific * sub-class for the feature. */ if (context instanceof IObservableValue) { context = ((IObservableValue) context).getValue(); } /* * Convert EObject to EClasses and Object to Classes */ if (context instanceof EClass) { // Do nothing } else if (context instanceof EObject) { context = ((EObject) context).eClass(); } else if (context instanceof Class) { // Do nothing } else if (context instanceof Object) { context = context.getClass(); } if (context instanceof EClass) { csf.eCls = (EClass) context; } else if (context instanceof Class) { final EClassifier c = IBindingDataType.Factory.convertToClassifier((Class<?>) context); if (c instanceof EClass) { csf.eCls = (EClass) c; } } else { csf.eCls = csf.eSF.getEContainingClass(); } if (DATA_TYPE_MAPPING.containsKey(csf)) return DATA_TYPE_MAPPING.get(element); dt = new EStructuralFeatureBindingDataType(csf.eCls, csf.eSF); DATA_TYPE_MAPPING.put(csf, dt); } else if (element instanceof EEnumLiteral) { if (DATA_TYPE_MAPPING.containsKey(element)) return DATA_TYPE_MAPPING.get(element); dt = new EEnumLiteralBindingDataType((EEnumLiteral) element); DATA_TYPE_MAPPING.put(element, dt); } else if (element instanceof Class<?>) { if (DATA_TYPE_MAPPING.containsKey(element)) return DATA_TYPE_MAPPING.get(element); /* * Try to look up the instance class to find a proper EClassifier */ classifier = IBindingDataType.Factory.convertToClassifier((Class<?>) element); if (classifier != null) { dt = create(null, classifier); DATA_TYPE_MAPPING.put(classifier, dt); } else { dt = new JavaClassBindingDataType((Class<?>) element); } DATA_TYPE_MAPPING.put(element, dt); } else if (element == null) { if (DATA_TYPE_MAPPING.containsKey(element)) return DATA_TYPE_MAPPING.get(element); dt = create(null, EcorePackage.Literals.EJAVA_OBJECT); DATA_TYPE_MAPPING.put(element, dt); } else { LogUtils.error(element, "No IBindingDataType for " + element); //$NON-NLS-1$ dt = null; } return dt; } /** * Data object used as key in {@link BindingDataTypeFactory#DATA_TYPE_MAPPING} for structural * features. */ private static class CSF { public EClass eCls; public EStructuralFeature eSF; } /** * Tries to convert a Java class to the corresponding {@link EClassifier}. * <p> * All registered EMF packages are searched. * * @param cls the class to convert * @return the corresponding classifier or <code>null</code> if not found */ public static EClassifier convertToClassifier(Class<?> cls) { /* * The interface class that corresponds to clss - if any exist */ Class<?> ifCls = null; if (!cls.isInterface()) { final Class<?>[] interfaces = cls.getInterfaces(); if (interfaces.length > 0) { ifCls = interfaces[0]; } } /* * To check the ecore package first! Otherwise the XML Type package * [http://www.eclipse.org/emf/2003/XMLType] will overshadow the basic Java types. */ for (final EClassifier c : EcorePackage.eINSTANCE.getEClassifiers()) { if (c.getInstanceClass() == cls) return c; if (ifCls != null && c.getInstanceClass() == ifCls) return c; } /* * Now try all the packages */ final Registry registry = EPackage.Registry.INSTANCE; for (final Object v : registry.values()) { if (!(v instanceof EPackage)) { continue; } if (v == EcorePackage.eINSTANCE) { continue; } final EPackage ep = (EPackage) v; for (final EClassifier c : ep.getEClassifiers()) { if (c.getInstanceClass() == cls) return c; if (ifCls != null && c.getInstanceClass() == ifCls) return c; } } return null; } /** * Mapping from class to the set of super classes as defined by * {@link IAdapterManager#computeClassOrder(Class)}. */ private static final Map<IBindingDataType, IBindingDataType[]> SUPER_TYPE_MAPPING = new HashMap<IBindingDataType, IBindingDataType[]>(); /** * Returns a list of the {@link IBindingDataType} objects that defines all the super types of * the specified data type. * <p> * If not already calculated, then do that by creating an array with * <ul> * <li>IBDTs for all super types (ECore classes)</li> * <li>IBDTs for all super classes (Java classes) not already added from their Ecore * counterparts</li> * </ul> * * @param dt the data type to test * @return the super types */ public static IBindingDataType[] getSuperTypes(IBindingDataType dt) { IBindingDataType[] dts = SUPER_TYPE_MAPPING.get(dt); if (dts == null) { final List<IBindingDataType> dtList = new ArrayList<IBindingDataType>(); final EClassifier classifier = dt.getEType(); if (classifier != null) { dtList.add(create(null, classifier)); if (classifier instanceof EClass) { /* * getEAllSuperTypes() returns the reverse list with the grand-father super-type * first... */ final List<EClass> superTypes = ((EClass) classifier).getEAllSuperTypes(); for (int i = superTypes.size() - 1; i >= 0; i--) { final EClass e = superTypes.get(i); final IBindingDataType d = create(null, e); if (dtList.contains(d)) { continue; } dtList.add(d); } } } final Class<?>[] superClasses = Platform.getAdapterManager().computeClassOrder(dt.getDataType()); for (final Class<?> c : superClasses) { final IBindingDataType d = create(null, c); if (dtList.contains(d)) { continue; } dtList.add(d); } dts = dtList.toArray(new IBindingDataType[dtList.size()]); SUPER_TYPE_MAPPING.put(dt, dts); } return dts; } }