freemarker.ext.jython.JythonWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of freemarker Show documentation
Show all versions of freemarker Show documentation
FreeMarker is a "template engine"; a generic tool to generate text output based on templates.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 freemarker.ext.jython;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import org.python.core.Py;
import org.python.core.PyDictionary;
import org.python.core.PyFloat;
import org.python.core.PyInteger;
import org.python.core.PyLong;
import org.python.core.PyObject;
import org.python.core.PySequence;
import org.python.core.PyString;
import org.python.core.PyStringMap;
import freemarker.ext.util.ModelCache;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.template.AdapterTemplateModel;
import freemarker.template.ObjectWrapper;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelAdapter;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.utility.OptimizerUtil;
/**
* An object wrapper that wraps Jython objects into FreeMarker template models
* and vice versa.
*/
public class JythonWrapper implements ObjectWrapper {
private static final Class PYOBJECT_CLASS = PyObject.class;
public static final JythonWrapper INSTANCE = new JythonWrapper();
private final ModelCache modelCache = new JythonModelCache(this);
private boolean attributesShadowItems = true;
public JythonWrapper() {
}
/**
* Sets whether this wrapper caches model instances. Default is false.
* When set to true, calling {@link #wrap(Object)} multiple times for
* the same object will return the same model.
*/
public void setUseCache(boolean useCache) {
modelCache.setUseCache(useCache);
}
/**
* Sets whether attributes shadow items in wrapped objects. When true
* (this is the default value), ${object.name}
will first
* try to locate a python attribute with the specified name on the object
* using {@link PyObject#__findattr__(java.lang.String)}, and only if it
* doesn't find the attribute will it call
* {@link PyObject#__getitem__(org.python.core.PyObject)}.
* When set to false, the lookup order is reversed and items
* are looked up before attributes.
*/
public synchronized void setAttributesShadowItems(boolean attributesShadowItems) {
this.attributesShadowItems = attributesShadowItems;
}
boolean isAttributesShadowItems() {
return attributesShadowItems;
}
/**
* Wraps the passed Jython object into a FreeMarker template model. If
* the object is not a Jython object, it's first coerced into one using
* {@link Py#java2py(java.lang.Object)}. {@link PyDictionary} and {@link
* PyStringMap} are wrapped into a hash model, {@link PySequence}
* descendants are wrapped into a sequence model, {@link PyInteger}, {@link
* PyLong}, and {@link PyFloat} are wrapped into a number model. All objects
* are wrapped into a scalar model (using {@link Object#toString()} and a
* boolean model (using {@link PyObject#__nonzero__()}. For internal
* general-purpose {@link PyObject}s returned from a call to {@link
* #unwrap(TemplateModel)}, the template model that was passed to
* unwrap
is returned.
*/
public TemplateModel wrap(Object obj) {
if (obj == null) {
return null;
}
return modelCache.getInstance(obj);
}
/**
* Coerces a template model into a {@link PyObject}.
* @param model the model to coerce
* @return the coerced model.
*
* -
*
- {@link AdapterTemplateModel}s (i.e. {@link freemarker.ext.beans.BeanModel}) are marshalled
* using the standard Python marshaller {@link Py#java2py(Object)} on
* the result of
getWrappedObject(PyObject.class)
s. The
* native JythonModel instances will just return the underlying PyObject.
* - All other models that are {@link TemplateScalarModel scalars} are
* marshalled as {@link PyString}.
*
- All other models that are {@link TemplateNumberModel numbers} are
* marshalled using the standard Python marshaller
* {@link Py#java2py(Object)} on their underlying
Number
* - All other models are marshalled to a generic internal
*
PyObject
subclass that'll correctly pass
* __finditem__
, __len__
,
* __nonzero__
, and __call__
invocations to
* appropriate hash, sequence, and method models.
*
*/
public PyObject unwrap(TemplateModel model) throws TemplateModelException {
if (model instanceof AdapterTemplateModel) {
return Py.java2py(((AdapterTemplateModel) model).getAdaptedObject(
PYOBJECT_CLASS));
}
if (model instanceof WrapperTemplateModel) {
return Py.java2py(((WrapperTemplateModel) model).getWrappedObject());
}
// Scalars are marshalled to PyString.
if (model instanceof TemplateScalarModel) {
return new PyString(((TemplateScalarModel) model).getAsString());
}
// Numbers are wrapped to Python built-in numeric types.
if (model instanceof TemplateNumberModel) {
Number number = ((TemplateNumberModel) model).getAsNumber();
if (number instanceof BigDecimal) {
number = OptimizerUtil.optimizeNumberRepresentation(number);
}
if (number instanceof BigInteger) {
// Py.java2py can't automatically coerce a BigInteger into
// a PyLong. This will probably get fixed in later Jython
// release.
return new PyLong((BigInteger) number);
} else {
return Py.java2py(number);
}
}
// Return generic TemplateModel-to-Python adapter
return new TemplateModelToJythonAdapter(model);
}
private class TemplateModelToJythonAdapter extends PyObject
implements TemplateModelAdapter {
private final TemplateModel model;
TemplateModelToJythonAdapter(TemplateModel model) {
this.model = model;
}
public TemplateModel getTemplateModel() {
return model;
}
@Override
public PyObject __finditem__(PyObject key) {
if (key instanceof PyInteger) {
return __finditem__(((PyInteger) key).getValue());
}
return __finditem__(key.toString());
}
@Override
public PyObject __finditem__(String key) {
if (model instanceof TemplateHashModel) {
try {
return unwrap(((TemplateHashModel) model).get(key));
} catch (TemplateModelException e) {
throw Py.JavaError(e);
}
}
throw Py.TypeError("item lookup on non-hash model (" + getModelClass() + ")");
}
@Override
public PyObject __finditem__(int index) {
if (model instanceof TemplateSequenceModel) {
try {
return unwrap(((TemplateSequenceModel) model).get(index));
} catch (TemplateModelException e) {
throw Py.JavaError(e);
}
}
throw Py.TypeError("item lookup on non-sequence model (" + getModelClass() + ")");
}
@Override
public PyObject __call__(PyObject args[], String keywords[]) {
if (model instanceof TemplateMethodModel) {
boolean isEx = model instanceof TemplateMethodModelEx;
List list = new ArrayList(args.length);
try {
for (int i = 0; i < args.length; ++i) {
list.add(
isEx
? (Object) wrap(args[i])
: (Object) (
args[i] == null
? null
: args[i].toString()));
}
return unwrap((TemplateModel) ((TemplateMethodModelEx) model).exec(list));
} catch (TemplateModelException e) {
throw Py.JavaError(e);
}
}
throw Py.TypeError("call of non-method model (" + getModelClass() + ")");
}
@Override
public int __len__() {
try {
if (model instanceof TemplateSequenceModel) {
return ((TemplateSequenceModel) model).size();
}
if (model instanceof TemplateHashModelEx) {
return ((TemplateHashModelEx) model).size();
}
} catch (TemplateModelException e) {
throw Py.JavaError(e);
}
return 0;
}
@Override
public boolean __nonzero__() {
try {
if (model instanceof TemplateBooleanModel) {
return ((TemplateBooleanModel) model).getAsBoolean();
}
if (model instanceof TemplateSequenceModel) {
return ((TemplateSequenceModel) model).size() > 0;
}
if (model instanceof TemplateHashModel) {
return !((TemplateHashModelEx) model).isEmpty();
}
} catch (TemplateModelException e) {
throw Py.JavaError(e);
}
return false;
}
private String getModelClass() {
return model == null ? "null" : model.getClass().getName();
}
}
}