Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 4cf7fafb50a3cf47e6b5d8bbf0bdeb2cfbd25431 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*****************************************************************************
 * Copyright (c) 2015 Christian W. Damus 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:
 *   Christian W. Damus - Initial API and implementation
 *   
 *****************************************************************************/

package org.eclipse.papyrus.infra.editor.welcome.internal;

import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.ResourceLocator;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
import org.eclipse.emf.transaction.RollbackException;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.sasheditor.di.contentprovider.utils.TransactionHelper;
import org.eclipse.papyrus.infra.editor.welcome.Welcome;
import org.eclipse.papyrus.infra.editor.welcome.WelcomeFactory;
import org.eclipse.papyrus.infra.editor.welcome.WelcomePackage;

/**
 * A manager of the default {@link Welcome} model for the workspace.
 */
public class WelcomeModelManager {
	private final URI welcomeURI = URI.createURI("papyrus.welcome:dynamic"); //$NON-NLS-1$

	private final Path welcomePath;

	private final Map<ModelSet, WelcomeLocator> resourceSets = new HashMap<>();
	private final Map<ModelSet, Consumer<Welcome>> welcomeChangedHandlers = new HashMap<>();

	public WelcomeModelManager(Path stateLocation) {
		super();

		welcomePath = stateLocation.resolve("welcome.xmi").toAbsolutePath(); //$NON-NLS-1$
	}

	public URI getWelcomeURI() {
		return welcomeURI;
	}

	public boolean welcomeModelExists() {
		return Files.exists(welcomePath);
	}

	public Welcome getWelcome(ResourceSet resourceSet) {
		Resource resource = getWelcomeResource(resourceSet);
		return (resource == null) ? null : getWelcome(resource);
	}

	public Resource getWelcomeResource(ResourceSet resourceSet) {
		return resourceSet.getResource(getWelcomeURI(), true);
	}

	public void connect(ModelSet resourceSet) {
		resourceSets.put(resourceSet, new WelcomeLocator(resourceSet));
	}

	public void disconnect(ModelSet resourceSet) {
		welcomeChangedHandlers.remove(resourceSet);

		WelcomeLocator locator = resourceSets.remove(resourceSet);
		if (locator != null) {
			locator.dispose();
		}
	}

	/**
	 * Adds a handler to be invoked whenever the default {@link Welcome} model changes.
	 * 
	 * @param ownerModelSet
	 *            the model set in which the default welcome model is used
	 * 
	 * @param welcomeChangedHandler
	 *            a handler that reacts to the changed welcome model
	 * 
	 * @throws IllegalArgumentException
	 *             if the {@code ownerModelSet} is not currently
	 *             {@linkplain #connect(ModelSet) connected} to the model manager
	 */
	public void onWelcomeChanged(ModelSet ownerModelSet, Consumer<? super Welcome> welcomeChangedHandler) {
		if (!resourceSets.containsKey(ownerModelSet)) {
			throw new IllegalArgumentException("ownerModelSet is not connected"); //$NON-NLS-1$
		}

		// Chain handlers, with a base handler that validates the incoming Welcome instance
		welcomeChangedHandlers.put(ownerModelSet,
				getWelcomeChangedHandler(ownerModelSet).andThen(welcomeChangedHandler));
	}

	private Consumer<Welcome> getWelcomeChangedHandler(ResourceSet resourceSet) {
		return welcomeChangedHandlers.getOrDefault(resourceSet, Objects::requireNonNull);
	}

	public void createDefaultWelcomeResource(Welcome welcome) throws IOException {
		Resource welcomeResource = new XMIResourceImpl(getWelcomeURI());
		welcomeResource.getContents().add(EcoreUtil.copy(welcome));

		try (OutputStream output = Files.newOutputStream(welcomePath, WRITE, CREATE)) {
			welcomeResource.save(output, null);

			notifyDefaultChanged();
		} catch (IOException e) {
			// Make sure that any partial file is cleaned up
			Files.deleteIfExists(welcomePath);
			throw e;
		} finally {
			welcomeResource.unload();
			welcomeResource.eAdapters().clear();
		}
	}

	public void deleteDefaultWelcomeResource() throws IOException {
		Files.deleteIfExists(welcomePath);

		notifyDefaultChanged();
	}

	private void notifyDefaultChanged() {
		resourceSets.keySet().stream()
				.map(this::getWelcomeResource)
				// This notifies the adapter, which re-loads the resource and notifies clients
				.forEach(Resource::unload);
	}

	static Welcome getWelcome(Resource resource) {
		return (Welcome) EcoreUtil.getObjectByType(resource.getContents(), WelcomePackage.Literals.WELCOME);
	}

	/**
	 * Make an on old object become something new by morphing into its likeness.
	 * 
	 * @param oldObject
	 *            the old object, which identity we need to keep
	 * @param newObject
	 *            what the old object should be transformed into to look like
	 * 
	 * @return the {@code oldObject}
	 */
	static Welcome become(Welcome oldObject, Welcome newObject) {
		if (oldObject == newObject) {
			return oldObject;
		}

		// Re-initialize the old object
		oldObject.eClass().getEAllStructuralFeatures().stream()
				.filter(EStructuralFeature::isChangeable)
				.forEach(f -> oldObject.eUnset(f));

		// And repopuplate it from the new state
		@SuppressWarnings("serial")
		EcoreUtil.Copier copier = new EcoreUtil.Copier(true, true) {
			@Override
			protected EObject createCopy(EObject eObject) {
				// We already have this "copy"
				return (eObject == newObject) ? oldObject : super.createCopy(eObject);
			}
		};

		// Replace the new object with the old
		EcoreUtil.replace(newObject, oldObject);

		return (Welcome) copier.copy(newObject);
	}

