| /******************************************************************************* |
| * Copyright (c) 2010, 2017 BestSolution.at and others. |
| * 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: |
| * Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation |
| * Olivier Prouvost <olivier.prouvost@opcoach.com> - added some cache for e4xmi resource management |
| ******************************************************************************/ |
| package org.eclipse.e4.tools.emf.ui.common; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| import org.eclipse.core.databinding.Binding; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtension; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.e4.tools.emf.ui.common.IEditorFeature.FeatureClass; |
| import org.eclipse.e4.ui.model.application.MApplicationElement; |
| import org.eclipse.e4.ui.model.application.impl.ApplicationPackageImpl; |
| import org.eclipse.e4.ui.model.application.ui.MElementContainer; |
| import org.eclipse.e4.ui.model.application.ui.MUIElement; |
| import org.eclipse.e4.ui.model.application.ui.impl.UiPackageImpl; |
| import org.eclipse.e4.ui.model.fragment.impl.FragmentPackageImpl; |
| import org.eclipse.emf.common.command.Command; |
| import org.eclipse.emf.common.util.TreeIterator; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; |
| import org.eclipse.emf.edit.command.MoveCommand; |
| import org.eclipse.emf.edit.domain.EditingDomain; |
| import org.eclipse.jface.fieldassist.ControlDecoration; |
| import org.eclipse.jface.fieldassist.FieldDecoration; |
| import org.eclipse.jface.fieldassist.FieldDecorationRegistry; |
| import org.eclipse.pde.internal.core.PDEExtensionRegistry; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.widgets.Control; |
| |
| public class Util { |
| |
| private static final String APP_E4XMI_DEFAULT = "Application.e4xmi"; //$NON-NLS-1$ |
| |
| public static final boolean isNullOrEmpty(String element) { |
| return element == null || element.trim().length() == 0; |
| } |
| |
| public static final boolean isImport(EObject object) { |
| return object.eContainingFeature() == FragmentPackageImpl.Literals.MODEL_FRAGMENTS__IMPORTS; |
| } |
| |
| public static final void addClasses(EPackage ePackage, List<FeatureClass> list) { |
| for (final EClassifier c : ePackage.getEClassifiers()) { |
| if (c instanceof EClass) { |
| final EClass eclass = (EClass) c; |
| if (eclass != ApplicationPackageImpl.Literals.APPLICATION && !eclass.isAbstract() |
| && !eclass.isInterface() |
| && eclass.getEAllSuperTypes().contains(ApplicationPackageImpl.Literals.APPLICATION_ELEMENT)) { |
| list.add(new FeatureClass(eclass.getName(), eclass)); |
| } |
| } |
| } |
| |
| for (final EPackage eSubPackage : ePackage.getESubpackages()) { |
| addClasses(eSubPackage, list); |
| } |
| } |
| |
| // TODO In future support different name formats something like |
| // ${project}.${classname}.${counter} |
| public static final String getDefaultElementId(Resource resource, MApplicationElement element, IProject project) { |
| try { |
| final EObject o = (EObject) element; |
| final String className = o.eClass().getName(); |
| final String projectName = project.getName(); |
| |
| final String prefix = (projectName + "." + className).toLowerCase(); //$NON-NLS-1$ |
| |
| final TreeIterator<EObject> it = resource.getAllContents(); |
| final SortedSet<Integer> numbers = new TreeSet<>(); |
| |
| while (it.hasNext()) { |
| final EObject tmp = it.next(); |
| if (tmp instanceof MApplicationElement) { |
| final String elementId = ((MApplicationElement) tmp).getElementId(); |
| if (elementId != null && elementId.length() > prefix.length() && elementId.startsWith(prefix)) { |
| final String suffix = elementId.substring(prefix.length()); |
| if (suffix.startsWith(".") && suffix.length() > 1) { //$NON-NLS-1$ |
| try { |
| numbers.add(Integer.parseInt(suffix.substring(1))); |
| } catch (final Exception e) { |
| // TODO: handle exception |
| } |
| } |
| } |
| } |
| } |
| |
| int lastNumber = -1; |
| for (final Integer number : numbers) { |
| if (lastNumber + 1 != number) { |
| break; |
| } |
| lastNumber = number; |
| } |
| |
| return (prefix + "." + ++lastNumber).toLowerCase(); //$NON-NLS-1$ |
| } catch (final Exception e) { |
| // TODO: handle exception |
| e.printStackTrace(); |
| } |
| return null; |
| } |
| |
| public static List<InternalPackage> loadPackages() { |
| final List<InternalPackage> packs = new ArrayList<>(); |
| |
| for (final Entry<String, Object> regEntry : EPackage.Registry.INSTANCE.entrySet()) { |
| if (regEntry.getValue() instanceof EPackage) { |
| final EPackage ePackage = (EPackage) regEntry.getValue(); |
| final InternalPackage iePackage = new InternalPackage(ePackage); |
| boolean found = false; |
| for (final EClassifier cl : ePackage.getEClassifiers()) { |
| if (cl instanceof EClass) { |
| final EClass eClass = (EClass) cl; |
| if (eClass.getEAllSuperTypes().contains(ApplicationPackageImpl.Literals.APPLICATION_ELEMENT)) { |
| if (!eClass.isInterface() && !eClass.isAbstract()) { |
| found = true; |
| final InternalClass ieClass = new InternalClass(iePackage, eClass); |
| iePackage.classes.add(ieClass); |
| for (final EReference f : eClass.getEAllReferences()) { |
| ieClass.features.add(new InternalFeature(ieClass, f)); |
| } |
| } |
| } |
| } |
| } |
| if (found) { |
| packs.add(iePackage); |
| } |
| } |
| } |
| |
| return packs; |
| } |
| |
| public static boolean moveElementByIndex(EditingDomain editingDomain, MUIElement element, boolean liveModel, |
| int index, EStructuralFeature feature) { |
| if (liveModel) { |
| final EObject container = ((EObject) element).eContainer(); |
| @SuppressWarnings("unchecked") |
| final List<Object> l = (List<Object>) container.eGet(feature); |
| l.remove(element); |
| |
| if (index >= 0) { |
| l.add(index, element); |
| } else { |
| l.add(element); |
| } |
| |
| return true; |
| } |
| final EObject container = ((EObject) element).eContainer(); |
| final Command cmd = MoveCommand.create(editingDomain, container, feature, element, index); |
| |
| if (cmd.canExecute()) { |
| editingDomain.getCommandStack().execute(cmd); |
| return true; |
| } |
| return false; |
| } |
| |
| public static boolean moveElementByIndex(EditingDomain editingDomain, MUIElement element, boolean liveModel, |
| int index) { |
| if (liveModel) { |
| final MElementContainer<MUIElement> container = element.getParent(); |
| container.getChildren().remove(element); |
| |
| if (index >= 0) { |
| container.getChildren().add(index, element); |
| } else { |
| container.getChildren().add(element); |
| } |
| |
| container.setSelectedElement(element); |
| return true; |
| } |
| final MElementContainer<MUIElement> container = element.getParent(); |
| final Command cmd = MoveCommand.create(editingDomain, container, |
| UiPackageImpl.Literals.ELEMENT_CONTAINER__CHILDREN, element, index); |
| |
| if (cmd.canExecute()) { |
| editingDomain.getCommandStack().execute(cmd); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * The set of resources containing model element. Updated when one changes |
| * in the workspace |
| */ |
| private static ResourceSet modelResourceSet; |
| |
| // The lis |
| private static boolean e4ModelResourceListenerRegistered = false; |
| |
| /** |
| * This method searches for fragments or application model elements |
| * resources. It is updated when the workspace changes.. else it returns the |
| * cached values. |
| */ |
| public static ResourceSet getModelElementResources() { |
| |
| // Return previous computed result while workspace did not change... |
| if (modelResourceSet != null) { |
| return modelResourceSet; |
| } |
| |
| registerE4XmiListener(); // Done only once. |
| |
| modelResourceSet = new ResourceSetImpl(); |
| final PDEExtensionRegistry reg = new PDEExtensionRegistry(); |
| IExtension[] extensions = reg.findExtensions("org.eclipse.e4.workbench.model", true); //$NON-NLS-1$ |
| final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| |
| for (final IExtension ext : extensions) { |
| for (final IConfigurationElement el : ext.getConfigurationElements()) { |
| if (el.getName().equals("fragment")) { //$NON-NLS-1$ |
| URI uri; |
| // System.err.println("Model-Ext: Checking: " + |
| // ext.getContributor().getName()); |
| final IProject p = root.getProject(ext.getContributor().getName()); |
| if (p.exists() && p.isOpen()) { |
| uri = URI.createPlatformResourceURI( |
| ext.getContributor().getName() + "/" + el.getAttribute("uri"), true); //$NON-NLS-1$ //$NON-NLS-2$ |
| } else { |
| uri = URI.createURI("platform:/plugin/" + ext.getContributor().getName() + "/" //$NON-NLS-1$ //$NON-NLS-2$ |
| + el.getAttribute("uri")); //$NON-NLS-1$ |
| } |
| // System.err.println(uri); |
| try { |
| modelResourceSet.getResource(uri, true); |
| } catch (final Exception e) { |
| e.printStackTrace(); |
| // System.err.println("=============> Failing"); |
| } |
| |
| } |
| } |
| } |
| |
| extensions = reg.findExtensions("org.eclipse.core.runtime.products", true); //$NON-NLS-1$ |
| for (final IExtension ext : extensions) { |
| for (final IConfigurationElement el : ext.getConfigurationElements()) { |
| if (el.getName().equals("product")) { //$NON-NLS-1$ |
| boolean xmiPropertyPresent = false; |
| for (final IConfigurationElement prop : el.getChildren("property")) { //$NON-NLS-1$ |
| if (prop.getAttribute("name").equals("applicationXMI")) { //$NON-NLS-1$//$NON-NLS-2$ |
| final String v = prop.getAttribute("value"); //$NON-NLS-1$ |
| setUpResourceSet(modelResourceSet, root, v); |
| xmiPropertyPresent = true; |
| break; |
| } |
| } |
| if (!xmiPropertyPresent) { |
| setUpResourceSet(modelResourceSet, root, |
| ext.getNamespaceIdentifier() + "/" + APP_E4XMI_DEFAULT); //$NON-NLS-1$ |
| break; |
| } |
| } |
| } |
| } |
| return modelResourceSet; |
| } |
| |
| /** |
| * A listener to reset the cache of e4Xmi resource for the index research |
| * |
| */ |
| private static void registerE4XmiListener() { |
| // Register once on the workspace. |
| // the listener could be optimized to remember of changed resource since |
| // the last call to getModelElementResources... |
| if (!e4ModelResourceListenerRegistered) { |
| ResourcesPlugin.getWorkspace().addResourceChangeListener(new IResourceChangeListener() { |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| // Nothing to do if resource set not yet used ! |
| if (modelResourceSet == null) { |
| return; |
| } |
| IResourceDelta delta = event.getDelta(); |
| checkDeltaContainsE4xmi(delta); |
| } |
| |
| private void checkDeltaContainsE4xmi(IResourceDelta delta) { |
| if (modelResourceSet == null) { |
| return; |
| } |
| |
| for (IResourceDelta rd : delta.getAffectedChildren()) { |
| IResource r = rd.getResource(); |
| if (r instanceof IFile) |
| { |
| if ("e4xmi".equals(((IFile) r).getFileExtension())) { |
| modelResourceSet = null; |
| break; |
| } |
| } else { |
| checkDeltaContainsE4xmi(rd); |
| } |
| } |
| |
| } |
| }); |
| e4ModelResourceListenerRegistered = true; |
| } |
| } |
| |
| |
| private static void setUpResourceSet(ResourceSet resourceSet, IWorkspaceRoot root, String v) { |
| final String[] s = v.split("/"); //$NON-NLS-1$ |
| URI uri; |
| // System.err.println("Product-Ext: Checking: " + v + " => P:" + s[0] + |
| // ""); |
| final IProject p = root.getProject(s[0]); |
| if (p.exists() && p.isOpen()) { |
| uri = URI.createPlatformResourceURI(v, true); |
| } else { |
| uri = URI.createURI("platform:/plugin/" + v); //$NON-NLS-1$ |
| } |
| |
| try { |
| // prevent some unnecessary calls by checking the uri |
| if (resourceSet.getURIConverter().exists(uri, null)) { |
| resourceSet.getResource(uri, true); |
| } |
| } catch (final Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * This method checks if an EClass can be extended using a fragment. ie : it |
| * must have containment EReference to a model object. |
| * |
| * @param c |
| * @return true if at least one reference type is not a StringStringToMap or |
| * other no editable type |
| */ |
| public static boolean canBeExtendedInAFragment(EClass c) { |
| boolean result = false; |
| for (EReference r : c.getEAllReferences()) { |
| if (referenceIsModelFragmentCompliant(r)) { |
| result = true; |
| break; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * This method checks if an EReference can be considered in a model fragment |
| * ie : it must be containment EReference to a model object. |
| * |
| * @param c |
| * @return true if the reference is containment and type is not a |
| * StringStringToMap or other no editable type |
| */ |
| public static boolean referenceIsModelFragmentCompliant(EReference r) { |
| String t = r.getEReferenceType().getName(); |
| return (r.isContainment() && !t.equals("StringToStringMap") && !t.equals("StringToObjectMap")); |
| |
| } |
| |
| public static final void addDecoration(Control control, Binding binding) { |
| final ControlDecoration dec = new ControlDecoration(control, SWT.BOTTOM); |
| binding.getValidationStatus().addValueChangeListener(event -> { |
| final IStatus s = (IStatus) event.getObservableValue().getValue(); |
| if (s.isOK()) { |
| dec.setDescriptionText(null); |
| dec.setImage(null); |
| } else { |
| dec.setDescriptionText(s.getMessage()); |
| |
| String fieldDecorationID = null; |
| switch (s.getSeverity()) { |
| case IStatus.INFO: |
| fieldDecorationID = FieldDecorationRegistry.DEC_INFORMATION; |
| break; |
| case IStatus.WARNING: |
| fieldDecorationID = FieldDecorationRegistry.DEC_WARNING; |
| break; |
| case IStatus.ERROR: |
| case IStatus.CANCEL: |
| fieldDecorationID = FieldDecorationRegistry.DEC_ERROR; |
| break; |
| } |
| final FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault() |
| .getFieldDecoration(fieldDecorationID); |
| dec.setImage(fieldDecoration == null ? null : fieldDecoration.getImage()); |
| } |
| }); |
| } |
| |
| public static class InternalPackage { |
| public final EPackage ePackage; |
| public List<InternalClass> classes = new ArrayList<>(); |
| |
| public InternalPackage(EPackage ePackage) { |
| this.ePackage = ePackage; |
| } |
| |
| @Override |
| public String toString() { |
| return ePackage.toString(); |
| } |
| |
| public List<EClass> getAllClasses() { |
| final ArrayList<EClass> rv = new ArrayList<>(classes.size()); |
| for (final InternalClass c : classes) { |
| rv.add(c.eClass); |
| } |
| return rv; |
| } |
| } |
| |
| public static class InternalClass { |
| public final InternalPackage pack; |
| public final EClass eClass; |
| public List<InternalFeature> features = new ArrayList<>(); |
| |
| public InternalClass(InternalPackage pack, EClass eClass) { |
| this.eClass = eClass; |
| this.pack = pack; |
| } |
| } |
| |
| public static class InternalFeature { |
| public final InternalClass clazz; |
| public final EStructuralFeature feature; |
| |
| public InternalFeature(InternalClass clazz, EStructuralFeature feature) { |
| this.clazz = clazz; |
| this.feature = feature; |
| } |
| |
| } |
| } |