com.nextep.datadesigner.vcs.impl.MergeStrategyDatabase.java Source code

Java tutorial

Introduction

Here is the source code for com.nextep.datadesigner.vcs.impl.MergeStrategyDatabase.java

Source

/*******************************************************************************
 * Copyright (c) 2011 neXtep Software and contributors.
 * All rights reserved.
 *
 * This file is part of neXtep designer.
 *
 * NeXtep designer is free software: you can redistribute it 
 * and/or modify it under the terms of the GNU General Public 
 * License as published by the Free Software Foundation, either 
 * version 3 of the License, or any later version.
 *
 * NeXtep designer is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     neXtep Softwares - initial API and implementation
 *******************************************************************************/
package com.nextep.datadesigner.vcs.impl;

import java.util.Collection;
import java.util.Map;
import org.apache.commons.collections.keyvalue.MultiKey;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.MultiKeyMap;
import com.nextep.datadesigner.exception.ErrorException;
import com.nextep.datadesigner.model.IElementType;
import com.nextep.datadesigner.model.INamedObject;
import com.nextep.datadesigner.model.IReference;
import com.nextep.datadesigner.model.IReferenceable;
import com.nextep.datadesigner.vcs.services.VersionHelper;
import com.nextep.designer.vcs.model.ComparisonScope;
import com.nextep.designer.vcs.model.IComparisonItem;
import com.nextep.designer.vcs.model.IMergeStrategy;
import com.nextep.designer.vcs.model.IMerger;
import com.nextep.designer.vcs.model.MergeInfo;
import com.nextep.designer.vcs.model.MergeStatus;

/**
 * A merge strategy for merging objects outside the repository. In this strategy, object types and
 * names are the main matching criteria.<br>
 * This strategy should be used when generating database objects from the repository to a target
 * database.<br>
 * This strategy is not thread-safe and should always be used within a single thread.
 * 
 * @author Christophe Fondacci
 * @author Bruno Gautier
 */
public class MergeStrategyDatabase extends MergeStrategy implements IMergeStrategy {

    // private static final Log log = LogFactory.getLog(MergeStrategyDatabase.class);

    private Map<MultiKey, IReferenceable> sourcesNameMap;
    private Map<MultiKey, IReferenceable> targetsNameMap;

    @Override
    public MergeInfo merge(IMerger merger, IComparisonItem result, IComparisonItem sourceRootDiff,
            IComparisonItem targetRootDiff) {
        // Initializing merge information
        MergeInfo mergeInfo = result.getMergeInfo();

        // Merging
        switch (result.getDifferenceType()) {
        case EQUALS:
            // When both items are equal we choose the target
            mergeInfo.setMergeProposal(result.getTarget());
            mergeSubItems(result, null, null);
            break;
        case MISSING_SOURCE:
            // We rely on the drop strategy
            if (shouldDrop(result.getTarget())) {
                mergeInfo.setMergeProposal(result.getSource());
            } else {
                mergeInfo.setMergeProposal(result.getTarget());
            }
            mergeSubItems(result, null, null);
            break;
        case MISSING_TARGET:
            mergeInfo.setMergeProposal(result.getSource());
            mergeSubItems(result, null, null);
            break;
        case DIFFER:
            MergeStatus status = mergeSubItems(result, null, null);
            if (status == MergeStatus.MERGE_UNRESOLVED) {
                mergeInfo.setMergeProposal(result.getSource());
            } else {
                mergeInfo.setStatus(status);
            }
            break;
        }
        return mergeInfo;
    }

    @Override
    public boolean existsInSources(IReferenceable target) {
        return sourcesNameMap.containsKey(getReferenceableKey(target));
    }

    @Override
    public boolean existsInTargets(IReferenceable source) {
        return targetsNameMap.containsKey(getReferenceableKey(source));
    }

    @Override
    public IReferenceable getMatchingSource(IReferenceable target) {
        return sourcesNameMap.get(getReferenceableKey(target));
    }

    @Override
    public IReferenceable getMatchingTarget(IReferenceable source) {
        return targetsNameMap.get(getReferenceableKey(source));
    }

