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

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import freemarker.cache.AndMatcher;
import freemarker.cache.ConditionalTemplateConfigurationFactory;
import freemarker.cache.FileExtensionMatcher;
import freemarker.cache.FileNameGlobMatcher;
import freemarker.cache.FirstMatchTemplateConfigurationFactory;
import freemarker.cache.MergingTemplateConfigurationFactory;
import freemarker.cache.NotMatcher;
import freemarker.cache.OrMatcher;
import freemarker.cache.PathGlobMatcher;
import freemarker.cache.PathRegexMatcher;
import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.SimpleObjectWrapper;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.Version;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.WriteProtectable;

Don't use this; used internally by FreeMarker, might changes without notice. Evaluates object builder expressions used in configuration Properties. It should be replaced with FTL later (when it was improved to be practical for this), so the syntax should be a subset of the future FTL syntax. This is also why this syntax is restrictive; it shouldn't accept anything that FTL will not.
/** * Don't use this; used internally by FreeMarker, might changes without notice. * * Evaluates object builder expressions used in configuration {@link Properties}. * It should be replaced with FTL later (when it was improved to be practical for this), so the syntax should be * a subset of the future FTL syntax. This is also why this syntax is restrictive; it shouldn't accept anything that * FTL will not. */
// Java 5: use generics for expectedClass // Java 5: Introduce ObjectBuilder interface public class _ObjectBuilderSettingEvaluator { private static final String INSTANCE_FIELD_NAME = "INSTANCE"; private static final String BUILD_METHOD_NAME = "build"; private static final String BUILDER_CLASS_POSTFIX = "Builder"; private static Map<String,String> SHORTHANDS; private static final Object VOID = new Object(); private final String src; private final Class expectedClass; private final boolean allowNull; private final _SettingEvaluationEnvironment env; // Parser state: private int pos; // Parsing results: private boolean modernMode = false; private _ObjectBuilderSettingEvaluator( String src, int pos, Class expectedClass, boolean allowNull, _SettingEvaluationEnvironment env) { this.src = src; this.pos = pos; this.expectedClass = expectedClass; this.allowNull = allowNull; this.env = env; } public static Object eval(String src, Class expectedClass, boolean allowNull, _SettingEvaluationEnvironment env) throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException, IllegalAccessException { return new _ObjectBuilderSettingEvaluator(src, 0, expectedClass, allowNull, env).eval(); }
Used for getting a list of setting assignments (like (x=1, y=2)) from an existing string, and apply it on an existing bean.
Returns:The location of the next character to process.
/** * Used for getting a list of setting assignments (like {@code (x=1, y=2)}) from an existing string, and apply it on * an existing bean. * * @return The location of the next character to process. */
public static int configureBean( String argumentListSrc, int posAfterOpenParen, Object bean, _SettingEvaluationEnvironment env) throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException, IllegalAccessException { return new _ObjectBuilderSettingEvaluator( argumentListSrc, posAfterOpenParen, bean.getClass(), true, env).configureBean(bean); } private Object eval() throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException, IllegalAccessException { Object value; skipWS(); try { value = ensureEvaled(fetchValue(false, true, false, true)); } catch (LegacyExceptionWrapperSettingEvaluationExpression e) { e.rethrowLegacy(); value = null; // newer reached } skipWS(); if (pos != src.length()) { throw new _ObjectBuilderSettingEvaluationException("end-of-expression", src, pos); } if (value == null && !allowNull) { throw new _ObjectBuilderSettingEvaluationException("Value can't be null."); } if (value != null && !expectedClass.isInstance(value)) { throw new _ObjectBuilderSettingEvaluationException("The resulting object (of class " + value.getClass() + ") is not a(n) " + expectedClass.getName() + "."); } return value; } private int configureBean(Object bean) throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException, IllegalAccessException { final PropertyAssignmentsExpression propAssignments = new PropertyAssignmentsExpression(bean); fetchParameterListInto(propAssignments); skipWS(); propAssignments.eval(); return pos; } private Object ensureEvaled(Object value) throws _ObjectBuilderSettingEvaluationException { return value instanceof SettingExpression ? ((SettingExpression) value).eval() : value; } private Object fetchBuilderCall(boolean optional, boolean topLevel) throws _ObjectBuilderSettingEvaluationException { int startPos = pos; BuilderCallExpression exp = new BuilderCallExpression(); // We need the canBeStaticField/mustBeStaticFiled complication to deal with legacy syntax where parentheses // weren't required for constructor calls. exp.canBeStaticField = true; final String fetchedClassName = fetchClassName(optional); { if (fetchedClassName == null) { if (!optional) { throw new _ObjectBuilderSettingEvaluationException("class name", src, pos); } return VOID; } exp.className = shorthandToFullQualified(fetchedClassName); if (!fetchedClassName.equals(exp.className)) { // Before 2.3.21 only full-qualified class names were allowed modernMode = true; exp.canBeStaticField = false; } } skipWS(); char openParen = fetchOptionalChar("("); // Only the top-level expression can omit the "(...)" if (openParen == 0 && !topLevel) { if (fetchedClassName.indexOf('.') != -1) { exp.mustBeStaticField = true; } else { pos = startPos; return VOID; } } if (openParen != 0) { fetchParameterListInto(exp); exp.canBeStaticField = false; } return exp; } private void fetchParameterListInto(ExpressionWithParameters exp) throws _ObjectBuilderSettingEvaluationException { // Before 2.3.21 there was no parameter list modernMode = true; skipWS(); if (fetchOptionalChar(")") != ')') { do { skipWS(); Object paramNameOrValue = fetchValue(false, false, true, false); if (paramNameOrValue != VOID) { skipWS(); if (paramNameOrValue instanceof Name) { exp.namedParamNames.add(((Name) paramNameOrValue).name); skipWS(); fetchRequiredChar("="); skipWS(); Object paramValue = fetchValue(false, false, true, true); exp.namedParamValues.add(ensureEvaled(paramValue)); } else { if (!exp.namedParamNames.isEmpty()) { throw new _ObjectBuilderSettingEvaluationException( "Positional parameters must precede named parameters"); } if (!exp.getAllowPositionalParameters()) { throw new _ObjectBuilderSettingEvaluationException( "Positional parameters not supported here"); } exp.positionalParamValues.add(ensureEvaled(paramNameOrValue)); } skipWS(); } } while (fetchRequiredChar(",)") == ','); } } private Object fetchValue(boolean optional, boolean topLevel, boolean resultCoerced, boolean resolveVariables) throws _ObjectBuilderSettingEvaluationException { if (pos < src.length()) { Object val = fetchNumberLike(true, resultCoerced); if (val != VOID) { return val; } val = fetchStringLiteral(true); if (val != VOID) { return val; } val = fetchListLiteral(true); if (val != VOID) { return val; } val = fetchMapLiteral(true); if (val != VOID) { return val; } val = fetchBuilderCall(true, topLevel); if (val != VOID) { return val; } String name = fetchSimpleName(true); if (name != null) { val = keywordToValueOrVoid(name); if (val != VOID) { return val; } if (resolveVariables) { // Not supported currently... throw new _ObjectBuilderSettingEvaluationException("Can't resolve variable reference: " + name); } else { return new Name(name); } } } if (optional) { return VOID; } else { throw new _ObjectBuilderSettingEvaluationException("value or name", src, pos); } } private boolean isKeyword(String name) { return keywordToValueOrVoid(name) != VOID; } private Object keywordToValueOrVoid(String name) { if (name.equals("true")) return Boolean.TRUE; if (name.equals("false")) return Boolean.FALSE; if (name.equals("null")) return null; return VOID; } private String fetchSimpleName(boolean optional) throws _ObjectBuilderSettingEvaluationException { char c = pos < src.length() ? src.charAt(pos) : 0; if (!isIdentifierStart(c)) { if (optional) { return null; } else { throw new _ObjectBuilderSettingEvaluationException("class name", src, pos); } } int startPos = pos; pos++; seekClassNameEnd: while (true) { if (pos == src.length()) { break seekClassNameEnd; } c = src.charAt(pos); if (!isIdentifierMiddle(c)) { break seekClassNameEnd; } pos++; } return src.substring(startPos, pos); } private String fetchClassName(boolean optional) throws _ObjectBuilderSettingEvaluationException { int startPos = pos; StringBuilder sb = new StringBuilder(); do { String name = fetchSimpleName(true); if (name == null) { if (!optional) { throw new _ObjectBuilderSettingEvaluationException("name", src, pos); } else { pos = startPos; return null; } } sb.append(name); skipWS(); if (pos >= src.length() || src.charAt(pos) != '.') { break; } sb.append('.'); pos++; skipWS(); } while (true); String className = sb.toString(); if (isKeyword(className)) { pos = startPos; return null; } return className; } private Object fetchNumberLike(boolean optional, boolean resultCoerced) throws _ObjectBuilderSettingEvaluationException { int startPos = pos; boolean isVersion = false; boolean hasDot = false; seekTokenEnd: while (true) { if (pos == src.length()) { break seekTokenEnd; } char c = src.charAt(pos); if (c == '.') { if (hasDot) { // More than one dot isVersion = true; } else { hasDot = true; } } else if (!(isASCIIDigit(c) || c == '-')) { break seekTokenEnd; } pos++; } if (startPos == pos) { if (optional) { return VOID; } else { throw new _ObjectBuilderSettingEvaluationException("number-like", src, pos); } } String numStr = src.substring(startPos, pos); if (isVersion) { try { return new Version(numStr); } catch (IllegalArgumentException e) { throw new _ObjectBuilderSettingEvaluationException("Malformed version number: " + numStr, e); } } else { // For example, in 1.0f, numStr is "1.0", and typePostfix is "f". String typePostfix = null; seekTypePostfixEnd: while (true) { if (pos == src.length()) { break seekTypePostfixEnd; } char c = src.charAt(pos); if (Character.isLetter(c)) { if (typePostfix == null) { typePostfix = String.valueOf(c); } else { typePostfix += c; } } else { break seekTypePostfixEnd; } pos++; } try { if (numStr.endsWith(".")) { throw new NumberFormatException("A number can't end with a dot"); } if (numStr.startsWith(".") || numStr.startsWith("-.") || numStr.startsWith("+.")) { throw new NumberFormatException("A number can't start with a dot"); } if (typePostfix == null) { // Auto-detect type if (numStr.indexOf('.') == -1) { BigInteger biNum = new BigInteger(numStr); final int bitLength = biNum.bitLength(); // Doesn't include sign bit if (bitLength <= 31) { return Integer.valueOf(biNum.intValue()); } else if (bitLength <= 63) { return Long.valueOf(biNum.longValue()); } else { return biNum; } } else { if (resultCoerced) { // The FTL way (BigDecimal is loseless, and it will be coerced to the target type later): return new BigDecimal(numStr); } else { // The Java way (lossy but familiar): return Double.valueOf(numStr); } } } else { // Has explicitly specified type if (typePostfix.equalsIgnoreCase("l")) { return Long.valueOf(numStr); } else if (typePostfix.equalsIgnoreCase("bi")) { return new BigInteger(numStr); } else if (typePostfix.equalsIgnoreCase("bd")) { return new BigDecimal(numStr); } else if (typePostfix.equalsIgnoreCase("d")) { return Double.valueOf(numStr); } else if (typePostfix.equalsIgnoreCase("f")) { return Float.valueOf(numStr); } else { throw new _ObjectBuilderSettingEvaluationException( "Unrecognized number type postfix: " + typePostfix); } } } catch (NumberFormatException e) { throw new _ObjectBuilderSettingEvaluationException("Malformed number: " + numStr, e); } } } private Object fetchStringLiteral(boolean optional) throws _ObjectBuilderSettingEvaluationException { int startPos = pos; char q = 0; boolean afterEscape = false; boolean raw = false; seekTokenEnd: while (true) { if (pos == src.length()) { if (q != 0) { // We had an open quotation throw new _ObjectBuilderSettingEvaluationException(String.valueOf(q), src, pos); } break seekTokenEnd; } char c = src.charAt(pos); if (q == 0) { if (c == 'r' && (pos + 1 < src.length())) { // Maybe it's like r"foo\bar" raw = true; c = src.charAt(pos + 1); } if (c == '\'') { q = '\''; } else if (c == '"') { q = '"'; } else { break seekTokenEnd; } if (raw) { // because of the preceding "r" pos++; } } else { if (!afterEscape) { if (c == '\\' && !raw) { afterEscape = true; } else if (c == q) { break seekTokenEnd; } else if (c == '{') { char prevC = src.charAt(pos - 1); if (prevC == '$' || prevC == '#') { throw new _ObjectBuilderSettingEvaluationException( "${...} and #{...} aren't allowed here."); } } } else { afterEscape = false; } } pos++; } if (startPos == pos) { if (optional) { return VOID; } else { throw new _ObjectBuilderSettingEvaluationException("string literal", src, pos); } } final String sInside = src.substring(startPos + (raw ? 2 : 1), pos); try { pos++; // skip closing quotation mark return raw ? sInside : StringUtil.FTLStringLiteralDec(sInside); } catch (ParseException e) { throw new _ObjectBuilderSettingEvaluationException("Malformed string literal: " + sInside, e); } } private Object fetchListLiteral(boolean optional) throws _ObjectBuilderSettingEvaluationException { if (pos == src.length() || src.charAt(pos) != '[') { if (!optional) { throw new _ObjectBuilderSettingEvaluationException("[", src, pos); } return VOID; } pos++; ListExpression listExp = new ListExpression(); while (true) { skipWS(); if (fetchOptionalChar("]") != 0) { return listExp; } if (listExp.itemCount() != 0) { fetchRequiredChar(","); skipWS(); } listExp.addItem(fetchValue(false, false, false, true)); skipWS(); } } private Object fetchMapLiteral(boolean optional) throws _ObjectBuilderSettingEvaluationException { if (pos == src.length() || src.charAt(pos) != '{') { if (!optional) { throw new _ObjectBuilderSettingEvaluationException("{", src, pos); } return VOID; } pos++; MapExpression mapExp = new MapExpression(); while (true) { skipWS(); if (fetchOptionalChar("}") != 0) { return mapExp; } if (mapExp.itemCount() != 0) { fetchRequiredChar(","); skipWS(); } Object key = fetchValue(false, false, false, true); skipWS(); fetchRequiredChar(":"); skipWS(); Object value = fetchValue(false, false, false, true); mapExp.addItem(new KeyValuePair(key, value)); skipWS(); } } private void skipWS() { while (true) { if (pos == src.length()) { return; } char c = src.charAt(pos); if (!Character.isWhitespace(c)) { return; } pos++; } } private char fetchOptionalChar(String expectedChars) throws _ObjectBuilderSettingEvaluationException { return fetchChar(expectedChars, true); } private char fetchRequiredChar(String expectedChars) throws _ObjectBuilderSettingEvaluationException { return fetchChar(expectedChars, false); } private char fetchChar(String expectedChars, boolean optional) throws _ObjectBuilderSettingEvaluationException { char c = pos < src.length() ? src.charAt(pos) : 0; if (expectedChars.indexOf(c) != -1) { pos++; return c; } else if (optional) { return 0; } else { StringBuilder sb = new StringBuilder(); for (int i = 0; i < expectedChars.length(); i++) { if (i != 0) { sb.append(" or "); } sb.append(StringUtil.jQuote(expectedChars.substring(i, i + 1))); } throw new _ObjectBuilderSettingEvaluationException( sb.toString(), src, pos); } } private boolean isASCIIDigit(char c) { return c >= '0' && c <= '9'; } private boolean isIdentifierStart(char c) { return Character.isLetter(c) || c == '_' || c == '$'; } private boolean isIdentifierMiddle(char c) { return isIdentifierStart(c) || isASCIIDigit(c); } private static synchronized String shorthandToFullQualified(String className) { if (SHORTHANDS == null) { SHORTHANDS = new HashMap/*<String,String>*/(); addWithSimpleName(SHORTHANDS, DefaultObjectWrapper.class); addWithSimpleName(SHORTHANDS, BeansWrapper.class); addWithSimpleName(SHORTHANDS, SimpleObjectWrapper.class); addWithSimpleName(SHORTHANDS, TemplateConfiguration.class); addWithSimpleName(SHORTHANDS, PathGlobMatcher.class); addWithSimpleName(SHORTHANDS, FileNameGlobMatcher.class); addWithSimpleName(SHORTHANDS, FileExtensionMatcher.class); addWithSimpleName(SHORTHANDS, PathRegexMatcher.class); addWithSimpleName(SHORTHANDS, AndMatcher.class); addWithSimpleName(SHORTHANDS, OrMatcher.class); addWithSimpleName(SHORTHANDS, NotMatcher.class); addWithSimpleName(SHORTHANDS, ConditionalTemplateConfigurationFactory.class); addWithSimpleName(SHORTHANDS, MergingTemplateConfigurationFactory.class); addWithSimpleName(SHORTHANDS, FirstMatchTemplateConfigurationFactory.class); addWithSimpleName(SHORTHANDS, HTMLOutputFormat.class); addWithSimpleName(SHORTHANDS, XHTMLOutputFormat.class); addWithSimpleName(SHORTHANDS, XMLOutputFormat.class); addWithSimpleName(SHORTHANDS, RTFOutputFormat.class); addWithSimpleName(SHORTHANDS, PlainTextOutputFormat.class); addWithSimpleName(SHORTHANDS, UndefinedOutputFormat.class); addWithSimpleName(SHORTHANDS, Locale.class); SHORTHANDS.put("TimeZone", "freemarker.core._TimeZone"); // For accessing static fields: addWithSimpleName(SHORTHANDS, Configuration.class); } String fullClassName = SHORTHANDS.get(className); return fullClassName == null ? className : fullClassName; } private static void addWithSimpleName(Map map, Class<?> pClass) { map.put(pClass.getSimpleName(), pClass.getName()); } private void setJavaBeanProperties(Object bean, List/*<String>*/ namedParamNames, List/*<Object>*/ namedParamValues) throws _ObjectBuilderSettingEvaluationException { if (namedParamNames.isEmpty()) { return; } final Class cl = bean.getClass(); Map/*<String,Method>*/ beanPropSetters; try { PropertyDescriptor[] propDescs = Introspector.getBeanInfo(cl).getPropertyDescriptors(); beanPropSetters = new HashMap(propDescs.length * 4 / 3, 1.0f); for (int i = 0; i < propDescs.length; i++) { PropertyDescriptor propDesc = propDescs[i]; final Method writeMethod = propDesc.getWriteMethod(); if (writeMethod != null) { beanPropSetters.put(propDesc.getName(), writeMethod); } } } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException("Failed to inspect " + cl.getName() + " class", e); } TemplateHashModel beanTM = null; for (int i = 0; i < namedParamNames.size(); i++) { String name = (String) namedParamNames.get(i); if (!beanPropSetters.containsKey(name)) { throw new _ObjectBuilderSettingEvaluationException( "The " + cl.getName() + " class has no writeable JavaBeans property called " + StringUtil.jQuote(name) + "."); } Method beanPropSetter = (Method) beanPropSetters.put(name, null); if (beanPropSetter == null) { throw new _ObjectBuilderSettingEvaluationException( "JavaBeans property " + StringUtil.jQuote(name) + " is set twice."); } try { if (beanTM == null) { TemplateModel wrappedObj = env.getObjectWrapper().wrap(bean); if (!(wrappedObj instanceof TemplateHashModel)) { throw new _ObjectBuilderSettingEvaluationException( "The " + cl.getName() + " class is not a wrapped as TemplateHashModel."); } beanTM = (TemplateHashModel) wrappedObj; } TemplateModel m = beanTM.get(beanPropSetter.getName()); if (m == null) { throw new _ObjectBuilderSettingEvaluationException( "Can't find " + beanPropSetter + " as FreeMarker method."); } if (!(m instanceof TemplateMethodModelEx)) { throw new _ObjectBuilderSettingEvaluationException( StringUtil.jQuote(beanPropSetter.getName()) + " wasn't a TemplateMethodModelEx."); } List/*TemplateModel*/ args = new ArrayList(); args.add(env.getObjectWrapper().wrap(namedParamValues.get(i))); ((TemplateMethodModelEx) m).exec(args); } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException( "Failed to set " + StringUtil.jQuote(name), e); } } } private static class Name { public Name(String name) { this.name = name; } private final String name; } private abstract static class SettingExpression { abstract Object eval() throws _ObjectBuilderSettingEvaluationException; } private abstract class ExpressionWithParameters extends SettingExpression { protected List positionalParamValues = new ArrayList(); protected List/*<String>*/ namedParamNames = new ArrayList(); protected List/*<Object>*/ namedParamValues = new ArrayList(); protected abstract boolean getAllowPositionalParameters(); } private class ListExpression extends SettingExpression { private List<Object> items = new ArrayList(); void addItem(Object item) { items.add(item); } public int itemCount() { return items.size(); } @Override Object eval() throws _ObjectBuilderSettingEvaluationException { ArrayList res = new ArrayList(items.size()); for (Object item : items) { res.add(ensureEvaled(item)); } return res; } } private class MapExpression extends SettingExpression { private List<KeyValuePair> items = new ArrayList(); void addItem(KeyValuePair item) { items.add(item); } public int itemCount() { return items.size(); } @Override Object eval() throws _ObjectBuilderSettingEvaluationException { LinkedHashMap res = new LinkedHashMap(items.size() * 4 / 3, 1f); for (KeyValuePair item : items) { Object key = ensureEvaled(item.key); if (key == null) { throw new _ObjectBuilderSettingEvaluationException("Map can't use null as key."); } res.put(key, ensureEvaled(item.value)); } return res; } } private static class KeyValuePair { private final Object key; private final Object value; public KeyValuePair(Object key, Object value) { this.key = key; this.value = value; } } private class BuilderCallExpression extends ExpressionWithParameters { private String className; private boolean canBeStaticField; private boolean mustBeStaticField; @Override Object eval() throws _ObjectBuilderSettingEvaluationException { if (mustBeStaticField) { if (!canBeStaticField) { throw new BugException(); } return getStaticFieldValue(className); } Class cl; if (!modernMode) { try { try { return ClassUtil.forName(className).newInstance(); } catch (InstantiationException e) { throw new LegacyExceptionWrapperSettingEvaluationExpression(e); } catch (IllegalAccessException e) { throw new LegacyExceptionWrapperSettingEvaluationExpression(e); } catch (ClassNotFoundException e) { throw new LegacyExceptionWrapperSettingEvaluationExpression(e); } } catch (LegacyExceptionWrapperSettingEvaluationExpression e) { if (!canBeStaticField || className.indexOf('.') == -1) { throw e; } // Silently try to interpret className as static filed, throw the original exception if that fails. try { return getStaticFieldValue(className); } catch (_ObjectBuilderSettingEvaluationException e2) { throw e; } } } boolean clIsBuilderClass; try { cl = ClassUtil.forName(className + BUILDER_CLASS_POSTFIX); clIsBuilderClass = true; } catch (ClassNotFoundException e) { clIsBuilderClass = false; try { cl = ClassUtil.forName(className); } catch (Exception e2) { boolean failedToGetAsStaticField; if (canBeStaticField) { // Try to interpret className as static filed: try { return getStaticFieldValue(className); } catch (_ObjectBuilderSettingEvaluationException e3) { // Suppress it failedToGetAsStaticField = true; } } else { failedToGetAsStaticField = false; } throw new _ObjectBuilderSettingEvaluationException( "Failed to get class " + StringUtil.jQuote(className) + (failedToGetAsStaticField ? " (also failed to resolve name as static field)" : "") + ".", e2); } } if (!clIsBuilderClass && hasNoParameters()) { try { Field f = cl.getField(INSTANCE_FIELD_NAME); if ((f.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) == (Modifier.PUBLIC | Modifier.STATIC)) { return f.get(null); } } catch (NoSuchFieldException e) { // Expected } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException( "Error when trying to access " + StringUtil.jQuote(className) + "." + INSTANCE_FIELD_NAME, e); } } // Create the object to return or its builder: Object constructorResult = callConstructor(cl); // Named parameters will set JavaBeans properties: setJavaBeanProperties(constructorResult, namedParamNames, namedParamValues); final Object result; if (clIsBuilderClass) { result = callBuild(constructorResult); } else { if (constructorResult instanceof WriteProtectable) { ((WriteProtectable) constructorResult).writeProtect(); } result = constructorResult; } return result; } private Object getStaticFieldValue(String dottedName) throws _ObjectBuilderSettingEvaluationException { int lastDotIdx = dottedName.lastIndexOf('.'); if (lastDotIdx == -1) { throw new IllegalArgumentException(); } String className = shorthandToFullQualified(dottedName.substring(0, lastDotIdx)); String fieldName = dottedName.substring(lastDotIdx + 1); Class<?> cl; try { cl = ClassUtil.forName(className); } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException( "Failed to get field's parent class, " + StringUtil.jQuote(className) + ".", e); } Field field; try { field = cl.getField(fieldName); } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException( "Failed to get field " + StringUtil.jQuote(fieldName) + " from class " + StringUtil.jQuote(className) + ".", e); } if ((field.getModifiers() & Modifier.STATIC) == 0) { throw new _ObjectBuilderSettingEvaluationException("Referred field isn't static: " + field); } if ((field.getModifiers() & Modifier.PUBLIC) == 0) { throw new _ObjectBuilderSettingEvaluationException("Referred field isn't public: " + field); } if (field.getName().equals(INSTANCE_FIELD_NAME)) { throw new _ObjectBuilderSettingEvaluationException( "The " + INSTANCE_FIELD_NAME + " field is only accessible through pseudo-constructor call: " + className + "()"); } try { return field.get(null); } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException("Failed to get field value: " + field, e); } } private Object callConstructor(Class cl) throws _ObjectBuilderSettingEvaluationException { if (hasNoParameters()) { // No need to create ObjectWrapper try { return cl.newInstance(); } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException( "Failed to call " + cl.getName() + " 0-argument constructor", e); } } else { BeansWrapper ow = env.getObjectWrapper(); List/*<TemplateModel>*/ tmArgs = new ArrayList(positionalParamValues.size()); for (int i = 0; i < positionalParamValues.size(); i++) { try { tmArgs.add(ow.wrap(positionalParamValues.get(i))); } catch (TemplateModelException e) { throw new _ObjectBuilderSettingEvaluationException("Failed to wrap arg #" + (i + 1), e); } } try { return ow.newInstance(cl, tmArgs); } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException( "Failed to call " + cl.getName() + " constructor", e); } } } private Object callBuild(Object constructorResult) throws _ObjectBuilderSettingEvaluationException { final Class cl = constructorResult.getClass(); Method buildMethod; try { buildMethod = constructorResult.getClass().getMethod(BUILD_METHOD_NAME, (Class[]) null); } catch (NoSuchMethodException e) { throw new _ObjectBuilderSettingEvaluationException("The " + cl.getName() + " builder class must have a public " + BUILD_METHOD_NAME + "() method", e); } catch (Exception e) { throw new _ObjectBuilderSettingEvaluationException("Failed to get the " + BUILD_METHOD_NAME + "() method of the " + cl.getName() + " builder class", e); } try { return buildMethod.invoke(constructorResult, (Object[]) null); } catch (Exception e) { Throwable cause; if (e instanceof InvocationTargetException) { cause = ((InvocationTargetException) e).getTargetException(); } else { cause = e; } throw new _ObjectBuilderSettingEvaluationException("Failed to call " + BUILD_METHOD_NAME + "() method on " + cl.getName() + " instance", cause); } } private boolean hasNoParameters() { return positionalParamValues.isEmpty() && namedParamValues.isEmpty(); } @Override protected boolean getAllowPositionalParameters() { return true; } } private class PropertyAssignmentsExpression extends ExpressionWithParameters { private final Object bean; public PropertyAssignmentsExpression(Object bean) { this.bean = bean; } @Override Object eval() throws _ObjectBuilderSettingEvaluationException { setJavaBeanProperties(bean, namedParamNames, namedParamValues); return bean; } @Override protected boolean getAllowPositionalParameters() { return false; } } private static class LegacyExceptionWrapperSettingEvaluationExpression extends _ObjectBuilderSettingEvaluationException { public LegacyExceptionWrapperSettingEvaluationExpression(Throwable cause) { super("Legacy operation failed", cause); if (!( (cause instanceof ClassNotFoundException) || (cause instanceof InstantiationException) || (cause instanceof IllegalAccessException) )) { throw new IllegalArgumentException(); } } public void rethrowLegacy() throws ClassNotFoundException, InstantiationException, IllegalAccessException { Throwable cause = getCause(); if (cause instanceof ClassNotFoundException) throw (ClassNotFoundException) cause; if (cause instanceof InstantiationException) throw (InstantiationException) cause; if (cause instanceof IllegalAccessException) throw (IllegalAccessException) cause; throw new BugException(); } } }