	//
	// Nested types
	//

	private class WelcomeLocator extends ResourceLocator {
		private final Resource welcomeResource;

		WelcomeLocator(ModelSet modelSet) {
			super(modelSet);

			welcomeResource = new XMIResourceImpl(getWelcomeURI()) {
				@Override
				public ResourceSet getResourceSet() {
					// Yes, this is a violation of the opposite constraint
					return modelSet;
				}

				@Override
				public NotificationChain basicSetResourceSet(ResourceSet resourceSet, NotificationChain notifications) {
					throw new UnsupportedOperationException("setResourceSet");
				}
			};

			welcomeResource.eAdapters().add(new DefaultWelcomeAdapter());
		}

		@Override
		public Resource getResource(URI uri, boolean loadOnDemand) {
			Resource result;

			if (uri.equals(welcomeResource.getURI())) {
				// Always implicitly load this one
				result = getWelcomeResource();
			} else {
				result = basicGetResource(uri, loadOnDemand);
			}

			return result;
		}

		@Override
		protected void demandLoadHelper(Resource resource) {
			if (getWelcomeURI().equals(resource.getURI())) {
				Welcome welcome = null;

				if (welcomeModelExists()) {
					try {
						try (InputStream input = Files.newInputStream(welcomePath, StandardOpenOption.READ)) {
							welcomeResource.load(input, null);
							welcome = WelcomeModelManager.getWelcome(welcomeResource);
						}
					} catch (IOException e) {
						Activator.log.error("Failed to load default welcome page layout", e); //$NON-NLS-1$
						welcomeResource.getContents().clear(); // In case of partial load
					}
				}

				if (welcome == null) {
					welcome = WelcomeFactory.eINSTANCE.createWelcome();
					welcomeResource.getContents().add(welcome);
				}
			} else {
				super.demandLoadHelper(resource);
			}
		}

		// More or less copied from EMF
		@Override
		protected Resource basicGetResource(URI uri, boolean loadOnDemand) {
			Map<URI, Resource> map = resourceSet.getURIResourceMap();
			if (map != null) {
				Resource resource = map.get(uri);
				if (resource != null) {
					if (loadOnDemand && !resource.isLoaded()) {
						demandLoadHelper(resource);
					}
					return resource;
				}
			}

			URIConverter theURIConverter = resourceSet.getURIConverter();
			URI normalizedURI = theURIConverter.normalize(uri);
			for (Resource resource : resourceSet.getResources()) {
				if (theURIConverter.normalize(resource.getURI()).equals(normalizedURI)) {
					if (loadOnDemand && !resource.isLoaded()) {
						demandLoadHelper(resource);
					}

					if (map != null) {
						map.put(uri, resource);
					}
					return resource;
				}
			}

			Resource delegatedResource = delegatedGetResource(uri, loadOnDemand);
			if (delegatedResource != null) {
				if (map != null) {
					map.put(uri, delegatedResource);
				}
				return delegatedResource;
			}

			if (loadOnDemand) {
				Resource resource = demandCreateResource(uri);
				if (resource == null) {
					throw new IllegalArgumentException(String.format("Cannot create a resource for '%s'; a registered resource factory is needed", uri));
				}

				demandLoadHelper(resource);

				if (map != null) {
					map.put(uri, resource);
				}
				return resource;
			}

			return null;

		}

		@Override
		public void dispose() {
			super.dispose();

			// Ensure that our adapter is no longer listening to re-load the resource
			welcomeResource.eAdapters().clear();
			welcomeResource.unload();
		}

		Resource getWelcomeResource() {
			if (!welcomeResource.isLoaded()) {
				demandLoadHelper(welcomeResource);
			}
			return welcomeResource;
		}
	}

	private class DefaultWelcomeAdapter extends AdapterImpl {
		private Welcome realWelcome;

		@Override
		public void unsetTarget(Notifier oldTarget) {
			super.unsetTarget(oldTarget);
			realWelcome = null;
		}

		@Override
		public void notifyChanged(Notification msg) {
			if (msg.getNotifier() instanceof Resource) {
				switch (msg.getFeatureID(Resource.class)) {
				case Resource.RESOURCE__CONTENTS:
					switch (msg.getEventType()) {
					case Notification.REMOVE:
						if (msg.getOldValue() instanceof Welcome) {
							realWelcome = (Welcome) msg.getOldValue();
						}
						break;
					case Notification.REMOVE_MANY:
						realWelcome = (Welcome) EcoreUtil.getObjectByType((Collection<?>) msg.getOldValue(), WelcomePackage.Literals.WELCOME);
						break;
					}
					break;
				case Resource.RESOURCE__IS_LOADED:
					if (msg.getOldBooleanValue() && !msg.getNewBooleanValue()) {
						ResourceSet rset = ((Resource) msg.getNotifier()).getResourceSet();

						// The resource was unloaded. Re-load it and push
						// the new Welcome object into the Welcome Page if
						// necessary. Be sure to maintain the old welcome's identity
						// for integrity of the welcome page reference
						try {
							TransactionHelper.run(TransactionUtil.getEditingDomain(rset), () -> {
								Welcome newWelcome = getWelcome(rset);
								become(realWelcome, newWelcome);

								// And it was unloaded, so it has a proxy URI that it shouldn't
								((InternalEObject) realWelcome).eSetProxyURI(null);
							});
						} catch (InterruptedException | RollbackException e) {
							Activator.log.error("Failed to re-load the default welcome model.", e); //$NON-NLS-1$
						}

						// Notify the client
						getWelcomeChangedHandler(rset).accept(realWelcome);

						realWelcome = null; // Don't need to hang onto this any longer
					}
					break;
				}
			}
		}
	}

}

Back to the top