blob: af73c89d2ba4139a50a4278c6d046b6ae3b0d412 [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.Dialog;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
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("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<EObject>();
/* 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), new IInputValidator() {
@Override
public String isValid(final String newText) {
if (!Pattern.matches("(\\d)+\\-(\\d)+", newText)) {
return "Specify the range in the following way int-int eg. 10-20 ";
}
return null;
}
});
int status = inputDialog.open();
List<Setting> subList = null;
if (status == Dialog.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<Setting>(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 refs of the EClass are found, then fill the plantUMLBuffer with the appropriate contents */
if (allRefSettings.size() > 0) {
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");
}
} else {
/*- no references for this EClass are found */
createClass(plantuml, selectedEObj);
}
}
/**
* 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<EStructuralFeature.Setting>();
if (allRefSettings.size() > 0) {
for (final EStructuralFeature.Setting setting : allRefSettings) {
EStructuralFeature eStructuralFeature = setting.getEStructuralFeature();
if (eStructuralFeature instanceof EReference
&& (((EReference) eStructuralFeature).isContainment() == false)
&& (((EReference) eStructuralFeature).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 eStructuralFeature = eObject.eClass().getEStructuralFeature("name");
if (eStructuralFeature == null) {
return false;
}
return true;
}
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;
}
}