001: /*******************************************************************************
002: * Copyright (c) 2002, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.cheatsheets.registry;
011:
012: import com.ibm.icu.text.Collator;
013: import java.util.ArrayList;
014: import java.util.HashMap;
015: import java.util.Iterator;
016: import java.util.Map;
017: import java.util.Set;
018: import java.util.StringTokenizer;
019:
020: import org.eclipse.core.runtime.*;
021: import org.eclipse.ui.internal.cheatsheets.*;
022: import org.eclipse.ui.model.AdaptableList;
023:
024: /**
025: * Instances access the registry that is provided at creation time
026: * in order to determine the contained CheatSheet Contents
027: */
028: public class CheatSheetRegistryReader extends RegistryReader implements
029: IRegistryChangeListener {
030:
031: private class CategoryNode {
032: private Category category;
033: private String path;
034:
035: public CategoryNode(Category cat) {
036: category = cat;
037: path = ICheatSheetResource.EMPTY_STRING;
038: String[] categoryPath = category.getParentPath();
039: if (categoryPath != null) {
040: for (int nX = 0; nX < categoryPath.length; nX++) {
041: path += categoryPath[nX] + '/';
042: }
043: }
044: path += cat.getId();
045: }
046:
047: public Category getCategory() {
048: return category;
049: }
050:
051: public String getPath() {
052: return path;
053: }
054: }
055:
056: /**
057: * Represents a taskEditor entry in the registry
058: */
059: public class TaskEditorNode {
060: private String className;
061: private String iconPath;
062: private String id;
063: private String pluginId;
064:
065: public void setClassName(String className) {
066: this .className = className;
067: }
068:
069: public String getClassName() {
070: return className;
071: }
072:
073: public void setIconPath(String iconPath) {
074: this .iconPath = iconPath;
075: }
076:
077: public String getIconPath() {
078: return iconPath;
079: }
080:
081: public void setId(String id) {
082: this .id = id;
083: }
084:
085: public String getId() {
086: return id;
087: }
088:
089: public void setPluginId(String pluginId) {
090: this .pluginId = pluginId;
091: }
092:
093: public String getPluginId() {
094: return pluginId;
095: }
096: }
097:
098: /**
099: * Represents a taskExplorer entry in the registry
100: */
101: public class TaskExplorerNode {
102: private String className;
103: private String iconPath;
104: private String name;
105: private String id;
106: private String pluginId;
107:
108: public void setClassName(String className) {
109: this .className = className;
110: }
111:
112: public String getClassName() {
113: return className;
114: }
115:
116: public void setIconPath(String iconPath) {
117: this .iconPath = iconPath;
118: }
119:
120: public String getIconPath() {
121: return iconPath;
122: }
123:
124: public void setName(String name) {
125: this .name = name;
126: }
127:
128: public String getName() {
129: return name;
130: }
131:
132: public void setId(String id) {
133: this .id = id;
134: }
135:
136: public String getId() {
137: return id;
138: }
139:
140: public void setPluginId(String pluginId) {
141: this .pluginId = pluginId;
142: }
143:
144: public String getPluginId() {
145: return pluginId;
146: }
147: }
148:
149: // constants
150: private final static String ATT_CATEGORY = "category"; //$NON-NLS-1$
151: public final static String ATT_CONTENTFILE = "contentFile"; //$NON-NLS-1$
152: protected final static String ATT_ICON = "icon"; //$NON-NLS-1$
153: protected final static String ATT_ID = "id"; //$NON-NLS-1$
154: protected final static String ATT_LISTENERCLASS = "listener"; //$NON-NLS-1$
155: protected final static String ATT_NAME = "name"; //$NON-NLS-1$
156: protected final static String ATT_CLASS = "class"; //$NON-NLS-1$
157: private final static String ATT_COMPOSITE = "composite"; //$NON-NLS-1$
158: private final static String CATEGORY_SEPARATOR = "/"; //$NON-NLS-1$
159: private final static String ATT_ITEM_ATTRIBUTE = "itemAttribute"; //$NON-NLS-1$
160: private static CheatSheetRegistryReader instance;
161: private final static String TAG_CATEGORY = "category"; //$NON-NLS-1$
162: public final static String TAG_CHEATSHEET = "cheatsheet"; //$NON-NLS-1$
163: protected final static String TAG_ITEM_EXTENSION = "itemExtension"; //$NON-NLS-1$
164: protected final static String TAG_TASK_EDITOR = "taskEditor"; //$NON-NLS-1$
165: protected final static String TAG_TASK_EXPLORER = "taskExplorer"; //$NON-NLS-1$
166: protected final static String trueString = "TRUE"; //$NON-NLS-1$
167: private final static String UNCATEGORIZED_CHEATSHEET_CATEGORY = "org.eclipse.ui.Other"; //$NON-NLS-1$
168: private final static String UNCATEGORIZED_CHEATSHEET_CATEGORY_LABEL = Messages.CHEAT_SHEET_OTHER_CATEGORY;
169: public final static String CHEAT_SHEET_CONTENT = "cheatSheetContent"; //$NON-NLS-1$
170:
171: /**
172: * Returns a list of cheatsheets, project and not.
173: *
174: * The return value for this method is cached since computing its value
175: * requires non-trivial work.
176: */
177: public static CheatSheetRegistryReader getInstance() {
178: if (instance == null) {
179: instance = new CheatSheetRegistryReader();
180: IExtensionRegistry xregistry = Platform
181: .getExtensionRegistry();
182: xregistry.addRegistryChangeListener(instance,
183: ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID);
184: }
185:
186: return instance;
187: }
188:
189: protected ArrayList cheatsheetItemExtensions;
190: protected AdaptableList cheatsheets;
191: private ArrayList deferCategories = null;
192: private ArrayList deferCheatSheets = null;
193: private final String csItemExtension = "cheatSheetItemExtension"; //$NON-NLS-1$
194: protected Map taskExplorers = new HashMap();
195: protected Map taskEditors = new HashMap();
196:
197: /**
198: * Create an instance of this class.
199: */
200: private CheatSheetRegistryReader() {
201: }
202:
203: /**
204: * Adds new cheatsheet to the provided collection. Override to
205: * provide more logic.
206: * <p>
207: * This implementation uses a defering strategy. For more info see
208: * <code>readCheatSheets</code>.
209: * </p>
210: */
211: protected void addNewElementToResult(CheatSheetElement element,
212: IConfigurationElement config, AdaptableList result) {
213: deferCheatSheet(element);
214: }
215:
216: /**
217: * Returns a new CheatSheetElement configured according to the parameters
218: * contained in the passed Registry.
219: *
220: * May answer null if there was not enough information in the Extension to create
221: * an adequate cheatsheet
222: */
223: protected CheatSheetElement createCheatSheetElement(
224: IConfigurationElement element) {
225: // CheatSheetElements must have a name attribute
226: String nameString = element.getAttribute(ATT_NAME);
227: if (nameString == null) {
228: logMissingAttribute(element, ATT_NAME);
229: return null;
230: }
231: CheatSheetElement result = new CheatSheetElement(nameString);
232: if (initializeCheatSheet(result, element))
233: return result; // ie.- initialization was successful
234:
235: return null;
236: }
237:
238: /**
239: * Create and answer a new CheatSheetCollectionElement, configured as a
240: * child of <code>parent</code>
241: *
242: * @return org.eclipse.ui.internal.model.CheatSheetCollectionElement
243: * @param parent org.eclipse.ui.internal.model.CheatSheetCollectionElement
244: * @param childName java.lang.String
245: */
246: protected CheatSheetCollectionElement createCollectionElement(
247: CheatSheetCollectionElement parent, String pluginId,
248: String id, String label) {
249: CheatSheetCollectionElement newElement = new CheatSheetCollectionElement(
250: pluginId, id, label, parent);
251:
252: parent.add(newElement);
253: return newElement;
254: }
255:
256: /**
257: * Creates empty element collection. Overrider to fill
258: * initial elements, if needed.
259: */
260: protected AdaptableList createEmptyCheatSheetCollection() {
261: return new CheatSheetCollectionElement(null,
262: "root", "root", null); //$NON-NLS-1$//$NON-NLS-2$
263: }
264:
265: /**
266: * Stores a category element for deferred addition.
267: */
268: private void deferCategory(IConfigurationElement config) {
269: // Create category.
270: Category category = null;
271: try {
272: category = new Category(config);
273: } catch (CoreException e) {
274: CheatSheetPlugin.getPlugin().getLog().log(e.getStatus());
275: return;
276: }
277:
278: // Defer for later processing.
279: if (deferCategories == null)
280: deferCategories = new ArrayList(20);
281: deferCategories.add(category);
282: }
283:
284: /**
285: * Stores a cheatsheet element for deferred addition.
286: */
287: private void deferCheatSheet(CheatSheetElement element) {
288: if (deferCheatSheets == null)
289: deferCheatSheets = new ArrayList(50);
290: deferCheatSheets.add(element);
291: }
292:
293: /**
294: * Returns the first cheatsheet
295: * with a given id.
296: */
297: public CheatSheetElement findCheatSheet(String id) {
298: Object[] cheatsheetsList = getCheatSheets().getChildren();
299: for (int nX = 0; nX < cheatsheetsList.length; nX++) {
300: CheatSheetCollectionElement collection = (CheatSheetCollectionElement) cheatsheetsList[nX];
301: CheatSheetElement element = collection.findCheatSheet(id,
302: true);
303: if (element != null)
304: return element;
305: }
306: return null;
307: }
308:
309: /**
310: * Returns the first task editor
311: * with a given id.
312: */
313: public TaskEditorNode findTaskEditor(String id) {
314: if (cheatsheets == null) {
315: readCheatSheets(); // Ensure that the registry has been read
316: }
317: return (TaskEditorNode) taskEditors.get(id);
318: }
319:
320: /**
321: * Returns the first task explorer
322: * with a given id.
323: */
324: public TaskExplorerNode findTaskExplorer(String id) {
325: if (cheatsheets == null) {
326: readCheatSheets(); // Ensure that the registry has been read
327: }
328: return (TaskExplorerNode) taskExplorers.get(id);
329: }
330:
331: /**
332: * Get the list of explorer ids
333: * @return an iterator for the explorer ids
334: */
335: public String[] getExplorerIds() {
336: if (cheatsheets == null) {
337: readCheatSheets(); // Ensure that the registry has been read
338: }
339: Set keys = taskExplorers.keySet();
340: return (String[]) keys.toArray(new String[keys.size()]);
341: }
342:
343: /**
344: * Finishes the addition of categories. The categories are sorted and
345: * added in a root to depth traversal.
346: */
347: private void finishCategories() {
348: // If no categories just return.
349: if (deferCategories == null)
350: return;
351:
352: // Sort categories by flattened name.
353: CategoryNode[] flatArray = new CategoryNode[deferCategories
354: .size()];
355: for (int i = 0; i < deferCategories.size(); i++) {
356: flatArray[i] = new CategoryNode((Category) deferCategories
357: .get(i));
358: }
359: Sorter sorter = new Sorter() {
360: private Collator collator = Collator.getInstance();
361:
362: public boolean compare(Object o1, Object o2) {
363: String s1 = ((CategoryNode) o1).getPath();
364: String s2 = ((CategoryNode) o2).getPath();
365: return collator.compare(s2, s1) > 0;
366: }
367: };
368: Object[] sortedCategories = sorter.sort(flatArray);
369:
370: // Add each category.
371: for (int nX = 0; nX < sortedCategories.length; nX++) {
372: Category cat = ((CategoryNode) sortedCategories[nX])
373: .getCategory();
374: finishCategory(cat);
375: }
376:
377: // Cleanup.
378: deferCategories = null;
379: }
380:
381: /**
382: * Save new category definition.
383: */
384: private void finishCategory(Category category) {
385: CheatSheetCollectionElement currentResult = (CheatSheetCollectionElement) cheatsheets;
386:
387: String[] categoryPath = category.getParentPath();
388: CheatSheetCollectionElement parent = currentResult; // ie.- root
389:
390: // Traverse down into parent category.
391: if (categoryPath != null) {
392: for (int i = 0; i < categoryPath.length; i++) {
393: CheatSheetCollectionElement tempElement = getChildWithID(
394: parent, categoryPath[i]);
395: if (tempElement == null) {
396: // The parent category is invalid. By returning here the
397: // category will be dropped and any cheatsheet within the category
398: // will be added to the "Other" category.
399: return;
400: }
401: parent = tempElement;
402: }
403: }
404:
405: // If another category already exists with the same id ignore this one.
406: Object test = getChildWithID(parent, category.getId());
407: if (test != null)
408: return;
409:
410: if (parent != null)
411: createCollectionElement(parent, category.getPluginId(),
412: category.getId(), category.getLabel());
413: }
414:
415: /**
416: * Insert the passed cheatsheet element into the cheatsheet collection appropriately
417: * based upon its defining extension's CATEGORY tag value
418: *
419: * @param element CheatSheetElement
420: * @param extension
421: * @param currentResult CheatSheetCollectionElement
422: */
423: private void finishCheatSheet(CheatSheetElement element,
424: IConfigurationElement config, AdaptableList result) {
425: CheatSheetCollectionElement currentResult = (CheatSheetCollectionElement) result;
426: StringTokenizer familyTokenizer = new StringTokenizer(
427: getCategoryStringFor(config), CATEGORY_SEPARATOR);
428:
429: // use the period-separated sections of the current CheatSheet's category
430: // to traverse through the NamedSolution "tree" that was previously created
431: CheatSheetCollectionElement currentCollectionElement = currentResult; // ie.- root
432: boolean moveToOther = false;
433:
434: while (familyTokenizer.hasMoreElements()) {
435: CheatSheetCollectionElement tempCollectionElement = getChildWithID(
436: currentCollectionElement, familyTokenizer
437: .nextToken());
438:
439: if (tempCollectionElement == null) { // can't find the path; bump it to uncategorized
440: moveToOther = true;
441: break;
442: }
443: currentCollectionElement = tempCollectionElement;
444: }
445:
446: if (moveToOther)
447: moveElementToUncategorizedCategory(currentResult, element);
448: else
449: currentCollectionElement.add(element);
450: }
451:
452: /**
453: * Finishes the addition of cheatsheets. The cheatsheets are processed and categorized.
454: */
455: private void finishCheatSheets() {
456: if (deferCheatSheets != null) {
457: Iterator iter = deferCheatSheets.iterator();
458: while (iter.hasNext()) {
459: CheatSheetElement cheatsheet = (CheatSheetElement) iter
460: .next();
461: IConfigurationElement config = cheatsheet
462: .getConfigurationElement();
463: finishCheatSheet(cheatsheet, config, cheatsheets);
464: }
465: deferCheatSheets = null;
466: }
467: }
468:
469: /**
470: * Return the appropriate category (tree location) for this CheatSheet.
471: * If a category is not specified then return a default one.
472: */
473: protected String getCategoryStringFor(IConfigurationElement config) {
474: String result = config.getAttribute(ATT_CATEGORY);
475: if (result == null)
476: result = UNCATEGORIZED_CHEATSHEET_CATEGORY;
477:
478: return result;
479: }
480:
481: /**
482: * Returns a list of cheatsheets, project and not.
483: *
484: * The return value for this method is cached since computing its value
485: * requires non-trivial work.
486: */
487: public AdaptableList getCheatSheets() {
488: if (cheatsheets == null)
489: readCheatSheets();
490: return cheatsheets;
491: }
492:
493: /**
494: * Go through the children of the passed parent and answer the child
495: * with the passed name. If no such child is found then return null.
496: *
497: * @return org.eclipse.ui.internal.model.CheatSheetCollectionElement
498: * @param parent org.eclipse.ui.internal.model.CheatSheetCollectionElement
499: * @param childName java.lang.String
500: */
501: protected CheatSheetCollectionElement getChildWithID(
502: CheatSheetCollectionElement parent, String id) {
503: Object[] children = parent.getChildren();
504: for (int i = 0; i < children.length; ++i) {
505: CheatSheetCollectionElement currentChild = (CheatSheetCollectionElement) children[i];
506: if (currentChild.getId().equals(id))
507: return currentChild;
508: }
509: return null;
510: }
511:
512: /**
513: * Initialize the passed element's properties based on the contents of
514: * the passed registry. Answer a boolean indicating whether the element
515: * was able to be adequately initialized.
516: *
517: * @return boolean
518: * @param element CheatSheetElement
519: * @param extension Extension
520: */
521: protected boolean initializeCheatSheet(CheatSheetElement element,
522: IConfigurationElement config) {
523: element.setID(config.getAttribute(ATT_ID));
524: element.setDescription(getDescription(config));
525: element.setConfigurationElement(config);
526: element.setRegistered(true);
527:
528: String contentFile = config.getAttribute(ATT_CONTENTFILE);
529: if (contentFile != null) {
530: element.setContentFile(contentFile);
531: }
532:
533: // ensure that a contentfile was specified
534: if (element.getConfigurationElement() == null
535: || element.getContentFile() == null) {
536: logMissingAttribute(config, ATT_CONTENTFILE);
537: return false;
538: }
539:
540: String listenerClass = config.getAttribute(ATT_LISTENERCLASS);
541: if (listenerClass != null) {
542: element.setListenerClass(listenerClass);
543: }
544: String composite = config.getAttribute(ATT_COMPOSITE);
545: if (composite != null) {
546: element
547: .setComposite(composite
548: .equalsIgnoreCase(trueString));
549: }
550: return true;
551: }
552:
553: /**
554: * Moves given element to "Other" category, previously creating one if missing.
555: */
556: protected void moveElementToUncategorizedCategory(
557: CheatSheetCollectionElement root, CheatSheetElement element) {
558: CheatSheetCollectionElement otherCategory = getChildWithID(
559: root, UNCATEGORIZED_CHEATSHEET_CATEGORY);
560:
561: if (otherCategory == null)
562: otherCategory = createCollectionElement(root, null,
563: UNCATEGORIZED_CHEATSHEET_CATEGORY,
564: UNCATEGORIZED_CHEATSHEET_CATEGORY_LABEL);
565:
566: otherCategory.add(element);
567: }
568:
569: /**
570: * Removes the empty categories from a cheatsheet collection.
571: */
572: private void pruneEmptyCategories(CheatSheetCollectionElement parent) {
573: Object[] children = parent.getChildren();
574: for (int nX = 0; nX < children.length; nX++) {
575: CheatSheetCollectionElement child = (CheatSheetCollectionElement) children[nX];
576: pruneEmptyCategories(child);
577: }
578: }
579:
580: /**
581: * Reads the cheatsheets in a registry.
582: * <p>
583: * This implementation uses a defering strategy. All of the elements
584: * (categories, cheatsheets) are read. The categories are created as the read occurs.
585: * The cheatsheets are just stored for later addition after the read completes.
586: * This ensures that cheatsheet categorization is performed after all categories
587: * have been read.
588: * </p>
589: */
590: protected void readCheatSheets() {
591: IExtensionRegistry xregistry = Platform.getExtensionRegistry();
592:
593: if (cheatsheets == null) {
594: cheatsheets = createEmptyCheatSheetCollection();
595: readRegistry(xregistry,
596: ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID,
597: CHEAT_SHEET_CONTENT);
598: }
599:
600: finishCategories();
601: finishCheatSheets();
602:
603: if (cheatsheets != null) {
604: CheatSheetCollectionElement parent = (CheatSheetCollectionElement) cheatsheets;
605: pruneEmptyCategories(parent);
606: }
607: }
608:
609: public ArrayList readItemExtensions() {
610: if (cheatsheetItemExtensions == null) {
611: cheatsheetItemExtensions = new ArrayList();
612:
613: IExtensionRegistry xregistry = Platform
614: .getExtensionRegistry();
615: //Now read the cheat sheet extensions.
616: readRegistry(xregistry,
617: ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID,
618: csItemExtension);
619: }
620:
621: return cheatsheetItemExtensions;
622: }
623:
624: private void createItemExtensionElement(
625: IConfigurationElement element) {
626: String className = element.getAttribute(ATT_CLASS);
627: String itemAttribute = element.getAttribute(ATT_ITEM_ATTRIBUTE);
628:
629: // ensure that a class was specified
630: if (className == null) {
631: logMissingAttribute(element, ATT_CLASS);
632: return;
633: }
634: // ensure that a itemAttribute was specified
635: if (itemAttribute == null) {
636: logMissingAttribute(element, ATT_ITEM_ATTRIBUTE);
637: return;
638: }
639:
640: CheatSheetItemExtensionElement itemExtensionElement = new CheatSheetItemExtensionElement();
641: itemExtensionElement.setClassName(className);
642: itemExtensionElement.setItemAttribute(itemAttribute);
643: itemExtensionElement.setConfigurationElement(element);
644:
645: cheatsheetItemExtensions.add(itemExtensionElement);
646: }
647:
648: /*
649: * Get a required attribute. Log an error if it has no value.
650: */
651: private String getAndCheckAttribute(IConfigurationElement element,
652: String name) {
653: String result = element.getAttribute(name);
654: if (result == null) {
655: logMissingAttribute(element, name);
656: }
657: return result;
658: }
659:
660: private void createTaskExplorerElement(IConfigurationElement element) {
661: String icon = element.getAttribute(ATT_ICON);
662: String className = getAndCheckAttribute(element, ATT_CLASS);
663: String name = getAndCheckAttribute(element, ATT_NAME);
664: String id = getAndCheckAttribute(element, ATT_ID);
665: String pluginId = element.getContributor().getName();
666: if (id != null && className != null && name != null) {
667: TaskExplorerNode node = new TaskExplorerNode();
668: node.setId(id);
669: node.setIconPath(icon);
670: node.setClassName(className);
671: node.setName(name);
672: node.setPluginId(pluginId);
673: taskExplorers.put(id, node);
674: }
675: }
676:
677: private void createTaskEditorElement(IConfigurationElement element) {
678: String icon = getAndCheckAttribute(element, ATT_ICON);
679: String className = getAndCheckAttribute(element, ATT_CLASS);
680: String id = getAndCheckAttribute(element, ATT_ID);
681: String pluginId = element.getContributor().getName();
682: if (id != null && className != null && icon != null) {
683: TaskEditorNode node = new TaskEditorNode();
684: node.setId(id);
685: node.setIconPath(icon);
686: node.setClassName(className);
687: node.setPluginId(pluginId);
688: taskEditors.put(id, node);
689: }
690: }
691:
692: /**
693: * Implement this method to read element attributes.
694: */
695: protected boolean readElement(IConfigurationElement element) {
696: if (element.getName().equals(TAG_CATEGORY)) {
697: deferCategory(element);
698: return true;
699: } else if (element.getName().equals(TAG_ITEM_EXTENSION)) {
700: createItemExtensionElement(element);
701: return true;
702: } else if (element.getName().equals(TAG_TASK_EDITOR)) {
703: createTaskEditorElement(element);
704: return true;
705: } else if (element.getName().equals(TAG_TASK_EXPLORER)) {
706: createTaskExplorerElement(element);
707: return true;
708: } else {
709: if (!element.getName().equals(TAG_CHEATSHEET))
710: return false;
711:
712: CheatSheetElement cheatsheet = createCheatSheetElement(element);
713: if (cheatsheet != null)
714: addNewElementToResult(cheatsheet, element, cheatsheets);
715: return true;
716: }
717: }
718:
719: /* (non-Javadoc)
720: * @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent)
721: */
722: public void registryChanged(IRegistryChangeEvent event) {
723: IExtensionDelta[] cheatSheetDeltas = event.getExtensionDeltas(
724: ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID,
725: CHEAT_SHEET_CONTENT);
726: if (cheatSheetDeltas.length > 0) {
727: // reset the list of cheat sheets, it will be build on demand
728: cheatsheets = null;
729: }
730:
731: IExtensionDelta[] itemExtensionDeltas = event
732: .getExtensionDeltas(
733: ICheatSheetResource.CHEAT_SHEET_PLUGIN_ID,
734: csItemExtension);
735: if (itemExtensionDeltas.length > 0) {
736: // reset the list of cheat sheets item extensions, it will be build on demand
737: cheatsheetItemExtensions = null;
738: }
739: }
740:
741: public void stop() {
742: IExtensionRegistry xregistry = Platform.getExtensionRegistry();
743: xregistry.removeRegistryChangeListener(instance);
744:
745: instance = null;
746: }
747: }
|