/* * Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky * * Licensed 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.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateDirectiveBody; import freemarker.template.utility.ClassUtil; /** * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can * access things inside this package that users shouldn't. */ public class _CoreAPI { public static final String ERROR_MESSAGE_HR = "----"; // Can't be instantiated private _CoreAPI() { } public static final Set/*<String>*/ 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); } /** * Returns the names of the currently supported "built-ins" ({@code expr?builtin_name}-like things). * @return {@link Set} of {@link String}-s. */ public static Set/*<String>*/ getSupportedBuiltInNames() { return Collections.unmodifiableSet(BuiltIn.builtins.keySet()); } public static void appendInstructionStackItem(TemplateElement stackEl, StringBuilder sb) { Environment.appendInstructionStackItem(stackEl, sb); } public static TemplateElement[] getInstructionStackSnapshot(Environment env) { return env.getInstructionStackSnapshot(); } public static void outputInstructionStack( TemplateElement[] instructionStackSnapshot, boolean terseMode, Writer pw) { Environment.outputInstructionStack(instructionStackSnapshot, terseMode, pw); } public static Map<String, ?> getCustomAttributes(UnboundTemplate unboundTemplate) { return unboundTemplate.getCustomAttributes(); } /** * For emulating legacy {@link Template#addMacro(Macro)}. */ public static void addMacro(UnboundTemplate unboundTemplate, Macro macro) { final UnboundCallable unboundCallable = macroToUnboundCallable(macro); unboundTemplate.addUnboundCallable(unboundCallable); } /** * In 2.4 {@link Macro} was split to {@link BoundCallable} and {@link UnboundCallable}, but because of BC * constraints sometimes we can only expect a {@link Macro}, but that can always converted to * {@link UnboundCallable}. */ private static UnboundCallable macroToUnboundCallable(Macro macro) { if (macro instanceof UnboundCallable) { // It's coming from the AST: return (UnboundCallable) macro; } else if (macro instanceof BoundCallable) { // It's coming from an FTL variable: return ((BoundCallable) macro).getUnboundCallable(); } else if (macro == null) { return null; } else { // Impossible, Macro should have only two subclasses. throw new BugException(); } } public static void addImport(UnboundTemplate unboundTemplate, LibraryLoad libLoad) { unboundTemplate.addImport(libLoad); } public static UnboundTemplate newUnboundTemplate(Reader reader, String sourceName, Configuration cfg, ParserConfiguration parserCfg, String assumedEncoding) throws IOException { return new UnboundTemplate(reader, sourceName, cfg, parserCfg, assumedEncoding); } public static boolean isBoundCallable(Object obj) { return obj instanceof BoundCallable; } public static UnboundTemplate newPlainTextUnboundTemplate(String content, String sourceName, Configuration config) { return UnboundTemplate.newPlainTextUnboundTemplate(content, sourceName, config); } /** Used for implementing the deprecated {@link Template} method with similar name. */ public static TemplateElement getRootTreeNode(UnboundTemplate unboundTemplate) { return unboundTemplate.getRootTreeNode(); } /** Used for implementing the deprecated {@link Template} method with similar name. */ public static Map<String, UnboundCallable> getUnboundCallables(UnboundTemplate unboundTemplate) { return unboundTemplate.getUnboundCallables(); } /** Used for implementing the deprecated {@link Template} method with similar name. */ public static List getImports(UnboundTemplate unboundTemplate) { return unboundTemplate.getImports(); } /** Used for implementing the deprecated {@link Template} method with similar name. */ public static void addPrefixNSMapping(UnboundTemplate unboundTemplate, String prefix, String nsURI) { unboundTemplate.addPrefixToNamespaceURIMapping(prefix, nsURI); } /** Used for implementing the deprecated {@link Template} method with similar name. */ public static List<TemplateElement> containingElements(UnboundTemplate unboundTemplate, int column, int line) { return unboundTemplate.containingElements(column, line); } /** * ATTENTION: This is used by https://github.com/kenshoo/freemarker-online. Don't break backward * compatibility without updating that project too! */ static final public void addThreadInterruptedChecks(Template template) { try { new ThreadInterruptionSupportTemplatePostProcessor().postProcess(template); } catch (TemplatePostProcessorException e) { throw new RuntimeException("Template post-processing failed", e); } } static final public void checkHasNoNestedContent(TemplateDirectiveBody body) throws NestedContentNotSupportedException { NestedContentNotSupportedException.check(body); } public static Map<String, Macro> createAdapterMacroMapForUnboundCallables(UnboundTemplate unboundTemplate) { return new AdapterMacroMap(getUnboundCallables(unboundTemplate)); } /** * Wraps a {@code Map<String, UnboundCallable>} as if it was a {@code Map<String, Macro>}. This is for backward * compatibility. The important use case is being able to put any {@link Macro} subclass into this {@code Map}, * despite that the backing {@link Macro} can only store {@code UnboundCallable}. Reading works a bit strangely, * because if you put a non-{@link UnboundCallable} value in, then get it with the same key, you get the * corresponding {@link UnboundCallable} back instead of the original object. That's also a {@code Macro} though. */ private static class AdapterMacroMap implements Map<String, Macro> { private final Map<String, UnboundCallable> adapted; public AdapterMacroMap(Map<String, UnboundCallable> unboundCallableMap) { this.adapted = unboundCallableMap; } public int size() { return adapted.size(); } public boolean isEmpty() { return adapted.isEmpty(); } public boolean containsKey(Object key) { return adapted.containsKey(key); } public boolean containsValue(Object value) { return adapted.containsValue(value); } public UnboundCallable get(Object key) { return adapted.get(key); } public UnboundCallable put(String key, Macro value) { return adapted.put(key, macroToUnboundCallable(value)); } public UnboundCallable remove(Object key) { return adapted.remove(key); } public void putAll(Map<? extends String, ? extends Macro> t) { for (Map.Entry<? extends String, ? extends Macro> ent : t.entrySet()) { put(ent.getKey(), ent.getValue()); } } public void clear() { adapted.clear(); } public Set<String> keySet() { return adapted.keySet(); } public Collection<Macro> values() { // According the Map API, this Collection doesn't allows adding elements, it's safe to treat as a // Collection<Macro>. return (Collection) adapted.values(); } public Set<Entry<String, Macro>> entrySet() { // According the Map API, this set doesn't allows adding elements, but, the Map.Entry-s are still // modifiable. return new AdapterMacroMapEntrySet(adapted.entrySet()); } @Override public boolean equals(Object o) { return adapted.equals(o); } @Override public int hashCode() { return adapted.hashCode(); } } /** Helper for {@link AdapterMacroMap}. */ private static class AdapterMacroMapEntrySet implements Set<Map.Entry<String, Macro>> { private final Set<Map.Entry<String, UnboundCallable>> adapted; public AdapterMacroMapEntrySet(Set<Entry<String, UnboundCallable>> adapted) { this.adapted = adapted; } public int size() { return adapted.size(); } public boolean isEmpty() { return adapted.isEmpty(); } public boolean contains(Object o) { return adapted.contains(o); } public Iterator<Entry<String, Macro>> iterator() { return new AdapterMacroMapEntrySetIterator(adapted.iterator()); } public Object[] toArray() { return adapted.toArray(); } public <T> T[] toArray(T[] a) { return adapted.toArray(a); } public boolean add(Entry<String, Macro> o) { // Won't be allowed anyway return adapted.add((Entry) o); } public boolean remove(Object o) { return adapted.remove(o); } public boolean containsAll(Collection<?> c) { return adapted.containsAll(c); } public boolean addAll(Collection<? extends Entry<String, Macro>> c) { // Won't be allowed anyway return adapted.addAll((Collection) c); } public boolean retainAll(Collection<?> c) { return adapted.retainAll(c); } public boolean removeAll(Collection<?> c) { return adapted.removeAll(c); } public void clear() { adapted.clear(); } @Override public boolean equals(Object o) { return adapted.equals(o); } @Override public int hashCode() { return adapted.hashCode(); } } /** Helper for {@link AdapterMacroMap}. */ private static class AdapterMacroMapEntrySetIterator implements Iterator<Map.Entry<String, Macro>> { private final Iterator<Map.Entry<String, UnboundCallable>> adapted; public AdapterMacroMapEntrySetIterator(Iterator<Entry<String, UnboundCallable>> adapted) { this.adapted = adapted; } public boolean hasNext() { return adapted.hasNext(); } public Entry<String, Macro> next() { return new AdapterMacroMapEntry(adapted.next()); } public void remove() { adapted.remove(); } } /** Helper for {@link AdapterMacroMap}. */ private static class AdapterMacroMapEntry implements Map.Entry<String, Macro> { private final Map.Entry<String, UnboundCallable> adapted; public AdapterMacroMapEntry(Entry<String, UnboundCallable> adapted) { this.adapted = adapted; } public String getKey() { return adapted.getKey(); } public UnboundCallable getValue() { return adapted.getValue(); } public UnboundCallable setValue(Macro value) { return adapted.setValue(macroToUnboundCallable(value)); } @Override public boolean equals(Object o) { return adapted.equals(o); } @Override public int hashCode() { return adapted.hashCode(); } } /** * @throws IllegalArgumentException * if the type of the some of the values isn't as expected */ public static void checkSettingValueItemsType(String somethingsSentenceStart, Class<?> expectedClass, Collection<? extends Object> values) { if (values == null) return; for (Object value : values) { if (!expectedClass.isInstance(value)) { throw new IllegalArgumentException(somethingsSentenceStart + " must be instances of " + ClassUtil.getShortClassName(expectedClass) + ", but one of them was a(n) " + ClassUtil.getShortClassNameOfObject(value) + "."); } } } }