001: package org.jivesoftware.util;
002:
003: import org.dom4j.Element;
004: import org.dom4j.Node;
005:
006: import java.util.*;
007:
008: /**
009: * <p>We use a simple
010: * naming convention of meta-data key names: data is stored
011: * heirarchically separated by dots. The last name may contain
012: * a colon ':' character that is read as name:attribute.
013: * For example setting X.Y.Z to someValue, would map to an XML snippet of:</p>
014: * <pre>
015: * <X>
016: * <Y>
017: * <Z>someValue</Z>
018: * </Y>
019: * </X>
020: * </pre>
021: * And X.Y.Z:key to anotherValue as:</p>
022: * <pre>
023: * <X>
024: * <Y>
025: * <Z key="anotherValue" />
026: * </Y>
027: * </X>
028: * </pre>
029: * <p>Some XML cannot be built or accessed using this naming
030: * convention (e.g. a typical Roster reset packet). More complex XML
031: * packet should be represented using the XMPPDOMFragment. The
032: * Element class is designed to provide 80% of XML
033: * manipulation capabilities with the simplest 20% of code and API size
034: * making it convenient for meta-data, simple IQ packets, etc.</p>
035: */
036: public class ElementUtil {
037:
038: private ElementUtil() {
039: }
040:
041: /**
042: * Returns the value of the specified property. A <tt>null</tt> answer does not necessarily mean
043: * that the property does not exist.
044: *
045: * @param name the name of the property to get.
046: * @return the value of the specified property.
047: */
048: public static String getProperty(Element element, String name) {
049: String value = null;
050: String[] propName = parsePropertyName(name);
051:
052: // Grab the attribute if there is one
053: String lastName = propName[propName.length - 1];
054: String attName = null;
055: int attributeIndex = lastName.indexOf(':');
056: if (attributeIndex >= 0) {
057: propName[propName.length - 1] = lastName.substring(0,
058: attributeIndex);
059: attName = lastName.substring(attributeIndex + 1);
060: }
061:
062: // Search for this property by traversing down the XML hierarchy.
063: int i = propName[0].equals(element.getName()) ? 1 : 0;
064: for (; i < propName.length; i++) {
065: element = element.element(propName[i]);
066: if (element == null) {
067: break;
068: }
069: }
070: if (element != null) {
071: if (attName == null) {
072: value = element.getTextTrim();
073: } else {
074: value = element.attributeValue(attName);
075: }
076: }
077:
078: return value;
079: }
080:
081: /**
082: * Returns true if the specified property is included in the XML hierarchy. A property could
083: * have a value associated or not. If the property has an associated value then
084: *
085: * @param name the name of the property to find out.
086: * @return true if the specified property is included in the XML hierarchy.
087: */
088: public static boolean includesProperty(Element element, String name) {
089: String[] propName = parsePropertyName(name);
090:
091: // Grab the attribute if there is one
092: String lastName = propName[propName.length - 1];
093: String attName = null;
094: int attributeIndex = lastName.indexOf(':');
095: if (attributeIndex >= 0) {
096: propName[propName.length - 1] = lastName.substring(0,
097: attributeIndex);
098: attName = lastName.substring(attributeIndex + 1);
099: }
100:
101: // Search for this property by traversing down the XML hierarchy.
102: int i = propName[0].equals(element.getName()) ? 1 : 0;
103: for (; i < propName.length; i++) {
104: element = element.element(propName[i]);
105: if (element == null) {
106: break;
107: }
108: }
109:
110: if (element != null) {
111: if (attName == null) {
112: // The property exists so return true
113: return true;
114: } else {
115: // The property exists if the attribute exists in the element
116: return element.attribute(attName) != null;
117: }
118: } else {
119: // The property does not exist so return false
120: return false;
121: }
122: }
123:
124: /**
125: * Return all values who's path matches the given property name as a String array,
126: * or an empty array if the if there are no children. You MAY NOT use the atttribute
127: * markup (using a ':' in the last element name) with this call.
128: * <p/>
129: * getProperties() allows you to retrieve several values with the same property name.
130: * For example, consider the XML file entry:
131: * <pre>
132: * <foo>
133: * <bar>
134: * <prop>some value</prop>
135: * <prop>other value</prop>
136: * <prop>last value</prop>
137: * </bar>
138: * </foo>
139: * </pre>
140: * If you call getProperties("foo.bar.prop") will return a string array containing
141: * {"some value", "other value", "last value"}.
142: *
143: * @param name the name of the property to retrieve
144: * @return all child property values for the given node name.
145: */
146: public String[] getProperties(Element element, String name) {
147: String[] propName = parsePropertyName(name);
148:
149: // Search for this property by traversing down the XML heirarchy, stopping one short.
150: int i = propName[0].equals(element.getName()) ? 1 : 0;
151: for (; i < propName.length - 1; i++) {
152: element = element.element(propName[i]);
153: if (element == null) {
154: // This node doesn't match this part of the property name which
155: // indicates this property doesn't exist so return empty array.
156: return new String[] {};
157: }
158: }
159: // We found matching property, return names of children.
160: Iterator iter = element
161: .elementIterator(propName[propName.length - 1]);
162: ArrayList props = new ArrayList();
163: while (iter.hasNext()) {
164: Element e = (Element) iter.next();
165: props.add(e.getName());
166: }
167: String[] childrenNames = new String[props.size()];
168: return (String[]) props.toArray(childrenNames);
169: }
170:
171: /**
172: * Sets a property to an array of values. You MAY NOT use the atttribute
173: * markup (using a ':' in the last element name) with this call. Multiple values matching the
174: * same property is mapped to an XML file as multiple elements containing each value.
175: * For example, using the name "foo.bar.prop", and the value string array containing
176: * {"some value", "other value", "last value"} would produce the following XML:
177: * <pre>
178: * <foo>
179: * <bar>
180: * <prop>some value</prop>
181: * <prop>other value</prop>
182: * <prop>last value</prop>
183: * </bar>
184: * </foo>
185: * </pre>
186: *
187: * @param name the name of the property.
188: * @param values The array of values for the property (can be empty but not null)
189: */
190: public static void setProperties(Element element, String name,
191: String[] values) {
192: String[] propName = parsePropertyName(name);
193: setProperty(element, name, values[0]);
194:
195: // Search for this property by traversing down the XML heirarchy, stopping one short.
196: int i = propName[0].equals(element.getName()) ? 1 : 0;
197: for (; i < propName.length - 1; i++) {
198: element = element.element(propName[i]);
199: if (element == null) {
200: // This node doesn't match this part of the property name which
201: // indicates this property doesn't exist so return empty array.
202: return;
203: }
204: }
205: String childName = propName[propName.length - 1];
206: // We found matching property, clear all children.
207: Iterator iter = element.elementIterator(childName);
208: while (iter.hasNext()) {
209: ((Node) iter.next()).detach();
210: }
211: for (int j = 0; i < values.length; i++) {
212: if (values[j] != null) {
213: element.addElement(childName).setText(values[j]);
214: }
215: }
216: }
217:
218: /**
219: * Return all children property names of a parent property as a String array,
220: * or an empty array if the if there are no children. You MAY NOT use the atttribute
221: * markup (using a ':' in the last element name) with this call.
222: * For example, given the properties <tt>X.Y.A</tt>, <tt>X.Y.B</tt>, and <tt>X.Y.C</tt>, then
223: * the child properties of <tt>X.Y</tt> are <tt>A</tt>, <tt>B</tt>, and
224: * <tt>C</tt>.
225: *
226: * @param parent the name of the parent property.
227: * @return all child property values for the given parent.
228: */
229: public static String[] getChildrenProperties(Element element,
230: String parent) {
231: String[] propName = parsePropertyName(parent);
232:
233: // Search for this property by traversing down the XML heirarchy.
234: int i = propName[0].equals(element.getName()) ? 1 : 0;
235: for (; i < propName.length; i++) {
236: element = element.element(propName[i]);
237: if (element == null) {
238: // This node doesn't match this part of the property name which
239: // indicates this property doesn't exist so return empty array.
240: return new String[] {};
241: }
242: }
243: // We found matching property, return names of children.
244: List children = element.elements();
245: int childCount = children.size();
246: String[] childrenNames = new String[childCount];
247: for (int j = 0; i < childCount; i++) {
248: childrenNames[j] = ((Element) children.get(j)).getName();
249: }
250: return childrenNames;
251: }
252:
253: /**
254: * Returns all recursive children of the given parent property or an empty string array
255: * if no children exist. The list of children is depth-first so the array is optimized
256: * for easy displaying.
257: *
258: * @param parent the parent property.
259: * @return all recursive children of the given property in depth-first order or an empty
260: * string array if no children exist.
261: */
262: public static String[] getRecursiveChildrenProperties(
263: Element element, String parent) {
264: String[] properties = getChildrenProperties(element, parent);
265: if (properties.length == 0) {
266: return properties;
267: } else {
268: List list = new ArrayList(15);
269: for (int i = 0; i < properties.length; i++) {
270: String propName = parent + "." + properties[i];
271: list.add(propName);
272: list.addAll(Arrays
273: .asList(getRecursiveChildrenProperties(element,
274: propName)));
275: }
276: return (String[]) list.toArray(new String[] {});
277: }
278: }
279:
280: /**
281: * Sets the value of the specified property. If the property doesn't
282: * currently exist, it will be automatically created.
283: *
284: * @param name the name of the property to set.
285: * @param value the new value for the property.
286: */
287: public static void setProperty(Element element, String name,
288: String value) {
289: if (name == null || name.length() == 0)
290: return;
291: if (value == null)
292: value = "";
293:
294: String[] propName = parsePropertyName(name);
295:
296: // Search for this property by traversing down the XML heirarchy.
297: int i = propName[0].equals(element.getName()) ? 1 : 0;
298: for (; i < propName.length - 1; i++) {
299: // If we don't find this part of the property in the XML heirarchy
300: // we add it as a new node
301: if (element.element(propName[i]) == null) {
302: element.addElement(propName[i]);
303: }
304: element = element.element(propName[i]);
305: }
306: String lastName = propName[propName.length - 1];
307: int attributeIndex = lastName.indexOf(':');
308: if (attributeIndex >= 0) {
309: String eleName = lastName.substring(0, attributeIndex);
310: String attName = lastName.substring(attributeIndex + 1);
311: // If we don't find this part of the property in the XML heirarchy
312: // we add it as a new node
313: if (element.element(eleName) == null) {
314: element.addElement(eleName);
315: }
316: element.element(eleName).addAttribute(attName, value);
317: } else {
318: // If we don't find this part of the property in the XML heirarchy
319: // we add it as a new node
320: if (element.element(lastName) == null) {
321: element.addElement(lastName);
322: }
323: // Set the value of the property in this node.
324: element.element(lastName).setText(value);
325: }
326: }
327:
328: /**
329: * <p>Deletes the specified property.</p>
330: * <p>You MAY NOT use the atttribute
331: * markup (using a ':' in the last element name) with this call.
332: * deleteProperty() removes both the containing text, and the element itself along with
333: * any attributes associated with that element.</p>
334: *
335: * @param name the property to delete.
336: */
337: public static void deleteProperty(Element element, String name) {
338: // Remove property from cache.
339: String[] propName = parsePropertyName(name);
340:
341: // Search for this property by traversing down the XML heirarchy.
342: for (int i = 0; i < propName.length - 1; i++) {
343: element = element.element(propName[i]);
344: // Can't find the property so return.
345: if (element == null) {
346: return;
347: }
348: }
349: // Found the correct element to remove, so remove it...
350: element.remove(element.element(propName[propName.length - 1]));
351: }
352:
353: /**
354: * Returns an array representation of the given Jive property. Jive
355: * properties are always in the format "prop.name.is.this" which would be
356: * represented as an array of four Strings.
357: *
358: * @param name the name of the Jive property.
359: * @return an array representation of the given Jive property.
360: */
361: private static String[] parsePropertyName(String name) {
362: List propName = new ArrayList(5);
363: // Use a StringTokenizer to tokenize the property name.
364: StringTokenizer tokenizer = new StringTokenizer(name, ".");
365: while (tokenizer.hasMoreTokens()) {
366: propName.add(tokenizer.nextToken());
367: }
368: return (String[]) propName.toArray(new String[propName.size()]);
369: }
370:
371: }
|