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.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/72927166 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/72927166 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/72927166 Branch: refs/heads/master Commit: 7292716630d9539bd47a40e20327006447d0c770 Parents: f20271e Author: ddekany <[email protected]> Authored: Tue Dec 29 02:03:58 2015 +0100 Committer: ddekany <[email protected]> Committed: Tue Dec 29 02:03:58 2015 +0100 ---------------------------------------------------------------------- src/main/java/freemarker/core/BuiltIn.java | 31 ++-- src/main/java/freemarker/core/_CoreAPI.java | 145 ++++++++++++------- .../java/freemarker/template/Configuration.java | 50 +++++-- src/manual/en_US/book.xml | 32 ++++ .../java/freemarker/core/CamelCaseTest.java | 4 +- .../freemarker/template/ConfigurationTest.java | 41 ++++++ 6 files changed, 225 insertions(+), 78 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/72927166/src/main/java/freemarker/core/BuiltIn.java ---------------------------------------------------------------------- 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.Collections; 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 @@ abstract class BuiltIn extends Expression implements Cloneable { 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 @@ abstract class BuiltIn extends Expression implements Cloneable { 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 @@ abstract class BuiltIn extends Expression implements Cloneable { 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 @@ abstract class BuiltIn extends Expression implements Cloneable { 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 @@ abstract class BuiltIn extends Expression implements Cloneable { "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; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/72927166/src/main/java/freemarker/core/_CoreAPI.java ---------------------------------------------------------------------- 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.Collections; 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 @@ public class _CoreAPI { // 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); + } + + 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>*/ BUILT_IN_DIRECTIVE_NAMES; + 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) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/72927166/src/main/java/freemarker/template/Configuration.java ---------------------------------------------------------------------- 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 @@ public class Configuration extends Configurable implements Cloneable, ParserConf } /** + * Same as {@link #getSupportedBuiltInNames(int)} with argument {@link #getNamingConvention()}. + * + * @since 2.3.20 + */ + public Set 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. * - * @return {@link Set} of {@link String}-s. + * @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.20 + * @since 2.3.24 */ - public Set getSupportedBuiltInNames() { - return _CoreAPI.getSupportedBuiltInNames(); + 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) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/72927166/src/manual/en_US/book.xml ---------------------------------------------------------------------- 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 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting> </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 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting> </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> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/72927166/src/test/java/freemarker/core/CamelCaseTest.java ---------------------------------------------------------------------- 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 @@ public class CamelCaseTest extends TemplateTest { 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); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/72927166/src/test/java/freemarker/template/ConfigurationTest.java ---------------------------------------------------------------------- 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.HashMap; 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 @@ public class ConfigurationTest extends TestCase { } } + @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;
