1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * 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  *     IBM Corporation - initial API and implementation
13  *     Danail Nachev - exception handling for registry listeners (bug 188369)
14  *******************************************************************************/
15 package org.eclipse.core.internal.registry;
16 
17 import java.io.*;
18 import java.lang.reflect.Array;
19 import java.util.*;
20 import javax.xml.parsers.ParserConfigurationException;
21 import org.eclipse.core.internal.registry.spi.ConfigurationElementAttribute;
22 import org.eclipse.core.internal.registry.spi.ConfigurationElementDescription;
23 import org.eclipse.core.runtime.*;
24 import org.eclipse.core.runtime.spi.*;
25 import org.eclipse.osgi.storagemanager.StorageManager;
26 import org.eclipse.osgi.util.NLS;
27 import org.xml.sax.InputSource;
28 import org.xml.sax.SAXException;
29 
30 /**
31  * An implementation for the extension registry API.
32  */
33 public class ExtensionRegistry implements IExtensionRegistry, IDynamicExtensionRegistry {
34 
35 	protected class ListenerInfo {
36 		public String filter;
37 		public EventListener listener;
38 
ListenerInfo(EventListener listener, String filter)39 		public ListenerInfo(EventListener listener, String filter) {
40 			this.listener = listener;
41 			this.filter = filter;
42 		}
43 
44 		/**
45 		 * Used by ListenerList to ensure uniqueness.
46 		 */
47 		@Override
equals(Object another)48 		public boolean equals(Object another) {
49 			return another instanceof ListenerInfo && ((ListenerInfo) another).listener == this.listener;
50 		}
51 
52 		/* (non-Javadoc)
53 		 * @see java.lang.Object#hashCode()
54 		 */
55 		@Override
hashCode()56 		public int hashCode() {
57 			return listener == null ? 0 : listener.hashCode();
58 		}
59 	}
60 
61 	// used to enforce concurrent access policy for readers/writers
62 	private final ReadWriteMonitor access = new ReadWriteMonitor();
63 
64 	// deltas not broadcasted yet. Deltas are kept organized by the namespace name (objects with the same namespace are grouped together)
65 	private transient Map<String, Object> deltas = new HashMap<>(11);
66 
67 	//storage manager associated with the registry cache
68 	protected StorageManager cacheStorageManager;
69 
70 	// all registry change listeners
71 	private transient ListenerList<ListenerInfo> listeners = new ListenerList<>();
72 
73 	private RegistryObjectManager registryObjects = null;
74 
75 	// Table reader associated with this extension registry
76 	protected TableReader theTableReader = new TableReader(this);
77 
78 	private final Object masterToken; // use to get full control of the registry; objects created as "static"
79 	private final Object userToken; // use to modify non-persisted registry elements
80 
81 	protected RegistryStrategy strategy; // overridable portions of the registry functionality
82 
83 	private final RegistryTimestamp aggregatedTimestamp = new RegistryTimestamp(); // tracks current contents of the registry
84 
85 	// encapsulates processing of new registry deltas
86 	private CombinedEventDelta eventDelta = null;
87 	// marks a new extended delta. The namespace that normally would not exists is used for this purpose
88 	private final static String notNamespace = ""; //$NON-NLS-1$
89 
90 	// does this instance of the extension registry has multiple language support enabled?
91 	private final boolean isMultiLanguage;
92 
93 	// have we already logged a error on usage of an unsupported multi-language method?
94 	private boolean mlErrorLogged = false;
95 
getObjectManager()96 	public RegistryObjectManager getObjectManager() {
97 		return registryObjects;
98 	}
99 
100 	/**
101 	 * Sets new cache file manager. If existing file manager was owned by the registry,
102 	 * closes it.
103 	 *
104 	 * @param cacheBase the base location for the registry cache
105 	 * @param isCacheReadOnly whether the file cache is read only
106 	 */
setFileManager(File cacheBase, boolean isCacheReadOnly)107 	protected void setFileManager(File cacheBase, boolean isCacheReadOnly) {
108 		if (cacheStorageManager != null)
109 			cacheStorageManager.close(); // close existing file manager first
110 
111 		if (cacheBase != null) {
112 			cacheStorageManager = new StorageManager(cacheBase, isCacheReadOnly ? "none" : null, isCacheReadOnly); //$NON-NLS-1$
113 			try {
114 				cacheStorageManager.open(!isCacheReadOnly);
115 			} catch (IOException e) {
116 				// Ignore the exception. The registry will be rebuilt from source.
117 			}
118 		}
119 	}
120 
121 	/**
122 	 * Adds and resolves all extensions and extension points provided by the
123 	 * plug-in.
124 	 * <p>
125 	 * A corresponding IRegistryChangeEvent will be broadcast to all listeners
126 	 * interested on changes in the given plug-in.
127 	 * </p>
128 	 */
add(Contribution element)129 	private void add(Contribution element) {
130 		access.enterWrite();
131 		try {
132 			eventDelta = CombinedEventDelta.recordAddition();
133 			basicAdd(element, true);
134 			fireRegistryChangeEvent();
135 			eventDelta = null;
136 		} finally {
137 			access.exitWrite();
138 		}
139 	}
140 
141 	/* Utility method to help with array concatenations */
concatArrays(Object a, Object b)142 	static Object concatArrays(Object a, Object b) {
143 		Object[] result = (Object[]) Array.newInstance(a.getClass().getComponentType(), Array.getLength(a) + Array.getLength(b));
144 		System.arraycopy(a, 0, result, 0, Array.getLength(a));
145 		System.arraycopy(b, 0, result, Array.getLength(a), Array.getLength(b));
146 		return result;
147 	}
148 
addExtension(int extension)149 	private String addExtension(int extension) {
150 		Extension addedExtension = (Extension) registryObjects.getObject(extension, RegistryObjectManager.EXTENSION);
151 		String extensionPointToAddTo = addedExtension.getExtensionPointIdentifier();
152 		ExtensionPoint extPoint = registryObjects.getExtensionPointObject(extensionPointToAddTo);
153 		//orphan extension
154 		if (extPoint == null) {
155 			registryObjects.addOrphan(extensionPointToAddTo, extension);
156 			return null;
157 		}
158 		// otherwise, link them
159 		int[] newExtensions;
160 		int[] existingExtensions = extPoint.getRawChildren();
161 		newExtensions = new int[existingExtensions.length + 1];
162 		System.arraycopy(existingExtensions, 0, newExtensions, 0, existingExtensions.length);
163 		newExtensions[newExtensions.length - 1] = extension;
164 		link(extPoint, newExtensions);
165 		if (eventDelta != null)
166 			eventDelta.rememberExtension(extPoint, extension);
167 		return recordChange(extPoint, extension, IExtensionDelta.ADDED);
168 	}
169 
170 	/**
171 	 * Looks for existing orphan extensions to connect to the given extension
172 	 * point. If none is found, there is nothing to do. Otherwise, link them.
173 	 */
addExtensionPoint(int extPoint)174 	private String addExtensionPoint(int extPoint) {
175 		ExtensionPoint extensionPoint = (ExtensionPoint) registryObjects.getObject(extPoint, RegistryObjectManager.EXTENSION_POINT);
176 		if (eventDelta != null)
177 			eventDelta.rememberExtensionPoint(extensionPoint);
178 		int[] orphans = registryObjects.removeOrphans(extensionPoint.getUniqueIdentifier());
179 		if (orphans == null)
180 			return null;
181 		link(extensionPoint, orphans);
182 		if (eventDelta != null)
183 			eventDelta.rememberExtensions(extensionPoint, orphans);
184 		return recordChange(extensionPoint, orphans, IExtensionDelta.ADDED);
185 	}
186 
addExtensionsAndExtensionPoints(Contribution element)187 	private Set<String> addExtensionsAndExtensionPoints(Contribution element) {
188 		// now add and resolve extensions and extension points
189 		Set<String> affectedNamespaces = new HashSet<>();
190 		for (int extPoint : element.getExtensionPoints()) {
191 			String namespace = this.addExtensionPoint(extPoint);
192 			if (namespace != null)
193 				affectedNamespaces.add(namespace);
194 		}
195 		for (int extension : element.getExtensions()) {
196 			String namespace = this.addExtension(extension);
197 			if (namespace != null)
198 				affectedNamespaces.add(namespace);
199 		}
200 		return affectedNamespaces;
201 	}
202 
203 	@Override
addListener(IRegistryEventListener listener)204 	public void addListener(IRegistryEventListener listener) {
205 		addListenerInternal(listener, null);
206 	}
207 
208 	@Override
addListener(IRegistryEventListener listener, String extensionPointId)209 	public void addListener(IRegistryEventListener listener, String extensionPointId) {
210 		addListenerInternal(listener, extensionPointId);
211 	}
212 
addListenerInternal(EventListener listener, String filter)213 	private void addListenerInternal(EventListener listener, String filter) {
214 		synchronized (listeners) {
215 			listeners.add(new ListenerInfo(listener, filter));
216 		}
217 	}
218 
219 	@Override
addRegistryChangeListener(IRegistryChangeListener listener)220 	public void addRegistryChangeListener(IRegistryChangeListener listener) {
221 		// this is just a convenience API - no need to do any sync'ing here
222 		addListenerInternal(listener, null);
223 	}
224 
225 	@Override
addRegistryChangeListener(IRegistryChangeListener listener, String filter)226 	public void addRegistryChangeListener(IRegistryChangeListener listener, String filter) {
227 		addListenerInternal(listener, filter);
228 	}
229 
basicAdd(Contribution element, boolean link)230 	private void basicAdd(Contribution element, boolean link) {
231 		registryObjects.addContribution(element);
232 		if (!link)
233 			return;
234 		Set<String> affectedNamespaces = addExtensionsAndExtensionPoints(element);
235 		setObjectManagers(affectedNamespaces, registryObjects.createDelegatingObjectManager(registryObjects.getAssociatedObjects(element.getContributorId())));
236 	}
237 
setObjectManagers(Set<String> affectedNamespaces, IObjectManager manager)238 	private void setObjectManagers(Set<String> affectedNamespaces, IObjectManager manager) {
239 		for (String namespace : affectedNamespaces) {
240 			getDelta(namespace).setObjectManager(manager);
241 		}
242 		if (eventDelta != null)
243 			eventDelta.setObjectManager(manager);
244 	}
245 
basicRemove(String contributorId)246 	private void basicRemove(String contributorId) {
247 		// ignore anonymous namespaces
248 		Set<String> affectedNamespaces = removeExtensionsAndExtensionPoints(contributorId);
249 		Map<Integer, RegistryObject> associatedObjects = registryObjects.getAssociatedObjects(contributorId);
250 		registryObjects.removeObjects(associatedObjects);
251 		registryObjects.addNavigableObjects(associatedObjects); // put the complete set of navigable objects
252 		setObjectManagers(affectedNamespaces, registryObjects.createDelegatingObjectManager(associatedObjects));
253 
254 		registryObjects.removeContribution(contributorId);
255 		registryObjects.removeContributor(contributorId);
256 	}
257 
258 	// allow other objects in the registry to use the same lock
enterRead()259 	void enterRead() {
260 		access.enterRead();
261 	}
262 
263 	// allow other objects in the registry to use the same lock
exitRead()264 	void exitRead() {
265 		access.exitRead();
266 	}
267 
268 	/**
269 	 * Broadcasts (asynchronously) the event to all interested parties.
270 	 */
fireRegistryChangeEvent()271 	private void fireRegistryChangeEvent() {
272 		// pack new extended delta together with the rest of deltas using invalid namespace
273 		deltas.put(notNamespace, eventDelta);
274 		// if there is nothing to say, just bail out
275 		if (listeners.isEmpty()) {
276 			deltas.clear();
277 			return;
278 		}
279 		// for thread safety, create tmp collections
280 		Object[] tmpListeners = listeners.getListeners();
281 		Map<String, Object> tmpDeltas = new HashMap<>(this.deltas);
282 		// the deltas have been saved for notification - we can clear them now
283 		deltas.clear();
284 		// do the notification asynchronously
285 		strategy.scheduleChangeEvent(tmpListeners, tmpDeltas, this);
286 	}
287 
288 	/*
289 	 *  (non-Javadoc)
290 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getConfigurationElementsFor(java.lang.String)
291 	 */
292 	@Override
getConfigurationElementsFor(String extensionPointId)293 	public IConfigurationElement[] getConfigurationElementsFor(String extensionPointId) {
294 		// this is just a convenience API - no need to do any sync'ing here
295 		int lastdot = extensionPointId.lastIndexOf('.');
296 		if (lastdot == -1)
297 			return new IConfigurationElement[0];
298 		return getConfigurationElementsFor(extensionPointId.substring(0, lastdot), extensionPointId.substring(lastdot + 1));
299 	}
300 
301 	/*
302 	 *  (non-Javadoc)
303 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getConfigurationElementsFor(java.lang.String, java.lang.String)
304 	 */
305 	@Override
getConfigurationElementsFor(String pluginId, String extensionPointSimpleId)306 	public IConfigurationElement[] getConfigurationElementsFor(String pluginId, String extensionPointSimpleId) {
307 		// this is just a convenience API - no need to do any sync'ing here
308 		IExtensionPoint extPoint = this.getExtensionPoint(pluginId, extensionPointSimpleId);
309 		if (extPoint == null)
310 			return new IConfigurationElement[0];
311 		return extPoint.getConfigurationElements();
312 	}
313 
314 	/*
315 	 *  (non-Javadoc)
316 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getConfigurationElementsFor(java.lang.String, java.lang.String, java.lang.String)
317 	 */
318 	@Override
getConfigurationElementsFor(String pluginId, String extensionPointName, String extensionId)319 	public IConfigurationElement[] getConfigurationElementsFor(String pluginId, String extensionPointName, String extensionId) {
320 		// this is just a convenience API - no need to do any sync'ing here
321 		IExtension extension = this.getExtension(pluginId, extensionPointName, extensionId);
322 		if (extension == null)
323 			return new IConfigurationElement[0];
324 		return extension.getConfigurationElements();
325 	}
326 
getDelta(String namespace)327 	private RegistryDelta getDelta(String namespace) {
328 		// is there a delta for the plug-in?
329 		RegistryDelta existingDelta = (RegistryDelta) deltas.get(namespace);
330 		if (existingDelta != null)
331 			return existingDelta;
332 
333 		//if not, create one
334 		RegistryDelta delta = new RegistryDelta();
335 		deltas.put(namespace, delta);
336 		return delta;
337 	}
338 
339 	/*
340 	 *  (non-Javadoc)
341 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtension(java.lang.String)
342 	 */
343 	@Override
getExtension(String extensionId)344 	public IExtension getExtension(String extensionId) {
345 		if (extensionId == null)
346 			return null;
347 		int lastdot = extensionId.lastIndexOf('.');
348 		if (lastdot == -1)
349 			return null;
350 		String namespace = extensionId.substring(0, lastdot);
351 
352 		ExtensionHandle[] extensions;
353 		access.enterRead();
354 		try {
355 			extensions = registryObjects.getExtensionsFromNamespace(namespace);
356 		} finally {
357 			access.exitRead();
358 		}
359 		for (ExtensionHandle suspect : extensions) {
360 			if (extensionId.equals(suspect.getUniqueIdentifier()))
361 				return suspect;
362 		}
363 		return null;
364 	}
365 
366 	/*
367 	 *  (non-Javadoc)
368 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtension(java.lang.String, java.lang.String)
369 	 */
370 	@Override
getExtension(String extensionPointId, String extensionId)371 	public IExtension getExtension(String extensionPointId, String extensionId) {
372 		// this is just a convenience API - no need to do any sync'ing here
373 		int lastdot = extensionPointId.lastIndexOf('.');
374 		if (lastdot == -1)
375 			return null;
376 		return getExtension(extensionPointId.substring(0, lastdot), extensionPointId.substring(lastdot + 1), extensionId);
377 	}
378 
379 	/*
380 	 *  (non-Javadoc)
381 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtension(java.lang.String, java.lang.String, java.lang.String)
382 	 */
383 	@Override
getExtension(String pluginId, String extensionPointName, String extensionId)384 	public IExtension getExtension(String pluginId, String extensionPointName, String extensionId) {
385 		// this is just a convenience API - no need to do any sync'ing here
386 		IExtensionPoint extPoint = getExtensionPoint(pluginId, extensionPointName);
387 		if (extPoint != null)
388 			return extPoint.getExtension(extensionId);
389 		return null;
390 	}
391 
392 	/*
393 	 *  (non-Javadoc)
394 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoint(java.lang.String)
395 	 */
396 	@Override
getExtensionPoint(String xptUniqueId)397 	public IExtensionPoint getExtensionPoint(String xptUniqueId) {
398 		access.enterRead();
399 		try {
400 			return registryObjects.getExtensionPointHandle(xptUniqueId);
401 		} finally {
402 			access.exitRead();
403 		}
404 	}
405 
406 	/*
407 	 *  (non-Javadoc)
408 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoint(java.lang.String, java.lang.String)
409 	 */
410 	@Override
getExtensionPoint(String elementName, String xpt)411 	public IExtensionPoint getExtensionPoint(String elementName, String xpt) {
412 		access.enterRead();
413 		try {
414 			return registryObjects.getExtensionPointHandle(elementName + '.' + xpt);
415 		} finally {
416 			access.exitRead();
417 		}
418 	}
419 
420 	/*
421 	 *  (non-Javadoc)
422 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoints()
423 	 */
424 	@Override
getExtensionPoints()425 	public IExtensionPoint[] getExtensionPoints() {
426 		access.enterRead();
427 		try {
428 			return registryObjects.getExtensionPointsHandles();
429 		} finally {
430 			access.exitRead();
431 		}
432 	}
433 
434 	/*
435 	 *  (non-Javadoc)
436 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoints(java.lang.String)
437 	 */
438 	@Override
getExtensionPoints(String namespaceName)439 	public IExtensionPoint[] getExtensionPoints(String namespaceName) {
440 		access.enterRead();
441 		try {
442 			return registryObjects.getExtensionPointsFromNamespace(namespaceName);
443 		} finally {
444 			access.exitRead();
445 		}
446 	}
447 
448 	/*
449 	 *  (non-Javadoc)
450 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensions(java.lang.String)
451 	 */
452 	@Override
getExtensions(String namespaceName)453 	public IExtension[] getExtensions(String namespaceName) {
454 		access.enterRead();
455 		try {
456 			return registryObjects.getExtensionsFromNamespace(namespaceName);
457 		} finally {
458 			access.exitRead();
459 		}
460 	}
461 
462 	@Override
getExtensions(IContributor contributor)463 	public IExtension[] getExtensions(IContributor contributor) {
464 		if (!(contributor instanceof RegistryContributor))
465 			throw new IllegalArgumentException(); // should never happen
466 		String contributorId = ((RegistryContributor) contributor).getActualId();
467 		access.enterRead();
468 		try {
469 			return registryObjects.getExtensionsFromContributor(contributorId);
470 		} finally {
471 			access.exitRead();
472 		}
473 	}
474 
475 	@Override
getExtensionPoints(IContributor contributor)476 	public IExtensionPoint[] getExtensionPoints(IContributor contributor) {
477 		if (!(contributor instanceof RegistryContributor))
478 			throw new IllegalArgumentException(); // should never happen
479 		String contributorId = ((RegistryContributor) contributor).getActualId();
480 		access.enterRead();
481 		try {
482 			return registryObjects.getExtensionPointsFromContributor(contributorId);
483 		} finally {
484 			access.exitRead();
485 		}
486 	}
487 
488 	/*
489 	 *  (non-Javadoc)
490 	 * @see org.eclipse.core.runtime.IExtensionRegistry#getNamespaces()
491 	 */
492 	@Override
getNamespaces()493 	public String[] getNamespaces() {
494 		access.enterRead();
495 		try {
496 			KeyedElement[] namespaceElements = registryObjects.getNamespacesIndex().elements();
497 			String[] namespaceNames = new String[namespaceElements.length];
498 			for (int i = 0; i < namespaceElements.length; i++) {
499 				namespaceNames[i] = (String) ((RegistryIndexElement) namespaceElements[i]).getKey();
500 			}
501 			return namespaceNames;
502 		} finally {
503 			access.exitRead();
504 		}
505 	}
506 
507 	@Override
hasContributor(IContributor contributor)508 	public boolean hasContributor(IContributor contributor) {
509 		if (!(contributor instanceof RegistryContributor))
510 			throw new IllegalArgumentException(); // should never happen
511 		String contributorId = ((RegistryContributor) contributor).getActualId();
512 		return hasContributor(contributorId);
513 	}
514 
hasContributor(String contributorId)515 	public boolean hasContributor(String contributorId) {
516 		access.enterRead();
517 		try {
518 			return registryObjects.hasContribution(contributorId);
519 		} finally {
520 			access.exitRead();
521 		}
522 	}
523 
link(ExtensionPoint extPoint, int[] extensions)524 	private void link(ExtensionPoint extPoint, int[] extensions) {
525 		extPoint.setRawChildren(extensions);
526 		registryObjects.add(extPoint, true);
527 	}
528 
529 	/*
530 	 * Records an extension addition/removal.
531 	 */
recordChange(ExtensionPoint extPoint, int extension, int kind)532 	private String recordChange(ExtensionPoint extPoint, int extension, int kind) {
533 		// avoid computing deltas when there are no listeners
534 		if (listeners.isEmpty())
535 			return null;
536 		ExtensionDelta extensionDelta = new ExtensionDelta();
537 		extensionDelta.setExtension(extension);
538 		extensionDelta.setExtensionPoint(extPoint.getObjectId());
539 		extensionDelta.setKind(kind);
540 		getDelta(extPoint.getNamespace()).addExtensionDelta(extensionDelta);
541 		return extPoint.getNamespace();
542 	}
543 
544 	/*
545 	 * Records a set of extension additions/removals.
546 	 */
recordChange(ExtensionPoint extPoint, int[] extensions, int kind)547 	private String recordChange(ExtensionPoint extPoint, int[] extensions, int kind) {
548 		if (listeners.isEmpty())
549 			return null;
550 		String namespace = extPoint.getNamespace();
551 		if (extensions == null || extensions.length == 0)
552 			return namespace;
553 		RegistryDelta pluginDelta = getDelta(extPoint.getNamespace());
554 		for (int extension : extensions) {
555 			ExtensionDelta extensionDelta = new ExtensionDelta();
556 			extensionDelta.setExtension(extension);
557 			extensionDelta.setExtensionPoint(extPoint.getObjectId());
558 			extensionDelta.setKind(kind);
559 			pluginDelta.addExtensionDelta(extensionDelta);
560 		}
561 		return namespace;
562 	}
563 
remove(String removedContributorId, long timestamp)564 	public void remove(String removedContributorId, long timestamp) {
565 		remove(removedContributorId);
566 		if (timestamp != 0)
567 			aggregatedTimestamp.remove(timestamp);
568 	}
569 
570 	@Override
removeContributor(IContributor contributor, Object key)571 	public void removeContributor(IContributor contributor, Object key) {
572 		if (!(contributor instanceof RegistryContributor))
573 			throw new IllegalArgumentException(); // should never happen
574 		if (!checkReadWriteAccess(key, true))
575 			throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.removeContributor() method. Check if proper access token is supplied."); //$NON-NLS-1$
576 		String contributorId = ((RegistryContributor) contributor).getActualId();
577 		remove(contributorId);
578 	}
579 
580 	/**
581 	 * Unresolves and removes all extensions and extension points provided by
582 	 * the plug-in.
583 	 * <p>
584 	 * A corresponding IRegistryChangeEvent will be broadcast to all listeners
585 	 * interested on changes in the given plug-in.
586 	 * </p>
587 	 */
remove(String removedContributorId)588 	public void remove(String removedContributorId) {
589 		access.enterWrite();
590 		try {
591 			eventDelta = CombinedEventDelta.recordRemoval();
592 			basicRemove(removedContributorId);
593 			fireRegistryChangeEvent();
594 			eventDelta = null;
595 		} finally {
596 			access.exitWrite();
597 		}
598 	}
599 
600 	//Return the affected namespace
removeExtension(int extensionId)601 	private String removeExtension(int extensionId) {
602 		Extension extension = (Extension) registryObjects.getObject(extensionId, RegistryObjectManager.EXTENSION);
603 		registryObjects.removeExtensionFromNamespaceIndex(extensionId, extension.getNamespaceIdentifier());
604 		String xptName = extension.getExtensionPointIdentifier();
605 		ExtensionPoint extPoint = registryObjects.getExtensionPointObject(xptName);
606 		if (extPoint == null) {
607 			registryObjects.removeOrphan(xptName, extensionId);
608 			return null;
609 		}
610 		// otherwise, unlink the extension from the extension point
611 		int[] existingExtensions = extPoint.getRawChildren();
612 		int[] newExtensions = RegistryObjectManager.EMPTY_INT_ARRAY;
613 		if (existingExtensions.length > 1) {
614 			if (existingExtensions.length == 1)
615 				newExtensions = RegistryObjectManager.EMPTY_INT_ARRAY;
616 
617 			newExtensions = new int[existingExtensions.length - 1];
618 			for (int i = 0, j = 0; i < existingExtensions.length; i++)
619 				if (existingExtensions[i] != extension.getObjectId())
620 					newExtensions[j++] = existingExtensions[i];
621 		}
622 		link(extPoint, newExtensions);
623 		if (eventDelta != null)
624 			eventDelta.rememberExtension(extPoint, extensionId);
625 		return recordChange(extPoint, extension.getObjectId(), IExtensionDelta.REMOVED);
626 	}
627 
removeExtensionPoint(int extPoint)628 	private String removeExtensionPoint(int extPoint) {
629 		ExtensionPoint extensionPoint = (ExtensionPoint) registryObjects.getObject(extPoint, RegistryObjectManager.EXTENSION_POINT);
630 		registryObjects.removeExtensionPointFromNamespaceIndex(extPoint, extensionPoint.getNamespace());
631 		int[] existingExtensions = extensionPoint.getRawChildren();
632 		if (existingExtensions != null && existingExtensions.length != 0) {
633 			registryObjects.addOrphans(extensionPoint.getUniqueIdentifier(), existingExtensions);
634 			link(extensionPoint, RegistryObjectManager.EMPTY_INT_ARRAY);
635 		}
636 		if (eventDelta != null) {
637 			eventDelta.rememberExtensionPoint(extensionPoint);
638 			eventDelta.rememberExtensions(extensionPoint, existingExtensions);
639 		}
640 		return recordChange(extensionPoint, existingExtensions, IExtensionDelta.REMOVED);
641 	}
642 
removeExtensionsAndExtensionPoints(String contributorId)643 	private Set<String> removeExtensionsAndExtensionPoints(String contributorId) {
644 		Set<String> affectedNamespaces = new HashSet<>();
645 		for (int extension : registryObjects.getExtensionsFrom(contributorId)) {
646 			String namespace = this.removeExtension(extension);
647 			if (namespace != null)
648 				affectedNamespaces.add(namespace);
649 		}
650 
651 		// remove extension points
652 		for (int extPoint : registryObjects.getExtensionPointsFrom(contributorId)) {
653 			String namespace = this.removeExtensionPoint(extPoint);
654 			if (namespace != null)
655 				affectedNamespaces.add(namespace);
656 		}
657 		return affectedNamespaces;
658 	}
659 
660 	@Override
removeRegistryChangeListener(IRegistryChangeListener listener)661 	public void removeRegistryChangeListener(IRegistryChangeListener listener) {
662 		synchronized (listeners) {
663 			listeners.remove(new ListenerInfo(listener, null));
664 		}
665 	}
666 
667 	@Override
removeListener(IRegistryEventListener listener)668 	public void removeListener(IRegistryEventListener listener) {
669 		synchronized (listeners) {
670 			listeners.remove(new ListenerInfo(listener, null));
671 		}
672 	}
673 
ExtensionRegistry(RegistryStrategy registryStrategy, Object masterToken, Object userToken)674 	public ExtensionRegistry(RegistryStrategy registryStrategy, Object masterToken, Object userToken) {
675 		isMultiLanguage = "true".equals(RegistryProperties.getProperty(IRegistryConstants.PROP_MULTI_LANGUAGE)); //$NON-NLS-1$
676 
677 		if (registryStrategy != null)
678 			strategy = registryStrategy;
679 		else
680 			strategy = new RegistryStrategy(null, null);
681 
682 		this.masterToken = masterToken;
683 		this.userToken = userToken;
684 		registryObjects = new RegistryObjectManager(this);
685 
686 		boolean isRegistryFilledFromCache = false; // indicates if registry was able to use cache to populate it's content
687 
688 		if (strategy.cacheUse()) {
689 			// Try to read the registry from the cache first. If that fails, create a new registry
690 			long start = 0;
691 			if (debug())
692 				start = System.currentTimeMillis();
693 
694 			//The cache is made of several files, find the real names of these other files. If all files are found, try to initialize the objectManager
695 			if (checkCache()) {
696 				try {
697 					theTableReader.setTableFile(cacheStorageManager.lookup(TableReader.TABLE, false));
698 					theTableReader.setExtraDataFile(cacheStorageManager.lookup(TableReader.EXTRA, false));
699 					theTableReader.setMainDataFile(cacheStorageManager.lookup(TableReader.MAIN, false));
700 					theTableReader.setContributionsFile(cacheStorageManager.lookup(TableReader.CONTRIBUTIONS, false));
701 					theTableReader.setContributorsFile(cacheStorageManager.lookup(TableReader.CONTRIBUTORS, false));
702 					theTableReader.setNamespacesFile(cacheStorageManager.lookup(TableReader.NAMESPACES, false));
703 					theTableReader.setOrphansFile(cacheStorageManager.lookup(TableReader.ORPHANS, false));
704 					long timestamp = strategy.getContributionsTimestamp();
705 					isRegistryFilledFromCache = registryObjects.init(timestamp);
706 					if (isRegistryFilledFromCache)
707 						aggregatedTimestamp.set(timestamp);
708 				} catch (IOException e) {
709 					// The registry will be rebuilt from the xml files. Make sure to clear anything filled
710 					// from cache so that we won't have partially filled items.
711 					isRegistryFilledFromCache = false;
712 					clearRegistryCache();
713 					log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, RegistryMessages.registry_bad_cache, e));
714 				}
715 			}
716 
717 			if (!isRegistryFilledFromCache) {
718 				// set cache storage manager to a first writable location
719 				for (int index = 0; index < strategy.getLocationsLength(); index++) {
720 					if (!strategy.isCacheReadOnly(index)) {
721 						setFileManager(strategy.getStorage(index), false);
722 						break;
723 					}
724 				}
725 			}
726 
727 			if (debug() && isRegistryFilledFromCache)
728 				System.out.println("Reading registry cache: " + (System.currentTimeMillis() - start)); //$NON-NLS-1$
729 
730 			if (debug()) {
731 				if (!isRegistryFilledFromCache)
732 					System.out.println("Reloading registry from manifest files..."); //$NON-NLS-1$
733 				else
734 					System.out.println("Using registry cache..."); //$NON-NLS-1$
735 			}
736 		}
737 
738 		if (debugEvents())
739 			addRegistryChangeListener(System.out::println);
740 
741 		// Do extra start processing if specified in the registry strategy
742 		strategy.onStart(this); // preserve for backward compatibility; might be removed later
743 		strategy.onStart(this, isRegistryFilledFromCache);
744 	}
745 
746 	/**
747 	 * Stops the registry. Registry has to be stopped to properly
748 	 * close cache and dispose of listeners.
749 	 * @param key - key token for this registry
750 	 */
751 	@Override
stop(Object key)752 	public void stop(Object key) {
753 		// If the registry creator specified a key token, check that the key mathches it
754 		// (it is assumed that registry owner keeps the key to prevent unautorized accesss).
755 		if (masterToken != null && masterToken != key) {
756 			throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.stop() method. Check if proper access token is supplied."); //$NON-NLS-1$
757 		}
758 
759 		// Do extra stop processing if specified in the registry strategy
760 		strategy.onStop(this);
761 
762 		stopChangeEventScheduler();
763 
764 		if (cacheStorageManager == null)
765 			return;
766 
767 		if (!registryObjects.isDirty() || cacheStorageManager.isReadOnly()) {
768 			cacheStorageManager.close();
769 			theTableReader.close();
770 			return;
771 		}
772 
773 		File tableFile = null;
774 		File mainFile = null;
775 		File extraFile = null;
776 		File contributionsFile = null;
777 		File contributorsFile = null;
778 		File namespacesFile = null;
779 		File orphansFile = null;
780 
781 		TableWriter theTableWriter = new TableWriter(this);
782 
783 		try {
784 			cacheStorageManager.lookup(TableReader.TABLE, true);
785 			cacheStorageManager.lookup(TableReader.MAIN, true);
786 			cacheStorageManager.lookup(TableReader.EXTRA, true);
787 			cacheStorageManager.lookup(TableReader.CONTRIBUTIONS, true);
788 			cacheStorageManager.lookup(TableReader.CONTRIBUTORS, true);
789 			cacheStorageManager.lookup(TableReader.NAMESPACES, true);
790 			cacheStorageManager.lookup(TableReader.ORPHANS, true);
791 			tableFile = File.createTempFile(TableReader.TABLE, ".new", cacheStorageManager.getBase()); //$NON-NLS-1$
792 			mainFile = File.createTempFile(TableReader.MAIN, ".new", cacheStorageManager.getBase()); //$NON-NLS-1$
793 			extraFile = File.createTempFile(TableReader.EXTRA, ".new", cacheStorageManager.getBase()); //$NON-NLS-1$
794 			contributionsFile = File.createTempFile(TableReader.CONTRIBUTIONS, ".new", cacheStorageManager.getBase()); //$NON-NLS-1$
795 			contributorsFile = File.createTempFile(TableReader.CONTRIBUTORS, ".new", cacheStorageManager.getBase()); //$NON-NLS-1$
796 			namespacesFile = File.createTempFile(TableReader.NAMESPACES, ".new", cacheStorageManager.getBase()); //$NON-NLS-1$
797 			orphansFile = File.createTempFile(TableReader.ORPHANS, ".new", cacheStorageManager.getBase()); //$NON-NLS-1$
798 			theTableWriter.setTableFile(tableFile);
799 			theTableWriter.setExtraDataFile(extraFile);
800 			theTableWriter.setMainDataFile(mainFile);
801 			theTableWriter.setContributionsFile(contributionsFile);
802 			theTableWriter.setContributorsFile(contributorsFile);
803 			theTableWriter.setNamespacesFile(namespacesFile);
804 			theTableWriter.setOrphansFile(orphansFile);
805 		} catch (IOException e) {
806 			cacheStorageManager.close();
807 			return; //Ignore the exception since we can recompute the cache
808 		}
809 		try {
810 			long timestamp;
811 			// A bit of backward compatibility: if registry was modified, but timestamp was not,
812 			// it means that the new timestamp tracking mechanism was not used. In this case
813 			// explicitly obtain timestamps for all contributions. Note that this logic
814 			// maintains a problem described in the bug 104267 for contributions that
815 			// don't use the timestamp tracking mechanism.
816 			if (aggregatedTimestamp.isModifed())
817 				timestamp = aggregatedTimestamp.getContentsTimestamp(); // use timestamp tracking
818 			else
819 				timestamp = strategy.getContributionsTimestamp(); // use legacy approach
820 
821 			if (theTableWriter.saveCache(registryObjects, timestamp))
822 				cacheStorageManager.update(new String[] {TableReader.TABLE, TableReader.MAIN, TableReader.EXTRA, TableReader.CONTRIBUTIONS, TableReader.CONTRIBUTORS, TableReader.NAMESPACES, TableReader.ORPHANS}, new String[] {tableFile.getName(), mainFile.getName(), extraFile.getName(), contributionsFile.getName(), contributorsFile.getName(), namespacesFile.getName(), orphansFile.getName()});
823 		} catch (IOException e) {
824 			//Ignore the exception since we can recompute the cache
825 		}
826 		theTableReader.close();
827 		cacheStorageManager.close();
828 	}
829 
830 	/*
831 	 * Clear the registry cache files from the file manager so on next start-up we recompute it.
832 	 */
clearRegistryCache()833 	public void clearRegistryCache() {
834 		for (String key : new String[] {TableReader.TABLE, TableReader.MAIN, TableReader.EXTRA, TableReader.CONTRIBUTIONS, TableReader.ORPHANS})
835 			try {
836 				cacheStorageManager.remove(key);
837 			} catch (IOException e) {
838 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IStatus.ERROR, RegistryMessages.meta_registryCacheReadProblems, e));
839 			}
840 		aggregatedTimestamp.reset();
841 	}
842 
843 	/////////////////////////////////////////////////////////////////////////////////////////////////
844 	// Registry Object Factory
845 	// The factory produces contributions, extension points, extensions, and configuration elements
846 	// to be stored in the extension registry.
847 	protected RegistryObjectFactory theRegistryObjectFactory = null;
848 
849 	// Override to provide domain-specific elements to be stored in the extension registry
setElementFactory()850 	protected void setElementFactory() {
851 		if (isMultiLanguage)
852 			theRegistryObjectFactory = new RegistryObjectFactoryMulti(this);
853 		else
854 			theRegistryObjectFactory = new RegistryObjectFactory(this);
855 	}
856 
857 	// Lazy initialization.
getElementFactory()858 	public RegistryObjectFactory getElementFactory() {
859 		if (theRegistryObjectFactory == null)
860 			setElementFactory();
861 		return theRegistryObjectFactory;
862 	}
863 
getTableReader()864 	TableReader getTableReader() {
865 		return theTableReader;
866 	}
867 
log(IStatus status)868 	public void log(IStatus status) {
869 		strategy.log(status);
870 	}
871 
872 	/**
873 	 * With multi-locale support enabled this method returns the non-translated
874 	 * key so that they can be cached and translated later into desired languages.
875 	 * In the absence of the multi-locale support the key gets translated immediately
876 	 * and only translated values is cached.
877 	 */
translate(String key, ResourceBundle resources)878 	public String translate(String key, ResourceBundle resources) {
879 		if (isMultiLanguage)
880 			return key;
881 		return strategy.translate(key, resources);
882 	}
883 
debug()884 	public boolean debug() {
885 		return strategy.debug();
886 	}
887 
debugEvents()888 	public boolean debugEvents() {
889 		return strategy.debugRegistryEvents();
890 	}
891 
useLazyCacheLoading()892 	public boolean useLazyCacheLoading() {
893 		return strategy.cacheLazyLoading();
894 	}
895 
computeState()896 	public long computeState() {
897 		return strategy.getContainerTimestamp();
898 	}
899 
900 	// Find the first location that contains a cache table file and set file manager to it.
checkCache()901 	protected boolean checkCache() {
902 		for (int index = 0; index < strategy.getLocationsLength(); index++) {
903 			File possibleCacheLocation = strategy.getStorage(index);
904 			if (possibleCacheLocation == null)
905 				break; // bail out on the first null
906 			setFileManager(possibleCacheLocation, strategy.isCacheReadOnly(index));
907 			if (cacheStorageManager != null) {
908 				// check this new location:
909 				File cacheFile = null;
910 				try {
911 					cacheFile = cacheStorageManager.lookup(TableReader.getTestFileName(), false);
912 				} catch (IOException e) {
913 					//Ignore the exception. The registry will be rebuilt from the xml files.
914 				}
915 				if (cacheFile != null && cacheFile.isFile())
916 					return true; // found the appropriate location
917 			}
918 		}
919 		return false;
920 	}
921 
createExecutableExtension(RegistryContributor defaultContributor, String className, String requestedContributorName)922 	public Object createExecutableExtension(RegistryContributor defaultContributor, String className, String requestedContributorName) throws CoreException {
923 		return strategy.createExecutableExtension(defaultContributor, className, requestedContributorName);
924 	}
925 
926 	//////////////////////////////////////////////////////////////////////////////////////////
927 	// Registry change events processing
928 
processChangeEvent(Object[] listenerInfos, final Map<String, ?> scheduledDeltas)929 	public IStatus processChangeEvent(Object[] listenerInfos, final Map<String, ?> scheduledDeltas) {
930 		// Separate new event delta from the pack
931 		final CombinedEventDelta extendedDelta = (CombinedEventDelta) scheduledDeltas.remove(notNamespace);
932 
933 		final MultiStatus result = new MultiStatus(RegistryMessages.OWNER_NAME, IStatus.OK, RegistryMessages.plugin_eventListenerError, null);
934 		for (Object info : listenerInfos) {
935 			final ListenerInfo listenerInfo = (ListenerInfo) info;
936 			if ((listenerInfo.listener instanceof IRegistryChangeListener) && scheduledDeltas.size() != 0) {
937 				if (listenerInfo.filter == null || scheduledDeltas.containsKey(listenerInfo.filter)) {
938 					SafeRunner.run(new ISafeRunnable() {
939 						@Override
940 						public void run() throws Exception {
941 							((IRegistryChangeListener) listenerInfo.listener).registryChanged(new RegistryChangeEvent(scheduledDeltas, listenerInfo.filter));
942 						}
943 
944 						@Override
945 						public void handleException(Throwable exception) {
946 							result.add(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, RegistryMessages.plugin_eventListenerError, exception));
947 						}
948 					});
949 				}
950 			}
951 			if (listenerInfo.listener instanceof IRegistryEventListener) {
952 				IRegistryEventListener extensionListener = (IRegistryEventListener) listenerInfo.listener;
953 				IExtension[] extensions = extendedDelta.getExtensions(listenerInfo.filter);
954 				IExtensionPoint[] extensionPoints = extendedDelta.getExtensionPoints(listenerInfo.filter);
955 
956 				// notification order - on addition: extension points; then extensions
957 				if (extendedDelta.isAddition()) {
958 					if (extensionPoints != null)
959 						extensionListener.added(extensionPoints);
960 					if (extensions != null)
961 						extensionListener.added(extensions);
962 				} else { // on removal: extensions; then extension points
963 					if (extensions != null)
964 						extensionListener.removed(extensions);
965 					if (extensionPoints != null)
966 						extensionListener.removed(extensionPoints);
967 				}
968 			}
969 		}
970 		for (Object delta : scheduledDeltas.values()) {
971 			((RegistryDelta) delta).getObjectManager().close();
972 		}
973 		IObjectManager manager = extendedDelta.getObjectManager();
974 		if (manager != null)
975 			manager.close();
976 		return result;
977 	}
978 
979 	private RegistryEventThread eventThread = null; // registry event loop
980 	protected final List<QueueElement> queue = new LinkedList<>(); // stores registry events info
981 
982 	// Registry events notifications are done on a separate thread in a sequential manner
983 	// (first in - first processed)
scheduleChangeEvent(Object[] listenerInfos, Map<String, ?> scheduledDeltas)984 	public void scheduleChangeEvent(Object[] listenerInfos, Map<String, ?> scheduledDeltas) {
985 		QueueElement newElement = new QueueElement(listenerInfos, scheduledDeltas);
986 		if (eventThread == null) {
987 			eventThread = new RegistryEventThread(this);
988 			eventThread.start();
989 		}
990 		synchronized (queue) {
991 			queue.add(newElement);
992 			queue.notify();
993 		}
994 	}
995 
996 	// The pair of values we store in the event queue
997 	private class QueueElement {
998 		Object[] listenerInfos;
999 		Map<String, ?> scheduledDeltas;
1000 
QueueElement(Object[] infos, Map<String, ?> deltas)1001 		QueueElement(Object[] infos, Map<String, ?> deltas) {
1002 			this.scheduledDeltas = deltas;
1003 			listenerInfos = infos;
1004 		}
1005 	}
1006 
1007 	private class RegistryEventThread extends Thread {
1008 		private final ExtensionRegistry registry;
1009 
RegistryEventThread(ExtensionRegistry registry)1010 		public RegistryEventThread(ExtensionRegistry registry) {
1011 			super("Extension Registry Event Dispatcher"); //$NON-NLS-1$
1012 			setDaemon(true);
1013 			this.registry = registry;
1014 		}
1015 
1016 		@Override
run()1017 		public void run() {
1018 			while (true) {
1019 				QueueElement element;
1020 				synchronized (queue) {
1021 					try {
1022 						while (queue.isEmpty())
1023 							queue.wait();
1024 					} catch (InterruptedException e) {
1025 						return;
1026 					}
1027 					element = queue.remove(0);
1028 				}
1029 				registry.processChangeEvent(element.listenerInfos, element.scheduledDeltas);
1030 			}
1031 		}
1032 	}
1033 
stopChangeEventScheduler()1034 	protected void stopChangeEventScheduler() {
1035 		if (eventThread != null) {
1036 			synchronized (queue) {
1037 				eventThread.interrupt();
1038 				eventThread = null;
1039 			}
1040 		}
1041 	}
1042 
1043 	/**
1044 	 * Access check for add/remove operations:
1045 	 * - Master key allows all operations
1046 	 * - User key allows modifications of non-persisted elements
1047 	 *
1048 	 * @param key key to the registry supplied by the user
1049 	 * @param persist true if operation affects persisted elements
1050 	 * @return true is the key grants read/write access to the registry
1051 	 */
checkReadWriteAccess(Object key, boolean persist)1052 	private boolean checkReadWriteAccess(Object key, boolean persist) {
1053 		if (masterToken == key)
1054 			return true;
1055 		if (userToken == key && !persist)
1056 			return true;
1057 		return false;
1058 	}
1059 
addContribution(InputStream is, IContributor contributor, boolean persist, String contributionName, ResourceBundle translationBundle, Object key, long timestamp)1060 	public boolean addContribution(InputStream is, IContributor contributor, boolean persist, String contributionName, ResourceBundle translationBundle, Object key, long timestamp) {
1061 		boolean result = addContribution(is, contributor, persist, contributionName, translationBundle, key);
1062 		if (timestamp != 0)
1063 			aggregatedTimestamp.add(timestamp);
1064 		return result;
1065 	}
1066 
1067 	@Override
addContribution(InputStream is, IContributor contributor, boolean persist, String contributionName, ResourceBundle translationBundle, Object key)1068 	public boolean addContribution(InputStream is, IContributor contributor, boolean persist, String contributionName, ResourceBundle translationBundle, Object key) {
1069 		if (!checkReadWriteAccess(key, persist))
1070 			throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.addContribution() method. Check if proper access token is supplied."); //$NON-NLS-1$
1071 		if (contributionName == null)
1072 			contributionName = ""; //$NON-NLS-1$
1073 
1074 		RegistryContributor internalContributor = (RegistryContributor) contributor;
1075 		registryObjects.addContributor(internalContributor); // only adds a contributor if it is not already present
1076 
1077 		String ownerName = internalContributor.getActualName();
1078 		String message = NLS.bind(RegistryMessages.parse_problems, ownerName);
1079 		MultiStatus problems = new MultiStatus(RegistryMessages.OWNER_NAME, ExtensionsParser.PARSE_PROBLEM, message, null);
1080 		ExtensionsParser parser = new ExtensionsParser(problems, this);
1081 		Contribution contribution = getElementFactory().createContribution(internalContributor.getActualId(), persist);
1082 
1083 		try {
1084 			parser.parseManifest(strategy.getXMLParser(), new InputSource(is), contributionName, getObjectManager(), contribution, translationBundle);
1085 			int status = problems.getSeverity();
1086 			if (status != IStatus.OK) {
1087 				log(problems);
1088 				if (status == IStatus.ERROR || status == IStatus.CANCEL)
1089 					return false;
1090 			}
1091 		} catch (ParserConfigurationException | SAXException | IOException e) {
1092 			logError(ownerName, contributionName, e);
1093 			return false;
1094 		} finally {
1095 			try {
1096 				is.close();
1097 			} catch (IOException ioe) {
1098 				// nothing to do
1099 			}
1100 		}
1101 		add(contribution); // the add() method does synchronization
1102 		return true;
1103 	}
1104 
logError(String owner, String contributionName, Exception e)1105 	private void logError(String owner, String contributionName, Exception e) {
1106 		String message = NLS.bind(RegistryMessages.parse_failedParsingManifest, owner + "/" + contributionName); //$NON-NLS-1$
1107 		log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, message, e));
1108 	}
1109 
1110 	/**
1111 	 * Adds an extension point to the extension registry.
1112 	 * <p>
1113 	 * If the registry is not modifiable, this method is an access controlled method.
1114 	 * Proper token should be passed as an argument for non-modifiable registries.
1115 	 * </p>
1116 	 * @param identifier Id of the extension point. If non-qualified names is supplied,
1117 	 * it will be converted internally into a fully qualified name
1118 	 * @param contributor the contributor of this extension point
1119 	 * @param persist indicates if contribution should be stored in the registry cache. If false,
1120 	 * contribution is not persisted in the registry cache and is lost on Eclipse restart
1121 	 * @param label display string for the extension point
1122 	 * @param schemaReference reference to the extension point schema. The schema reference
1123 	 * is a URL path relative to the plug-in installation URL. May be null
1124 	 * @param token the key used to check permissions. Two registry keys are set in the registry
1125 	 * constructor {@link RegistryFactory#createRegistry(org.eclipse.core.runtime.spi.RegistryStrategy, Object, Object)}:
1126 	 * master token and a user token. Master token allows all operations; user token
1127 	 * allows non-persisted registry elements to be modified.
1128 	 * @return <code>true</code> if successful, <code>false</code> if a problem was encountered
1129 	 * @throws IllegalArgumentException if incorrect token is passed in
1130 	 */
addExtensionPoint(String identifier, IContributor contributor, boolean persist, String label, String schemaReference, Object token)1131 	public boolean addExtensionPoint(String identifier, IContributor contributor, boolean persist, String label, String schemaReference, Object token) throws IllegalArgumentException {
1132 		if (!checkReadWriteAccess(token, persist))
1133 			throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.addExtensionPoint() method. Check if proper access token is supplied."); //$NON-NLS-1$
1134 
1135 		RegistryContributor internalContributor = (RegistryContributor) contributor;
1136 		registryObjects.addContributor(internalContributor); // only adds a contributor if it is not already present
1137 		String contributorId = internalContributor.getActualId();
1138 
1139 		// Extension point Id might not be null
1140 		if (identifier == null) {
1141 			String message = NLS.bind(RegistryMessages.create_failedExtensionPoint, label);
1142 			log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, message, null));
1143 		}
1144 		if (schemaReference == null)
1145 			schemaReference = ""; //$NON-NLS-1$
1146 
1147 		// addition wraps in a contribution
1148 		Contribution contribution = getElementFactory().createContribution(contributorId, persist);
1149 		ExtensionPoint currentExtPoint = getElementFactory().createExtensionPoint(persist);
1150 
1151 		String uniqueId;
1152 		String namespaceName;
1153 		int simpleIdStart = identifier.lastIndexOf('.');
1154 		if (simpleIdStart == -1) {
1155 			namespaceName = contribution.getDefaultNamespace();
1156 			uniqueId = namespaceName + '.' + identifier;
1157 		} else {
1158 			namespaceName = identifier.substring(0, simpleIdStart);
1159 			uniqueId = identifier;
1160 		}
1161 		currentExtPoint.setUniqueIdentifier(uniqueId);
1162 		currentExtPoint.setNamespace(namespaceName);
1163 		String labelNLS = translate(label, null);
1164 		currentExtPoint.setLabel(labelNLS);
1165 		currentExtPoint.setSchema(schemaReference);
1166 
1167 		if (!getObjectManager().addExtensionPoint(currentExtPoint, true)) {
1168 			if (debug()) {
1169 				String msg = NLS.bind(RegistryMessages.parse_duplicateExtensionPoint, uniqueId, contribution.getDefaultNamespace());
1170 				log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, msg, null));
1171 			}
1172 			return false;
1173 		}
1174 
1175 		currentExtPoint.setContributorId(contributorId);
1176 
1177 		// array format: {Number of extension points, Number of extensions, Extension Id}
1178 		int[] contributionChildren = new int[3];
1179 		// Put the extension points into this namespace
1180 		contributionChildren[Contribution.EXTENSION_POINT] = 1;
1181 		contributionChildren[Contribution.EXTENSION] = 0;
1182 		contributionChildren[Contribution.EXTENSION + 1] = currentExtPoint.getObjectId();
1183 
1184 		contribution.setRawChildren(contributionChildren);
1185 
1186 		add(contribution);
1187 		return true;
1188 	}
1189 
1190 	/**
1191 	 * Adds an extension to the extension registry.
1192 	 * <p>
1193 	 * If the registry is not modifiable, this method is an access controlled method.
1194 	 * Proper token should be passed as an argument for non-modifiable registries.
1195 	 * </p>
1196 	 * @see org.eclipse.core.internal.registry.spi.ConfigurationElementDescription
1197 	 *
1198 	 * @param identifier Id of the extension. If non-qualified name is supplied,
1199 	 * it will be converted internally into a fully qualified name
1200 	 * @param contributor the contributor of this extension
1201 	 * @param persist indicates if contribution should be stored in the registry cache. If false,
1202 	 * contribution is not persisted in the registry cache and is lost on Eclipse restart
1203 	 * @param label display string for this extension
1204 	 * @param extensionPointId Id of the point being extended. If non-qualified
1205 	 * name is supplied, it is assumed to have the same contributorId as this extension
1206 	 * @param configurationElements contents of the extension
1207 	 * @param token the key used to check permissions. Two registry keys are set in the registry
1208 	 * constructor {@link RegistryFactory#createRegistry(org.eclipse.core.runtime.spi.RegistryStrategy, Object, Object)}:
1209 	 * master token and a user token. Master token allows all operations; user token
1210 	 * allows non-persisted registry elements to be modified.
1211 	 * @return <code>true</code> if successful, <code>false</code> if a problem was encountered
1212 	 * @throws IllegalArgumentException if incorrect token is passed in
1213 	 */
addExtension(String identifier, IContributor contributor, boolean persist, String label, String extensionPointId, ConfigurationElementDescription configurationElements, Object token)1214 	public boolean addExtension(String identifier, IContributor contributor, boolean persist, String label, String extensionPointId, ConfigurationElementDescription configurationElements, Object token) throws IllegalArgumentException {
1215 		if (!checkReadWriteAccess(token, persist))
1216 			throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.addExtensionPoint() method. Check if proper access token is supplied."); //$NON-NLS-1$
1217 		// prepare namespace information
1218 		RegistryContributor internalContributor = (RegistryContributor) contributor;
1219 		registryObjects.addContributor(internalContributor); // only adds a contributor if it is not already present
1220 		String contributorId = internalContributor.getActualId();
1221 
1222 		// addition wraps in a contribution
1223 		Contribution contribution = getElementFactory().createContribution(contributorId, persist);
1224 		Extension currentExtension = getElementFactory().createExtension(persist);
1225 
1226 		String simpleId;
1227 		String namespaceName;
1228 		int simpleIdStart = identifier.lastIndexOf('.');
1229 		if (simpleIdStart != -1) {
1230 			simpleId = identifier.substring(simpleIdStart + 1);
1231 			namespaceName = identifier.substring(0, simpleIdStart);
1232 		} else {
1233 			simpleId = identifier;
1234 			namespaceName = contribution.getDefaultNamespace();
1235 		}
1236 		currentExtension.setSimpleIdentifier(simpleId);
1237 		currentExtension.setNamespaceIdentifier(namespaceName);
1238 
1239 		String extensionLabelNLS = translate(label, null);
1240 		currentExtension.setLabel(extensionLabelNLS);
1241 
1242 		String targetExtensionPointId;
1243 		if (extensionPointId.indexOf('.') == -1) // No dots -> namespace name added at the start
1244 			targetExtensionPointId = contribution.getDefaultNamespace() + '.' + extensionPointId;
1245 		else
1246 			targetExtensionPointId = extensionPointId;
1247 		currentExtension.setExtensionPointIdentifier(targetExtensionPointId);
1248 
1249 		// if we have an Id specified, check for duplicates. Only issue warning if duplicate found
1250 		// as it might still work fine - depending on the access pattern.
1251 		if (simpleId != null && debug()) {
1252 			String uniqueId = namespaceName + '.' + simpleId;
1253 			IExtension existingExtension = getExtension(uniqueId);
1254 			if (existingExtension != null) {
1255 				String currentSupplier = contribution.getDefaultNamespace();
1256 				String existingSupplier = existingExtension.getContributor().getName();
1257 				String msg = NLS.bind(RegistryMessages.parse_duplicateExtension, new String[] {currentSupplier, existingSupplier, uniqueId});
1258 				log(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, 0, msg, null));
1259 				return false;
1260 			}
1261 		}
1262 
1263 		getObjectManager().add(currentExtension, true);
1264 
1265 		createExtensionData(contributorId, configurationElements, currentExtension, persist);
1266 
1267 		currentExtension.setContributorId(contributorId);
1268 
1269 		int[] contributionChildren = new int[3];
1270 
1271 		contributionChildren[Contribution.EXTENSION_POINT] = 0;
1272 		contributionChildren[Contribution.EXTENSION] = 1;
1273 		contributionChildren[Contribution.EXTENSION + 1] = currentExtension.getObjectId();
1274 		contribution.setRawChildren(contributionChildren);
1275 
1276 		add(contribution);
1277 		return true;
1278 	}
1279 
1280 	// Fill in the actual content of this extension
createExtensionData(String contributorId, ConfigurationElementDescription description, RegistryObject parent, boolean persist)1281 	private void createExtensionData(String contributorId, ConfigurationElementDescription description, RegistryObject parent, boolean persist) {
1282 		ConfigurationElement currentConfigurationElement = getElementFactory().createConfigurationElement(persist);
1283 		currentConfigurationElement.setContributorId(contributorId);
1284 		currentConfigurationElement.setName(description.getName());
1285 
1286 		ConfigurationElementAttribute[] descriptionProperties = description.getAttributes();
1287 
1288 		if (descriptionProperties != null && descriptionProperties.length != 0) {
1289 			int len = descriptionProperties.length;
1290 			String[] properties = new String[len * 2];
1291 			for (int i = 0; i < len; i++) {
1292 				properties[i * 2] = descriptionProperties[i].getName();
1293 				properties[i * 2 + 1] = translate(descriptionProperties[i].getValue(), null);
1294 			}
1295 			currentConfigurationElement.setProperties(properties);
1296 		} else
1297 			currentConfigurationElement.setProperties(RegistryObjectManager.EMPTY_STRING_ARRAY);
1298 
1299 		String value = description.getValue();
1300 		if (value != null)
1301 			currentConfigurationElement.setValue(value);
1302 
1303 		getObjectManager().add(currentConfigurationElement, true);
1304 
1305 		// process children
1306 		ConfigurationElementDescription[] children = description.getChildren();
1307 		if (children != null) {
1308 			for (ConfigurationElementDescription element : children) {
1309 				createExtensionData(contributorId, element, currentConfigurationElement, persist);
1310 			}
1311 		}
1312 
1313 		int[] oldValues = parent.getRawChildren();
1314 		int size = oldValues.length;
1315 		int[] newValues = new int[size + 1];
1316 		for (int i = 0; i < size; i++) {
1317 			newValues[i] = oldValues[i];
1318 		}
1319 		newValues[size] = currentConfigurationElement.getObjectId();
1320 		parent.setRawChildren(newValues);
1321 		currentConfigurationElement.setParentId(parent.getObjectId());
1322 		currentConfigurationElement.setParentType(parent instanceof ConfigurationElement ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.EXTENSION);
1323 	}
1324 
1325 	@Override
removeExtension(IExtension extension, Object token)1326 	public boolean removeExtension(IExtension extension, Object token) throws IllegalArgumentException {
1327 		if (!(extension instanceof ExtensionHandle))
1328 			return false;
1329 		return removeObject(((ExtensionHandle) extension).getObject(), false, token);
1330 	}
1331 
1332 	@Override
removeExtensionPoint(IExtensionPoint extensionPoint, Object token)1333 	public boolean removeExtensionPoint(IExtensionPoint extensionPoint, Object token) throws IllegalArgumentException {
1334 		if (!(extensionPoint instanceof ExtensionPointHandle))
1335 			return false;
1336 		return removeObject(((ExtensionPointHandle) extensionPoint).getObject(), true, token);
1337 	}
1338 
removeObject(RegistryObject registryObject, boolean isExtensionPoint, Object token)1339 	private boolean removeObject(RegistryObject registryObject, boolean isExtensionPoint, Object token) {
1340 		if (!checkReadWriteAccess(token, registryObject.shouldPersist()))
1341 			throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.removeExtension() method. Check if proper access token is supplied."); //$NON-NLS-1$
1342 		int id = registryObject.getObjectId();
1343 
1344 		access.enterWrite();
1345 		try {
1346 			eventDelta = CombinedEventDelta.recordRemoval();
1347 			String namespace;
1348 			if (isExtensionPoint)
1349 				namespace = removeExtensionPoint(id);
1350 			else
1351 				namespace = removeExtension(id);
1352 			Map<Integer, RegistryObject> removed = new HashMap<>(1);
1353 			removed.put(Integer.valueOf(id), registryObject);
1354 			// There is some asymmetry between extension and extension point removal. Removing extension point makes
1355 			// extensions "orphans" but does not remove them. As a result, only extensions needs to be processed.
1356 			if (!isExtensionPoint)
1357 				registryObjects.addAssociatedObjects(removed, registryObject);
1358 			registryObjects.removeObjects(removed);
1359 			registryObjects.addNavigableObjects(removed);
1360 			IObjectManager manager = registryObjects.createDelegatingObjectManager(removed);
1361 			getDelta(namespace).setObjectManager(manager);
1362 			eventDelta.setObjectManager(manager);
1363 
1364 			registryObjects.unlinkChildFromContributions(id);
1365 			fireRegistryChangeEvent();
1366 			eventDelta = null;
1367 		} finally {
1368 			access.exitWrite();
1369 		}
1370 		return true;
1371 	}
1372 
1373 	@Override
getAllContributors()1374 	public IContributor[] getAllContributors() {
1375 		access.enterRead();
1376 		try {
1377 			return registryObjects.getContributorsSync();
1378 		} finally {
1379 			access.exitRead();
1380 		}
1381 	}
1382 
1383 	/**
1384 	 * <strong>EXPERIMENTAL</strong>. This method has been added as part of a work in progress.
1385 	 * There is a guarantee neither that this API will work nor that it will remain the same.
1386 	 * Please do not use this method without consulting with the Equinox team.
1387 	 */
getTemporaryUserToken()1388 	public Object getTemporaryUserToken() {
1389 		return userToken;
1390 	}
1391 
1392 	@Override
isMultiLanguage()1393 	public boolean isMultiLanguage() {
1394 		return isMultiLanguage;
1395 	}
1396 
translate(String[] nonTranslated, IContributor contributor, String locale)1397 	public String[] translate(String[] nonTranslated, IContributor contributor, String locale) {
1398 		return strategy.translate(nonTranslated, contributor, locale);
1399 	}
1400 
getLocale()1401 	public String getLocale() {
1402 		return strategy.getLocale();
1403 	}
1404 
logMultiLangError()1405 	public void logMultiLangError() {
1406 		if (mlErrorLogged) // only log this error ones
1407 			return;
1408 		log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, RegistryMessages.registry_non_multi_lang, new IllegalArgumentException()));
1409 		mlErrorLogged = true;
1410 	}
1411 }
1412