| /** |
| ******************************************************************************** |
| * 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; |
| } |
| |
| } |