001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 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.registry;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.Collections;
015: import java.util.Comparator;
016: import java.util.HashSet;
017: import java.util.Iterator;
018: import java.util.Set;
019: import java.util.StringTokenizer;
020:
021: import org.eclipse.core.runtime.CoreException;
022: import org.eclipse.core.runtime.IConfigurationElement;
023: import org.eclipse.core.runtime.IExtensionRegistry;
024: import org.eclipse.core.runtime.Platform;
025: import org.eclipse.ui.internal.WorkbenchMessages;
026: import org.eclipse.ui.internal.WorkbenchPlugin;
027: import org.eclipse.ui.internal.dialogs.WizardCollectionElement;
028: import org.eclipse.ui.internal.dialogs.WorkbenchWizardElement;
029: import org.eclipse.ui.internal.util.Util;
030:
031: import com.ibm.icu.text.Collator;
032:
033: /**
034: * Instances access the registry that is provided at creation time
035: * in order to determine the contained Wizards
036: */
037: public class WizardsRegistryReader extends RegistryReader {
038:
039: private String pluginPoint;
040:
041: private WizardCollectionElement wizardElements = null;
042:
043: private ArrayList deferWizards = null;
044:
045: private ArrayList deferCategories = null;
046:
047: private Set deferPrimary;
048:
049: // constants
050: /**
051: * Examples wizard category id
052: */
053: public final static String FULL_EXAMPLES_WIZARD_CATEGORY = "org.eclipse.ui.Examples";//$NON-NLS-1$
054: /**
055: * Other wizard category id
056: */
057: final public static String UNCATEGORIZED_WIZARD_CATEGORY = "org.eclipse.ui.Other";//$NON-NLS-1$
058: /**
059: * General wizard category id
060: */
061: final public static String GENERAL_WIZARD_CATEGORY = "org.eclipse.ui.Basic"; //$NON-NLS-1$
062:
063: final private static String UNCATEGORIZED_WIZARD_CATEGORY_LABEL = WorkbenchMessages.NewWizardsRegistryReader_otherCategory;
064:
065: private final static String CATEGORY_SEPARATOR = "/";//$NON-NLS-1$
066:
067: private WorkbenchWizardElement[] primaryWizards = new WorkbenchWizardElement[0];
068:
069: private class CategoryNode {
070: private Category category;
071:
072: private String path;
073:
074: CategoryNode(Category cat) {
075: category = cat;
076: path = ""; //$NON-NLS-1$
077: String[] categoryPath = category.getParentPath();
078: if (categoryPath != null) {
079: for (int nX = 0; nX < categoryPath.length; nX++) {
080: path += categoryPath[nX] + '/';
081: }
082: }
083: path += cat.getId();
084: }
085:
086: String getPath() {
087: return path;
088: }
089:
090: Category getCategory() {
091: return category;
092: }
093: }
094:
095: private static final Comparator comparer = new Comparator() {
096: private Collator collator = Collator.getInstance();
097:
098: public int compare(Object arg0, Object arg1) {
099: String s1 = ((CategoryNode) arg0).getPath();
100: String s2 = ((CategoryNode) arg1).getPath();
101: return collator.compare(s1, s2);
102: }
103: };
104:
105: private boolean readAll = true;
106:
107: private String plugin;
108:
109: /**
110: *Create an instance of this class.
111: *
112: * @param pluginId the plugin id
113: * @param pluginPointId java.lang.String
114: */
115: public WizardsRegistryReader(String pluginId, String pluginPointId) {
116: pluginPoint = pluginPointId;
117: plugin = pluginId;
118: }
119:
120: /* (non-Javadoc)
121: * Method declared on WizardRegistryReader.
122: * <p>
123: * This implementation uses a defering strategy. For more info see
124: * <code>readWizards</code>.
125: * </p>
126: */
127: protected void addNewElementToResult(
128: WorkbenchWizardElement element, IConfigurationElement config) {
129: // TODO: can we remove the config parameter?
130: deferWizard(element);
131: }
132:
133: /**
134: *
135: * @param parent
136: * @param element
137: * @since 3.1
138: */
139: private WizardCollectionElement createCollectionElement(
140: WizardCollectionElement parent,
141: IConfigurationElement element) {
142: WizardCollectionElement newElement = new WizardCollectionElement(
143: element, parent);
144:
145: parent.add(newElement);
146: return newElement;
147: }
148:
149: /**
150: * Create and answer a new WizardCollectionElement, configured as a
151: * child of <code>parent</code>
152: *
153: * @return org.eclipse.ui.internal.model.WizardCollectionElement
154: * @param parent org.eclipse.ui.internal.model.WizardCollectionElement
155: * @param id the id of the new collection
156: * @param pluginId the originating plugin id of the collection, if any. <code>null</code> otherwise.
157: * @param label java.lang.String
158: */
159: protected WizardCollectionElement createCollectionElement(
160: WizardCollectionElement parent, String id, String pluginId,
161: String label) {
162: WizardCollectionElement newElement = new WizardCollectionElement(
163: id, pluginId, label, parent);
164:
165: parent.add(newElement);
166: return newElement;
167: }
168:
169: /**
170: * Creates empty element collection. Overrider to fill
171: * initial elements, if needed.
172: */
173: protected void createEmptyWizardCollection() {
174: wizardElements = new WizardCollectionElement(
175: "root", null, "root", null);//$NON-NLS-2$//$NON-NLS-1$
176: }
177:
178: /**
179: * Set the initial wizard set for supplemental reading via dynamic plugin loading.
180: *
181: * @param wizards the wizards
182: * @since 3.1
183: */
184: public void setInitialCollection(WizardCollectionElement wizards) {
185: wizardElements = wizards;
186: readAll = false;
187: }
188:
189: /**
190: * Stores a category element for deferred addition.
191: */
192: private void deferCategory(IConfigurationElement config) {
193: // Create category.
194: Category category = null;
195: try {
196: category = new Category(config);
197: } catch (CoreException e) {
198: WorkbenchPlugin.log(
199: "Cannot create category: ", e.getStatus());//$NON-NLS-1$
200: return;
201: }
202:
203: // Defer for later processing.
204: if (deferCategories == null) {
205: deferCategories = new ArrayList(20);
206: }
207: deferCategories.add(category);
208: }
209:
210: /**
211: * Stores a wizard element for deferred addition.
212: */
213: private void deferWizard(WorkbenchWizardElement element) {
214: if (deferWizards == null) {
215: deferWizards = new ArrayList(50);
216: }
217: deferWizards.add(element);
218: }
219:
220: /**
221: * Finishes the addition of categories. The categories are sorted and
222: * added in a root to depth traversal.
223: */
224: private void finishCategories() {
225: // If no categories just return.
226: if (deferCategories == null) {
227: return;
228: }
229:
230: // Sort categories by flattened name.
231: CategoryNode[] flatArray = new CategoryNode[deferCategories
232: .size()];
233: for (int i = 0; i < deferCategories.size(); i++) {
234: flatArray[i] = new CategoryNode((Category) deferCategories
235: .get(i));
236: }
237: Collections.sort(Arrays.asList(flatArray), comparer);
238:
239: // Add each category.
240: for (int nX = 0; nX < flatArray.length; nX++) {
241: Category cat = flatArray[nX].getCategory();
242: finishCategory(cat);
243: }
244:
245: // Cleanup.
246: deferCategories = null;
247: }
248:
249: /**
250: * Save new category definition.
251: */
252: private void finishCategory(Category category) {
253: String[] categoryPath = category.getParentPath();
254: WizardCollectionElement parent = wizardElements; // ie.- root
255:
256: // Traverse down into parent category.
257: if (categoryPath != null) {
258: for (int i = 0; i < categoryPath.length; i++) {
259: WizardCollectionElement tempElement = getChildWithID(
260: parent, categoryPath[i]);
261: if (tempElement == null) {
262: // The parent category is invalid. By returning here the
263: // category will be dropped and any wizard within the category
264: // will be added to the "Other" category.
265: return;
266: }
267: parent = tempElement;
268: }
269: }
270:
271: // If another category already exists with the same id ignore this one.
272: Object test = getChildWithID(parent, category.getId());
273: if (test != null) {
274: return;
275: }
276:
277: if (parent != null) {
278: createCollectionElement(parent,
279: (IConfigurationElement) Util.getAdapter(category,
280: IConfigurationElement.class));
281: }
282: }
283:
284: /**
285: * Finishes the recognition of primary wizards.
286: */
287: private void finishPrimary() {
288: if (deferPrimary != null) {
289: ArrayList primary = new ArrayList();
290: for (Iterator i = deferPrimary.iterator(); i.hasNext();) {
291: String id = (String) i.next();
292: WorkbenchWizardElement element = getWizardElements()
293: .findWizard(id, true);
294: if (element != null) {
295: primary.add(element);
296: }
297: }
298:
299: primaryWizards = (WorkbenchWizardElement[]) primary
300: .toArray(new WorkbenchWizardElement[primary.size()]);
301:
302: deferPrimary = null;
303: }
304: }
305:
306: /**
307: * Insert the passed wizard element into the wizard collection appropriately
308: * based upon its defining extension's CATEGORY tag value
309: *
310: * @param element WorkbenchWizardElement
311: * @param config configuration element
312: */
313: private void finishWizard(WorkbenchWizardElement element,
314: IConfigurationElement config) {
315: StringTokenizer familyTokenizer = new StringTokenizer(
316: getCategoryStringFor(config), CATEGORY_SEPARATOR);
317:
318: // use the period-separated sections of the current Wizard's category
319: // to traverse through the NamedSolution "tree" that was previously created
320: WizardCollectionElement currentCollectionElement = wizardElements; // ie.- root
321: boolean moveToOther = false;
322:
323: while (familyTokenizer.hasMoreElements()) {
324: WizardCollectionElement tempCollectionElement = getChildWithID(
325: currentCollectionElement, familyTokenizer
326: .nextToken());
327:
328: if (tempCollectionElement == null) { // can't find the path; bump it to uncategorized
329: moveToOther = true;
330: break;
331: }
332: currentCollectionElement = tempCollectionElement;
333: }
334:
335: if (moveToOther) {
336: moveElementToUncategorizedCategory(wizardElements, element);
337: } else {
338: currentCollectionElement.add(element);
339: element.setParent(currentCollectionElement);
340: }
341: }
342:
343: /**
344: * Finishes the addition of wizards. The wizards are processed and categorized.
345: */
346: private void finishWizards() {
347: if (deferWizards != null) {
348: Iterator iter = deferWizards.iterator();
349: while (iter.hasNext()) {
350: WorkbenchWizardElement wizard = (WorkbenchWizardElement) iter
351: .next();
352: IConfigurationElement config = wizard
353: .getConfigurationElement();
354: finishWizard(wizard, config);
355: }
356: deferWizards = null;
357: }
358: }
359:
360: /**
361: * Return the appropriate category (tree location) for this Wizard.
362: * If a category is not specified then return a default one.
363: */
364: protected String getCategoryStringFor(IConfigurationElement config) {
365: String result = config
366: .getAttribute(IWorkbenchRegistryConstants.TAG_CATEGORY);
367: if (result == null) {
368: result = UNCATEGORIZED_WIZARD_CATEGORY;
369: }
370:
371: return result;
372: }
373:
374: /**
375: * Go through the children of the passed parent and answer the child
376: * with the passed name. If no such child is found then return null.
377: *
378: * @return org.eclipse.ui.internal.model.WizardCollectionElement
379: * @param parent org.eclipse.ui.internal.model.WizardCollectionElement
380: * @param id java.lang.String
381: */
382: protected WizardCollectionElement getChildWithID(
383: WizardCollectionElement parent, String id) {
384: Object[] children = parent.getChildren(null);
385: for (int i = 0; i < children.length; ++i) {
386: WizardCollectionElement currentChild = (WizardCollectionElement) children[i];
387: if (currentChild.getId().equals(id)) {
388: return currentChild;
389: }
390: }
391: return null;
392: }
393:
394: /**
395: * Moves given element to "Other" category, previously creating one if missing.
396: */
397: protected void moveElementToUncategorizedCategory(
398: WizardCollectionElement root, WorkbenchWizardElement element) {
399: WizardCollectionElement otherCategory = getChildWithID(root,
400: UNCATEGORIZED_WIZARD_CATEGORY);
401:
402: if (otherCategory == null) {
403: otherCategory = createCollectionElement(root,
404: UNCATEGORIZED_WIZARD_CATEGORY, null,
405: UNCATEGORIZED_WIZARD_CATEGORY_LABEL);
406: }
407:
408: otherCategory.add(element);
409: element.setParent(otherCategory);
410: }
411:
412: /**
413: * Removes the empty categories from a wizard collection.
414: */
415: private void pruneEmptyCategories(WizardCollectionElement parent) {
416: Object[] children = parent.getChildren(null);
417: for (int nX = 0; nX < children.length; nX++) {
418: WizardCollectionElement child = (WizardCollectionElement) children[nX];
419: pruneEmptyCategories(child);
420: boolean shouldPrune = child.getId().equals(
421: FULL_EXAMPLES_WIZARD_CATEGORY);
422: if (child.isEmpty() && shouldPrune) {
423: parent.remove(child);
424: }
425: }
426: }
427:
428: /**
429: * Implement this method to read element attributes.
430: */
431: public boolean readElement(IConfigurationElement element) {
432: if (element.getName().equals(
433: IWorkbenchRegistryConstants.TAG_CATEGORY)) {
434: deferCategory(element);
435: return true;
436: } else if (element.getName().equals(
437: IWorkbenchRegistryConstants.TAG_PRIMARYWIZARD)) {
438: if (deferPrimary == null) {
439: deferPrimary = new HashSet();
440: }
441: deferPrimary.add(element
442: .getAttribute(IWorkbenchRegistryConstants.ATT_ID));
443:
444: return true;
445: } else {
446: if (!element.getName().equals(
447: IWorkbenchRegistryConstants.TAG_WIZARD)) {
448: return false;
449: }
450: WorkbenchWizardElement wizard = createWizardElement(element);
451: if (wizard != null) {
452: addNewElementToResult(wizard, element);
453: }
454: return true;
455: }
456: }
457:
458: /**
459: * Reads the wizards in a registry.
460: * <p>
461: * This implementation uses a defering strategy. All of the elements
462: * (categories, wizards) are read. The categories are created as the read occurs.
463: * The wizards are just stored for later addition after the read completes.
464: * This ensures that wizard categorization is performed after all categories
465: * have been read.
466: * </p>
467: */
468: protected void readWizards() {
469: if (readAll) {
470: if (!areWizardsRead()) {
471: createEmptyWizardCollection();
472: IExtensionRegistry registry = Platform
473: .getExtensionRegistry();
474: readRegistry(registry, plugin, pluginPoint);
475: }
476: }
477: finishCategories();
478: finishWizards();
479: finishPrimary();
480: if (wizardElements != null) {
481: pruneEmptyCategories(wizardElements);
482: }
483: }
484:
485: /**
486: * Returns the list of wizards that are considered 'primary'.
487: *
488: * The return value for this method is cached since computing its value
489: * requires non-trivial work.
490: *
491: * @return the primary wizards
492: */
493: public WorkbenchWizardElement[] getPrimaryWizards() {
494: if (!areWizardsRead()) {
495: readWizards();
496: }
497: return primaryWizards;
498: }
499:
500: /**
501: * Returns whether the wizards have been read already
502: */
503: protected boolean areWizardsRead() {
504: return wizardElements != null && readAll;
505: }
506:
507: /**
508: * Returns a list of wizards, project and not.
509: *
510: * The return value for this method is cached since computing its value
511: * requires non-trivial work.
512: *
513: * @return the wizard collection
514: */
515: public WizardCollectionElement getWizardElements() {
516: if (!areWizardsRead()) {
517: readWizards();
518: }
519: return wizardElements;
520: }
521:
522: protected Object[] getWizardCollectionElements() {
523: if (!areWizardsRead()) {
524: readWizards();
525: }
526: return wizardElements.getChildren();
527: }
528:
529: /**
530: * Returns a new WorkbenchWizardElement configured according to the parameters
531: * contained in the passed Registry.
532: *
533: * May answer null if there was not enough information in the Extension to create
534: * an adequate wizard
535: */
536: protected WorkbenchWizardElement createWizardElement(
537: IConfigurationElement element) {
538: // WizardElements must have a name attribute
539: if (element.getAttribute(IWorkbenchRegistryConstants.ATT_NAME) == null) {
540: logMissingAttribute(element,
541: IWorkbenchRegistryConstants.ATT_NAME);
542: return null;
543: }
544:
545: if (getClassValue(element,
546: IWorkbenchRegistryConstants.ATT_CLASS) == null) {
547: logMissingAttribute(element,
548: IWorkbenchRegistryConstants.ATT_CLASS);
549: return null;
550: }
551: return new WorkbenchWizardElement(element);
552: }
553:
554: /**
555: * Returns the first wizard with a given id.
556: *
557: * @param id wizard id to search for
558: * @return WorkbenchWizardElement matching the given id, if found; null otherwise
559: */
560: public WorkbenchWizardElement findWizard(String id) {
561: Object[] wizards = getWizardCollectionElements();
562: for (int nX = 0; nX < wizards.length; nX++) {
563: WizardCollectionElement collection = (WizardCollectionElement) wizards[nX];
564: WorkbenchWizardElement element = collection.findWizard(id,
565: true);
566: if (element != null) {
567: return element;
568: }
569: }
570: return null;
571: }
572: }
|