/** * <copyright> * Copyright (c) 2010-2014 Henshin developers. 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 * </copyright> */ package org.eclipse.emf.henshin.interpreter.matching.constraints; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.emf.common.util.BasicEMap; import org.eclipse.emf.common.util.BasicEMap.Entry; import org.eclipse.emf.common.util.EMap; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.henshin.interpreter.util.HashList; /** * This constraint checks whether the value of an EReference contains * objects from the target domain. * * @author Enrico Biermann, Christian Krause */ public class ReferenceConstraint implements BinaryConstraint { // Target variable: final Variable targetVariable; // Reference: final EReference reference; // Index (either a constant or a parameter name): final Object index; // Whether the index is a constant or a parameter name: final boolean isConstantIndex; /** * Default constructor. * @param target Target variable. * @param reference Reference. * @param index Either a constant index (can be <code>null</code>) or a parameter name. * @param isConstantIndex Whether the index a constant or a parameter name. */ public ReferenceConstraint(Variable target, EReference reference, Object index, boolean isConstantIndex) { this.targetVariable = target; this.reference = reference; this.index = index; this.isConstantIndex = isConstantIndex; } /** * Convenience constructor. * @param target Target variable. * @param reference Reference. */ public ReferenceConstraint(Variable target, EReference reference) { this(target, reference, null, true); } /* * (non-Javadoc) * @see org.eclipse.emf.henshin.interpreter.matching.constraints.BinaryConstraint#check(org.eclipse.emf.henshin.interpreter.matching.constraints.DomainSlot, org.eclipse.emf.henshin.interpreter.matching.constraints.DomainSlot) */ @Override @SuppressWarnings("unchecked") public boolean check(DomainSlot source, DomainSlot target) { // Source variable must be locked: if (!source.locked) { return false; } // Get the target objects: List<EObject> targetObjects; if (reference.isMany()) { targetObjects = (List<EObject>) source.value.eGet(reference); // it is a map, so lets try if we have a key to use if (targetObjects instanceof EMap && targetVariable.typeConstraint != null && targetVariable.typeConstraint.type!= null) { @SuppressWarnings("rawtypes") EMap map = (EMap) targetObjects; EAttribute keyAttribute = (EAttribute) targetVariable.typeConstraint.type.getEStructuralFeature("key"); for (AttributeConstraint attributeConstraint : targetVariable.attributeConstraints) { if (keyAttribute == attributeConstraint.attribute) { Object to = null; try{ if (attributeConstraint.isConstantValue) { to = getEntryFromMap(map, attributeConstraint.value); } else { String paramName = (String) attributeConstraint.value; if (source.conditionHandler.isSet(paramName)) { Object paramValue = source.conditionHandler.getParameter(paramName); to = getEntryFromMap(map, paramValue); } //if not set, we simply fall back to the existing code to restrict the values of the domainslot } targetObjects = Collections.singletonList((EObject) to); } catch (Exception ex){/* fail silently and simply use the complete targetObjects list */ } break; // there will be only one "key" attribute constraint, thus we break the loop here. } } } if (targetObjects.isEmpty()) { return false; } } else { EObject obj = (EObject) source.value.eGet(reference); if (obj==null) { return false; } targetObjects = Collections.singletonList(obj); } // Calculate the index: Integer calculatedIndex = null; if (isConstantIndex) { calculatedIndex = (index!=null) ? ((Number) index).intValue() : null; } else { String parameterName = (String) index; if (source.conditionHandler.isSet(parameterName)) { calculatedIndex = ((Number) source.conditionHandler.getParameter(parameterName)).intValue(); } } // Take care of negative indices: if (calculatedIndex!=null && calculatedIndex<0) { calculatedIndex = targetObjects.size() + calculatedIndex; } // Target domain slot locked? if (target.locked) { // Check if the parameter value still needs to be set: if (!isConstantIndex && !source.conditionHandler.isSet((String) index)) { // Try to initialize the parameter with real index. Might fail due to attribute conditions. calculatedIndex = targetObjects.indexOf(target.value); if (target.conditionHandler.setParameter((String) index, calculatedIndex)) { target.initializedParameters.add((String) index); return true; } else { target.conditionHandler.unsetParameter((String) index); return false; } } else { // Check if the reference constraint if fulfilled: return (calculatedIndex!=null) ? targetObjects.indexOf(target.value)==calculatedIndex : targetObjects.contains(target.value); } } else { // Target not locked yet. Create a domain change to restrict the target domain: DomainChange change = new DomainChange(target, target.temporaryDomain); source.remoteChangeMap.put(this, change); // Calculated temporary domain: if (calculatedIndex!=null) { if (calculatedIndex>=0 && calculatedIndex<targetObjects.size()) { target.temporaryDomain = Collections.singletonList(targetObjects.get(calculatedIndex)); } else { target.temporaryDomain = Collections.emptyList(); } } else { target.temporaryDomain = new HashList<EObject>(targetObjects); } if (change.originalValues!=null) { target.temporaryDomain.retainAll(change.originalValues); } // Temporary domain must not be empty: return !target.temporaryDomain.isEmpty(); } } /* * This method resembles the code in V BasicEMap::get(Object key) in order to get the entry (not only the value) from the map. * Unfortunately, the public interface does not allow getting the entry. However, we need the entry to properly use the * ReferenceConstaint to filter the targetObjects (which is a set of entries). Thus, we revert back to reflection to call the * protected methods directly which are used in the V BasicEMap::get(Object key) method. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private Entry getEntryFromMap(EMap map, Object paramValue) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class mapClass = map.getClass(); // we need to get the BasicEMap class since it contains the protected methods while (mapClass != null && !mapClass.equals(BasicEMap.class)) { mapClass = mapClass.getSuperclass(); } if (mapClass == null) throw new NoSuchMethodException("Map class does not subclass from BasicEMap which is required to call hashOf, indexOf and entryForKey methods"); // ensureEntryDataExists(); Method ensureEntryDataExists = mapClass.getDeclaredMethod("ensureEntryDataExists"); ensureEntryDataExists.setAccessible(true); ensureEntryDataExists.invoke(map); // int hash = hashOf(key); Method hashOf = mapClass.getDeclaredMethod("hashOf", Object.class); hashOf.setAccessible(true); int hash = (Integer) hashOf.invoke(map, paramValue); // int index = indexOf(hash); Method indexOf = mapClass.getDeclaredMethod("indexOf", int.class); indexOf.setAccessible(true); int index = (Integer) indexOf.invoke(map, hash); // Entry<K, V> entry = entryForKey(index, hash, key); Method entryForKey = mapClass.getDeclaredMethod("entryForKey", int.class, int.class, Object.class); entryForKey.setAccessible(true); Entry entry = (Entry) entryForKey.invoke(map, index, hash, paramValue); return entry; } }