1 /*******************************************************************************
2  * Copyright (c) 2004, 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  *******************************************************************************/
14 package org.eclipse.core.internal.registry;
15 
16 import java.lang.ref.SoftReference;
17 import java.util.*;
18 import org.eclipse.core.runtime.IContributor;
19 import org.eclipse.core.runtime.InvalidRegistryObjectException;
20 import org.eclipse.core.runtime.spi.RegistryContributor;
21 
22 /**
23  * This class manage all the object from the registry but does not deal with their dependencies.
24  * It serves the objects which are either directly obtained from memory or read from a cache.
25  * It also returns handles for objects.
26  */
27 public class RegistryObjectManager implements IObjectManager {
28 	//Constants used to get the objects and their handles
29 	static public final byte CONFIGURATION_ELEMENT = 1;
30 	static public final byte EXTENSION = 2;
31 	static public final byte EXTENSION_POINT = 3;
32 	static public final byte THIRDLEVEL_CONFIGURATION_ELEMENT = 4;
33 
34 	static final int CACHE_INITIAL_SIZE = 512; //This value has been picked because it is the minimal size required to startup an RCP app. (FYI, eclipse requires 3 growths).
35 	static final float DEFAULT_LOADFACTOR = 0.75f; //This is the default factor used in reference map.
36 
37 	static final int[] EMPTY_INT_ARRAY = new int[0];
38 	static final String[] EMPTY_STRING_ARRAY = new String[0];
39 
40 	static final ExtensionHandle[] EMPTY_EXTENSIONS_ARRAY = new ExtensionHandle[0];
41 
42 	static int UNKNOWN = -1;
43 
44 	// key: extensionPointName, value: object id
45 	private HashtableOfStringAndInt extensionPoints; //This is loaded on startup. Then entries can be added when loading a new plugin from the xml.
46 	// key: object id, value: an object
47 	private ReferenceMap cache; //Entries are added by getter. The structure is not thread safe.
48 	//key: int, value: int
49 	private OffsetTable fileOffsets = null; //This is read once on startup when loading from the cache. Entries are never added here. They are only removed to prevent "removed" objects to be reloaded.
50 
51 	private int nextId = 1; //This is only used to get the next number available.
52 
53 	//Those two data structures are only used when the addition or the removal of a plugin occurs.
54 	//They are used to keep track on a contributor basis of the extension being added or removed
55 	private final KeyedHashSet newContributions; //represents the contributers added during this session.
56 	private Object formerContributions; //represents the contributers encountered in previous sessions. This is loaded lazily.
57 
58 	private HashMap<String, RegistryContributor> contributors; // key: contributor ID; value: contributor name
59 	private HashMap<String, RegistryContributor> removedContributors; // key: contributor ID; value: contributor name
60 	private KeyedHashSet namespacesIndex; // registry elements (extension & extensionpoints) indexed by namespaces
61 
62 	// Map key: extensionPointFullyQualifiedName, value int[] of orphan extensions.
63 	// The orphan access does not need to be synchronized because the it is protected by the lock in extension registry.
64 	private Object orphanExtensions;
65 
66 	private final KeyedHashSet heldObjects = new KeyedHashSet(); //strong reference to the objects that must be hold on to
67 
68 	//Indicate if objects have been removed or added from the table. This only needs to be set in a couple of places (addNamespace and removeNamespace)
69 	private boolean isDirty = false;
70 
71 	private boolean fromCache = false;
72 
73 	private final ExtensionRegistry registry;
74 
75 	// TODO this option is not used
76 	// OSGI system properties.  Copied from EclipseStarter
77 	public static final String PROP_NO_REGISTRY_FLUSHING = "eclipse.noRegistryFlushing"; //$NON-NLS-1$
78 
RegistryObjectManager(ExtensionRegistry registry)79 	public RegistryObjectManager(ExtensionRegistry registry) {
80 		extensionPoints = new HashtableOfStringAndInt();
81 		if ("true".equalsIgnoreCase(RegistryProperties.getProperty(PROP_NO_REGISTRY_FLUSHING))) { //$NON-NLS-1$
82 			cache = new ReferenceMap(ReferenceMap.HARD, CACHE_INITIAL_SIZE, DEFAULT_LOADFACTOR);
83 		} else {
84 			cache = new ReferenceMap(ReferenceMap.SOFT, CACHE_INITIAL_SIZE, DEFAULT_LOADFACTOR);
85 		}
86 		newContributions = new KeyedHashSet();
87 
88 		this.registry = registry;
89 	}
90 
91 	/**
92 	 * Initialize the object manager. Return true if the initialization succeeded, false otherwise
93 	 */
init(long timeStamp)94 	synchronized boolean init(long timeStamp) {
95 		TableReader reader = registry.getTableReader();
96 		Object[] results = reader.loadTables(timeStamp);
97 		if (results == null) {
98 			return false;
99 		}
100 		fileOffsets = (OffsetTable) results[0];
101 		extensionPoints = (HashtableOfStringAndInt) results[1];
102 		nextId = ((Integer) results[2]).intValue();
103 		fromCache = true;
104 
105 		if (!registry.useLazyCacheLoading()) {
106 			//TODO Here we could grow all the tables to the right size (ReferenceMap)
107 			reader.setHoldObjects(true);
108 			markOrphansHasDirty(getOrphans());
109 			fromCache = reader.readAllCache(this);
110 			formerContributions = getFormerContributions();
111 		}
112 		return fromCache;
113 	}
114 
addContribution(Contribution contribution)115 	synchronized void addContribution(Contribution contribution) {
116 		isDirty = true;
117 		Object Id = contribution.getKey();
118 
119 		KeyedElement existingContribution = getFormerContributions().getByKey(Id);
120 		if (existingContribution != null) { // move it from former to new contributions
121 			removeContribution(Id);
122 			newContributions.add(existingContribution);
123 		} else
124 			existingContribution = newContributions.getByKey(Id);
125 
126 		if (existingContribution != null) // merge
127 			((Contribution) existingContribution).mergeContribution(contribution);
128 		else
129 			newContributions.add(contribution);
130 
131 		updateNamespaceIndex(contribution, true);
132 	}
133 
134 	// TODO make ExtensionPoint, Extension provide namespace in a same way (move it to the RegistryObject?)
135 	// See if all the registryObjects have the same namespace. If not, return null.
136 	// Also can return null if empty array is passed in or objects are of an unexpected type
findCommonNamespaceIdentifier(RegistryObject[] registryObjects)137 	private String findCommonNamespaceIdentifier(RegistryObject[] registryObjects) {
138 		String namespaceName = null;
139 		for (RegistryObject currentObject : registryObjects) {
140 			String tmp = null;
141 			if (currentObject instanceof ExtensionPoint)
142 				tmp = ((ExtensionPoint) currentObject).getNamespace();
143 			else if (currentObject instanceof Extension)
144 				tmp = ((Extension) currentObject).getNamespaceIdentifier();
145 
146 			if (namespaceName == null) {
147 				namespaceName = tmp;
148 				continue;
149 			}
150 			if (!namespaceName.equals(tmp)) {
151 				return null;
152 			}
153 		}
154 		return namespaceName;
155 	}
156 
removeExtensionPointFromNamespaceIndex(int extensionPoint, String namespaceName)157 	synchronized void removeExtensionPointFromNamespaceIndex(int extensionPoint, String namespaceName) {
158 		RegistryIndexElement indexElement = getNamespaceIndex(namespaceName);
159 		indexElement.updateExtensionPoint(extensionPoint, false);
160 	}
161 
removeExtensionFromNamespaceIndex(int extensions, String namespaceName)162 	synchronized void removeExtensionFromNamespaceIndex(int extensions, String namespaceName) {
163 		RegistryIndexElement indexElement = getNamespaceIndex(namespaceName);
164 		indexElement.updateExtension(extensions, false);
165 	}
166 
167 	// Called from a synchronized method
updateNamespaceIndex(Contribution contribution, boolean added)168 	private void updateNamespaceIndex(Contribution contribution, boolean added) {
169 		// if all extension points are from the same namespace combine them in one block and add them all together
170 		int[] contribExtensionPoints = contribution.getExtensionPoints();
171 		RegistryObject[] extensionPointObjects = getObjects(contribExtensionPoints, EXTENSION_POINT);
172 		String commonExptsNamespace = null;
173 		if (contribExtensionPoints.length > 1)
174 			commonExptsNamespace = findCommonNamespaceIdentifier(extensionPointObjects);
175 		if (commonExptsNamespace != null) {
176 			RegistryIndexElement indexElement = getNamespaceIndex(commonExptsNamespace);
177 			indexElement.updateExtensionPoints(contribExtensionPoints, added);
178 		} else {
179 			for (int i = 0; i < contribExtensionPoints.length; i++) {
180 				String namespaceName = ((ExtensionPoint) extensionPointObjects[i]).getNamespace();
181 				RegistryIndexElement indexElement = getNamespaceIndex(namespaceName);
182 				indexElement.updateExtensionPoint(contribExtensionPoints[i], added);
183 			}
184 		}
185 
186 		// if all extensions are from the same namespace combine them in one block and add them all together
187 		int[] contrExtensions = contribution.getExtensions();
188 		RegistryObject[] extensionObjects = getObjects(contrExtensions, EXTENSION);
189 		String commonExtNamespace = null;
190 		if (contrExtensions.length > 1)
191 			commonExtNamespace = findCommonNamespaceIdentifier(extensionObjects);
192 		if (commonExtNamespace != null) {
193 			RegistryIndexElement indexElement = getNamespaceIndex(commonExtNamespace);
194 			indexElement.updateExtensions(contrExtensions, added);
195 		} else {
196 			for (int i = 0; i < contrExtensions.length; i++) {
197 				String namespaceName = ((Extension) extensionObjects[i]).getNamespaceIdentifier();
198 				RegistryIndexElement indexElement = getNamespaceIndex(namespaceName);
199 				indexElement.updateExtension(contrExtensions[i], added);
200 			}
201 		}
202 	}
203 
getExtensionPointsFrom(String id)204 	synchronized int[] getExtensionPointsFrom(String id) {
205 		KeyedElement tmp = newContributions.getByKey(id);
206 		if (tmp == null)
207 			tmp = getFormerContributions().getByKey(id);
208 		if (tmp == null)
209 			return EMPTY_INT_ARRAY;
210 		return ((Contribution) tmp).getExtensionPoints();
211 	}
212 
hasContribution(String id)213 	synchronized boolean hasContribution(String id) {
214 		Object result = newContributions.getByKey(id);
215 		if (result == null)
216 			result = getFormerContributions().getByKey(id);
217 		return result != null;
218 	}
219 
getFormerContributions()220 	private KeyedHashSet getFormerContributions() {
221 		KeyedHashSet result;
222 		if (fromCache == false)
223 			return new KeyedHashSet(0);
224 
225 		if (formerContributions == null || (result = ((KeyedHashSet) ((formerContributions instanceof SoftReference) ? ((SoftReference<?>) formerContributions).get() : formerContributions))) == null) {
226 			result = registry.getTableReader().loadContributions();
227 			formerContributions = new SoftReference<>(result);
228 		}
229 		return result;
230 	}
231 
add(RegistryObject registryObject, boolean hold)232 	synchronized public void add(RegistryObject registryObject, boolean hold) {
233 		if (registryObject.getObjectId() == UNKNOWN) {
234 			int id = nextId++;
235 			registryObject.setObjectId(id);
236 		}
237 		cache.put(registryObject.getObjectId(), registryObject);
238 		if (hold)
239 			hold(registryObject);
240 	}
241 
remove(RegistryObject registryObject, boolean release)242 	private void remove(RegistryObject registryObject, boolean release) {
243 		cache.remove(registryObject.getObjectId());
244 		if (release)
245 			release(registryObject);
246 	}
247 
remove(int id, boolean release)248 	synchronized void remove(int id, boolean release) {
249 		RegistryObject toRemove = (RegistryObject) cache.get(id);
250 		if (fileOffsets != null)
251 			fileOffsets.removeKey(id);
252 		if (toRemove != null)
253 			remove(toRemove, release);
254 	}
255 
hold(RegistryObject toHold)256 	private void hold(RegistryObject toHold) {
257 		heldObjects.add(toHold);
258 	}
259 
release(RegistryObject toRelease)260 	private void release(RegistryObject toRelease) {
261 		heldObjects.remove(toRelease);
262 	}
263 
264 	@Override
getObject(int id, byte type)265 	public synchronized Object getObject(int id, byte type) {
266 		return basicGetObject(id, type);
267 	}
268 
basicGetObject(int id, byte type)269 	private Object basicGetObject(int id, byte type) {
270 		Object result = cache.get(id);
271 		if (result != null)
272 			return result;
273 		if (fromCache)
274 			result = load(id, type);
275 		if (result == null)
276 			throw new InvalidRegistryObjectException();
277 		cache.put(id, result);
278 		return result;
279 	}
280 
281 	// The current impementation of this method assumes that we don't cache dynamic
282 	// extension. In this case all extensions not yet loaded (i.e. not in the memory cache)
283 	// are "not dynamic" and we actually check memory objects to see if they are dynamic.
284 	//
285 	// If we decide to allow caching of dynamic objects, the implementation
286 	// of this method would have to retrieved the object from disk and check
287 	// its "dynamic" status. The problem is that id alone is not enough to get the object
288 	// from the disk; object type is needed as well.
shouldPersist(int id)289 	public boolean shouldPersist(int id) {
290 		Object result = cache.get(id);
291 		if (result != null)
292 			return ((RegistryObject) result).shouldPersist();
293 		return true;
294 	}
295 
296 	@Override
getObjects(int[] values, byte type)297 	public synchronized RegistryObject[] getObjects(int[] values, byte type) {
298 		if (values.length == 0) {
299 			switch (type) {
300 				case EXTENSION_POINT :
301 					return ExtensionPoint.EMPTY_ARRAY;
302 				case EXTENSION :
303 					return Extension.EMPTY_ARRAY;
304 				case CONFIGURATION_ELEMENT :
305 				case THIRDLEVEL_CONFIGURATION_ELEMENT :
306 					return ConfigurationElement.EMPTY_ARRAY;
307 			}
308 		}
309 
310 		RegistryObject[] results = null;
311 		switch (type) {
312 			case EXTENSION_POINT :
313 				results = new ExtensionPoint[values.length];
314 				break;
315 			case EXTENSION :
316 				results = new Extension[values.length];
317 				break;
318 			case CONFIGURATION_ELEMENT :
319 			case THIRDLEVEL_CONFIGURATION_ELEMENT :
320 				results = new ConfigurationElement[values.length];
321 				break;
322 		}
323 		for (int i = 0; i < values.length; i++) {
324 			results[i] = (RegistryObject) basicGetObject(values[i], type);
325 		}
326 		return results;
327 	}
328 
getExtensionPointObject(String xptUniqueId)329 	synchronized ExtensionPoint getExtensionPointObject(String xptUniqueId) {
330 		int id;
331 		if ((id = extensionPoints.get(xptUniqueId)) == HashtableOfStringAndInt.MISSING_ELEMENT)
332 			return null;
333 		return (ExtensionPoint) getObject(id, EXTENSION_POINT);
334 	}
335 
336 	@Override
getHandle(int id, byte type)337 	public Handle getHandle(int id, byte type) {
338 		switch (type) {
339 			case EXTENSION_POINT :
340 				return new ExtensionPointHandle(this, id);
341 
342 			case EXTENSION :
343 				return new ExtensionHandle(this, id);
344 
345 			case CONFIGURATION_ELEMENT :
346 				return new ConfigurationElementHandle(this, id);
347 
348 			case THIRDLEVEL_CONFIGURATION_ELEMENT :
349 			default : //avoid compiler error, type should always be known
350 				return new ThirdLevelConfigurationElementHandle(this, id);
351 		}
352 	}
353 
354 	@Override
getHandles(int[] ids, byte type)355 	public Handle[] getHandles(int[] ids, byte type) {
356 		Handle[] results = null;
357 		int nbrId = ids.length;
358 		switch (type) {
359 			case EXTENSION_POINT :
360 				if (nbrId == 0)
361 					return ExtensionPointHandle.EMPTY_ARRAY;
362 				results = new ExtensionPointHandle[nbrId];
363 				for (int i = 0; i < nbrId; i++) {
364 					results[i] = new ExtensionPointHandle(this, ids[i]);
365 				}
366 				break;
367 
368 			case EXTENSION :
369 				if (nbrId == 0)
370 					return ExtensionHandle.EMPTY_ARRAY;
371 				results = new ExtensionHandle[nbrId];
372 				for (int i = 0; i < nbrId; i++) {
373 					results[i] = new ExtensionHandle(this, ids[i]);
374 				}
375 				break;
376 
377 			case CONFIGURATION_ELEMENT :
378 				if (nbrId == 0)
379 					return ConfigurationElementHandle.EMPTY_ARRAY;
380 				results = new ConfigurationElementHandle[nbrId];
381 				for (int i = 0; i < nbrId; i++) {
382 					results[i] = new ConfigurationElementHandle(this, ids[i]);
383 				}
384 				break;
385 
386 			case THIRDLEVEL_CONFIGURATION_ELEMENT :
387 				if (nbrId == 0)
388 					return ConfigurationElementHandle.EMPTY_ARRAY;
389 				results = new ThirdLevelConfigurationElementHandle[nbrId];
390 				for (int i = 0; i < nbrId; i++) {
391 					results[i] = new ThirdLevelConfigurationElementHandle(this, ids[i]);
392 				}
393 				break;
394 		}
395 		return results;
396 	}
397 
getExtensionPointsHandles()398 	synchronized ExtensionPointHandle[] getExtensionPointsHandles() {
399 		return (ExtensionPointHandle[]) getHandles(extensionPoints.getValues(), EXTENSION_POINT);
400 	}
401 
getExtensionPointHandle(String xptUniqueId)402 	synchronized ExtensionPointHandle getExtensionPointHandle(String xptUniqueId) {
403 		int id = extensionPoints.get(xptUniqueId);
404 		if (id == HashtableOfStringAndInt.MISSING_ELEMENT)
405 			return null;
406 		return (ExtensionPointHandle) getHandle(id, EXTENSION_POINT);
407 	}
408 
load(int id, byte type)409 	private Object load(int id, byte type) {
410 		TableReader reader = registry.getTableReader();
411 		if (fileOffsets == null)
412 			return null;
413 		int offset = fileOffsets.get(id);
414 		if (offset == Integer.MIN_VALUE)
415 			return null;
416 		switch (type) {
417 			case CONFIGURATION_ELEMENT :
418 				return reader.loadConfigurationElement(offset);
419 
420 			case THIRDLEVEL_CONFIGURATION_ELEMENT :
421 				return reader.loadThirdLevelConfigurationElements(offset, this);
422 
423 			case EXTENSION :
424 				return reader.loadExtension(offset);
425 
426 			case EXTENSION_POINT :
427 			default : //avoid compile errors. type must always be known
428 				return reader.loadExtensionPointTree(offset, this);
429 		}
430 	}
431 
getExtensionsFrom(String contributorId)432 	synchronized int[] getExtensionsFrom(String contributorId) {
433 		KeyedElement tmp = newContributions.getByKey(contributorId);
434 		if (tmp == null)
435 			tmp = getFormerContributions().getByKey(contributorId);
436 		if (tmp == null)
437 			return EMPTY_INT_ARRAY;
438 		return ((Contribution) tmp).getExtensions();
439 	}
440 
addExtensionPoint(ExtensionPoint currentExtPoint, boolean hold)441 	synchronized boolean addExtensionPoint(ExtensionPoint currentExtPoint, boolean hold) {
442 		String uniqueId = currentExtPoint.getUniqueIdentifier();
443 		if (extensionPoints.get(uniqueId) != HashtableOfStringAndInt.MISSING_ELEMENT)
444 			return false;
445 		add(currentExtPoint, hold);
446 		extensionPoints.put(uniqueId, currentExtPoint.getObjectId());
447 		return true;
448 	}
449 
removeExtensionPoint(String extensionPointId)450 	synchronized void removeExtensionPoint(String extensionPointId) {
451 		int pointId = extensionPoints.removeKey(extensionPointId);
452 		if (pointId == HashtableOfStringAndInt.MISSING_ELEMENT)
453 			return;
454 		remove(pointId, true);
455 	}
456 
isDirty()457 	public boolean isDirty() {
458 		return isDirty;
459 	}
460 
markDirty()461 	public void markDirty() {
462 		isDirty = true;
463 	}
464 
removeContribution(Object contributorId)465 	synchronized void removeContribution(Object contributorId) {
466 		boolean removed = newContributions.removeByKey(contributorId);
467 		if (removed == false) {
468 			removed = getFormerContributions().removeByKey(contributorId);
469 			if (removed)
470 				formerContributions = getFormerContributions(); //This forces the removed namespace to stay around, so we do not forget about removed namespaces
471 		}
472 
473 		if (removed) {
474 			isDirty = true;
475 			return;
476 		}
477 
478 	}
479 
480 	@SuppressWarnings("unchecked")
getOrphans()481 	private Map<String, int[]> getOrphans() {
482 		Object result;
483 		if (orphanExtensions == null && !fromCache) {
484 			result = new HashMap<>();
485 			orphanExtensions = result;
486 		} else if (orphanExtensions == null || (result = ((orphanExtensions instanceof SoftReference) ? ((SoftReference<?>) orphanExtensions).get() : orphanExtensions)) == null) {
487 			result = registry.getTableReader().loadOrphans();
488 			orphanExtensions = new SoftReference<>(result);
489 		}
490 		return (HashMap<String, int[]>) result;
491 	}
492 
addOrphans(String extensionPoint, int[] extensions)493 	void addOrphans(String extensionPoint, int[] extensions) {
494 		Map<String, int[]> orphans = getOrphans();
495 		int[] existingOrphanExtensions = orphans.get(extensionPoint);
496 
497 		if (existingOrphanExtensions != null) {
498 			// just add
499 			int[] newOrphanExtensions = new int[existingOrphanExtensions.length + extensions.length];
500 			System.arraycopy(existingOrphanExtensions, 0, newOrphanExtensions, 0, existingOrphanExtensions.length);
501 			System.arraycopy(extensions, 0, newOrphanExtensions, existingOrphanExtensions.length, extensions.length);
502 			orphans.put(extensionPoint, newOrphanExtensions);
503 		} else {
504 			// otherwise this is the first one
505 			orphans.put(extensionPoint, extensions);
506 		}
507 		markOrphansHasDirty(orphans);
508 	}
509 
markOrphansHasDirty(Map<String, int[]> orphans)510 	void markOrphansHasDirty(Map<String, int[]> orphans) {
511 		orphanExtensions = orphans;
512 	}
513 
addOrphan(String extensionPoint, int extension)514 	void addOrphan(String extensionPoint, int extension) {
515 		Map<String, int[]> orphans = getOrphans();
516 		int[] existingOrphanExtensions = orphans.get(extensionPoint);
517 
518 		if (existingOrphanExtensions != null) {
519 			// just add
520 			int[] newOrphanExtensions = new int[existingOrphanExtensions.length + 1];
521 			System.arraycopy(existingOrphanExtensions, 0, newOrphanExtensions, 0, existingOrphanExtensions.length);
522 			newOrphanExtensions[existingOrphanExtensions.length] = extension;
523 			orphans.put(extensionPoint, newOrphanExtensions);
524 		} else {
525 			// otherwise this is the first one
526 			orphans.put(extensionPoint, new int[] {extension});
527 		}
528 		markOrphansHasDirty(orphans);
529 	}
530 
removeOrphans(String extensionPoint)531 	int[] removeOrphans(String extensionPoint) {
532 		Map<String, int[]> orphans = getOrphans();
533 		int[] existingOrphanExtensions = orphans.remove(extensionPoint);
534 		if (existingOrphanExtensions != null) {
535 			markOrphansHasDirty(orphans);
536 		}
537 		return existingOrphanExtensions;
538 	}
539 
removeOrphan(String extensionPoint, int extension)540 	void removeOrphan(String extensionPoint, int extension) {
541 		Map<String, int[]> orphans = getOrphans();
542 		int[] existingOrphanExtensions = orphans.get(extensionPoint);
543 
544 		if (existingOrphanExtensions == null)
545 			return;
546 
547 		markOrphansHasDirty(orphans);
548 		int newSize = existingOrphanExtensions.length - 1;
549 		if (newSize == 0) {
550 			orphans.remove(extensionPoint);
551 			return;
552 		}
553 
554 		int[] newOrphanExtensions = new int[existingOrphanExtensions.length - 1];
555 		for (int i = 0, j = 0; i < existingOrphanExtensions.length; i++)
556 			if (extension != existingOrphanExtensions[i])
557 				newOrphanExtensions[j++] = existingOrphanExtensions[i];
558 
559 		orphans.put(extensionPoint, newOrphanExtensions);
560 		return;
561 	}
562 
563 	//This method is only used by the writer to reach in
getOrphanExtensions()564 	Map<String, int[]> getOrphanExtensions() {
565 		return getOrphans();
566 	}
567 
568 	//	This method is only used by the writer to reach in
getNextId()569 	int getNextId() {
570 		return nextId;
571 	}
572 
573 	//	This method is only used by the writer to reach in
getExtensionPoints()574 	HashtableOfStringAndInt getExtensionPoints() {
575 		return extensionPoints;
576 	}
577 
578 	//	This method is only used by the writer to reach in
getContributions()579 	KeyedHashSet[] getContributions() {
580 		return new KeyedHashSet[] {newContributions, getFormerContributions()};
581 	}
582 
583 	// This method is used internally and by the writer to reach in. Notice that it doesn't
584 	// return contributors marked as removed.
getContributors()585 	HashMap<String, RegistryContributor> getContributors() {
586 		if (contributors == null) {
587 			if (fromCache == false)
588 				contributors = new HashMap<>();
589 			else
590 				contributors = registry.getTableReader().loadContributors();
591 		}
592 		return contributors;
593 	}
594 
getContributorsSync()595 	synchronized IContributor[] getContributorsSync() {
596 		Collection<RegistryContributor> contributorValues = getContributors().values();
597 		return contributorValues.toArray(new IContributor[contributorValues.size()]);
598 	}
599 
getContributor(String id)600 	synchronized RegistryContributor getContributor(String id) {
601 		RegistryContributor contributor = getContributors().get(id);
602 		if (contributor != null)
603 			return contributor;
604 		// check if we have it among removed contributors - potentially
605 		// notification of removals might be processed after the contributor
606 		// marked as removed:
607 		if (removedContributors != null)
608 			return removedContributors.get(id);
609 		return null;
610 	}
611 
612 	// only adds a contributor if it is not already present in the table
addContributor(RegistryContributor newContributor)613 	synchronized void addContributor(RegistryContributor newContributor) {
614 		String key = newContributor.getActualId();
615 		if (!getContributors().containsKey(key)) {
616 			isDirty = true;
617 			if (removedContributors != null)
618 				removedContributors.remove(key);
619 			getContributors().put(key, newContributor);
620 		}
621 	}
622 
removeContributor(String id)623 	synchronized void removeContributor(String id) {
624 		isDirty = true;
625 		RegistryContributor removed = getContributors().remove(id);
626 		if (removed != null) {
627 			if (removedContributors == null)
628 				removedContributors = new HashMap<>();
629 			removedContributors.put(id, removed);
630 		}
631 	}
632 
getNamespacesIndex()633 	KeyedHashSet getNamespacesIndex() {
634 		if (namespacesIndex == null) {
635 			if (fromCache == false)
636 				namespacesIndex = new KeyedHashSet(0);
637 			else
638 				namespacesIndex = registry.getTableReader().loadNamespaces();
639 		}
640 		return namespacesIndex;
641 	}
642 
643 	// Find or create required index element
getNamespaceIndex(String namespaceName)644 	private RegistryIndexElement getNamespaceIndex(String namespaceName) {
645 		RegistryIndexElement indexElement = (RegistryIndexElement) getNamespacesIndex().getByKey(namespaceName);
646 		if (indexElement == null) {
647 			indexElement = new RegistryIndexElement(namespaceName);
648 			namespacesIndex.add(indexElement);
649 		}
650 		return indexElement;
651 	}
652 
653 	/**
654 	 * Collect all the objects that are removed by this operation and store
655 	 * them in a IObjectManager so that they can be accessed from the appropriate
656 	 * deltas but not from the registry.
657 	 */
getAssociatedObjects(String contributionId)658 	synchronized Map<Integer, RegistryObject> getAssociatedObjects(String contributionId) {
659 		//Collect all the objects associated with this contribution
660 		int[] xpts = getExtensionPointsFrom(contributionId);
661 		int[] exts = getExtensionsFrom(contributionId);
662 		Map<Integer, RegistryObject> actualObjects = new HashMap<>(xpts.length + exts.length);
663 		for (int ext : exts) {
664 			Extension tmp = (Extension) basicGetObject(ext, RegistryObjectManager.EXTENSION);
665 			actualObjects.put(Integer.valueOf(ext), tmp);
666 			collectChildren(tmp, 0, actualObjects);
667 		}
668 		for (int xpt2 : xpts) {
669 			ExtensionPoint xpt = (ExtensionPoint) basicGetObject(xpt2, RegistryObjectManager.EXTENSION_POINT);
670 			actualObjects.put(Integer.valueOf(xpt2), xpt);
671 		}
672 
673 		return actualObjects;
674 	}
675 
676 	/**
677 	 * Adds elements to be removed along with the registry object.
678 	 */
addAssociatedObjects(Map<Integer, RegistryObject> map, RegistryObject registryObject)679 	synchronized void addAssociatedObjects(Map<Integer, RegistryObject> map, RegistryObject registryObject) {
680 		collectChildren(registryObject, 0, map);
681 	}
682 
683 	/**
684 	 * Add to the set of the objects all extensions and extension points that
685 	 * could be navigated to from the objects in the set.
686 	 */
addNavigableObjects(Map<Integer, RegistryObject> associatedObjects)687 	synchronized void addNavigableObjects(Map<Integer, RegistryObject> associatedObjects) {
688 		Map<Integer, RegistryObject> result = new HashMap<>();
689 		for (RegistryObject object : associatedObjects.values()) {
690 			if (object instanceof Extension) {
691 				// add extension point
692 				ExtensionPoint extPoint = getExtensionPointObject(((Extension) object).getExtensionPointIdentifier());
693 				if (extPoint == null) // already removed?
694 					continue;
695 
696 				Integer extPointIndex = Integer.valueOf(extPoint.getKeyHashCode());
697 				if (!associatedObjects.containsKey(extPointIndex))
698 					result.put(Integer.valueOf(extPoint.getKeyHashCode()), extPoint);
699 
700 				// add all extensions for the extension point
701 				for (int childId : extPoint.getRawChildren()) {
702 					Extension tmp = (Extension) basicGetObject(childId, RegistryObjectManager.EXTENSION);
703 					if (tmp == null) // already removed
704 						continue;
705 					Integer extensionIndex = Integer.valueOf(childId);
706 					if (!associatedObjects.containsKey(extensionIndex)) {
707 						result.put(extensionIndex, tmp);
708 						collectChildren(tmp, 0, result);
709 					}
710 				}
711 			} else if (object instanceof ExtensionPoint) {
712 				// by now extensions of this extension point have been marked as orphans
713 				Map<String, int[]> orphans = getOrphans();
714 				String name = ((ExtensionPoint) object).getUniqueIdentifier();
715 				int[] extensions = orphans.get(name);
716 				if (extensions != null) {
717 					for (int orphanId : extensions) {
718 						Extension tmp = (Extension) basicGetObject(orphanId, RegistryObjectManager.EXTENSION);
719 						if (tmp == null) // already removed
720 							continue;
721 						Integer extensionIndex = Integer.valueOf(orphanId);
722 						if (!associatedObjects.containsKey(extensionIndex)) {
723 							result.put(extensionIndex, tmp);
724 							collectChildren(tmp, 0, result);
725 						}
726 					}
727 				}
728 			}
729 		}
730 		associatedObjects.putAll(result);
731 	}
732 
removeObjects(Map<?, ?> associatedObjects)733 	synchronized void removeObjects(Map<?, ?> associatedObjects) {
734 		//Remove the objects from the main object manager so they can no longer be accessed.
735 		for (Object registryObject : associatedObjects.values()) {
736 			RegistryObject toRemove = (RegistryObject) registryObject;
737 			remove((toRemove).getObjectId(), true);
738 			if (toRemove instanceof ExtensionPoint)
739 				removeExtensionPoint(((ExtensionPoint) toRemove).getUniqueIdentifier());
740 		}
741 	}
742 
createDelegatingObjectManager(Map<?, ?> object)743 	IObjectManager createDelegatingObjectManager(Map<?, ?> object) {
744 		return new TemporaryObjectManager(object, this);
745 	}
746 
collectChildren(RegistryObject ce, int level, Map<Integer, RegistryObject> collector)747 	private void collectChildren(RegistryObject ce, int level, Map<Integer, RegistryObject> collector) {
748 		ConfigurationElement[] children = (ConfigurationElement[]) getObjects(ce.getRawChildren(), level == 0 || ce.noExtraData() ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
749 		for (ConfigurationElement child : children) {
750 			collector.put(Integer.valueOf(child.getObjectId()), child);
751 			collectChildren(child, level + 1, collector);
752 		}
753 	}
754 
755 	@Override
close()756 	public void close() {
757 		//do nothing.
758 	}
759 
getRegistry()760 	public ExtensionRegistry getRegistry() {
761 		return registry;
762 	}
763 
764 	// Called from a synchronized method only
unlinkChildFromContributions(KeyedElement[] contributions, int id)765 	private boolean unlinkChildFromContributions(KeyedElement[] contributions, int id) {
766 		for (KeyedElement contribution : contributions) {
767 			Contribution candidate = (Contribution) contribution;
768 			if (candidate == null)
769 				continue;
770 			if (candidate.hasChild(id)) {
771 				candidate.unlinkChild(id);
772 				if (candidate.isEmpty())
773 					removeContribution(candidate.getContributorId());
774 				return true;
775 			}
776 		}
777 		return false;
778 	}
779 
unlinkChildFromContributions(int id)780 	synchronized boolean unlinkChildFromContributions(int id) {
781 		if (unlinkChildFromContributions(newContributions.elements, id))
782 			return true;
783 		return unlinkChildFromContributions(getFormerContributions().elements, id);
784 	}
785 
getExtensionPointsFromNamespace(String namespaceName)786 	synchronized public ExtensionPointHandle[] getExtensionPointsFromNamespace(String namespaceName) {
787 		RegistryIndexElement indexElement = getNamespaceIndex(namespaceName);
788 		int[] namespaceExtensionPoints = indexElement.getExtensionPoints();
789 		return (ExtensionPointHandle[]) getHandles(namespaceExtensionPoints, EXTENSION_POINT);
790 	}
791 
792 	// This method filters out extensions with no extension point
getExtensionsFromNamespace(String namespaceName)793 	synchronized public ExtensionHandle[] getExtensionsFromNamespace(String namespaceName) {
794 		RegistryIndexElement indexElement = getNamespaceIndex(namespaceName);
795 		int[] namespaceExtensions = indexElement.getExtensions();
796 
797 		// filter extensions with no extension point (orphan extensions)
798 		List<Handle> tmp = new ArrayList<>();
799 		Extension[] exts = (Extension[]) getObjects(namespaceExtensions, EXTENSION);
800 		for (Extension ext : exts) {
801 			if (getExtensionPointObject(ext.getExtensionPointIdentifier()) != null) {
802 				tmp.add(getHandle(ext.getObjectId(), EXTENSION));
803 			}
804 		}
805 		if (tmp.size() == 0)
806 			return EMPTY_EXTENSIONS_ARRAY;
807 		ExtensionHandle[] result = new ExtensionHandle[tmp.size()];
808 		return tmp.toArray(result);
809 	}
810 
getExtensionsFromContributor(String contributorId)811 	public ExtensionHandle[] getExtensionsFromContributor(String contributorId) {
812 		int[] ids = getExtensionsFrom(contributorId); // never null
813 		return (ExtensionHandle[]) getHandles(ids, RegistryObjectManager.EXTENSION);
814 	}
815 
getExtensionPointsFromContributor(String contributorId)816 	public ExtensionPointHandle[] getExtensionPointsFromContributor(String contributorId) {
817 		int[] ids = getExtensionPointsFrom(contributorId); // never null
818 		return (ExtensionPointHandle[]) getHandles(ids, RegistryObjectManager.EXTENSION_POINT);
819 	}
820 }
821