3 * Copyright (C) 2017 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.taxeditor
;
13 import java
.io
.IOException
;
15 import java
.net
.URLConnection
;
16 import java
.util
.Collections
;
19 import javax
.annotation
.PostConstruct
;
20 import javax
.inject
.Inject
;
21 import javax
.inject
.Named
;
23 import org
.eclipse
.core
.internal
.runtime
.PlatformURLPluginConnection
;
24 import org
.eclipse
.core
.runtime
.Platform
;
25 import org
.eclipse
.core
.runtime
.URIUtil
;
26 import org
.eclipse
.e4
.core
.contexts
.ContextInjectionFactory
;
27 import org
.eclipse
.e4
.core
.contexts
.IEclipseContext
;
28 import org
.eclipse
.e4
.core
.services
.log
.Logger
;
29 import org
.eclipse
.e4
.ui
.internal
.workbench
.CommandLineOptionModelProcessor
;
30 import org
.eclipse
.e4
.ui
.internal
.workbench
.E4Workbench
;
31 import org
.eclipse
.e4
.ui
.internal
.workbench
.E4XMIResourceFactory
;
32 import org
.eclipse
.e4
.ui
.internal
.workbench
.ModelAssembler
;
33 import org
.eclipse
.e4
.ui
.internal
.workbench
.URIHelper
;
34 import org
.eclipse
.e4
.ui
.model
.application
.MApplication
;
35 import org
.eclipse
.e4
.ui
.model
.application
.MApplicationElement
;
36 import org
.eclipse
.e4
.ui
.model
.application
.commands
.impl
.CommandsPackageImpl
;
37 import org
.eclipse
.e4
.ui
.model
.application
.impl
.ApplicationPackageImpl
;
38 import org
.eclipse
.e4
.ui
.model
.application
.ui
.advanced
.impl
.AdvancedPackageImpl
;
39 import org
.eclipse
.e4
.ui
.model
.application
.ui
.basic
.impl
.BasicPackageImpl
;
40 import org
.eclipse
.e4
.ui
.model
.application
.ui
.impl
.UiPackageImpl
;
41 import org
.eclipse
.e4
.ui
.model
.application
.ui
.menu
.impl
.MenuPackageImpl
;
42 import org
.eclipse
.e4
.ui
.workbench
.IModelResourceHandler
;
43 import org
.eclipse
.e4
.ui
.workbench
.IWorkbench
;
44 import org
.eclipse
.emf
.common
.util
.TreeIterator
;
45 import org
.eclipse
.emf
.common
.util
.URI
;
46 import org
.eclipse
.emf
.ecore
.EObject
;
47 import org
.eclipse
.emf
.ecore
.resource
.Resource
;
48 import org
.eclipse
.emf
.ecore
.resource
.URIConverter
;
49 import org
.eclipse
.emf
.ecore
.resource
.impl
.ResourceSetImpl
;
50 import org
.eclipse
.emf
.ecore
.util
.EcoreUtil
;
51 import org
.osgi
.framework
.Bundle
;
58 public class ModelResourceHandler
implements IModelResourceHandler
{
60 private static final String WORKBENCH_XMI
= "workbench.xmi";
61 private ResourceSetImpl resourceSetImpl
;
62 private Resource resource
;
65 private Logger logger
;
68 private IEclipseContext context
;
71 @Named(E4Workbench
.INITIAL_WORKBENCH_MODEL_URI
)
72 private URI applicationDefinitionInstance
;
75 * Dictates whether the model should be stored using EMF or with the merging algorithm.
76 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=295524
79 final private boolean saveAndRestore
;
80 final private boolean clearPersistedState
;
85 * @param saveAndRestore
86 * @param clearPersistedState
89 public ModelResourceHandler(@Named(IWorkbench
.PERSIST_STATE
) boolean saveAndRestore
,
90 @Named(IWorkbench
.CLEAR_PERSISTED_STATE
) boolean clearPersistedState
) {
91 this.saveAndRestore
= saveAndRestore
;
92 this.clearPersistedState
= clearPersistedState
;
97 resourceSetImpl
= new ResourceSetImpl();
98 resourceSetImpl
.getResourceFactoryRegistry().getExtensionToFactoryMap()
99 .put(Resource
.Factory
.Registry
.DEFAULT_EXTENSION
, new E4XMIResourceFactory());
101 resourceSetImpl
.getPackageRegistry().put(ApplicationPackageImpl
.eNS_URI
,
102 ApplicationPackageImpl
.eINSTANCE
);
103 resourceSetImpl
.getPackageRegistry().put(CommandsPackageImpl
.eNS_URI
,
104 CommandsPackageImpl
.eINSTANCE
);
105 resourceSetImpl
.getPackageRegistry().put(UiPackageImpl
.eNS_URI
, UiPackageImpl
.eINSTANCE
);
106 resourceSetImpl
.getPackageRegistry()
107 .put(MenuPackageImpl
.eNS_URI
, MenuPackageImpl
.eINSTANCE
);
108 resourceSetImpl
.getPackageRegistry().put(BasicPackageImpl
.eNS_URI
,
109 BasicPackageImpl
.eINSTANCE
);
110 resourceSetImpl
.getPackageRegistry().put(AdvancedPackageImpl
.eNS_URI
,
111 AdvancedPackageImpl
.eINSTANCE
);
113 .getPackageRegistry()
114 .put(org
.eclipse
.e4
.ui
.model
.application
.descriptor
.basic
.impl
.BasicPackageImpl
.eNS_URI
,
115 org
.eclipse
.e4
.ui
.model
.application
.descriptor
.basic
.impl
.BasicPackageImpl
.eINSTANCE
);
120 * @return {@code true} if the current application model has top-level windows.
122 public boolean hasTopLevelWindows() {
123 return hasTopLevelWindows(resource
);
127 * @return {@code true} if the specified application model has top-level windows.
129 private boolean hasTopLevelWindows(Resource applicationResource
) {
130 if (applicationResource
== null || applicationResource
.getContents() == null) {
131 // If the application resource doesn't exist or has no contents, then it has no
132 // top-level windows (and we are in an error state).
135 MApplication application
= (MApplication
) applicationResource
.getContents().get(0);
136 return !application
.getChildren().isEmpty();
140 public Resource
loadMostRecentModel() {
141 File workbenchData
= null;
142 URI restoreLocation
= null;
144 if (saveAndRestore
) {
145 workbenchData
= getWorkbenchSaveLocation();
146 restoreLocation
= URI
.createFileURI(workbenchData
.getAbsolutePath());
149 if (clearPersistedState
&& workbenchData
!= null && workbenchData
.exists()) {
150 workbenchData
.delete();
153 // last stored time-stamp
154 long restoreLastModified
= restoreLocation
== null ?
0L : new File(
155 restoreLocation
.toFileString()).lastModified();
157 // See bug 380663, bug 381219
158 // long lastApplicationModification = getLastApplicationModification();
159 // boolean restore = restoreLastModified > lastApplicationModification;
160 boolean restore
= restoreLastModified
> 0;
161 boolean initialModel
;
164 if (restore
&& saveAndRestore
) {
165 resource
= loadResource(restoreLocation
);
166 // If the saved model does not have any top-level windows, Eclipse will exit
167 // immediately, so throw out the persisted state and reinitialize with the defaults.
168 if (!hasTopLevelWindows(resource
)) {
169 if (logger
!= null) {
170 logger
.error(new Exception(), // log a stack trace to help debug the corruption
171 "The persisted workbench has no top-level windows, so reinitializing with defaults."); //$NON-NLS-1$
176 if (resource
== null) {
177 Resource applicationResource
= loadResource(applicationDefinitionInstance
);
178 MApplication theApp
= (MApplication
) applicationResource
.getContents().get(0);
179 resource
= createResourceWithApp(theApp
);
180 context
.set(E4Workbench
.NO_SAVED_MODEL_FOUND
, Boolean
.TRUE
);
183 initialModel
= false;
186 // Add model items described in the model extension point
187 // This has to be done before commands are put into the context
188 MApplication appElement
= (MApplication
) resource
.getContents().get(0);
190 this.context
.set(MApplication
.class, appElement
);
191 ModelAssembler contribProcessor
= ContextInjectionFactory
.make(ModelAssembler
.class,
193 contribProcessor
.processModel(initialModel
);
195 if (!hasTopLevelWindows(resource
) && logger
!= null) {
196 logger
.error(new Exception(), // log a stack trace to help debug the
198 "Initializing from the application definition instance yields no top-level windows! " //$NON-NLS-1$
199 + "Continuing execution, but the missing windows may cause other initialization failures."); //$NON-NLS-1$
202 if (!clearPersistedState
) {
203 CommandLineOptionModelProcessor processor
= ContextInjectionFactory
.make(
204 CommandLineOptionModelProcessor
.class, context
);
212 public void save() throws IOException
{
213 if (saveAndRestore
) {
219 * Creates a resource with an app Model, used for saving copies of the main app model.
222 * the application model to add to the resource
223 * @return a resource with a proper save path with the model as contents
226 public Resource
createResourceWithApp(MApplication theApp
) {
227 Resource res
= createResource();
228 res
.getContents().add((EObject
) theApp
);
232 private Resource
createResource() {
233 if (saveAndRestore
) {
234 URI saveLocation
= URI
.createFileURI(getWorkbenchSaveLocation().getAbsolutePath());
235 return resourceSetImpl
.createResource(saveLocation
);
237 return resourceSetImpl
.createResource(URI
.createURI(WORKBENCH_XMI
)); //$NON-NLS-1$
240 private File
getWorkbenchSaveLocation() {
241 File workbenchData
= new File(getBaseLocation(), WORKBENCH_XMI
); //$NON-NLS-1$
242 return workbenchData
;
245 private File
getBaseLocation() {
248 baseLocation
= new File(URIUtil
.toURI(Platform
.getInstallLocation().getURL()));
249 } catch (Exception e
) {
250 throw new RuntimeException(e
);
252 baseLocation
= new File(baseLocation
, "configuration"); //$NON-NLS-1$
256 // Ensures that even models with error are loaded!
257 private Resource
loadResource(URI uri
) {
260 resource
= getResource(uri
);
261 } catch (Exception e
) {
262 // TODO We could use diagnostics for better analyzing the error
263 logger
.error(e
, "Unable to load resource " + uri
.toString()); //$NON-NLS-1$
267 // TODO once we switch from deltas, we only need this once on the default model?
268 String contributorURI
= URIHelper
.EMFtoPlatform(uri
);
269 if (contributorURI
!= null) {
270 TreeIterator
<EObject
> it
= EcoreUtil
.getAllContents(resource
.getContents());
271 while (it
.hasNext()) {
272 EObject o
= it
.next();
273 if (o
instanceof MApplicationElement
) {
274 ((MApplicationElement
) o
).setContributorURI(contributorURI
);
281 private Resource
getResource(URI uri
) throws Exception
{
283 if (saveAndRestore
) {
284 resource
= resourceSetImpl
.getResource(uri
, true);
286 // Workaround for java.lang.IllegalStateException: No instance data can be specified
287 // thrown by org.eclipse.core.internal.runtime.DataArea.assertLocationInitialized
288 // The DataArea.assertLocationInitialized is called by ResourceSetImpl.getResource(URI,
290 resource
= resourceSetImpl
.createResource(uri
);
291 resource
.load(new URL(uri
.toString()).openStream(), resourceSetImpl
.getLoadOptions());
297 protected long getLastApplicationModification() {
298 long appLastModified
= 0L;
299 ResourceSetImpl resourceSetImpl
= new ResourceSetImpl();
301 Map
<String
, ?
> attributes
= resourceSetImpl
.getURIConverter().getAttributes(
302 applicationDefinitionInstance
,
303 Collections
.singletonMap(URIConverter
.OPTION_REQUESTED_ATTRIBUTES
,
304 Collections
.singleton(URIConverter
.ATTRIBUTE_TIME_STAMP
)));
306 Object timestamp
= attributes
.get(URIConverter
.ATTRIBUTE_TIME_STAMP
);
307 if (timestamp
instanceof Long
) {
308 appLastModified
= ((Long
) timestamp
).longValue();
309 } else if (applicationDefinitionInstance
.isPlatformPlugin()) {
311 java
.net
.URL url
= new java
.net
.URL(applicationDefinitionInstance
.toString());
312 // can't just use 'url.openConnection()' as it usually returns a
313 // PlatformURLPluginConnection which doesn't expose the
314 // last-modification time. So we try to resolve the file through
315 // the bundle to obtain a BundleURLConnection instead.
316 Object
[] obj
= PlatformURLPluginConnection
.parse(url
.getFile().trim(), url
);
317 Bundle b
= (Bundle
) obj
[0];
318 // first try to resolve as an bundle file entry, then as a resource using
319 // the bundle's classpath
320 java
.net
.URL resolved
= b
.getEntry((String
) obj
[1]);
321 if (resolved
== null) {
322 resolved
= b
.getResource((String
) obj
[1]);
324 if (resolved
!= null) {
325 URLConnection openConnection
= resolved
.openConnection();
326 appLastModified
= openConnection
.getLastModified();
328 } catch (Exception e
) {
333 return appLastModified
;