/* license-start
*
* Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>.
*
* This program 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 version 3.
*
* This program 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, at <http://www.gnu.org/licenses/>.
*
* Contributors:
* Crispico - Initial API and implementation
*
* license-end
*/
package com.crispico.flower.mp.codesync.merge;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
/**
* Contains an algorithm based on the CodeSync algorithm that is used
* when merging models during team work - (C-MRG).
*
* <p>
* It has been adapted, so that this class may be overridden and
* used for other purposes:
* <ul>
* <li>in the internal action that combines 2 duplicate elements (that
* may result after the merge of 2 models that were not 100% in sync
* with the code,
* <li>when merging user libraries within the application - (CS-LIB).
* </ul>
*
* The logic in this class is intended for (C-MRG). Some parts of the
* algorithm have been broken down into methods (instead of blocks of
* code) in order to disable and/or customize blocks of code, for (CS-LIB).
*
* <p>
* Although it uses the same concepts and principles as the CodeSync
* algorithm, it was rewritten because we thought it was dangerous to
* alter the CodeSync code (no unit tests, no clear spec; the "dam"
* effect). However, on long term, this code will be merged with the
* CodeSync code.
*
* <p>
* There are activity diagrams that depict this algorithm.
*
* @author Cristi
*
*/
public class ModelMerge {
// /**
// *
// * Replace the usual DeleteCommand to make sure validation errors annotations are also removed
// * preventing Dangling HRefExceptions to happen
// *
// * @author Luiza
// */
// public static class ModelMergeDeleteCommand extends FlowerDeleteCommand {
//
// public ModelMergeDeleteCommand(FlowerEditingDomain domain, Collection<?> collection) {
// super(domain, collection);
// }
//
// public ModelMergeDeleteCommand(EditingDomain domain, DeleteContext context) {
// super(domain, context);
// }
//
// protected boolean shouldNotRemoveInverseReference(EObject currentDeletedObject, Setting setting) {
// return false; // remove all inverse references no exception
// }
//
// protected boolean shouldRemoveReferencingEObject(EObject referencingEObject, EStructuralFeature eStructuralFeature) {
// // remove validation errors along with the object
// if(referencingEObject instanceof EAnnotation && "Validation error".equals(((EAnnotation)referencingEObject).getSource())) {
// return true;
// }
// return super.shouldRemoveReferencingEObject(referencingEObject, eStructuralFeature);
// }
//
// protected Command create(EditingDomain domain, Collection<?> collection) {
// ModelMergeDeleteCommand cmd = new ModelMergeDeleteCommand(domain, new DeleteContext(collection, ancestorAllContentsList, additionalInfo));
// return cmd;
// }
// }
//
/**
* Needs to be initialized by the CodeSync metamodel with
* CodeSyncPackage.eINSTANCE.getTimeStampedSyncElement_SyncTimeStamp()
* This hack is done because ~.mp project cannot depend on ~.mp.codesync,
* and a more general approach, just for this small case, seems to be quite an overkill.
*
*/
public static EStructuralFeature codeSyncTimeStampFeature;
//
// /**
// * Switch that can be used to show/hide "COPY" log messages for newly added elements.
// * This kind of operations are unlikely to cause problems and they pollute the log
// * considerably.
// *
// * If enabled, this kind of entries are prefixed with ">>>".
// */
// protected boolean logNewElementNestedAddAndCopy = false;
//
// /**
// * Used by <code>logWithTime()</code>.
// *
// */
// protected long previousTime;
// /**
// * Variable used during the algorithm.
// *
// * @see #setFeature()
// * @see #processDeferredOperations()
// *
// */
// protected List<DeferredReferenceToSetEntry> deferredReferenceToSetEntries = new ArrayList<DeferredReferenceToSetEntry>();
//
// /**
// * Variable used during the algorithm.
// *
// * @see #processDeferredOperations()
// */
// protected Set<EObject> deferredObjectToDelete = new HashSet<EObject>();
//
// /**
// * Variable used during the algorithm.
// *
// * @see #mergeStandardFeature()
// * @see #getAncestorResource()
// *
// */
// protected Resource ancestorResource;
//
// /**
// * The classes which provides functionality for showing
// * the merge mechanism log must implement this interface.
// *
// * @author Cristina
// */
// public interface ILogProvider {
// /**
// * The method writes the given <code>message</code> in log file.
// * @param message
// */
// void writeToLog(String message);
// }
//
// /**
// *
// */
// protected ILogProvider logProvider;
//
// /**
// * @return the logProvider
// */
// public ILogProvider getLogProvider() {
// return logProvider;
// }
//
// /**
// * @param logProvider the logProvider to set
// *
// */
// public void setLogProvider(ILogProvider logProvider) {
// this.logProvider = logProvider;
// }
//
// /**
// * @return The xmi:id of the element.
// *
// */
// protected Object getKeyForEObject(EObject object) {
// return object.eResource().getURIFragment(object);
// }
//
// /**
// * External entry point for algorithm. Resets <code>deferredSetReferenceEntries</code>,
// * invokes the algorithm and invokes <code>processDeferredSetReferenceEntries</code>.
// *
// * See <code>merge()</code> for additional doc.
// *
// */
// public boolean doMerge(EObject ancestor, EObject left, EObject right) {
// deferredReferenceToSetEntries.clear();
// this.ancestorResource = ancestor.eResource();
// try {
// if (!merge(ancestor, left, right, false))
// return false;
// processDeferredOperations();
// } finally {
// // this is necessary to avoid memory leak.
// this.ancestorResource = null;
// }
// return true;
// }
//
// /**
// * The algorithm's (recursive) entry point.
// *
// * @param ancestor A common ancestor of left and right. May be null, meaning that object was newly added in right.
// * A new (corresponding) instance has been created in left, and a full copy is in progress.
// * @param left Contains MY modifications; it is also the destination of the merge.
// * @param right Contains OTHER's modifications (i.e. from SVN); the source of the merge.
// * @param leftWasDeleted If <code>true</code> then the processing looks that left was
// * deleted and we do the processing only to look for modifications (and to generate
// * conflicts).
// * @return <code>false</code> if conflicts were detected.
// *
// */
// protected boolean merge(EObject ancestor, EObject left, EObject right, boolean leftWasDeleted) {
// for (EStructuralFeature feature : left.eClass().getEAllStructuralFeatures()) {
// if (feature.isDerived() || feature.isTransient() || !feature.isChangeable())
// continue; // filter out features that don't need to be processed
// else if (feature instanceof EReference && ((EReference) feature).isContainment()) {
// // feature with children
// if (!mergeContainmentFeature(feature, ancestor, left, right, leftWasDeleted))
// return false;
// } else {
// // "normal" feature
// if (!mergeStandardFeature(feature, ancestor, left, right, leftWasDeleted))
// return false;
// }
// }
// return true;
// }
//
// /**
// * Merges EAttributes or EReferences that are not containment.
// *
// * @param ancestor May be null, meaning that object was newly added in right.
// * A new (corresponding) instance has been created in left, and a full copy is in progress.
// * @return false if conflicts.
// *
// */
// protected boolean mergeStandardFeature(EStructuralFeature feature, EObject ancestor, EObject left, EObject right, boolean leftWasDeleted) {
// Object leftValue = getFeature(left, feature);
// Object rightValue = getFeature(right, feature);
// Object ancestorValue = ancestor != null ? getFeature(ancestor, feature) : leftValue;
// if (!safeEquals(rightValue, leftValue) && !safeEquals(rightValue, ancestorValue))
// if (!safeEquals(ancestorValue, leftValue)) {
// // conflict
// log("%s, feature %s: CONFLICT\n\tAncestor value:\t%s\n\tLeft value:\t%s\n\tRight value:\t%s", getFullyQualifiedName(left), feature.getName(), ancestorValue, leftValue, rightValue);
// return false;
// } else {
// if (leftWasDeleted) {
// // conflict
// log("%s, feature %s: CONFLICT on left DELETE. The left element (or someone from the upper hierarchy) was deleted.\n\tAncestor value:\t%s\n\tLeft value:\t%s\n\tRight value:\t%s", getFullyQualifiedName(left), feature.getName(), ancestorValue, leftValue, rightValue);
// return false;
// }
// // left == ancestor, so it was unchanged; we'll copy
// if (!setFeature(left, feature, rightValue, ancestor != null)) {
// // the reference set has been deferred
//
// // the algorithm might be optimized to get rid of ancestorResource
// // but for the moment we need it, as ancestor might be null (by choice, used
// // as flag for logging
//
// // this block might be disabled by overridding class
// if (getAncestorResource() != null && getAncestorResource().getEObject(((Reference) rightValue).getXmiId()) != null) {
// // CONFLICT the referenced object was deleted on left
// log("%s, feature %s: reference beying COPIED right to left but left DELETE CONFLICT. The referenced left element (or someone from the upper hierarchy) was deleted.\n\tOld left value:\t%s\n\tNew left value:\t%s", getFullyQualifiedName(left), feature.getName(), getFeature(left, feature), rightValue);
// return false;
// }
// }
// }
// return true;
// }
//
// /**
// * By default invokes <code>eGet()</code>. There are special cases:
// * <ul>
// * <li>timeStamp feature is completely ignored
// * <li>clientDependency feature is ignored because has an opposite that
// * is already processed
// * <li>EReference that are single (or many with a single value),
// * are wrapped within a <code>Reference</code>
// * <li>if is EReference, many and has several entries => ignored
// * </ul>
// *
// */
// @SuppressWarnings("unchecked")
// protected Object getFeature(EObject object, EStructuralFeature feature) {
// // TODO santier
// // special cases
// if (codeSyncTimeStampFeature.equals(feature) ||
// UMLPackage.eINSTANCE.getNamedElement_ClientDependency().equals(feature) ||
//// UMLPackage.eINSTANCE.getAssociation_NavigableOwnedEnd().equals(feature) ||
// feature instanceof EReference && feature.isMany() && ((EReference) feature).getEOpposite() != null && !((EReference) feature).getEOpposite().isMany())
// return null;
// if (feature instanceof EReference) {
// Object value = object.eGet(feature);
// if (feature.isMany())
// if (((List<EObject>) value).size() > 1) {
//// throw new RuntimeException(String.format("%s feature %s has more that 1 references; this is not supported.", getFullyQualifiedName(object), feature));
//// System.err.println(">>>>>>>>>>>>>>>>>>>>>>>>>>> " + feature.getName());
// return null;
// } else if (((List<EObject>) value).size() == 1) {
// value = ((List<EObject>) value).get(0);
// } else
// value = null; // empty list
// return new Reference((EObject) value);
// }
// return object.eGet(feature);
// }
//
// /**
// * The default case: calls <code>EObject.eSet()</code>. There are some
// * exceptions however:
// * <ul>
// * <li>If the value is a <code>Reference</code>, the wrapped referenced object
// * is set ONLY if it exists in the resource; otherwise schedules a <code>
// * DeferredSetReferenceEntry</code>.
// * </ul>
// *
// * @param ancestorExists Used together with <code>logNewElementNestedAddAndCopy</code>,
// * to control logging.
// * @see #processDeferredOperations()
// *
// */
// @SuppressWarnings("unchecked")
// protected boolean setFeature(EObject object, EStructuralFeature feature, Object newValue, boolean ancestorExists) {
// Object newValueForLog = newValue;
// if (newValue instanceof Reference) {
// Reference ref = (Reference) newValue;
// if (ref.getReferencedObject() == null)
// newValue = null; // the referenced object may be null
// else {
// newValue = object.eResource().getEObject(ref.getXmiId());
// if (newValue == null) {
// // the referenced object doesn't exist (yet) in the resource; setting it should be deferred
// log("\t\tThe referenced object doesn't exist (yet) in the resource; deferring the feature set.");
// deferredReferenceToSetEntries.add(new DeferredReferenceToSetEntry(object, feature, ref));
// return false;
// }
// }
// }
// if (ancestorExists || logNewElementNestedAddAndCopy)
// log("%s%s, feature %s: COPIED right to left\n\tOld left value:\t%s\n\tNew left value:\t%s",
// ancestorExists ? "" : ">>>", getFullyQualifiedName(object), feature.getName(),
// getFeature(object, feature), newValueForLog);
// if (feature instanceof EReference && feature.isMany()) {
// if (newValue == null)
// throw new UnsupportedOperationException(String.format("%s feature %s is being set to null. This is not normal and might mean that the EReference has more than 1 values, which is not supported. ", getFullyQualifiedName(object), feature.getName()));
// List<EObject> list = (List<EObject>) object.eGet(feature);
// if (list.size() > 1) {
// throw new UnsupportedOperationException(String.format("%s feature %s has more that 1 references; this is not supported.", getFullyQualifiedName(object), feature.getName()));
// } else if (list.size() == 1) {
// list.set(0, (EObject) newValue);
// } else
// list.add((EObject) newValue);
// } else
// object.eSet(feature, newValue);
// return true;
// }
//
// /**
// * Getter for <code>ancestorResource</code> used within
// * <code>mergeStandardFeature()</code> that might be disabled.
// *
// */
// protected Resource getAncestorResource() {
// return ancestorResource;
// }
//
// /**
// * Invoked at the end of the merge:
// * <ul>
// * <li>sets references that were deferred (postponed),
// * <li>deletes objects scheduled for delete.
// * </ul>
// *
// *
// */
// protected void processDeferredOperations() {
// log("Processing deferred set references...");
// for (DeferredReferenceToSetEntry entry : deferredReferenceToSetEntries)
// if (!setFeature(entry.object, entry.feature, entry.reference, true))
// throw new RuntimeException("A deferred set reference has failed. See the last log entry for details.");
// deferredReferenceToSetEntries.clear();
// log("Processing scheduled deletes...");
// for (EObject object : deferredObjectToDelete) {
// // a previous delete in the list might have cascaded a delete on an
// // item that follows; that's why this check
// if (object.eResource() != null) {
// log("\t%s is being DELETED (because it was deleted on right => scheduled for delete)", getFullyQualifiedName(object));
// new ModelMergeDeleteCommand(FlowerEditingDomain.getFlowerEditingDomainFor(object), Collections.singleton(object)).executeNoUndo();
//// FlowerDeleteCommand.create(FlowerEditingDomain.getFlowerEditingDomainFor(object), object, true).executeNoUndo();
// }
// }
// deferredObjectToDelete.clear();
// }
//
// /**
// * Merges EReferences that are containment:
// *
// * <ul>
// * <li>matches right children with left children and processes deeper (recurses),
// * <li>detects newly added elements,
// * <li>removes deleted elements.
// * </ul>
// *
// * For parameter description, see <code>merge()</code>.
// *
// * @param feature May by many (list) or single (object).
// *
// */
// @SuppressWarnings("unchecked")
// protected boolean mergeContainmentFeature(EStructuralFeature feature, EObject ancestor, EObject left, EObject right, boolean leftWasDeleted) {
// List<EObject> leftList = getListFromContainmentFeature(left, feature);
// // filter out features that are many and null
// if (leftList == null)
// return true;
// // FILL_LEFT_MAP: fill a map with LEFT children
// Map<Object, EObject> leftChildrenMap = new HashMap<Object, EObject>();
// for (EObject o : leftList)
// leftChildrenMap.put(getKeyForEObject(o), o);
//
// // FILL_LEFT_DELETED_MAP: fill a map with chilren DELETED from LEFT
// // externalized to be able to disable this step
// Map<Object, EObject> childrenDeletedFromLeftMap = fillLeftDeletedMap(ancestor, feature, leftChildrenMap);
//
// // ITERATE_RIGHT: iterate on RIGHT children
// for (EObject rightObject : getListFromContainmentFeature(right, feature)) {
// EObject leftObject;
// EObject ancestorObject = null;
// // for a "normal" list, proceed with the algorithm (i.e. remove from the map)
// if (leftList instanceof EList) {
// leftObject = leftChildrenMap.remove(getKeyForEObject(rightObject));
// if (ancestor != null) {
// // externalized; see method doc
// ancestorObject = getCorrespondingAncestor(ancestor, rightObject);
// }
// } else {
// // otherwise (a single list, because the feature is not many), get the element directly
// // even if the xmi:ids (keys) are different; the clearing the map is necessary to avoid
// // detecting a delete conflict
// leftObject = (EObject) left.eGet(feature);
// if (ancestor != null)
// ancestorObject = (EObject) ancestor.eGet(feature);
// leftChildrenMap.clear();
// }
// if (leftObject != null) {
// // a 3 match was found; dig deeper (recurse)
// if (!merge(ancestorObject, leftObject, rightObject, leftWasDeleted))
// return false;
// } else {
// EObject leftDeletedObject = childrenDeletedFromLeftMap.remove(getKeyForEObject(rightObject));
// // the following block is NOT executed if left == ancestor
// if (leftDeletedObject != null) {
// // left was deleted; continue digging but the purpose is to find
// // modifications; on first modification found => conflict raised
// if (!merge(leftDeletedObject, leftDeletedObject, rightObject, true))
// return false;
// } else {
// // right element newly added
// // conflict; an addition was performed on right, but left was deleted (directly
// // or indirectly by some parent in the hierarchy)
// if (leftWasDeleted) {
// log("%s.%s has been ADDED but is in DELETE CONFLICT. The left element (or someone from the upper hierarchy) was deleted.", getFullyQualifiedName(left), getName(rightObject));
// return false;
// }
//
// // ADD_OR_MOVE_OBJECT
// if (!(Boolean) addNewObject(ancestor, left, feature, leftList, rightObject)[0])
// return false;
// }
// }
// }
//
// // ITERATE_REMAINING_LEFT_MAP: iterate objects that are still in the LEFT children map
// // i.e. that were deleted on right (or newly added on left)
// for (EObject leftObject : leftChildrenMap.values()) {
// if (!processRemaingObjectInLeftMap(ancestor, leftObject, leftWasDeleted))
// return false;
// }
//
// // if deleted both on left and right, don't do nothing
//
// return true;
// }
//
// /**
// * Utility method used by <code>mergeContainmentFeature()</code> that
// * was externalized in order to be able to disable this block (if needed).
// *
// * @return A map with elements deleted from left. See calling method for details.
// *
// */
// protected Map<Object, EObject> fillLeftDeletedMap(EObject ancestor, EStructuralFeature feature, Map<Object, EObject> leftChildrenMap) {
// Map<Object, EObject> childrenDeletedFromLeftMap = new HashMap<Object, EObject>();
// if (ancestor != null) {
// for (EObject o : getListFromContainmentFeature(ancestor, feature))
// if (leftChildrenMap.get(getKeyForEObject(o)) == null)
// childrenDeletedFromLeftMap.put(getKeyForEObject(o), o);
// }
// return childrenDeletedFromLeftMap;
// }
//
// /**
// * Utility method used by <code>mergeContainmentFeature()</code> that
// * was externalized in order to be able to alter this block (if needed).
// *
// * @return A child of <code>parentAncestor</code> that corresponds to
// * <code>object</code>. This implementation lookups globally
// * based on xmi:id.
// *
// */
// protected EObject getCorrespondingAncestor(EObject parentAncestor, EObject object) {
// // TODO santier pentru breakpoint
// try {
// return parentAncestor.eResource().getEObject((String) getKeyForEObject(object));
// } catch (Exception e) {
// throw new RuntimeException("Temporary: should not happen; actually it happens when ancestor == null but left & right != null. See code...", e);
// }
// }
//
// /**
// * Utility method used by <code>mergeContainmentFeature()</code> that
// * was externalized in order to be able to invoke this block (if needed)
// * separately.
// *
// * <p>
// * It adds a new object and recurses in order to initialize it OR if a
// * corresponding element is found by xmi:id, it moves it and recurses to
// * continue the merge algorithm.
// *
// * @return result[0] - boolean that indicates the conflict state (according to the
// * convention); result[1] the new (or moved) EObject
// *
// */
// @SuppressWarnings("unchecked")
// protected Object[] addNewObject(EObject ancestor, EObject left, EStructuralFeature feature, List<EObject> leftList, EObject rightObject) {
// boolean isMove;
// EObject existingAncestorWithSameId = null;
//
// String newXmiId = rightObject.eResource().getURIFragment(rightObject);
// // look for the new object within left
// EObject newObject = left.eResource().getEObject(newXmiId);
// if (newObject != null) {
// // the new xmi:id of the element that we are adding in left already
// // exists; that means it was moved (in right)
// isMove = true;
//
// // this is not normally null for correct input files; if it's null, probably
// // the ancestor file is not really the ancestor of left/right, but it doesn't
// // really matter; the algorithm will consider the element as newly added
// existingAncestorWithSameId = ancestorResource.getEObject(newXmiId);
// // the object that we just found might be scheduled for delete
// deferredObjectToDelete.remove(newObject);
// log("%s was moved to %s", getFullyQualifiedName(newObject), getFullyQualifiedName(rightObject));
// } else {
// isMove = false;
// newObject = rightObject.eClass().getEPackage().getEFactoryInstance().create(rightObject.eClass());
//
// if (ancestor != null || logNewElementNestedAddAndCopy)
// log("%s%s.%s has been ADDED; copying it...", ancestor != null ? "" : ">>>", getFullyQualifiedName(left), getName(rightObject));
// }
//
// if (leftList instanceof EList)
// leftList.add(newObject); // for "many" features
// else
// left.eSet(feature, newObject); // for "single" features
//
// if (!isMove)
// ((XMLResource) left.eResource()).setID(newObject, newXmiId);
//
// // for !isMove case, existingAncestorWithSameId == null;
// // we could have put newObject as ancestor and the result would have been
// // the same; we put null however, to be able to determine (in the nested calls)
// // that it is not a 3-way copy, but a "initialize new instance" copy; we need to
// // know this to be able to disable the logging in this case
// return new Object[] {
// merge(existingAncestorWithSameId, newObject, rightObject, false),
// newObject
// };
// }
//
// /**
// * Utility method used by <code>mergeContainmentFeature()</code> that
// * was externalized in order to be able to alter this block (if needed).
// *
// * <p>
// * Processes object that are still left in <code>leftMap</code> that may
// * be deleted on right or newly added on left.
// *
// */
// protected boolean processRemaingObjectInLeftMap(EObject ancestor, EObject leftObject, boolean leftWasDeleted) {
// EObject ancestorObject = getCorrespondingAncestor(ancestor, leftObject);
// if (ancestorObject != null) {
// // an object with the same xmi:id exists in ancestor...
// if (ancestorObject.eContainer() == ancestor) {
// // ... and its parent is a child of ancestor => a match exists between
// // ancestor & left; right doesn't exist => object deleted from right
// // recurse to find modifications (meaning conflicts)
// if (!merge(ancestorObject, ancestorObject, leftObject, true))
// return false;
//
// // no conflicts found, so the element can be deleted
//
// // the delete is deferred until the end of the algorithm; the associated
// // activity diagram explains why
//
// // if leftWasDeleted, we don't report a conflict, but there is
// // no need for a delete command (which, anyway, would operate on ancestor,
// // because when that flag == true => ancestor == left)
// if (!leftWasDeleted) {
// deferredObjectToDelete.add(leftObject);
// log("%s is scheduled for DELETE (because it was deleted on right)", getFullyQualifiedName(leftObject));
// }
// }
// // else the object exists somewhere in ancestor, but not in its original
// // location => it was moved; do nothing
// }
// // else newly added on left; don't do anything
// return true;
// }
//
// /**
// * Utility method for <code>mergeContainmentFeature()</code>.
// * This solution was adopted to keep the code more compact. Otherwise some duplication
// * would be needed.
// *
// * @return A list with children. If the feature is many, the result is the existing EList.
// * If the feature is single, a singleton list is returned.
// *
// */
// @SuppressWarnings("unchecked")
// protected List<EObject> getListFromContainmentFeature(EObject object, EStructuralFeature feature) {
// Object listCandidate = object.eGet(feature);
// if (feature.isMany())
// return (List<EObject>) listCandidate; // "many" feature
// else if (listCandidate != null)
// return Collections.singletonList((EObject) listCandidate); // "one" feature
// else
// return Collections.emptyList();
// }
//
/**
* Uses <code>equals()</code> and works even if parameters are <code>null</code>.
*
*/
public static boolean safeEquals(Object a, Object b) {
if (a == null && b == null)
return true;
else if (a == null || b == null)
return false;
else
return a.equals(b);
}
/**
* Used in logging.
*
* @return The name of the element if it is a <code>NamedElement</code>
* or %eClassName% otherwise.
*
*/
public static String getName(EObject object) {
String name = null;
if (object == null)
return "null";
// else if (object instanceof NamedElement)
// name = ((NamedElement) object).getName();
//
// if (name == null) {
// if (object instanceof View && ((View) object).getRefElement() != object) {
// // second check is done because at the time of writing
// // note connectors have themselves as refElement; this doesn't seem
// // right and I created #3610
// name = "%" + object.eClass().getName() + "[RefEl:" + getName(((View) object).getRefElement()) + "]%";
// }
// else
name = "%" + object.eClass().getName() + "%";
// }
return name;
}
/**
* Used in logging.
*
* @param object Accepts a <code>null</code> object.
* @return The fully qualified name; uses <code>getName()</code>.
*
*/
public static String getFullyQualifiedName(EObject object) {
if (object == null)
return "null";
EObject parent = object.eContainer();
StringBuffer result = new StringBuffer(getName(object));
while (parent != null) {
result.insert(0, getName(parent) + ".");
parent = parent.eContainer();
}
return result.toString();
}
//
// /**
// * Works like <code>String.format()</code>, but if there are parameters
// * that have more than 1 line, they get truncated.
// *
// *
// */
// protected void log(String format, Object ... params) {
// for (int i = 0; i < params.length; i++)
// if (params[i] instanceof String) {
// String string = (String) params[i];
// int pos = string.indexOf('\n');
// if (pos != -1) {
// if (pos > 0 && '\r' == string.charAt(pos - 1))
// pos--;
// params[i] = string.substring(0, pos) + "...";
// }
// }
// if (logProvider != null) {
// logProvider.writeToLog(String.format(format, params) + "\n");
// } else {
// System.out.println(String.format(format, params));
// }
// }
//
// /**
// *
// */
// protected void logWithTime(String message) {
// long currentTime = new Date().getTime();
// if (logProvider != null) {
// logProvider.writeToLog(String.format("[%5.2fs] ", ((float)(currentTime - previousTime)) / 1000));
// } else {
// System.out.print(String.format("[%5.2fs] ", ((float)(currentTime - previousTime)) / 1000));
// }
// previousTime = currentTime;
// log(message);
// }
//
}