/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.core.compare; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.mapping.Mapping; import org.eclipse.emf.mapping.MappingFactory; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.util.ModelVisitor; import org.teiid.designer.core.util.ModelVisitorProcessor; /** * MappingProducer * * @since 8.0 */ public class MappingProducer { private final EObjectMatcherCache matchers; private final LinkedList unmappedObjects; private final Map inputsToOutputs; public MappingProducer() { this(new HashMap()); } /** * Construct an instance of MappingProducer. */ public MappingProducer( final HashMap inputsToOutputs ) { super(); this.matchers = new EObjectMatcherCache(); this.unmappedObjects = new LinkedList(); this.inputsToOutputs = inputsToOutputs; } public EObjectMatcherCache getEObjectMatcherCache() { return this.matchers; } public Map getInputsToOutputs() { return this.inputsToOutputs; } /** * This method creates the mappings between the list of inputs and outputs. * <p> * Note that this method will call the {@link MappingFactory#createMappingRoot()} method to create the outer mapping object * that is returned by this method. The {@link #createMappings(List, List, boolean, Mapping, MappingFactory)} method allows * the caller to pass in the outer mapping object to which all the mappings will be added. * </p> * * @param inputs the list of {@link org.eclipse.emf.ecore.EObject} instances that are to be the inputs of the computed * mappings; may not be null but may be empty * @param outputs the list of {@link org.eclipse.emf.ecore.EObject} instances that are to be the outputs of the computed * mappings; may not be null but may be empty * @param recursive true if this method should map not only the supplied inputs and outputs but it should also map their * children recursively; or false otherwise. * @param factory the {@link MappingFactory} that should be used to create the nested {@link Mapping} objects. * @param monitor the progress monitor; may not be null (use {@link org.eclipse.core.runtime.NullProgressMonitor} if needed) * @return the nested mapping structure containing the mappings between the inputs and outputs; never null * @see #createMappings(List, List, boolean, Mapping, MappingFactory) */ public Mapping createMappings( final List inputs, final List outputs, final boolean recursive, final MappingFactory factory, final IProgressMonitor monitor ) { CoreArgCheck.isNotNull(inputs); CoreArgCheck.isNotNull(outputs); CoreArgCheck.isNotNull(factory); // ------------------------------------------------------------- // Perform phase 0 - initialization // ------------------------------------------------------------- // Clear the mapping state ... this.unmappedObjects.clear(); // this.inputsToOutputs.clear(); // ------------------------------------------------------------- // Perform phase 1 - Match without inputs-to-outputs // ------------------------------------------------------------- // Create the outer Mapping object ... final Mapping mappingRoot = factory.createMapping(); final List inputCopies = new LinkedList(inputs); final List outputCopies = new LinkedList(outputs); // Copy all of the inputs/outputs into the mapping root inputs/outputs mappingRoot.getInputs().addAll(inputs); mappingRoot.getOutputs().addAll(outputs); // Add the mappings for the roots-level objects ... final List matchers = this.matchers.getEObjectMatchersForRoots(); final Iterator iter = matchers.iterator(); while (iter.hasNext()) { final EObjectMatcher matcher = (EObjectMatcher)iter.next(); matcher.addMappingsForRoots(inputCopies, outputCopies, mappingRoot, factory); if (inputCopies.isEmpty() || outputCopies.isEmpty()) { break; } } // If there are any left ... enqueueUnmappedMappings(null, inputCopies, outputCopies, mappingRoot, factory); // Go through all of the mappings that were generated ... doProcessNestedMappings(factory, mappingRoot); // ------------------------------------------------------------- // Perform phase 2 - Match remaining using inputs-to-outputs // ------------------------------------------------------------- // First, create the map of inputs to outputs ... initializeInputToOutputMapping(mappingRoot, inputsToOutputs); // This processes the queue and creates additional mappings ... while (this.unmappedObjects.size() != 0) { final UnmappedObjects uo = (UnmappedObjects)this.unmappedObjects.removeFirst(); final Mapping parentMapping = uo.parentMapping; // Make a copy of the existing nested mappings ... final List existingNested = new ArrayList(parentMapping.getNested()); final EReference ref = uo.reference; if (ref == null) { // root-level objects ... final List theMatchers = this.matchers.getEObjectMatchersForRoots(); final Iterator matcherIter = theMatchers.iterator(); while (matcherIter.hasNext()) { final EObjectMatcher matcher = (EObjectMatcher)matcherIter.next(); if (matcher instanceof TwoPhaseEObjectMatcher) { final TwoPhaseEObjectMatcher tpMatcher = (TwoPhaseEObjectMatcher)matcher; tpMatcher.addMappingsForRoots(uo.unmappedInputs, uo.unmappedOutputs, this.inputsToOutputs, parentMapping, factory); if (uo.unmappedInputs.isEmpty() || uo.unmappedOutputs.isEmpty()) { break; } } } // If there are remaining inputs and outputs, put them } else { // It is not a root-level object ... final List theMatchers = this.matchers.getEObjectMatchers(ref); final Iterator matcherIter = theMatchers.iterator(); while (matcherIter.hasNext()) { final EObjectMatcher matcher = (EObjectMatcher)matcherIter.next(); if (matcher instanceof TwoPhaseEObjectMatcher) { final TwoPhaseEObjectMatcher tpMatcher = (TwoPhaseEObjectMatcher)matcher; tpMatcher.addMappings(ref, uo.unmappedInputs, uo.unmappedOutputs, this.inputsToOutputs, parentMapping, factory); if (uo.unmappedInputs.isEmpty() || uo.unmappedOutputs.isEmpty()) { break; } } } } // Find the nested mappings that were added by this second phase, and then // process them. Do this by removing the ones that existed above final List nested = parentMapping.getNested(); if (nested.size() != existingNested.size()) { // Must have added at least one ... final List newNestedMappings = new ArrayList(parentMapping.getNested()); newNestedMappings.removeAll(existingNested); final Iterator newNestedIter = newNestedMappings.iterator(); while (newNestedIter.hasNext()) { final Mapping newNested = (Mapping)newNestedIter.next(); // Go through all of the mappings under new mappings ... doProcessMapping(factory, newNested); } } // Take anything that is left, and create the adds/deletes // (this may enqueue more unmatched) addUnmappedMappings(uo.unmappedInputs, uo.unmappedOutputs, parentMapping, factory); } return mappingRoot; } protected void doProcessNestedMappings( final MappingFactory factory, final Mapping mappingRoot ) { // Go through all of the mappings that were generated ... final Iterator nestedIter = mappingRoot.getNested().iterator(); while (nestedIter.hasNext()) { final Mapping nestedMapping = (Mapping)nestedIter.next(); doProcessMapping(factory, nestedMapping); } } protected void doProcessMapping( final MappingFactory factory, final Mapping mapping ) { final List nestedInputs = mapping.getInputs(); final List nestedOutputs = mapping.getOutputs(); if (nestedInputs.size() == 1 && nestedOutputs.size() == 1) { // The nested mapping has exactly 1 input and 1 output final EObject inputObj = (EObject)nestedInputs.get(0); final EObject outputObj = (EObject)nestedOutputs.get(0); final EClass inputEClass = inputObj.eClass(); final EClass outputEClass = outputObj.eClass(); // Only compare if the metaclasses are the same if (inputEClass.equals(outputEClass)) { // Iterate over all of the containment references ... final List refs = inputEClass.getEAllContainments(); final Iterator refIter = refs.iterator(); while (refIter.hasNext()) { final EReference ref = (EReference)refIter.next(); // Get the values for this ref from the input and output object ... final List inputValues = new LinkedList(); final List outputValues = new LinkedList(); if (ref.isMany()) { inputValues.addAll((List)inputObj.eGet(ref)); outputValues.addAll((List)outputObj.eGet(ref)); } else { final Object inputValue = inputObj.eGet(ref); final Object outputValue = outputObj.eGet(ref); if (inputValue != null) { inputValues.add(inputValue); } if (outputValue != null) { outputValues.add(outputValue); } } if (inputValues.size() != 0 && outputValues.size() != 0) { // There are both input values and output values, so run through the matchers ... final List refMatchers = this.matchers.getEObjectMatchers(ref); final Iterator refMatcherIter = refMatchers.iterator(); while (refMatcherIter.hasNext()) { final EObjectMatcher refMatcher = (EObjectMatcher)refMatcherIter.next(); refMatcher.addMappings(ref, inputValues, outputValues, mapping, factory); if (inputValues.isEmpty() || outputValues.isEmpty()) { break; } } } // If there are any left ... if (inputValues.size() != 0 || outputValues.size() != 0) { enqueueUnmappedMappings(ref, inputValues, outputValues, mapping, factory); } } } } // Go through all of the mappings that were generated ... doProcessNestedMappings(factory, mapping); } protected void enqueueUnmappedMappings( final EReference reference, final List inputs, final List outputs, final Mapping mapping, final MappingFactory factory ) { // If there is anything unmapped (on each side), put it into the queue ... if (inputs.size() != 0 || outputs.size() != 0) { final UnmappedObjects uo = new UnmappedObjects(reference, inputs, outputs, mapping); this.unmappedObjects.add(uo); } } protected void addUnmappedMappings( final List inputs, final List outputs, final Mapping mapping, final MappingFactory factory ) { // Any remaining inputs and outputs are extras, so create single-mappings for them ... final Iterator inputIter = inputs.iterator(); while (inputIter.hasNext()) { final EObject input = (EObject)inputIter.next(); final Mapping deletionMapping = factory.createMapping(); deletionMapping.getInputs().add(input); mapping.getNested().add(deletionMapping); } final Iterator outputIter = outputs.iterator(); while (outputIter.hasNext()) { final EObject output = (EObject)outputIter.next(); final Mapping additionMapping = factory.createMapping(); additionMapping.getOutputs().add(output); mapping.getNested().add(additionMapping); } } protected void initializeInputToOutputMapping( final Mapping mapping, final Map inputsToOutputsMap ) { // Walk through the mappings and build up the object equivalence maps ... final ModelVisitor visitor = new ModelVisitor() { @Override public boolean visit( final EObject object ) { if (object instanceof Mapping) { final Mapping mapping = (Mapping)object; final List inputs = mapping.getInputs(); final List outputs = mapping.getOutputs(); if (inputs.size() == 1 && outputs.size() == 1) { final EObject input = (EObject)inputs.get(0); final EObject output = (EObject)outputs.get(0); inputsToOutputsMap.put(input, output); } return true; } return false; } @Override public boolean visit( Resource resource ) { return true; } }; final ModelVisitorProcessor visitorProcessor = new ModelVisitorProcessor(visitor); try { visitorProcessor.walk(mapping, ModelVisitorProcessor.DEPTH_INFINITE); } catch (ModelerCoreException e) { ModelerCore.Util.log(e); } } protected class UnmappedObjects { protected final List unmappedInputs; protected final List unmappedOutputs; protected final Mapping parentMapping; protected final EReference reference; public UnmappedObjects( final EReference reference, final List unmappedInputs, final List unmappedOutputs, final Mapping parentMapping ) { this.unmappedInputs = unmappedInputs; this.unmappedOutputs = unmappedOutputs; this.parentMapping = parentMapping; this.reference = reference; } } }