/*******************************************************************************
* Copyright (c) 2006-2012
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026
*
* 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:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Berlin, Germany
* - initial API and implementation
******************************************************************************/
package org.reuseware.lacome;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.util.EContentsEList.FeatureIterator;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.reuseware.lacome.provider.SourceDiagramInformationProvider;
import org.reuseware.lacome.provider.TargetDiagramInformationProvider;
import org.reuseware.lacome.strategy.DiagramAligner;
import org.reuseware.lacome.strategy.DiagramArranger;
import org.reuseware.lacome.strategy.DiagramComparator;
import org.reuseware.lacome.strategy.DiagramCompositionStrategy;
import org.reuseware.lacome.strategy.DiagramMerger;
import org.reuseware.lacome.strategy.MultiSourceDiagramArranger;
import org.reuseware.lacome.strategy.SingleSourceDiagramArranger;
/**
* Central management class of Lacome. It handles
* diagram composition and layout adjustment by
* calling registered mergers and arrangers.
* <p>
* This class manages the following extension points
* that may be used to extend Lacome with support for
* further layout formats and adjustment algorithms.
*
* <ul>
* <li>org.reuseware.lacome.diagramCompositionStrategy</li>
* <li>org.reuseware.lacome.diagramArranger</li>
* <li>org.reuseware.lacome.diagramMerger</li>
* <li>org.reuseware.lacome.targetDiagramInformationProvider</li>
* <li>org.reuseware.lacome.sourceDiagramInformationProvider</li>
* <li>org.reuseware.lacome.diagramAligner</li>
* </ul>
*
*
*/
public final class CompositionDiagramUtil {
private CompositionDiagramUtil() { }
/**
* Extension point ID:
* <i>org.reuseware.lacome.diagramCompositionStrategy</i>.
*/
public static final String DIAGRAM_COMPOSITION_STRATEGY_EP_ID = "org.reuseware.lacome.diagramCompositionStrategy";
/**
* Extension point ID:
* <i>org.reuseware.lacome.diagramArranger</i>.
*/
public static final String DIAGRAM_ARRANGER_EP_ID = "org.reuseware.lacome.diagramArranger";
/**
* Extension point ID:
* <i>org.reuseware.lacome.diagramMerger</i>.
*/
public static final String DIAGRAM_MERGER_EP_ID = "org.reuseware.lacome.diagramMerger";
/**
* Extension point ID:
* <i>org.reuseware.lacome.targetDiagramInformationProvider</i>.
*/
public static final String TARGET_DIAGRAM_PROVIDER_EP_ID = "org.reuseware.lacome.targetDiagramInformationProvider";
/**
* Extension point ID:
* <i>org.reuseware.lacome.sourceDiagramInformationProvider</i>.
*/
public static final String SOURCE_DIAGRAM_PROVIDER_EP_ID = "org.reuseware.lacome.sourceDiagramInformationProvider";
/**
* Extension point ID:
* <i>org.reuseware.lacome.diagramAligner</i>.
*/
public static final String DIAGRAM_ALIGNER_EP_ID = "org.reuseware.lacome.diagramAligner";
protected static Map<String, DiagramMerger> diagramMergers =
new LinkedHashMap<String, DiagramMerger>();
protected static List<SourceDiagramInformationProvider> sdiProviders =
new ArrayList<SourceDiagramInformationProvider>();
protected static List<TargetDiagramInformationProvider> tdiProviders =
new ArrayList<TargetDiagramInformationProvider>();
protected static Map<String, DiagramCompositionStrategy> diagramCompositionStrategies =
new LinkedHashMap<String, DiagramCompositionStrategy>();
protected static Map<String, DiagramArranger<?>> diagramArrangers =
new LinkedHashMap<String, DiagramArranger<?>>();
protected static List<DiagramAligner> diagramAligners =
new ArrayList<DiagramAligner>();
protected static TraceProvider traceProvider = null;
/**
* Composes the tow give diagrams.
*
* @param contributingDiagrams the diagram contributing new layout information
* @param receivingDiagram the diagram receiving new layout information
* @param traceProvider a trace provider that exploits information about
* the modification that were made to the models
* of the diagrams during model composition
*/
public static void composeDiagrams(
List<DiagramDescription> contributingDiagrams,
DiagramDescription receivingDiagram,
TraceProvider traceProvider) {
initEPs();
CompositionDiagramUtil.traceProvider = traceProvider;
//get target bounds of receiving diagram
for (TargetDiagramInformationProvider provider : tdiProviders) {
if (provider.canProvide(receivingDiagram)) {
provider.provideBounds(receivingDiagram);
}
}
//compute source bounds of receiving diagram
for (SourceDiagramInformationProvider provider : sdiProviders) {
if (provider.canProvide(receivingDiagram)) {
provider.provideBounds(receivingDiagram);
}
}
//prepare contributing diagrams
for (DiagramDescription contributingDiagram : contributingDiagrams) {
//get target bounds of contributing diagram
for (TargetDiagramInformationProvider provider : tdiProviders) {
if (provider.canProvide(contributingDiagram)) {
provider.provideBounds(contributingDiagram);
}
}
//get source bounds of contributing diagram
for (SourceDiagramInformationProvider provider : sdiProviders) {
if (provider.canProvide(contributingDiagram)) {
provider.provideBounds(contributingDiagram);
}
}
}
Map<String, List<DiagramDescription>> sortedContributingDiagrams =
sortDiagramsByStrategy(contributingDiagrams);
for (String strategyID : sortedContributingDiagrams.keySet()) {
DiagramCompositionStrategy diagramCompositionStrategy = diagramCompositionStrategies
.get(strategyID);
if (diagramCompositionStrategy == null) {
continue;
}
List<DiagramDescription> contributingDiagramsWithStrategy = sortedContributingDiagrams
.get(strategyID);
if (!diagramCompositionStrategy.getArrangerIDs().isEmpty()) {
// arrange multiple if an arranger exists
for (String arrangerID : diagramCompositionStrategy.getArrangerIDs()) {
DiagramArranger<?> arranger = diagramArrangers.get(arrangerID);
if (arranger instanceof MultiSourceDiagramArranger) {
if (!contributingDiagramsWithStrategy.isEmpty()) {
// sort the list of contributing diagrams
// for composition order
DiagramComparator comparator = arranger.getComparator();
if (comparator != null) {
Collections.sort(
contributingDiagramsWithStrategy,
comparator);
}
((MultiSourceDiagramArranger) arranger).arrange(
contributingDiagramsWithStrategy,
receivingDiagram);
}
}
if (!contributingDiagramsWithStrategy.isEmpty()) {
for (String mergerID : diagramMergers.keySet()) {
if (!diagramCompositionStrategy.getDeactivateMergerIDs()
.contains(mergerID)) {
DiagramMerger merger = diagramMergers.get(mergerID);
if (merger.canMerge(receivingDiagram)) {
if (arranger instanceof SingleSourceDiagramArranger) {
// sort the list of contributing
// diagrams for composition order
DiagramComparator comparator = arranger.getComparator();
if (comparator != null) {
Collections.sort(
contributingDiagramsWithStrategy,
comparator);
}
for (DiagramDescription contributingDiagram : contributingDiagramsWithStrategy) {
// arrange
((SingleSourceDiagramArranger) arranger).arrange(
contributingDiagram,
receivingDiagram);
// merge
merger.merge(Collections.singletonList(contributingDiagram),
receivingDiagram,
contributingDiagramsWithStrategy);
// re-compute source bounds of the
// receiving diagram that is now
// merged
// with the contributing diagram
for (SourceDiagramInformationProvider provider : sdiProviders) {
if (provider.canProvide(receivingDiagram)) {
provider.provideBounds(receivingDiagram);
}
}
}
} else {
merger.merge(
contributingDiagramsWithStrategy,
receivingDiagram,
new BasicEList<DiagramDescription>());
}
}
}
}
}
}
} else {
//if there are no arrangers for the strategy
//the merge has to be performed all the same
for (String mergerID : diagramMergers.keySet()) {
if (!diagramCompositionStrategy.getDeactivateMergerIDs()
.contains(mergerID)) {
DiagramMerger merger = diagramMergers.get(mergerID);
if (merger.canMerge(receivingDiagram)) {
merger.merge(contributingDiagramsWithStrategy,
receivingDiagram,
new BasicEList<DiagramDescription>());
}
}
}
}
cleanup(receivingDiagram);
}
CompositionDiagramUtil.traceProvider = null;
}
private static Map<String, List<DiagramDescription>> sortDiagramsByStrategy(
List<DiagramDescription> fragmentDiagrams) {
Map<String, List<DiagramDescription>> result =
new LinkedHashMap<String, List<DiagramDescription>>();
for (DiagramDescription fragmentDiagram : fragmentDiagrams) {
String strategy = fragmentDiagram.getStrategy();
if (!diagramCompositionStrategies.containsKey(strategy)) {
strategy = "default";
}
if (!result.containsKey(strategy)) {
result.put(strategy, new ArrayList<DiagramDescription>());
}
result.get(strategy).add(fragmentDiagram);
}
return result;
}
/**
* Cleanup the diagram by removing nodes and edges
* that are broken, because relating elements were
* removed.
*
* @param diagram the diagram to cleanup
*/
public static void cleanup(
DiagramDescription diagram) {
//remove all dangling
boolean diagramModified = true;
while (diagramModified) {
diagramModified = false;
treeIteration:
for (TreeIterator<EObject> diagramElIt = EcoreUtil.getAllContents(
diagram.getDiagramRoots()); diagramElIt.hasNext();) {
EObject next = diagramElIt.next();
boolean crossReferencesModified = true;
while (crossReferencesModified) {
crossReferencesModified = false;
for (FeatureIterator<EObject> featureIterator = (FeatureIterator<EObject>) next.eCrossReferences().iterator(); featureIterator.hasNext();) {
EObject nextChild = featureIterator.next();
EStructuralFeature feature = featureIterator.feature();
if (!EcoreUtil.isAncestor(diagram.getDiagramRoots(), nextChild)
&& !EcoreUtil.isAncestor(diagram.getContents(), nextChild)) {
EcoreUtil.remove(next, feature, nextChild);
for (DiagramMerger diagramMerger : diagramMergers.values()) {
diagramModified = diagramMerger.cleanup(next, feature, nextChild);
if (diagramModified) {
break treeIteration;
}
}
crossReferencesModified = true;
break;
}
}
}
}
}
}
/**
* Uses diagram aligners tries to transfer layout information from an old version
* of a diagram to a new one, preserving the information of new elements in the
* new version of the diagram.
* <p>
*
* <i>Currently not implemented</i>
*
* @param newDiagram the new diagram
* @param oldDiagram the old diagram
*/
public static void alignDiagrams(
DiagramDescription newDiagram,
DiagramDescription oldDiagram) {
if (diagramAligners.isEmpty()) {
initAligners();
}
/* TODO #1473: activate alignment here
FragmentDiagram newDiagram =
new FragmentDiagram(newDiagrams);
FragmentDiagram oldDiagram =
new FragmentDiagram(oldDiagrams);
DiagramAligner aligner = null;
for(DiagramAligner candidate : diagramAligners) {
if(candidate.canAlign(newDiagram)) {
aligner = candidate;
break;
}
}
if (aligner != null) {
aligner.align(newDiagram, oldDiagram);
}
*/
}
private static void initEPs() {
if (diagramArrangers.isEmpty()) {
initDiagramArranger();
}
if (diagramMergers.isEmpty()) {
initDiagramMerger();
}
if (sdiProviders.isEmpty()) {
initSDInformationProvider();
}
if (tdiProviders.isEmpty()) {
initTDInformationProvider();
}
if (diagramCompositionStrategies.isEmpty()) {
initDiagramCompositionStrategies();
}
}
private static void initDiagramArranger() {
if (Platform.isRunning()) {
//read extension point
IExtensionPoint sdipComposerEP =
Platform.getExtensionRegistry().getExtensionPoint(DIAGRAM_ARRANGER_EP_ID);
IConfigurationElement[] entries = sdipComposerEP.getConfigurationElements();
for (int i = 0; i < entries.length; i++) {
try {
DiagramComparator comparator = null;
if (entries[i].getAttribute("comparator") != null) {
comparator = (DiagramComparator) entries[i].createExecutableExtension("comparator");
}
DiagramArranger<?> diagramArranger =
(DiagramArranger<?>) entries[i].createExecutableExtension("arranger");
//set the comparator for that arranger
diagramArranger.setComparator(comparator);
String iD = entries[i].getAttribute("id");
diagramArrangers.put(iD, diagramArranger);
} catch (CoreException e) {
e.printStackTrace();
}
}
}
}
private static void initDiagramMerger() {
if (Platform.isRunning()) {
//read extension point
IExtensionPoint sdipComposerEP =
Platform.getExtensionRegistry().getExtensionPoint(DIAGRAM_MERGER_EP_ID);
IConfigurationElement[] entries = sdipComposerEP.getConfigurationElements();
for (int i = 0; i < entries.length; i++) {
try {
DiagramMerger diagramMerger =
(DiagramMerger) entries[i].createExecutableExtension("merger");
String iD = entries[i].getAttribute("id");
diagramMergers.put(iD, diagramMerger);
} catch (CoreException e) {
e.printStackTrace();
}
}
}
}
private static void initSDInformationProvider() {
if (Platform.isRunning()) {
//read extension point
IExtensionPoint sdipComposerEP =
Platform.getExtensionRegistry().getExtensionPoint(SOURCE_DIAGRAM_PROVIDER_EP_ID);
IConfigurationElement[] entries = sdipComposerEP.getConfigurationElements();
for (int i = 0; i < entries.length; i++) {
try {
SourceDiagramInformationProvider sdiProvider =
(SourceDiagramInformationProvider) entries[i].createExecutableExtension("provider");
sdiProviders.add(sdiProvider);
} catch (CoreException e) {
e.printStackTrace();
}
}
}
}
private static void initTDInformationProvider() {
if (Platform.isRunning()) {
//read extension point
IExtensionPoint tdipEP =
Platform.getExtensionRegistry().getExtensionPoint(TARGET_DIAGRAM_PROVIDER_EP_ID);
IConfigurationElement[] entries = tdipEP.getConfigurationElements();
for (int i = 0; i < entries.length; i++) {
try {
TargetDiagramInformationProvider tdiProvider =
(TargetDiagramInformationProvider) entries[i].createExecutableExtension("provider");
tdiProviders.add(tdiProvider);
} catch (CoreException e) {
e.printStackTrace();
}
}
}
}
private static void initDiagramCompositionStrategies() {
if (Platform.isRunning()) {
//read extension point
IExtensionPoint diagramCompositionStrategyEP =
Platform.getExtensionRegistry().getExtensionPoint(DIAGRAM_COMPOSITION_STRATEGY_EP_ID);
IConfigurationElement[] entries = diagramCompositionStrategyEP.getConfigurationElements();
for (int i = 0; i < entries.length; i++) {
try {
String iD = entries[i].getAttribute("id");
DiagramCompositionStrategy compositionStrategy = new DiagramCompositionStrategy(iD);
for (IConfigurationElement arranger : entries[i].getChildren("arranger")) {
if (arranger.getAttribute("id") != null) {
String arrangerID = arranger.getAttribute("id");
compositionStrategy.getArrangerIDs().add(arrangerID);
}
}
for (IConfigurationElement deactivateMerger : entries[i].getChildren("deactivateMerger")) {
if (deactivateMerger.getAttribute("id") != null) {
String mergerID = deactivateMerger.getAttribute("id");
compositionStrategy.getDeactivateMergerIDs().add(mergerID);
}
}
diagramCompositionStrategies.put(iD, compositionStrategy);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private static void initAligners() {
if (Platform.isRunning()) {
//read extension point
IExtensionPoint diagramAlignerEP =
Platform.getExtensionRegistry().getExtensionPoint(DIAGRAM_ALIGNER_EP_ID);
IConfigurationElement[] entries = diagramAlignerEP.getConfigurationElements();
for (int i = 0; i < entries.length; i++) {
try {
DiagramAligner aligner =
(DiagramAligner) entries[i].createExecutableExtension("aligner");
diagramAligners.add(aligner);
} catch (CoreException e) {
e.printStackTrace();
}
}
}
}
/**
* Delegates to the current trace provider.
*
* @param copy a copy of a model element
* @return the original model element
*/
public static EObject getOriginal(EObject copy) {
return traceProvider.getOriginal(copy);
}
/**
* Delegates to the current trace provider.
*
* @param element a model element
* @return model elements that were replaced by the given element
*/
public static List<EObject> getReplacedValues(EObject element) {
if (traceProvider == null) {
return Collections.emptyList();
}
List<EObject> result = traceProvider.getReplacedValues(element);
if (result == null) {
return Collections.emptyList();
}
return result;
}
/**
* Delegates to the current trace provider.
*
* @param element a model element
* @return the setting from which the element was removed (if any)
*/
public static Setting getRemovedFromSetting(EObject element) {
return traceProvider.getRemovedFromSetting(element);
}
/**
* Delegates to the current trace provider.
*
* @param element a model element
* @return all elements from which this element was derived
* (e.g., by some kind of model transformation)
*/
public static List<EObject> getDerivedFrom(EObject element) {
if (traceProvider == null) {
return null;
}
List<EObject> result = traceProvider.getDerivedFrom(element);
if (result == null) {
return Collections.emptyList();
}
return result;
}
}