001: /*
002: *
003: * JMoney - A Personal Finance Manager
004: * Copyright (c) 2004 Nigel Westbury <westbury@users.sourceforge.net>
005: *
006: *
007: * This program is free software; you can redistribute it and/or modify
008: * it under the terms of the GNU General Public License as published by
009: * the Free Software Foundation; either version 2 of the License, or
010: * (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: *
021: */
022:
023: package net.sf.jmoney.views;
024:
025: import java.util.ArrayList;
026: import java.util.HashMap;
027: import java.util.Map;
028: import java.util.Vector;
029:
030: import net.sf.jmoney.JMoneyPlugin;
031: import net.sf.jmoney.model2.ExtendablePropertySet;
032: import net.sf.jmoney.model2.MalformedPluginException;
033: import net.sf.jmoney.model2.PageEntry;
034: import net.sf.jmoney.model2.PropertySet;
035: import net.sf.jmoney.model2.PropertySetNotFoundException;
036:
037: import org.eclipse.core.runtime.CoreException;
038: import org.eclipse.core.runtime.IAdaptable;
039: import org.eclipse.core.runtime.IConfigurationElement;
040: import org.eclipse.core.runtime.IExtensionRegistry;
041: import org.eclipse.core.runtime.Platform;
042: import org.eclipse.jface.resource.ImageDescriptor;
043: import org.eclipse.swt.graphics.Image;
044:
045: /*
046: * The content provider class is responsible for
047: * providing objects to the view. It can wrap
048: * existing objects in adapters or simply return
049: * objects as-is. These objects may be sensitive
050: * to the current input of the view, or ignore
051: * it and always show the same content
052: * (like Task List, for example).
053: */
054: public class TreeNode implements IAdaptable {
055:
056: /**
057: * Maps the full id of the node to the TreeNode.
058: */
059: private static Map<String, TreeNode> idToNodeMap = new HashMap<String, TreeNode>();
060:
061: private static TreeNode invisibleRoot;
062:
063: private String id;
064: private String label;
065: private Image image = null;
066: private ImageDescriptor imageDescriptor;
067: private TreeNode parent;
068: private String parentId;
069: private int position;
070: private IDynamicTreeNode dynamicTreeNode;
071: protected ArrayList<Object> children = null;
072:
073: private Vector<PageEntry> pageFactories = new Vector<PageEntry>();
074:
075: /**
076: * Initialize the navigation tree nodes.
077: * <P>
078: * The initialization of the navigation nodes depends on
079: * the property sets. Therefore PropertySet.init must be
080: * called before this method.
081: */
082: public static void init() {
083: // Load the extensions
084: IExtensionRegistry registry = Platform.getExtensionRegistry();
085: for (IConfigurationElement element : registry
086: .getConfigurationElementsFor("net.sf.jmoney.pages")) {
087: if (element.getName().equals("node")) {
088:
089: String label = element.getAttribute("label");
090: String icon = element.getAttribute("icon");
091: String id = element.getAttribute("id");
092: String parentNodeId = element.getAttribute("parent");
093: String position = element.getAttribute("position");
094:
095: if (id != null && id.length() != 0) {
096: String fullNodeId = element
097: .getNamespaceIdentifier()
098: + '.' + id;
099: ImageDescriptor descriptor = null;
100: if (icon != null) {
101: // Try getting the image from this plug-in.
102: descriptor = JMoneyPlugin
103: .imageDescriptorFromPlugin(element
104: .getContributor().getName(),
105: icon);
106: if (descriptor == null) {
107: // try getting the image from the JMoney plug-in.
108: descriptor = JMoneyPlugin
109: .imageDescriptorFromPlugin(
110: "net.sf.jmoney", icon);
111: }
112: }
113:
114: int positionNumber = 800;
115: if (position != null) {
116: positionNumber = Integer.parseInt(position);
117: }
118:
119: IDynamicTreeNode dynamicTreeNode = null;
120: try {
121: Object listener = element
122: .createExecutableExtension("class");
123: if (!(listener instanceof IDynamicTreeNode)) {
124: throw new MalformedPluginException(
125: "Plug-in "
126: + element.getContributor()
127: .getName()
128: + " extends the net.sf.jmoney.pages extension point. "
129: + "However, the class specified by the class attribute in the node element "
130: + "("
131: + listener.getClass()
132: .getName()
133: + ") "
134: + "does not implement the IDynamicTreeNode interface. "
135: + "This interface must be implemented by all classes referenced "
136: + "by the class attribute.");
137: }
138:
139: dynamicTreeNode = (IDynamicTreeNode) listener;
140: } catch (CoreException e) {
141: if (e.getStatus().getException() == null) {
142: /*
143: * The most likely situation is that no class is specified.
144: * This is valid because the class attribute is optional.
145: * It this situation we contruct a TreeNode.
146: */
147: } else {
148: if (e.getStatus().getException() instanceof ClassNotFoundException) {
149: ClassNotFoundException e2 = (ClassNotFoundException) e
150: .getStatus().getException();
151: throw new MalformedPluginException(
152: "Plug-in "
153: + element
154: .getContributor()
155: .getName()
156: + " extends the net.sf.jmoney.pages extension point. "
157: + "However, the class specified by the class attribute in the node element "
158: + "("
159: + e2.getMessage()
160: + ") "
161: + "could not be found. "
162: + "The class attribute must specify a class that implements the "
163: + "IDynamicTreeNode interface.");
164: }
165: e.printStackTrace();
166: continue;
167: }
168: }
169:
170: TreeNode node = new TreeNode(fullNodeId, label,
171: descriptor, parentNodeId, positionNumber,
172: dynamicTreeNode);
173: idToNodeMap.put(fullNodeId, node);
174: }
175: }
176: }
177:
178: // Set each node's parent. If no node exists
179: // with the given parent node id then the node
180: // is placed at the root.
181:
182: invisibleRoot = new TreeNode("root", "", null, "", 0, null);
183:
184: for (TreeNode treeNode : idToNodeMap.values()) {
185: TreeNode parentNode;
186: if (treeNode.getParentId() != null) {
187: parentNode = idToNodeMap.get(treeNode.getParentId());
188: if (parentNode == null) {
189: parentNode = invisibleRoot;
190: }
191: } else {
192: parentNode = invisibleRoot;
193: }
194: treeNode.setParent(parentNode);
195: parentNode.addChild(treeNode);
196: }
197:
198: // Set the list of pages for each node.
199: for (IConfigurationElement element : registry
200: .getConfigurationElementsFor("net.sf.jmoney.pages")) {
201: if (element.getName().equals("pages")) {
202: // TODO: remove plug-in as bad if the id is not unique.
203: String id = element.getAttribute("id");
204: String pageId = element.getNamespaceIdentifier() + '.'
205: + id;
206: String nodeId = element.getAttribute("node");
207:
208: String position = element.getAttribute("position");
209: int pos = 5;
210: if (position != null) {
211: pos = Integer.parseInt(position);
212: }
213:
214: if (nodeId != null && nodeId.length() != 0) {
215: TreeNode node = idToNodeMap.get(nodeId);
216: if (node != null) {
217: node.addPage(pageId, element, pos);
218: } else {
219: // No node found with given id, so the
220: // page listener is dropped.
221: // TODO Log missing node.
222: }
223: } else {
224: // No 'node' attribute so see if we have
225: // an 'extendable-property-set' attribute.
226: // (This means the page should be supplied if
227: // the node represents an object that contains
228: // the given property set).
229: String propertySetId = element
230: .getAttribute("extendable-property-set");
231: if (propertySetId != null) {
232: try {
233: ExtendablePropertySet<?> pagePropertySet = PropertySet
234: .getExtendablePropertySet(propertySetId);
235: PageEntry pageEntry = new PageEntry(pageId,
236: element, pos);
237:
238: for (ExtendablePropertySet<?> derivedPropertySet : pagePropertySet
239: .getDerivedPropertySets()) {
240: derivedPropertySet.addPage(pageEntry);
241: }
242: } catch (PropertySetNotFoundException e1) {
243: // This is a plug-in error.
244: // TODO implement properly.
245: e1.printStackTrace();
246: }
247: }
248: }
249: }
250: }
251:
252: // If a node has no child nodes and no page listeners
253: // then the node is removed. This allows nodes to be
254: // created by the framework or the more general plug-ins
255: // that have no functionality provided by the plug-in that
256: // created the node but that can be extended by other
257: // plug-ins. By doing this, rather than expecting plug-ins
258: // to create their own nodes, it is more likely that
259: // different plug-in developers will share nodes, and
260: // thus avoiding hundreds of root nodes in the navigation
261: // tree, each with a single tab view.
262:
263: // TODO: implement this
264: }
265:
266: public static TreeNode getInvisibleRoot() {
267: return invisibleRoot;
268: }
269:
270: /**
271: * @param nodeId the full id of a node
272: * @return the node, or null if no node with the given id exists
273: */
274: public static TreeNode getTreeNode(String nodeId) {
275: return idToNodeMap.get(nodeId);
276: }
277:
278: public TreeNode(String id, String label,
279: ImageDescriptor imageDescriptor, String parentId,
280: int position, IDynamicTreeNode dynamicTreeNode) {
281: this .id = id;
282: this .label = label;
283: this .imageDescriptor = imageDescriptor;
284: this .parentId = parentId;
285: this .position = position;
286: this .dynamicTreeNode = dynamicTreeNode;
287: }
288:
289: /**
290: * @return
291: */
292: public String getId() {
293: return id;
294: }
295:
296: public String getLabel() {
297: return label;
298: }
299:
300: public TreeNode getParent() {
301: return parent;
302: }
303:
304: int getPosition() {
305: return position;
306: }
307:
308: @Override
309: public String toString() {
310: return getLabel();
311: }
312:
313: public Object getAdapter(Class key) {
314: return null;
315: }
316:
317: public Image getImage() {
318: if (image == null && imageDescriptor != null) {
319: image = imageDescriptor.createImage();
320: }
321: return image;
322: }
323:
324: public void addChild(Object child) {
325: if (children == null) {
326: children = new ArrayList<Object>();
327: }
328: children.add(child);
329: }
330:
331: public void removeChild(Object child) {
332: children.remove(child);
333: }
334:
335: public Object[] getChildren() {
336: if (children == null) {
337: if (dynamicTreeNode == null) {
338: return new Object[0];
339: } else {
340: return dynamicTreeNode.getChildren().toArray();
341: }
342: } else {
343: if (dynamicTreeNode == null) {
344: return children.toArray();
345: } else {
346: ArrayList<Object> combinedChildren = new ArrayList<Object>();
347: combinedChildren.addAll(dynamicTreeNode.getChildren());
348: combinedChildren.addAll(children);
349: return combinedChildren.toArray();
350: }
351: }
352: }
353:
354: public boolean hasChildren() {
355: return (children != null && children.size() > 0)
356: || (dynamicTreeNode != null && dynamicTreeNode
357: .hasChildren());
358: }
359:
360: /**
361: * @return
362: */
363: public Object getParentId() {
364: return parentId;
365: }
366:
367: /**
368: * @param parentNode
369: */
370: public void setParent(TreeNode parent) {
371: this .parent = parent;
372: }
373:
374: /**
375: * @param pageListener
376: */
377: public void addPage(String pageId,
378: IConfigurationElement pageElement, int pos) {
379: PageEntry pageEntry = new PageEntry(pageId, pageElement, pos);
380: pageFactories.add(pageEntry);
381: }
382:
383: /**
384: * @return An array of objects that implement the IBookkeepingPage
385: * interface. The returned value is never null but the Vector may
386: * be empty if there are no pages for this node.
387: */
388: public Vector<PageEntry> getPageFactories() {
389: return pageFactories;
390: }
391: }
|