001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032: package com.vividsolutions.jump.util.java2xml;
033:
034: import com.vividsolutions.jts.util.Assert;
035:
036: import com.vividsolutions.jump.util.LangUtil;
037: import com.vividsolutions.jump.util.StringUtil;
038:
039: import org.jdom.Attribute;
040: import org.jdom.Element;
041: import org.jdom.JDOMException;
042:
043: import org.jdom.input.SAXBuilder;
044:
045: import java.awt.Color;
046: import java.awt.Font;
047:
048: import java.io.File;
049: import java.io.IOException;
050: import java.io.InputStream;
051:
052: import java.lang.reflect.Method;
053: import java.lang.reflect.Modifier;
054:
055: import java.util.ArrayList;
056: import java.util.HashMap;
057: import java.util.Iterator;
058: import java.util.List;
059:
060: //I wrote Java2XML and XML2Java because I couldn't get Betwixt to do Collections.
061: //Java2XML and XML2Java are very easy to setup, are easier to comprehend, and
062: //have better error reporting. [Jon Aquino]
063: public class XMLBinder {
064: private HashMap classToCustomConverterMap = new HashMap();
065:
066: public XMLBinder() {
067: classToCustomConverterMap.put(Class.class,
068: new CustomConverter() {
069: public Object toJava(String value) {
070: try {
071: return Class.forName(value);
072: } catch (ClassNotFoundException e) {
073: Assert.shouldNeverReachHere();
074:
075: return null;
076: }
077: }
078:
079: public String toXML(Object object) {
080: return ((Class) object).getName();
081: }
082: });
083: classToCustomConverterMap.put(Color.class,
084: new CustomConverter() {
085: public Object toJava(String value) {
086: List parameters = StringUtil
087: .fromCommaDelimitedString(value);
088:
089: return new Color(Integer
090: .parseInt((String) parameters.get(0)),
091: Integer.parseInt((String) parameters
092: .get(1)), Integer
093: .parseInt((String) parameters
094: .get(2)), Integer
095: .parseInt((String) parameters
096: .get(3)));
097: }
098:
099: public String toXML(Object object) {
100: Color color = (Color) object;
101: ArrayList parameters = new ArrayList();
102: parameters.add(new Integer(color.getRed()));
103: parameters.add(new Integer(color.getGreen()));
104: parameters.add(new Integer(color.getBlue()));
105: parameters.add(new Integer(color.getAlpha()));
106:
107: return StringUtil
108: .toCommaDelimitedString(parameters);
109: }
110: });
111: classToCustomConverterMap.put(Font.class,
112: new CustomConverter() {
113: public Object toJava(String value) {
114: List parameters = StringUtil
115: .fromCommaDelimitedString(value);
116:
117: return new Font((String) parameters.get(0),
118: Integer.parseInt((String) parameters
119: .get(1)), Integer
120: .parseInt((String) parameters
121: .get(2)));
122: }
123:
124: public String toXML(Object object) {
125: Font font = (Font) object;
126: ArrayList parameters = new ArrayList();
127: parameters.add(font.getName());
128: parameters.add(new Integer(font.getStyle()));
129: parameters.add(new Integer(font.getSize()));
130:
131: return StringUtil
132: .toCommaDelimitedString(parameters);
133: }
134: });
135: classToCustomConverterMap.put(double.class,
136: new CustomConverter() {
137: public Object toJava(String value) {
138: return new Double(value);
139: }
140:
141: public String toXML(Object object) {
142: return object.toString();
143: }
144: });
145: classToCustomConverterMap.put(Double.class,
146: new CustomConverter() {
147: public Object toJava(String value) {
148: return new Double(value);
149: }
150:
151: public String toXML(Object object) {
152: return object.toString();
153: }
154: });
155: classToCustomConverterMap.put(int.class, new CustomConverter() {
156: public Object toJava(String value) {
157: return new Integer(value);
158: }
159:
160: public String toXML(Object object) {
161: return object.toString();
162: }
163: });
164: classToCustomConverterMap.put(Integer.class,
165: new CustomConverter() {
166: public Object toJava(String value) {
167: return new Integer(value);
168: }
169:
170: public String toXML(Object object) {
171: return object.toString();
172: }
173: });
174: //not fixed in original jump
175: classToCustomConverterMap.put(Long.class,
176: new CustomConverter() {
177: public Object toJava(String value) {
178: return new Long(value);
179: }
180:
181: public String toXML(Object object) {
182: return object.toString();
183: }
184: });
185: classToCustomConverterMap.put(String.class,
186: new CustomConverter() {
187: public Object toJava(String value) {
188: return value;
189: }
190:
191: public String toXML(Object object) {
192: return object.toString();
193: }
194: });
195: classToCustomConverterMap.put(boolean.class,
196: new CustomConverter() {
197: public Object toJava(String value) {
198: return new Boolean(value);
199: }
200:
201: public String toXML(Object object) {
202: return object.toString();
203: }
204: });
205: classToCustomConverterMap.put(Boolean.class,
206: new CustomConverter() {
207: public Object toJava(String value) {
208: return new Boolean(value);
209: }
210:
211: public String toXML(Object object) {
212: return object.toString();
213: }
214: });
215: classToCustomConverterMap.put(File.class,
216: new CustomConverter() {
217: public Object toJava(String value) {
218: return new File(value);
219: }
220:
221: public String toXML(Object object) {
222: return object.toString();
223: }
224: });
225: }
226:
227: private String specFilename(Class c) {
228: return StringUtil
229: .classNameWithoutPackageQualifiers(c.getName())
230: + ".java2xml";
231: }
232:
233: protected List specElements(Class c) throws XMLBinderException,
234: JDOMException, IOException {
235: InputStream stream = specResourceStream(c);
236:
237: if (stream == null) {
238: throw new XMLBinderException(
239: "Could not find java2xml file for " + c.getName()
240: + " or its interfaces or superclasses");
241: }
242:
243: try {
244: Element root = new SAXBuilder().build(stream)
245: .getRootElement();
246:
247: if (!root.getAttributes().isEmpty()) {
248: throw new XMLBinderException("Root element of "
249: + specFilename(c)
250: + " should not have attributes");
251: }
252:
253: if (!root.getName().equals("root")) {
254: throw new XMLBinderException("Root element of "
255: + specFilename(c) + " should be named 'root'");
256: }
257:
258: return root.getChildren();
259: } finally {
260: stream.close();
261: }
262: }
263:
264: private InputStream specResourceStream(Class c) {
265: for (Iterator i = LangUtil.classesAndInterfaces(c).iterator(); i
266: .hasNext();) {
267: Class type = (Class) i.next();
268: Assert.isTrue(type.isAssignableFrom(c));
269:
270: InputStream stream = type
271: .getResourceAsStream(specFilename(type));
272:
273: if (stream != null) {
274: return stream;
275: }
276: }
277:
278: return null;
279: }
280:
281: public void addCustomConverter(Class c, CustomConverter converter) {
282: classToCustomConverterMap.put(c, converter);
283: }
284:
285: /**
286: * @param c for error messages
287: */
288: protected void visit(List specElements, SpecVisitor visitor, Class c)
289: throws Exception {
290: for (Iterator i = specElements.iterator(); i.hasNext();) {
291: Element specElement = (Element) i.next();
292: Attribute xmlName = specElement.getAttribute("xml-name");
293:
294: if (xmlName == null) {
295: throw new XMLBinderException(StringUtil
296: .classNameWithoutPackageQualifiers(c.getName())
297: + ": Expected 'xml-name' attribute in <"
298: + specElement.getName() + "> but found none");
299: }
300:
301: Attribute javaName = specElement.getAttribute("java-name");
302:
303: //javaName is null if tag does nothing other than add a level to the
304: //hierarchy [Jon Aquino]
305: if (specElement.getName().equals("element")) {
306: visitor
307: .tagSpecFound(xmlName.getValue(),
308: (javaName != null) ? javaName
309: .getValue() : null, specElement
310: .getChildren());
311: }
312:
313: if (specElement.getName().equals("attribute")) {
314: visitor.attributeSpecFound(xmlName.getValue(), javaName
315: .getValue());
316: }
317: }
318: }
319:
320: public Object toJava(String text, Class c) {
321: return (!text.equals("null")) ? ((CustomConverter) classToCustomConverterMap
322: .get(customConvertableClass(c))).toJava(text)
323: : null;
324: }
325:
326: protected boolean specifyingTypeExplicitly(Class c)
327: throws XMLBinderException {
328: //The int and double classes are abstract. Filter them out. [Jon Aquino]
329: if (hasCustomConverter(c)) {
330: return false;
331: }
332:
333: //In the handling of Maps, c may be the Object class. [Jon Aquino]
334: return (c == Object.class)
335: || Modifier.isAbstract(c.getModifiers())
336: || c.isInterface();
337: }
338:
339: protected Class fieldClass(Method setter) {
340: Assert.isTrue(setter.getParameterTypes().length == 1);
341:
342: return setter.getParameterTypes()[0];
343: }
344:
345: public Method setter(Class c, String field)
346: throws XMLBinderException {
347: Method[] methods = c.getMethods();
348:
349: //Exact match first [Jon Aquino]
350: for (int i = 0; i < methods.length; i++) {
351: if (!methods[i].getName().toUpperCase().equals(
352: "SET" + field.toUpperCase())
353: && !methods[i].getName().toUpperCase().equals(
354: "ADD" + field.toUpperCase())) {
355: continue;
356: }
357:
358: if (methods[i].getParameterTypes().length != 1) {
359: continue;
360: }
361:
362: return methods[i];
363: }
364:
365: for (int i = 0; i < methods.length; i++) {
366: if (!methods[i].getName().toUpperCase().startsWith(
367: "SET" + field.toUpperCase())
368: && !methods[i].getName().toUpperCase().startsWith(
369: "ADD" + field.toUpperCase())) {
370: continue;
371: }
372:
373: if (methods[i].getParameterTypes().length != 1) {
374: continue;
375: }
376:
377: return methods[i];
378: }
379:
380: throw new XMLBinderException(
381: "Could not find setter named like '" + field
382: + "' in class " + c);
383: }
384:
385: protected String toXML(Object object) {
386: return ((CustomConverter) classToCustomConverterMap
387: .get(customConvertableClass(object.getClass())))
388: .toXML(object);
389: }
390:
391: protected boolean hasCustomConverter(Class fieldClass) {
392: return customConvertableClass(fieldClass) != null;
393: }
394:
395: /**
396: * @return null if c doesn't have a custom converter
397: */
398: private Class customConvertableClass(Class c) {
399: //Use #isAssignableFrom rather than #contains because some classes
400: //may be interfaces. [Jon Aquino]
401: for (Iterator i = classToCustomConverterMap.keySet().iterator(); i
402: .hasNext();) {
403: Class customConvertableClass = (Class) i.next();
404:
405: if (customConvertableClass.isAssignableFrom(c)) {
406: return customConvertableClass;
407: }
408: }
409:
410: return null;
411: }
412:
413: protected interface SpecVisitor {
414: public void tagSpecFound(String xmlName, String javaName,
415: List specChildElements) throws Exception;
416:
417: public void attributeSpecFound(String xmlName, String javaName)
418: throws Exception;
419: }
420:
421: /**
422: * Sometimes you need to use a CustomConverter rather than a .java2xml
423: * file i.e. when the class is from a third party (e.g. a Swing class) and you
424: * can't add a .java2xml file to the jar.
425: */
426: public interface CustomConverter {
427: public Object toJava(String value);
428:
429: public String toXML(Object object);
430: }
431:
432: public static class XMLBinderException extends Exception {
433: public XMLBinderException(String message) {
434: super(message);
435: }
436: }
437:
438: }
|