Skip to content

Package: GenericEditor$GenericEditorActivationListener

GenericEditor$GenericEditorActivationListener

nameinstructionbranchcomplexitylinemethod
GenericEditor.GenericEditorActivationListener(GenericEditor)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
partActivated(IWorkbenchPart)
M: 0 C: 51
100%
M: 3 C: 7
70%
M: 3 C: 3
50%
M: 0 C: 10
100%
M: 0 C: 1
100%
partBroughtToTop(IWorkbenchPart)
M: 0 C: 1
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
partClosed(IWorkbenchPart)
M: 0 C: 1
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
partDeactivated(IWorkbenchPart)
M: 0 C: 1
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
partOpened(IWorkbenchPart)
M: 0 C: 1
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2011-2020 EclipseSource Muenchen GmbH and others.
3: *
4: * All rights reserved. This program and the accompanying materials
5: * are made available under the terms of the Eclipse Public License 2.0
6: * which accompanies this distribution, and is available at
7: * https://www.eclipse.org/legal/epl-2.0/
8: *
9: * SPDX-License-Identifier: EPL-2.0
10: *
11: * Contributors:
12: * Clemens Elflein - initial API and implementation
13: * Johannes Faltermeier - initial API and implementation
14: * Christian W. Damus - bugs 545460, 548592, 559116
15: ******************************************************************************/
16:
17: package org.eclipse.emfforms.spi.editor;
18:
19: import java.io.IOException;
20: import java.net.URL;
21: import java.util.ArrayList;
22: import java.util.Collection;
23: import java.util.EventObject;
24: import java.util.HashMap;
25: import java.util.LinkedList;
26: import java.util.List;
27: import java.util.Map;
28: import java.util.Objects;
29: import java.util.concurrent.atomic.AtomicReference;
30: import java.util.stream.Collectors;
31:
32: import org.eclipse.core.resources.IFile;
33: import org.eclipse.core.resources.IMarker;
34: import org.eclipse.core.resources.IResource;
35: import org.eclipse.core.resources.IResourceChangeEvent;
36: import org.eclipse.core.resources.IResourceChangeListener;
37: import org.eclipse.core.resources.IResourceDelta;
38: import org.eclipse.core.resources.IResourceDeltaVisitor;
39: import org.eclipse.core.resources.ResourcesPlugin;
40: import org.eclipse.core.runtime.CoreException;
41: import org.eclipse.core.runtime.FileLocator;
42: import org.eclipse.core.runtime.IConfigurationElement;
43: import org.eclipse.core.runtime.IExtensionRegistry;
44: import org.eclipse.core.runtime.IPath;
45: import org.eclipse.core.runtime.IProgressMonitor;
46: import org.eclipse.core.runtime.IStatus;
47: import org.eclipse.core.runtime.Platform;
48: import org.eclipse.core.runtime.Status;
49: import org.eclipse.core.runtime.jobs.Job;
50: import org.eclipse.emf.common.command.BasicCommandStack;
51: import org.eclipse.emf.common.command.CommandStackListener;
52: import org.eclipse.emf.common.notify.Notifier;
53: import org.eclipse.emf.common.ui.MarkerHelper;
54: import org.eclipse.emf.common.util.Diagnostic;
55: import org.eclipse.emf.common.util.URI;
56: import org.eclipse.emf.ecore.EObject;
57: import org.eclipse.emf.ecore.EStructuralFeature;
58: import org.eclipse.emf.ecore.resource.Resource;
59: import org.eclipse.emf.ecore.resource.ResourceSet;
60: import org.eclipse.emf.ecore.util.EcoreUtil;
61: import org.eclipse.emf.ecore.xmi.XMLResource;
62: import org.eclipse.emf.ecp.common.spi.ChildrenDescriptorCollector;
63: import org.eclipse.emf.ecp.common.spi.UniqueSetting;
64: import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
65: import org.eclipse.emf.ecp.view.spi.model.reporting.StatusReport;
66: import org.eclipse.emf.edit.domain.EditingDomain;
67: import org.eclipse.emf.edit.domain.IEditingDomainProvider;
68: import org.eclipse.emf.edit.ui.util.EditUIMarkerHelper;
69: import org.eclipse.emf.edit.ui.util.EditUIUtil;
70: import org.eclipse.emfforms.common.Optional;
71: import org.eclipse.emfforms.internal.editor.Activator;
72: import org.eclipse.emfforms.internal.editor.toolbaractions.LoadEcoreAction;
73: import org.eclipse.emfforms.internal.editor.ui.EditorToolBar;
74: import org.eclipse.emfforms.internal.swt.treemasterdetail.defaultprovider.DefaultDeleteActionBuilder;
75: import org.eclipse.emfforms.spi.editor.helpers.ResourceSetHelpers;
76: import org.eclipse.emfforms.spi.editor.messages.Messages;
77: import org.eclipse.emfforms.spi.swt.treemasterdetail.MenuProvider;
78: import org.eclipse.emfforms.spi.swt.treemasterdetail.TreeMasterDetailComposite;
79: import org.eclipse.emfforms.spi.swt.treemasterdetail.TreeMasterDetailMenuListener;
80: import org.eclipse.emfforms.spi.swt.treemasterdetail.TreeMasterDetailSWTBuilder;
81: import org.eclipse.emfforms.spi.swt.treemasterdetail.TreeMasterDetailSWTFactory;
82: import org.eclipse.emfforms.spi.swt.treemasterdetail.TreeViewerBuilder;
83: import org.eclipse.emfforms.spi.swt.treemasterdetail.actions.ActionCollector;
84: import org.eclipse.emfforms.spi.swt.treemasterdetail.actions.MasterDetailAction;
85: import org.eclipse.emfforms.spi.swt.treemasterdetail.decorator.validation.ecp.ECPValidationLabelDecoratorProvider;
86: import org.eclipse.emfforms.spi.swt.treemasterdetail.diagnostic.DiagnosticCache;
87: import org.eclipse.emfforms.spi.swt.treemasterdetail.diagnostic.DiagnosticCache.ValidationListener;
88: import org.eclipse.emfforms.spi.swt.treemasterdetail.util.CreateElementCallback;
89: import org.eclipse.emfforms.spi.swt.treemasterdetail.util.DetailPanelRenderingFinishedCallback;
90: import org.eclipse.emfforms.spi.swt.treemasterdetail.util.RootObject;
91: import org.eclipse.jface.action.Action;
92: import org.eclipse.jface.action.MenuManager;
93: import org.eclipse.jface.dialogs.MessageDialog;
94: import org.eclipse.jface.viewers.ISelection;
95: import org.eclipse.jface.viewers.ISelectionChangedListener;
96: import org.eclipse.jface.viewers.ISelectionProvider;
97: import org.eclipse.jface.viewers.StructuredSelection;
98: import org.eclipse.jface.viewers.TreeViewer;
99: import org.eclipse.jface.window.Window;
100: import org.eclipse.swt.SWT;
101: import org.eclipse.swt.events.FocusEvent;
102: import org.eclipse.swt.events.FocusListener;
103: import org.eclipse.swt.graphics.Color;
104: import org.eclipse.swt.layout.FormAttachment;
105: import org.eclipse.swt.layout.FormData;
106: import org.eclipse.swt.layout.FormLayout;
107: import org.eclipse.swt.widgets.Composite;
108: import org.eclipse.swt.widgets.Display;
109: import org.eclipse.swt.widgets.Menu;
110: import org.eclipse.ui.IEditorInput;
111: import org.eclipse.ui.IEditorSite;
112: import org.eclipse.ui.IFileEditorInput;
113: import org.eclipse.ui.IPartListener;
114: import org.eclipse.ui.IWorkbenchPart;
115: import org.eclipse.ui.PartInitException;
116: import org.eclipse.ui.contexts.IContextActivation;
117: import org.eclipse.ui.contexts.IContextService;
118: import org.eclipse.ui.dialogs.SaveAsDialog;
119: import org.eclipse.ui.ide.IGotoMarker;
120: import org.eclipse.ui.part.EditorPart;
121:
122: /**
123: * The Class GenericEditor it is the generic part for editing any EObject.
124: */
125: public class GenericEditor extends EditorPart implements IEditingDomainProvider, IGotoMarker {
126:
127:         private static final String GENERIC_EDITOR_CONTEXT = "org.eclipse.emfforms.editor.context"; //$NON-NLS-1$
128:
129:         private static final String FRAGMENT_URI = "FRAGMENT_URI"; //$NON-NLS-1$
130:
131:         private static final String RESOURCE_URI = "RESOURCE_URI"; //$NON-NLS-1$
132:
133:         private static final String FEATURE_URI = "FEATURE_URI"; //$NON-NLS-1$
134:
135:         private static final String ITOOLBAR_ACTIONS_ID = "org.eclipse.emfforms.editor.toolbarActions"; //$NON-NLS-1$
136:
137:         /** The Resource loaded from the provided EditorInput. */
138:         private ResourceSet resourceSet;
139:
140:         /** The command stack. It is used to mark the editor as dirty as well as undo/redo operations */
141:         private final BasicCommandStack commandStack = new BasicCommandStack();
142:
143:         /** The root view. It is the main Editor panel. */
144:         private TreeMasterDetailComposite rootView;
145:
146:         /**
147:          * True, if there were changes in the filesystem while the editor was in the background and the changes could not be
148:          * applied to current view.
149:          */
150:         private boolean filesChangedWithConflict;
151:
152:         private final IPartListener partListener = new GenericEditorActivationListener();
153:
154:         private final IResourceChangeListener resourceChangeListener = new GenericEditorResourceChangeListener();
155:
156:         private final MarkerHelper markerHelper = new GenericEditorMarkerHelper();
157:
158:         private final AtomicReference<Job> markerJob = new AtomicReference<>();
159:
160:         private DiagnosticCache cache;
161:
162:         private boolean reloading;
163:
164:         private boolean closing;
165:
166:         /**
167:          * @return the {@link DiagnosticCache}. may be <code>null</code>
168:          * @since 1.10
169:          */
170:         protected DiagnosticCache getDiagnosticCache() {
171:                 return cache;
172:         }
173:
174:         @Override
175:         public void doSave(IProgressMonitor monitor) {
176:                 // Remove the Listener, so that we won't get a changed notification for our own save operation
177:                 preSave();
178:                 if (ResourceSetHelpers.save(resourceSet, getResourceSaveOptions())) {
179:                         // Tell the CommandStack, that we have saved the file successfully
180:                         // and inform the Workspace, that the Dirty property has changed.
181:                         getCommandStack().saveIsDone();
182:                         firePropertyChange(PROP_DIRTY);
183:                         filesChangedWithConflict = false;
184:                 }
185:                 // Add the listener again, so that we get notifications for future changes
186:                 postSave();
187:         }
188:
189:         /**
190:          * Executes the code which needs to be executed before a save, e.g. removing listeners.
191:          *
192:          * @since 1.10
193:          */
194:         protected void preSave() {
195:                 ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceChangeListener);
196:         }
197:
198:         /**
199:          * Executes the code which needs to be executed after a save, e.g. readding listeners.
200:          *
201:          * @since 1.10
202:          */
203:         protected void postSave() {
204:                 ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener);
205:         }
206:
207:         /**
208:          * Handles filesystem changes.
209:          *
210:          * @param changedResources A List of changed Resources
211:          * @param removedResources A List of removed Resources
212:          */
213:         protected void handleResourceChange(final Collection<Resource> changedResources,
214:                 final Collection<Resource> removedResources) {
215:                 if (!isDirty()) {
216:                         getSite().getShell().getDisplay().asyncExec(() -> {
217:                                 if (resourceSet == null || rootView.isDisposed()) {
218:                                         return;
219:                                 }
220:                                 reloading = true;
221:                                 removeResources(removedResources);
222:
223:                                 // We need to get every changed resource by its URI from the resource set because otherwise proxies will
224:                                 // not be able to resolve after the reload. This is the case because the given resources are not
225:                                 // part of this editor's resource set.
226:                                 final List<Resource> toReload = changedResources.stream()
227:                                         .map(changed -> resourceSet.getResource(changed.getURI(), false))
228:                                         .filter(Objects::nonNull)
229:                                         .collect(Collectors.toList());
230:
231:                                 reloadResources(toReload);
232:                                 reloading = false;
233:                                 getCommandStack().flush();
234:                                 initMarkers();
235:                         });
236:                 } else {
237:                         filesChangedWithConflict = true;
238:                 }
239:         }
240:
241:         /**
242:          * Reloads the given resources and refreshes the tree accordingly.
243:          *
244:          * @param resources The {@link Resource Resources} to reload
245:          * @since 1.22
246:          */
247:         protected void reloadResources(Collection<Resource> resources) {
248:                 for (final Resource r : resources) {
249:                         r.unload();
250:                         try {
251:                                 r.load(getResourceLoadOptions());
252:                         } catch (final IOException e) {
253:                         }
254:                 }
255:                 ResourceSetHelpers.resolveAllProxies(resourceSet);
256:                 refreshTreeAfterResourceChange();
257:         }
258:
259:         /**
260:          * Called after a resource change to refresh the tree master detail of the editor. By default only the tree is
261:          * refreshed. If the tree's input is not this editor's resource but only derived from it, this method should be
262:          * overridden to reset the tree's input.
263:          *
264:          * @since 1.22
265:          */
266:         protected void refreshTreeAfterResourceChange() {
267:                 rootView.refresh();
268:         }
269:
270:         private boolean discardChanges() {
271:                 return MessageDialog.openQuestion(Display.getCurrent().getActiveShell(),
272:                         Messages.GenericEditor_DiscardChangesTitle,
273:                         Messages.GenericEditor_DiscardChangesDescription);
274:         }
275:
276:         @Override
277:         public void doSaveAs() {
278:                 final SaveAsDialog saveAsDialog = new SaveAsDialog(getSite().getShell());
279:                 final int result = saveAsDialog.open();
280:                 if (result == Window.OK) {
281:                         final IPath path = saveAsDialog.getResult();
282:                         setPartName(path.lastSegment());
283:                         resourceSet.getResources().get(0)
284:                                 .setURI(URI.createFileURI(path.toOSString()));
285:                         doSave(null);
286:                 }
287:         }
288:
289:         @Override
290:         public void init(IEditorSite site, IEditorInput input)
291:                 throws PartInitException {
292:                 setSite(site);
293:                 setInput(input);
294:
295:                 // Set the Title for this Editor to the Name of the Input (= Filename)
296:                 setPartName(input.getName());
297:
298:                 // As soon as the resource changed, we inform the Workspace, that it is
299:                 // now dirty
300:                 getCommandStack().addCommandStackListener(new CommandStackListener() {
301:                         @Override
302:                         public void commandStackChanged(EventObject event) {
303:                                 GenericEditor.this.firePropertyChange(PROP_DIRTY);
304:                         }
305:                 });
306:
307:                 site.getPage().addPartListener(partListener);
308:
309:                 ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener);
310:                 // Load the resource from the provided input and display the editor
311:                 resourceSet = loadResource(getEditorInput());
312:         }
313:
314:         /**
315:          * Returns the context id set for this editor.
316:          *
317:          * @return the context id
318:          */
319:         protected String getContextId() {
320:                 return GENERIC_EDITOR_CONTEXT;
321:         }
322:
323:         @Override
324:         public boolean isDirty() {
325:                 return getCommandStack().isSaveNeeded();
326:         }
327:
328:         @Override
329:         public boolean isSaveAsAllowed() {
330:                 return true;
331:         }
332:
333:         @Override
334:         public void createPartControl(Composite parent) {
335:                 parent.setBackground(new Color(Display.getCurrent(), 255, 255, 255));
336:                 parent.setBackgroundMode(SWT.INHERIT_FORCE);
337:
338:                 final Object editorInput = modifyEditorInput(resourceSet);
339:                 if (enableValidation()) {
340:                         setupDiagnosticCache(editorInput);
341:                         getDiagnosticCache().registerValidationListener(new MarkerValidationListener());
342:                 }
343:                 rootView = createRootView(parent, getEditorTitle(), editorInput, getToolbarActions(),
344:                         getCreateElementCallback());
345:
346:                 initMarkers();
347:
348:                 // We need to set the selectionProvider for the editor, so that the EditingDomainActionBarContributor
349:                 // knows the currently selected object to copy/paste
350:                 getEditorSite().setSelectionProvider(rootView.getMasterDetailSelectionProvider());
351:         }
352:
353:         private synchronized void initMarkers() {
354:                 if (getDiagnosticCache() == null || reloading) {
355:                         return;
356:                 }
357:                 final Job oldJob = markerJob.get();
358:                 if (oldJob != null) {
359:                         oldJob.cancel();
360:                         markerJob.compareAndSet(oldJob, null);
361:                 }
362:
363:                 final Job job = Job.create(Messages.GenericEditor_ValidationMarkersJobName, monitor -> {
364:                         try {
365:                                 adjustMarkers(monitor);
366:                                 return Status.OK_STATUS;
367:                         } catch (final CoreException ex) {
368:                                 return ex.getStatus();
369:                         } finally {
370:                                 markerJob.compareAndSet(Job.getJobManager().currentJob(), null);
371:                         }
372:                 });
373:                 job.setPriority(Job.LONG);
374:
375:                 if (markerJob.compareAndSet(null, job)) {
376:                         job.schedule(500);
377:                 }
378:         }
379:
380:         private synchronized void adjustMarkers(IProgressMonitor monitor) throws CoreException {
381:                 if (monitor.isCanceled() || reloading) {
382:                         return;
383:                 }
384:                 deleteMarkers();
385:                 for (final Object o : getDiagnosticCache().getObjects()) {
386:                         try {
387:                                 if (monitor.isCanceled() || reloading) {
388:                                         return;
389:                                 }
390:                                 final Diagnostic value = getDiagnosticCache().getOwnValue(o);
391:                                 if (value.getSeverity() < Diagnostic.WARNING) {
392:                                         continue;
393:                                 }
394:                                 markerHelper.createMarkers(value);
395:                         } catch (final CoreException ex) {
396:                                 /* silent */
397:                         }
398:                 }
399:         }
400:
401:         /**
402:          * Deletes the problem markers created by this Editor. <b>Please take care that this method should be called from a
403:          * {@link Job}</b> to avoid problems with a locked index.
404:          *
405:          * @throws CoreException if the method fails
406:          * @since 1.10
407:          */
408:         protected void deleteMarkers() throws CoreException {
409:                 final Optional<IFile> file = getFile();
410:                 if (!file.isPresent()) {
411:                         return;
412:                 }
413:                 markerHelper.deleteMarkers(file.get());
414:         }
415:
416:         /**
417:          * Get the Notifier from the tree input.
418:          *
419:          * @param editorInput The editor input to transform
420:          * @return {@link Notifier}
421:          * @throws IllegalStateException if the editor input is not a Notifier
422:          */
423:         protected Notifier getNotifierFromEditorInput(Object editorInput) {
424:                 Object input = editorInput;
425:                 if (input instanceof RootObject) {
426:                         input = ((RootObject) input).getRoot();
427:                 }
428:                 if (!Notifier.class.isInstance(input)) {
429:                         throw new IllegalStateException("The editor input is not a Notifier!"); //$NON-NLS-1$
430:                 }
431:                 return (Notifier) input;
432:         }
433:
434:         private void setupDiagnosticCache(Object editorInput) {
435:                 cache = createDiangosticCache(getNotifierFromEditorInput(editorInput));
436:         }
437:
438:         /**
439:          * Creates the diagnostic cache.
440:          *
441:          * @param input the input
442:          * @return the cache
443:          * @since 1.10
444:          */
445:         protected DiagnosticCache createDiangosticCache(final Notifier input) {
446:                 return new DiagnosticCache(input);
447:         }
448:
449:         /**
450:          * @return whether a diagnostic cache should be managed.
451:          * @since 1.10
452:          */
453:         protected boolean enableValidation() {
454:                 return false;
455:         }
456:
457:         /**
458:          * Creates the top area of the editor.
459:          *
460:          * @param parent The parent {@link Composite}
461:          * @param editorTitle The title of the editor
462:          * @param editorInput the editor input
463:          * @param toolbarActions The actions shown on the top area
464:          * @param createElementCallback a call back if elements are created
465:          * @return a {@link TreeMasterDetailComposite}
466:          * @since 1.14
467:          */
468:         protected TreeMasterDetailComposite createRootView(Composite parent, String editorTitle, Object editorInput,
469:                 List<Action> toolbarActions, CreateElementCallback createElementCallback) {
470:                 final Composite composite = new Composite(parent, SWT.NONE);
471:
472:                 composite.setLayout(new FormLayout());
473:
474:                 final FormData toolbarLayoutData = new FormData();
475:                 toolbarLayoutData.left = new FormAttachment(0);
476:                 toolbarLayoutData.right = new FormAttachment(100);
477:                 toolbarLayoutData.top = new FormAttachment(0);
478:                 final EditorToolBar toolbar = new EditorToolBar(composite, SWT.NONE, editorTitle, toolbarActions);
479:                 toolbar.setLayoutData(toolbarLayoutData);
480:
481:                 final FormData treeMasterDetailLayoutData = new FormData();
482:                 treeMasterDetailLayoutData.top = new FormAttachment(toolbar, 5);
483:                 treeMasterDetailLayoutData.left = new FormAttachment(0);
484:                 treeMasterDetailLayoutData.right = new FormAttachment(100);
485:                 treeMasterDetailLayoutData.bottom = new FormAttachment(100);
486:                 final TreeMasterDetailComposite treeMasterDetail = createTreeMasterDetail(composite, editorInput,
487:                         createElementCallback);
488:                 treeMasterDetail.setLayoutData(treeMasterDetailLayoutData);
489:
490:                 for (final Action action : toolbarActions) {
491:                         if (action instanceof IEditingDomainAware) {
492:                                 ((IEditingDomainAware) action).setEditingDomain(getEditingDomain());
493:                         }
494:                 }
495:                 return treeMasterDetail;
496:         }
497:
498:         /**
499:          * Returns the root composite containing the tree and the detail view. This is null before the editor control is
500:          * created.
501:          *
502:          * @return The root {@link TreeMasterDetailComposite} of this editor
503:          * @since 1.20
504:          */
505:         protected TreeMasterDetailComposite getRootView() {
506:                 return rootView;
507:         }
508:
509:         /**
510:          * This method creates a tree master detail. Override this method if you want to customize the tree.
511:          *
512:          * @param composite the parent composite
513:          * @param editorInput the editor input
514:          * @param createElementCallback the create element callback to add
515:          *
516:          * @return the {@link TreeMasterDetailComposite}
517:          */
518:         protected TreeMasterDetailComposite createTreeMasterDetail(
519:                 final Composite composite,
520:                 Object editorInput,
521:                 final CreateElementCallback createElementCallback) {
522:                 final TreeMasterDetailSWTBuilder builder = TreeMasterDetailSWTFactory
523:                         .fillDefaults(composite, SWT.NONE, editorInput)
524:                         .customizeCildCreation(createElementCallback)
525:                         .customizeMenu(new MenuProvider() {
526:                                 @Override
527:                                 public Menu getMenu(TreeViewer treeViewer, EditingDomain editingDomain) {
528:                                         final MenuManager menuMgr = new MenuManager();
529:                                         menuMgr.setRemoveAllWhenShown(true);
530:                                         final List<MasterDetailAction> masterDetailActions = ActionCollector.newList()
531:                                                 .addCutAction(editingDomain).addCopyAction(editingDomain).addPasteAction(editingDomain)
532:                                                 .getList();
533:                                         menuMgr.addMenuListener(new TreeMasterDetailMenuListener(new ChildrenDescriptorCollector(), menuMgr,
534:                                                 treeViewer, editingDomain, masterDetailActions, createElementCallback,
535:                                                 new DefaultDeleteActionBuilder()));
536:                                         final Menu menu = menuMgr.createContextMenu(treeViewer.getControl());
537:                                         return menu;
538:
539:                                 }
540:                         })
541:                         .customizeTree(createTreeViewerBuilder())
542:                         .customizeReadOnly(!isEditable(getEditorInput()));
543:
544:                 if (enableValidation()) {
545:                         builder.customizeLabelDecorator(
546:                                 new ECPValidationLabelDecoratorProvider(getNotifierFromEditorInput(editorInput), getDiagnosticCache()));
547:                 }
548:
549:                 final TreeMasterDetailComposite treeMasterDetail = customizeTree(builder).create();
550:                 treeMasterDetail.registerDetailPanelRenderingFinishedCallback(
551:                         DetailPanelRenderingFinishedCallback.adapt((ctx, __) -> handleDetailActivated(ctx)));
552:                 return treeMasterDetail;
553:         }
554:
555:         /**
556:          * Create the {@link TreeViewerBuilder} customization which creates the tree for the editor's tree master detail.
557:          * <p>
558:          * Clients can override this to customize the tree viewer.
559:          *
560:          * @return the {@link TreeViewerBuilder} which creates the tree viewer for the editor's tree master detail
561:          * @since 1.20
562:          */
563:         protected TreeViewerBuilder createTreeViewerBuilder() {
564:                 return parent -> {
565:                         final TreeViewer treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.BORDER);
566:                         treeViewer.setAutoExpandLevel(3);
567:                         treeViewer.getTree().addFocusListener(new FocusListener() {
568:                                 private IContextActivation activation;
569:
570:                                 @Override
571:                                 public void focusLost(FocusEvent e) {
572:                                         getSite().getService(IContextService.class).deactivateContext(activation);
573:                                 }
574:
575:                                 @Override
576:                                 public void focusGained(FocusEvent e) {
577:                                         activation = getSite().getService(IContextService.class).activateContext(getContextId());
578:                                 }
579:                         });
580:                         return treeViewer;
581:                 };
582:         }
583:
584:         /**
585:          * Allows to modify the input object for the editor.
586:          *
587:          * @param resourceSet the resourceSet which is the default editor input
588:          * @return the object to set as the input
589:          */
590:         protected Object modifyEditorInput(ResourceSet resourceSet) {
591:                 return resourceSet;
592:         }
593:
594:         /**
595:          * Creates a resource set and loads all required resources for the editor input.
596:          *
597:          * @param editorInput the editor input
598:          * @return the resource set
599:          * @throws PartInitException if the resource could not be loaded
600:          */
601:         protected ResourceSet loadResource(IEditorInput editorInput) throws PartInitException {
602:                 ResourceSet resourceSet = ResourceSetHelpers.createResourceSet(getCommandStack());
603:                 final URI resourceURI = EditUIUtil.getURI(editorInput, resourceSet.getURIConverter());
604:
605:                 try {
606:                         resourceSet = ResourceSetHelpers.loadResourceWithProxies(resourceURI, resourceSet,
607:                                 getResourceLoadOptions());
608:                         verifyEditorResource(resourceURI, resourceSet);
609:                         return resourceSet;
610:                         // CHECKSTYLE.OFF: IllegalCatch
611:                 } catch (final Exception e) {
612:                         throw new PartInitException(e.getLocalizedMessage(), e);
613:                 }
614:                 // CHECKSTYLE.ON: IllegalCatch
615:         }
616:
617:         /**
618:          * Returns whether the editor input allows editing of its contents.
619:          *
620:          * @param editorInput the editor's {@link IEditorInput}
621:          * @return <code>true</code> if the input source allows editing, <code>false</code> otherwise
622:          * @since 1.22
623:          */
624:         protected boolean isEditable(IEditorInput editorInput) {
625:                 // Only allow editing data if it can be persisted
626:                 return editorInput.getPersistable() != null;
627:         }
628:
629:         /**
630:          * Check that the resource was loaded correctly and show any warnings to the user.
631:          *
632:          * @param resourceSet the resource set
633:          * @param resourceURI the URI of the resource
634:          * @since 1.19
635:          *
636:          */
637:         protected void verifyEditorResource(URI resourceURI, ResourceSet resourceSet) {
638:                 final Resource resource = resourceSet.getResource(resourceURI, true);
639:                 if (XMLResource.class.isInstance(resource)
640:                         && !XMLResource.class.cast(resource).getEObjectToExtensionMap().isEmpty()) {
641:                         // we are showing a view which wasn't fully loaded
642:                         MessageDialog
643:                                 .openWarning(
644:                                         getSite().getShell(),
645:                                         Messages.GenericEditor_UnknownFeaturesDialogTitle,
646:                                         Messages.GenericEditor_UnknownFeaturesDialogDescription);
647:                 }
648:         }
649:
650:         /**
651:          * The options to be used when loading the editor's resource.
652:          *
653:          * @return the load options
654:          * @since 1.19
655:          */
656:         protected Map<Object, Object> getResourceLoadOptions() {
657:                 final HashMap<Object, Object> options = new HashMap<Object, Object>();
658:                 options.put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE,
659:                         Boolean.TRUE);
660:                 options.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, Boolean.TRUE);
661:                 return options;
662:         }
663:
664:         /**
665:          * The options to be used when saving the editor's resource.
666:          *
667:          * @return the save options
668:          * @since 1.19
669:          */
670:         protected Map<Object, Object> getResourceSaveOptions() {
671:                 final Map<Object, Object> saveOptions = new HashMap<Object, Object>();
672:                 saveOptions.put(XMLResource.OPTION_ENCODING, "UTF-8"); //$NON-NLS-1$
673:                 return saveOptions;
674:         }
675:
676:         @Override
677:         public void setFocus() {
678:                 // NOOP
679:         }
680:
681:         /**
682:          * Returns true, if the editor should have shortcuts.
683:          *
684:          * @return true, if the editor has shortcuts
685:          */
686:         protected boolean hasShortcuts() {
687:                 return false;
688:         }
689:
690:         /**
691:          * Returns the title for the currently displayed editor.
692:          * Subclasses should override this function to change the Editor's title
693:          *
694:          * @return the title
695:          */
696:         protected String getEditorTitle() {
697:                 return Messages.GenericEditor_EditorTitle;
698:         }
699:
700:         /**
701:          * Returns the createElementCallback for this editor. By default, there is none.
702:          *
703:          * @return the callback
704:          */
705:         protected CreateElementCallback getCreateElementCallback() {
706:                 return null;
707:         }
708:
709:         /**
710:          * {@inheritDoc}
711:          */
712:         @Override
713:         public EditingDomain getEditingDomain() {
714:                 if (rootView == null) {
715:                         return null;
716:                 }
717:                 return rootView.getEditingDomain();
718:         }
719:
720:         /**
721:          * Returns the toolbar actions for this editor.
722:          *
723:          * @return A list of actions to show in the Editor's Toolbar
724:          * @since 1.10
725:          */
726:         protected List<Action> getToolbarActions() {
727:                 final List<Action> result = new LinkedList<Action>();
728:                 if (!isEditable(getEditorInput())) {
729:                         // If the input isn't editable, toolbar actions are disabled
730:                         return result;
731:                 }
732:
733:                 result.add(new LoadEcoreAction(resourceSet));
734:
735:                 result.addAll(readToolbarActions());
736:                 return result;
737:         }
738:
739:         /**
740:          * Read toolbar actions from all extensions.
741:          *
742:          * @return the Actions registered via extension point
743:          * @since 1.10
744:          */
745:         protected List<Action> readToolbarActions() {
746:                 final List<Action> result = new LinkedList<Action>();
747:
748:                 final ISelectionProvider selectionProvider = new GenericEditorSelectionProvider();
749:
750:                 final IExtensionRegistry registry = Platform.getExtensionRegistry();
751:                 if (registry == null) {
752:                         return result;
753:                 }
754:
755:                 final IConfigurationElement[] config = registry.getConfigurationElementsFor(ITOOLBAR_ACTIONS_ID);
756:                 for (final IConfigurationElement e : config) {
757:                         try {
758:                                 final Object o = e.createExecutableExtension("toolbarAction"); //$NON-NLS-1$
759:                                 if (o instanceof IToolbarAction) {
760:                                         final IToolbarAction action = (IToolbarAction) o;
761:                                         if (!action.canExecute(resourceSet)) {
762:                                                 continue;
763:                                         }
764:
765:                                         result.add(action.getAction(resourceSet, selectionProvider));
766:                                 }
767:                         } catch (final CoreException ex) {
768:                                 Activator.getDefault().getReportService().report(
769:                                         new StatusReport(new Status(IStatus.ERROR, Activator.PLUGIN_ID, ex.getMessage(), ex)));
770:                         }
771:                 }
772:                 return result;
773:         }
774:
775:         /**
776:          * Returns the ResouceSet of this Editor.
777:          *
778:          * @return The resource set
779:          */
780:         public ResourceSet getResourceSet() {
781:                 return resourceSet;
782:         }
783:
784:         @Override
785:         public void dispose() {
786:                 if (getDiagnosticCache() != null) {
787:                         getDiagnosticCache().dispose();
788:                 }
789:                 ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceChangeListener);
790:                 getSite().getPage().removePartListener(partListener);
791:                 super.dispose();
792:         }
793:
794:         private Optional<IFile> getFile() {
795:                 final IEditorInput input = GenericEditor.this.getEditorInput();
796:                 if (isEditable(getEditorInput()) && IFileEditorInput.class.isInstance(input)) {
797:                         return Optional.of(IFileEditorInput.class.cast(input).getFile());
798:                 }
799:                 return Optional.empty();
800:         }
801:
802:         /**
803:          *
804:          * {@inheritDoc}
805:          *
806:          * @see org.eclipse.ui.ide.IGotoMarker#gotoMarker(org.eclipse.core.resources.IMarker)
807:          * @since 1.10
808:          */
809:         @Override
810:         public void gotoMarker(IMarker marker) {
811:                 try {
812:                         EObject eObject = null;
813:                         EStructuralFeature feature = null;
814:
815:                         final String resourceURI = (String) marker.getAttribute(RESOURCE_URI);
816:                         final String fragmentURI = (String) marker.getAttribute(FRAGMENT_URI);
817:                         if (resourceURI != null && fragmentURI != null) {
818:                                 final Resource resource = getEditingDomain().getResourceSet().getResource(URI.createURI(resourceURI),
819:                                         true);
820:                                 eObject = resource.getEObject(fragmentURI);
821:
822:                                 final String featureURI = marker.getAttribute(FEATURE_URI, null);
823:                                 if (featureURI != null) {
824:                                         // Don't load on demand because this should be a delegated look-up in the package registry
825:                                         // or else find the Ecore resource already loaded to resolve our model's schema
826:                                         final EObject featureObject = getEditingDomain().getResourceSet().getEObject(
827:                                                 URI.createURI(featureURI),
828:                                                 false);
829:                                         if (featureObject instanceof EStructuralFeature) {
830:                                                 feature = (EStructuralFeature) featureObject;
831:                                         }
832:                                 }
833:                         } else {
834:                                 // Maybe it's an EMF-standard marker?
835:                                 final List<?> targets = markerHelper.getTargetObjects(getEditingDomain(), marker, false);
836:                                 for (final Object next : targets) {
837:                                         if (next instanceof EObject) {
838:                                                 if (eObject == null) {
839:                                                         eObject = (EObject) next;
840:                                                 } else if (feature == null && next instanceof EStructuralFeature) {
841:                                                         feature = (EStructuralFeature) next;
842:                                                 }
843:                                         }
844:                                 }
845:                         }
846:
847:                         if (eObject == null) {
848:                                 // Nothing to navigate to
849:                                 return;
850:                         }
851:
852:                         if (feature == null) {
853:                                 reveal(eObject);
854:                         } else {
855:                                 reveal(UniqueSetting.createSetting(eObject, feature));
856:                         }
857:                 } catch (final CoreException ex) {
858:                         // silent
859:                 }
860:         }
861:
862:         /**
863:          * The given element will be revealed in the tree of the editor.
864:          *
865:          * @param objectToReveal the object to reveal
866:          * @since 1.10
867:          */
868:         public void reveal(EObject objectToReveal) {
869:                 rootView.refresh();
870:                 rootView.selectAndReveal(objectToReveal);
871:         }
872:
873:         /**
874:          * Reveal the control that edits a {@code setting} of some object.
875:          *
876:          * @param setting the feature setting to reveal of object
877:          * @since 1.22
878:          */
879:         public void reveal(UniqueSetting setting) {
880:                 rootView.refresh();
881:                 rootView.selectAndReveal(setting);
882:         }
883:
884:         /**
885:          * @return the commandStack the {@link
886:          * import org.eclipse.emf.common.command.CommandStack;}
887:          * @since 1.10
888:          */
889:         protected BasicCommandStack getCommandStack() {
890:                 return commandStack;
891:         }
892:
893:         /**
894:          * Override this method to set additional attributes on the given {@link IMarker}, e.g. location information.
895:          *
896:          * @param marker the {@link IMarker} to adjust
897:          * @param diagnostic the {@link Diagnostic}
898:          * @return <code>true</code> if the marker was changed, <code>false</code> otherwise
899:          * @throws CoreException in case of an error
900:          * @since 1.10
901:          */
902:         protected boolean adjustErrorMarker(IMarker marker, Diagnostic diagnostic) throws CoreException {
903:                 final List<?> data = diagnostic.getData();
904:                 if (data.size() < 1) {
905:                         return false;
906:                 }
907:                 if (!EObject.class.isInstance(data.get(0))) {
908:                         return false;
909:                 }
910:                 final EObject eObject = EObject.class.cast(data.get(0));
911:                 if (eObject.eResource() == null) {
912:                         /* possible when job still running but getting closed */
913:                         return false;
914:                 }
915:                 final EStructuralFeature feature = data.subList(1, data.size()).stream()
916:                         .filter(EStructuralFeature.class::isInstance).map(EStructuralFeature.class::cast)
917:                         .findFirst().orElse(null);
918:                 final String uri = eObject.eResource().getURI().toString();
919:                 final String uriFragment = eObject.eResource().getURIFragment(eObject);
920:                 marker.setAttribute(RESOURCE_URI, uri);
921:                 marker.setAttribute(FRAGMENT_URI, uriFragment);
922:                 if (feature != null) {
923:                         marker.setAttribute(FEATURE_URI, String.valueOf(EcoreUtil.getURI(feature)));
924:                 }
925:                 return true;
926:         }
927:
928:         /**
929:          * Returns whether this editor is currently in the process of shutting down.
930:          *
931:          * @return <code>true</code> if the editor is currently closing, <code>false</code> otherwise
932:          * @since 1.18
933:          */
934:         protected boolean isClosing() {
935:                 return closing;
936:         }
937:
938:         /**
939:          * Set whether this editor is currently in the process of shutting down.
940:          * Set this flag in case you will close the editor.
941:          *
942:          * @param closing Whether the editor is currently closing (shutting down)
943:          * @since 1.18
944:          */
945:         protected void setClosing(boolean closing) {
946:                 this.closing = closing;
947:         }
948:
949:         /**
950:          * Removes the given {@linkplain Resource Resources} from this editor's {@linkplain ResourceSet}. Thereby the
951:          * resources are matched by URI.
952:          *
953:          * @param resources The {@linkplain Resource Resources} to remove from this editor's {@linkplain ResourceSet}.
954:          * @since 1.18
955:          */
956:         protected void removeResources(final Collection<Resource> resources) {
957:                 for (final Resource removed : resources) {
958:                         final Resource toRemove = resourceSet.getResource(removed.getURI(), false);
959:                         if (toRemove != null) {
960:                                 resourceSet.getResources().remove(toRemove);
961:                         }
962:                 }
963:         }
964:
965:         /**
966:          * Customize the tree {@code builder}. Subclasses are free to add
967:          * customizations or override default customizations installed by this
968:          * class, but the latter requires care not to break expected editor
969:          * behaviour.
970:          *
971:          * @param builder the tree builder
972:          * @return the {@code builder} for convenience of call chaining, or if
973:          * absolutely necessary an entirely new builder
974:          *
975:          * @since 1.24
976:          */
977:         protected TreeMasterDetailSWTBuilder customizeTree(TreeMasterDetailSWTBuilder builder) {
978:                 return builder;
979:         }
980:
981:         /**
982:          * React to the rendering or re-activation of a detail context. The default
983:          * implementation does nothing; subclasses may just override it.
984:          *
985:          * @param detailContext the active detail context
986:          *
987:          * @since 1.24
988:          */
989:         protected void handleDetailActivated(ViewModelContext detailContext) {
990:                 // Nothing to do
991:         }
992:
993:         /**
994:          * Listens to part events.
995:          *
996:          */
997:         private final class GenericEditorActivationListener implements IPartListener {
998:                 @Override
999:                 public void partOpened(IWorkbenchPart part) {
1000:                 }
1001:
1002:                 @Override
1003:                 public void partDeactivated(IWorkbenchPart part) {
1004:                 }
1005:
1006:                 @Override
1007:                 public void partClosed(IWorkbenchPart part) {
1008:                 }
1009:
1010:                 @Override
1011:                 public void partBroughtToTop(IWorkbenchPart part) {
1012:                 }
1013:
1014:                 @Override
1015:                 public void partActivated(IWorkbenchPart part) {
1016:•                        if (!isClosing() && part == GenericEditor.this && isDirty() && filesChangedWithConflict
1017:•                                && discardChanges()) {
1018:                                 reloading = true;
1019:                                 reloadResources(resourceSet.getResources());
1020:                                 reloading = false;
1021:                                 getCommandStack().flush();
1022:                                 initMarkers();
1023:                                 firePropertyChange(PROP_DIRTY);
1024:                                 filesChangedWithConflict = false;
1025:                         }
1026:                 }
1027:         }
1028:
1029:         /**
1030:          * Reacts to revalidation changes and creates/removes marker accordingly.
1031:          *
1032:          * @author Johannes Faltermeier
1033:          *
1034:          */
1035:         private final class MarkerValidationListener implements ValidationListener {
1036:                 @Override
1037:                 public void revalidationOccurred(final Collection<EObject> object, boolean potentialStructuralChange) {
1038:                         initMarkers();
1039:                 }
1040:         }
1041:
1042:         /**
1043:          * {@link MarkerHelper} for this editor.
1044:          *
1045:          * @author Johannes Faltermeier
1046:          *
1047:          */
1048:         private final class GenericEditorMarkerHelper extends EditUIMarkerHelper {
1049:                 @Override
1050:                 public IFile getFile(Diagnostic diagnostic) {
1051:                         final Optional<IFile> file = GenericEditor.this.getFile();
1052:                         if (file.isPresent()) {
1053:                                 return file.get();
1054:                         }
1055:                         return super.getFile(diagnostic);
1056:                 }
1057:
1058:                 @Override
1059:                 protected boolean adjustMarker(IMarker marker, Diagnostic diagnostic) throws CoreException {
1060:                         return adjustErrorMarker(marker, diagnostic);
1061:                 }
1062:         }
1063:
1064:         /**
1065:          * The GenericEditorResourceChangeListener listens for changes in currently opened Ecore files and reports
1066:          * them to the EcoreEditor.
1067:          */
1068:         private final class GenericEditorResourceChangeListener implements IResourceChangeListener {
1069:
1070:                 @Override
1071:                 public void resourceChanged(IResourceChangeEvent event) {
1072:                         final Collection<Resource> changedResources = new ArrayList<Resource>();
1073:                         final Collection<Resource> removedResources = new ArrayList<Resource>();
1074:                         final IResourceDelta delta = event.getDelta();
1075:
1076:                         if (delta == null) {
1077:                                 return;
1078:                         }
1079:
1080:                         try {
1081:                                 delta.accept(new GenericEditorResourceDeltaVisitor(removedResources, changedResources));
1082:                         } catch (final CoreException ex) {
1083:                                 Activator.getDefault().getReportService().report(
1084:                                         new StatusReport(new Status(IStatus.ERROR, Activator.PLUGIN_ID, ex.getMessage(), ex)));
1085:                         }
1086:                         if (changedResources.isEmpty() && removedResources.isEmpty()) {
1087:                                 return;
1088:                         }
1089:                         handleResourceChange(changedResources, removedResources);
1090:                 }
1091:         }
1092:
1093:         /**
1094:          * The delata visitor deciding if changes are relevant for reloading.
1095:          */
1096:         private final class GenericEditorResourceDeltaVisitor implements IResourceDeltaVisitor {
1097:                 private final Collection<Resource> removedResources;
1098:                 private final Collection<Resource> changedResources;
1099:
1100:                 GenericEditorResourceDeltaVisitor(Collection<Resource> removedResources,
1101:                         Collection<Resource> changedResources) {
1102:                         this.removedResources = removedResources;
1103:                         this.changedResources = changedResources;
1104:                 }
1105:
1106:                 @Override
1107:                 public boolean visit(final IResourceDelta delta) {
1108:                         if ((delta.getFlags() & IResourceDelta.MARKERS) != 0) {
1109:                                 return false;
1110:                         }
1111:                         if (delta.getResource().getType() == IResource.FILE
1112:                                 && (delta.getKind() == IResourceDelta.REMOVED ||
1113:                                         delta.getKind() == IResourceDelta.CHANGED)) {
1114:                                 final ResourceSet resourceSet = getResourceSet();
1115:                                 if (resourceSet == null) {
1116:                                         return false;
1117:                                 }
1118:                                 Resource resource = null;
1119:
1120:                                 final URI uri = URI.createPlatformResourceURI(delta.getFullPath().toString(), true);
1121:                                 resource = resourceSet.getResource(uri, false);
1122:                                 if (resource == null) {
1123:                                         try {
1124:                                                 final URL fileURL = FileLocator.resolve(new URL(uri.toString()));
1125:                                                 resource = resourceSet.getResource(URI.createFileURI(fileURL.getPath()), false);
1126:                                         } catch (final IOException ex) {
1127:                                                 return false;
1128:                                         }
1129:                                 }
1130:
1131:                                 if (resource != null) {
1132:                                         if (delta.getKind() == IResourceDelta.REMOVED) {
1133:                                                 removedResources.add(resource);
1134:                                         } else {
1135:                                                 changedResources.add(resource);
1136:                                         }
1137:                                 }
1138:                                 return false;
1139:                         }
1140:                         return true;
1141:                 }
1142:         }
1143:
1144:         /** Selection Provider for the GenericEditor. */
1145:         private class GenericEditorSelectionProvider implements ISelectionProvider {
1146:
1147:                 @Override
1148:                 public void setSelection(ISelection selection) {
1149:                         if (rootView == null) {
1150:                                 return;
1151:                         }
1152:                         rootView.getMasterDetailSelectionProvider().setSelection(selection);
1153:                 }
1154:
1155:                 @Override
1156:                 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
1157:                         if (rootView == null) {
1158:                                 return;
1159:                         }
1160:                         rootView.getMasterDetailSelectionProvider().removeSelectionChangedListener(listener);
1161:                 }
1162:
1163:                 @Override
1164:                 public ISelection getSelection() {
1165:                         if (rootView == null) {
1166:                                 return StructuredSelection.EMPTY;
1167:                         }
1168:                         return rootView.getMasterDetailSelectionProvider().getSelection();
1169:                 }
1170:
1171:                 @Override
1172:                 public void addSelectionChangedListener(ISelectionChangedListener listener) {
1173:                         if (rootView == null) {
1174:                                 return;
1175:                         }
1176:                         rootView.getMasterDetailSelectionProvider().addSelectionChangedListener(listener);
1177:                 }
1178:         }
1179: }