/* * 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 3 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, see <http://www.gnu.org/licenses/>. */ /* * GenericPropertiesCreator.java * Copyright (C) 2005-2012 University of Waikato, Hamilton, New Zealand * */ package weka.gui; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; import weka.core.ClassDiscovery; import weka.core.ClassDiscovery.StringCompare; import weka.core.Utils; /** * This class can generate the properties object that is normally loaded from * the <code>GenericObjectEditor.props</code> file (= PROPERTY_FILE). It takes * the <code>GenericPropertiesCreator.props</code> file as a template to * determine all the derived classes by checking the classes in the given * packages (a file with the same name in your home directory overrides the * the one in the weka/gui directory/package). <br> * E.g. if we want to have all the subclasses of the <code>Classifier</code> * class then we specify the superclass ("weka.classifiers.Classifier") and the * packages where to look for ("weka.classifiers.bayes" etc.): * * <pre> * * weka.classifiers.Classifier=\ * weka.classifiers.bayes,\ * weka.classifiers.functions,\ * weka.classifiers.lazy,\ * weka.classifiers.meta,\ * weka.classifiers.trees,\ * weka.classifiers.rules * * </pre> * * This creates the same list as stored in the * <code>GenericObjectEditor.props</code> file, but it will also add * additional classes, that are not listed in the static list (e.g. a newly * developed Classifier), but still in the classpath. <br> * <br> * For discovering the subclasses the whole classpath is inspected, which means * that you can have several parallel directories with the same package * structure (e.g. a release directory and a developer directory with additional * classes). <br> * <br> * The dynamic discovery can be turned off via the <code>UseDyanmic</code> * property in the props file (values: true|false). * * @see #CREATOR_FILE * @see #PROPERTY_FILE * @see #USE_DYNAMIC * @see GenericObjectEditor * @see weka.core.ClassDiscovery * @author FracPete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ public class GenericPropertiesCreator { /** whether to output some debug information */ public final static boolean VERBOSE = false; /** name of property whether to use the dynamic approach or the old * GenericObjectEditor.props file */ public final static String USE_DYNAMIC = "UseDynamic"; /** The name of the properties file to use as a template. Contains the * packages in which to look for derived classes. It has the same structure * as the <code>PROPERTY_FILE</code> * @see #PROPERTY_FILE */ protected static String CREATOR_FILE = "weka/gui/GenericPropertiesCreator.props"; /** The name of the properties file that lists classes/interfaces/superclasses * to exclude from being shown in the GUI. See the file for more information. */ protected static String EXCLUDE_FILE = "weka/gui/GenericPropertiesCreator.excludes"; /** the prefix for an interface exclusion */ protected static String EXCLUDE_INTERFACE = "I"; /** the prefix for an (exact) class exclusion */ protected static String EXCLUDE_CLASS = "C"; /** the prefix for a superclass exclusion */ protected static String EXCLUDE_SUPERCLASS = "S"; /** The name of the properties file for the static GenericObjectEditor * (<code>USE_DYNAMIC</code> = <code>false</code>) * @see GenericObjectEditor * @see #USE_DYNAMIC */ protected static String PROPERTY_FILE = "weka/gui/GenericObjectEditor.props"; /** the input file with the packages */ protected String m_InputFilename; /** the output props file for the GenericObjectEditor */ protected String m_OutputFilename; /** the "template" properties file with the layout and the packages */ protected Properties m_InputProperties; /** the output properties file with the filled in classes */ protected Properties m_OutputProperties; /** Globally available properties */ protected static GenericPropertiesCreator GLOBAL_CREATOR; protected static Properties GLOBAL_INPUT_PROPERTIES; protected static Properties GLOBAL_OUTPUT_PROPERTIES; /** whether an explicit input file was given - if false, the Utils class * is used to locate the props-file */ protected boolean m_ExplicitPropsFile; /** the hashtable that stores the excludes: * key -> Hashtable(prefix -> Vector of classnames) */ protected Hashtable m_Excludes; static { try { GenericPropertiesCreator creator = new GenericPropertiesCreator(); GLOBAL_CREATOR = creator; if (creator.useDynamic()) { creator.execute(false, true); GLOBAL_INPUT_PROPERTIES = creator.getInputProperties(); GLOBAL_OUTPUT_PROPERTIES = creator.getOutputProperties(); } else { // Read the static information from the GenericObjectEditor.props GLOBAL_OUTPUT_PROPERTIES = Utils.readProperties("weka/gui/GenericObjectEditor.props"); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Get the global output properties * * @return the global output properties */ public static Properties getGlobalOutputProperties() { return GLOBAL_OUTPUT_PROPERTIES; } /** * Get the global input properties * * @return the global input properties */ public static Properties getGlobalInputProperties() { return GLOBAL_INPUT_PROPERTIES; } /** * Regenerate the global output properties. Does not load the * input properties, instead uses the GLOBAL_INPUT_PROPERTIES */ public static void regenerateGlobalOutputProperties() { if (GLOBAL_CREATOR != null) { try { GLOBAL_CREATOR.execute(false, false); GLOBAL_OUTPUT_PROPERTIES = GLOBAL_CREATOR.getOutputProperties(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * initializes the creator, locates the props file with the Utils class. * * @throws Exception if loading of CREATOR_FILE fails * @see #CREATOR_FILE * @see Utils#readProperties(String) * @see #loadInputProperties() */ public GenericPropertiesCreator() throws Exception { this(CREATOR_FILE); m_ExplicitPropsFile = false; } /** * initializes the creator, the given file overrides the props-file search * of the Utils class * * @param filename the file containing the packages to create a props file from * @throws Exception if loading of the file fails * @see #CREATOR_FILE * @see Utils#readProperties(String) * @see #loadInputProperties() */ public GenericPropertiesCreator(String filename) throws Exception { super(); m_InputFilename = filename; m_OutputFilename = PROPERTY_FILE; m_InputProperties = null; m_OutputProperties = null; m_ExplicitPropsFile = true; m_Excludes = new Hashtable(); } /** * if FALSE, the locating of a props-file of the Utils-class is used, * otherwise it's tried to load the specified file * * @param value if true the specified file will be loaded not via the * Utils-class * @see Utils#readProperties(String) * @see #loadInputProperties() */ public void setExplicitPropsFile(boolean value) { m_ExplicitPropsFile = value; } /** * returns TRUE, if a file is loaded and not the Utils class used for * locating the props file. * * @return true if the specified file is used and not the one found by * the Utils class * @see Utils#readProperties(String) * @see #loadInputProperties() */ public boolean getExplicitPropsFile() { return m_ExplicitPropsFile; } /** * returns the name of the output file * * @return the name of the output file */ public String getOutputFilename() { return m_OutputFilename; } /** * sets the file to output the properties for the GEO to * * @param filename the filename for the output */ public void setOutputFilename(String filename) { m_OutputFilename = filename; } /** * returns the name of the input file * * @return the name of the input file */ public String getInputFilename() { return m_InputFilename; } /** * sets the file to get the information about the packages from. * automatically sets explicitPropsFile to TRUE. * * @param filename the filename for the input * @see #setExplicitPropsFile(boolean) */ public void setInputFilename(String filename) { m_InputFilename = filename; setExplicitPropsFile(true); } /** * returns the input properties object (template containing the packages) * * @return the input properties (the template) */ public Properties getInputProperties() { return m_InputProperties; } /** * returns the output properties object (structure like the template, but * filled with classes instead of packages) * * @return the output properties (filled with classes) */ public Properties getOutputProperties() { return m_OutputProperties; } /** * loads the property file containing the layout and the packages of * the output-property-file. The exlcude property file is also read here. * * @see #m_InputProperties * @see #m_InputFilename */ protected void loadInputProperties() { if (VERBOSE) System.out.println("Loading '" + getInputFilename() + "'..."); m_InputProperties = new Properties(); try { File f = new File(getInputFilename()); if (getExplicitPropsFile() && f.exists()) m_InputProperties.load(new FileInputStream(getInputFilename())); else m_InputProperties = Utils.readProperties(getInputFilename()); // excludes m_Excludes.clear(); Properties p = Utils.readProperties(EXCLUDE_FILE); Enumeration enm = p.propertyNames(); while (enm.hasMoreElements()) { String name = enm.nextElement().toString(); // new Hashtable for key Hashtable t = new Hashtable(); m_Excludes.put(name, t); t.put(EXCLUDE_INTERFACE, new Vector()); t.put(EXCLUDE_CLASS, new Vector()); t.put(EXCLUDE_SUPERCLASS, new Vector()); // process entries StringTokenizer tok = new StringTokenizer(p.getProperty(name), ","); while (tok.hasMoreTokens()) { String item = tok.nextToken(); // get list Vector list = new Vector(); if (item.startsWith(EXCLUDE_INTERFACE + ":")) list = (Vector) t.get(EXCLUDE_INTERFACE); else if (item.startsWith(EXCLUDE_CLASS + ":")) list = (Vector) t.get(EXCLUDE_CLASS); else if (item.startsWith(EXCLUDE_SUPERCLASS)) list = (Vector) t.get(EXCLUDE_SUPERCLASS); // add to list list.add(item.substring(item.indexOf(":") + 1)); } } } catch (Exception e) { e.printStackTrace(); } } /** * gets whether the dynamic approach should be used or not * * @return true if the dynamic approach is to be used */ public boolean useDynamic() { if (getInputProperties() == null) loadInputProperties(); // check our classloader against the system one - if different then // return false (as dynamic classloading only works for classes discoverable // in the system classpath /*if (!ClassLoader.getSystemClassLoader().equals(this.getClass().getClassLoader())) { if (Boolean.parseBoolean(getInputProperties().getProperty(USE_DYNAMIC, "true")) == true) { System.out.println("[GenericPropertiesCreator] classloader in use is not the system " + "classloader: using static entries in weka/gui/GenericObjectEditor.props rather " + "than dynamic class discovery."); } return false; }*/ return Boolean.parseBoolean( getInputProperties().getProperty(USE_DYNAMIC, "true")); } /** * checks whether the classname is a valid one, i.e., from a public class * * @param classname the classname to check * @return whether the classname is a valid one */ protected boolean isValidClassname(String classname) { return (classname.indexOf("$") == -1); } /** * Checks whether the classname is a valid one for the given key. This is * based on the settings in the Exclude file. * * @param key the property key * @param classname the classname to check * @return whether the classname is a valid one * @see #EXCLUDE_FILE */ protected boolean isValidClassname(String key, String classname) { boolean result; Class cls; Class clsCurrent; Vector list; int i; result = true; // are there excludes for this key? if (m_Excludes.containsKey(key)) { try { clsCurrent = Class.forName(classname); } catch (Exception e) { // we ignore this Exception clsCurrent = null; } // interface if ((clsCurrent != null) && result) { list = (Vector) ((Hashtable) m_Excludes.get(key)).get(EXCLUDE_INTERFACE); for (i = 0; i < list.size(); i++) { try { cls = Class.forName(list.get(i).toString()); if (ClassDiscovery.hasInterface(cls, clsCurrent)) { result = false; break; } } catch (Exception e) { // we ignore this Exception } } } // superclass if ((clsCurrent != null) && result) { list = (Vector) ((Hashtable) m_Excludes.get(key)).get(EXCLUDE_SUPERCLASS); for (i = 0; i < list.size(); i++) { try { cls = Class.forName(list.get(i).toString()); if (ClassDiscovery.isSubclass(cls, clsCurrent)) { result = false; break; } } catch (Exception e) { // we ignore this Exception } } } // class if ((clsCurrent != null) && result) { list = (Vector) ((Hashtable) m_Excludes.get(key)).get(EXCLUDE_CLASS); for (i = 0; i < list.size(); i++) { try { cls = Class.forName(list.get(i).toString()); if (cls.getName().equals(clsCurrent.getName())) result = false; } catch (Exception e) { // we ignore this Exception } } } } return result; } /** * fills in all the classes (based on the packages in the input properties * file) into the output properties file * * @throws Exception if something goes wrong * @see #m_OutputProperties */ protected void generateOutputProperties() throws Exception { Enumeration keys; String key; String value; String pkg; StringTokenizer tok; Vector classes; HashSet names; int i; m_OutputProperties = new Properties(); keys = m_InputProperties.propertyNames(); while (keys.hasMoreElements()) { key = keys.nextElement().toString(); if (key.equals(USE_DYNAMIC)) continue; tok = new StringTokenizer(m_InputProperties.getProperty(key), ","); names = new HashSet(); // get classes for all packages while (tok.hasMoreTokens()) { pkg = tok.nextToken().trim(); try { classes = ClassDiscovery.find(Class.forName(key), pkg); } catch (Exception e) { System.out.println("Problem with '" + key + "': " + e); classes = new Vector(); } for (i = 0; i < classes.size(); i++) { // skip non-public classes if (!isValidClassname(classes.get(i).toString())) continue; // some classes should not be listed for some keys if (!isValidClassname(key, classes.get(i).toString())) continue; names.add(classes.get(i)); } } // generate list value = ""; classes = new Vector(); classes.addAll(names); Collections.sort(classes, new StringCompare()); for (i = 0; i < classes.size(); i++) { if (!value.equals("")) value += ","; value += classes.get(i).toString(); } if (VERBOSE) System.out.println(pkg + " -> " + value); // set value m_OutputProperties.setProperty(key, value); } } /** * stores the generated output properties file * * @throws Exception if the saving fails * @see #m_OutputProperties * @see #m_OutputFilename */ protected void storeOutputProperties() throws Exception { if (VERBOSE) System.out.println("Saving '" + getOutputFilename() + "'..."); m_OutputProperties.store( new FileOutputStream(getOutputFilename()), " Customises the list of options given by the GenericObjectEditor\n# for various superclasses."); } /** * generates the props-file for the GenericObjectEditor and stores it * * @throws Exception if something goes wrong * @see #execute(boolean) */ public void execute() throws Exception { execute(true, true); } /** * generates the props-file for the GenericObjectEditor * * @param store true if the generated props should be stored * @throws Exception */ public void execute(boolean store) throws Exception { execute(store, true); } /** * generates the props-file for the GenericObjectEditor and stores it only * if the the param <code>store</code> is TRUE. If it is FALSE then the * generated properties file can be retrieved via the <code>getOutputProperties</code> * method. * * @param store if TRUE then the properties file is stored to the stored * filename * @param loadInputProps true if the input properties should be loaded * @throws Exception if something goes wrong * @see #getOutputFilename() * @see #setOutputFilename(String) * @see #getOutputProperties() */ public void execute(boolean store, boolean loadInputProps) throws Exception { // read properties file if (loadInputProps) { loadInputProperties(); } // generate the props file generateOutputProperties(); // write properties file if (store) storeOutputProperties(); } /** * for generating props file: * <ul> * <li> * no parameter: * see default constructor * </li> * <li> * 1 parameter (i.e., filename): * see default constructor + setOutputFilename(String) * </li> * <li> * 2 parameters (i.e, filenames): * see constructor with String argument + setOutputFilename(String) * </li> * </ul> * * @param args the commandline arguments * @throws Exception if something goes wrong * @see #GenericPropertiesCreator() * @see #GenericPropertiesCreator(String) * @see #setOutputFilename(String) */ public static void main(String[] args) throws Exception { GenericPropertiesCreator c = null; if (args.length == 0) { c = new GenericPropertiesCreator(); } else if (args.length == 1) { c = new GenericPropertiesCreator(); c.setOutputFilename(args[0]); } else if (args.length == 2) { c = new GenericPropertiesCreator(args[0]); c.setOutputFilename(args[1]); } else { System.out.println("usage: " + GenericPropertiesCreator.class.getName() + " [<input.props>] [<output.props>]"); System.exit(1); } c.execute(true); } }