You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2015/12/30 19:11:26 UTC
[06/35] incubator-freemarker git commit: In the mini-language used
for configuring FreeMarker from java.util.Properties or other string-only
sources (but not used inside templates): public static fields can be referred
like com.example.MyClass.MY_CONSTAN
In the mini-language used for configuring FreeMarker from java.util.Properties or other string-only sources (but not used inside templates): public static fields can be referred like com.example.MyClass.MY_CONSTANT or Configuration.AUTO_DETECT_TAG_SYNTAX.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/fe799f5d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/fe799f5d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/fe799f5d
Branch: refs/heads/2.3
Commit: fe799f5d382fad7e35be634f660e73f62d6ec9e8
Parents: 731be2c
Author: ddekany <dd...@apache.org>
Authored: Mon Dec 21 02:05:03 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Dec 21 02:05:16 2015 +0100
----------------------------------------------------------------------
src/main/java/freemarker/core/Configurable.java | 6 +-
.../core/_ObjectBuilderSettingEvaluator.java | 123 ++++++++++++++++---
src/manual/en_US/book.xml | 18 ++-
.../core/ObjectBuilderSettingsTest.java | 43 +++++++
4 files changed, 167 insertions(+), 23 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index ba5d8c8..9c82428 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -1887,6 +1887,8 @@ public class Configurable {
* The keys and values can be any kind of expression, like even object builder expressions.
* The resulting Java object will be a {@link Map} that keeps the item order ({@link LinkedHashMap} as
* of this writing).
+ * <li>A reference to a public static filed, like {@code Configuration.AUTO_DETECT_TAG_SYNTAX} or
+ * {@code com.example.MyClass.MY_CONSTANT}.
* <li>An object builder expression. That is, object builder expressions can be nested into each other.
* </ul>
* </li>
@@ -1901,13 +1903,13 @@ public class Configurable {
* can't be omitted for nested expressions.
* </li>
* <li>
- * <p>The following classes can be referred to with short class name instead of full qualified name:
+ * <p>The following classes can be referred to with simple (unqualified) name instead of fully qualified name:
* {@link DefaultObjectWrapper}, {@link BeansWrapper}, {@link SimpleObjectWrapper}, {@link Locale},
* {@link TemplateConfiguration}, {@link PathGlobMatcher}, {@link FileNameGlobMatcher}, {@link PathRegexMatcher},
* {@link AndMatcher}, {@link OrMatcher}, {@link NotMatcher}, {@link ConditionalTemplateConfigurationFactory},
* {@link MergingTemplateConfigurationFactory}, {@link FirstMatchTemplateConfigurationFactory},
* {@link HTMLOutputFormat}, {@link XMLOutputFormat}, {@link RTFOutputFormat}, {@link PlainTextOutputFormat},
- * {@link UndefinedOutputFormat}.
+ * {@link UndefinedOutputFormat}, {@link Configuration}.
* </li>
* <li>
* <p>{@link TimeZone} objects can be created like {@code TimeZone("UTC")}, despite that there's no a such
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
index a1e1c22..afbb436 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -46,6 +46,7 @@ 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;
@@ -75,7 +76,7 @@ public class _ObjectBuilderSettingEvaluator {
private static final String BUILDER_CLASS_POSTFIX = "Builder";
- private static Map/*<String,String>*/ SHORTHANDS;
+ private static Map<String,String> SHORTHANDS;
private static final Object VOID = new Object();
@@ -165,9 +166,12 @@ public class _ObjectBuilderSettingEvaluator {
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);
{
- final String fetchedClassName = fetchClassName(optional);
if (fetchedClassName == null) {
if (!optional) {
throw new _ObjectBuilderSettingEvaluationException("class name", src, pos);
@@ -178,6 +182,7 @@ public class _ObjectBuilderSettingEvaluator {
if (!fetchedClassName.equals(exp.className)) {
// Before 2.3.21 only full-qualified class names were allowed
modernMode = true;
+ exp.canBeStaticField = false;
}
}
@@ -186,15 +191,17 @@ public class _ObjectBuilderSettingEvaluator {
char openParen = fetchOptionalChar("(");
// Only the top-level expression can omit the "(...)"
if (openParen == 0 && !topLevel) {
- if (!optional) {
- throw new _ObjectBuilderSettingEvaluationException("(", src, pos);
+ if (fetchedClassName.indexOf('.') != -1) {
+ exp.mustBeStaticField = true;
+ } else {
+ pos = startPos;
+ return VOID;
}
- pos = startPos;
- return VOID;
}
if (openParen != 0) {
fetchParameterListInto(exp);
+ exp.canBeStaticField = false;
}
return exp;
@@ -683,8 +690,11 @@ public class _ObjectBuilderSettingEvaluator {
addWithSimpleName(SHORTHANDS, Locale.class);
SHORTHANDS.put("TimeZone", "freemarker.core._TimeZone");
+
+ // For accessing static fields:
+ addWithSimpleName(SHORTHANDS, Configuration.class);
}
- String fullClassName = (String) SHORTHANDS.get(className);
+ String fullClassName = SHORTHANDS.get(className);
return fullClassName == null ? className : fullClassName;
}
@@ -842,20 +852,41 @@ public class _ObjectBuilderSettingEvaluator {
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 {
- 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);
+ 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) {
+ 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;
+ }
}
}
@@ -868,8 +899,23 @@ public class _ObjectBuilderSettingEvaluator {
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) + ".", e2);
+ "Failed to get class " + StringUtil.jQuote(className)
+ + (failedToGetAsStaticField ? " (also failed to resolve name as static field)" : "")
+ + ".",
+ e2);
}
}
@@ -908,6 +954,53 @@ public class _ObjectBuilderSettingEvaluator {
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()) {
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index c561322..ce8137e 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -26131,12 +26131,12 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
<listitem>
<para>Fixes and improvements in the <quote>object
- builder</quote> syntax used for configuring FreeMarker from
- <literal>java.util.Properties</literal> (or other string-only
- sources). This is not to be confused with the template language
- syntax, which has nothing to do with the <quote>object
- builder</quote> syntax we are writing about here. The
- improvements are:</para>
+ builder</quote> mini-language used for configuring FreeMarker
+ from <literal>java.util.Properties</literal> or other
+ string-only sources (not used in templates). This is not to be
+ confused with the template language syntax, which has nothing to
+ do with the <quote>object builder</quote> syntax we are writing
+ about here. The improvements are:</para>
<itemizedlist>
<listitem>
@@ -26196,6 +26196,12 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
<literal>BigDecimal</literal> and <literal>bi</literal> for
<literal>BigInteger</literal>.</para>
</listitem>
+
+ <listitem>
+ <para>Public static fields can be referred, like
+ <literal>com.example.MyClass.MY_CONSTANT</literal> or
+ <literal>Configuration.AUTO_DETECT_TAG_SYNTAX</literal>.</para>
+ </listitem>
</itemizedlist>
</listitem>
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java b/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
index b708314..4276c7a 100644
--- a/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
@@ -913,6 +913,46 @@ public class ObjectBuilderSettingsTest {
"{ -1: -11, -0.5: -0.55, -0.1bd: -0.11bd }");
}
+ @Test
+ public void testStaticFields() throws Exception {
+ {
+ TestBean1 res = (TestBean1) _ObjectBuilderSettingEvaluator.eval(
+ "freemarker.core.ObjectBuilderSettingsTest$TestBean1("
+ + "freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST, true)",
+ Object.class, false, _SettingEvaluationEnvironment.getCurrent());
+ assertEquals(TestStaticFields.CONST, (int) res.i);
+ }
+ {
+ TestBean1 res = (TestBean1) _ObjectBuilderSettingEvaluator.eval(
+ "freemarker.core.ObjectBuilderSettingsTest$TestBean1("
+ + "p2 = freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST)",
+ Object.class, false, _SettingEvaluationEnvironment.getCurrent());
+ assertEquals(TestStaticFields.CONST, res.getP2());
+ }
+ assertEqualsEvaled(123, "freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST");
+
+ // With shorthand class name:
+ assertEqualsEvaled(Configuration.AUTO_DETECT_TAG_SYNTAX, "Configuration.AUTO_DETECT_TAG_SYNTAX");
+
+ try {
+ _ObjectBuilderSettingEvaluator.eval(
+ "freemarker.core.ObjectBuilderSettingsTest$TestBean1("
+ + "p2 = freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST())",
+ Object.class, false, _SettingEvaluationEnvironment.getCurrent());
+ fail();
+ } catch (_ObjectBuilderSettingEvaluationException e) {
+ assertThat(e.getMessage(),
+ containsString("freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST"));
+ }
+ try {
+ assertEqualsEvaled(123, "freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST()");
+ fail();
+ } catch (_ObjectBuilderSettingEvaluationException e) {
+ assertThat(e.getMessage(),
+ containsString("freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST"));
+ }
+ }
+
private void assertEqualsEvaled(Object expectedValue, String s)
throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException,
IllegalAccessException {
@@ -1352,6 +1392,9 @@ public class ObjectBuilderSettingsTest {
}
+ public static class TestStaticFields {
+ public static final int CONST = 123;
+ }
public static class DummyArithmeticEngine extends ArithmeticEngine {