NOTES:

  • this is a work-in-progress.
  • there are some related code samples at the bottom of the page (below the jythonuberspect code)

UPDATES:

  • added support for integers in the get method. (JRB)
  • incorporated changes suggested by Kent Johnson


/*
 * Copyright 2000-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.velocity.tools.generic.introspection;

import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;

import org.apache.log4j.Logger;

import org.apache.velocity.util.introspection.*;

import org.python.core.*;

/**
 * a subclass of UberspectImpl that supports dynamic Jython objects.  Methods and
 * attributes can therefore be called on a jython object within a velocity template
 * without requiring additional tools (and without resorting to direct method calls
 * against the jython methods (i.e. no __findattr__ in your template)
 * @author <a href="mailto:jasonrbriggs@gmail.com">Jason R Briggs</a>
 */
public class JythonUberspect extends UberspectImpl
{
    private static final Logger log = Logger.getLogger(JythonUberspect.class.getName());
    
   /**
    * support the standard jython iterators (otherwise pass up to UberspectImpl)
    * in a velocity #foreach
    */
    public Iterator getIterator(Object obj, Info i) throws Exception
    {
        if (obj instanceof PySequence) 
        {
            return new PySequenceIterator((PySequence)obj);
        }
        else if (obj instanceof PyDictionary) 
        {
            return new PySequenceIterator(((PyDictionary)obj).values());
        }
        else
        {
            return super.getIterator(obj, i);
        }
    }
    
   /**
    * get the method for a jython object (or pass up)
    */
    public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception
    {   
        if (obj instanceof PyObject)
        {
            return new PyMethod(methodName);
        }
        else
        {
            return super.getMethod(obj, methodName, args, i);
        }
    }
    
   /**
    * get a property from a jython object for data retrieval
    */
    public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i) throws Exception
    {
        if (obj instanceof PyObject)
        {
            return new PyGetProperty(identifier);
        }
        else
        {
            return super.getPropertyGet(obj, identifier, i);
        }
    }
    
   /**
    * get a property from a jython object for data modification
    */
    public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info info) throws Exception
    {
        if (obj instanceof PyObject)
        {
            return new PySetProperty(identifier);
        }
        else
        {
            return super.getPropertySet(obj, identifier, arg, info);
        }
    }
}

/**
 * a jython velocity method
 */
class PyMethod implements VelMethod
{
    private static final Logger log = Logger.getLogger(JythonUberspect.class.getName());
    final PyString methodname;

    public PyMethod(String methodname)
    {
        this.methodname = new PyString(methodname);
    }

   /**
    * returns the jython method name
    */
    public String getMethodName()
    {
        return methodname.toString();
    }
   
   /**
    * the return type of the invoked method.  Just being lazy here and returning 'Object'
    * for everything at the moment
    */
    public Class getReturnType()
    {
        return Object.class;
    }
    
   /**
    * execute the jython method
    */
    public Object invoke(Object o, Object[] params)
    {
        PyObject po = (PyObject)o;
        PyObject rtn = null;
        try
        {
            // find the method attr on the python object
            PyObject meth = po.__findattr__(methodname);
            if (params == null || params.length < 1)
            {
                rtn = meth.__call__();
            }
            else
            {   
                // build a python params array
                PyObject[] pparams = new PyObject[params.length];
                for (int i = 0; i < pparams.length; i++)
                {
                    if (params[i] instanceof String)
                    {
                        pparams[i] = new PyString((String)params[i]);
                    }
                    else if (params[i] instanceof PyObject)
                    {
                        pparams[i] = (PyObject)params[i];
                    }
                    else if (params[i] instanceof Integer)
                    {
                        pparams[i] = new PyInteger(((Integer)params[i]).intValue());   
                    }
                    else
                    {
                        System.err.println("unsupported param type " + params[i].getClass().getName());
                        log.error("unsupported param type : " + params[i].getClass().getName());
                        return null;
                    }
                }

                rtn = meth.__call__(pparams);
                if (rtn instanceof PyNone)
                {
                    rtn = null;
                }
            }

            return rtn;
        }
        catch (Exception e)
        {
            log.error(e);
        }

        return null;
    }
    
