You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by jv...@locus.apache.org on 2000/08/30 12:51:26 UTC

cvs commit: jakarta-velocity/src/java/org/apache/velocity/introspection ArrayIterator.java Constant.java EnumIterator.java Log.java Macro.java Named.java PropertyException.java PropertyMethod.java PropertyOperator.java PropertyReference.java RefMap.java

jvanzyl     00/08/30 03:51:25

  Added:       src/java/org/apache/velocity/introspection
                        ArrayIterator.java Constant.java EnumIterator.java
                        Log.java Macro.java Named.java
                        PropertyException.java PropertyMethod.java
                        PropertyOperator.java PropertyReference.java
                        RefMap.java
  Log:
  - started playing with the WM introspection engine. I removed
    some references to make the build work. Hopefully later on today
    Bob and I can get this puppy running!
  
  Revision  Changes    Path
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/ArrayIterator.java
  
  Index: ArrayIterator.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  import java.util.*;
  
  
  /**
    * This provides an iterator interface to an array
    */
  final public class ArrayIterator implements Iterator
  {
     final Object[] a;
     int pos;
  
     /**
       * Construct an iterator given an enumeration
       */
     public ArrayIterator(Object[] array) 
     {
        this.a = array;
        pos = 0;
     }
  
     /**
       * Return true if we have not yet reached the end of the enumeration
       */
     final public boolean hasNext() 
     {
        return (pos < a.length);
     }
  
     /**
       * Advance the iterator and return the next value. Return null if we
       * reach the end of the enumeration.
       */
     final public Object next() throws NoSuchElementException
     {
        if (pos < a.length) {
           return a[pos++];
        } else {
           throw new NoSuchElementException("Advanced beyond end of array");
        }
     }
  
     /**
       * Unsupported 
       */
     final public void remove() throws UnsupportedOperationException
     {
        throw new UnsupportedOperationException();
     }
  
     /**
       * Test harness
       */
     static public void main(String arg[]) {
  
        try {
           Iterator i = new ArrayIterator(arg);
           while (i.hasNext()) {
              System.out.println("item: " + i.next());
           }
        } catch (Exception e) {
           e.printStackTrace();
        }
     }
  }
  
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/Constant.java
  
  Index: Constant.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  
  /**
    * This class allows you to use an object to represent a constant. The
    * default implementation allows the object to have both a String 
    * and an integer representation. All subclasses must have both 
    * String and integer representations as well--the integer value 
    * is used to order the constants.
    * <p>
    * Constants are immutable, and usually declared final.
    */
  final public class Constant
  {
  
     private String name;
     private int order;
  
     public Constant(String name, int ord)
     {
        this.name = name;
        this.order = ord;
     }
  
     /**
       * Return the declared String name for this constant
       */
     final public String toString() {
        return name;
     }
  
     /**
       * Same as toString()
       */
     final public String getName() {
        return name;
     }
  
     /**
       * Return the integer (order) value of this constant
       */
     final public int getOrder() {
        return order;
     }
  
  }
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/EnumIterator.java
  
  Index: EnumIterator.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  
  import java.util.*;
  
  
  /**
    * Allow a Java 1.1 enumeration to be used as a JDK 1.2 style Iterator
    */
  final public class EnumIterator implements Iterator
  {
     final Enumeration enum;
     private boolean hasNext;
  
     /**
       * Construct an iterator given an enumeration
       */
     public EnumIterator(Enumeration e) 
     {
        enum = e;
        hasNext = e.hasMoreElements();
     }
  
     /**
       * Return true if we have not yet reached the end of the enumeration
       */
     final public boolean hasNext() 
     {
        return hasNext;   
     }
  
     /**
       * Advance the iterator and return the next value. Return null if we
       * reach the end of the enumeration.
       */
     final public Object next() throws NoSuchElementException
     {
        if (!hasNext) {
           throw new NoSuchElementException("advanced past end of list");
        }
        Object o = enum.nextElement();
        hasNext = enum.hasMoreElements();
        return o;
     }
  
     /**
       * Unsupported 
       */
     final public void remove() throws UnsupportedOperationException
     {
        throw new UnsupportedOperationException();
     }
  
     /**
       * Test harness
       */
     static public void main(String arg[]) {
        java.util.Vector v = new java.util.Vector(arg.length);
        for (int i = 0; i < arg.length; i++) {
           v.addElement(arg[i]);
        }
  
        try {
           Iterator i = new EnumIterator(v.elements());
           while (i.hasNext()) {
              System.out.println("item: " + i.next());
           }
        } catch (Exception e) {
           e.printStackTrace();
        }
     }
  }
  
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/Log.java
  
  Index: Log.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  
  import java.io.*;
  import java.util.*;
  import java.text.*;
  
  /**
    * The Semiotek Log interface is intended to make logging as simple as 
    * possible in order to encourage its use throughout the system. Log
    * messages are categorized two ways : what type/level they are, 
    * and what part of the system they come from. The type of the log 
    * level ranges from "error" (most severe) to "debug" (least severe). 
    * The part of the system is represented by the "class" of the log 
    * message--this is an arbitrary string.
    * <P>
    * The Log class is divided into static methods which are used to control
    * the overall behavior of the logging system, and instance methods which
    * are used to submit log messages to the log.
    * <P>
    * The expected way to use this class is to create a Log object in a 
    * package called "log" and then call it from various points in that 
    * package to generate log messages. The "log" object should not be 
    * visible beyond the scope of its intended callers.
    *
    */
  final public class Log
  {
  
     // static portion relates to overall control & log behavior
  
  
     // STATIC VARIABLES
  
  
     /**
       * Use this variable in your code to decide whether or not to include 
       * debug() log messages. A good technique is to use a local variable in
       * your class like this:
       *<blockquote>
       *    final boolean debug = false && Log.debug
       *</blockquote>
       * @see #debug()
       */
     public static final boolean debug = true;
  
     /**
       * All the current log object names and types
       */
     final private static Vector logsRegistered = new Vector();
  
     /**
       * The log types that are selected for output. null means all.
       */
     private static Hashtable typesEnabled = null;
  
     /**
       * setLevel(Log.NONE) turns off all log messages
       */
     public static final Constant NONE     = new Constant("NONE",0);
  
     /**
       * use with setLevel() to turn off all log messages except ERROR
       * @see #error()
       */
     public static final Constant ERROR     = new Constant("ERROR",1);
  
     /**
       * use with setLevel() to turn off all log messages except ERROR/WARNING
       * @see #warning()
       */
     public static final Constant WARNING   = new Constant("WARNING",2);
  
     /**
       * use with setLevel() to turn off messages other than ERROR/WARNING/INFO
       * @see #info()
       */
     public static final Constant INFO      = new Constant("INFO",3);
  
     /**
       * use with setLevel() to turn off all log messages except 
       * ERROR/WARNING/INFO/EXCEPTION
       * @see #exception()
       */
     public static final Constant EXCEPTION = new Constant("EXCEPTION",4);
  
     /**
       * use with setLevel() to turn off all log messages except 
       * ERROR/WARNING/INFO/EXCEPTION/DEBUG
       * @see #debug()
       */
     public static final Constant DEBUG     = new Constant("DEBUG",5);
  
     /**
       * use with setLevel() to turn on all log messages
       */
     public static final Constant ALL       = new Constant("ALL",6);
  
     /**
       * Used to flag which types are enabled
       */
     private static final String TYPE_ENABLED = "ENABLED";
  
     /**
       * system independent line separator
       */
     private final static String NEWLINE = System.getProperty("line.separator");
  
     /**
       * Current level of log messages being printed, defaults to INFO
       */
     static private int myLevel = INFO.getOrder();
  
     /**
       * Current target for the log
       */
     static private PrintWriter myTarget = new PrintWriter(System.out,true);
  
     /**
       * Whether or not we should be stack tracing exceptions
       */
     static private boolean iTraceExceptions = false;
  
     /**
       * How timestamps in the log are written out
       */
     static private DateFormat dateFmt 
        = DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT);
  
  
     // STATIC METHODS
  
  
    /**
      * Get the log constant that corresponds to the supplied integer. If 
      * the integer is out of range the closest constant will be returned.
      */
     static final public Constant getConstant(int i) {
        Constant ret;
        switch(i) {
           case 0: ret = NONE; break;
           case 1: ret = ERROR; break;
           case 2: ret = WARNING; break;
           case 3: ret = INFO; break;
           case 4: ret = EXCEPTION; break;
           case 5: ret = DEBUG; break;
           case 6: ret = ALL; break;
           default: 
                   if (i < 0) {
                      ret = NONE;
                   } else {
                      ret = ALL;
                   } 
                   break;
        }
        return ret;
     }
  
     /**
       * Find the constant that matches the supplied string by name. Case 
       * is ignored. If the supplied name does not match any constants, then
       * the constant ALL is returned.
       */
     static final public Constant getConstant(String name) {
        Constant ret;
        if (name.equalsIgnoreCase(NONE.getName())) { ret = NONE; } 
        else if (name.equalsIgnoreCase(ERROR.getName())) { ret = ERROR; }
        else if (name.equalsIgnoreCase(WARNING.getName())) { ret = WARNING; }
        else if (name.equalsIgnoreCase(INFO.getName())) { ret = INFO; }
        else if (name.equalsIgnoreCase(EXCEPTION.getName())) { ret = EXCEPTION; }
        else if (name.equalsIgnoreCase(DEBUG.getName())) { ret = DEBUG; }
        else { ret = ALL; }
  
        return ret;
     }
  
     /**
       * Log level: what messages to we print out?
       * @param logLevelConstant use one of the log constants defined above
       */
     static final public void setLevel(Constant logLevel) {
        myLevel = logLevel.getOrder();
        myTarget.println("*** LOG LEVEL SET TO: " + logLevel);
     }
  
     /**
       * Log target: where do we write log messages?
       * @param target the PrintWriter you want to send log messages to
       */
     static final public void setTarget(PrintWriter target) { 
        if (target == null) {
           target = new PrintWriter(new OutputStreamWriter(System.err));
        }
        myTarget = target;
        myTarget.println("*** BEGIN: " + dateFmt.format(new Date()) + "***");
     }
  
  
     /**
       * Log target: where do we write log messages?
       * @param target the OutputStream you want to send log messages to
       */
     static final public void setTarget(OutputStream target) { 
        setTarget( (target == null) ?
           null : new PrintWriter(new OutputStreamWriter(target)));
     }
  
     /**
       * Log target: set the log to be this file
       * @param logfile the name of a file you want to log messages to
       * @throws IOException if the file could not be opened
       */
     static final public void setTarget(String logfile) throws IOException
     {
        PrintWriter out;
        if (logfile != null) {
           out = new PrintWriter(new FileWriter(logfile,true));
        } else {
           out = new PrintWriter(new OutputStreamWriter(System.err));
        }
        setTarget(out);
        out.flush();
     }
  
     /**
       * Log includes exception stacktraces
       * @param trace true if you want exception stacktraces, false otherwise
       */
     static final public void traceExceptions(boolean trace) {
        iTraceExceptions = trace;
     }
  
  
     /**
       * Private utility method for writing the log (do not add newline)
       */
     static final private void write(String level, String type, Object message)
     {
  
        if ((typesEnabled != null) && typesEnabled.get(type) != TYPE_ENABLED) {
           return;
        }
  
        try {
           myTarget.print(dateFmt.format(new Date())
                           + "\t" + type 
                           + "\t" + level 
                           + "\t" + message); 
           myTarget.flush();
        } catch (java.lang.Exception e) {
           System.err.println("** COULD NOT WRITE LOG! SWITCHING TO STDERR **");
           System.err.println(dateFmt.format(new Date()) 
                 + "\t" + level + "\t" + message);
           System.err.flush();
           setTarget(new PrintWriter(System.err));
        }
     }
  
     /**
       * Private utility method for writing the log (add newline)
       */
     static final private void writeln(String level, String type, Object message)
     {
        write(level,type,message + NEWLINE);
     }
  
  
     /**
       * Return an enumeration containing a list of the registered log types
       */
     static final public Enumeration getTypes() {
        return logsRegistered.elements();
     }
  
     /**
       * Return an enumeration of the logs types that are currently enabled.
       */
     static final public Enumeration getTypesEnabled() {
        if (typesEnabled == null) {
           return logsRegistered.elements();
        } else {
           return typesEnabled.keys();
        }
     }
  
     /**
       * Allow printing of only the supplied log types
       */
     static final public void enableTypes(String[] types) 
     {
        disableAllTypes();
        for (int i = 0; i < types.length; i++) {
           enableType(types[i]);
        }
     }
  
     /**
       * Allow printing of the supplied type. If all types are currently
       * allowed (via enableAllTypes) then this does nothing; if all types
       * are currently disallowed (via disableAllTypes) then this re-enables
       * just this one type. You can call it repeatedly to re-enable 
       * several different types.
       */
     static public final void enableType(String type) {
        Hashtable types = typesEnabled;
        if (typesEnabled == null) {
           return; // all enabled already
        } else {
           types.put(type,TYPE_ENABLED);
        }
     }
  
     /**
       * Allow printing of all types. 
       */
     static public final void enableAllTypes() {
        typesEnabled = null;
     }
  
     /**
       * Disallow printing of all types. You probably want to call 
       * enableType() a few times after this.
       */
     static public final void disableAllTypes() {
        typesEnabled = new Hashtable();
     }
  
  
  
     // INSTANCE VARIABLES
  
  
     /**
       * The type name for this log type, should be fairly short
       */
     private String logType;
  
     /**
       * A description of what this log type is for
       */
     private String logDescr;
  
  
     // INSTANCE METHODS
  
  
     /**
       * Create a new log in the specified class. It is recomended that
       * the type paramter be a word of seven or fewer characters in order
       * to produce a consistent looking log.
       * <P>
       * @param type a single word representing package/category 
       * @description a brief explanation of what this category means
       */
     public Log(String type, String description)
     {
        this.logType = type.intern(); // allow "==" tests for equality
        this.logDescr = description;
        logsRegistered.addElement(this);
     }
  
     /**
       * Use to write a log message that indicates a programming error
       * or a system level misconfiguration: for example, unable to 
       * locate the config file, etc. Error should not be used
       * to indicate an error in data being processed, but rather an 
       * error condition that is the fault of the program itself.
       */
     final  public void error(Object logMessage) {
        if (myLevel >= ERROR.getOrder()) { 
           writeln("ERROR", logType, logMessage);
        }
     }
  
     /**
       * Use to write a log message that indicats suspicious but 
       * non-fatal behavior in the program; or else which represents a 
       * fatal error that is the fault of data passed to the program. 
       */
     final  public void warning(Object logMessage) {
        if (myLevel >= WARNING.getOrder()) { 
           writeln("WARN", logType, logMessage);
        }
     }
  
     /**
       * Write a log message that simply informs of some interesting 
       * events, such as program start up or shut down. 
       */
     final  public void info(Object logMessage) {
        if (myLevel >= INFO.getOrder()) { 
           writeln("INFO", logType, logMessage);
        }
     }
  
     /**
       * Write a log message that indicates an exceptional condition 
       * has occurred. It is normal to pass an actual exception in as 
       * the object to be logged--if the object passed is actually an 
       * exception and stack tracing is enabled, this message will then
       * be able to generate the appropraite stack trace for the Log.
       */
     final public void exception(Object logMessage) {
        if (myLevel < EXCEPTION.getOrder()) {
           return;
        } 
  
        if (iTraceExceptions && logMessage instanceof Exception) {
           Exception e = (Exception) logMessage;
           write("EXCPT", logType, "");
           e.printStackTrace(myTarget); 
           myTarget.flush();
        }  else {
           writeln("EXCPT", logType, logMessage);
        }
     }
  
     /**
       * Use to write debugging information to the log. This is information
       * that would normally only be of interest to someone trying to 
       * discover the actual behavior of the program at runtime. 
       * <p>
       * It is normal to wrap a Log.debug() like this:
       * <blockquote><pre>
       * if (Log.debug) { log.debug("debug msg"); } 
       * </pre></blockquote>
       * The "debug" boolean in the Log class can then be toggled on or 
       * off in the source, thereby allowing debug statements to be optimized
       * out of the source code at compile time. 
       */
     final  public void debug(Object logMessage) {
        if (myLevel >= DEBUG.getOrder()) { 
           writeln("DEBUG", logType, logMessage);
        }
     }
  
  
     // TEST HARNESS
  
  
     /**
       * Test harness: opens "log" in the current directory
       */
     public static void main(String arg[]) {
  
        try {
           System.out.println("Logging to \"log\" in the current directory.");
           Log.setTarget("log"); 
           Log log = new Log("testing", "just used for testing");
           for (int i = NONE.getOrder(); i <= ALL.getOrder(); i++) {
              log.setLevel(getConstant(i));
              log.error("Testing error");
              log.warning("This is a warning");
              log.info("This is informative.");
              log.exception("An exception");
              log.debug("Debug junk");
           }
        } catch (java.lang.Exception e) {
           e.printStackTrace();
        }
  
  
     }
  
  }
  
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/Macro.java
  
  Index: Macro.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  
  import java.util.*;
  import java.io.*;
  
  import org.apache.velocity.Context;
  
  /**
    * Directives, variables, macro calls, blocks, conditions, text, etc., all 
    * have this as their supertype.
    */
  public interface Macro extends PropertyReference
  {
  
     /**
       * Interpret the directive and write it out, using the values in
       * the supplied context as appropriate.
       * <p>
       * @exception ContextException if required data was missing from context
       * @exception IOException if we could not successfully write to out
       */
     
     //public void write(FastWriter out, Context context) 
     //   throws ContextException, IOException;
  
     /**
       * same as out but returns a String
       * <p>
       * @exception ContextException if required data was missing from context
       */
     public Object evaluate(Context context);
        //throws ContextException;
  
  }
  
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/Named.java
  
  Index: Named.java
  ===================================================================
  
  package org.apache.velocity.introspection;
  
  public interface Named
  {
     public String getName();
  }
  
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyException.java
  
  Index: PropertyException.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  
  /**
    * This exception is raised by the Property class when, for some reason,
    * an unrecoverable error occurs while attempting to access the declared 
    * property name from the parent object. Possible reasons are: security 
    * violation, the property doesn't exist, or a supplied argument is of 
    * the wrong type.
    */
  final public class PropertyException extends Exception
  {
     
     final Throwable t; 
  
     public PropertyException(String reason, Throwable t) 
     {
        super(reason);
        this.t = t;
     }
  
     /**
       * You can query a PropertyException to find out what the 
       * underlying exception was, if there was one.
       * <p>
       * @returns null if the property was simply not found
       */ 
     public Throwable getThrowable() {
        return t;
     }
  }
  
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyMethod.java
  
  Index: PropertyMethod.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  
  import org.apache.velocity.Context;
  
  //import org.webmacro.*;
  
  /**
    * A property method can function as part of a name in a 
    * property, and represents a method call that should be used
    * to resolve that portion of the name.
    * <p>
    * For example: a.b.get("C").d is equivalent to a.b.hi.d, you can 
    * also use this to access methods which are not normally available
    * through the regular introspection algorithms, for example:
    * #set $thing = a.get("thing")
    * <p>
    * The arguments supplied to a PropertyMethod can be a list 
    * including Macro objects which need to be resolved
    * against a context. The introspection process will supply the 
    * context and resolve these references at execution time.
    */
  final public class PropertyMethod implements Named
  {
  
     private Object _args;
     private String _name;
     private boolean _reference;
  
     /**
       * Create a new PropertyMethod
       * @param name the name of the method to call
       * @param args the arguments, including Macro objects
       */
     public PropertyMethod(String name, Object[] args)
     {
        _name = name;
        _args = args;
        _reference = false;
     }
  
     /**
       * Create a new PropertyMethod
       * @param name the name of the method to call
       * @param args the arguments, including Macro objects
       */
     public PropertyMethod(String name, Macro args)
     {
        _name = name;
        _args = args;
        _reference = true;
     }
  
     /**
       * Return the name of this PropertyMethod
       */
     final public String getName() {
        return _name;
     }
  
     /**
       * Return a signature of this method
       */
     final public String toString() {
        if (_reference) {
           return _name +_args.toString();
        }
        Object[] argList = (Object[]) _args;
        StringBuffer vname = new StringBuffer(); 
        vname.append(_name);
        vname.append("(");
        for (int i = 0; i < argList.length; i++) {
           if (i != 0) {
              vname.append(",");
           }
           vname.append(argList[i]);
        }
        vname.append(")");
        return vname.toString();
     }
  
  
     /**
       * Return the arguments for this method, after resolving them
       * against the supplied context. Any arguments which are of 
       * type Macro will be resolved into a regular 
       * object via the Macro.evaluate method.
       * @exception ContextException a Macro in the arguments failed to resolve against the supplied context
       */
     final public Object[] getArguments(Context context)
        //throws ContextException
     {
        Object[] argList;
        if (_reference) {
           argList = (Object[]) ((Macro) _args).evaluate(context);
        } else {
           argList = (Object[]) _args;
        }
  
        Object ret[] = new Object[ argList.length ];
        System.arraycopy(argList,0,ret,0,argList.length);
        for (int i = 0; i < ret.length; i++) {
           while (ret[i] instanceof Macro) {
              Object repl = ((Macro) ret[i]).evaluate(context);
              if (repl == ret[i]) {
                 break; // avoid infinite loop
              }
              ret[i] = repl;
           }
        }
        return ret;
     }
  
  }
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyOperator.java
  
  Index: PropertyOperator.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  
  package org.apache.velocity.introspection;
  
  import java.util.*;
  import java.lang.reflect.*;
  
  import org.apache.velocity.*;
  
  //import org.webmacro.*;
  
  
  /**
    * This class knows how to extract properties from objects efficiently.
    * <p>
    * A simple property "Bar" can be accessed in a sub-object "Foo" (and
    * in one case, sub-object "Baz") using any of the following method 
    * signatures, listed in the order that they will be tried:
    * <ul>
    * <li>Foo.Bar
    * <li>Foo.getBar() / Foo.setBar(object)
    * <li>Foo.getBar("Baz") / Foo.setBar("Baz", object)
    * <li>Foo.get("Bar") / Foo.put("Bar",object)
    * </ul>
    * <p>
    * The PropertyOperator is capable of extracting an iterator from an object
    * if in any of the following conditions are true, listed in the order that
    * they will be tried:
    * <ul>
    * <li>The object itself is an array
    * <li>The object itself is an Iterator
    * <li>The object itself is an Enumeration
    * <li>The object has an "Iterator iterator()" method
    * <li>The object has an "Enumeration elements()" method
    * </ul>
    * <p>
    * You can specify a long list of property names, and the above methods
    * will be recursively applied. For example, if your list were 
    * Order,Customer,Fred,Name,Last the PropertyOperator might be 
    * able to access it as:<pre>
    *     Order.getCustomer("Fred").getName().Last
    * </pre>
    */
  final public class PropertyOperator
  {
  
     // debugging
  
     static final boolean _debug = Log.debug && false;
     static final Log _log = new Log("prop","Property Introspection");
  
     // static public interface
  
     /**
       * Attempt to retrieve a property using the rules of property 
       * introspection described above. Begin reading names at position 
       * start in the array of names.
       * @param context is used to resolve sub-properties in arguments
       * @param instance is the root of introspection
       * @param names property names, one per array entry 
       * @return the property described by the names, inside the instance
       * @exception PropertyException the property we'd like to look at
       * @exception SecurityExeption you are not permitted to try
       * @exception ContextException one of the names is a PropertyReference which could not be evaluated against the supplied context
       */
     static final public Object getProperty(
           final Context context, final Object instance, 
           final Object[] names, int start) 
        throws PropertyException, SecurityException //, ContextException
     {
        try {
           if (instance == null) {
              return null;
           } else {
              return getOperator(instance.getClass()).getProperty(
                 context,instance,names,start,names.length - 1);
           }
        } catch (NoSuchMethodException e) {
           _log.exception(e);
           throw new PropertyException("No method to access property: " + e,e);
        }
     }
  
     /**
       * Calls getProperty(context, instance, names, 0)
       */
     static final public Object getProperty(
           final Context context, final Object instance, final Object[] names) 
        throws PropertyException, SecurityException //, ContextException
     {
        return getProperty(context, instance, names, 0);
     }
  
  
     /**
       * Given a property description name, attempt to set the property
       * value to the supplied object.
       * @param context An object containing a property
       * @param names The string names of that property 
       * @param value the new value the property is to be set to
       * @exception PropertyException not possible to set the property
       * @exception SecurityException you are not permitted to try
       * @exception ContextException a PropertyReference in the argument names could not be resolved against the supplied context
       */
     static final public boolean setProperty(
           final Context context, Object instance, 
           final Object[] names, int start, final Object value) 
        throws PropertyException, SecurityException //, ContextException
     {
        try {
           if (instance == null) {
              return false;
           }
           return getOperator(instance.getClass()).setProperty(context,instance,names,value,start);
        } catch (NoSuchMethodException e) {
           throw new PropertyException("No method to access property: " + e,e);
        }
     }
  
     /**
       * Calls setProperty(context, names, 0, value)
       */
     static final public boolean setProperty(
           final Context context, final Object instance, 
           final Object[] names, final Object value) 
        throws PropertyException, SecurityException //, ContextException
     { 
        return setProperty(context,instance,names,0,value);
     }
   
     /**
       * Evaluate the supplied object and work out a way to return it 
       * as an iterator.
       * @param context an object believed to represent a list
       * @return an Iterator that iterates through that list
       * @exception PropertyException could not extract iterator from instance
       */
     static final public Iterator getIterator(Object instance)
        throws PropertyException
     {
        if (instance instanceof Object[]) {
           return new ArrayIterator((Object[])instance);
        } else if (instance instanceof Iterator) {
           return (Iterator) instance;
        } else if (instance instanceof Enumeration) {
           return new EnumIterator((Enumeration)instance);
        } else {
           return getOperator(instance.getClass()).findIterator(instance);
        }
     }
  
     // operator cache
  
     /**
       * Private cache of all the property operators constructed so far
       */
     static private RefMap _operators = new RefMap();
  
  
     /**
       * Find the PropertyOperator that knows about this type of object
       */
     static private PropertyOperator getOperator(final Class type)
        throws PropertyException
     {
        PropertyOperator o = (PropertyOperator) _operators.get(type);
        if (o == null) {
           synchronized (_operators) {
              // if ((_operators.size() * 2) > _operators.capacity()) {
              // _operators = _operators.copy(_operators.capacity() * 2 + 1);
              // }
  
              try {
                 o = new PropertyOperator(type);
              } catch (Exception e) {
                 e.printStackTrace();
              }
              _operators.put(type,o); 
           }
        }
        return o;
     }
  
     /**
       * My accessors for fields, and binary methods
       */
     final private HashMap _unaryAccessors = new HashMap();
  
     /**
       * Accessors that require an additional property name
       */
     final private HashMap _binaryAccessors = new HashMap();
  
     /**
       * Accessors for direct method calls
       */
     final private HashMap _directAccessors = new HashMap();
  
     /**
       * Hash table accessor
       */
     private BinaryMethodAccessor _hashAccessor;
  
     /**
       * The iterator method we found
       */
     private Method iteratorMethod = null;
  
     /**
       * Get the public methods for the named class. The vector meths 
       * will be populated by a list of all the methods. Note that a method
       * may appear more than once in the vector if it is declared in more
       * than one superclass or interface.
       */
     private void getAllMethods(HashMap meths, Class c)
        throws SecurityException
     {
        if (Modifier.isPublic( c.getModifiers() ) ) {
           Method m[] = c.getDeclaredMethods();
           for (int i = 0; i < m.length; i++) {
              if ( Modifier.isPublic( m[i].getModifiers() ) ) {
                 addMethod(meths, m[i]);
              }
           }
        }
        Class iface[] = c.getInterfaces();
        for (int i = 0; i < iface.length; i++) {
           getAllMethods(meths, iface[i]);
        }
  
        Class sup = c.getSuperclass();
        if (sup != null) {
           getAllMethods(meths, sup);
        }
     }
  
     /**
       * The lhs precedes the rhs if it has fewer parameters. If the lhs 
       * has the same number of parameters then the lhs precedes the rhs 
       * if it can be used anywhere the rhs can be used--meaning that for
       * each and every term, the lhs is the same or more specific than
       * the rhs. If they have the same number of parameters but are not
       * related at all, put the lhs later.
       */
     private int precedes(Class[] lhs, Class[] rhs)
     {
  
        if (lhs.length == rhs.length) {
           for (int i = 0; i < lhs.length; i++) {
  
              if (! rhs[i].equals(lhs[i])) {
  
                 if (lhs[i].isAssignableFrom(rhs[i])) {
                    // rhs is more specific than lhs
                    return 1;
                 }
  
                 if (rhs[i].isAssignableFrom(lhs[i])) {
                    // lhs is more specific than rhs
                    return -1;
                 }
  
                 // not related by inheritance, put lhs later on
                 return 1;
  
              }
           }
           return 0; // all the same
        } else {
           return (lhs.length < rhs.length) ? -1 : 1;
        }
     }
  
     private void addMethod(HashMap hm, Method m) {
        String name = m.getName();
        Object o = hm.get( name );
        if (o == null) {
           hm.put(name, m);
           return;
        }
  
        Vector v;
        if (o instanceof Method) {
           v = new Vector();
           v.addElement(o);
           hm.put(name, v);
        } else {
           v = (Vector) o;
        }
  
        Class ptypes[] = m.getParameterTypes();
        for (int i = 0; i < v.size(); i++) {
           Class curTypes[] = ((Method) v.elementAt(i)).getParameterTypes();
  
           int order = precedes(ptypes, curTypes);
  
           if (order < 0) {
              v.insertElementAt(m,i);
              return;
           } else if (order == 0) {
              // ignore duplicate method
              return;
           }
        }
        v.addElement(m);
     }
  
     /**
       * Get all the public methods of the supplied class. They will be 
       * returned in arbitrary alphabetical order, but where there are 
       * multiple methods with the same name, they will be returned in 
       * order of precedence: least arguments first, and most specific 
       * arguments before less specific arguments. See precedes().
       */
     private Vector getMethods(Class c) {
        Vector v = new Vector();
        HashMap h = new HashMap();
        getAllMethods(h,c);
        Iterator iter = h.values().iterator();
        while (iter.hasNext()) {
           Object elem = iter.next();
  
           if (elem instanceof Method) {
              v.addElement( elem );
           } else {
              Vector v1 = (Vector) elem;
              for (int i = 0; i < v1.size(); i++) {
                 v.addElement( v1.elementAt(i) );
              }
           }
        }
        return v;
     }
  
  
     /**
       * Construct a property operator for the target class
       */
     private PropertyOperator(final Class target)
        throws SecurityException, PropertyException
     {
  
        if (_debug) {
           _log.debug("new PropertyOperator(" + target + ")");
        }
        
        Accessor acc;
  
        // introspect fields first
  
        Field[] fields = target.getFields();
        for (int i = 0; i < fields.length; i++) {
           if (Modifier.isPublic(fields[i].getModifiers())) {
              if (_debug) {
                 _log.debug("Adding field: " + fields[i]);
              }
              acc = new FieldAccessor(fields[i]);
              _unaryAccessors.put(acc.getName(),acc);
           } else if (_debug) {
              _log.debug("Skipped non-public field: " + fields[i]);
           }
        }
  
        // introspect methods second
  
        Vector methods = getMethods(target);
  
        Method meth;
        Class[] params;
        String name,propName;
  
        for (int i = 0; i < methods.size(); i++) 
        {
           meth = ((Method) methods.elementAt(i));
  
           name = meth.getName();
           params = meth.getParameterTypes();
           int plength = params.length;
  
           // add direct accessor
           acc = (Accessor) _directAccessors.get(name);
           if (acc != null) {
              ((DirectAccessor) acc).addMethod(meth,params); 
           } else {
              acc = new DirectAccessor(name,meth,params);
              _directAccessors.put(name,acc);
           }
  
           // check for get/set/put method
           if ((name.startsWith("get") || 
                 name.startsWith("set")) || name.equals("put"))
           {
  
              propName = name.substring(3);
  
              if ( ((plength == 0) && name.startsWith("get"))  ||
                   ((plength == 1) && name.startsWith("set")) )
              {
  
                 // unary get/set method
                 acc = (Accessor) _unaryAccessors.get(propName);
                 if (acc != null) {
                    if (acc instanceof MethodAccessor) {
                       if (_debug) {
                          _log.debug("Updating existing accessor: " + meth);
                       }
                       ((MethodAccessor) acc).addMethod(meth,params);
                    } else if (_debug) {
                       _log.debug("Superceded by a field: " + meth);
                    }
                 } else {
                    if (_debug) {
                       _log.debug("Adding new accessor: " + meth);
                    }
                    acc = new UnaryMethodAccessor(propName,meth,params);
                    _unaryAccessors.put(propName,acc);
                 }
              } else if ( (plength > 0) && (
                            (params[0].isInstance("string") && 
                             ((plength == 2) && name.equals("put"))) ||
                            ((plength == 1) && name.equals("get"))))
              {
                 // hashtable get/put
                 if (_hashAccessor != null) {
                    if (_debug) {
                       _log.debug("Updating hash accessor: " + meth);
                    }
                    _hashAccessor.addMethod(meth,params);
                 } else {
                    if (_debug) {
                       _log.debug("Creating a new hash accessor: " + meth);
                    }
                    _hashAccessor = new BinaryMethodAccessor(propName,meth,params);
                 }
              } else if ((plength > 0) && (params[0].isInstance("string")) &&
                         (((plength == 1) && name.startsWith("get")) ||
                          ((plength == 2) && name.startsWith("set"))))
              {
                 // binary get/set method
                 acc = (Accessor) _binaryAccessors.get(propName);
                 if (acc != null) {
                    if (_debug) {
                       _log.debug("Updating binary accessor: " + meth);
                    }
                    ((MethodAccessor) acc).addMethod(meth,params);
                 } else {
                    if (_debug) {
                       _log.debug("Creating a new binary accessor: " + meth);
                    }
                    acc = new BinaryMethodAccessor(propName,meth,params);
                    _binaryAccessors.put(propName,acc);
                 }
              }
           } else if (name.equals("elements") || 
                      name.equals("enumeration") ||
                      name.equals("iterator") ||
                      name.equals("toArray")) 
           {
              if (params.length == 0) {
                 Class returnType = meth.getReturnType();
  
                 // iterator supercedes enumeration supercedes Object[]
                 Class iterClass = Iterator.class;
                 boolean iterA = iterClass.isAssignableFrom(returnType);
                 if (
                      iterA ||
                      (((iteratorMethod == null) || 
                         iteratorMethod.getName().equals("toArray")) &&
                       Object[].class.isAssignableFrom(returnType) ||
                         Enumeration.class.isAssignableFrom(returnType)))
                 {
                    if (_debug) {
                       _log.debug("Setting iterator method: " + meth);
                    }
                    iteratorMethod = meth;
                 }
  
              }
           }
        }
     }
  
  
     /**
       * Locate the requested property as follows: beginning from instance,
       * look at names[start] and resolve it, and continue resolving names 
       * recursively until names[end] has been resolved. Return that.
       * @param instance object to start from 
       * @param names the property names we are searching for 
       * @param start which name to look for first 
       * @param end which name to look for lst
       * @exception PropertyException error resolving a name 
       * @exception NoSuchMethodException no method available for name
       * @return the property requested
       *
       */
     private Object getProperty(
           final Context context, final Object instance, final Object[] names, 
              int start, int end) 
        throws PropertyException, NoSuchMethodException //, ContextException
     {
  
        if (_debug) {
           _log.debug("getProperty(" + instance + "," + names[start] + "..."
                 + names[end] + "," + start + "," + end + ")");
        }
  
        String prop;
        Object nextProp = null;
        Accessor acc = null;
  
        if (names[start] instanceof String) {
           prop = (String) names[start];
        } else if (names[start] instanceof PropertyMethod) {
           PropertyMethod pm = (PropertyMethod) names[start];
           prop = pm.getName();
           acc = (Accessor) _directAccessors.get(prop);
           Object[] args = pm.getArguments(context);
  
           try {
              nextProp = acc.get(instance,args);
              start++;
           } catch (NoSuchMethodException e) {
              throw new NoSuchMethodException("No method " + pm + " on object " + instance);
           }
  
        } else {
           prop = names[start].toString();
        }
  
        // unary?
        if (acc == null) {
           acc = (Accessor) _unaryAccessors.get(prop);
           if (acc != null) {
  
              if (_debug) {
                 _log.debug("Trying unary accesor: " + acc);
              }
  
              try {
                 nextProp = acc.get(instance);
                 start++;
              } catch (NoSuchMethodException e) { 
                 if (_debug) {
                    _log.debug("No suitable unary get in " + acc);
                 }
                 acc = null;
              }
           }
        } 
  
        // binary?
        if (acc == null) {
           acc = (Accessor) _binaryAccessors.get(prop);
           if ((acc != null) && ( (start+1) <= end) ) {
              if (_debug) {
                 _log.debug("Trying binary accesor: " + acc);
              }
              try {
                 nextProp = acc.get(instance, (String) names[start + 1]);
                 start += 2; 
              } catch (NoSuchMethodException e) {
                 if (_debug) {
                    _log.debug("No suitable binary get in " + acc);
                 }
                 acc = null;
              } catch (ClassCastException e) {
                 // names[start + 1] was not a String, just move on
                 // this catch is more efficient than using instanceof
                 // since 90% of the time it really will be a string
                 acc = null;
              }
           }  else {
              acc = null;
           }
        } 
  
        // hash?
        if (acc == null) {
           acc = _hashAccessor;
           try {
              if (acc != null) {
                 if (_debug) {
                    _log.debug("Trying hash accessor=" + acc +
                          " with prop=" + prop);
                 }
                 nextProp = acc.get(instance,prop);
                 start++;
                 if (_debug) {
                    _log.debug("Got: " + nextProp);
                 }
              }
           } catch (NoSuchMethodException e) {
              if (_debug) {
                 _log.debug("No suitable hash get in " + acc);
              }
              acc = null;
           }
        } 
        
        if (acc == null) {
           throw new NoSuchMethodException("No public method on object " + 
                 instance + " of " + instance.getClass() + 
                 " for property " + names[start] + "--is this the right class?");
        }
  
        if (_debug) {
           _log.debug("Using accessor: " + acc);
        }
        if (start <= end) {
           try {
             return getOperator(nextProp.getClass()).getProperty(context,nextProp,names,start,end);
           } catch (NullPointerException e) {
              throw new PropertyException("No way to access property " + 
                    names[start] + " on object " + instance + " of " 
                    + instance.getClass() + "--possibly null?",e);
           }
        } else {
           return nextProp;
        }
     }
  
     /**
       * This method behaves a lot like getProperty, but it's tricker. It 
       * first tries to resolve the property using a direct method, then 
       * it falls back and tries a binary approach last. In order to do 
       * this it has to recurse into the direct approach, then detect if 
       * that failed and try the binary approach. It relies on getProperty
       * for navigation, which is why getProperty takes start/end args.
       * @param instance the object to start from
       * @names path to a property we would like to set 
       * @value the value we'd like to set it to 
       * @pos   we could set names[pos] from here
       * @return true if we succeeded in setting, false otherwise
       */
     private boolean setProperty(
           Context context, Object instance, Object[] names, Object value, int pos) 
        throws PropertyException, NoSuchMethodException //, ContextException
     {
        if (_debug) {
           _log.debug("setProperty(" + instance + "," + names[pos] + "..."
                 + "," + value + "," + pos + ")");
        }
  
        // names[pos] is what we could set from here
  
        int parentPos = names.length - 1;
        int binPos = parentPos - 1;
  
        // if we're not yet at the binary-settable parent, go there
        if (pos < binPos) {
           Object grandparent = getProperty(context,instance,names,pos,binPos - 1);
           if (_debug) {
              _log.debug("Advanced to " + names[binPos] + "=" + grandparent);
           }
           PropertyOperator po = getOperator(grandparent.getClass());
           return po.setProperty(context,grandparent,names,value,binPos);
        } 
  
        // if we're at the binary-settable parent, try direct first
        if (pos == binPos) {
  
           if (_debug) {
              _log.debug("I am " + names[pos] + "(" + instance + ")" +
                    " could be binary, but first recurse to try unary access for " + names[pos]);
           }
  
           // try direct -- move to direct parent and try from there
           Object parent = null;
           try {
              parent = getProperty(context,instance,names,pos,pos);
              if (parent != null) {
                 PropertyOperator po = getOperator(parent.getClass());
                 if (po.setProperty(context,parent,names,value,pos+1)) {
                    return true;
                 }
              }
           } catch (NoSuchMethodException e) {
              // oh well, keep trying: XXX this makes binOp expensive
           }
  
           // if direct failed, try binary
           if (_debug) {
              _log.debug("I am " + names[pos] + "(" + instance + ")" +
                     "direct failed, try bin for " + names[pos] + "." + names[pos + 1]);
           }
           Accessor binOp = (Accessor) _binaryAccessors.get(names[pos]);
           if (binOp != null) {
              if (_debug) {
                 _log.debug("Found binOp accessor: " + binOp);
              }
              try {
                 return binOp.set(instance,(String) names[pos+1],value);
              } catch (ClassCastException e) {
                 if (_debug) {
                    _log.debug("Binary Failed because " + names[pos + 1] + " was not a string; it is " + names[pos + 1].getClass());
                 }
                 // names[pos+1] was not a string, just move on
                 return false;
              } catch (NoSuchMethodException e) {
                 if (_debug) {
                    _log.debug("Binary Failed: " + e);
                 }
                 return false;
              }
           } else {
              if (_debug) {
                 _log.debug("No binary accessor for " + names[pos]);
              }
           }
           return false;
        }
  
        // we're the direct parent, use unaryOp or hash method
        if (_debug) {
           _log.debug("I am " + names[pos] + "(" + instance + ")"
                 + "trying direct");
        }
        Accessor unaryOp = (Accessor) _unaryAccessors.get(names[pos]);
        try {
           if ((unaryOp != null) && unaryOp.set(instance,value)) {
              return true;
           }
           if (_hashAccessor != null) {
              return _hashAccessor.set(instance,(String) names[pos],value);
           }
        } catch(NoSuchMethodException e) {
           // fall through
        } catch(ClassCastException e) {
           // names[pos] was not a string, fall through
        }
        return false;
     }
  
     /**
       * Introspect the current object and return an Iterator representation
       * of it, if possible.
       * @param instance the object we think contains a list
       * @exception PropertyException current object is not any sort of list
       * @return an iterator representing the current object's list
       */
     private Iterator findIterator(Object instance)
        throws PropertyException
     {
        if (iteratorMethod != null) {
           try {
                 Object ret = invoke(iteratorMethod,instance,null);
              if (ret instanceof Iterator) {
                 return (Iterator) ret;
              } else if (ret instanceof Enumeration) {
                 return new EnumIterator((Enumeration) ret);
              } else if (ret instanceof Object[]) {
                 return new ArrayIterator((Object[]) ret);
              }
           } catch (NoSuchMethodException e) {
              throw new PropertyException("Error in PropertyOperator!",e);
           }
        }
        throw new PropertyException(instance + " is not a list",null);
     }
  
     /**
       * Invoke a method on an instance, with arguments--generate 
       * PropertyException rather than the default Java exceptions.
       * @param meth the method to invoke
       * @param instance the object to invoke it on
       * @param args arguments for the method
       * @return return value of the method
       */
     static Object invoke(Method meth, Object instance, Object[] args)
        throws PropertyException, NoSuchMethodException
     {
        try {
           return meth.invoke(instance,args);
        } catch (IllegalAccessException e) {
           throw new PropertyException(
              "You don't have permission to access the requested method (" +
              meth + " in class " + instance.getClass() + 
              " on object " + instance + "). Private/protected/package access " +
              " values cannot be accessed via property introspection.",e);
        } catch (IllegalArgumentException e) {
           throw new PropertyException(
              "Some kind of error occurred processing your request: this " + 
              "indicates a failure in PropertyOperator.java that should be " +
              "reported: attempt to access method " + meth + " on object " +
              instance + " with " +args.length + " parameters " +
              " threw an exception: " + e,e);
        } catch (InvocationTargetException e) {
           throw new PropertyException(
              "Attempt to invoke method " + meth + " on object " 
              + instance + " of " + instance.getClass() + 
              " raised an exception: " + e.getTargetException(),
              e.getTargetException());
        } catch (NullPointerException e) {
           if (meth == null) {
              throw new NoSuchMethodException("Null method");
           }
           throw new PropertyException(
              "NullPointerException thrown from method " + meth +
              " on object " + instance + " -- most likely you have attempted " + 
              "to use an undefined value, or a failure in that method.",e);
        }
     }
  }
  
  
  
  // helper classes
  
  
  /**
    * An accessor represents one particular operation that can be 
    * performed on one particular class: getting/setting a field, or 
    * getting/setting via a method.
    */
  abstract class Accessor {
  
     private String _name;
  
     Accessor(String name) {
        _name = name;
     }
  
     final String  getName() {
        return _name;
     }
  
     public final String toString() {
        return "Accessor:" + _name;
     }
   
     /**
       * Unary get
       */
     Object get(Object instance) 
        throws PropertyException, NoSuchMethodException
     {
        throw new PropertyException("BUG in PropertyOperator.java!",null);
     }
  
     /**
       * Unary set
       */
     boolean set(Object instance, Object value) 
        throws PropertyException, NoSuchMethodException
     {
        throw new PropertyException("BUG in PropertyOperator.java!",null);
     }
  
     /**
       * Binary get
       */
     Object get(Object instance, String subName) 
        throws PropertyException, NoSuchMethodException
     {
        throw new PropertyException("BUG in PropertyOperator.java!",null);
     }
  
     /**
       * Binary
       */
     boolean set(Object instance, String subName, Object value)
        throws PropertyException, NoSuchMethodException
     {
        throw new PropertyException("BUG in PropertyOperator.java!",null);
     }
  
  
     /**
       * Direct get
       */
     Object get(Object instance, Object[] args)
        throws PropertyException, NoSuchMethodException
     {
        throw new PropertyException("BUG in PropertyOperator.java!",null);
     }
  
  }
  
  
  /**
    * An accessor that knows how to get/set from a field
    */
  final class FieldAccessor extends Accessor
  {
     private Field _field;
  
     FieldAccessor(final Field f) {
        super(f.getName());
        _field = f;
     }
  
     final Object get(final Object instance)
        throws PropertyException
     {
        try {
           return _field.get(instance);
        } catch (Exception e) {
           throw new PropertyException("Unable to read field " + _field +
                 " on object " + instance + " of " + instance.getClass(),
                 e);
        }
     }
  
     final boolean set(final Object instance, final Object value)
        throws PropertyException
     {
        try {
           _field.set(instance,value);
        } catch (Exception e) {
           throw new PropertyException("Unable to write field " + _field +
                 " on object " + instance + " of " + instance.getClass(),
                 e);
        }
        return true;
     }
  }
  
  
  /**
    * accessor for direct method calls, rather than property-style
    */
  final class DirectAccessor extends Accessor
  {
  
     Vector _methods = new Vector();
  
     DirectAccessor(final String name, final Method m, final Class[] params)
     {
        super(name);
        addMethod(m,params);
     }
  
     final void addMethod(final Method m, Class[] params)
     {
        _methods.addElement(m);
     }
  
  
     final boolean matches(Class[] sig, Class[] args)
     {
        if (args.length != sig.length) {
           return false;
        }
  
        for (int i = 0; i < sig.length; i++) {
           try {
              if (! sig[i].isAssignableFrom(args[i]))
              {
                 if (sig[i].isPrimitive())
                 {
                    Class s = sig[i];
                    Class a = args[i];
                    if (
                        (s.equals(Integer.TYPE) && a.equals(Integer.class)) ||
                        (s.equals(Boolean.TYPE) && a.equals(Boolean.class)) || 
                        (s.equals(Character.TYPE) && a.equals(Character.class)) ||  
                        (s.equals(Long.TYPE) && a.equals(Long.class)) ||  
                        (s.equals(Short.TYPE) && a.equals(Short.class)) ||  
                        (s.equals(Double.TYPE) && a.equals(Double.class)) ||  
                        (s.equals(Float.TYPE) && a.equals(Float.class)) ||  
                        (s.equals(Void.TYPE) && a.equals(Void.class)) ||  
                        (s.equals(Byte.TYPE) && a.equals(Byte.class)) 
                        )
                    {
                        continue;
                    }
                 }
                 if ( PropertyOperator._debug) 
                 {
                    PropertyOperator._log.debug("method " + this + " failed to match because " + sig[i] + " required but got " + args[i]);
                 }
                 return false;
              }
           } catch (NullPointerException e) {
              return false; // XXX: block nulls, isAssign... throws this
           }
        }
        return true;
     }
  
     final Object get(Object instance, Object[] args)
        throws PropertyException, NoSuchMethodException
     {
        Class[] types = new Class[ args.length ];
        for (int i = 0; i < args.length; i++) {
           try {
              types[i] = args[i].getClass();
           } catch (NullPointerException e) {
              types[i] = null;
           }
        }
  
        for (int i = 0; i < _methods.size(); i++) {
           Method m = (Method) _methods.elementAt(i);
           Class[] sig = m.getParameterTypes();
           if (matches(sig,types)) {
              return PropertyOperator.invoke(m,instance,args);
           }
        }
  
        // not found
  
        StringBuffer msg = new StringBuffer();
        msg.append("No method ");
        msg.append(getName());
        msg.append("(");
        for (int i = 0; i < args.length; i++) {
           if (i > 0) {
              msg.append(",");
           }
           msg.append((args[i] == null) ? "null" : args[i].getClass().toString());
        }
        msg.append(") on object ");
        msg.append(instance);
  
        throw new PropertyException(msg.toString(),null);
     }
     
  }
  
  
  abstract class MethodAccessor extends Accessor
  {
     Method _getMethod;           // only one get method allowed
     Method[] _setMethods = null; // may be multiple set methods
     Class[]  _setParams = null;  // variable arg type for set meth N
     int setCount = 0;            // how many set methods do we have
  
     abstract int numArgsGet();
     abstract int numArgsSet();
  
     MethodAccessor(final String name, final Method m, final Class[] params) 
        throws PropertyException
     {
        super(name);
        addMethod(m,params);
     }
  
     final void addMethod(final Method m, Class[] params) 
        throws PropertyException
     {
  
        final int setArgsLength = numArgsSet();
        final int getArgsLength = numArgsGet();
  
        if (params.length == getArgsLength) {
           _getMethod = m;
        } else if (params.length == setArgsLength) {
           setCount++;
           if (_setMethods == null) {
              _setMethods = new Method[1];
              _setParams = new Class[1];
           } else if (_setMethods.length <= setCount) {
              Method[] tmpMethods = new Method[ (setCount + 1) * 2 ];
              Class[] tmpParams  = new Class[(setCount + 1) * 2 ];
              System.arraycopy(_setMethods,0,tmpMethods,0,_setMethods.length);
              System.arraycopy(_setParams,0,tmpParams,0,_setParams.length);
              _setMethods = tmpMethods;
              _setParams = tmpParams;
           }
  
           // record the method, and the type of the variable parameter
           _setMethods[setCount - 1] = m;
           _setParams[setCount - 1] = params[setArgsLength - 1];
  
        } else {
           throw new PropertyException("PropertyOperator FAILED for method " 
                 + m + "--please report this bug!",null);
        }
     }
  
     final boolean setImpl(final Object inst, final Object[] args) 
        throws PropertyException, NoSuchMethodException
     {
        //which method to use? check params for first match
        for (int i = 0; i < setCount; i++) {
           Object arg = args[args.length - 1];
           // XXX: null values are blocked by the next line
           if (_setParams[i].isInstance(args[args.length - 1])) {
              PropertyOperator.invoke(_setMethods[i],inst,args);
              return true;
           }
        }
        return false;
     }
  
  }
  
  final class UnaryMethodAccessor extends MethodAccessor
  {
  
     UnaryMethodAccessor(final String name, final Method m, final Class[] params) 
        throws PropertyException
     {
        super(name,m,params);
     }
  
     final int numArgsGet() {
        return 0;
     }
  
     final int numArgsSet() {
        return 1;
     }
  
     final Object get(final Object instance)
        throws PropertyException, NoSuchMethodException
     {
        return PropertyOperator.invoke(_getMethod,instance,null);      
     }
  
     final boolean set(final Object instance, final Object value) 
        throws PropertyException, NoSuchMethodException
     {
        Object[] args = new Object[1];
        args[0] = value;
        return setImpl(instance,args);
     }
  
  }
  
  final class BinaryMethodAccessor extends MethodAccessor
  {
     
     BinaryMethodAccessor(String name, Method m, Class[] params) 
        throws PropertyException
     {      
        super( name,m,params);
     }
  
     final int numArgsGet() {
        return 1;
     }
  
     final int numArgsSet() {
        return 2;
     }
  
     final Object get(final Object instance, String prop)
        throws PropertyException, NoSuchMethodException
     {
        Object[] args = new Object[1];
        args[0] = prop;
        return PropertyOperator.invoke(_getMethod,instance,args);      
     }
  
     final boolean set(final Object instance, String prop, Object value)
        throws PropertyException, NoSuchMethodException
     {
        Object[] args = new Object[2];
        args[0] = prop;
        args[1] = value;
        return setImpl(instance,args);
     }
  }
  
  
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyReference.java
  
  Index: PropertyReference.java
  ===================================================================
  
  /*
   * Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
   *
   * This software is the confidential intellectual property of
   * of Semiotek Inc.; it is copyrighted and licensed, not sold.
   * You may use it under the terms of the GNU General Public License,
   * version 2, as published by the Free Software Foundation. If you 
   * do not want to use the GPL, you may still use the software after
   * purchasing a proprietary developers license from Semiotek Inc.
   *
   * This software is provided "as is", with NO WARRANTY, not even the 
   * implied warranties of fitness to purpose, or merchantability. You
   * assume all risks and liabilities associated with its use.
   *
   * See the attached License.html file for details, or contact us
   * by e-mail at info@semiotek.com to get a copy.
   */
  
  package org.apache.velocity.introspection;
  //import org.webmacro.*;
  
  import org.apache.velocity.Context;
  
  /**
    * This is a generic interface which PropertyOperator users may 
    * wish to subclass from. The intent is that implementors of this
    * class may use PropertyOperator to examine the context in order
    * to extract some kind of value, which is returned.
    */
  public interface PropertyReference
  {
  
     /**
       * Apply this object to the supplied context and return 
       * the appropriate reference.
       * @param context An object which may be introspected for information
       * @return The appropriate value given this context
       * @exception ContextException required data not found in context
       */
     public Object evaluate(Context context);
      //throws ContextException;
  
  }
  
  
  
  1.1                  jakarta-velocity/src/java/org/apache/velocity/introspection/RefMap.java
  
  Index: RefMap.java
  ===================================================================
  
  package org.apache.velocity.introspection;
  
  import java.util.HashMap;
  
  /**
    * RefMap is a very specialized data structure that is probably
    * not suitable for most uses. It is a faster version of a 
    * HashMap type structure which is broken in the following 
    * ways, for efficiency: (1) it does not use object.equals()
    * to compare keys, so you must use the identical instance
    * as the key with get() as with put().  (2) it never exands
    * itself, but instead throws an exception when it fills up.
    * you can use the copy() method to make a bigger one in 
    * this case. (3) you cannot remove data from the map. 
    * (4) it is not synchronized on reads, but it does guarantee 
    * that the value read did at one point belong to the key 
    * supplied (and not to some other key).
    * <p>
    * RefMap was implemented to optimize a particular section
    * of PropertyOperator where the above characteristics are
    * acceptable and the speed increase desirable. Do not use 
    * it in any other context unless you are really sure you 
    * understand just how inconvenient the above limitations 
    * can be. 
    */
  
  final public class RefMap {
  
     final Object _key[];
     final Object _value[];
     int _size;
  
     public RefMap() {
        this(1001);
     }
  
     public RefMap(int size) {
        _key = new Object[size];
        _value = new Object[size];
        _size = 0;
     }
  
     public void put(Object key, Object value) 
        throws java.lang.IndexOutOfBoundsException
     {
        if (key == null) {
           return;
        }
        int loc = key.hashCode() % _key.length;
        if (loc < 0) { loc *= -1; }
        if ((_key[loc] != null) || (_key[loc] == key)) {
           int i = loc + 1;
           while (i != loc) {
              if (i == _key.length) {
                 i = 0;
              } else if ((_key[i] == null) || (_key[loc] == key)) {
                 break;
              } else {
                 i++;
              }
           }
           loc = i;
        }
        if ((_key[loc] == null) || (_key[loc] == key)) {
           _size++;
           _value[loc] = value;
           synchronized(_key) { } // control update order
           _key[loc] = key;
        } else {
           throw new java.lang.IndexOutOfBoundsException("RefMap is Full, use realloc");
        }
     }
  
     /**
       * WARNING: this method is unsynchronized and it might 
       * return invalid data. This could happen if one thread
       * attempted to write at the same moment as another 
       * thread attempted a read.
       */
     public Object get(Object key) {
        if (key == null) {
           return null;
        }
        int loc = key.hashCode() % _key.length;
        if (loc < 0) { loc *= -1; }
        if (_key[loc] == key) {
           return _value[loc];   
        } else {
           int i = loc + 1;
           while (i != loc) {
              if (i == _key.length) {
                 i = 0;
              } else if (_key[i] == key) {
                 return _value[i];
              } else {
                 i++;
              }
           }
           return null;
        }
     }
  
     public RefMap copy(int size) 
        throws IndexOutOfBoundsException
     {
        if (_size > size) {
           throw new IndexOutOfBoundsException("New map must be bigger than the old map");
        }
        RefMap nm = new RefMap(size);
        for (int i = 0; i < _key.length; i++) {
           if (_key[i] != null) {
              nm.put(_key[i],_value[i]);
           }
        }
        return nm;
     }
  
     public void clear() {
        for (int i = 0; i < _key.length; i++) {
           _key[i] = null;
           synchronized(_key) { } // control update order
           _value[i] = null;
        }
     }
  
     public int size() {
        return _size;
     }
  
     public int capacity() {
        return _key.length;
     }
  
  
     public static void main(String arg[]) {
  
        RefMap rm = new RefMap(11);
        HashMap hm = new HashMap();
        Integer[] vals = new Integer[arg.length];
  
        for(int i = 0; i < arg.length; i++) {
           arg[i] = new String(arg[i]);
        }
  
        for(int i = 0; i < arg.length; i++) { 
           vals[i] = new Integer(i);
        }
  
        for(int i = 0; i < arg.length; i++) {
           rm.put(arg[i],vals[i]);
           hm.put(arg[i],vals[i]);
        }
  
        for(int i = 0; i < arg.length; i++) {
           System.out.println(arg[i] + " :rm: " + rm.get(arg[i]));
        }
        System.out.println();
        for(int i = 0; i < arg.length; i++) {
           System.out.println(arg[i] + " :hm: " + hm.get(arg[i]));
        }
  
        long time;
        int size = 10;
        for(int q = 0; q < 200; q++) {
           time = System.currentTimeMillis();
           for(int i = 1; i < size; i++) {
              for(int j = 1; j < arg.length; j++) {
                 Object o = rm.get(arg[j]);
              }
           }
           System.out.println("refmap : " + (System.currentTimeMillis() - time));
      
           time = System.currentTimeMillis();
           for(int i = 1; i < size; i++) {
              for(int j = 1; j < arg.length; j++) {
                 Object o = rm.get(arg[j]);
              }
           }
           System.out.println("hashmap: " + (System.currentTimeMillis() - time));
        }
     }
  }