/* * 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/>. */ /* * PropertyPath.java * Copyright (C) 2006-2012 University of Waikato, Hamilton, New Zealand */ package weka.core; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.StringTokenizer; import java.util.Vector; /** * A helper class for accessing properties in nested objects, e.g., accessing * the "getRidge" method of a LinearRegression classifier part of * MultipleClassifierCombiner, e.g., Vote. For doing so, one needs to * supply the object to work on and a property path. The property path is a * dot delimited path of property names ("getFoo()" and "setFoo(int)" have * "foo" as property name), indices of arrays are 0-based. E.g.: <p/> * * <code>getPropertyDescriptor(vote, "classifiers[1].ridge")</code> will return * the second classifier (which should be our LinearRegression) of the given * Vote meta-classifier and there the property descriptor of the "ridge" * property. <code>getValue(...)</code> will return the actual value of the * ridge parameter and <code>setValue(...)</code> will set it. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ public class PropertyPath implements RevisionHandler { /** * Represents a single element of a property path * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ public static class PathElement implements Cloneable, RevisionHandler { /** the property */ protected String m_Name; /** the index of the array (-1 for none) */ protected int m_Index; /** * initializes the path element with the given property * * @param property the property to initialize with */ public PathElement(String property) { super(); if (property.indexOf("[") > -1) { m_Name = property.replaceAll("\\[.*$", ""); m_Index = Integer.parseInt( property.replaceAll(".*\\[", "").replaceAll("\\].*", "")); } else { m_Name = property; m_Index = -1; } } /** * returns a clone of the current object * * @return the clone of the current state */ public Object clone() { return new PathElement(this.toString()); } /** * returns the name of the property * * @return the name of the property */ public String getName() { return m_Name; } /** * returns whether the property is an index-based one * * @return true if the property has an index */ public boolean hasIndex() { return (getIndex() > -1); } /** * returns the index of the property, -1 if the property is not an * index-based one * * @return the index of the property */ public int getIndex() { return m_Index; } /** * returns the element once again as string * * @return the property as string */ public String toString() { String result; result = getName(); if (hasIndex()) result += "[" + getIndex() + "]"; return result; } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } } /** * Contains a (property) path structure * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ public static class Path implements RevisionHandler { /** the structure */ protected Vector<PathElement> m_Elements; /** * default constructor, only used internally */ protected Path() { super(); m_Elements = new Vector<PathElement>(); } /** * uses the given dot-path * * @param path path in dot-notation */ public Path(String path) { this(); m_Elements = breakUp(path); } /** * uses the vector with PathElement objects to initialize with * * @param elements the PathElements to use */ public Path(Vector<PathElement> elements) { this(); for (int i = 0; i < elements.size(); i++) m_Elements.add((PathElement) elements.get(i).clone()); } /** * uses the given array as elements for the path * * @param elements the path elements to use */ public Path(String[] elements) { this(); for (int i = 0; i < elements.length; i++) m_Elements.add(new PathElement(elements[i])); } /** * breaks up the given path and returns it as vector * * @param path the path to break up * @return the single elements of the path */ protected Vector<PathElement> breakUp(String path) { Vector<PathElement> result; StringTokenizer tok; result = new Vector<PathElement>(); tok = new StringTokenizer(path, "."); while (tok.hasMoreTokens()) result.add(new PathElement(tok.nextToken())); return result; } /** * returns the element at the given index * * @param index the index of the element to return * @return the specified element */ public PathElement get(int index) { return (PathElement) m_Elements.get(index); } /** * returns the number of path elements of this structure * * @return the number of path elements */ public int size() { return m_Elements.size(); } /** * returns a path object based on the given path string * * @param path path to work on * @return the path structure */ public static Path parsePath(String path) { return new Path(path); } /** * returns a subpath of the current structure, starting with the specified * element index up to the end * * @param startIndex the first element of the subpath * @return the new subpath */ public Path subpath(int startIndex) { return subpath(startIndex, size()); } /** * returns a subpath of the current structure, starting with the specified * element index up. The endIndex specifies the element that is not part * of the new subpath. In other words, the new path contains the elements * from "startIndex" up to "(endIndex-1)". * * @param startIndex the first element of the subpath * @param endIndex the element that is after the last added element * @return the new subpath */ public Path subpath(int startIndex, int endIndex) { Vector<PathElement> list; int i; list = new Vector<PathElement>(); for (i = startIndex; i < endIndex; i++) list.add(get(i)); return new Path(list); } /** * returns the structure again as a dot-path * * @return the path structure as dot-path */ public String toString() { String result; int i; result = ""; for (i = 0; i < m_Elements.size(); i++) { if (i > 0) result += "."; result += m_Elements.get(i); } return result; } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } } /** * A helper class that stores Object and PropertyDescriptor together. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ */ protected static class PropertyContainer implements RevisionHandler { /** the descriptor */ protected PropertyDescriptor m_Descriptor; /** the associated object */ protected Object m_Object; /** * initializes the container * * @param desc the property descriptor * @param obj the associated object */ public PropertyContainer(PropertyDescriptor desc, Object obj) { super(); m_Descriptor = desc; m_Object = obj; } /** * returns the stored descriptor * * @return the stored descriptor */ public PropertyDescriptor getDescriptor() { return m_Descriptor; } /** * returns the stored object * * @return the stored object */ public Object getObject() { return m_Object; } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } } /** * returns the property and object associated with the given path, null if * a problem occurred. * * @param src the object to start from * @param path the path to follow * @return not null, if the property could be found */ public static PropertyContainer find(Object src, Path path) { PropertyContainer result; PropertyDescriptor desc; Object newSrc; PathElement part; Method method; Object methodResult; // get descriptor part = path.get(0); try { desc = new PropertyDescriptor(part.getName(), src.getClass()); } catch (Exception e) { desc = null; e.printStackTrace(); } // problem occurred? -> stop if (desc == null) return null; // end of path reached? if (path.size() == 1) { result = new PropertyContainer(desc, src); } // recurse further else { try { method = desc.getReadMethod(); methodResult = method.invoke(src, (Object[]) null); if (part.hasIndex()) newSrc = Array.get(methodResult, part.getIndex()); else newSrc = methodResult; result = find(newSrc, path.subpath(1)); } catch (Exception e) { result = null; e.printStackTrace(); } } return result; } /** * returns the property associated with the given path, null if a problem * occurred. * * @param src the object to start from * @param path the path to follow * @return not null, if the property could be found */ public static PropertyDescriptor getPropertyDescriptor(Object src, Path path) { PropertyContainer cont; cont = find(src, path); if (cont == null) return null; else return cont.getDescriptor(); } /** * returns the property associated with the given path * * @param src the object to start from * @param path the path to follow * @return not null, if the property could be found */ public static PropertyDescriptor getPropertyDescriptor(Object src, String path) { return getPropertyDescriptor(src, new Path(path)); } /** * returns the value specified by the given path from the object * * @param src the object to work on * @param path the retrieval path * @return the value, null if an error occurred */ public static Object getValue(Object src, Path path) { Object result; PropertyContainer cont; Method method; Object methodResult; PathElement part; result = null; cont = find(src, path); // problem? if (cont == null) return null; // retrieve the value try { part = path.get(path.size() - 1); method = cont.getDescriptor().getReadMethod(); methodResult = method.invoke(cont.getObject(), (Object[]) null); if (part.hasIndex()) result = Array.get(methodResult, part.getIndex()); else result = methodResult; } catch (Exception e) { result = null; e.printStackTrace(); } return result; } /** * returns the value specified by the given path from the object * * @param src the object to work on * @param path the retrieval path * @return the value, null if an error occurred */ public static Object getValue(Object src, String path) { return getValue(src, new Path(path)); } /** * set the given value specified by the given path in the object * * @param src the object to work on * @param path the retrieval path * @param value the value to set * @return true if the value could be set */ public static boolean setValue(Object src, Path path, Object value) { boolean result; PropertyContainer cont; Method methodRead; Method methodWrite; Object methodResult; PathElement part; result = false; cont = find(src, path); // problem? if (cont == null) return result; // set the value try { part = path.get(path.size() - 1); methodRead = cont.getDescriptor().getReadMethod(); methodWrite = cont.getDescriptor().getWriteMethod(); if (part.hasIndex()) { methodResult = methodRead.invoke(cont.getObject(), (Object[]) null); Array.set(methodResult, part.getIndex(), value); methodWrite.invoke(cont.getObject(), new Object[]{methodResult}); } else { methodWrite.invoke(cont.getObject(), new Object[]{value}); } result = true; } catch (Exception e) { result = false; e.printStackTrace(); } return result; } /** * set the given value specified by the given path in the object * * @param src the object to work on * @param path the retrieval path * @param value the value to set */ public static void setValue(Object src, String path, Object value) { setValue(src, new Path(path), value); } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } /** * for testing only * * @param args the commandline options - ignored * @throws Exception if something goes wrong */ public static void main(String[] args) throws Exception { // Path Path path = new Path("hello.world[2].nothing"); System.out.println("Path: " + path); System.out.println(" -size: " + path.size()); System.out.println(" -elements:"); for (int i = 0; i < path.size(); i++) System.out.println( " " + i + ". " + path.get(i).getName() + " -> " + path.get(i).getIndex()); /* // retrieving ridge with path weka.classifiers.meta.Vote vote = new weka.classifiers.meta.Vote(); vote.setClassifiers( new weka.classifiers.Classifier[]{ new weka.classifiers.trees.J48(), new weka.classifiers.functions.LinearRegression()}); path = new Path("classifiers[1].ridge"); System.out.println("path: " + path + " -> " + getValue(vote, path)); // setting ridge with path and retrieving it again setValue(vote, path.toString(), new Double(0.1)); System.out.println("path: " + path + " -> " + getValue(vote, path)); */ } }