blob: 0992b4a27f46675d90e35b6f9da61860ecd4404b [file] [log] [blame]
/**
********************************************************************************
* Copyright (c) 2017-2020 Robert Bosch GmbH.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.emf.viewer.plantuml.builders;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
public class EObjectRefsBuilder extends AbstractPlantUMLBuilder {
UISynchronize sync;
public EObjectRefsBuilder(UISynchronize sync) {
this.sync = sync;
}
// NOTE: EClass is an EObject
@Override
public BuilderResult generatePlantUML(final EClass eObject) {
return generatePlantUML((EObject) eObject);
}
@Override
public BuilderResult generatePlantUML(final EObject eObject) {
resetCaches();
final StringBuilder plantuml = new StringBuilder();
plantuml.append("@startuml\n\n");
plantuml.append("' Created by EObjectRefsBuilder (" + timestamp() + ")\n\n");
plantuml.append("hide empty members\n\n");
plantuml.append("skinparam class {\n");
plantuml.append(" BackgroundColor<<selected>> Moccasin\n");
plantuml.append("}\n\n");
plantuml.append("' ===== Main Object =====\n\n");
buildBuffer(eObject, plantuml);
plantuml.append("\n@enduml");
return new BuilderResult(getId2ObjectMap(), plantuml.toString());
}
private void buildBuffer(final EObject selectedEObj, final StringBuilder plantuml) {
final List<EObject> result = new ArrayList<>();
/* getting all the elements from the selected objects EClass --> by supplying the EResource of the corresponding EClass (i.e. ecore or Xcore) */
final Collection<Setting> allRefSettings = filterBackReferences(getAllRefSettings(selectedEObj));
/*
* In case of many elements it is difficult to visualize the contents in the diagram.
*
* Below code provides the possibility to specify the range
*/
if (allRefSettings.size() > 100) {
if (sync != null) {
sync.syncExec(() -> {
InputDialog inputDialog = new InputDialog(
Display.getDefault().getActiveShell(),
"Element selection dialog",
"There are " + allRefSettings.size() + " references. Specify the range for which contents should be represented graphically",
"0-" + (allRefSettings.size() - 1),
newText -> (Pattern.matches("(\\d)+\\-(\\d)+", newText))
? null
: "Specify the range in the following way int-int eg. 10-20"
);
int status = inputDialog.open();
List<Setting> subList = null;
if (status == Window.OK) {
String value = inputDialog.getValue();
String[] split = value.split("-");
subList = new ArrayList<>(allRefSettings).subList(Integer.parseInt(split[0]),
Integer.parseInt(split[1]) < allRefSettings.size()
? Integer.parseInt(split[1])
: (allRefSettings.size() - 1));
} else {
subList = new ArrayList<>(allRefSettings).subList(0, 99);
}
allRefSettings.clear();
allRefSettings.addAll(subList);
});
} else {
List<Setting> subList = new ArrayList<>(allRefSettings).subList(0, 99);
allRefSettings.clear();
allRefSettings.addAll(subList);
}
}
if (allRefSettings.isEmpty()) {
/*- no references found */
createClass(plantuml, selectedEObj);
} else {
/*- references found -> fill the plantUMLBuffer with the appropriate contents */
for (final EStructuralFeature.Setting setting : allRefSettings) {
if (!isElementContained(result, setting.getEObject())) {
result.add(setting.getEObject());
}
}
createClass(plantuml, selectedEObj);
for (final EObject eobj : result) {
createClass(plantuml, eobj);
if (!isNamePresent(eobj)) {
/*- As name is not present, generate the hierarchy till the level Object with Structural Feature name is present */
associateContainer(plantuml, eobj);
}
}
for (final EObject eobj : result) {
final String name = getName(eobj).toString();
plantuml.append(name + "--> " + getName(selectedEObj));
plantuml.append("\n");
}
}
}
/**
* In case of certain meta-models (e.g: Amalthea), to have back references -> derived attributes are specified on Reference in Ecore.
* This method is used to remove back reference objects from the collection.
* @param allRefSettings
* @return filteredSettings which do not have derived references
*/
private Collection<Setting> filterBackReferences(Collection<Setting> allRefSettings) {
Collection<Setting> filteredSettingElements = new ArrayList<>();
for (final EStructuralFeature.Setting setting : allRefSettings) {
EStructuralFeature feature = setting.getEStructuralFeature();
if (feature instanceof EReference) {
final EReference ref = (EReference) feature;
if (ref.isContainment() == false && ref.isDerived() == false) {
filteredSettingElements.add(setting);
}
}
}
return filteredSettingElements;
}
private Collection<Setting> getAllRefSettings(EObject selectedEObj) {
Resource eResource = selectedEObj.eResource();
if (eResource == null) {
return EcoreUtil.UsageCrossReferencer.find(selectedEObj, EcoreUtil.getRootContainer(selectedEObj));
} else if (eResource.getResourceSet() == null) {
return EcoreUtil.UsageCrossReferencer.find(selectedEObj, eResource);
}
return EcoreUtil.UsageCrossReferencer.find(selectedEObj, eResource.getResourceSet());
}
private void createClass(final StringBuilder plantuml, final EObject selectedEObj) {
if (!getNodes().contains(selectedEObj)) {
addNode(selectedEObj); // mark as created
final Object name = getName(selectedEObj);
final String className = selectedEObj.eClass().getName();
final String uuid = getIdForObject(selectedEObj);
plantuml.append("class " + name + " << (O,#B4A7E5) " + className + " >>" + " [[" + uuid + "]]");
plantuml.append("\n");
}
}
private Object getName(final EObject eObject) {
final EStructuralFeature eStructuralFeature = eObject.eClass().getEStructuralFeature("name");
if (eStructuralFeature != null) {
final Object originalName = eObject.eGet(eStructuralFeature);
if (originalName == null || originalName.toString().length() == 0) {
return eObject.eClass().getName() + "__" + eObject.hashCode();
}
return "\"" + originalName + "\"";
}
return eObject.eClass().getName() + "__" + eObject.hashCode();
}
private void associateContainer(final StringBuilder plantuml, final EObject eObject) {
final EObject eContainerObj = eObject.eContainer();
if (eContainerObj == null) {
return;
}
createClass(plantuml, eContainerObj);
plantuml.append(getName(eContainerObj) + " *-- " + getName(eObject));
plantuml.append("\n");
if (!isNamePresent(eContainerObj)) {
associateContainer(plantuml, eContainerObj);
}
}
private boolean isNamePresent(final EObject eObject) {
final EStructuralFeature nameFeature = eObject.eClass().getEStructuralFeature("name");
return nameFeature != null;
}
private boolean isElementContained(final List<EObject> list, final EObject eRef) {
boolean isElementFound = false;
final Iterator<EObject> it = list.iterator();
while (it.hasNext() && !isElementFound) {
final EObject isContained = it.next();
if (isContained.equals(eRef)) {
isElementFound = true;
}
}
return isElementFound;
}
}