/* * 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.jdbc.relational.impl; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.jdbc.metadata.JdbcNode; import org.teiid.designer.jdbc.relational.JdbcRelationalPlugin; import org.teiid.designer.metamodels.relational.RelationalEntity; /** * This ObjectMatcher class can be used to find the {@link EObject model object} that best matches a set of {@link JdbcNode * JdbcNode database objects}. Generally, a single instance of this class is created and used to match all of the JdbcNode * instances to the {@link EObject objects} in a model, but the {@link #findBestMatches(List, List)} method is invoked once with * the children from each model object and the children from the corresponding database object. This is because the * {@link #findBestMatches(List, List)} method only compares the name (rather than the full name or path to the object). * * @since 8.0 */ public class ObjectMatcher { /** * Default implementation of {@link MatchValueProvider} that simply performs a {@link Object#toString() toString()} on the * supplied object. */ static MatchValueProvider DEFAULT_MATCH_VALUE_PROVIDER = new MatchValueProvider() { @Override public String getMatchValue( final Object obj ) { CoreArgCheck.isNotNull(obj); return obj.toString(); } }; /** * Implementation of {@link MatchValueProvider} that returns the name of a {@link JdbcNode} object. This method assumes the * supplied object is an instance of {@link JdbcNode}. */ static MatchValueProvider JDBC_NODE_VALUE_PROVIDER = new MatchValueProvider() { @Override public String getMatchValue( final Object obj ) { CoreArgCheck.isNotNull(obj); return ((JdbcNode)obj).getName(); } }; /** * Implementation of {@link MatchValueProvider} that returns the name of a {@link RelationalEntity} object. This method * assumes the supplied object is an instance of {@link RelationalEntity}. */ static MatchValueProvider RELATION_OBJECT_NAME_PROVIDER = new MatchValueProvider() { @Override public String getMatchValue( final Object obj ) { CoreArgCheck.isNotNull(obj); return ((RelationalEntity)obj).getName(); } }; private final Map destination; private final LinkedList unmatchedModelObjects; private final LinkedList unmatchedNodes; private MatchValueProvider modelObjectNameProvider; private MatchValueProvider jdbcNodeNameProvider; /** * Construct an instance of MatchUtil. */ public ObjectMatcher( final Map destination ) { super(); this.destination = (destination != null ? destination : new HashMap()); this.unmatchedModelObjects = new LinkedList(); this.unmatchedNodes = new LinkedList(); this.modelObjectNameProvider = RELATION_OBJECT_NAME_PROVIDER; this.jdbcNodeNameProvider = JDBC_NODE_VALUE_PROVIDER; } /** * Find the best matches between the supplied lists of {@link JdbcNode} instances and {@link RelationalEntity} instances. Each * time a match between a JdbcNode and RelationalEntity, the pair are inserted into the {@link #getDestination() destination * map} with the JdbcNode as the key and the RelationalEntity as the value. * <p> * Any JdbcNode instances for which no matching RelationalEntity can be found are placed in the {@link #getUnmatchedNodes() * unmatched nodes} list. Similarly, any RelationalEntity instances for which no matching JdbcNode cannot be found are placed * in the {@link #getUnmatchedNodes() unmatched nodes} list. * </p> * <p> * This method can be called repeatedly on the same instance. * </p> * * @param jdbcNodes the list of JdbcNode instances; may not be null * @param modelObjects the list of RelationalEntity instances; may not be null */ public void findBestMatches( final List jdbcNodes, final List modelObjects ) { // See if we can quit quickly ... if (jdbcNodes.isEmpty()) { // Put all of the model objects in the unmatched list ... this.unmatchedModelObjects.addAll(modelObjects); return; } if (modelObjects.isEmpty()) { // Put all of the nodes in the unmatched list ... this.unmatchedNodes.addAll(jdbcNodes); return; } final List tempUnmatchedNodes = new LinkedList(); final List tempUnmatchedObjs = new LinkedList(); // ------------------------------------------------------------------------- // Find exact matches first // ------------------------------------------------------------------------- // Theoretically, any time a model is refreshed/updated, the names should not // differ by case. Therefore, by doing a case-sensitive match first, we're // most likely to find the majority of the matches. process(true, jdbcNodes, modelObjects, tempUnmatchedNodes, tempUnmatchedObjs); // ------------------------------------------------------------------------- // Find case-insensitive matches next // ------------------------------------------------------------------------- process(false, tempUnmatchedNodes, tempUnmatchedObjs, this.unmatchedNodes, this.unmatchedModelObjects); } /** * Method used to determine whether the supplied {@link JdbcNode JdbcNode object} has a type that matches the supplied * {@link RelationalEntity model object}. * * @param node * @param modelObject * @return */ protected boolean isMatchingType( final Object node, final Object modelObject ) { if (node instanceof JdbcNode && modelObject instanceof RelationalEntity) { final JdbcNode theNode = (JdbcNode)node; final RelationalEntity entity = (RelationalEntity)modelObject; final EClass entityEClass = entity.eClass(); final EClass nodeEClass = JdbcRelationalPlugin.getJdbcNodeToRelationalMapping().getRelationalClassForJdbcNode(theNode); if (entityEClass.equals(nodeEClass)) { return true; } } // If we don't know what the objects are, assume the types match return true; } protected void process( final boolean caseSensitive, final List nodes, final List objs, final List unmatchedNodes, final List unmatchedObjs ) { // Create a list of objects by name. In the situation where there are multiple objects // with the same name, the value in the map is changed to a List final Map objByName = new HashMap(); final Iterator iter = objs.iterator(); while (iter.hasNext()) { final Object obj = iter.next(); if (obj != null /*&& obj instanceof RelationalEntity*/) { final String name = caseSensitive ? this.modelObjectNameProvider.getMatchValue(obj) : this.modelObjectNameProvider.getMatchValue(obj).toUpperCase(); final Object existing = objByName.put(name, obj); if (existing != null) { // There were at least two objects with duplicate names if (existing instanceof List) { ((List)existing).add(obj); // add the object to the list objByName.put(name, existing); // put the list back into the map } else { List listOfExisting = new LinkedList(); listOfExisting.add(obj); // add the object to the list objByName.put(name, listOfExisting); // put the list back into the map } } } } // Iterator over the nodes and look up exact matches ... final Iterator nodeIter = nodes.iterator(); while (nodeIter.hasNext()) { final Object node = nodeIter.next(); final String name = caseSensitive ? this.jdbcNodeNameProvider.getMatchValue(node) : this.jdbcNodeNameProvider.getMatchValue(node).toUpperCase(); // Look up the object(s) with the same name ... Object objWithExactMatch = objByName.get(name); if (objWithExactMatch == null) { // Put into the unmatched unmatchedNodes.add(node); } else { if (objWithExactMatch instanceof LinkedList) { // Then there are multiple model objects with this name ... final LinkedList multipleMatches = (LinkedList)objWithExactMatch; // Find the first one that has a matching type ... objWithExactMatch = null; // clear this, in case no types match either final Iterator iterator = multipleMatches.iterator(); while (iterator.hasNext()) { final Object matchingObj = iterator.next(); if (isMatchingType(node, matchingObj)) { // This object matches by name and by type iterator.remove(); objWithExactMatch = matchingObj; break; } } // If there are no more items in the list, the clean up the list if (multipleMatches.isEmpty()) { objByName.remove(name); } } else { // There's only one match by name ... if (isMatchingType(node, objWithExactMatch)) { // The type also matches, so remove from the working map ... objByName.remove(name); } else { // Otherwise the type doesn't match objWithExactMatch = null; } } if (objWithExactMatch != null) { // Found a match, so put in the destination ... this.destination.put(node, objWithExactMatch); } } } // Put any remaining objs in the list ... final Iterator remainingObjsIter = objByName.entrySet().iterator(); while (remainingObjsIter.hasNext()) { final Map.Entry entry = (Map.Entry)remainingObjsIter.next(); final Object value = entry.getValue(); if (value instanceof List) { unmatchedObjs.addAll((List)value); } else { unmatchedObjs.add(value); } } } /** * @return */ public MatchValueProvider getJdbcNodeNameProvider() { return jdbcNodeNameProvider; } /** * @return */ public MatchValueProvider getModelObjectNameProvider() { return modelObjectNameProvider; } /** * @param provider */ public void setJdbcNodeNameProvider( MatchValueProvider provider ) { jdbcNodeNameProvider = provider; } /** * @param provider */ public void setModelObjectNameProvider( MatchValueProvider provider ) { modelObjectNameProvider = provider; } /** * @return */ public Map getDestination() { return destination; } /** * @return */ public LinkedList getUnmatchedModelObjects() { return unmatchedModelObjects; } /** * @return */ public LinkedList getUnmatchedJdbcNodes() { return unmatchedNodes; } }