Source code: hk/hku/cecid/phoenix/common/util/XMLProperty.java
1 /*
2 * Academic Free License
3 * Version 1.0
4 *
5 * This Academic Free License applies to any software and associated
6 * documentation (the "Software") whose owner (the "Licensor") has placed the
7 * statement "Licensed under the Academic Free License Version 1.0" immediately
8 * after the copyright notice that applies to the Software.
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of the Software (1) to use, copy, modify, merge, publish, perform,
12 * distribute, sublicense, and/or sell copies of the Software, and to permit
13 * persons to whom the Software is furnished to do so, and (2) under patent
14 * claims owned or controlled by the Licensor that are embodied in the Software
15 * as furnished by the Licensor, to make, use, sell and offer for sale the
16 * Software and derivative works thereof, subject to the following conditions:
17 *
18 * - Redistributions of the Software in source code form must retain all
19 * copyright notices in the Software as furnished by the Licensor, this list
20 * of conditions, and the following disclaimers.
21 * - Redistributions of the Software in executable form must reproduce all
22 * copyright notices in the Software as furnished by the Licensor, this list
23 * of conditions, and the following disclaimers in the documentation and/or
24 * other materials provided with the distribution.
25 * - Neither the names of Licensor, nor the names of any contributors to the
26 * Software, nor any of their trademarks or service marks, may be used to
27 * endorse or promote products derived from this Software without express
28 * prior written permission of the Licensor.
29 *
30 * DISCLAIMERS: LICENSOR WARRANTS THAT THE COPYRIGHT IN AND TO THE SOFTWARE IS
31 * OWNED BY THE LICENSOR OR THAT THE SOFTWARE IS DISTRIBUTED BY LICENSOR UNDER
32 * A VALID CURRENT LICENSE. EXCEPT AS EXPRESSLY STATED IN THE IMMEDIATELY
33 * PRECEDING SENTENCE, THE SOFTWARE IS PROVIDED BY THE LICENSOR, CONTRIBUTORS
34 * AND COPYRIGHT OWNERS "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
37 * LICENSOR, CONTRIBUTORS OR COPYRIGHT OWNERS BE LIABLE FOR ANY CLAIM, DAMAGES
38 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
39 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE.
40 *
41 * This license is Copyright (C) 2002 Lawrence E. Rosen. All rights reserved.
42 * Permission is hereby granted to copy and distribute this license without
43 * modification. This license may not be modified without the express written
44 * permission of its copyright owner.
45 */
46
47 /* =====
48 *
49 * $Header: /ebxml/staff/cecid/cvs_repository/common/src/hk/hku/cecid/phoenix/common/util/XMLProperty.java,v 1.9 2002/12/13 03:59:03 kcyee Exp $
50 *
51 * Code authored by:
52 *
53 * kcyee [2002-05-28]
54 *
55 * Code reviewed by:
56 *
57 * username [YYYY-MM-DD]
58 *
59 * Remarks:
60 *
61 * =====
62 */
63
64 package hk.hku.cecid.phoenix.common.util;
65
66 import java.io.File;
67 import java.io.FileWriter;
68 import java.io.IOException;
69 import java.util.ArrayList;
70 import java.util.List;
71 import java.util.StringTokenizer;
72 import org.jdom.Document;
73 import org.jdom.Element;
74 import org.jdom.JDOMException;
75 import org.jdom.Text;
76 import org.jdom.input.SAXBuilder;
77 import org.jdom.output.XMLOutputter;
78
79 /**
80 * This is a concrete implementation of the property object for loading
81 * and saving the property content into a XML-based file. The file is
82 * assumed to be rooted by a "Property" element. And the get/set path
83 * is simplified XPath, with "/" as separator. For example, a path of
84 * "A/B/C" and value of "test" creates a tree like:
85 * <br>
86 * <br>
87 * <Property> <br>
88 * <A> <br>
89 * <B> <br>
90 * <C>test</C> <br>
91 * </B> <br>
92 * </A> <br>
93 * </Property> <br>
94 * <br>
95 *
96 * @author kcyee
97 * @version $Revision: 1.9 $
98 */
99 public class XMLProperty extends Property {
100
101 /**
102 * Internal variable for holding the file name of the property file
103 */
104 protected String fileName;
105
106 /**
107 * Internal variable for holding the DOM tree of the XML property object
108 */
109 protected Document doc;
110
111 /**
112 * Internal constructor. Not to be called by user. This loads the
113 * content of the specified property file into memory.
114 *
115 * @param fileName the file name of the property file to be loaded
116 * @throws IOException when error occuring while loading
117 */
118 XMLProperty(String fileName) throws IOException {
119 this.fileName = fileName;
120 SAXBuilder builder = new SAXBuilder();
121 try {
122 doc = builder.build(new File(fileName));
123 }
124 catch (JDOMException e) {
125 throw new IOException("SAXBuilder cannot load '" + fileName
126 + "'.\n" + e.getMessage());
127 }
128 try {
129 if (!doc.getRootElement().getName().equals("Property")) {
130 throw new IOException("Format of '" + fileName
131 + "' not correct: not starting with 'Property'.");
132 }
133 }
134 catch (Exception e) {
135 throw new IOException("Format of '" + fileName
136 + "' not correct.\n" + e.getMessage());
137 }
138 }
139
140 /**
141 * Default constructor. This is for creating a brand new XML-based
142 * property object.
143 */
144 public XMLProperty() {
145 this.fileName = null;
146 Element ele = new Element("Property");
147 doc = new Document(ele);
148 }
149
150 /**
151 * Reloads the property file.
152 */
153 public void reload() {
154 super.reload();
155
156 SAXBuilder builder = new SAXBuilder();
157 try {
158 doc = builder.build(new File(fileName));
159 if (!doc.getRootElement().getName().equals("Property")) {
160 throw new IOException();
161 }
162 }
163 catch (Exception e) {
164 Element ele = new Element("Property");
165 doc = new Document(ele);
166 }
167 }
168
169 /**
170 * Gets the property value given the path (key). Multiple values are
171 * referenced by the same path will be returned using a String array.
172 * Multiple values are searched only at leaf nodes.
173 *
174 * @param path the path (key) of the property
175 * @return the property values given the path (key)
176 */
177 public synchronized String[] getMultiple(String path) {
178 if (path == null) {
179 return null;
180 }
181
182 Element curElement = doc.getRootElement();
183
184 StringTokenizer st = new StringTokenizer(path, "/");
185 while (st.hasMoreElements()) {
186 String token = st.nextToken().trim();
187 if (token.equals("")) {
188 continue;
189 }
190
191 Element nextElement = getChildElement(curElement, token);
192 if (nextElement == null) {
193 return null;
194 }
195 else {
196 if (!st.hasMoreElements()) {
197 // last level
198 List children = curElement.getChildren(token);
199 ArrayList values = new ArrayList();
200 for (int i=0 ; i<children.size() ; i++) {
201 values.add(((Element)children.get(i)).getText());
202 }
203 String [] ret = new String[values.size()];
204 for (int i=0 ; i<values.size() ; i++) {
205 ret[i] = (String) values.get(i);
206 }
207 return ret;
208 }
209 else {
210 curElement = nextElement;
211 }
212 }
213 }
214
215 return null;
216 }
217
218 /**
219 * Gets the property value given the path (key).
220 *
221 * @param path the path (key) of the property
222 * @return the property value given the path (key)
223 */
224 public synchronized String get(String path) {
225 return get(path, null);
226 }
227
228 /**
229 * Gets the property value given the path (key). If the property value
230 * is not found, the default value passed in is returned.
231 *
232 * @param path the path (key) of the property
233 * @param defaultValue the value to be returned if the property value
234 * is not found
235 * @return the property value given the path (key)
236 */
237 public synchronized String get(String path, String defaultValue) {
238
239 if (path == null) {
240 return null;
241 }
242 String ret = getFromCache(path);
243 if (ret == null) {
244 Element curElement = doc.getRootElement();
245
246 StringTokenizer st = new StringTokenizer(path, "/");
247 while (st.hasMoreElements()) {
248 String token = st.nextToken().trim();
249 if (token.equals("")) {
250 continue;
251 }
252
253 Element nextElement = getChildElement(curElement, token);
254
255 if (nextElement == null) {
256 ret = null;
257 break;
258 }
259 else {
260 curElement = nextElement;
261 }
262
263 if (!st.hasMoreElements()) {
264 // last level
265 ret = curElement.getText();
266 if (ret == null) {
267 ret = defaultValue;
268 }
269 }
270 }
271
272 if (ret == null && defaultValue != null) {
273 ret = defaultValue;
274 }
275
276 saveToCache(path, ret);
277 }
278 return ret;
279 }
280
281 /**
282 * Sets the property value of the given path (key).
283 *
284 * @param path the path (key) of the property to be set
285 * @param value the property value to be set
286 */
287 public synchronized void set(String path, String value) {
288
289 saveToCache(path, value);
290
291 Element curElement = doc.getRootElement();
292
293 StringTokenizer st = new StringTokenizer(path, "/");
294 while (st.hasMoreElements()) {
295 String token = st.nextToken().trim();
296 if (token.equals("")) {
297 continue;
298 }
299
300 Element nextElement = curElement.getChild(token);
301 if (nextElement == null) {
302 nextElement = new Element(token);
303 curElement.addContent(nextElement);
304 }
305 curElement = nextElement;
306
307 if (!st.hasMoreElements()) {
308 // last level
309 curElement.setText(value);
310 }
311 }
312 }
313
314 /**
315 * Saves the property object to the same location when loading.
316 *
317 * @throws IOException when error occurs when saving the property object
318 */
319 public synchronized void save() throws IOException {
320 save(fileName);
321 }
322
323 /**
324 * Saves the property object to the specified location.
325 *
326 * @param location the location for saving the property object
327 * @throws IOException when error occurs when saving the property object
328 */
329 public synchronized void save(String fileName) throws IOException {
330 if (fileName == null) {
331 throw new IOException("Cannot save to <null>!");
332 }
333
334 XMLOutputter outputter = new XMLOutputter(" ", true);
335 FileWriter fw = new FileWriter(fileName);
336 outputter.output(doc, fw);
337 fw.close();
338 }
339
340 /**
341 * Gets the child element of the specified element, with the specified
342 * node name. The node name supports getting the nth child by using
343 * node-name[n]. If there is anything wrong when parsing the node name,
344 * we will use abandon the parsing and fall back to assume the whole
345 * specified string as node name.
346 *
347 * @param parent the element to search for children
348 * @param token the node name, may be contains the square bracket to
349 * specify which child to get
350 * @return the child element specified. If there is anything wrong,
351 * null is returned.
352 */
353 protected Element getChildElement(Element parent, String token) {
354 if (token == null || parent == null) {
355 return null;
356 }
357
358 token = token.trim();
359 String origToken = token;
360
361 if (token.endsWith("]")) {
362 token = token.substring(0, token.length()-1);
363 int idx = token.indexOf("[");
364 if (idx > 0) {
365 String idxStr = token.substring(idx+1).trim();
366 token = token.substring(0, idx).trim();
367 try {
368 int elementIdx = Integer.parseInt(idxStr);
369 if (elementIdx >= 0) {
370 List children = parent.getChildren(token);
371 if (elementIdx < children.size()) {
372 return (Element) children.get(elementIdx);
373 }
374 }
375 }
376 catch (NumberFormatException e) {}
377 }
378 }
379 return parent.getChild(origToken);
380 }
381 }