001/*
002 *  Copyright 2001-2013 Stephen Colebourne
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.joda.beans.integrate.freemarker;
017
018import org.joda.beans.Bean;
019import org.joda.beans.MetaProperty;
020
021import freemarker.ext.beans.BeanModel;
022import freemarker.ext.beans.BeansWrapper;
023import freemarker.template.AdapterTemplateModel;
024import freemarker.template.SimpleCollection;
025import freemarker.template.SimpleScalar;
026import freemarker.template.TemplateCollectionModel;
027import freemarker.template.TemplateHashModelEx;
028import freemarker.template.TemplateModel;
029import freemarker.template.TemplateModelException;
030import freemarker.template.WrappingTemplateModel;
031
032/**
033 * Template model converting a Joda-Bean to a Freemarker model.
034 * <p>
035 * Although this class is public, it should not normally be use directly.
036 * Follow the instructions in {@link FreemarkerObjectWrapper} to use this class.
037 */
038public class FreemarkerTemplateModel
039        extends WrappingTemplateModel
040        implements TemplateHashModelEx, AdapterTemplateModel {
041
042    /**
043     * The bean being wrapped.
044     */
045    private final Bean bean;
046
047    /**
048     * Creates an instance of the model.
049     * @param bean  the bean being wrapped, not null
050     * @param wrapper  the default wrapper for further wrapping, not null
051     */
052    public FreemarkerTemplateModel(final Bean bean, final FreemarkerObjectWrapper wrapper) {
053        super(wrapper);
054        this.bean = bean;
055    }
056
057    //-------------------------------------------------------------------------
058    /**
059     * Gets the value for the specified key, wrapping the result in another model.
060     * @param key  the property name, not null
061     * @return the model, null if not found
062     */
063    @Override
064    public TemplateModel get(String key) throws TemplateModelException {
065        MetaProperty<?> metaProperty = bean.metaBean().metaPropertyMap().get(key);
066        if (metaProperty == null) {
067            // try standard approach via BeanModel for non-bean properties and methods
068            BeanModel model = new BeanModel(bean, (BeansWrapper) getObjectWrapper());
069            TemplateModel result = model.get(key);
070            if (result instanceof SimpleScalar) {
071                // have to map empty string to null
072                String str = ((SimpleScalar) result).getAsString();
073                if (str == null || str.isEmpty()) {
074                    return null;
075                }
076            }
077            return result;
078        }
079        return wrap(metaProperty.get(bean));
080    }
081
082    /**
083     * Checks if there are no properties.
084     * @return true if no properties
085     */
086    @Override
087    public boolean isEmpty() {
088        return size() == 0;
089    }
090
091    /**
092     * Gets the number of properties.
093     * @return the number of properties
094     */
095    @Override
096    public int size() {
097        return bean.metaBean().metaPropertyCount();
098    }
099
100    /**
101     * Gets the full set of property names, allowing the bean to be accessed as a sequence.
102     * @return the property names, not null
103     */
104    @Override
105    public TemplateCollectionModel keys() {
106        return new SimpleCollection(bean.propertyNames(), getObjectWrapper());
107    }
108
109    /**
110     * Gets the full set of property values, allowing the bean to be accessed as a sequence.
111     * @return the wrapped property values, not null
112     */
113    @Override
114    public TemplateCollectionModel values() {
115        return new SimpleCollection(bean.metaBean().createPropertyMap(bean).flatten().values(), getObjectWrapper());
116    }
117
118    /**
119     * Unwraps the model, returning the bean.
120     * @param hint  the class hint
121     * @return the underlying bean, not null
122     */
123    @SuppressWarnings("rawtypes")
124    @Override
125    public Object getAdaptedObject(Class hint) {
126        return bean;
127    }
128
129}