/*
* Copyright (c) 2006, 2010 Borland Software Corporation and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michael Golubev (Borland) - initial API and implementation
* Artem Tikhomirov (independent) support to handle not-matched new elements (Cleaner)
*/
package org.eclipse.gmf.internal.common.reconcile;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
public class ReconcilerConfigBase implements ReconcilerConfig {
private static final EClassRecord EMPTY_RECORD = new EClassRecord();
private final HashMap<EClass, EClassRecord> myEClass2Record;
private final HashMap<EClass, EClassRecord> myAbstractEClass2SubclassesRecord;
public ReconcilerConfigBase(){
myEClass2Record = new HashMap<EClass, EClassRecord>();
myAbstractEClass2SubclassesRecord = new HashMap<EClass, EClassRecord>();
}
public final Matcher getMatcher(EClass eClass) {
Matcher result = getRecord(eClass, false).getMatcher();
if (result != Matcher.FALSE) {
return result;
}
// XXX Correct strategy whould be to look up first *non-default*
// matcher in the hierarchy, however, for now, expect no more that
// two records per hierarchy chain (e.g. a nondefault matcher for superclass
// plus a record with default matcher for subclass
return getExistingRecordFromHierarchy(eClass).getMatcher();
}
public Copier getCopier(EClass eClass) {
return getRecord(eClass, false).getCopier();
}
public Cleaner getCleaner(EClass eClass) {
return getRecord(eClass, false).getCleaner();
}
public final Decision[] getDecisions(EClass eClass) {
return getRecord(eClass, false).getDecisions();
}
protected final void setMatcher(EClass eClass, Matcher matcher){
getRecord(eClass, true).setMatcher(matcher);
}
protected final void setMatcher(EClass eClass, EAttribute attribute){
checkStructuralFeature(eClass, attribute);
setMatcher(eClass, new ReflectiveMatcher(attribute));
}
protected final void setMatcher(EClass eClass, EReference reference) {
checkStructuralFeature(eClass, reference);
// XXX Perhaps, for cases, when reference's target is in some other package,
// might be reasonable to have an alternative matching, non-resolving, just comparing proxyURI?
setMatcher(eClass, new ReflectiveMatcher(reference));
}
protected final void setMatcherForAllSubclasses(EClass eClass, Matcher matcher){
checkAbstract(eClass);
getTemplateRecord(eClass, true).setMatcher(matcher);
}
protected final void setCopier(EClass eClass, Copier copier){
getRecord(eClass, true).setCopier(copier);
}
protected final void setCopierForAllSubclasses(EClass eClass, Copier copier){
checkAbstract(eClass);
getTemplateRecord(eClass, true).setCopier(copier);
}
protected final void setCleaner(EClass eClass, Cleaner cleaner) {
getRecord(eClass, true).setCleaner(cleaner);
}
protected final void setCleanerForAllSubclasses(EClass eClass, Cleaner cleaner) {
checkAbstract(eClass);
getTemplateRecord(eClass, true).setCleaner(cleaner);
}
private static void checkAbstract(EClass eClass){
if (!eClass.isAbstract()){
throw new IllegalArgumentException(
"This is not safe method that may lead to strange behaviour in case of multiple inheritance. " +
"We tried to limit its usage as much as possible");
}
}
protected final void addDecision(EClass eClass, Decision decision){
getRecord(eClass, true).addDecision(decision);
}
private EClassRecord getRecord(EClass eClass, boolean force){
EClassRecord result = myEClass2Record.get(eClass);
if (result == null){
if (force){
result = new EClassRecord();
myEClass2Record.put(eClass, result);
} else {
result = getExistingRecordFromHierarchy(eClass);
if (result != EMPTY_RECORD){
//cache it for the next time
myEClass2Record.put(eClass, result);
}
}
}
return result;
}
/**
* Looks through the hierarchy of superclasses, checking for registered
* records for abstract classes.
* @return never null, {@link #EMPTY_RECORD} in case none found
*/
private EClassRecord getExistingRecordFromHierarchy(EClass eClass) {
EClassRecord result= EMPTY_RECORD;
for (Iterator<EClass> superClasses = eClass.getEAllSuperTypes().iterator(); result == EMPTY_RECORD && superClasses.hasNext();){
EClass nextSuper = superClasses.next();
if (nextSuper.isAbstract()) {
result = getTemplateRecord(nextSuper, false);
}
}
return result;
}
private EClassRecord getTemplateRecord(EClass abstractSuperClass, boolean force){
assert abstractSuperClass.isAbstract();
EClassRecord result = myAbstractEClass2SubclassesRecord.get(abstractSuperClass);
if (result == null && force){
result = new EClassRecord();
myAbstractEClass2SubclassesRecord.put(abstractSuperClass, result);
}
return result == null ? EMPTY_RECORD : result;
}
private static void checkStructuralFeature(EClass expectedClass, EStructuralFeature feature) {
if (expectedClass.getEStructuralFeature(feature.getFeatureID()) != feature){
throw new IllegalArgumentException(MessageFormat.format("Alien feature {0} for EClass {1}", new Object[] {feature, expectedClass}));
}
}
protected static final Matcher ALWAYS_MATCH = new Matcher(){
public boolean match(EObject current, EObject old) {
return current.eClass().equals(old.eClass());
}
};
private static class EClassRecord {
private Matcher myMatcher = Matcher.FALSE;
private Copier myCopier = Copier.NEVER_COPY;
private Cleaner myCleaner = new Cleaner();
private final List<Decision> myDecisions = new LinkedList<Decision>();
private Decision[] myMakersArray;
public void addDecision(Decision maker){
myDecisions.add(maker);
makersSetChanged();
}
public void setCopier(Copier copier) {
myCopier = copier;
}
public void setCleaner(Cleaner cleaner) {
myCleaner = cleaner;
}
public Decision[] getDecisions(){
if (myMakersArray == null){
myMakersArray = myDecisions.toArray(new Decision[myDecisions.size()]);
}
return myMakersArray;
}
public void setMatcher(Matcher matcher) {
myMatcher = matcher;
}
public Matcher getMatcher() {
return myMatcher;
}
public Copier getCopier() {
return myCopier;
}
public Cleaner getCleaner() {
return myCleaner;
}
private void makersSetChanged(){
myMakersArray = null;
}
}
}