/* * 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.mapping.factory; import static org.teiid.designer.mapping.PluginConstants.PLUGIN_ID; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.edit.provider.IItemLabelProvider; import org.osgi.service.prefs.BackingStoreException; import org.teiid.core.designer.ModelerCoreException; import org.teiid.designer.core.ClearEObjectReferences; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.util.ModelContents; import org.teiid.designer.core.util.ModelResourceContainerFactory; import org.teiid.designer.core.util.ModelVisitorProcessor; import org.teiid.designer.core.util.NewModelObjectHelperManager; import org.teiid.designer.mapping.PluginConstants; import org.teiid.designer.metamodels.diagram.Diagram; import org.teiid.designer.metamodels.diagram.DiagramContainer; import org.teiid.designer.metamodels.diagram.DiagramEntity; import org.teiid.designer.metamodels.transformation.InputSet; import org.teiid.designer.metamodels.transformation.MappingClass; import org.teiid.designer.metamodels.transformation.MappingClassColumn; import org.teiid.designer.metamodels.transformation.MappingClassSet; import org.teiid.designer.metamodels.transformation.StagingTable; import org.teiid.designer.metamodels.transformation.TransformationFactory; /** * MappingClassFactory is a class for creating and editing mapping classes and attributes in a mappable tree. * * @since 8.0 */ public class MappingClassFactory { private static MappingClassBuilderStrategy defaultStrategy; private static final String STRATEGY_PREF_KEY = "MappingClassFactory.defaultStrategy"; //$NON-NLS-1$ private static final String COMPOSITOR_PREF_VALUE = "MappingClassFactory.compositorStrategy"; //$NON-NLS-1$ private static final String ITERATION_PREF_VALUE = "MappingClassFactory.iterationStrategy"; //$NON-NLS-1$ public static MappingClassBuilderStrategy getDefaultStrategy() { if (defaultStrategy == null) { IEclipsePreferences prefs = ModelerCore.getPreferences(PLUGIN_ID); IEclipsePreferences defaultPrefs = ModelerCore.getDefaultPreferences(PLUGIN_ID); String defValue = prefs.get(STRATEGY_PREF_KEY, defaultPrefs.get(STRATEGY_PREF_KEY, null)); if (COMPOSITOR_PREF_VALUE.equals(defValue)) { defaultStrategy = MappingClassBuilderStrategy.compositorStrategy; } else { defaultStrategy = MappingClassBuilderStrategy.iterationStrategy; } } return defaultStrategy; } public static void setDefaultStrategy( MappingClassBuilderStrategy strategy ) { IEclipsePreferences prefs = ModelerCore.getPreferences(PLUGIN_ID); if (strategy instanceof CompositorBasedBuilderStrategy) { prefs.put(STRATEGY_PREF_KEY, COMPOSITOR_PREF_VALUE); } else { assert (strategy instanceof IterationBasedBuilderStrategy); prefs.put(STRATEGY_PREF_KEY, ITERATION_PREF_VALUE); } defaultStrategy = strategy; try { ModelerCore.savePreferences(PLUGIN_ID); } catch (BackingStoreException e) { PluginConstants.Util.log(e); } } private EObject treeRoot; private ITreeToRelationalMapper mapper; private IMappableTree tree; private TreeMappingAdapter mapping; private AdapterFactory emfAdapter; private TransformationFactory metamodelFactory; private MappingClassSet mappingClassSet; private boolean generatingMappingClasses = false; /** * Construct an instance of MappingClassBuilder. */ public MappingClassFactory( ITreeToRelationalMapper mapper ) { this.mapper = mapper; this.tree = mapper.getMappableTree(); this.treeRoot = mapper.getMappableTree().getTreeRoot(); this.mapping = new TreeMappingAdapter(treeRoot); this.metamodelFactory = TransformationFactory.eINSTANCE; } /** * Construct an instance of MappingClassBuilder. */ public MappingClassFactory( ITreeToRelationalMapper mapper, TreeMappingAdapter mapping ) { this.mapper = mapper; this.tree = mapper.getMappableTree(); this.treeRoot = mapper.getMappableTree().getTreeRoot(); this.mapping = mapping; this.metamodelFactory = TransformationFactory.eINSTANCE; } public TreeMappingAdapter getMappingAdapter() { return mapping; } /** * Recursive method to generate Mapping Classes at and beneath the specified node. * * @param node the model object in the tree to begin looking for flat fragments. * @param autoPopulateAttributes if true, this method will automatically generate virtual mapping attributes within the new * mapping classes and assign their values to the mapping property of the corresponding document model nodes. * @return a Set of all datatypes assigned to the Mapping Class Columns that were created. */ public Set generateMappingClasses( EObject node, MappingClassBuilderStrategy strategy, boolean autoPopulateAttributes ) { setGeneratingMappingClasses(true); Set result = new HashSet(); Map mappingClassMap = strategy.buildMappingClassMap(node, this.tree, this.mapper); MappingClassGenerationVisitor visitor = new MappingClassGenerationVisitor(mapper, this, mappingClassMap, autoPopulateAttributes, true, result); MappableTreeIterator nodeIter = new MappableTreeIterator(this.tree); if (nodeIter.hasNext()) { // skip over the root itself nodeIter.next(); } while (nodeIter.hasNext()) { visitor.visit((EObject)nodeIter.next()); } finishGeneratingMappingClasses(autoPopulateAttributes); setGeneratingMappingClasses(false); return result; } private void setGeneratingMappingClasses( boolean generating ) { mapping.setGeneratingMappingClasses(generating); generatingMappingClasses = generating; } public boolean isGeneratingMappingClasses() { return generatingMappingClasses; } /** * Determine if a MappingClass can be created at the specified location. * * @param location * @return */ public boolean canCreateMappingClass( EObject location ) { // Defect 21999 // mapping AND mapper should NEVER be null if (mapping != null) { // if there is already a mapping class at this location, you can't add another one if (mapping.getMappingClass(location) != null) { return false; } } if (mapper != null) { return mapper.allowsMappingClass(location); } return false; } /** * Determine if a MappingClass can be created at the specified location. * * @param location * @return */ public boolean canCreateStagingTable( EObject location ) { // Defect 21999 // mapping AND mapper should NEVER be null if (mapping != null) { // if there is already a mapping class at this location, you can't add another one if (mapping.getStagingTable(location) != null) { return false; } } if (mapper != null) { // can put a StagingTable anywhere you can put a MappingClass return mapper.allowsMappingClass(location); } return false; } /** * Create a MappingClass at the specified location of the mappable tree. * * @param location * @param moveParentAttributes * @param markRecursive * @return */ public MappingClass createMappingClass( EObject location, boolean moveParentAttributes, boolean markRecursive ) { // System.out.println(" <<<< TEMP LOGGING >>>> MappingClassFactory.createMappingClass() START"); //$NON-NLS-1$ //startTracking("createMappingClass()"); //$NON-NLS-1$ MappingClass newMappingClass = metamodelFactory.createMappingClass(); if (newMappingClass != null) { boolean setGenerated = false; if (!isGeneratingMappingClasses()) { setGeneratingMappingClasses(true); setGenerated = true; } EObject newTreeMappingRoot = mapping.createTreeMappingRoot(newMappingClass); newMappingClass.setMappingClassSet(getMappingClassSet()); newMappingClass.setName(convertLocationNameToMappingClassName(location)); InputSet inputSet = metamodelFactory.createInputSet(); inputSet.setMappingClass(newMappingClass); mapping.addMappingClassAtLocation(newTreeMappingRoot, newMappingClass, location); if (markRecursive) { newMappingClass.setRecursionAllowed(true); } // Whenever a MappingClass is created, the corresponding Transformation should be created ModelContents modelContents = ModelerCore.getModelEditor().getModelContents(newMappingClass); // Fix for Defect 17638 if (modelContents != null) { // Check to see if transformation exists already. (Don't duplicate it) List allTransforms = modelContents.getTransformations(newMappingClass); if (allTransforms.isEmpty()) { // Defect 18433 - BML 9/15/05 - Changed to add both root object (and helpers) and // Transformation Diagram try { NewModelObjectHelperManager.helpCreate(newMappingClass, null); } catch (ModelerCoreException err) { PluginConstants.Util.log(err); } } } if (moveParentAttributes) { Collection documentNodes = getMappingClassExtentNodes(newMappingClass); Collection mappableDocumentNodes = new ArrayList(documentNodes.size() + 1); // first, see if this node itself is mappable if (mapper.isMappable(location)) { // make or move a mapping attribute for this attribute mappableDocumentNodes.add(location); } // next, gather all mappable nodes in the coarse extent Iterator iter = documentNodes.iterator(); while (iter.hasNext()) { EObject docNode = (EObject)iter.next(); if (mapper.isMappable(docNode) && !mappableDocumentNodes.contains(docNode)) { mappableDocumentNodes.add(docNode); } } // finally, move or create the attributes, as appropriate if (!mappableDocumentNodes.isEmpty()) { moveOrCreateMappingClassColumns(newMappingClass, mappableDocumentNodes, new HashMap(), false, new HashSet()); } } if (setGenerated) { setGeneratingMappingClasses(false); } } return newMappingClass; } /** * Create a MappingClass at the specified location of the mappable tree. * * @param location * @param moveParentAttributes * @param markRecursive * @return */ public MappingClass createMappingClass( EObject location, boolean markRecursive ) { //startTracking("createMappingClass(GENERATING)"); //$NON-NLS-1$ MappingClass result = metamodelFactory.createMappingClass(); if (result != null) { EObject newTreeMappingRoot = mapping.createTreeMappingRoot(result); result.setMappingClassSet(getMappingClassSet()); result.setName(convertLocationNameToMappingClassName(location)); mapping.addMappingClassAtLocation(newTreeMappingRoot, result, location); InputSet inputSet = metamodelFactory.createInputSet(); inputSet.setMappingClass(result); if (markRecursive) { result.setRecursionAllowed(true); } // Whenever a MappingClass is created, the corresponding Transformation should be created ModelContents modelContents = ModelerCore.getModelEditor().getModelContents(result); // Fix for Defect 17638 if (modelContents != null) { // Check to see if transformation exists already. (Don't duplicate it) List allTransforms = modelContents.getTransformations(result); if (allTransforms.isEmpty()) { // Defect 18433 - BML 9/15/05 - Changed to add both root object (and helpers) and // Transformation Diagram try { NewModelObjectHelperManager.helpCreate(result, null); } catch (ModelerCoreException err) { PluginConstants.Util.log(err); } } } } return result; } private void finishGeneratingMappingClasses( boolean createAttributes ) { // re-order keeper's structure Iterator iter = getMappingClassSet().getMappingClasses().iterator(); MappingClass mappingClass = null; while (iter.hasNext()) { mappingClass = (MappingClass)iter.next(); EObject attribute = mapping.getMappingClassLocation(mappingClass); finishCreateMappingClass(attribute, mappingClass, createAttributes); } } private void finishCreateMappingClass( EObject location, MappingClass mappingClass, boolean moveParentAttributes ) { if (mappingClass != null) { if (moveParentAttributes) { Collection documentNodes = getMappingClassExtentNodes(mappingClass); Collection mappableDocumentNodes = new ArrayList(documentNodes.size() + 1); // first, see if this node itself is mappable if (mapper.isMappable(location)) { // make or move a mapping attribute for this attribute mappableDocumentNodes.add(location); } // next, gather all mappable nodes in the coarse extent Iterator iter = documentNodes.iterator(); while (iter.hasNext()) { EObject docNode = (EObject)iter.next(); if (mapper.isMappable(docNode) && !mappableDocumentNodes.contains(docNode)) { mappableDocumentNodes.add(docNode); } } // finally, move or create the attributes, as appropriate if (!mappableDocumentNodes.isEmpty()) { moveOrCreateMappingClassColumns(mappingClass, mappableDocumentNodes, new HashMap(), true, new HashSet()); } } } } /** * Obtain an ordered list of all locations visible in the TreeViewer that are in the extent of the specified MappingClass. * * @param theMappingClass * @return */ public List getMappingClassExtentNodes( MappingClass theMappingClass ) { // get all locations for this mapping class List extentNodes = new ArrayList(); List locations = mapping.getMappingClassOutputLocations(theMappingClass); List columnLocations = new ArrayList(); for (Iterator iter = theMappingClass.getColumns().iterator(); iter.hasNext();) { columnLocations.addAll(mapping.getMappingClassColumnOutputLocations((MappingClassColumn)iter.next())); } if (!locations.isEmpty()) { List allMCLocations = mapping.getAllMappingClassLocations(); for (Iterator iter = locations.iterator(); iter.hasNext();) { EObject nextLocation = (EObject)iter.next(); // add the location to the collection of extent nodes extentNodes.add(nextLocation); // recurse down this location and collect up the extent nodes extentNodes.addAll(gatherExtentNodes(nextLocation, columnLocations, allMCLocations)); } } // return the extent nodes return extentNodes; } /** * Create a StagingTable at the specified location. * * @param location * @return */ public StagingTable createStagingTable( EObject location ) { StagingTable newStagingTable = metamodelFactory.createStagingTable(); if (newStagingTable != null) { EObject newTreeMappingRoot = mapping.createTreeMappingRoot(newStagingTable); newStagingTable.setMappingClassSet(getMappingClassSet()); newStagingTable.setName(convertLocationNameToStagingTableName(location)); // set the Document Reference property value on the MappingClass // Note: setLocation() will result in the TreeMappingRoot being created mapping.addStagingTableAtLocation(newTreeMappingRoot, newStagingTable, location); // Whenever a Staging Table is created, the corresponding Transformation should be created ModelContents modelContents = ModelerCore.getModelEditor().getModelContents(newStagingTable); // Fix for Defect 17638 if (modelContents != null) { // Check to see if transformation exists already. (Don't duplicate it) List allTransforms = modelContents.getTransformations(newStagingTable); if (allTransforms.isEmpty()) { // Defect 18433 - BML 9/15/05 - Changed to add both root object (and helpers) and // Transformation Diagram try { NewModelObjectHelperManager.helpCreate(newStagingTable, null); } catch (ModelerCoreException err) { PluginConstants.Util.log(err); } } } } return newStagingTable; } /** * Delete the specified StagingTable. Also unhooks the StagingTable from this tree mapping. * * @param table * @throws ModelerCoreException */ public void deleteStagingTable( StagingTable table ) throws ModelerCoreException { mapping.deleteMappingClass(table); } /** * Delete the specified MappingClass. Also unhooks the MappingClass from this tree mapping. * * @param mappingClass * @throws ModelerCoreException */ public void deleteMappingClass( MappingClass mappingClass ) throws ModelerCoreException { mapping.deleteMappingClass(mappingClass); } /** * Delete the specified MappingClassColumn. Also unhooks the column from any tree mappings. * * @param column * @throws ModelerCoreException */ public void deleteMappingClassColumn( MappingClassColumn column ) throws ModelerCoreException { ArrayList locationList = new ArrayList(mapping.getMappingClassColumnOutputLocations(column)); for (Iterator iter = locationList.iterator(); iter.hasNext();) { mapping.removeMappingClassColumnLocation(column, (EObject)iter.next()); } ModelerCore.getModelEditor().delete(column); } /** * Determine if the specified MappingClasses may be merged into one. This is only allowed if they are siblings or if the * upperMappingClass has a location that is an ancestor of the lower MappingClass. * * @param upperMappingClass * @param lowerMappingClass * @return */ public boolean canMergeMappingClasses( MappingClass upperMappingClass, MappingClass lowerMappingClass ) { boolean result = false; // two classes can be merged if their location nodes are siblings or if one is a descendent of the other List topLocations = mapping.getMappingClassOutputLocations(upperMappingClass); if (!topLocations.isEmpty()) { EObject topLocation = (EObject)topLocations.get(0); List bottomLocations = mapping.getMappingClassOutputLocations(lowerMappingClass); if (!bottomLocations.isEmpty()) { // any node will do EObject bottomLocation = (EObject)bottomLocations.get(0); EObject topParent = tree.getParent(topLocation); EObject bottomParent = tree.getParent(bottomLocation); if (topParent.equals(bottomParent)) { result = true; } else if (tree.isAncestorOf(topLocation, bottomLocation) || tree.isAncestorOf(bottomLocation, topLocation)) { // the can also be merged if one is an ancestor of the other result = true; } else { // the nodes must share a common Choice ancestor boolean keepGoing = true; EObject topChoice = null; EObject bottomChoice = null; while (keepGoing) { // see if we have found a choice node ancestor for topParent if (topChoice == null) { topParent = tree.getParent(topParent); if (topParent != null && tree.isChoiceNode(topParent)) { topChoice = topParent; } } // see if we have found a choice node ancestor for bottomParent if (bottomChoice == null) { bottomParent = tree.getParent(bottomParent); if (bottomParent != null && tree.isChoiceNode(bottomParent)) { bottomChoice = bottomParent; } } if (topChoice != null && bottomChoice != null) { // found two choice nodes, but they must be the same one result = topChoice.equals(bottomChoice); // no need to keep going keepGoing = false; } // if either returns null, then we're at the top - break out if (topParent == null || bottomParent == null) { keepGoing = false; } } } } } return result; } /** * Merge the specified mapping classes into one. This method assumes that canMergeMappingClasses has already been called and * returned true. * * @param upperMappingClass * @param lowerMappingClass * @throws ModelerCoreException */ public void mergeMappingClasses( MappingClass upperMappingClass, MappingClass lowerMappingClass, boolean removeDuplicates ) throws ModelerCoreException { setGeneratingMappingClasses(true); List upperLocations = mapping.getMappingClassOutputLocations(upperMappingClass); List lowerLocations = new ArrayList(mapping.getMappingClassOutputLocations(lowerMappingClass)); // if any bottom document node is a descendent of any top node (merging up to a parent), // then just add the bottom references to the top ones boolean keepLooking = true; Iterator topIter = upperLocations.iterator(); while (keepLooking && topIter.hasNext()) { EObject topLocation = (EObject)topIter.next(); Iterator bottomIter = lowerLocations.iterator(); while (keepLooking && bottomIter.hasNext()) { EObject bottomLocation = (EObject)bottomIter.next(); if (tree.isAncestorOf(topLocation, bottomLocation)) { keepLooking = false; } } } // if we never found an ancestor, then we are merging siblings if (keepLooking) { // add the bottom document references to the top Iterator bottomIter = lowerLocations.iterator(); while (bottomIter.hasNext()) { EObject location = (EObject)bottomIter.next(); mapping.addMappingClassLocation(upperMappingClass, location); mapping.removeMappingClassLocation(lowerMappingClass, location); } } Collection bottomAttributes = lowerMappingClass.getColumns(); Collection copyOfChildren = new ArrayList(bottomAttributes); // Move attributes from bottom mapping class to top mapping class. Iterator iter = copyOfChildren.iterator(); MappingClassColumn nextAttribute = null; MappingClassColumn duplicateAttribute = null; while (iter.hasNext()) { nextAttribute = (MappingClassColumn)iter.next(); List locations = mapping.getMappingClassColumnOutputLocations(nextAttribute); duplicateAttribute = getDuplicateAttributeMO(upperMappingClass, nextAttribute); if (duplicateAttribute != null && !removeDuplicates) { // changed the attribute naming strategy in 4.1 // duplicate attributes are always created if like-names are found in the merge. // both the old attribute and the new attribute will be renamed to avoid name clash. // might need to un-clash the duplicateAttribute name - see if the unique names match Collection existingColumns = upperMappingClass.getColumns(); HashMap existingNames = new HashMap(existingColumns.size() + 2); for (Iterator nameIter = existingColumns.iterator(); nameIter.hasNext();) { String theName = ((MappingClassColumn)nameIter.next()).getName(); existingNames.put(theName, theName); } if (duplicateAttribute.getName().equals(nextAttribute.getName())) { String dupeName = generateAttributeName(duplicateAttribute.getName(), existingNames, duplicateAttribute); List dupeLocations = this.mapping.getMappingClassColumnOutputLocations(duplicateAttribute); if (!dupeLocations.isEmpty()) { dupeName = generateAttributeName(duplicateAttribute.getName(), existingNames, (EObject)dupeLocations.get(0)); } ModelerCore.getModelEditor().rename(duplicateAttribute, dupeName); existingNames.put(dupeName, dupeName); } String newName = generateAttributeName(nextAttribute.getName(), existingNames, nextAttribute); if (!locations.isEmpty()) { newName = generateAttributeName(nextAttribute.getName(), existingNames, (EObject)locations.get(0)); } ModelerCore.getModelEditor().rename(nextAttribute, newName); } if (!removeDuplicates) { ModelerCore.getModelEditor().move(upperMappingClass, nextAttribute); for (Iterator locIter = locations.iterator(); locIter.hasNext();) { mapping.addMappingClassColumnLocation(nextAttribute, (EObject)locIter.next()); } } else if (duplicateAttribute == null) { ModelerCore.getModelEditor().move(upperMappingClass, nextAttribute); for (Iterator locIter = locations.iterator(); locIter.hasNext();) { mapping.addMappingClassColumnLocation(nextAttribute, (EObject)locIter.next()); } } } // Remove the associated transformation and diagram for the mapping class being deleted ModelContents modelContents = ModelerCore.getModelEditor().getModelContents(lowerMappingClass); if (modelContents != null) { // Delete the transformations for the mapping class List allTransforms = modelContents.getTransformations(lowerMappingClass); if (allTransforms != null && !allTransforms.isEmpty()) { Iterator tIter = allTransforms.iterator(); while (tIter.hasNext()) { ModelerCore.getModelEditor().delete((EObject)tIter.next(), true, false); } } // Delete the diagrams for the mapping class List allDiagrams = modelContents.getDiagrams(lowerMappingClass); if (allDiagrams != null && !allDiagrams.isEmpty()) { Iterator dIter = allDiagrams.iterator(); while (dIter.hasNext()) { ModelerCore.getModelEditor().delete((EObject)dIter.next(), true, false); } } // Clear all DiagramEntity references to the mapping class (defect 21451) clearDiagramRefs(lowerMappingClass); } // delete the mappingClass mapping.deleteMappingClass(lowerMappingClass); setGeneratingMappingClasses(false); } /** * Ensure that any modelObject references by a DiagramEntity to the specified EObject are either unset or removed * * @param eObj * @since 4.3 */ protected void clearDiagramRefs( final EObject eObj ) { if (eObj != null) { ModelContents modelContents = ModelerCore.getModelEditor().getModelContents(eObj); if (modelContents != null && modelContents.getDiagramContainer(false) != null) { DiagramContainer cntr = modelContents.getDiagramContainer(false); try { // Use a visitor to clear any non-containment references to this EObject final ClearEObjectReferences visitor = new ClearEObjectReferences(eObj); final ModelVisitorProcessor processor = new ModelVisitorProcessor(visitor); // Visit all EObject within the DiagramContainer processor.walk(cntr, ModelVisitorProcessor.DEPTH_INFINITE); // Remove any affected Diagram Entity instance that no longer references a model object for (Iterator iter = visitor.getAffectedObjects().iterator(); iter.hasNext();) { final EObject affectedObj = (EObject)iter.next(); if (affectedObj instanceof DiagramEntity && ((DiagramEntity)affectedObj).getModelObject() == null) { Diagram diagram = ((DiagramEntity)affectedObj).getDiagram(); diagram.getDiagramEntity().remove(affectedObj); } } } catch (ModelerCoreException err) { PluginConstants.Util.log(err); } } } } /** * Determine if the specified MappingClass may be split into two. This is an expensive method to call. * * @param theMappingClass * @return */ public boolean canSplitMappingClass( MappingClass theMappingClass, MappingClassBuilderStrategy strategy ) { boolean result = false; List locations = mapping.getMappingClassOutputLocations(theMappingClass); if (locations.size() > 1) { // any mapping class located at multiple document nodes can be split. result = true; } else { if (!locations.isEmpty()) { EObject location = (EObject)locations.get(0); if (location != null) { // build the MappingClass Map beneath the specified mapping class Map mappingClassMap = strategy.buildMappingClassMap(location, this.tree, this.mapper); // go through the keys and see if any do not already store a MappingClass Iterator iter = mappingClassMap.keySet().iterator(); while (iter.hasNext()) { EObject docNode = (EObject)iter.next(); // if there should be a mapping class here, but there is currently none if (shouldContainMappingClass(docNode) && mapping.getMappingClass(docNode) == null) { result = true; break; } } } } } return result; } /** * Recursive method to regenerate Mapping Classes at and beneath the specified node, for use with the "split mapping classes" * functionality. * * @param node the MetaObject in the XML document to begin looking for flat document fragments. * @param autoPopulateAttributes if true, this method will automatically generate virtual mapping attributes within the new * mapping classes and assign their values to the mapping property of the corresponding document model nodes. */ public void splitMappingClass( MappingClass mappingClass, MappingClassBuilderStrategy strategy, boolean moveAttributes ) { //startTracking("splitMappingClass()"); //$NON-NLS-1$ // build a map of where the mapping class algorithm thinks the mapping classes should be Map mappingClassMap = strategy.buildMappingClassMap(tree.getTreeRoot(), this.tree, this.mapper); // see if we have a multiple-location mapping class List docNodes = new ArrayList(mapping.getMappingClassOutputLocations(mappingClass)); if (docNodes.size() > 1) { // iterate through the nodes and create new mapping class at each one Iterator docIter = docNodes.iterator(); // skip the first one EObject remainingNode = (EObject)docIter.next(); while (docIter.hasNext()) { EObject docNode = (EObject)docIter.next(); MappingClass newClass = createMappingClass(docNode, false, false); Collection nodesToMap = (Collection)mappingClassMap.get(docNode); if (nodesToMap != null && !nodesToMap.isEmpty()) { moveOrCreateMappingClassColumns(newClass, nodesToMap, new HashMap(), false, new HashSet()); } } // update the reference list on only the original mapping class List locationsToRemove = new ArrayList(docNodes); locationsToRemove.remove(remainingNode); Iterator removeIter = locationsToRemove.iterator(); while (removeIter.hasNext()) { mapping.removeMappingClassLocation(mappingClass, (EObject)removeIter.next()); } } else { // not a multi-located mapping class List referencedNodes = mapping.getMappingClassOutputLocations(mappingClass); EObject documentNode = (EObject)referencedNodes.get(0); // launch the SplitterVisitor to re-generate the mapping classes MappingClassSplitterVisitor visitor = new MappingClassSplitterVisitor(mappingClass, mapping, mapper, this, mappingClassMap); MappableTreeIterator nodeIter = new MappableTreeIterator(this.tree, documentNode); while (nodeIter.hasNext()) { visitor.visit((EObject)nodeIter.next()); } } //stopTracking("splitMappingClass()"); //$NON-NLS-1$ // printTracking(); } /** * Determine if the specified location can be added as a mapping to the specified MappingClassColumn. Considers whether the * location is appropriate given the MappingClass location, and whether or not the location is currently mapped elsewhere. * * @param column * @param location * @return */ public boolean canAddLocation( MappingClassColumn column, EObject location ) { boolean result = false; // Precheck to see if location is an xml document node... if (ModelMapperFactory.isXmlTreeNode(location)) { // first, the location must not currently be mapped here result = !mapping.getMappingClassColumnOutputLocations(column).contains(location); if (result) { // second, the location must be at or below one of the mapping class locations MappingClass mc = column.getMappingClass(); List mcLocations = mapping.getMappingClassOutputLocations(mc); if (!mcLocations.contains(location)) { // if not a location, then it must be a decendent of one of the locations for (Iterator locIter = mcLocations.iterator(); locIter.hasNext();) { if (tree.isAncestorOf((EObject)locIter.next(), location)) { result = true; break; } } } } if (result) { // third, the location must not already be mapped result = mapping.getMappingClassColumn(location) == null; } } return result; } /** * Add the specified location as a mapping to the specified MappingClassColumn. Assumes that the canAddLocation method has * been called first and responded with true. * * @param column * @param location */ public void addLocation( MappingClassColumn column, EObject location ) { mapping.addMappingClassColumnLocation(column, location); } /** * Determines if the specified location may be removed from the MappingClassColumn mapping. The method simply checks to * determine that this mapping already exists. * * @param column * @param location * @return */ public boolean canRemoveLocation( MappingClassColumn column, EObject location ) { return mapping.getMappingClassColumnOutputLocations(column).contains(location); } /** * Remove the specified location as a mapping from the specified MappingClassColumn. Assumes that the canRemoveLocation method * has been called first and responded with true. * * @param column * @param location */ public void removeLocation( MappingClassColumn column, EObject location ) { mapping.removeMappingClassColumnLocation(column, location); } public MappingClassSet getMappingClassSet() { if (mappingClassSet == null) { // Defect 18433 - BML 8/31/05 - Changed call to create mapping class set via a new // utility method that correctly adds it to the model (via addValue()) mappingClassSet = ModelResourceContainerFactory.getMappingClassSet(this.treeRoot, true); } return mappingClassSet; } /** * Used by GenerateMappingClassesAction to enable/disable action. Only cares that the mappingClassSet is null or the set is * Empty (i.e. no mapping classes yet). * * @return canGenerate */ public boolean canGenerateMappingClasses() { boolean canGenerate = true; if (getMappingClassSet() == null || !getMappingClassSet().eContents().isEmpty()) { canGenerate = false; } return canGenerate; } /** * Access to the document's tree root reference. * * @return */ public EObject getTreeRoot() { return treeRoot; } // =========================================================================================================================== // PACKAGE METHODS // =========================================================================================================================== /** * Generate an appropriate name for a MappingClass to be located at the specified node. The algorithm generates a unique name * appropriate for the location. * * @return the generated name. */ String convertLocationNameToMappingClassName( EObject node ) { //startTracking("convertLocationNameToMappingClassName()"); //$NON-NLS-1$ String newName = getName(node); String possibleName = null; if (mapper.isContainerNode(node)) { // if this compositor has only one child node, then name the mapping class after the child Collection children = tree.getChildren(node); if (children.size() == 1) { EObject child = (EObject)children.iterator().next(); newName = getName(child); } else { EObject parent = tree.getParent(node); if (parent != null && tree.getTreeRoot() != parent) { newName = getName(parent); // generate a compositor-based name in case we need to disambiguate newName possibleName = getName(parent) + '_' + getName(node); } } } // see if we should try to use the compositor-based name // if ( names.contains(newName) && possibleName != null ) { if (mapping.containsMappingClassWithName(newName) && possibleName != null) { newName = possibleName; } // check to see if the name is allowed String result = newName; int suffix = 0; boolean tryAgain = true; while (tryAgain) { if (mapping.containsMappingClassWithName(result)) { result = newName + (++suffix); } else { tryAgain = false; } } //stopTracking("convertLocationNameToMappingClassName()"); //$NON-NLS-1$ return result; } String convertLocationNameToStagingTableName( EObject node ) { String newName = "ST_" + getName(node); //$NON-NLS-1$ // collect up the existing names so we can check for a conflict Collection existingStagingTables = mapping.getAllStagingTables(); Collection names = new ArrayList(existingStagingTables.size()); Iterator iter = existingStagingTables.iterator(); while (iter.hasNext()) { names.add(((StagingTable)iter.next()).getName()); } // check to see if the name is allowed String result = newName; int suffix = 0; boolean tryAgain = true; while (tryAgain) { if (names.contains(result)) { result = newName + (++suffix); } else { tryAgain = false; } } return result; } void moveOrCreateMappingClassColumns( MappingClass mappingClass, Collection documentNodeList, Map nameMappingClassColumnMap, boolean initialBuild, Set datatypeAccumulator ) { //startTracking("moveOrCreateMappingClassColumns()"); //$NON-NLS-1$ // iterate through the document nodes HashMap mcColumnNameMap = new HashMap(documentNodeList.size()); Iterator docNodeIter = (documentNodeList).iterator(); while (docNodeIter.hasNext()) { EObject nodeToMap = (EObject)docNodeIter.next(); boolean createMappingClassColumn = true; // see if there is already a Mapping Attribute for this document node MappingClassColumn mcColumn = null; if (!initialBuild) { mcColumn = mapping.getMappingClassColumn(nodeToMap); } if (mcColumn != null) { // there is an attribute mapped to this node. see how many nodes use this attribute. List locations = new ArrayList(mapping.getMappingClassColumnOutputLocations(mcColumn)); if (locations.size() > 1) { // this attribute is used by more than this one node. // delete this mapping and create the attribute in the new mapping class mapping.removeMappingClassColumnLocation(mcColumn, nodeToMap); } else { // move the node and don't create a new one. // TODO: detect name clash before the move? String mcColumnName = mcColumn.getName(); if (!mappingClassContainsAttribute(mappingClass, mcColumn)) { try { // Remove Location mapping.removeMappingClassColumnLocation(mcColumn, nodeToMap); ModelerCore.getModelEditor().move(mappingClass, mcColumn); // add location mapping.addMappingClassColumnLocation(mcColumn, nodeToMap); createMappingClassColumn = false; nameMappingClassColumnMap.put(mcColumnName, mcColumn); mcColumnNameMap.put(mcColumnName, mcColumnName); } catch (Exception e) { PluginConstants.Util.log(e); } } else { if (nameMappingClassColumnMap.get(mcColumnName) != null) { nameMappingClassColumnMap.put(mcColumnName, mcColumn); mcColumnNameMap.put(mcColumnName, mcColumnName); } createMappingClassColumn = false; } } } if (createMappingClassColumn) { // create the MappingAttribute MappingClassColumn existingMappingClassColumn = null; try { boolean createNewMappingClassColumn = true; String proposedNewMappingClassColumnName = getName(nodeToMap); // detect attribute name clashes for (final Iterator iter = nameMappingClassColumnMap.entrySet().iterator(); iter.hasNext();) { final Entry entry = (Entry)iter.next(); if (proposedNewMappingClassColumnName.equalsIgnoreCase((String)entry.getKey())) { // an attribute with this name already exists. // see if the types match also. existingMappingClassColumn = (MappingClassColumn)nameMappingClassColumnMap.get(proposedNewMappingClassColumnName); // NOTE: the following if() check will always be FALSE if (areDataTypesEquivalent(nodeToMap, existingMappingClassColumn)) { // same data types, so we'll reuse this MappingAttribute createNewMappingClassColumn = false; } else { // different data types, so we'll need an attribute name that doesn't clash proposedNewMappingClassColumnName = generateAttributeName(proposedNewMappingClassColumnName, mcColumnNameMap, nodeToMap); } break; } } MappingClassColumn newMappingClassColumn = null; if (createNewMappingClassColumn) { newMappingClassColumn = metamodelFactory.createMappingClassColumn(); newMappingClassColumn.setName(proposedNewMappingClassColumnName); newMappingClassColumn.setMappingClass(mappingClass); EObject datatype = tree.getDatatype(nodeToMap); if (datatype != null) { newMappingClassColumn.setType(datatype); datatypeAccumulator.add(datatype); } nameMappingClassColumnMap.put(proposedNewMappingClassColumnName, newMappingClassColumn); mcColumnNameMap.put(proposedNewMappingClassColumnName, proposedNewMappingClassColumnName); } // set the document node's mapping property to this attribute mapping.addMappingClassColumnLocation(newMappingClassColumn, nodeToMap); } catch (Exception e) { PluginConstants.Util.log(e); } } } //stopTracking("moveOrCreateMappingClassColumns()"); //$NON-NLS-1$ } // =========================================================================================================================== // PRIVATE METHODS // =========================================================================================================================== private boolean mappingClassContainsAttribute( MappingClass mappingClass, MappingClassColumn mappingClassColumn ) { boolean result = false; if (mappingClass.getColumns() != null && !mappingClass.getColumns().isEmpty()) { Iterator iter = mappingClass.getColumns().iterator(); MappingClassColumn nextColumn = null; while (iter.hasNext() && !result) { nextColumn = (MappingClassColumn)iter.next(); if (nextColumn == mappingClassColumn) result = true; } } return result; } String getName( EObject node ) { //startTracking("getName()"); //$NON-NLS-1$ if (emfAdapter == null) { emfAdapter = ModelerCore.getMetamodelRegistry().getAdapterFactory(); } // Cache the Label Providers here!!!!! IItemLabelProvider provider = (IItemLabelProvider)emfAdapter.adapt(node, IItemLabelProvider.class); //stopTracking("getName()"); //$NON-NLS-1$ return provider.getText(node); } private boolean shouldContainMappingClass( EObject node ) { boolean result = false; if (mapper.allowsMappingClass(node)) { if (mapper.canIterate(node)) { result = true; } else if (mapper.isRecursive(node)) { result = true; } } return result; } private boolean areDataTypesEquivalent( EObject documentNode, MappingClassColumn mappingAttribute ) { // NOTE: BML as of 5/10/07, both DefaultMappableTree & XmlMappableTree both have areEquivalent() Methods that are // hard-coded to return FALSE. SO, we are going to hard-code this here for performance purposes return false; // get the datatype for the tree node that the mappingAttribute is bound to // Collection locations = mapping.getMappingClassColumnOutputLocations(mappingAttribute); // if ( locations.isEmpty() ) { // return false; // } // EObject mappedNode = (EObject) locations.iterator().next(); // return mapper.getMappableTree().areEquivalent(mappedNode, documentNode); } private String generateAttributeName( String duplicateName, HashMap existingNames, EObject node ) { int count = 0; String baseName = mapper.getMappableTree().getUniqueName(node); String result = baseName; while (existingNames.get(result) != null) { result = baseName + (++count); } return result; } /** * Used by mergeMappingClasses, checks the proposed column to see if there is already a MappingClassColumn in the * topMappingClass by this name. * * @param topMappingClass * @param proposedColumn * @return */ private MappingClassColumn getDuplicateAttributeMO( MappingClass topMappingClass, MappingClassColumn proposedColumn ) { Collection topAttributes = topMappingClass.getColumns(); if (topAttributes == null || topAttributes.isEmpty()) { return null; } MappingClassColumn nextColumn = null; Iterator iter = topAttributes.iterator(); while (iter.hasNext()) { nextColumn = (MappingClassColumn)iter.next(); if (nextColumn.getName().equalsIgnoreCase(proposedColumn.getName())) { return nextColumn; } } return null; } /** * Recursive method used by getMappingClassExtentNodes to walk down a branch of the tree and find all nodes in the extent. * * @param visibleNode the branch node that this method will look beneath * @param columnLocations a Collection of tree nodes that should automatically be added in the result * @param mappingClassLocations a Collection of mapping class locations. Any node inside this collection should not be added * to the result. * @return */ private List gatherExtentNodes( EObject locationNode, Collection columnLocations, Collection mappingClassLocations ) { ArrayList result = new ArrayList(); for (Iterator childIter = mapper.getMappableTree().getChildren(locationNode).iterator(); childIter.hasNext();) { EObject node = (EObject)childIter.next(); // check to see if this node is mapped into the MappingClass by checking columnLocations if (columnLocations.contains(node)) { // if so, then this node is in the extent result.add(node); // recurse down this node's children result.addAll(gatherExtentNodes(node, columnLocations, mappingClassLocations)); } else { // see if there is a mapping class located at this node if (mappingClassLocations.contains(node)) { // stop; this node is in another extent. do not check this node's children. } else { // this node is in the extent result.add(node); // recurse down this node's children result.addAll(gatherExtentNodes(node, columnLocations, mappingClassLocations)); } } } return result; } /** * Method used primarily for JUnit testing. Allows generic named finder of XML Document element/attribtutes nodes NOTE: this * method finds the first named occurance of a element/attribute * * @param treeNodeName * @return * @since 5.0 */ public EObject findXmlDocumentTreeNode( String treeNodeName ) { XmlDocumentNodeFinderVisitor visitor = new XmlDocumentNodeFinderVisitor(treeNodeName); MappableTreeIterator nodeIter = new MappableTreeIterator(this.tree); if (nodeIter.hasNext()) { // skip over the root itself nodeIter.next(); } while (nodeIter.hasNext() && visitor.keepSearching()) { visitor.visit((EObject)nodeIter.next()); } return visitor.getTreeNode(); } /** * Simple visitor designed to look for an EObject who's name matches the target "treeNodeName" in the constructor * * @since 5.0 */ class XmlDocumentNodeFinderVisitor { EObject treeNode; String treeNodeName; public XmlDocumentNodeFinderVisitor( String name ) { super(); treeNodeName = name; } public void visit( EObject eObject ) { if (treeNode == null) { String eObjectName = getName(eObject); if (treeNodeName.equalsIgnoreCase(eObjectName)) { treeNode = eObject; } } } public boolean keepSearching() { return treeNode == null; } public EObject getTreeNode() { return treeNode; } } }