/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.controls; import java.awt.Color; import java.awt.Dimension; import java.awt.Point; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * This defines the ObjectLoader interface and static methods for managing and * accessing ObjectLoader implementations. * * @author Douglas Brown * @version 1.0 */ public class XML { // static constants @SuppressWarnings("javadoc") public static String NEW_LINE = System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ @SuppressWarnings("javadoc") public static final String CDATA_PRE = "<![CDATA["; //$NON-NLS-1$ @SuppressWarnings("javadoc") public static final String CDATA_POST = "]]>"; //$NON-NLS-1$ @SuppressWarnings("javadoc") public static final int INDENT = 4; // static fields private static Map<Class<?>, ObjectLoader> loaders = new HashMap<Class<?>, ObjectLoader>(); private static ObjectLoader defaultLoader; private static String dtdName; private static String dtd; // the dtd as a string private static String defaultName = "osp10.dtd"; //$NON-NLS-1$ private static ClassLoader classLoader; // added by W. Christian static { try { // system properties may not be readable in some environments NEW_LINE = System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ } catch(SecurityException ex) { /** empty block */ } } /** * Private constructor to prevent instantiation. */ private XML() { /** empty block */ } /** * Sets the ObjectLoader for a specified class. * * @param classtype the class * @param loader the ObjectLoader */ public static void setLoader(Class<?> classtype, XML.ObjectLoader loader) { loaders.put(classtype, loader); } /** * Gets the ObjectLoader for the specified class. * * @param classtype the class * @return the ObjectLoader */ public static XML.ObjectLoader getLoader(Class<?> classtype) { // look for registered loader first ObjectLoader loader = loaders.get(classtype); // if no registered loader, look for static getLoader() method in class if(loader==null) { try { Method method = classtype.getMethod("getLoader", (Class<?>[]) null); //$NON-NLS-1$ if((method!=null)&&Modifier.isStatic(method.getModifiers())) { loader = (ObjectLoader) method.invoke(null, (Object[]) null); if(loader!=null) { // register loader for future calls setLoader(classtype, loader); } } } catch(Exception ex) { /** empty block */ } } // if still no loader found, use the default loader if(loader==null) { if(defaultLoader==null) { defaultLoader = new XMLLoader(); } loader = defaultLoader; } return loader; } /** * Sets the default ObjectLoader. May be set to null. * * @param loader the ObjectLoader */ public static void setDefaultLoader(XML.ObjectLoader loader) { defaultLoader = loader; } /** * Gets the datatype of the object. * * @param obj the object * @return the type */ public static String getDataType(Object obj) { if(obj==null) { return null; } if(obj instanceof String) { return "string"; //$NON-NLS-1$ } else if(obj instanceof Collection<?>) { return "collection"; //$NON-NLS-1$ } else if(obj.getClass().isArray()) { // make sure ultimate component class is acceptable Class<?> componentType = obj.getClass().getComponentType(); while(componentType.isArray()) { componentType = componentType.getComponentType(); } String type = componentType.getName(); if((type.indexOf(".")==-1)&&("intdoubleboolean".indexOf(type)==-1)) { //$NON-NLS-1$ //$NON-NLS-2$ return null; } return "array"; //$NON-NLS-1$ } else if(obj instanceof Double) { return "double"; //$NON-NLS-1$ } else if(obj instanceof Integer) { return "int"; //$NON-NLS-1$ } else { return "object"; //$NON-NLS-1$ } } /** * Gets an array containing all supported data types. * * @return an array of types */ public static String[] getDataTypes() { return new String[] { "object", "array", "collection", "string", "int", "double", "boolean" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ }; } /** * Determines whether the specified string requires CDATA tags. * * @param text the string * @return <code>true</code> if CDATA tags are required */ public static boolean requiresCDATA(String text) { if (text.indexOf("\"")!=-1 //$NON-NLS-1$ || text.indexOf("<")!=-1 //$NON-NLS-1$ || text.indexOf(">")!=-1 //$NON-NLS-1$ || text.indexOf("&")!=-1 //$NON-NLS-1$ || text.indexOf("'")!=-1) { //$NON-NLS-1$ return true; } return false; } /** * Gets the DTD for the specified doctype file name. * * @param doctype the doctype file name (e.g., "osp10.dtd") * @return the DTD as a string */ public static String getDTD(String doctype) { if(dtdName!=doctype) { // set to defaults in case doctype is not found dtdName = defaultName; try { String dtdPath = "/org/opensourcephysics/resources/controls/doctypes/"; //$NON-NLS-1$ java.net.URL url = XML.class.getResource(dtdPath+doctype); if(url==null) { return dtd; } Object content = url.getContent(); if(content instanceof InputStream) { BufferedReader reader = new BufferedReader(new InputStreamReader((InputStream) content)); StringBuffer buffer = new StringBuffer(0); String line; while((line = reader.readLine())!=null) { buffer.append(line+NEW_LINE); } dtd = buffer.toString(); dtdName = doctype; } } catch(IOException ex) { ex.printStackTrace(); } } return dtd; } /** * Sets the ClassLoader. * * @param loader the classLoader */ public static void setClassLoader(ClassLoader loader) { classLoader = loader; } /** * Gets the ClassLoader. May be null. * * @return the classLoader */ public static ClassLoader getClassLoader() { return classLoader; } /** * Replaces backslashes with slashes. * * @param path the path * @return the path with forward slashes */ public static String forwardSlash(String path) { if(path==null) { return ""; //$NON-NLS-1$ } int i = path.indexOf("\\"); //$NON-NLS-1$ while(i!=-1) { path = path.substring(0, i)+"/"+path.substring(i+1); //$NON-NLS-1$ i = path.indexOf("\\"); //$NON-NLS-1$ } return path; } /** * Gets the name from the specified path. * * @param path the full path * @return the name alone */ public static String getName(String path) { if(path==null) { return ""; //$NON-NLS-1$ } // remove path int i = path.lastIndexOf("/"); //$NON-NLS-1$ if(i==-1) { i = path.lastIndexOf("\\"); //$NON-NLS-1$ } if(i!=-1) { return path.substring(i+1); } return path; } /** * Gets the extension of the specified file name. * * @param fileName the file name with or without path * @return the extension, or null if none */ public static String getExtension(String fileName) { if(fileName==null) { return null; } int i = fileName.lastIndexOf('.'); int j = forwardSlash(fileName).lastIndexOf('/'); if(i>0 && i<fileName.length()-1 && i>j) { return fileName.substring(i+1); } return null; } /** * Gets a simple class name for the specified class type. * * @param type the class * @return the simple class name */ public static String getSimpleClassName(Class<?> type) { String name = type.getName(); // trim trailing semicolon, if any int i = name.indexOf(";"); //$NON-NLS-1$ if(i>-1) { name = name.substring(0, i); } // add brackets for arrays while(name.startsWith("[")) { //$NON-NLS-1$ name = name.substring(1); name = name+"[]"; //$NON-NLS-1$ } // eliminate leading package name, if any String ext = XML.getExtension(name); if(ext!=null) { name = ext; } // substitute int for I and double for D arrays i = name.indexOf("["); //$NON-NLS-1$ if(i>-1) { String s = name.substring(0, i); if(s.equals("I")) { //$NON-NLS-1$ s = "int"; //$NON-NLS-1$ } else if(s.equals("D")) { //$NON-NLS-1$ s = "double"; //$NON-NLS-1$ } else if(s.equals("Z")) { //$NON-NLS-1$ s = "boolean"; //$NON-NLS-1$ } name = s+name.substring(i); } return name; } /** * Strips the extension from the specified file name. * * @param fileName the file name with or without path * @return the file name without extension */ public static String stripExtension(String fileName) { if(fileName==null) { return null; } int n = XML.forwardSlash(fileName).lastIndexOf("/"); //$NON-NLS-1$ String name = getName(fileName); int i = name.lastIndexOf('.'); if((i>0)&&(i<name.length()-1)) { name = name.substring(0, i); } fileName = fileName.substring(0, n+1)+name; // strip off any extra dots at end while (fileName.lastIndexOf('.')==fileName.length()-1 && fileName.length()>0) fileName = fileName.substring(0, fileName.length()-1); return fileName; } /** * Gets the path relative to the specified base directory. * * @param absolutePath the absolute path * @param base the absolute base directory path * @return (with forward slashes) the path relative to the base, * or the absolutePath if no relative path is found (eg, different drive) * or the absolutePath if it is really a relative path */ public static String getPathRelativeTo(String absolutePath, String base) { // if no base specified then base is current user directory if((base==null)||base.equals("")) { //$NON-NLS-1$ base = getUserDirectory(); } // make sure both paths use forward slashes absolutePath = forwardSlash(absolutePath); base = forwardSlash(base); // return absolutePath if either path not absolute if(!absolutePath.startsWith("/")&&(absolutePath.indexOf(":")==-1)) { //$NON-NLS-1$ //$NON-NLS-2$ return absolutePath; } if(!base.startsWith("/")&&(base.indexOf(":")==-1)) { //$NON-NLS-1$ //$NON-NLS-2$ return absolutePath; } // look for paths in jar files int jar = absolutePath.indexOf("jar!"); //$NON-NLS-1$ if(jar>-1) { absolutePath = absolutePath.substring(jar+5); return absolutePath; } // construct relative path String relativePath = ""; //$NON-NLS-1$ // search for base up containing hierarchy if(base.endsWith("/")) { //$NON-NLS-1$ base = base.substring(0, base.length()-1); } for(int j = 0; j<6; j++) { // move base up one level each iteration after the first if(j>0) { int k = base.lastIndexOf("/"); //$NON-NLS-1$ if(k!=-1) { base = base.substring(0, k); // doesn't include the slash relativePath += "../"; //$NON-NLS-1$ } else if(!base.equals("")) { //$NON-NLS-1$ base = ""; //$NON-NLS-1$ relativePath += "../"; //$NON-NLS-1$ } else { break; // no more levels } } // construct and return relative path once base is in the path if(!base.equals("")&&absolutePath.startsWith(base)) { //$NON-NLS-1$ String path = absolutePath.substring(base.length()); // eliminate leading slash, if any int k = path.indexOf("/"); //$NON-NLS-1$ if(k==0) { path = path.substring(1); } relativePath += path; return relativePath; } } // relative path not found return absolutePath; } /** * Gets a path relative to the default user directory. * * @param absolutePath the absolute path * @return the relative path, with forward slashes */ public static String getRelativePath(String absolutePath) { return getPathRelativeTo(absolutePath, getUserDirectory()); } /** * Gets the default user directory. * * @return the user directory */ public static String getUserDirectory() { String userDir = System.getProperty("user.dir", "."); //$NON-NLS-1$ //$NON-NLS-2$ return userDir; } /** * Gets the path of the directory containing the specified file. * * @param fileName the full file name, including path * @return the directory path, with forward slashes */ public static String getDirectoryPath(String fileName) { if(fileName==null) { return ""; //$NON-NLS-1$ } fileName = forwardSlash(fileName); int slash = fileName.lastIndexOf("/"); //$NON-NLS-1$ if(slash!=-1) { return fileName.substring(0, slash); } return ""; //$NON-NLS-1$ } /** * Gets the absolute path of the specified file. * * @param file the file * @return the absolute path, with forward slashes */ public static String getAbsolutePath(File file) { if(file==null) { return null; } String path = forwardSlash(file.getAbsolutePath()); int n = path.indexOf("/../"); //$NON-NLS-1$ while (n>-1) { String pre = path.substring(0, n); int m = pre.lastIndexOf("/"); //$NON-NLS-1$ if (m>-1) { String post = path.substring(n+3); path = pre.substring(0, m)+post; n = path.indexOf("/../"); //$NON-NLS-1$ } } n = path.indexOf("/./"); //$NON-NLS-1$ while (n>-1) { path = path.substring(0, n)+path.substring(n+2); n = path.indexOf("/./"); //$NON-NLS-1$ } return path; } /** * Resolves the name of a file specified relative to a base path. * * @param relativePath the relative file name * @param base the absolute base path * @return the resolved file name with forward slashes */ public static String getResolvedPath(String relativePath, String base) { if (base!=null && base.endsWith("/")) //$NON-NLS-1$ base = base.substring(0, base.length()-1); relativePath = forwardSlash(relativePath); // return relativePath if it is really absolute if(relativePath.startsWith("/")||(relativePath.indexOf(":/")!=-1)) { //$NON-NLS-1$ //$NON-NLS-2$ return relativePath; } base = forwardSlash(base); while(relativePath.startsWith("../")&&!base.equals("")) { //$NON-NLS-1$ //$NON-NLS-2$ if(base.indexOf("/")==-1) { //$NON-NLS-1$ base = "/"+base; //$NON-NLS-1$ } relativePath = relativePath.substring(3); base = base.substring(0, base.lastIndexOf("/")); //$NON-NLS-1$ } if (relativePath.startsWith("./")) //$NON-NLS-1$ relativePath = relativePath.substring(2); if (relativePath.equals(".")) //$NON-NLS-1$ relativePath = ""; //$NON-NLS-1$ if(base.equals("")) { //$NON-NLS-1$ return relativePath; } if(base.endsWith("/")) { //$NON-NLS-1$ return base+relativePath; } return base+"/"+relativePath; //$NON-NLS-1$ } /** * Creates any missing folders in the specified path. * * @param path the path to construct */ public static void createFolders(String path) { // work way up path to find existing folder File dir = new File(path); ArrayList<File> dirs = new ArrayList<File>(); // list of needed directories while(!dir.exists()) { dirs.add(0, dir); int j = path.lastIndexOf("/"); //$NON-NLS-1$ if(j==-1) { break; } path = path.substring(0, j); dir = new File(path); } // work back down path and create needed directories Iterator<File> it = dirs.iterator(); while(it.hasNext()) { dir = it.next(); dir.mkdir(); } } // _________________________ ObjectLoader interface _____________________________ /** * This defines methods for moving xml data between an XMLControl and * a corresponding Java object. * * @author Douglas Brown * @version 1.0 */ public interface ObjectLoader { /** * Saves data from an object to an XMLControl. The object must * be castable to the class control.getObjectClass(). * * @param control the xml control * @param obj the object */ public void saveObject(XMLControl control, Object obj); /** * Creates an object from data in an XMLControl. The returned object must * be castable to the class control.getObjectClass(). * * @param control the xml control * @return a new object */ public Object createObject(XMLControl control); /** * Loads an object with data from an XMLControl. The object must * be castable to the class control.getObjectClass(). * * @param control the xml control * @param obj the object * @return the loaded object */ public Object loadObject(XMLControl control, Object obj); } // static initializer defines loaders for commonly used classes static { setLoader(Color.class, new XML.ObjectLoader() { public void saveObject(XMLControl control, Object obj) { java.awt.Color color = (java.awt.Color) obj; control.setValue("red", color.getRed()); //$NON-NLS-1$ control.setValue("green", color.getGreen()); //$NON-NLS-1$ control.setValue("blue", color.getBlue()); //$NON-NLS-1$ control.setValue("alpha", color.getAlpha()); //$NON-NLS-1$ } public Object createObject(XMLControl control) { int r = control.getInt("red"); //$NON-NLS-1$ int g = control.getInt("green"); //$NON-NLS-1$ int b = control.getInt("blue"); //$NON-NLS-1$ int a = control.getInt("alpha"); //$NON-NLS-1$ return new java.awt.Color(r, g, b, a); } public Object loadObject(XMLControl control, Object obj) { int r = control.getInt("red"); //$NON-NLS-1$ int g = control.getInt("green"); //$NON-NLS-1$ int b = control.getInt("blue"); //$NON-NLS-1$ int a = control.getInt("alpha"); //$NON-NLS-1$ return new java.awt.Color(r, g, b, a); } }); setLoader(Double.class, new XML.ObjectLoader() { public void saveObject(XMLControl control, Object obj) { Double dbl = (Double) obj; control.setValue("value", dbl.doubleValue()); //$NON-NLS-1$ } public Object createObject(XMLControl control) { double val = control.getDouble("value"); //$NON-NLS-1$ return new Double(val); } public Object loadObject(XMLControl control, Object obj) { Double dbl = (Double) obj; double val = control.getDouble("value"); //$NON-NLS-1$ if(dbl.doubleValue()==val) { return dbl; } return new Double(val); } }); setLoader(Integer.class, new XML.ObjectLoader() { public void saveObject(XMLControl control, Object obj) { Integer i = (Integer) obj; control.setValue("value", i.intValue()); //$NON-NLS-1$ } public Object createObject(XMLControl control) { int val = control.getInt("value"); //$NON-NLS-1$ return new Integer(val); } public Object loadObject(XMLControl control, Object obj) { Integer i = (Integer) obj; int val = control.getInt("value"); //$NON-NLS-1$ if(i.intValue()==val) { return i; } return new Integer(val); } }); setLoader(Boolean.class, new XML.ObjectLoader() { public void saveObject(XMLControl control, Object obj) { Boolean bool = (Boolean) obj; control.setValue("value", bool.booleanValue()); //$NON-NLS-1$ } public Object createObject(XMLControl control) { boolean val = control.getBoolean("value"); //$NON-NLS-1$ return new Boolean(val); } public Object loadObject(XMLControl control, Object obj) { Boolean bool = (Boolean) obj; boolean val = control.getBoolean("value"); //$NON-NLS-1$ if(bool.booleanValue()==val) { return bool; } return new Boolean(val); } }); setLoader(Dimension.class, new XML.ObjectLoader() { public void saveObject(XMLControl control, Object obj) { Dimension dim = (Dimension) obj; control.setValue("dimensions", new int[] {dim.width, dim.height}); //$NON-NLS-1$ } public Object createObject(XMLControl control) { return new Dimension(); } public Object loadObject(XMLControl control, Object obj) { Dimension dim = (Dimension) obj; int[] dimensions = (int[]) control.getObject("dimensions"); //$NON-NLS-1$ dim.width = dimensions[0]; dim.height = dimensions[1]; return dim; } }); setLoader(Point.class, new XML.ObjectLoader() { public void saveObject(XMLControl control, Object obj) { Point p = (Point) obj; control.setValue("location", new int[] {p.x, p.y}); //$NON-NLS-1$ } public Object createObject(XMLControl control) { return new Point(); } public Object loadObject(XMLControl control, Object obj) { Point p = (Point) obj; int[] location = (int[]) control.getObject("location"); //$NON-NLS-1$ p.x = location[0]; p.y = location[1]; return p; } }); } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */