/* * 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 java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.mapping.Mapping; import org.eclipse.emf.mapping.MappingRoot; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.I18nUtil; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.util.ListAndMapUtil; import org.teiid.designer.mapping.PluginConstants; import org.teiid.designer.metamodels.transformation.MappingClass; import org.teiid.designer.metamodels.transformation.MappingClassColumn; /** * TreeMappingClassColumnLocator provides a data-structure and data management methods to maintain an efficient set of information about * Mapping Class Columns mapped and referenced to a specific XML Document & XML Root * This class is designed to work in conjunction with the TreeMappingClassLocator (and hence MappingClassFactory). * * This improves performance and simplifies follow-on code maintenance. * Created: BML 5/9/07 * @since 8.0 */ public class TreeMappingClassColumnLocator implements PluginConstants { // --------------------------------------------------------------------------------------------------------------------------- // CONSTANTS // --------------------------------------------------------------------------------------------------------------------------- /** Properties file key prefix. Used for logging and localization. */ private static final String PREFIX = I18nUtil.getPropertyPrefix(TreeMappingAdapter.class); //private PerformanceTracker pTracker = new PerformanceTracker(getClass().getName()); // --------------------------------------------------------------------------------------------------------------------------- // FIELDS // --------------------------------------------------------------------------------------------------------------------------- /** */ private TreeMappingClassLocator mappingClassLocator; /** Map of key=tree node, value=Mapping Class */ private HashMap treeNodesToMappingClassColumnsMap = new HashMap(); /** * @since 5.0 */ public TreeMappingClassColumnLocator(TreeMappingClassLocator mappingClassLocator) { super(); this.mappingClassLocator = mappingClassLocator; } // private void startTracking(String method) { // pTracker.start(method); // } // // private void stopTracking(String method) { // pTracker.stop(method); // } // // public void print() { // pTracker.print(); // } // // public void resetTracker() { // pTracker.reset(); // } /** * Maps the specified <code>MappingClassColumn</code> to the specified tree node (<code>EObject</code>). * @param theMappingColumn the <code>Mapping</code> input * @param theTreeNode the <code>Mapping</code> output * @throws IllegalArgumentException if either input parameter is <code>null</code> */ public void addMappingClassColumnLocation(MappingClassColumn theMappingColumn, EObject theTreeNode) { //startTracking("addMappingClassColumnLocation()"); //$NON-NLS-1$ CoreArgCheck.isNotNull(theMappingColumn); CoreArgCheck.isNotNull(theTreeNode); MappingList theColumnMappingList = getMappingClassColumnMappingList(theMappingColumn); if ( theColumnMappingList != null ) { if (!theColumnMappingList.contains(theTreeNode)) { try { ModelerCore.getModelEditor().addValue(theColumnMappingList.mapping, theTreeNode, theColumnMappingList.list); // jh Lyra enh: Must update this map as well treeNodesToMappingClassColumnsMap.put( theTreeNode, theMappingColumn ); } catch (Exception e) { PluginConstants.Util.log(IStatus.ERROR, e, e.getMessage()); } } else { // adding mapping that already exists Util.log(IStatus.WARNING, Util.getString(PREFIX + "duplicateMapping", //$NON-NLS-1$ new Object[] {"addLocation", //$NON-NLS-1$ "MappingClassColumn", //$NON-NLS-1$ theMappingColumn, theTreeNode})); } } else { // should never happen Util.log(IStatus.ERROR, Util.getString(PREFIX + "mappingRootsProblem", //$NON-NLS-1$ new Object[] {"addLocation", //$NON-NLS-1$ "MappingClass", //$NON-NLS-1$ theMappingColumn, theTreeNode})); } //stopTracking("addMappingClassColumnLocation()"); //$NON-NLS-1$ } /** * Removes the specified {@link org.eclipse.emf.mapping.Mapping}. * * @param theMappingColumn * the <code>Mapping</code> input * @param theTreeNode * the <code>Mapping</code> output * @throws IllegalArgumentException * if either input parameter is <code>null</code> */ public void removeMappingClassColumnLocation(MappingClassColumn theMappingColumn, EObject theTreeNode) { //startTracking("removeMappingClassColumnLocation()"); //$NON-NLS-1$ CoreArgCheck.isNotNull(theMappingColumn); CoreArgCheck.isNotNull(theTreeNode); MappingList theColumnMappingList = getMappingClassColumnMappingList(theMappingColumn); if (theColumnMappingList != null) { try { ModelerCore.getModelEditor().removeValue(theColumnMappingList.mapping, theTreeNode, theColumnMappingList.list); // jh Lyra enh: Must update this map as well treeNodesToMappingClassColumnsMap.remove(theTreeNode); } catch (Exception e) { Util.log(IStatus.ERROR, e, Util.getString(PREFIX + "removeLocationTreeNodeNotFound", //$NON-NLS-1$ new Object[] { theTreeNode, "MappingClassColumn", //$NON-NLS-1$ theMappingColumn })); } } else { Util.log(IStatus.ERROR, Util.getString(PREFIX + "removeLocationTreeNodeNotFound", //$NON-NLS-1$ new Object[] { theTreeNode, "MappingClassColumn", //$NON-NLS-1$ theMappingColumn })); } //stopTracking("removeMappingClassColumnLocation()"); //$NON-NLS-1$ } public HashMap getTreeNodesToMappingClassColumnsMap() { //startTracking("getTreeNodesToMappingClassColumnsMap()"); //$NON-NLS-1$ if( treeNodesToMappingClassColumnsMap == null ) { loadTreeNodesToMappingClassColumnsMap(); } //stopTracking("getTreeNodesToMappingClassColumnsMap()"); //$NON-NLS-1$ return treeNodesToMappingClassColumnsMap; } /** * Obtains the <code>MappingClassColumn</code> where the specified tree node is mapped. * @param theTreeNode the tree node whose <code>MappingClassColumn</code> is being requested * @return the <code>MappingClassColumn</code> or <code>null</code> if not mapped */ public void loadTreeNodesToMappingClassColumnsMap() { //startTracking("loadTreeNodesToMappingClassColumnsMap()"); //$NON-NLS-1$ /* * This is a linear search, and the MCs are not necessarily in any * optimal order. Let's replace this with a HashMap. * * Note: There can be many treeNodes for one Mapping Class Column, * So the TreeNode must be the key to this hashmap, which is * what we intended. */ if ( treeNodesToMappingClassColumnsMap != null ) { treeNodesToMappingClassColumnsMap = new HashMap( treeNodesToMappingClassColumnsMap.keySet().size() ); } else { treeNodesToMappingClassColumnsMap = new HashMap(); } List mappingClasses = mappingClassLocator.getMappingClasses(); // for each mapping class, get mapping class columns, see if specified node is mapped for (int size = mappingClasses.size(), i = 0; i < size; i++) { MappingClass mappingClass = (MappingClass)mappingClasses.get(i); // null entries are contained in the list so check first if (mappingClass != null) { List columns = mappingClass.getColumns(); if ( ( columns != null ) && !columns.isEmpty() ) { for ( int numColumns = columns.size(), j = 0; j < numColumns; j++ ) { MappingClassColumn col = (MappingClassColumn)columns.get( j ); List treeNodes = getMappingClassColumnOutputLocations( col ); Iterator itNodes = treeNodes.iterator(); while( itNodes.hasNext() ) { EObject oTreeNodeTemp = (EObject)itNodes.next(); /* * jh Defect 21277: Shouldn't we also do this when we add a new MC? * ( Also see: getTreeNodesToMappingClassScopeMap */ treeNodesToMappingClassColumnsMap.put( oTreeNodeTemp, col ); } } } } } //stopTracking("loadTreeNodesToMappingClassColumnsMap()"); //$NON-NLS-1$ } /** * Obtains the tree nodes that are mapped to the specified <code>MappingClassColumn</code>. * @param theMappingColumn the <code>MappingClassColumn</code> whose mapped tree nodes are being requested * @return the mapped tree nodes or an empty list * @throws IllegalArgumentException if input parameter is <code>null</code> */ public MappingList getMappingClassColumnMappingList(MappingClassColumn theMappingColumn) { //startTracking("getMappingClassColumnMappingList()"); //$NON-NLS-1$ CoreArgCheck.isNotNull(theMappingColumn); MappingList result = null; MappingClass mappingClass = theMappingColumn.getMappingClass(); MappingRoot mappingRoot = (MappingRoot)mappingClassLocator.getMappingRoot(mappingClass); if (mappingRoot != null) { List columnMappings = mappingRoot.getNested(); if (!columnMappings.isEmpty()) { for (int size = columnMappings.size(), i = 0; i < size; i++) { Mapping mapping = (Mapping)columnMappings.get(i); // jh PERFORMANCE: change columns from a List to a Map, List columns = mapping.getInputs(); /** * If column size is only ONE, then let's NOT create a MAP */ if( columns.size() == 1 ) { Object column_1 = columns.get(0); if( column_1 != null && column_1 == theMappingColumn) { result = new MappingList(mapping, mapping.getOutputs()); break; } } else { HashMap columnsMap = ListAndMapUtil.createMapFromList( columns ); if (columnsMap.get(theMappingColumn) != null) { result = new MappingList(mapping, mapping.getOutputs()); break; } } } } // if mapping not found create it if (result == null) { Mapping newMapping = createColumnMapping(mappingRoot, theMappingColumn); result = new MappingList(newMapping, newMapping.getOutputs()); } } //stopTracking("getMappingClassColumnMappingList()"); //$NON-NLS-1$ return result; } /** * * @param theTreeNode * @param theMappingClass * @return * @since 5.0 */ public MappingClassColumn getMappingClassColumn(EObject theTreeNode, MappingClass theMappingClass) { //startTracking("getMappingClassColumn()"); //$NON-NLS-1$ CoreArgCheck.isNotNull(theTreeNode); MappingClassColumn result = null; for ( Iterator iter = theMappingClass.getColumns().iterator() ; iter.hasNext() ; ) { MappingClassColumn column = (MappingClassColumn) iter.next(); // jh PERFORMANCE: change currentLocations from a List to a Map, MappingList theColumnMappingList = getMappingClassColumnMappingList(column); if ( theColumnMappingList != null ) { /** * If List size is only ONE, then let's NOT create a MAP */ if( theColumnMappingList.list.size() == 1 ) { Object mappedNode = theColumnMappingList.list.get(0); if( mappedNode != null && mappedNode == theTreeNode ) { result = column; break; } } else if( theColumnMappingList.contains(theTreeNode) ) { result = column; break; } } } //stopTracking("getMappingClassColumn()"); //$NON-NLS-1$ return result; } public MappingClassColumn getMappingClassColumn(EObject theTreeNode) { //startTracking("getMappingClassColumn(TREE NODE)"); //$NON-NLS-1$ CoreArgCheck.isNotNull(theTreeNode); MappingClassColumn result = (MappingClassColumn)treeNodesToMappingClassColumnsMap.get( theTreeNode ); //stopTracking("getMappingClassColumn(TREE NODE)"); //$NON-NLS-1$ return result; } /** * Obtains the tree nodes that are mapped to the specified <code>MappingClassColumn</code>. * @param theMappingColumn the <code>MappingClassColumn</code> whose mapped tree nodes are being requested * @return an unmodifiable list of mapped tree nodes or an empty list * @throws IllegalArgumentException if input parameter is <code>null</code> */ public List getMappingClassColumnOutputLocations(MappingClassColumn theMappingColumn) { //startTracking("getMappingClassColumnOutputLocations()"); //$NON-NLS-1$ MappingList theColumnMappingList = getMappingClassColumnMappingList(theMappingColumn); List result = null; if( theColumnMappingList == null) { result = Collections.EMPTY_LIST; } else { result = Collections.unmodifiableList(theColumnMappingList.list); } //stopTracking("getMappingClassColumnOutputLocations()"); //$NON-NLS-1$ return result; } // --------------------------------------------------------------------------------------------------------------------------- // PRIVATE METHODS // --------------------------------------------------------------------------------------------------------------------------- /** * Creates a <code>Mapping</code> using the specified <code>MappingClassColumn</code> as it's input. * @param theMappingRoot the <code>MappingRoot</code> where the nested <code>Mapping</code> is being created * @param theMappingColumn the <code>MappingClassColumn</code> used as an input to the new <code>Mapping</code> * @return the new <code>Mapping</code> */ private Mapping createColumnMapping(MappingRoot theMappingRoot, MappingClassColumn theMappingColumn) { //startTracking("createColumnMapping()"); //$NON-NLS-1$ CoreArgCheck.isNotNull(theMappingRoot); CoreArgCheck.isNotNull(theMappingColumn); Mapping mapping = theMappingRoot.createMapping(Collections.singletonList(theMappingColumn), Collections.EMPTY_LIST); theMappingRoot.getNested().add(mapping); //stopTracking("createColumnMapping()"); //$NON-NLS-1$ return mapping; } }