/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.util.java2xml; import com.vividsolutions.jts.util.Assert; import com.vividsolutions.jump.util.LangUtil; import com.vividsolutions.jump.util.StringUtil; import org.jdom.Attribute; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import java.awt.Color; import java.awt.Font; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; //I wrote Java2XML and XML2Java because I couldn't get Betwixt to do Collections. //Java2XML and XML2Java are very easy to setup, are easier to comprehend, and //have better error reporting. [Jon Aquino] public class XMLBinder { private HashMap classToCustomConverterMap = new HashMap(); public XMLBinder() { classToCustomConverterMap.put(Class.class, new CustomConverter() { public Object toJava(String value) { try { return Class.forName(value); } catch (ClassNotFoundException e) { Assert.shouldNeverReachHere(); return null; } } public String toXML(Object object) { return ((Class) object).getName(); } }); classToCustomConverterMap.put(Color.class, new CustomConverter() { public Object toJava(String value) { List parameters = StringUtil.fromCommaDelimitedString(value); return new Color(Integer.parseInt( (String) parameters.get(0)), Integer.parseInt((String) parameters.get(1)), Integer.parseInt((String) parameters.get(2)), Integer.parseInt((String) parameters.get(3))); } public String toXML(Object object) { Color color = (Color) object; ArrayList parameters = new ArrayList(); parameters.add(new Integer(color.getRed())); parameters.add(new Integer(color.getGreen())); parameters.add(new Integer(color.getBlue())); parameters.add(new Integer(color.getAlpha())); return StringUtil.toCommaDelimitedString(parameters); } }); classToCustomConverterMap.put(Font.class, new CustomConverter() { public Object toJava(String value) { List parameters = StringUtil.fromCommaDelimitedString(value); return new Font((String) parameters.get(0), Integer.parseInt((String) parameters.get(1)), Integer.parseInt((String) parameters.get(2))); } public String toXML(Object object) { Font font = (Font) object; ArrayList parameters = new ArrayList(); parameters.add(font.getName()); parameters.add(new Integer(font.getStyle())); parameters.add(new Integer(font.getSize())); return StringUtil.toCommaDelimitedString(parameters); } }); classToCustomConverterMap.put(double.class, new CustomConverter() { public Object toJava(String value) { return new Double(value); } public String toXML(Object object) { return object.toString(); } }); classToCustomConverterMap.put(Double.class, new CustomConverter() { public Object toJava(String value) { return new Double(value); } public String toXML(Object object) { return object.toString(); } }); classToCustomConverterMap.put(int.class, new CustomConverter() { public Object toJava(String value) { return new Integer(value); } public String toXML(Object object) { return object.toString(); } }); classToCustomConverterMap.put(Integer.class, new CustomConverter() { public Object toJava(String value) { return new Integer(value); } public String toXML(Object object) { return object.toString(); } }); //not fixed in original jump classToCustomConverterMap.put(Long.class, new CustomConverter() { public Object toJava(String value) { return new Long(value); } public String toXML(Object object) { return object.toString(); } }); classToCustomConverterMap.put(String.class, new CustomConverter() { public Object toJava(String value) { return value; } public String toXML(Object object) { return object.toString(); } }); classToCustomConverterMap.put(boolean.class, new CustomConverter() { public Object toJava(String value) { return new Boolean(value); } public String toXML(Object object) { return object.toString(); } }); classToCustomConverterMap.put(Boolean.class, new CustomConverter() { public Object toJava(String value) { return new Boolean(value); } public String toXML(Object object) { return object.toString(); } }); classToCustomConverterMap.put(File.class, new CustomConverter() { public Object toJava(String value) { return new File(value); } public String toXML(Object object) { return object.toString(); } }); } private String specFilename(Class c) { return StringUtil.classNameWithoutPackageQualifiers(c.getName()) + ".java2xml"; } protected List specElements(Class c) throws XMLBinderException, JDOMException, IOException { InputStream stream = specResourceStream(c); if (stream == null) { throw new XMLBinderException("Could not find java2xml file for " + c.getName() + " or its interfaces or superclasses"); } try { Element root = new SAXBuilder().build(stream).getRootElement(); if (!root.getAttributes().isEmpty()) { throw new XMLBinderException("Root element of " + specFilename(c) + " should not have attributes"); } if (!root.getName().equals("root")) { throw new XMLBinderException("Root element of " + specFilename(c) + " should be named 'root'"); } return root.getChildren(); } finally { stream.close(); } } private InputStream specResourceStream(Class c) { for (Iterator i = LangUtil.classesAndInterfaces(c).iterator(); i.hasNext();) { Class type = (Class) i.next(); Assert.isTrue(type.isAssignableFrom(c)); InputStream stream = type.getResourceAsStream(specFilename(type)); if (stream != null) { return stream; } } return null; } public void addCustomConverter(Class c, CustomConverter converter) { classToCustomConverterMap.put(c, converter); } /** * @param c for error messages */ protected void visit(List specElements, SpecVisitor visitor, Class c) throws Exception { for (Iterator i = specElements.iterator(); i.hasNext();) { Element specElement = (Element) i.next(); Attribute xmlName = specElement.getAttribute("xml-name"); if (xmlName == null) { throw new XMLBinderException(StringUtil.classNameWithoutPackageQualifiers( c.getName()) + ": Expected 'xml-name' attribute in <" + specElement.getName() + "> but found none"); } Attribute javaName = specElement.getAttribute("java-name"); //javaName is null if tag does nothing other than add a level to the //hierarchy [Jon Aquino] if (specElement.getName().equals("element")) { visitor.tagSpecFound(xmlName.getValue(), (javaName != null) ? javaName.getValue() : null, specElement.getChildren()); } if (specElement.getName().equals("attribute")) { visitor.attributeSpecFound(xmlName.getValue(), javaName.getValue()); } } } public Object toJava(String text, Class c) { return (!text.equals("null")) ? ((CustomConverter) classToCustomConverterMap.get(customConvertableClass( c))).toJava(text) : null; } protected boolean specifyingTypeExplicitly(Class c) throws XMLBinderException { //The int and double classes are abstract. Filter them out. [Jon Aquino] if (hasCustomConverter(c)) { return false; } //In the handling of Maps, c may be the Object class. [Jon Aquino] return (c == Object.class) || Modifier.isAbstract(c.getModifiers()) || c.isInterface(); } protected Class fieldClass(Method setter) { Assert.isTrue(setter.getParameterTypes().length == 1); return setter.getParameterTypes()[0]; } public Method setter(Class c, String field) throws XMLBinderException { Method[] methods = c.getMethods(); //Exact match first [Jon Aquino] for (int i = 0; i < methods.length; i++) { if (!methods[i].getName().toUpperCase().equals("SET" + field.toUpperCase()) && !methods[i].getName().toUpperCase().equals("ADD" + field.toUpperCase())) { continue; } if (methods[i].getParameterTypes().length != 1) { continue; } return methods[i]; } for (int i = 0; i < methods.length; i++) { if (!methods[i].getName().toUpperCase().startsWith("SET" + field.toUpperCase()) && !methods[i].getName().toUpperCase().startsWith("ADD" + field.toUpperCase())) { continue; } if (methods[i].getParameterTypes().length != 1) { continue; } return methods[i]; } throw new XMLBinderException("Could not find setter named like '" + field + "' in class " + c); } protected String toXML(Object object) { return ((CustomConverter) classToCustomConverterMap.get(customConvertableClass( object.getClass()))).toXML(object); } protected boolean hasCustomConverter(Class fieldClass) { return customConvertableClass(fieldClass) != null; } /** * @return null if c doesn't have a custom converter */ private Class customConvertableClass(Class c) { //Use #isAssignableFrom rather than #contains because some classes //may be interfaces. [Jon Aquino] for (Iterator i = classToCustomConverterMap.keySet().iterator(); i.hasNext();) { Class customConvertableClass = (Class) i.next(); if (customConvertableClass.isAssignableFrom(c)) { return customConvertableClass; } } return null; } protected interface SpecVisitor { public void tagSpecFound(String xmlName, String javaName, List specChildElements) throws Exception; public void attributeSpecFound(String xmlName, String javaName) throws Exception; } /** * Sometimes you need to use a CustomConverter rather than a .java2xml * file i.e. when the class is from a third party (e.g. a Swing class) and you * can't add a .java2xml file to the jar. */ public interface CustomConverter { public Object toJava(String value); public String toXML(Object object); } public static class XMLBinderException extends Exception { public XMLBinderException(String message) { super(message); } } }