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