All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.velocity.tools.generic.FieldTool Maven / Gradle / Ivy

Go to download

Generic tools that can be used in any context. PLEASE NOTE: this is a temporary fork to unblock projects migrating to Jakarta, but I won't continue maintaining it in the future as the Velocity team doesn't understand the value of Jakarta. I strongly suggest you plan a switch to a more modern template engine such as Thymeleaf.

The newest version!
package org.apache.velocity.tools.generic;

/*
 * 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.    
 */

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import org.apache.velocity.tools.ClassUtils;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.config.DefaultKey;
import org.apache.velocity.tools.config.ValidScope;

/**
 * 

* This is a simple tools class to allow easy access to static fields in a class, * such as string constants from within a template. Velocity will not introspect * for class fields (and won't in the future :), but writing setter/getter methods * to do this is a pain, so use this if you really have to access fields.

* *

Example uses in a template:

*
 *   ## here we access a constant in a class include in the configuration
 *     $field.COUNTER_NAME
 *
 *   ## here we dynamically lookup a class' fields to find another constant
 *     $field.in("org.com.SomeClass").ANOTHER_CONSTANT
 *
 *   ## here we pass an object instance in (an Integer in this case) and
 *   ## retrieve a static constant from that instance's class
 *     $field.in(0).MIN_VALUE
 *
 *   ## by default, once we've searched a class' fields, those fields stay
 *   ## available in the tool (change this by storeDynamicLookups="false")
 *   ## so here we get another constant from the Integer class
 *     $field.MAX_VALUE
 * 
*

Example tools.xml config:

*
 * <tools>
 *   <toolbox scope="application">
 *     <tool class="org.apache.velocity.tools.generic.FieldTool"
 *              include="org.apache.velocity.runtime.RuntimeConstants,com.org.MyConstants"/>
 *   </toolbox>
 * </tools>
 * 
* *

* Right now, this tool only gives access to public static fields. * It seems that anything else is too dangerous. This is for convenient access * to 'constants'. If you have fields that aren't static, * handle them by explicitly placing them into the context or writing a getter * method. * * @author Geir Magnusson Jr. * @author Nathan Bubna * @since VelocityTools 2.0 * @version $Id: FieldTool.java 463298 2006-10-12 16:10:32Z henning $ */ @DefaultKey("field") @ValidScope(Scope.APPLICATION) public class FieldTool extends SafeConfig { /** * The key used for specifying which classes should be inspected * for public static methods to be made available. */ public static final String INCLUDE_KEY = "include"; /** * The key used for specifying whether or not the tool should store * fields in classes dynamically looked up from within a template. * The default value is true. */ public static final String STORE_DYNAMIC_KEY = "storeDynamicLookups"; protected HashMap storage = new HashMap(); protected boolean storeDynamicLookups = true; protected void configure(ValueParser values) { // retrieve any classnames to be inspected and inspect them // *before* setting the storeDynamicLookups property! String[] classnames = values.getStrings(INCLUDE_KEY); if (classnames != null) { for (String classname : classnames) { // make sure we get results for each classname // since these come from the configuration, it's // an error if they're invalid if (in(classname) == null) { // shame that ClassNotFoundException is checked... throw new RuntimeException("Could not find "+classname+" in the classpath"); } } } // find out whether or not we should store dynamic lookups this.storeDynamicLookups = values.getBoolean(STORE_DYNAMIC_KEY, this.storeDynamicLookups); } /** * Returns the value for the specified field name as found * in the stored {@link Map} of field names to values (or placeholders). * Returns {@code null} if there is no matching field. * @param name field name * @return field value */ public Object get(String name) { Object o = storage.get(name); // if it was not a final field, get the current value if (o instanceof MutableField) { return ((MutableField)o).getValue(); } // if we have no value and the name looks like a path else if (o == null && name.indexOf('.') > 0) { // treat the name as a full fieldpath try { return ClassUtils.getFieldValue(name); } catch (Exception e) { getLog().debug("Unable to retrieve value of field at {}", name, e); } } // otherwise, we should have stored the value directly return o; } /** * Returns a {@link FieldToolSub} holding a {@link Map} * of all the public static field names to values (or a placeholder * if the value is not final) for the specified class(name). If the * {@link Class} with the specified name cannot be loaded, this will * return {@code null}, rather than throw an exception. * * @param classname target class name * @return {@link FieldToolSub} object * @see #in(Class clazz) */ public FieldToolSub in(String classname) { try { return in(ClassUtils.getClass(classname)); } catch (ClassNotFoundException cnfe) { return null; } } /** * Returns a {@link FieldToolSub} holding a {@link Map} * of all the public static field names to values (or a placeholder * if the value is not final) for the {@link Class} of the * specified Object. * @param instance target instance * @return {@link FieldToolSub} object * @see #in(Class clazz) */ public FieldToolSub in(Object instance) { if (instance == null) { return null; } return in(instance.getClass()); } /** * Returns a {@link FieldToolSub} holding a {@link Map} * of all the public static field names to values (or a placeholder * if the value is not final) for the specified {@link Class}. * @param clazz target class * @return {@link FieldToolSub} object */ public FieldToolSub in(Class clazz) { if (clazz == null) { return null; } Map results = inspect(clazz); if (storeDynamicLookups && !results.isEmpty()) { storage.putAll(results); } return new FieldToolSub(results); } /** * Looks for all public, static fields in the specified class and * stores their value (if final) or else a {@link MutableField} for * in a {@link Map} under the fields' names. This will never return * null, only an empty Map if there are no public static fields. * @param clazz target class * @return fields map */ protected Map inspect(Class clazz) { Map results = new HashMap(); for(Field field : clazz.getFields()) { // ignore anything non-public or non-static int mod = field.getModifiers(); if (Modifier.isStatic(mod) && Modifier.isPublic(mod)) { // make it easy to debug key collisions if (getLog().isDebugEnabled() && results.containsKey(field.getName())) { getLog().debug("{} is being overridden by {}", field.getName(), clazz.getName()); } // if the field is final if (Modifier.isFinal(mod)) { // just get the value now results.put(field.getName(), retrieve(field, clazz)); } else { // put a wrapper with easy access results.put(field.getName(), new MutableField(field, clazz)); } } } return results; } /** * Retrieves and returns the value of the specified {@link Field} * in the specified {@link Class}. Returns {@code null} in case of failure. * * @param field target field * @param clazz target class * @return field value */ protected Object retrieve(Field field, Class clazz) { try { return field.get(clazz); } catch(IllegalAccessException iae) { getLog().warn("IllegalAccessException while trying to access {}", field.getName(), iae); return null; } } /** * Holds a {@link Map} of results for a particular class. * This exists simply to enable the $field.in("class.Name").FOO * syntax, even when storeDynamicLookups is set to false. * NOTE: we can't simply return the results Map when the in() * methods are called, because the Map contains placeholders * for any mutable fields found. We want to put off reading non-final * field values to the last moment, in case their values change. */ public static class FieldToolSub { private final Map results; public FieldToolSub(Map results) { if (results == null) { throw new NullPointerException("Cannot create sub with null field results map"); } this.results = results; } public Object get(String name) { Object o = results.get(name); // if it was not a final field, get the current value if (o instanceof MutableField) { return ((MutableField)o).getValue(); } // otherwise, we should have stored the value directly return o; } /** * Return the toString() value of the internal results Map for this sub. */ public String toString() { return results.toString(); } } /** * Holds a {@link Field} and {@link Class} reference for later * retrieval of the value of a field that is not final and may * change at different lookups. */ public class MutableField { private final Class clazz; private final Field field; public MutableField(Field f, Class c) { if (f == null || c == null) { throw new NullPointerException("Both Class and Field must NOT be null"); } field = f; clazz = c; } public Object getValue() { return retrieve(field, clazz); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy