Added an overload to Configuration.getSupportedBuiltInNames and Configuration.getSupportedBuiltInDirectiveNames that has a namingConvention parameter. This is useful for tooling as since 2.3.23 we support both camel case naming convention (like s?upperCase) and the legacy one (like s?upper_case). Furthermore the old 0 argument overload will now utilize Configuration.getNamingConvention() to only return the relevant names if it's not AUTO_DETECT_NAMING_CONVENTION.
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index 144d70b..ab796b0 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -24,6 +24,8 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
import freemarker.core.BuiltInsForDates.iso_BI;
import freemarker.core.BuiltInsForDates.iso_utc_or_local_BI;
@@ -77,8 +79,11 @@
protected Expression target;
protected String key;
+ static final Set<String> CAMEL_CASE_NAMES = new TreeSet<String>();
+ static final Set<String> SNAKE_CASE_NAMES = new TreeSet<String>();
+
static final int NUMBER_OF_BIS = 259;
- static final HashMap builtins = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
+ static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
static {
// Note that you must update NUMBER_OF_BIS if you add new items here!
@@ -272,7 +277,7 @@
putBI("url", new BuiltInsForStringsEncoding.urlBI());
putBI("url_path", "urlPath", new BuiltInsForStringsEncoding.urlPathBI());
putBI("values", new BuiltInsForHashes.valuesBI());
- putBI("web_safe", "webSafe", (BuiltIn) builtins.get("html")); // deprecated; use ?html instead
+ putBI("web_safe", "webSafe", BUILT_INS_BY_NAME.get("html")); // deprecated; use ?html instead
putBI("word_list", "wordList", new BuiltInsForStringsBasic.word_listBI());
putBI("xhtml", new BuiltInsForStringsEncoding.xhtmlBI());
putBI("xml", new BuiltInsForStringsEncoding.xmlBI());
@@ -280,18 +285,22 @@
putBI("groups", new BuiltInsForStringsRegexp.groupsBI());
putBI("replace", new BuiltInsForStringsRegexp.replace_reBI());
- if (NUMBER_OF_BIS < builtins.size()) {
- throw new AssertionError("Update NUMBER_OF_BIS! Should be: " + builtins.size());
+ if (NUMBER_OF_BIS < BUILT_INS_BY_NAME.size()) {
+ throw new AssertionError("Update NUMBER_OF_BIS! Should be: " + BUILT_INS_BY_NAME.size());
}
}
private static void putBI(String name, BuiltIn bi) {
- builtins.put(name, bi);
+ BUILT_INS_BY_NAME.put(name, bi);
+ SNAKE_CASE_NAMES.add(name);
+ CAMEL_CASE_NAMES.add(name);
}
- private static void putBI(String name, String nameCamelCase, BuiltIn bi) {
- builtins.put(name, bi);
- builtins.put(nameCamelCase, bi);
+ private static void putBI(String nameSnakeCase, String nameCamelCase, BuiltIn bi) {
+ BUILT_INS_BY_NAME.put(nameSnakeCase, bi);
+ BUILT_INS_BY_NAME.put(nameCamelCase, bi);
+ SNAKE_CASE_NAMES.add(nameSnakeCase);
+ CAMEL_CASE_NAMES.add(nameCamelCase);
}
/**
@@ -303,7 +312,7 @@
static BuiltIn newBuiltIn(int incompatibleImprovements, Expression target, Token keyTk,
FMParserTokenManager tokenManager) throws ParseException {
String key = keyTk.image;
- BuiltIn bi = (BuiltIn) builtins.get(key);
+ BuiltIn bi = BUILT_INS_BY_NAME.get(key);
if (bi == null) {
StringBuilder buf = new StringBuilder("Unknown built-in: ").append(StringUtil.jQuote(key)).append(". ");
@@ -311,8 +320,8 @@
"Help (latest version): http://freemarker.org/docs/ref_builtins.html; "
+ "you're using FreeMarker ").append(Configuration.getVersion()).append(".\n"
+ "The alphabetical list of built-ins:");
- List names = new ArrayList(builtins.keySet().size());
- names.addAll(builtins.keySet());
+ List names = new ArrayList(BUILT_INS_BY_NAME.keySet().size());
+ names.addAll(BUILT_INS_BY_NAME.keySet());
Collections.sort(names);
char lastLetter = 0;
diff --git a/src/main/java/freemarker/core/_CoreAPI.java b/src/main/java/freemarker/core/_CoreAPI.java
index 826f29e..df95196 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -25,6 +25,7 @@
import java.util.Set;
import java.util.TreeSet;
+import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateException;
@@ -45,69 +46,101 @@
// Can't be instantiated
private _CoreAPI() { }
+
+ private static void addName(Set<String> allNames, Set<String> lcNames, Set<String> ccNames,
+ String commonName) {
+ allNames.add(commonName);
+ lcNames.add(commonName);
+ ccNames.add(commonName);
+ }
- public static final Set/*<String>*/ BUILT_IN_DIRECTIVE_NAMES;
+ private static void addName(Set<String> allNames, Set<String> lcNames, Set<String> ccNames,
+ String lcName, String ccName) {
+ allNames.add(lcName);
+ allNames.add(ccName);
+ lcNames.add(lcName);
+ ccNames.add(ccName);
+ }
+
+ public static final Set<String> ALL_BUILT_IN_DIRECTIVE_NAMES;
+ public static final Set<String> LEGACY_BUILT_IN_DIRECTIVE_NAMES;
+ public static final Set<String> CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES;
static {
- Set/*<String>*/ names = new TreeSet();
- names.add("assign");
- names.add("attempt");
- names.add("autoEsc");
- names.add("autoesc");
- names.add("break");
- names.add("call");
- names.add("case");
- names.add("comment");
- names.add("compress");
- names.add("default");
- names.add("else");
- names.add("elseif");
- names.add("elseIf");
- names.add("escape");
- names.add("fallback");
- names.add("flush");
- names.add("foreach");
- names.add("forEach");
- names.add("ftl");
- names.add("function");
- names.add("global");
- names.add("if");
- names.add("import");
- names.add("include");
- names.add("items");
- names.add("list");
- names.add("local");
- names.add("lt");
- names.add("macro");
- names.add("nested");
- names.add("noautoesc");
- names.add("noAutoEsc");
- names.add("noescape");
- names.add("noEscape");
- names.add("noparse");
- names.add("noParse");
- names.add("nt");
- names.add("outputformat");
- names.add("outputFormat");
- names.add("recover");
- names.add("recurse");
- names.add("return");
- names.add("rt");
- names.add("sep");
- names.add("setting");
- names.add("stop");
- names.add("switch");
- names.add("t");
- names.add("transform");
- names.add("visit");
- BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(names);
+ Set<String> allNames = new TreeSet();
+ Set<String> lcNames = new TreeSet();
+ Set<String> ccNames = new TreeSet();
+
+ addName(allNames, lcNames, ccNames, "assign");
+ addName(allNames, lcNames, ccNames, "attempt");
+ addName(allNames, lcNames, ccNames, "autoesc", "autoEsc");
+ addName(allNames, lcNames, ccNames, "break");
+ addName(allNames, lcNames, ccNames, "call");
+ addName(allNames, lcNames, ccNames, "case");
+ addName(allNames, lcNames, ccNames, "comment");
+ addName(allNames, lcNames, ccNames, "compress");
+ addName(allNames, lcNames, ccNames, "default");
+ addName(allNames, lcNames, ccNames, "else");
+ addName(allNames, lcNames, ccNames, "elseif", "elseIf");
+ addName(allNames, lcNames, ccNames, "escape");
+ addName(allNames, lcNames, ccNames, "fallback");
+ addName(allNames, lcNames, ccNames, "flush");
+ addName(allNames, lcNames, ccNames, "foreach", "forEach");
+ addName(allNames, lcNames, ccNames, "ftl");
+ addName(allNames, lcNames, ccNames, "function");
+ addName(allNames, lcNames, ccNames, "global");
+ addName(allNames, lcNames, ccNames, "if");
+ addName(allNames, lcNames, ccNames, "import");
+ addName(allNames, lcNames, ccNames, "include");
+ addName(allNames, lcNames, ccNames, "items");
+ addName(allNames, lcNames, ccNames, "list");
+ addName(allNames, lcNames, ccNames, "local");
+ addName(allNames, lcNames, ccNames, "lt");
+ addName(allNames, lcNames, ccNames, "macro");
+ addName(allNames, lcNames, ccNames, "nested");
+ addName(allNames, lcNames, ccNames, "noautoesc", "noAutoEsc");
+ addName(allNames, lcNames, ccNames, "noescape", "noEscape");
+ addName(allNames, lcNames, ccNames, "noparse", "noParse");
+ addName(allNames, lcNames, ccNames, "nt");
+ addName(allNames, lcNames, ccNames, "outputformat", "outputFormat");
+ addName(allNames, lcNames, ccNames, "recover");
+ addName(allNames, lcNames, ccNames, "recurse");
+ addName(allNames, lcNames, ccNames, "return");
+ addName(allNames, lcNames, ccNames, "rt");
+ addName(allNames, lcNames, ccNames, "sep");
+ addName(allNames, lcNames, ccNames, "setting");
+ addName(allNames, lcNames, ccNames, "stop");
+ addName(allNames, lcNames, ccNames, "switch");
+ addName(allNames, lcNames, ccNames, "t");
+ addName(allNames, lcNames, ccNames, "transform");
+ addName(allNames, lcNames, ccNames, "visit");
+
+ ALL_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(allNames);
+ LEGACY_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(lcNames);
+ CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(ccNames);
}
/**
* Returns the names of the currently supported "built-ins" ({@code expr?builtin_name}-like things).
- * @return {@link Set} of {@link String}-s.
+ *
+ * @param namingConvention
+ * One of {@link Configuration#AUTO_DETECT_NAMING_CONVENTION},
+ * {@link Configuration#LEGACY_NAMING_CONVENTION}, and
+ * {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}. If it's
+ * {@link Configuration#AUTO_DETECT_NAMING_CONVENTION} then the union of the names in all the naming
+ * conventions is returned.
*/
- public static Set/*<String>*/ getSupportedBuiltInNames() {
- return Collections.unmodifiableSet(BuiltIn.builtins.keySet());
+ public static Set<String> getSupportedBuiltInNames(int namingConvention) {
+ Set<String> names;
+ if (namingConvention == Configuration.AUTO_DETECT_NAMING_CONVENTION) {
+ names = BuiltIn.BUILT_INS_BY_NAME.keySet();
+ } else if (namingConvention == Configuration.LEGACY_NAMING_CONVENTION) {
+ names = BuiltIn.SNAKE_CASE_NAMES;
+ } else if (namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+ names = BuiltIn.CAMEL_CASE_NAMES;
+ } else {
+ throw new IllegalArgumentException("Unsupported naming convention constant: " + namingConvention);
+ }
+ return Collections.unmodifiableSet(names);
}
public static void appendInstructionStackItem(TemplateElement stackEl, StringBuilder sb) {
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index bd7d46c..8be37db 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -3157,30 +3157,62 @@
}
/**
- * Returns the names of the supported "built-ins". These are the ({@code expr?builtin_name}-like things). As of this
- * writing, this information doesn't depend on the configuration options, so it could be a static method, but
- * to be future-proof, it's an instance method.
- *
- * @return {@link Set} of {@link String}-s.
+ * Same as {@link #getSupportedBuiltInNames(int)} with argument {@link #getNamingConvention()}.
*
* @since 2.3.20
*/
public Set getSupportedBuiltInNames() {
- return _CoreAPI.getSupportedBuiltInNames();
+ return getSupportedBuiltInNames(getNamingConvention());
+ }
+
+ /**
+ * Returns the names of the supported "built-ins". These are the ({@code expr?builtin_name}-like things). As of this
+ * writing, this information doesn't depend on the configuration options, so it could be a static method, but
+ * to be future-proof, it's an instance method.
+ *
+ * @param namingConvention
+ * One of {@link #AUTO_DETECT_NAMING_CONVENTION}, {@link #LEGACY_NAMING_CONVENTION}, and
+ * {@link #CAMEL_CASE_NAMING_CONVENTION}. If it's {@link #AUTO_DETECT_NAMING_CONVENTION} then the union
+ * of the names in all the naming conventions is returned.
+ *
+ * @since 2.3.24
+ */
+ public Set<String> getSupportedBuiltInNames(int namingConvention) {
+ return _CoreAPI.getSupportedBuiltInNames(namingConvention);
}
/**
- * Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like
- * <tt><#directiveName ...></tt>.
- *
- * @return {@link Set} of {@link String}-s.
+ * Same as {@link #getSupportedBuiltInDirectiveNames(int)} with argument {@link #getNamingConvention()}.
*
* @since 2.3.21
*/
public Set getSupportedBuiltInDirectiveNames() {
- return _CoreAPI.BUILT_IN_DIRECTIVE_NAMES;
+ return getSupportedBuiltInDirectiveNames(getNamingConvention());
}
+ /**
+ * Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like
+ * <tt><#directiveName ...></tt>.
+ *
+ * @param namingConvention
+ * One of {@link #AUTO_DETECT_NAMING_CONVENTION}, {@link #LEGACY_NAMING_CONVENTION}, and
+ * {@link #CAMEL_CASE_NAMING_CONVENTION}. If it's {@link #AUTO_DETECT_NAMING_CONVENTION} then the union
+ * of the names in all the naming conventions is returned.
+ *
+ * @since 2.3.24
+ */
+ public Set<String> getSupportedBuiltInDirectiveNames(int namingConvention) {
+ if (namingConvention == AUTO_DETECT_NAMING_CONVENTION) {
+ return _CoreAPI.ALL_BUILT_IN_DIRECTIVE_NAMES;
+ } else if (namingConvention == LEGACY_NAMING_CONVENTION) {
+ return _CoreAPI.LEGACY_BUILT_IN_DIRECTIVE_NAMES;
+ } else if (namingConvention == CAMEL_CASE_NAMING_CONVENTION) {
+ return _CoreAPI.CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES;
+ } else {
+ throw new IllegalArgumentException("Unsupported naming convention constant: " + namingConvention);
+ }
+ }
+
private static String getRequiredVersionProperty(Properties vp, String properyName) {
String s = vp.getProperty(properyName);
if (s == null) {
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 6318f75..9c888d2 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -26986,6 +26986,22 @@
</listitem>
<listitem>
+ <para>Added an overload to
+ <literal>Configuration.getSupportedBuiltInNames</literal> and
+ <literal>Configuration.getSupportedBuiltInDirectiveNames</literal>
+ that has a <literal>namingConvention</literal> parameter. This
+ is useful for tooling as since 2.3.23 we support both camel case
+ naming convention (like
+ <literal><replaceable>s</replaceable>?upperCase</literal>) and
+ the legacy one (like
+ <literal><replaceable>s</replaceable>?upper_case</literal>).
+ Furthermore the old 0 argument overload will now utilize
+ <literal>Configuration.getNamingConvention()</literal> to only
+ return the relevant names if it's not
+ <literal>AUTO_DETECT_NAMING_CONVENTION</literal>.</para>
+ </listitem>
+
+ <listitem>
<para>Internal reworking to simplify the AST (the
<literal>TemplateElement</literal> structure). The API related
technically public API was marked as internal for a good while.
@@ -27356,6 +27372,22 @@
</listitem>
</itemizedlist>
</listitem>
+
+ <listitem>
+ <para>Added an overload to
+ <literal>Configuration.getSupportedBuiltInNames</literal> and
+ <literal>Configuration.getSupportedBuiltInDirectiveNames</literal>
+ that has a <literal>namingConvention</literal> parameter. This
+ is useful for tooling as since 2.3.23 we support both camel case
+ naming convention (like
+ <literal><replaceable>s</replaceable>?upperCase</literal>) and
+ the legacy one (like
+ <literal><replaceable>s</replaceable>?upper_case</literal>).
+ Furthermore the old 0 argument overload will now utilize
+ <literal>Configuration.getNamingConvention()</literal> to only
+ return the relevant names if it's not
+ <literal>AUTO_DETECT_NAMING_CONVENTION</literal>.</para>
+ </listitem>
</itemizedlist>
</section>
</section>
diff --git a/src/test/java/freemarker/core/CamelCaseTest.java b/src/test/java/freemarker/core/CamelCaseTest.java
index ece5730..16232c1 100644
--- a/src/test/java/freemarker/core/CamelCaseTest.java
+++ b/src/test/java/freemarker/core/CamelCaseTest.java
@@ -256,8 +256,8 @@
assertContainsBothNamingStyles(getConfiguration().getSupportedBuiltInNames(), new NamePairAssertion() {
public void assertPair(String name1, String name2) {
- BuiltIn bi1 = (BuiltIn) BuiltIn.builtins.get(name1);
- BuiltIn bi2 = (BuiltIn) BuiltIn.builtins.get(name2);
+ BuiltIn bi1 = (BuiltIn) BuiltIn.BUILT_INS_BY_NAME.get(name1);
+ BuiltIn bi2 = (BuiltIn) BuiltIn.BUILT_INS_BY_NAME.get(name2);
assertTrue("\"" + name1 + "\" and \"" + name2 + "\" doesn't belong to the same BI object.",
bi1 == bi2);
}
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 9e69d90..39ee7b6 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -32,6 +32,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import java.util.TimeZone;
import org.junit.Test;
@@ -1640,6 +1641,46 @@
}
}
+ @Test
+ public void testGetSupportedBuiltInDirectiveNames() {
+ Configuration cfg = new Configuration();
+
+ Set<String> allNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.AUTO_DETECT_NAMING_CONVENTION);
+ Set<String> lNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.LEGACY_NAMING_CONVENTION);
+ Set<String> cNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+
+ checkNamingConventionNameSets(allNames, lNames, cNames);
+
+ for (String name : cNames) {
+ assertThat(name.toLowerCase(), isIn(lNames));
+ }
+ }
+
+ @Test
+ public void testGetSupportedBuiltInNames() {
+ Configuration cfg = new Configuration();
+
+ Set<String> allNames = cfg.getSupportedBuiltInNames(Configuration.AUTO_DETECT_NAMING_CONVENTION);
+ Set<String> lNames = cfg.getSupportedBuiltInNames(Configuration.LEGACY_NAMING_CONVENTION);
+ Set<String> cNames = cfg.getSupportedBuiltInNames(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+
+ checkNamingConventionNameSets(allNames, lNames, cNames);
+ }
+
+ private void checkNamingConventionNameSets(Set<String> allNames, Set<String> lNames, Set<String> cNames) {
+ for (String name : lNames) {
+ assertThat(allNames, hasItem(name));
+ assertTrue("Should be all-lowercase: " + name, name.equals(name.toLowerCase()));
+ }
+ for (String name : cNames) {
+ assertThat(allNames, hasItem(name));
+ }
+ for (String name : allNames) {
+ assertThat(name, anyOf(isIn(lNames), isIn(cNames)));
+ }
+ assertEquals(lNames.size(), cNames.size());
+ }
+
@SuppressWarnings("boxing")
private void assertStartsWith(List<String> list, List<String> headList) {
int index = 0;