    @Override
    public <V extends IReferenceable> void initializeCollectionComparison(Collection<V> sources,
            Collection<V> targets) {
        sourcesNameMap = hashByNameAndType(sources);
        targetsNameMap = hashByNameAndType(targets);
    }

    @Override
    public boolean match(IReferenceable source, IReferenceable target) {
        // We compare names of source and target items only if their types are the same
        if (source != null && target != null && getElementType(source).equals(getElementType(target))) {
            final String sourceName = getNamedObjectName(source);
            final String targetName = getNamedObjectName(target);

            return sourceName.equals(targetName);
        } else if (source == null && target == null) {
            return true;
        }
        return false;
    }

    public static INamedObject getNamedObject(IReferenceable item) {
        INamedObject namedItem = null;
        if (item instanceof IReference) {
            // Retrieving referenced instance
            IReferenceable ref = VersionHelper.getReferencedItem((IReference) item);
            // This instance should be named
            if (ref instanceof INamedObject) {
                namedItem = (INamedObject) ref;
            } else {
                throw new ErrorException("Unnamed object encountered during database target merge."); //$NON-NLS-1$
            }
        } else {
            // The specified item should be named
            if (item instanceof INamedObject) {
                namedItem = (INamedObject) item;
            } else {
                throw new ErrorException("Unnamed object encountered during database target merge."); //$NON-NLS-1$
            }
        }
        return namedItem;
    }

    /**
     * Convenience method to retrieve the name of the specified {@link IReferenceable} object. This
     * method handles the case sensitivity of the merge strategy by converting the name to upper
     * case when it is not case sensitive.
     * 
     * @param item the {@link IReferenceable} object from which we must retrieve the name
     * @return the name (converted to upper case if the merge strategy is not case sensitive) of the
     *         {@link IReferenceable} object if the referenced object implements
     *         {@link INamedObject}, null otherwise
     */
    public static String getNamedObjectName(IReferenceable item) {
        String name = null;
        /*
         * [BGA] FIXME: Maybe the #getNamedObject(IReferenceable) method should explicitly declare
         * that it might throw an ErrorException and the #getNamedObjectName(IReferenceable) method
         * should catch this error and replace the name by null or an empty string. But I cannot
         * think of a use case where the error should be caught, so I prefer to leave it as is.
         */
        final INamedObject namedItem = getNamedObject(item);
        if (namedItem != null) {
            name = namedItem.getName();
            if (!MergeStrategy.isCaseSensitive()) {
                return name.toUpperCase();
            }
        }
        return name;
    }

    /**
     * Hashes a collection of <code>IReferenceable</code> objects by their reference
     * 
     * @param <V> type of the collection which must be a subclass of <code>IReferenceable</code>
     * @param refList a collection of <code>IReferenceable</code>
     * @return a Map of the <code>IReferenceable</code> hashed by reference
     */
    @SuppressWarnings("unchecked")
    protected <V extends IReferenceable> Map<MultiKey, IReferenceable> hashByNameAndType(Collection<V> refList) {
        int size = refList.size();
        Map<MultiKey, IReferenceable> m = MultiKeyMap.decorate(new HashedMap((0 == size ? 1 : size)));

        for (V r : refList) {
            INamedObject o = getNamedObject(r);
            if (isCaseSensitive()) {
                m.put(new MultiKey(o.getName(), getElementType(r)), r);
            } else {
                m.put(new MultiKey(o.getName().toUpperCase(), getElementType(r)), r);
            }
        }
        return m;
    }

    private IElementType getElementType(IReferenceable ref) {
        return ((ref instanceof IReference) ? IElementType.getInstance(IReference.TYPE_ID)
                : ref.getReference().getType());
    }

    private MultiKey getReferenceableKey(IReferenceable ref) {
        return new MultiKey(getNamedObjectName(ref), getElementType(ref));
    }

    /**
     * This strategy has a DATABASE comparison scope because it is dedicated to comparison / merge
     * of database objects with repository objects.
     * 
     * @see com.nextep.designer.vcs.model.IMergeStrategy#getComparisonScope()
     */
    @Override
    public ComparisonScope getComparisonScope() {
        return ComparisonScope.DATABASE;
    }

}