001: package net.refractions.udig.style.sld.editor;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.Iterator;
006: import java.util.List;
007: import java.util.StringTokenizer;
008:
009: import org.eclipse.core.runtime.IConfigurationElement;
010: import org.eclipse.core.runtime.IExtension;
011: import org.eclipse.core.runtime.IExtensionPoint;
012: import org.eclipse.core.runtime.IRegistryChangeEvent;
013: import org.eclipse.core.runtime.IRegistryChangeListener;
014: import org.eclipse.core.runtime.Platform;
015: import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;
016: import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
017: import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
018: import org.eclipse.jface.preference.PreferenceManager;
019: import org.eclipse.jface.util.Assert;
020: import org.eclipse.ui.PlatformUI;
021:
022: public class EditorPageManager implements IExtensionChangeHandler {
023:
024: public static final String ATT_ID = "id"; //$NON-NLS-1$
025: public static final String ATT_CLASS = "class"; //$NON-NLS-1$
026: public static final String ATT_NAME = "name"; //$NON-NLS-1$
027: public static final String ATT_LABEL = "label"; //$NON-NLS-1$
028: public static final String PL_KEYWORDS = "keywords"; //$NON-NLS-1$
029:
030: /**
031: * Pre-order traversal means visit the root first,
032: * then the children.
033: */
034: public static final int PRE_ORDER = 0;
035:
036: /**
037: * Post-order means visit the children, and then the root.
038: */
039: public static final int POST_ORDER = 1;
040:
041: /**
042: * The root node.
043: * Note that the root node is a special internal node
044: * that is used to collect together all the nodes that
045: * have no parent; it is not given out to clients.
046: */
047: EditorNode root = new EditorNode("");//$NON-NLS-1$
048:
049: /**
050: * The path separator character.
051: */
052: String separator;
053:
054: /**
055: * Creates a new preference manager.
056: */
057: public EditorPageManager() {
058: this ('.');
059: }
060:
061: /**
062: * Create a new instance of the receiver with the specified seperatorChar
063: *
064: * @param separatorChar
065: */
066: public EditorPageManager(char separatorChar) {
067: separator = new String(new char[] { separatorChar });
068:
069: IExtensionTracker tracker = PlatformUI.getWorkbench()
070: .getExtensionTracker();
071: tracker.registerHandler(this , ExtensionTracker
072: .createExtensionPointFilter(getExtensionPointFilter()));
073:
074: // add a listener for keyword deltas. If any occur clear all page caches
075: Platform.getExtensionRegistry().addRegistryChangeListener(
076: new IRegistryChangeListener() {
077:
078: /*
079: * (non-Javadoc)
080: *
081: * @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent)
082: */
083: public void registryChanged(
084: IRegistryChangeEvent event) {
085: if (event.getExtensionDeltas(
086: StyleEditorPage.XPID, PL_KEYWORDS).length > 0) {
087: for (Iterator j = getElements(
088: PreferenceManager.POST_ORDER)
089: .iterator(); j.hasNext();) {
090: ((EditorNode) j.next()).clearKeywords();
091: }
092: }
093: }
094: });
095: }
096:
097: /**
098: * Add the pages and the groups to the receiver.
099: *
100: * @param pageContributions
101: */
102: public void addPages(Collection pageContributions) {
103:
104: // Add the contributions to the manager
105: Iterator iterator = pageContributions.iterator();
106: while (iterator.hasNext()) {
107: Object next = iterator.next();
108: if (next instanceof EditorNode) {
109: EditorNode node = (EditorNode) next;
110: addToRoot(node);
111: registerNode(node);
112: }
113: }
114:
115: }
116:
117: /**
118: * Register a node with the extension tracker.
119: *
120: * @param node
121: * register the given node and its subnodes with the extension
122: * tracker
123: */
124: public void registerNode(EditorNode node) {
125: PlatformUI.getWorkbench().getExtensionTracker().registerObject(
126: node.getConfigurationElement().getDeclaringExtension(),
127: node, IExtensionTracker.REF_WEAK);
128: EditorNode[] subNodes = node.getSubNodes();
129: for (int i = 0; i < subNodes.length; i++) {
130: registerNode((EditorNode) subNodes[i]);
131: }
132:
133: }
134:
135: /* (non-Javadoc)
136: * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#addExtension(org.eclipse.core.runtime.dynamicHelpers.IExtensionTracker, org.eclipse.core.runtime.IExtension)
137: */
138: public void addExtension(IExtensionTracker tracker,
139: IExtension extension) {
140:
141: IConfigurationElement[] elements = extension
142: .getConfigurationElements();
143: for (int i = 0; i < elements.length; i++) {
144: EditorNode node = null;
145:
146: boolean nameMissing = elements[i].getAttribute(ATT_NAME) == null;
147: String id = elements[i].getAttribute(ATT_ID);
148: boolean classMissing = getClassValue(elements[i], ATT_CLASS) == null;
149:
150: //System.out.println(elements[i].id+","+nameMissing+","+classMissing);
151: if (!(nameMissing || id == null || classMissing)) {
152: node = new EditorNode(id, elements[i]);
153: }
154:
155: if (node == null)
156: continue;
157: registerNode(node);
158: String category = node.getCategory();
159: if (category == null) {
160: addToRoot(node);
161: } else {
162: EditorNode parent = null;
163: for (Iterator j = getElements(
164: PreferenceManager.POST_ORDER).iterator(); j
165: .hasNext();) {
166: EditorNode element = (EditorNode) j.next();
167: if (category.equals(element.getId())) {
168: parent = element;
169: break;
170: }
171: }
172: if (parent == null) {
173: //TODO: log error
174: // Could not find the parent - log
175: // WorkbenchPlugin
176: // .log("Invalid preference page path: " + category); //$NON-NLS-1$
177: addToRoot(node);
178: } else {
179: parent.add(node);
180: }
181: }
182: }
183: }
184:
185: public String getClassValue(IConfigurationElement configElement,
186: String classAttributeName) {
187: String className = configElement
188: .getAttribute(classAttributeName);
189: if (className != null)
190: return className;
191: IConfigurationElement[] candidateChildren = configElement
192: .getChildren(classAttributeName);
193: if (candidateChildren.length == 0)
194: return null;
195:
196: return candidateChildren[0].getAttribute(ATT_CLASS);
197: }
198:
199: /*
200: * (non-Javadoc)
201: *
202: * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionAdditionHandler#getExtensionPointFilter()
203: */
204: private IExtensionPoint getExtensionPointFilter() {
205: return Platform.getExtensionRegistry().getExtensionPoint(
206: StyleEditorPage.XPID);
207: }
208:
209: /* (non-Javadoc)
210: * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension, java.lang.Object[])
211: */
212: public void removeExtension(IExtension extension, Object[] objects) {
213: for (int i = 0; i < objects.length; i++) {
214: if (objects[i] instanceof EditorNode) {
215: EditorNode node = (EditorNode) objects[i];
216: node.disposeResources();
217: deepRemove(getRoot(), node);
218: }
219: }
220: }
221:
222: /**
223: * Removes the node from the manager, searching through all subnodes.
224: *
225: * @param parent
226: * the node to search
227: * @param nodeToRemove
228: * the node to remove
229: * @return whether the node was removed
230: */
231: private boolean deepRemove(EditorNode parent,
232: EditorNode nodeToRemove) {
233: if (parent == nodeToRemove)
234: if (parent == getRoot()) {
235: removeAll(); // we're removing the root
236: return true;
237: }
238:
239: if (parent.remove(nodeToRemove))
240: return true;
241:
242: EditorNode[] subNodes = parent.getSubNodes();
243: for (int i = 0; i < subNodes.length; i++) {
244: if (deepRemove(subNodes[i], nodeToRemove))
245: return true;
246: }
247: return false;
248: }
249:
250: /**
251: * Adds the given preference node as a subnode of the
252: * node at the given path.
253: *
254: * @param path the path
255: * @param node the node to add
256: * @return <code>true</code> if the add was successful,
257: * and <code>false</code> if there is no contribution at
258: * the given path
259: */
260: public boolean addTo(String path, EditorNode node) {
261: EditorNode target = find(path);
262: if (target == null)
263: return false;
264: target.add(node);
265: return true;
266: }
267:
268: /**
269: * Adds the given preference node as a subnode of the
270: * root.
271: *
272: * @param node the node to add, which must implement
273: * <code>SLDEditorPageNode</code>
274: */
275: public void addToRoot(EditorNode node) {
276: Assert.isNotNull(node);
277: root.add(node);
278: }
279:
280: /**
281: * Recursively enumerates all nodes at or below the given node
282: * and adds them to the given list in the given order.
283: *
284: * @param node the starting node
285: * @param sequence a read-write list of preference nodes
286: * (element type: <code>SLDEditorPageNode</code>)
287: * in the given order
288: * @param order the traversal order, one of
289: * <code>PRE_ORDER</code> and <code>POST_ORDER</code>
290: */
291: protected void buildSequence(EditorNode node,
292: List<EditorNode> sequence, int order) {
293: if (order == PRE_ORDER)
294: sequence.add(node);
295: EditorNode[] subnodes = node.getSubNodes();
296: for (int i = 0; i < subnodes.length; i++) {
297: buildSequence(subnodes[i], sequence, order);
298: }
299: if (order == POST_ORDER)
300: sequence.add(node);
301: }
302:
303: /**
304: * Finds and returns the contribution node at the given path.
305: *
306: * @param path the path
307: * @return the node, or <code>null</code> if none
308: */
309: public EditorNode find(String path) {
310: return find(path, root);
311: }
312:
313: /**
314: * Finds and returns the preference node directly
315: * below the top at the given path.
316: *
317: * @param path the path
318: * @return the node, or <code>null</code> if none
319: *
320: * @since 3.1
321: */
322: protected EditorNode find(String path, EditorNode top) {
323: Assert.isNotNull(path);
324: StringTokenizer stok = new StringTokenizer(path, separator);
325: EditorNode node = top;
326: while (stok.hasMoreTokens()) {
327: String id = stok.nextToken();
328: node = node.findSubNode(id);
329: if (node == null)
330: return null;
331: }
332: if (node == top)
333: return null;
334: return node;
335: }
336:
337: /**
338: * Returns all preference nodes managed by this
339: * manager.
340: *
341: * @param order the traversal order, one of
342: * <code>PRE_ORDER</code> and <code>POST_ORDER</code>
343: * @return a list of preference nodes
344: * (element type: <code>SLDEditorPageNode</code>)
345: * in the given order
346: */
347: public List getElements(int order) {
348: Assert.isTrue(order == PRE_ORDER || order == POST_ORDER,
349: "invalid traversal order");//$NON-NLS-1$
350: ArrayList<EditorNode> sequence = new ArrayList<EditorNode>();
351: EditorNode[] subnodes = getRoot().getSubNodes();
352: for (int i = 0; i < subnodes.length; i++)
353: buildSequence(subnodes[i], sequence, order);
354: return sequence;
355: }
356:
357: /**
358: * Returns the root node.
359: * Note that the root node is a special internal node
360: * that is used to collect together all the nodes that
361: * have no parent; it is not given out to clients.
362: *
363: * @return the root node
364: */
365: public EditorNode getRoot() {
366: return root;
367: }
368:
369: /**
370: * Removes the prefernece node at the given path.
371: *
372: * @param path the path
373: * @return the node that was removed, or <code>null</code>
374: * if there was no node at the given path
375: */
376: public EditorNode remove(String path) {
377: Assert.isNotNull(path);
378: int index = path.lastIndexOf(separator);
379: if (index == -1)
380: return root.remove(path);
381: // Make sure that the last character in the string isn't the "."
382: Assert.isTrue(index < path.length() - 1,
383: "Path can not end with a dot");//$NON-NLS-1$
384: String parentPath = path.substring(0, index);
385: String id = path.substring(index + 1);
386: EditorNode parentNode = find(parentPath);
387: if (parentNode == null)
388: return null;
389: return parentNode.remove(id);
390: }
391:
392: /**
393: * Removes the given prefreence node if it is managed by
394: * this contribution manager.
395: *
396: * @param node the node to remove
397: * @return <code>true</code> if the node was removed,
398: * and <code>false</code> otherwise
399: */
400: public boolean remove(EditorNode node) {
401: Assert.isNotNull(node);
402:
403: return root.remove(node);
404: }
405:
406: /**
407: * Removes all contribution nodes known to this manager.
408: */
409: public void removeAll() {
410: root = new EditorNode("");//$NON-NLS-1$
411: }
412:
413: public EditorNode[] getRootSubNodes() {
414: return getRoot().getSubNodes();
415: }
416:
417: /**
418: * Returns true if the specified node exists in the manager.
419: *
420: * @param nodeId Unique identified for a node
421: * @return boolean
422: */
423: public boolean hasNode(String nodeId) {
424: EditorNode[] nodes = getRoot().getSubNodes();
425: for (int i = 0; i < nodes.length; i++) {
426: if (nodes[i].getId().equalsIgnoreCase(nodeId))
427: return true;
428: }
429: return false;
430: }
431:
432: }
|