/******************************************************************************* * Copyright (c) 2007, 2008 Gregory Jordan * * This file is part of PhyloWidget. * * PhyloWidget 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. * * PhyloWidget 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 * PhyloWidget. If not, see <http://www.gnu.org/licenses/>. */ package org.andrewberman.ui.menu; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Enumeration; import java.util.Stack; import org.andrewberman.ui.tools.Tool; import processing.core.PApplet; import processing.xml.XMLElement; public class MenuIO { PApplet app; Object[] actionObjects; ClassLoader cl; public MenuIO() { String menuPackage = Menu.class.getPackage().getName(); menuPackages.add(menuPackage); } /** * * @param p * @param filename * @param actionHolder * (optional) the object which will contain the action methods * for this menu set. * @return */ public ArrayList<MenuItem> loadFromXML(Reader in, PApplet p, Object... actionHolders) { ArrayList<MenuItem> menus = new ArrayList<MenuItem>(); app = p; cl = p.getClass().getClassLoader(); actionObjects = actionHolders; // InputStream in = p.openStream(filename); /* * Search depth-first through the XML tree, adding the highest-level * menu elements we can find. */ Stack s = new Stack(); try { XMLElement x = new XMLElement(in); s.push(x); while (!s.isEmpty()) { XMLElement curEl = (XMLElement) s.pop(); if (curEl.getName().equalsIgnoreCase("menu")) { // If curEl is a menu, parse it and add it to the ArrayList. menus.add(processElement(null, curEl)); } else { // If not, keep going through the XML tree and search for // more <menu> elements. Enumeration en = curEl.enumerateChildren(); while (en.hasMoreElements()) { s.push(en.nextElement()); } } } } catch (Exception e) { e.printStackTrace(); } return menus; } public MenuItem processElement(MenuItem parent, XMLElement el) { // long t = System.currentTimeMillis(); MenuItem newItem = null; String elName = el.getName(); String itemName = el.getStringAttribute("name"); el.removeAttribute("name"); if (el.hasAttribute("type")) { /* * If this element has the "type" attribute, then we use that to * create a new Menu or MenuItem from scratch. */ String type = el.getStringAttribute("type"); el.removeAttribute("type"); newItem = createMenu(type); // Set this Menu's name. if (itemName != null) newItem.setName(itemName); else newItem.setName(""); } /* * If this is any other element (I expect it to be <item>), then * let's make sure it has a parent Menu or MenuItem: */ if (parent == null && !elName.equalsIgnoreCase("menu")) throw new RuntimeException("[MenuIO] XML menu parsing error on " + elName + " element: <item> requires a parent <menu> or <item>!"); if (elName.equalsIgnoreCase("item") || elName.equalsIgnoreCase("menu")) { /* * If all is well, then we use the parent item's add() method to * create this new Item element. */ if (newItem != null && parent != null) newItem = parent.add(newItem); else if (parent != null) newItem = parent.add(itemName); } else if (elName.equalsIgnoreCase("methodcall")) { String mName = el.getStringAttribute("method"); String p = el.getStringAttribute("param"); el.removeAttribute("method"); el.removeAttribute("param"); try { Method m = parent.getClass().getMethod(mName, new Class[] { String.class }); m.invoke(parent, new Object[] { p }); } catch (Exception e) { e.printStackTrace(); } } /* * At this point, we have a good "newItem" MenuItem. Now we need to * populate its attributes using a bean-like Reflection scheme. Every */ Enumeration attrs = el.enumerateAttributeNames(); while (attrs.hasMoreElements()) { String attr = (String) attrs.nextElement(); setAttribute(newItem, attr, el.getStringAttribute(attr)); } // long curT = System.currentTimeMillis(); // long dt = curT - t; // System.out.println((dt / 1000f) + " " + itemName); /* * Now, keep the recursion going: go through the current XMLElement's * children and call menuElement() on each one. */ XMLElement[] els = el.getChildren(); for (int i = 0; i < els.length; i++) { XMLElement child = els[i]; processElement(newItem, child); } return newItem; } protected ArrayList<String> menuPackages = new ArrayList<String>(); // protected static final String menuPackage = Menu.class.getPackage().getName(); // protected static final String toolPackage = Tool.class.getPackage().getName(); /** * Uses Reflection to create a Menu of the given class type. * * @param classType * The desired Menu class to create, either as a simple class * name (if the class resides within the base Menu package) or as * the fully-qualified Class name (i.e. * org.something.SomethingElse). * @return */ protected MenuItem createMenu(String classType) { /* * We need to give the complete package name of the desired Class, so we * need to assume that the desired class resides within the base Menu * package. */ Class c = null; if (classType.indexOf('.') != -1) { try { c = cl.loadClass(classType); } catch (ClassNotFoundException e) { // e.printStackTrace(); } } if (c == null) { for (String menuPackage : menuPackages) { // System.out.println("H"); String fullClass = menuPackage + "." + classType; try { c = cl.loadClass(fullClass); // c = Class.forName(fullClass); // System.out.println(fullClass); // c = getClass().getClassLoader().loadClass(fullClass); // c = Class.forName(fullClass, false,app.getClass().getClas); // app.getClass().getClassLoader().loadClass(fullClass); break; } catch (Exception e) { // e.printStackTrace(); continue; } } } // // /* // * If using the predefined package names didn't work, try loading as if we were given the full class name. // */ // if (c == null) // { // try // { // c = Class.forName(classType); // } catch (java.lang.ClassNotFoundException e2) // { // e2.printStackTrace(); // } // } Constructor construct; try { construct = c.getConstructor(new Class[] { PApplet.class }); Object newMenu = construct.newInstance(new Object[] { app }); return (MenuItem) newMenu; } catch (Exception e) { // e.printStackTrace(); try { construct = c.getConstructor(new Class[] {}); Object newMenu = construct.newInstance(new Object[] {}); return (MenuItem) newMenu; } catch (Exception e2) { e.printStackTrace(); e2.printStackTrace(); return null; } } } /** * Use reflection to call the setXXX function for the given attribute and * value. * * All setXXX methods should just take a String argument, except for the * defined exceptions. * * @param item * @param attr * @param value */ protected void setAttribute(MenuItem item, String attr, String value) { String attrL = attr.toLowerCase(); String upperFirst = "set" + upperFirst(attr); /* * Special case: setAction(Object o, String s) * setProperty(Object o, String s) * setMethodCall(Object o, String s) * setCondition(Object o, String s) */ if (attrL.matches("(action|property|methodcall|condition)")) { setWithObjectRef(item, attr, value); return; } Class c = item.getClass(); try { /* * Start with the bread-and-butter, setXXX(String s) method call. */ Method m = c.getMethod(upperFirst, String.class); m.invoke(item, value); } catch (Exception e) { try { /* * Ok, that didn't work... let's parse to a float. */ Method m = c.getMethod(upperFirst, Float.TYPE); m.invoke(item, Float.parseFloat(value)); } catch (Exception e2) { try { /* * Ok, let's parse to a boolean. */ Method m = c.getMethod(upperFirst, Boolean.TYPE); m.invoke(item, Boolean.parseBoolean(value)); } catch (Exception e3) { /* * Dammit, nothing worked. Let's let loose on the console. */ // e.printStackTrace(); // e2.printStackTrace(); // e3.printStackTrace(); } } } } protected void setWithObjectRef(MenuItem item, String attr, String value) { String methodS = "set" + upperFirst(attr); Class c = item.getClass(); try { Method m = c.getMethod(methodS, Object.class, String.class); for (Object ao : actionObjects) { try { // System.out.println(item + " " + ao + " " + value); m.invoke(item, ao, value); break; } catch (Exception e) { // e.printStackTrace(); continue; } } } catch (Exception e) { // e.printStackTrace(); } } public static String upperFirst(String s) { String upper = s.substring(0, 1).toUpperCase(); String orig = s.substring(1, s.length()); return upper + orig; } }