001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.ext.jython;
054:
055: import java.math.BigDecimal;
056: import java.math.BigInteger;
057: import java.util.ArrayList;
058: import java.util.Collection;
059: import java.util.Date;
060: import java.util.List;
061: import java.util.Map;
062:
063: import org.python.core.Py;
064: import org.python.core.PyDictionary;
065: import org.python.core.PyFloat;
066: import org.python.core.PyInteger;
067: import org.python.core.PyJavaInstance;
068: import org.python.core.PyLong;
069: import org.python.core.PyNone;
070: import org.python.core.PyObject;
071: import org.python.core.PySequence;
072: import org.python.core.PyString;
073: import org.python.core.PyStringMap;
074:
075: import freemarker.ext.beans.BeansWrapper;
076: import freemarker.ext.beans.DateModel;
077: import freemarker.ext.util.ModelCache;
078: import freemarker.ext.util.WrapperTemplateModel;
079: import freemarker.template.AdapterTemplateModel;
080: import freemarker.template.ObjectWrapper;
081: import freemarker.template.TemplateBooleanModel;
082: import freemarker.template.TemplateHashModel;
083: import freemarker.template.TemplateHashModelEx;
084: import freemarker.template.TemplateMethodModel;
085: import freemarker.template.TemplateMethodModelEx;
086: import freemarker.template.TemplateModel;
087: import freemarker.template.TemplateModelAdapter;
088: import freemarker.template.TemplateModelException;
089: import freemarker.template.TemplateNumberModel;
090: import freemarker.template.TemplateScalarModel;
091: import freemarker.template.TemplateSequenceModel;
092: import freemarker.template.utility.OptimizerUtil;
093:
094: /**
095: * An object wrapper that wraps Jython objects into FreeMarker template models
096: * and vice versa.
097: * @version $Id: JythonWrapper.java,v 1.23.2.1 2005/10/04 16:18:08 revusky Exp $
098: * @author Attila Szegedi
099: */
100: public class JythonWrapper implements ObjectWrapper {
101: private static final Class PYOBJECT_CLASS = PyObject.class;
102: public static final JythonWrapper INSTANCE = new JythonWrapper();
103:
104: private final ModelCache modelCache = new ModelCache(this );
105:
106: private boolean attributesShadowItems = true;
107:
108: public JythonWrapper() {
109: }
110:
111: /**
112: * Sets whether this wrapper caches model instances. Default is false.
113: * When set to true, calling {@link #wrap(Object)} multiple times for
114: * the same object will return the same model.
115: */
116: public void setUseCache(boolean useCache) {
117: modelCache.setUseCache(useCache);
118: }
119:
120: /**
121: * Sets whether attributes shadow items in wrapped objects. When true
122: * (this is the default value), <code>${object.name}</code> will first
123: * try to locate a python attribute with the specified name on the object
124: * using {@link PyObject#__findattr__(java.lang.String)}, and only if it
125: * doesn't find the attribute will it call
126: * {@link PyObject#__getitem__(org.python.core.PyObject)}.
127: * When set to false, the lookup order is reversed and items
128: * are looked up before attributes.
129: */
130: public synchronized void setAttributesShadowItems(
131: boolean attributesShadowItems) {
132: this .attributesShadowItems = attributesShadowItems;
133: }
134:
135: boolean isAttributesShadowItems() {
136: return attributesShadowItems;
137: }
138:
139: /**
140: * Wraps the passed Jython object into a FreeMarker template model. If
141: * the object is not a Jython object, it is first coerced into one using
142: * {@link Py#java2py(java.lang.Object)}. {@link PyDictionary} and {@link
143: * PyStringMap} are wrapped into a hash model, {@link PySequence}
144: * descendants are wrapped into a sequence model, {@link PyInteger}, {@link
145: * PyLong}, and {@link PyFloat} are wrapped into a number model. All objects
146: * are wrapped into a scalar model (using {@link Object#toString()} and a
147: * boolean model (using {@link PyObject#__nonzero__()}. For internal
148: * general-purpose {@link PyObject}s returned from a call to {@link
149: * #unwrap(TemplateModel)}, the template model that was passed to
150: * <code>unwrap</code> is returned.
151: */
152: public TemplateModel wrap(Object obj) {
153: if (obj == null) {
154: return null;
155: }
156: if (obj instanceof TemplateModel) {
157: return (TemplateModel) obj;
158:
159: }
160: if (obj instanceof TemplateModelAdapter) {
161: return ((TemplateModelAdapter) obj).getTemplateModel();
162: }
163: boolean asHash = false;
164: boolean asSequence = false;
165: if (obj instanceof PyJavaInstance) {
166: Object jobj = ((PyJavaInstance) obj)
167: .__tojava__(java.lang.Object.class);
168: // FreeMarker-aware, Jython-wrapped Java objects are left intact
169: if (jobj instanceof TemplateModel) {
170: return (TemplateModel) jobj;
171: }
172: if (jobj instanceof Map) {
173: asHash = true;
174: }
175: if (jobj instanceof Date) {
176: return new DateModel((Date) jobj, BeansWrapper
177: .getDefaultInstance());
178: } else if (jobj instanceof Collection) {
179: asSequence = true;
180: // FIXME: This is an ugly hack, but AFAIK, there's no better
181: // solution if we want to have Sets and other non-List
182: // collections managed by this layer, as Jython quite clearly
183: // doesn't support sets.
184: if (!(jobj instanceof List)) {
185: obj = new ArrayList((Collection) jobj);
186: }
187: }
188: }
189:
190: // If it's not a PyObject, first make a PyObject out of it.
191: if (!(obj instanceof PyObject)) {
192: obj = Py.java2py(obj);
193: }
194: if (asHash || obj instanceof PyDictionary
195: || obj instanceof PyStringMap) {
196: return modelCache.getInstance(obj, JythonHashModel.FACTORY);
197: }
198: if (asSequence || obj instanceof PySequence) {
199: return modelCache.getInstance(obj,
200: JythonSequenceModel.FACTORY);
201: }
202: if (obj instanceof PyInteger || obj instanceof PyLong
203: || obj instanceof PyFloat) {
204: return modelCache.getInstance(obj,
205: JythonNumberModel.FACTORY);
206: }
207: if (obj instanceof PyNone) {
208: return null;
209: }
210: return modelCache.getInstance(obj, JythonModel.FACTORY);
211: }
212:
213: /**
214: * Coerces a template model into a {@link PyObject}.
215: * @param model the model to coerce
216: * @return the coerced model.
217: * <ul>
218: * <li>
219: * <li>{@link AdapterTemplateModel}s (i.e. {@link freemarker.ext.beans.BeanModel}) are marshalled
220: * using the standard Python marshaller {@link Py#java2py(Object)} on
221: * the result of <code>getWrappedObject(PyObject.class)</code>s. The
222: * native JythonModel instances will just return the underlying PyObject.
223: * <li>All other models that are {@link TemplateScalarModel scalars} are
224: * marshalled as {@link PyString}.
225: * <li>All other models that are {@link TemplateNumberModel numbers} are
226: * marshalled using the standard Python marshaller
227: * {@link Py#java2py(Object)} on their underlying <code>Number</code></li>
228: * <li>All other models are marshalled to a generic internal
229: * <code>PyObject</code> subclass that'll correctly pass
230: * <code>__finditem__</code>, <code>__len__</code>,
231: * <code>__nonzero__</code>, and <code>__call__</code> invocations to
232: * appropriate hash, sequence, and method models.</li>
233: * </ul>
234: */
235: public PyObject unwrap(TemplateModel model)
236: throws TemplateModelException {
237: if (model instanceof AdapterTemplateModel) {
238: return Py.java2py(((AdapterTemplateModel) model)
239: .getAdaptedObject(PYOBJECT_CLASS));
240: }
241: if (model instanceof WrapperTemplateModel) {
242: return Py.java2py(((WrapperTemplateModel) model)
243: .getWrappedObject());
244: }
245:
246: // Scalars are marshalled to PyString.
247: if (model instanceof TemplateScalarModel) {
248: return new PyString(((TemplateScalarModel) model)
249: .getAsString());
250: }
251:
252: // Numbers are wrapped to Python built-in numeric types.
253: if (model instanceof TemplateNumberModel) {
254: Number number = ((TemplateNumberModel) model).getAsNumber();
255: if (number instanceof BigDecimal) {
256: number = OptimizerUtil
257: .optimizeNumberRepresentation(number);
258: }
259: if (number instanceof BigInteger) {
260: // Py.java2py can't automatically coerce a BigInteger into
261: // a PyLong. This will probably get fixed in later Jython
262: // release.
263: return new PyLong((BigInteger) number);
264: } else {
265: return Py.java2py(number);
266: }
267: }
268: // Return generic TemplateModel-to-Python adapter
269: return new TemplateModelToJythonAdapter(model);
270: }
271:
272: private class TemplateModelToJythonAdapter extends PyObject
273: implements TemplateModelAdapter {
274: private final TemplateModel model;
275:
276: TemplateModelToJythonAdapter(TemplateModel model) {
277: this .model = model;
278: }
279:
280: public TemplateModel getTemplateModel() {
281: return model;
282: }
283:
284: public PyObject __finditem__(PyObject key) {
285: if (key instanceof PyInteger) {
286: return __finditem__(((PyInteger) key).getValue());
287: }
288: return __finditem__(key.toString());
289: }
290:
291: public PyObject __finditem__(String key) {
292: if (model instanceof TemplateHashModel) {
293: try {
294: return unwrap(((TemplateHashModel) model).get(key));
295: } catch (TemplateModelException e) {
296: throw Py.JavaError(e);
297: }
298: }
299: throw Py.TypeError("item lookup on non-hash model ("
300: + getModelClass() + ")");
301: }
302:
303: public PyObject __finditem__(int index) {
304: if (model instanceof TemplateSequenceModel) {
305: try {
306: return unwrap(((TemplateSequenceModel) model)
307: .get(index));
308: } catch (TemplateModelException e) {
309: throw Py.JavaError(e);
310: }
311: }
312: throw Py.TypeError("item lookup on non-sequence model ("
313: + getModelClass() + ")");
314: }
315:
316: public PyObject __call__(PyObject args[], String keywords[]) {
317: if (model instanceof TemplateMethodModel) {
318: boolean isEx = model instanceof TemplateMethodModelEx;
319: List list = new ArrayList(args.length);
320: try {
321: for (int i = 0; i < args.length; ++i) {
322: list.add(isEx ? (Object) wrap(args[i])
323: : (Object) (args[i] == null ? null
324: : args[i].toString()));
325: }
326: return unwrap((TemplateModel) ((TemplateMethodModelEx) model)
327: .exec(list));
328: } catch (TemplateModelException e) {
329: throw Py.JavaError(e);
330: }
331: }
332: throw Py.TypeError("call of non-method model ("
333: + getModelClass() + ")");
334: }
335:
336: public int __len__() {
337: try {
338: if (model instanceof TemplateSequenceModel) {
339: return ((TemplateSequenceModel) model).size();
340: }
341: if (model instanceof TemplateHashModelEx) {
342: return ((TemplateHashModelEx) model).size();
343: }
344: } catch (TemplateModelException e) {
345: throw Py.JavaError(e);
346: }
347:
348: return 0;
349: }
350:
351: public boolean __nonzero__() {
352: try {
353: if (model instanceof TemplateBooleanModel) {
354: return ((TemplateBooleanModel) model)
355: .getAsBoolean();
356: }
357: if (model instanceof TemplateSequenceModel) {
358: return ((TemplateSequenceModel) model).size() > 0;
359: }
360: if (model instanceof TemplateHashModel) {
361: return !((TemplateHashModelEx) model).isEmpty();
362: }
363: } catch (TemplateModelException e) {
364: throw Py.JavaError(e);
365: }
366: return false;
367: }
368:
369: private String getModelClass() {
370: return model == null ? "null" : model.getClass().getName();
371: }
372: }
373: }
|