   /**
    * is this method cacheable
    */
    public boolean isCacheable()
    {
        return true;
    }
}

/**
 * a jython velocity GET property
 */
class PyGetProperty implements VelPropertyGet
{
    private static final Logger log = Logger.getLogger(JythonUberspect.class.getName());
    private PyString propname;

    public PyGetProperty(String propname)
    {
        this.propname = new PyString(propname);
    }

   /**
    * the name of the jython property/attribute
    */
    public String getMethodName()
    {
        return propname.toString();
    }
    
   /**
    * returns the property value
    */
    public Object invoke(java.lang.Object o)
    {
        PyObject po = (PyObject)o;
        try
        {

            Object rtn = po.__findattr__(propname);
            if (rtn instanceof PyNone)
            {
                // handle python None correctly
                return null;
            }
            else
            {
                return rtn;
            }
        }
        catch (Exception e)
        {
            log.error(e);
        }

        return null;
    }
    
   /**
    * is this property cacheable
    */
    public boolean isCacheable()
    {
        return true;
    }
}

/**
 * a jython velocity SET property
 */
class PySetProperty implements VelPropertySet
{
    private static final Logger log = Logger.getLogger(JythonUberspect.class.getName());
    private PyString propname;

    public PySetProperty(String propname)
    {
        this.propname = new PyString(propname);
    }

   /**
    * the name of the property/attribute
    */
    public String getMethodName()
    {
        return propname.toString();
    }
    
   /**
    * set the value of a property
    */
    public Object invoke(Object o, Object arg)
    {
        PyObject po = (PyObject)o;
        try
        {
            if (arg instanceof String)
            {
                po.__setattr__(propname, new PyString((String)arg));
            }
            else if (arg instanceof PyObject)
            {
                po.__setattr__(propname, (PyObject)arg);
            }
            else
            {
                log.error("unsupported argument type : " + arg.getClass().getName());
            }
        }
        catch (Exception e)
        {
            log.error(e);
        }

        return null;
    }
    
   /**
    * is this property cacheable
    */
    public boolean isCacheable()
    {
        return true;
    }
}

/**
 *  <p>
 *  An Iterator wrapper for a PySequence.
 *  </p>
 *  <p>
 *  WARNING : this class's operations are NOT synchronized.
 *  It is meant to be used in a single thread, newly created
 *  for each use in the #foreach() directive.
 *  If this is used or shared, synchronize in the
 *  next() method.
 *  </p>
 *
 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
 * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
 * @author Kent Johnson
 */
class PySequenceIterator implements Iterator
{
    /**
     * The objects to iterate.
     */
    private PySequence seq;

    /**
     * The current position and size in the array.
     */
    private int pos;
    private int size;

    /**
     * Creates a new iterator instance for the specified array.
     *
     * @param array The array for which an iterator is desired.
     */
    public PySequenceIterator(PySequence seq)
    {
        this.seq = seq;
        pos = 0;
        size = seq.__len__();
    }

    /**
     * Move to next element in the array.
     *
     * @return The next object in the array.
     */
    public Object next()
    {
        if (pos < size )
            return seq.__getitem__(pos++);
        
        throw new NoSuchElementException("No more elements: " + pos +
                                         " / " + size);
    }
    
    /**
     * Check to see if there is another element in the array.
     *
     * @return Whether there is another element.
     */
    public boolean hasNext()
    {
        return (pos < size );
    }

    /**
     * No op--merely added to satify the <code>Iterator</code> interface.
     */
    public void remove()
    {
        throw new UnsupportedOperationException();
    }
}

The following Jython class can be used to get an element by index from a list. You would put the listtool into the context (in your jython servlet) and then use it in the template as such: $listtool.get($myjythonlist, 4)

class listtool:
    def get(self, obj, idx):
        if type(obj) in (types.TupleType, types.ListType) and idx < len(obj):
            return obj[idx]
        else:
            return None
  • No labels