001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library 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 GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.resources;
018:
019: // J2SE dependencies
020: import java.lang.reflect.Constructor;
021: import javax.swing.tree.TreeNode;
022: import javax.swing.tree.TreeModel;
023: import javax.swing.tree.MutableTreeNode;
024: import javax.swing.tree.DefaultTreeModel;
025: import javax.swing.tree.DefaultMutableTreeNode;
026: import org.w3c.dom.NamedNodeMap;
027: import org.w3c.dom.Node;
028:
029: /**
030: * Bridges to optional dependencies (especially {@code widget-swing} module).
031: *
032: * @since 2.0
033: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/resources/OptionalDependencies.java $
034: * @version $Id: OptionalDependencies.java 26518 2007-08-10 17:05:01Z desruisseaux $
035: * @author Martin Desruisseaux
036: */
037: public final class OptionalDependencies {
038: /**
039: * Constructor for {@link org.geotools.gui.swing.tree.NamedTreeNode}.
040: */
041: private static Constructor treeNodeConstructor;
042:
043: /**
044: * Set to {@code true} if {@link #treeNodeConstructor} can't be obtained.
045: */
046: private static boolean noNamedTreeNode = false;
047:
048: /**
049: * Interdit la création d'objets de cette classe.
050: */
051: private OptionalDependencies() {
052: }
053:
054: /**
055: * Creates an initially empty tree node.
056: *
057: * @param name The value to be returned by {@link TreeNode#toString}.
058: * @param object The user object to be returned by the tree node. May
059: * or may not be the same than {@code name}.
060: * @param allowsChildren if children are allowed.
061: */
062: public static DefaultMutableTreeNode createTreeNode(
063: final String name, final Object object,
064: final boolean allowsChildren) {
065: /*
066: * If the "modules/extension/swing-widgets" JAR is in the classpath, then create an
067: * instance of NamedTreeNode (see org.geotools.swing.tree javadoc for an explanation
068: * about why the NamedTreeNode workaround is needed). We use reflection because the
069: * swing-widgets module is optional, so we fallback on the standard Swing object if
070: * we can't create an instance of NamedTreeNode. We will attempt to use reflection
071: * only once in order to avoid a overhead if the swing-widgets module is not available.
072: *
073: * The swing-widgets module contains a "NamedTreeNodeTest" for making sure that the
074: * NamedTreeNode instances are properly created.
075: *
076: * Note: No need to sychronize; this is not a big deal if we make the attempt twice.
077: */
078: if (!noNamedTreeNode)
079: try {
080: if (treeNodeConstructor == null) {
081: treeNodeConstructor = Class
082: .forName(
083: "org.geotools.gui.swing.tree.NamedTreeNode")
084: .getConstructor(
085: new Class[] { String.class,
086: Object.class, Boolean.TYPE });
087: }
088: return (DefaultMutableTreeNode) treeNodeConstructor
089: .newInstance(new Object[] { name, object,
090: Boolean.valueOf(allowsChildren) });
091: } catch (Exception e) {
092: /*
093: * There is a large amount of checked and unchecked exceptions that the above code
094: * may thrown. We catch all of them because a reasonable fallback exists (creation
095: * of the default Swing object below). Note that none of the unchecked exceptions
096: * (IllegalArgumentException, NullPointerException...) should occurs, except maybe
097: * SecurityException. Maybe we could let the unchecked exceptions propagate...
098: */
099: noNamedTreeNode = true;
100: }
101: return new DefaultMutableTreeNode(name, allowsChildren);
102: }
103:
104: /**
105: * Creates a Swing root tree node from a XML root tree node. Together with
106: * {@link #toString(TreeNode)}, this method provides a convenient way to print
107: * the content of a XML document for debugging purpose.
108: * <p>
109: * This method should not be defined here, since this class is about optional dependencies.
110: * It should be defined in {@link org.geotools.gui.swing.tree.Trees} instead. However we put
111: * it here (for now) because it is used in some module that don't want to depend on widgets.
112: */
113: public static MutableTreeNode xmlToSwing(final Node node) {
114: String label = node.getNodeName();
115: final String value = node.getNodeValue();
116: if (value != null) {
117: label += "=\"" + value + '"';
118: }
119: final DefaultMutableTreeNode root = createTreeNode(label, node,
120: true);
121: final NamedNodeMap attributes = node.getAttributes();
122: final int length = attributes.getLength();
123: for (int i = 0; i < length; i++) {
124: final Node attribute = attributes.item(i);
125: if (attribute != null) {
126: label = attribute.getNodeName() + "=\""
127: + attribute.getNodeValue() + '"';
128: root.add(createTreeNode(label, attribute, false));
129: }
130: }
131: for (Node child = node.getFirstChild(); child != null; child = child
132: .getNextSibling()) {
133: root.add(xmlToSwing(child));
134: }
135: return root;
136: }
137:
138: /**
139: * Construit une chaîne de caractères qui contiendra le
140: * noeud spécifié ainsi que tous les noeuds enfants.
141: *
142: * @param model Arborescence à écrire.
143: * @param node Noeud de l'arborescence à écrire.
144: * @param buffer Buffer dans lequel écrire le noeud.
145: * @param level Niveau d'indentation (à partir de 0).
146: * @param last Indique si les niveaux précédents sont en train d'écrire leurs derniers items.
147: * @return Le tableau {@code last}, qui peut éventuellement avoir été agrandit.
148: */
149: private static boolean[] toString(final TreeModel model,
150: final Object node, final StringBuffer buffer,
151: final int level, boolean[] last, final String lineSeparator) {
152: for (int i = 0; i < level; i++) {
153: if (i != level - 1) {
154: buffer.append(last[i] ? '\u00A0' : '\u2502').append(
155: "\u00A0\u00A0\u00A0");
156: } else {
157: buffer.append(last[i] ? '\u2514' : '\u251C').append(
158: "\u2500\u2500\u2500");
159: }
160: }
161: buffer.append(node).append(lineSeparator);
162: if (level >= last.length) {
163: last = XArray.resize(last, level * 2);
164: }
165: final int count = model.getChildCount(node);
166: for (int i = 0; i < count; i++) {
167: last[level] = (i == count - 1);
168: last = toString(model, model.getChild(node, i), buffer,
169: level + 1, last, lineSeparator);
170: }
171: return last;
172: }
173:
174: /**
175: * Returns a graphical representation of the specified tree model. This representation can
176: * be printed to the {@linkplain System#out standard output stream} (for example) if it uses
177: * a monospaced font and supports unicode.
178: *
179: * @param tree The tree to format.
180: * @param root First node to format.
181: * @return A string representation of the tree, or {@code null} if it doesn't contain any node.
182: */
183: private static String toString(final TreeModel tree,
184: final Object root) {
185: if (root == null) {
186: return null;
187: }
188: final StringBuffer buffer = new StringBuffer();
189: toString(tree, root, buffer, 0, new boolean[64], System
190: .getProperty("line.separator", "\n"));
191: return buffer.toString();
192: }
193:
194: /**
195: * Returns a graphical representation of the specified tree model. This representation can
196: * be printed to the {@linkplain System#out standard output stream} (for example) if it uses
197: * a monospaced font and supports unicode.
198: * <p>
199: * This method should not be defined here, since this class is about optional dependencies.
200: * It should be defined in {@link org.geotools.gui.swing.tree.Trees} instead. However we put
201: * it here (for now) because it is used in some module that don't want to depend on widgets.
202: *
203: * @param tree The tree to format.
204: * @return A string representation of the tree, or {@code null} if it doesn't contain any node.
205: */
206: public static String toString(final TreeModel tree) {
207: return toString(tree, tree.getRoot());
208: }
209:
210: /**
211: * Returns a graphical representation of the specified tree. This representation can be
212: * printed to the {@linkplain System#out standard output stream} (for example) if it uses
213: * a monospaced font and supports unicode.
214: * <p>
215: * This method should not be defined here, since this class is about optional dependencies.
216: * It should be defined in {@link org.geotools.gui.swing.tree.Trees} instead. However we put
217: * it here (for now) because it is used in some module that don't want to depend on widgets.
218: *
219: * @param node The root node of the tree to format.
220: * @return A string representation of the tree, or {@code null} if it doesn't contain any node.
221: */
222: public static String toString(final TreeNode node) {
223: return toString(new DefaultTreeModel(node, true));
224: }
225: }
|