framework/webapp/src/com/ilscipio/scipio/ce/webapp/ftl/lang/LangFtlUtil.java
package com.ilscipio.scipio.ce.webapp.ftl.lang;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.rmi.server.UID;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.template.FreeMarkerWorker;
import org.ofbiz.base.util.template.ScipioFtlWrappers.EscapingModel;
import org.ofbiz.base.util.template.ScipioFtlWrappers.ScipioBeansWrapper;
import org.ofbiz.base.util.template.ScipioFtlWrappers.ScipioExtendedObjectWrapper;
import freemarker.core.Environment;
import freemarker.ext.beans.BeanModel;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.SimpleMapModel;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.template.AdapterTemplateModel;
import freemarker.template.DefaultArrayAdapter;
import freemarker.template.DefaultListAdapter;
import freemarker.template.DefaultMapAdapter;
import freemarker.template.ObjectWrapper;
import freemarker.template.ObjectWrapperAndUnwrapper;
import freemarker.template.SimpleHash;
import freemarker.template.SimpleScalar;
import freemarker.template.SimpleSequence;
import freemarker.template.Template;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.utility.DeepUnwrap;
import freemarker.template.utility.ObjectWrapperWithAPISupport;
import freemarker.template.utility.RichObjectWrapper;
/**
* SCIPIO: Freemarker language utils.
* <p>
* These should generally not include Ofbiz-specific utils, except in the case
* where the Ofbiz-specific code is merely a configuration of Freemarker (e.g.
* selected usage of <code>BeansWrapper</code> or the special case of <code>EscapingModel</code>).
* <p>
* <strong>WARN:</strong> All utility methods here (except special wrap methods)
* using ObjectWrapper should take an ObjectWrapper from caller - let caller decide which - and never
* call Environment.getObjectWrapper anymore.
*
* @see com.ilscipio.scipio.ce.webapp.ftl.CommonFtlUtil
*/
public abstract class LangFtlUtil {
private static final Debug.OfbizLogger module = Debug.getOfbizLogger(java.lang.invoke.MethodHandles.lookup().lookupClass());
// NOTE: there's no _real_ need to synchronize on these. if two templates are built for one builtin its not big deal.
private static final Map<String, Template> builtInCalls = new ConcurrentHashMap<>();
private static Template stringBuiltInCall = null;
private static final Map<String, Template> functionCalls = new ConcurrentHashMap<>();
private static final Map<String, Template> macroCalls = new ConcurrentHashMap<>();
protected LangFtlUtil() {
}
/**
* Used for TemplateModel <-> unwrapped/raw value conversions.
*/
public enum TemplateValueTargetType {
PRESERVE,
RAW,
MODEL,
SIMPLEMODEL,
COMPLEXMODEL
}
public enum SetOperations {
UNION,
INTERSECT,
DIFFERENCE
}
public interface FtlVarHandler {
void setVariable(String name, TemplateModel model) throws TemplateModelException;
TemplateModel getVariable(String name) throws TemplateModelException;
}
public static class GlobalFtlVarHandler implements FtlVarHandler {
private final Environment env;
public GlobalFtlVarHandler(Environment env) {
this.env = env;
}
@Override
public void setVariable(String name, TemplateModel model) throws TemplateModelException {
env.setGlobalVariable(name, model);
}
@Override
public TemplateModel getVariable(String name) throws TemplateModelException {
return env.getGlobalVariable(name);
}
}
public static class CurrentFtlVarHandler implements FtlVarHandler {
private final Environment env;
public CurrentFtlVarHandler(Environment env) {
this.env = env;
}
@Override
public void setVariable(String name, TemplateModel model) throws TemplateModelException {
env.setVariable(name, model);
}
@Override
public TemplateModel getVariable(String name) throws TemplateModelException {
return env.getVariable(name);
}
}
public static class LocalFtlVarHandler implements FtlVarHandler {
private final Environment env;
public LocalFtlVarHandler(Environment env) {
this.env = env;
}
@Override
public void setVariable(String name, TemplateModel model) throws TemplateModelException {
env.setLocalVariable(name, model);
}
@Override
public TemplateModel getVariable(String name) throws TemplateModelException {
return env.getLocalVariable(name);
}
}
/**
* Returns a TemplateModel representing a null value. Will always return
* a model, even if the bean wrapper does not have a null model set.
*/
public static TemplateModel getNullModelAlways() {
return TemplateNullModel.getNullModel();
}
/**
* Gets current object wrapper, whatever it may be.
*/
public static ObjectWrapper getCurrentObjectWrapper(Environment env) {
return env.getObjectWrapper();
}
/**
* Gets current object wrapper, whatever it may be.
*/
public static ObjectWrapper getCurrentObjectWrapper() {
return FreeMarkerWorker.getCurrentEnvironment().getObjectWrapper();
}
/**
* Checks if the wrapper is a special escaping wrapper, and if so,
* returns a non-escaping one.
*/
public static ObjectWrapper getNonEscapingObjectWrapper(ObjectWrapper objectWrapper) {
if (objectWrapper instanceof ScipioExtendedObjectWrapper) {
return FreeMarkerWorker.getDefaultOfbizWrapper();
} else {
return objectWrapper;
}
}
/**
* Checks if the current env wrapper is a special escaping wrapper, and if so,
* returns a non-escaping one.
*/
public static ObjectWrapper getNonEscapingObjectWrapper(Environment env) {
ObjectWrapper objectWrapper = env.getObjectWrapper();
if (objectWrapper instanceof ScipioExtendedObjectWrapper) {
return FreeMarkerWorker.getDefaultOfbizWrapper();
} else {
return objectWrapper;
}
}
/**
* Checks if the current env wrapper is a special escaping wrapper, and if so,
* returns a non-escaping one.
*/
public static ObjectWrapper getNonEscapingObjectWrapper() {
Environment env = FreeMarkerWorker.getCurrentEnvironment();
ObjectWrapper objectWrapper = env.getObjectWrapper();
if (objectWrapper instanceof ScipioExtendedObjectWrapper) {
return FreeMarkerWorker.getDefaultOfbizWrapper();
} else {
return objectWrapper;
}
}
/**
* Returns a non-escaping, simple-types-only object wrapper.
*/
public static ObjectWrapper getSimpleTypeNonEscapingObjectWrapper(ObjectWrapper objectWrapper) {
return FreeMarkerWorker.getDefaultSimpleTypeWrapper();
}
/**
* Returns a non-escaping, simple-types-only object wrapper.
*/
public static ObjectWrapper getSimpleTypeNonEscapingObjectWrapper(Environment env) {
return FreeMarkerWorker.getDefaultSimpleTypeWrapper();
}
/**
* Returns a non-escaping, simple-types-only object wrapper.
*/
public static ObjectWrapper getSimpleTypeNonEscapingObjectWrapper() {
return FreeMarkerWorker.getDefaultSimpleTypeWrapper();
}
/**
* Returns a non-escaping, simple-types-only copying object wrapper.
*/
public static ObjectWrapper getSimpleTypeCopyingNonEscapingObjectWrapper(ObjectWrapper objectWrapper) {
return FreeMarkerWorker.getDefaultSimpleTypeCopyingWrapper();
}
/**
* Returns a non-escaping, simple-types-only copying object wrapper.
*/
public static ObjectWrapper getSimpleTypeCopyingNonEscapingObjectWrapper(Environment env) {
return FreeMarkerWorker.getDefaultSimpleTypeCopyingWrapper();
}
/**
* Returns a non-escaping, simple-types-only copying object wrapper.
*/
public static ObjectWrapper getSimpleTypeCopyingNonEscapingObjectWrapper() {
return FreeMarkerWorker.getDefaultSimpleTypeCopyingWrapper();
}
/**
* Scipio wrapper around ObjectWrapper.wrap in case extra logic is needed.
* <p>
* Currently leaving all to object wrapper, but may change...
*/
public static TemplateModel wrap(Object object, ObjectWrapper objectWrapper) throws TemplateModelException {
return objectWrapper.wrap(object);
}
/**
* Scipio wrapper around ObjectWrapper.wrap in case extra logic is needed.
* <p>
* Currently leaving all to object wrapper, but may change...
*/
public static TemplateModel wrap(Object object, Environment env) throws TemplateModelException {
return getCurrentObjectWrapper(env).wrap(object);
}
/**
* Scipio wrapper around ObjectWrapper.wrap in case extra logic is needed.
* <p>
* Currently leaving all to object wrapper, but may change...
*/
public static TemplateModel wrap(Object object) throws TemplateModelException {
return getCurrentObjectWrapper().wrap(object);
}
/**
* Scipio wrapper around ObjectWrapper.wrap in case extra logic is needed.
* <p>
* Currently leaving all to object wrapper, but may change...
*/
public static TemplateModel wrapNonEscaping(Object object, ObjectWrapper objectWrapper) throws TemplateModelException {
return getNonEscapingObjectWrapper(objectWrapper).wrap(object);
}
/**
* Scipio wrapper around ObjectWrapper.wrap in case extra logic is needed.
* <p>
* Currently leaving all to object wrapper, but may change...
*/
public static TemplateModel wrapNonEscaping(Object object, Environment env) throws TemplateModelException {
return getNonEscapingObjectWrapper(env).wrap(object);
}
/**
* Scipio wrapper around ObjectWrapper.wrap in case extra logic is needed.
* <p>
* Currently leaving all to object wrapper, but may change...
*/
public static TemplateModel wrapNonEscaping(Object object) throws TemplateModelException {
return getNonEscapingObjectWrapper().wrap(object);
}
/**
* Unwraps template model; if cannot, returns null.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapOrNull(TemplateModel templateModel) throws TemplateModelException {
if (templateModel != null) {
Object res = DeepUnwrap.permissiveUnwrap(templateModel);
if (res != templateModel) {
return res;
} else {
return null;
}
} else {
return null;
}
}
/**
* If TemplateModel, unwraps value, or if cannot, returns null;
* if not TemplateModel, returns as-is.
* <p>
* Ensures no TemplateModels remain.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapOrNull(Object value) throws TemplateModelException {
if (value instanceof TemplateModel) {
Object res = DeepUnwrap.permissiveUnwrap((TemplateModel) value);
if (res != value) {
return res;
} else {
return null;
}
} else {
return value;
}
}
/**
* Unwraps template model; if cannot, returns as-is.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapPermissive(TemplateModel templateModel) throws TemplateModelException {
if (templateModel != null) {
return DeepUnwrap.permissiveUnwrap(templateModel);
} else {
return null;
}
}
/**
* Unwraps value; if cannot, returns value, even if still TemplateModel.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapPermissive(Object value) throws TemplateModelException {
if (value instanceof TemplateModel) {
return DeepUnwrap.permissiveUnwrap((TemplateModel) value);
} else {
return value;
}
}
/**
* Unwraps template model; if cannot, throws exception. If null, returns null.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrap(TemplateModel templateModel) throws TemplateModelException {
if (templateModel != null) {
return DeepUnwrap.unwrap(templateModel); // will throw exception if improper type
} else {
return null;
}
}
/**
* If template model, unwraps, or if cannot, throws exception;
* if not template model or null, returns value.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrap(Object value) throws TemplateModelException {
if (value instanceof TemplateModel) {
return DeepUnwrap.unwrap((TemplateModel) value);
} else {
return value;
}
}
/**
* Unwraps template model; if cannot, throws exception.
* <p>
* Interpretation of null depends on the ObjectWrapper.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapAlways(TemplateModel templateModel) throws TemplateModelException {
return DeepUnwrap.unwrap(templateModel); // will throw exception if improper type
}
/**
* Unwraps value if template model and unwrappable; else exception.
* <p>
* Interpretation of null depends on the ObjectWrapper.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapAlways(Object value) throws TemplateModelException {
if (value instanceof TemplateModel || value == null) {
return DeepUnwrap.unwrap((TemplateModel) value);
} else {
throw new TemplateModelException("Cannot unwrap non-TemplateModel value (type " + value.getClass().getName() + ")");
}
}
/**
* Unwraps template model; if cannot, throws exception. Special case where null accepted.
* <p>
* Interpretation of null depends on the ObjectWrapper.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapAlwaysUnlessNull(TemplateModel templateModel) throws TemplateModelException {
if (templateModel == null) {
return null;
} else {
return DeepUnwrap.unwrap(templateModel); // will throw exception if improper type
}
}
/**
* Unwraps value if template model and unwrappable; else exception. Special case where null accepted.
* <p>
* Interpretation of null depends on the ObjectWrapper.
* <p>
* NOTE: This automatically bypasses the auto-escaping done by wrappers implementing the <code>EscapingModel</code> interface
* (such as Ofbiz special widget wrappers).
*/
public static Object unwrapAlwaysUnlessNull(Object value) throws TemplateModelException {
if (value instanceof TemplateModel) {
return DeepUnwrap.unwrap((TemplateModel) value);
} else if (value == null) {
return null;
} else {
throw new TemplateModelException("Cannot unwrap non-TemplateModel value (type " + value.getClass().getName() + ")");
}
}
/**
* SCIPIO: Special unwrap that unwraps only objects wrapped with special escaping (Ofbiz) wrappers.
* If doesn't apply to the value, returns the value as-is.
* <p>
* NOTE: The other unwrap methods automatically perform this operation as well.
*/
public static Object unwrapIfEscaping(TemplateModel templateModel) throws TemplateModelException {
if (templateModel instanceof EscapingModel) {
return ((EscapingModel) templateModel).getWrappedObject();
}
return templateModel;
}
/**
* SCIPIO: Special unwrap that unwraps only objects wrapped with special escaping (Ofbiz) wrappers.
* If doesn't apply to the value, returns the value as-is.
* <p>
* NOTE: The other unwrap methods automatically perform this operation as well.
*/
public static Object unwrapIfEscaping(Object value) throws TemplateModelException {
if (value instanceof EscapingModel) {
return ((EscapingModel) value).getWrappedObject();
}
return value;
}
/**
* SCIPIO: Special unwrap that unwraps only objects wrapped with special escaping (Ofbiz) wrappers.
* If doesn't apply to the value, returns null.
* <p>
* NOTE: The other unwrap methods automatically perform this operation as well.
*/
public static Object unwrapIfEscapingOrNull(TemplateModel templateModel) throws TemplateModelException {
if (templateModel instanceof EscapingModel) {
return ((EscapingModel) templateModel).getWrappedObject();
}
return null;
}
/**
* SCIPIO: Special unwrap that unwraps only objects wrapped with special escaping (Ofbiz) wrappers.
* If doesn't apply to the value, returns null.
* <p>
* NOTE: The other unwrap methods automatically perform this operation as well.
*/
public static Object unwrapIfEscapingOrNull(Object value) throws TemplateModelException {
if (value instanceof EscapingModel) {
return ((EscapingModel) value).getWrappedObject();
}
return null;
}
public static String camelCaseToDashLowerName(String name) {
// TODO: optimize
return name.replaceAll("([A-Z])", "-$1").toLowerCase();
}
public static <K, V> Map<K, V> concatMaps(Map<? extends K, ? extends V> first, Map<? extends K, ? extends V> second) {
Map<K, V> res = new LinkedHashMap<K, V>();
if (first != null) {
res.putAll(first);
}
if (second != null) {
res.putAll(second);
}
return res;
}
/**
* Checks if the given model matches the logical FTL object type.
*
* @see com.ilscipio.scipio.ce.webapp.ftl.lang.OfbizFtlObjectType
*/
public static boolean isObjectType(String ftlTypeName, TemplateModel object) {
return OfbizFtlObjectType.isObjectTypeSafe(ftlTypeName, object);
}
/**
* Gets map keys, either as collection or Set.
* <p>
* WARN: auto-escaping is bypassed on all keys, caller handles.
* (e.g. the object wrapper used to rewrap the result).
* DEV NOTE: we MUST manually bypass auto-escaping for all on this one.
*/
public static Object getMapKeys(TemplateModel object) throws TemplateModelException {
if (OfbizFtlObjectType.COMPLEXMAP.isObjectType(object)) {
// WARN: bypasses auto-escaping
Map<Object, Object> wrappedObject = UtilGenerics.cast(((WrapperTemplateModel) object).getWrappedObject());
return wrappedObject.keySet();
} else if (object instanceof TemplateHashModelEx) {
// 2016-04-20: cannot do this because we MUST trigger bypass of auto-escaping,
// so just do a deep unwrap, which automatically bypasses the escaping,
// and then caller handles the result, which is probably an arraylist
//return ((TemplateHashModelEx) object).keys();
return unwrapAlways(((TemplateHashModelEx) object).keys());
} else {
throw new TemplateModelException("object is not a map or does not support key iteration");
}
}
/**
* Shallow-copies map or list. Note: won't preserve order for maps.
*
* @param object
* @param targetType if true, converts to simple FTL type instead of beans, where possible
* @return
* @throws TemplateModelException
*/
public static Object copyObject(TemplateModel object, TemplateValueTargetType targetType, ObjectWrapper objectWrapper) throws TemplateModelException {
if (targetType == null) {
targetType = TemplateValueTargetType.PRESERVE;
}
if (OfbizFtlObjectType.COMPLEXMAP.isObjectType(object) || (object instanceof TemplateHashModelEx && OfbizFtlObjectType.MAP.isObjectType(object))) {
return LangFtlUtil.copyMap(object, null, null, targetType, objectWrapper);
} else if (object instanceof TemplateCollectionModel || object instanceof TemplateSequenceModel) {
return LangFtlUtil.copyList(object, targetType, objectWrapper);
} else {
throw new TemplateModelException("object is not cloneable");
}
}
/**
* Copies map.
* <p>
* WARN: For complex maps, auto-escaping is bypassed; caller must decide how to handle.
* (e.g. the object wrapper used to rewrap the result).
* <p>
* FIXME: The rewrapping objectWrapper behavior is inconsistent! may lead to auto-escape issues
*/
public static Object copyMap(TemplateModel object, Set<String> inExKeys, Boolean include,
TemplateValueTargetType targetType, ObjectWrapper objectWrapper) throws TemplateModelException {
if (targetType == null) {
targetType = TemplateValueTargetType.PRESERVE;
}
if (OfbizFtlObjectType.COMPLEXMAP.isObjectType(object)) {
// WARN: bypasses auto-escaping
Map<String, Object> wrappedObject = UtilGenerics.cast(((WrapperTemplateModel) object).getWrappedObject());
// TODO: this only handles most urgent targetType case
if (targetType == TemplateValueTargetType.SIMPLEMODEL) {
return LangFtlUtil.copyMapToSimple(wrappedObject, inExKeys, include, objectWrapper);
} else {
return LangFtlUtil.copyMapToRawMap(wrappedObject, inExKeys, include);
}
} else if (object instanceof TemplateHashModel && OfbizFtlObjectType.MAP.isObjectType(object)) {
// TODO: this ignores targetType
return LangFtlUtil.copyMapToSimple((TemplateHashModel) object, inExKeys, include, objectWrapper);
}
throw new TemplateModelException("Cannot copy map of type " + object.getClass().toString() +
" to target type: " + targetType.toString());
}
public static SimpleHash copyMapToSimple(TemplateHashModel hashModel, Set<String> inExKeys, Boolean include, ObjectWrapper objectWrapper) throws TemplateModelException {
SimpleHash res = new SimpleHash(objectWrapper);
putAll(res, hashModel, inExKeys, include, objectWrapper);
return res;
}
public static void putAll(SimpleHash res, TemplateHashModel hashModel, Set<String> inExKeys, Boolean include, ObjectWrapper objectWrapper) throws TemplateModelException {
if (include == Boolean.TRUE) {
if (inExKeys == null) {
inExKeys = new HashSet<String>();
}
for(String key : inExKeys) {
TemplateModel valueModel = hashModel.get(key);
if (inExKeys.contains(key)) {
res.put(key, valueModel);
}
}
} else if (include == null || inExKeys == null || inExKeys.isEmpty()) {
if (!(hashModel instanceof TemplateHashModelEx)) {
throw new TemplateModelException("Hash to copy does not support ?keys");
}
TemplateCollectionModel keys = ((TemplateHashModelEx) hashModel).keys();
TemplateModelIterator keysIt = keys.iterator();
while(keysIt.hasNext()) {
String key = getAsStringNonEscaping((TemplateScalarModel) keysIt.next());
res.put(key, hashModel.get(key));
}
} else {
if (!(hashModel instanceof TemplateHashModelEx)) {
throw new TemplateModelException("Hash to copy does not support ?keys");
}
TemplateCollectionModel keys = ((TemplateHashModelEx) hashModel).keys();
TemplateModelIterator keysIt = keys.iterator();
while(keysIt.hasNext()) {
String key = getAsStringNonEscaping((TemplateScalarModel) keysIt.next());
TemplateModel valueModel = hashModel.get(key);
if (!inExKeys.contains(key)) {
res.put(key, valueModel);
}
}
}
}
public static SimpleHash copyMapToSimple(Map<String, Object> map, Set<String> inExKeys, Boolean include, ObjectWrapper objectWrapper) throws TemplateModelException {
if (include == Boolean.TRUE) {
SimpleHash res = new SimpleHash(objectWrapper);
if (inExKeys == null) {
inExKeys = new HashSet<String>();
}
for(String key : inExKeys) {
Object valueModel = map.get(key);
if (inExKeys.contains(key)) {
res.put(key, valueModel);
}
}
return res;
}
else if (include == null || inExKeys == null || inExKeys.isEmpty()) {
return new SimpleHash(map, objectWrapper);
}
else {
SimpleHash res = new SimpleHash(objectWrapper);
for(Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
if (!inExKeys.contains(key)) {
res.put(key, entry.getValue());
}
}
return res;
}
}
public static Map<String, Object> copyMapToRawMap(Map<String, Object> map, Set<String> inExKeys, Boolean include) throws TemplateModelException {
if (include == Boolean.TRUE) {
Map<String, Object> res = new HashMap<>(map.size());
if (inExKeys == null) {
inExKeys = new HashSet<String>();
}
for(String key : inExKeys) {
Object valueModel = map.get(key);
if (inExKeys.contains(key)) {
res.put(key, valueModel);
}
}
return res;
}
else if (include == null || inExKeys == null || inExKeys.isEmpty()) {
return new HashMap<>(map);
}
else {
Map<String, Object> res = new HashMap<>(map.size());
for(Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
if (!inExKeys.contains(key)) {
res.put(key, entry.getValue());
}
}
return res;
}
}
/**
* Copies a list to a target model/raw list type. In general does not wrap/unwrap individual values.
*/
public static Object copyList(Object object, TemplateValueTargetType targetType, ObjectWrapper objectWrapper) throws TemplateModelException {
if (targetType == null) {
targetType = TemplateValueTargetType.PRESERVE;
}
if (object instanceof Iterable) {
return LangFtlUtil.copyList(UtilGenerics.<Iterable<Object>>cast(object), targetType, objectWrapper);
}
else if (object instanceof TemplateModel) {
return LangFtlUtil.copyList((TemplateModel) object, targetType, objectWrapper);
}
throw new TemplateModelException("Cannot copy list of type " + object.getClass().toString() +
" to target type: " + targetType.toString());
}
public static Object copyList(Iterable<Object> object, TemplateValueTargetType targetType, ObjectWrapper objectWrapper) throws TemplateModelException {
if (targetType == null) {
targetType = TemplateValueTargetType.PRESERVE;
}
if (object instanceof Collection) {
Collection<Object> collection = UtilGenerics.<Collection<Object>>cast(object);
if (targetType == TemplateValueTargetType.PRESERVE || targetType == TemplateValueTargetType.RAW) {
return new ArrayList<>(collection);
}
else if (targetType == TemplateValueTargetType.MODEL || targetType == TemplateValueTargetType.SIMPLEMODEL) {
return new SimpleSequence(collection, objectWrapper);
}
else if (targetType == TemplateValueTargetType.COMPLEXMODEL) {
// no choice but to use user-supplied object wrapper
return wrap(new ArrayList<>(collection), objectWrapper);
}
}
else {
Iterable<Object> iterable = UtilGenerics.<Iterable<Object>>cast(object);
if (targetType == TemplateValueTargetType.PRESERVE || targetType == TemplateValueTargetType.RAW) {
List<Object> res = new ArrayList<>();
for(Object val : iterable) {
res.add(val);
}
return res;
}
else if (targetType == TemplateValueTargetType.MODEL || targetType == TemplateValueTargetType.SIMPLEMODEL) {
SimpleSequence res = new SimpleSequence(objectWrapper);
for(Object val : iterable) {
res.add(val);
}
return res;
}
else if (targetType == TemplateValueTargetType.COMPLEXMODEL) {
List<Object> res = new ArrayList<>();
for(Object val : iterable) {
res.add(val);
}
return wrap(res, objectWrapper);
}
}
throw new TemplateModelException("Cannot copy list of type " + object.getClass().toString() +
" to target type: " + targetType.toString());
}
/**
* Copies list.
* <p>
* WARN: For complex lists, auto-escaping is bypassed. Caller must decide how to handle.
* (e.g. the object wrapper used to rewrap the result).
* <p>
* FIXME: The rewrapping objectWrapper behavior is inconsistent! may lead to auto-escape issues
*/
public static Object copyList(TemplateModel object, TemplateValueTargetType targetType, ObjectWrapper objectWrapper) throws TemplateModelException {
if (targetType == null) {
targetType = TemplateValueTargetType.PRESERVE;
}
if (object instanceof TemplateCollectionModel) { // TODO: isObjectType
TemplateCollectionModel collectionModel = (TemplateCollectionModel) object;
if (targetType == TemplateValueTargetType.RAW) {
List<Object> res = new ArrayList<>();
TemplateModelIterator it = collectionModel.iterator();
while(it.hasNext()) {
res.add(it.next());
}
return res;
}
else if (targetType == TemplateValueTargetType.MODEL || targetType == TemplateValueTargetType.SIMPLEMODEL || targetType == TemplateValueTargetType.PRESERVE) {
// We CANNOT do this, we must make a new one with the specified object wrapper (unfortunately),
// even if nothing else about it changes
//return new SimpleSequence(collectionModel);
SimpleSequence res = new SimpleSequence(objectWrapper);
TemplateModelIterator it = collectionModel.iterator();
while(it.hasNext()) {
res.add(it.next());
}
return res;
}
else if (targetType == TemplateValueTargetType.COMPLEXMODEL) {
List<Object> res = new ArrayList<>();
TemplateModelIterator it = collectionModel.iterator();
while(it.hasNext()) {
res.add(it.next());
}
return wrap(res, objectWrapper);
}
}
else if (object instanceof TemplateSequenceModel) { // TODO: isObjectType
TemplateSequenceModel seqModel = (TemplateSequenceModel) object;
if (targetType == TemplateValueTargetType.RAW) {
List<Object> res = new ArrayList<>();
for(int i=0; i < seqModel.size(); i++) {
res.add(seqModel.get(i));
}
return res;
}
else if (targetType == TemplateValueTargetType.MODEL || targetType == TemplateValueTargetType.SIMPLEMODEL || targetType == TemplateValueTargetType.PRESERVE) {
SimpleSequence res = new SimpleSequence(seqModel.size(), objectWrapper);
for(int i=0; i < seqModel.size(); i++) {
res.add(seqModel.get(i));
}
return res;
}
else if (targetType == TemplateValueTargetType.COMPLEXMODEL) {
List<Object> res = new ArrayList<>();
for(int i=0; i < seqModel.size(); i++) {
res.add(seqModel.get(i));
}
return wrap(res, objectWrapper);
}
}
else if (object instanceof WrapperTemplateModel) {
// WARN: bypasses auto-escaping
Object wrappedObj = ((WrapperTemplateModel) object).getWrappedObject();
if (wrappedObj instanceof Collection) {
Collection<Object> collection = UtilGenerics.<Collection<Object>>cast(object);
if (targetType == TemplateValueTargetType.RAW) {
return new ArrayList<>(collection);
}
else if (targetType == TemplateValueTargetType.MODEL || targetType == TemplateValueTargetType.SIMPLEMODEL) {
return new SimpleSequence(collection, objectWrapper);
}
else if (targetType == TemplateValueTargetType.PRESERVE || targetType == TemplateValueTargetType.COMPLEXMODEL) {
return wrap(new ArrayList<>(collection), objectWrapper);
}
}
else if (wrappedObj instanceof Iterable) {
Iterable<Object> iterable = UtilGenerics.<Iterable<Object>>cast(object);
if (targetType == TemplateValueTargetType.RAW) {
List<Object> res = new ArrayList<>();
for(Object val : iterable) {
res.add(val);
}
return res;
}
else if (targetType == TemplateValueTargetType.MODEL || targetType == TemplateValueTargetType.SIMPLEMODEL) {
SimpleSequence res = new SimpleSequence(objectWrapper);
for(Object val : iterable) {
res.add(val);
}
return res;
}
else if (targetType == TemplateValueTargetType.PRESERVE || targetType == TemplateValueTargetType.COMPLEXMODEL) {
List<Object> res = new ArrayList<>();
for(Object val : iterable) {
res.add(val);
}
return wrap(res, objectWrapper);
}
}
}
throw new TemplateModelException("Cannot copy list of type " + object.getClass().toString() +
" to target type: " + targetType.toString());
}
/**
* Converts map to a simple wrapper, if applicable. Currently only applies to complex maps.
* <p>
* 2017-01-26: This has been changed so that it will by default try to wrap everything
* with DefaultMapAdapter (or SimpleMapModel for BeansWrapper compatibility), which will always work as long as
* the ObjectWrapper implements ObjectWrapperWithAPISupport.
* <p>
* WARN: Bypasses auto-escaping for complex maps; caller must decide how to handle
* (e.g. the object wrapper used to rewrap the result).
* Other types of maps are not altered.
*/
public static TemplateHashModel toSimpleMap(TemplateModel object, Boolean copy, ObjectWrapper objectWrapper) throws TemplateModelException {
if (OfbizFtlObjectType.COMPLEXMAP.isObjectType(object)) {
// WARN: bypasses auto-escaping
Map<?, ?> wrappedObject = UtilGenerics.cast(((WrapperTemplateModel) object).getWrappedObject());
if (Boolean.TRUE.equals(copy)) {
return makeSimpleMapCopy(wrappedObject, objectWrapper);
} else {
return makeSimpleMapAdapter(wrappedObject, objectWrapper, true);
}
} else if (object instanceof TemplateHashModel) {
return (TemplateHashModel) object;
} else {
throw new TemplateModelException("object is not a recognized map type");
}
}
public static TemplateHashModelEx makeSimpleMapCopy(Map<?, ?> map, ObjectWrapper objectWrapper) throws TemplateModelException {
return new SimpleHash(map, objectWrapper);
}
/**
* Adapts a map to a TemplateHashModelEx using an appropriate simple adapter, normally
* DefaultMapAdapter (or SimpleMapModel for BeansWrapper compatibility).
* <p>
* The ObjectWrapper is expected to implement at least ObjectWrapperWithAPISupport.
* <p>
* WARN: If impossible, it will duplicate the map using SimpleHash; but because this may result
* in loss of ordering, a log warning will be printed.
*/
public static TemplateHashModelEx makeSimpleMapAdapter(Map<?, ?> map, ObjectWrapper objectWrapper, boolean permissive) throws TemplateModelException {
// COMPATIBILITY MODE: check if exactly BeansWrapper, or a class that we know extends it WITHOUT extending DefaultObjectWrapper
if (objectWrapper instanceof ScipioBeansWrapper || BeansWrapper.class.equals(objectWrapper.getClass())) {
return new SimpleMapModel(map, (BeansWrapper) objectWrapper);
} else if (objectWrapper instanceof ObjectWrapperWithAPISupport) {
return DefaultMapAdapter.adapt(map, (ObjectWrapperWithAPISupport) objectWrapper);
} else {
if (permissive) {
Debug.logWarning("Scipio: adaptSimpleMap: Unsupported Freemarker object wrapper (expected to implement ObjectWrapperWithAPISupport or BeansWrapper); forced to adapt map"
+ " using SimpleHash; this could cause loss of map insertion ordering; please switch renderer setup to a different ObjectWrapper", module);
return new SimpleHash(map, objectWrapper);
} else {
throw new TemplateModelException("Tried to wrap a Map using an adapter class,"
+ " but our ObjectWrapper does not implement ObjectWrapperWithAPISupport or BeansWrapper"
+ "; please switch renderer setup to a different ObjectWrapper");
}
}
}
/**
* Converts map to a simple wrapper, if applicable, by rewrapping
* known complex map wrappers that implement <code>WrapperTemplateModel</code>.
* <p>
* If the specified ObjectWrapper is a BeansWrapper, this forces rewrapping as a SimpleMapModel.
* If it isn't we assume caller specified an objectWrapper that will rewrap the map with
* a simple model (we have no way of knowing).
* <p>
* WARN: Bypasses auto-escaping for complex maps; caller must decide how to handle
* (e.g. the object wrapper used to rewrap the result).
* Other types of maps are not altered.
*
* @deprecated don't use
*/
@SuppressWarnings("unused")
@Deprecated
private static TemplateHashModel toSimpleMapRewrapAdapters(TemplateModel object, ObjectWrapper objectWrapper) throws TemplateModelException {
if (object instanceof SimpleMapModel || object instanceof BeanModel || object instanceof DefaultMapAdapter) {
// Permissive
Map<?, ?> wrappedObject = (Map<?, ?>) ((WrapperTemplateModel) object).getWrappedObject();
if (objectWrapper instanceof BeansWrapper) {
// Bypass the beanswrapper wrap method and always make simple wrapper
return new SimpleMapModel(wrappedObject, (BeansWrapper) objectWrapper);
} else {
// If anything other than BeansWrapper, assume caller is aware and his wrapper will create a simple map
return (TemplateHashModel) objectWrapper.wrap(wrappedObject);
}
}
else if (object instanceof TemplateHashModel) {
return (TemplateHashModel) object;
}
else {
throw new TemplateModelException("object is not a recognized map type");
}
}
/**
* Converts map to a simple wrapper, if applicable, by rewrapping
* any map wrappers that implement <code>WrapperTemplateModel</code>.
* <p>
* This method is very permissive: anything that wraps a Map is accepted.
* Other types of hashes are returned as-is.
* <p>
* If the specified ObjectWrapper is a BeansWrapper, this forces rewrapping as a SimpleMapModel.
* If it isn't we assume caller specified an objectWrapper that will rewrap the map with
* a simple model (we have no way of knowing).
* <p>
* WARN: Bypasses auto-escaping for complex maps; caller must decide how to handle
* (e.g. the object wrapper used to rewrap the result).
* Other types of maps are not altered.
*
* @deprecated don't use
*/
@SuppressWarnings("unused")
@Deprecated
private static TemplateHashModel toSimpleMapRewrapAny(TemplateModel object, ObjectWrapper objectWrapper) throws TemplateModelException {
if (object instanceof WrapperTemplateModel) {
// Permissive
Map<?, ?> wrappedObject = (Map<?, ?>) ((WrapperTemplateModel) object).getWrappedObject();
if (objectWrapper instanceof BeansWrapper) {
// Bypass the beanswrapper wrap method and always make simple wrapper
return new SimpleMapModel(wrappedObject, (BeansWrapper) objectWrapper);
} else {
// If anything other than BeansWrapper, assume caller is aware and his wrapper will create a simple map
return (TemplateHashModel) objectWrapper.wrap(wrappedObject);
}
}
else if (object instanceof TemplateHashModel) {
return (TemplateHashModel) object;
}
else {
throw new TemplateModelException("object is not a recognized map type");
}
}
/**
* Supposed to convert to simple sequence.
* <p>
* WARN: Bypasses auto-escaping for complex maps, caller must decide how to handle.
* (e.g. the object wrapper used to rewrap the result).
* <p>
* DEV NOTE: I stopped writing/testing this when found out most of the problems w.r.t. collections are not
* the FTL types this time but the way they're used in Ofbiz templates.
* FTL's CollectionModel (subclass of TemplateCollectionModel) is supposed to cover everything and
* won't suffer from the same problems maps have.
*/
@SuppressWarnings({ "unchecked", "unused" })
@Deprecated
private static TemplateSequenceModel toSimpleSequence(TemplateModel object, ObjectWrapper objectWrapper) throws TemplateModelException {
if (object instanceof TemplateSequenceModel) {
return (TemplateSequenceModel) object;
}
else if (object instanceof WrapperTemplateModel) {
WrapperTemplateModel wrapperModel = (WrapperTemplateModel) object;
// WARN: bypasses auto-escaping
Object wrappedObject = wrapperModel.getWrappedObject();
if (wrappedObject instanceof List) {
return DefaultListAdapter.adapt((List<Object>) wrappedObject, (RichObjectWrapper) objectWrapper);
}
else if (wrappedObject instanceof Object[]) {
return DefaultArrayAdapter.adapt((Object[]) wrappedObject, (ObjectWrapperAndUnwrapper) objectWrapper);
}
else if (wrappedObject instanceof Set) {
throw new UnsupportedOperationException("Not yet implemented");
}
else if (wrappedObject instanceof Collection) {
throw new UnsupportedOperationException("Not yet implemented");
}
else if (wrappedObject instanceof Iterable) {
throw new UnsupportedOperationException("Not yet implemented");
}
else {
throw new TemplateModelException("Cannot convert bean-wrapped object of type " + (object != null ? object.getClass() : "null") + " to simple sequence");
}
} else if (object instanceof TemplateCollectionModel) {
TemplateCollectionModel collModel = (TemplateCollectionModel) object;
SimpleSequence res = new SimpleSequence(objectWrapper);
TemplateModelIterator it = collModel.iterator();
while(it.hasNext()) {
res.add(it.next());
}
return res;
} else {
throw new TemplateModelException("Cannot convert object of type " + (object != null ? object.getClass() : "null") + " to simple sequence");
}
}
/**
* Adds all the elements in the given collection to a new set.
* @deprecated 2019-01-28: objectWrapper is not necessary.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
@Deprecated
public static <T> Set<T> toSet(TemplateModel object, ObjectWrapper objectWrapper) throws TemplateModelException {
return toSet(object);
}
/**
* Adds all the elements in the given collection to a new set.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
@SuppressWarnings("unchecked")
public static <T> Set<T> toSet(TemplateModel object) throws TemplateModelException {
if (object instanceof WrapperTemplateModel && ((WrapperTemplateModel) object).getWrappedObject() instanceof Set) {
return (Set<T>) ((WrapperTemplateModel) object).getWrappedObject();
} else if (object instanceof TemplateCollectionModel) {
return toSet((TemplateCollectionModel) object);
} else if (object instanceof TemplateSequenceModel) {
return toSet((TemplateSequenceModel) object);
} else {
throw new TemplateModelException("Cannot convert object of type " + (object != null ? object.getClass() : "null") + " to set");
}
}
/**
* Adds all the elements in the given collection to a new set.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T> Set<T> toSet(TemplateCollectionModel object) throws TemplateModelException {
// would be safer to let the wrapper do it, but we know it's just a BeanModel in Ofbiz so we can optimize.
TemplateCollectionModel collModel = (TemplateCollectionModel) object;
Set<Object> res = new HashSet<>();
TemplateModelIterator it = collModel.iterator();
while(it.hasNext()) {
res.add(LangFtlUtil.unwrapAlways(it.next()));
}
return UtilGenerics.cast(res);
}
/**
* Adds all the elements in the given collection to a new set.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T> Set<T> toSet(TemplateSequenceModel object) throws TemplateModelException {
TemplateSequenceModel seqModel = (TemplateSequenceModel) object;
Set<Object> res = new HashSet<>();
for(int i=0; i < seqModel.size(); i++) {
res.add(LangFtlUtil.unwrapAlways(seqModel.get(i)));
}
return UtilGenerics.cast(res);
}
/**
* Adds all the elements in the given collection to a new set, as TemplateModels.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T extends TemplateModel> Set<T> toSetNoUnwrap(TemplateCollectionModel object) throws TemplateModelException {
// would be safer to let the wrapper do it, but we know it's just a BeanModel in Ofbiz so we can optimize.
TemplateCollectionModel collModel = (TemplateCollectionModel) object;
Set<Object> res = new HashSet<>();
TemplateModelIterator it = collModel.iterator();
while(it.hasNext()) {
res.add(it.next());
}
return UtilGenerics.cast(res);
}
/**
* Adds all the elements in the given collection to a new set, as TemplateModels.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T extends TemplateModel> Set<T> toSetNoUnwrap(TemplateSequenceModel object) throws TemplateModelException {
TemplateSequenceModel seqModel = (TemplateSequenceModel) object;
Set<Object> res = new HashSet<>();
for(int i=0; i < seqModel.size(); i++) {
res.add(seqModel.get(i));
}
return UtilGenerics.cast(res);
}
/**
* Adds all the elements in the given collection to a new list.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T> List<T> toList(TemplateCollectionModel collModel) throws TemplateModelException {
// would be safer to let the wrapper do it, but we know it's just a BeanModel in Ofbiz so we can optimize.
List<Object> res = new ArrayList<>();
TemplateModelIterator it = collModel.iterator();
while(it.hasNext()) {
res.add(LangFtlUtil.unwrapAlways(it.next()));
}
return UtilGenerics.cast(res);
}
/**
* Adds all the elements in the given collection to a new list.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T> List<T> toList(TemplateSequenceModel seqModel) throws TemplateModelException {
List<Object> res = new ArrayList<>();
for(int i=0; i < seqModel.size(); i++) {
res.add(LangFtlUtil.unwrapAlways(seqModel.get(i)));
}
return UtilGenerics.cast(res);
}
/**
* Adds all the elements in the given collection to a new list, as TemplateModels.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T extends TemplateModel> List<T> toListNoUnwrap(TemplateCollectionModel collModel) throws TemplateModelException {
// would be safer to let the wrapper do it, but we know it's just a BeanModel in Ofbiz so we can optimize.
List<Object> res = new ArrayList<>();
TemplateModelIterator it = collModel.iterator();
while(it.hasNext()) {
res.add(it.next());
}
return UtilGenerics.cast(res);
}
/**
* Adds all the elements in the given collection to a new list, as TemplateModels.
* <p>
* NOTE: This method does NOT handle string escaping explicitly; you may want {@link #toStringSet} instead.
*/
public static <T extends TemplateModel> List<T> toListNoUnwrap(TemplateSequenceModel seqModel) throws TemplateModelException {
List<Object> res = new ArrayList<>();
for(int i=0; i < seqModel.size(); i++) {
res.add(seqModel.get(i));
}
return UtilGenerics.cast(res);
}
/*
* DEV NOTE: This has been removed along with all code that relied on it. it adds too much liability.
* for now there are no more places we need it.
*
* Same as Freemarker's ?is_directive.
* <p>
* <em>NOTE:</em> This <em>must</em> have the exact same behavior as Freemarker's ?is_directive.
* Please refer to Freemarker source code.
* Unfortunately there is no evident way of reusing their code from here...
* <p>
* <strong>WARNING:</strong> FIXME: This currently refers to the FTL freemarker.core.Macro class, which is set
* to change at any time. this needs a better solution!!!
*/
/*
public static boolean isDirective(Object object) {
return (object instanceof TemplateTransformModel || object instanceof freemarker.core.Macro || object instanceof TemplateDirectiveModel);
}
*/
/**
* Adds to simple hash from source map.
* <p>
* <em>WARN</em>: This is not BeanModel-aware (complex map).
*/
public static void addToSimpleMap(SimpleHash dest, TemplateHashModelEx source) throws TemplateModelException {
TemplateCollectionModel keysModel = source.keys();
TemplateModelIterator modelIt = keysModel.iterator();
while(modelIt.hasNext()) {
String key = getAsStringNonEscaping((TemplateScalarModel) modelIt.next());
dest.put(key, source.get(key));
}
}
public static void addToSimpleMap(SimpleHash dest, TemplateHashModel source, Set<String> keys) throws TemplateModelException {
for(String key : keys) {
dest.put(key, source.get(key));
}
}
/**
* Adds the still-wrapped TemplateModels in hash to a java Map.
* <p>
* <em>WARN</em>: This is not BeanModel-aware (complex map).
*/
public static void addModelsToMap(Map<String, ? super TemplateModel> dest, TemplateHashModelEx source) throws TemplateModelException {
TemplateCollectionModel keysModel = source.keys();
TemplateModelIterator modelIt = keysModel.iterator();
while(modelIt.hasNext()) {
String key = getAsStringNonEscaping((TemplateScalarModel) modelIt.next());
dest.put(key, source.get(key));
}
}
public static void addModelsToMap(Map<String, ? super TemplateModel> dest, TemplateHashModel source, Set<String> keys) throws TemplateModelException {
for(String key : keys) {
dest.put(key, source.get(key));
}
}
/**
* Makes a simple hash from source map; only specified keys.
* <p>
* <em>WARN</em>: This is not BeanModel-aware (complex map).
*/
public static SimpleHash makeSimpleMap(TemplateHashModel map, Set<String> keys, ObjectWrapper objectWrapper) throws TemplateModelException {
SimpleHash res = new SimpleHash(objectWrapper);
addToSimpleMap(res, map, keys);
return res;
}
public static SimpleHash makeSimpleMap(TemplateHashModel map, TemplateCollectionModel keys, ObjectWrapper objectWrapper) throws TemplateModelException {
SimpleHash res = new SimpleHash(objectWrapper);
addToSimpleMap(res, map, LangFtlUtil.toStringSet(keys));
return res;
}
public static SimpleHash makeSimpleMap(TemplateHashModel map, TemplateSequenceModel keys, ObjectWrapper objectWrapper) throws TemplateModelException {
SimpleHash res = new SimpleHash(objectWrapper);
addToSimpleMap(res, map, LangFtlUtil.toStringSet(keys));
return res;
}
public static Map<String, TemplateModel> makeModelMap(TemplateHashModelEx source) throws TemplateModelException {
Map<String, TemplateModel> map = new HashMap<>();
addModelsToMap(map, source);
return map;
}
public static Map<String, Object> makeModelObjectMap(TemplateHashModelEx source) throws TemplateModelException {
Map<String, Object> map = new HashMap<>();
addModelsToMap(map, source);
return map;
}
/**
* To string set.
* <p>
* WARN: Bypasses auto-escaping, caller handles.
* (e.g. the object wrapper used to rewrap the result).
*/
public static Set<String> toStringSet(TemplateCollectionModel collModel) throws TemplateModelException {
Set<String> set = new HashSet<String>();
TemplateModelIterator modelIt = collModel.iterator();
while(modelIt.hasNext()) {
set.add(getAsStringNonEscaping((TemplateScalarModel) modelIt.next()));
}
return set;
}
/**
* Add to string set.
* <p>
* WARN: bypasses auto-escaping, caller handles.
* (e.g. the object wrapper used to rewrap the result).
*/
public static void addToStringSet(Set<String> dest, TemplateCollectionModel collModel) throws TemplateModelException {
TemplateModelIterator modelIt = collModel.iterator();
while(modelIt.hasNext()) {
dest.add(getAsStringNonEscaping((TemplateScalarModel) modelIt.next()));
}
}
/**
* To string set.
* <p>
* WARN: bypasses auto-escaping, caller handles.
* (e.g. the object wrapper used to rewrap the result).
*/
public static Set<String> toStringSet(TemplateSequenceModel seqModel) throws TemplateModelException {
Set<String> set = new HashSet<String>();
for(int i=0; i < seqModel.size(); i++) {
set.add(getAsStringNonEscaping((TemplateScalarModel) seqModel.get(i)));
}
return set;
}
/**
* Add to string set.
* <p>
* WARN: bypasses auto-escaping, caller handles.
* (e.g. the object wrapper used to rewrap the result).
*/
public static void addToStringSet(Set<String> dest, TemplateSequenceModel seqModel) throws TemplateModelException {
for(int i=0; i < seqModel.size(); i++) {
dest.add(getAsStringNonEscaping(((TemplateScalarModel) seqModel.get(i))));
}
}
/**
* Combines two maps with the given operator into a new hash.
*/
public static TemplateHashModelEx combineMaps(TemplateHashModelEx first, TemplateHashModelEx second, SetOperations ops,
ObjectWrapper objectWrapper) throws TemplateModelException {
SimpleHash res = new SimpleHash(objectWrapper);
if (ops == null || ops == SetOperations.UNION) {
// this is less efficient than freemarker + operator, but provides the "alternative" implementation, so have choice
addToSimpleMap(res, first);
addToSimpleMap(res, second);
}
else if (ops == SetOperations.INTERSECT) {
Set<String> intersectKeys = toStringSet(second.keys());
intersectKeys.retainAll(toStringSet(first.keys()));
addToSimpleMap(res, second, intersectKeys);
}
else if (ops == SetOperations.DIFFERENCE) {
Set<String> diffKeys = toStringSet(first.keys());
diffKeys.removeAll(toStringSet(second.keys()));
addToSimpleMap(res, first, diffKeys);
}
else {
throw new TemplateModelException("Unsupported combineMaps operation");
}
return res;
}
/**
* Gets collection as a keys.
* <p>
* WARN: This bypasses auto-escaping in all cases. Caller must decide how to handle.
* (e.g. the object wrapper used to rewrap the result).
*/
public static Set<String> getAsStringSet(TemplateModel model) throws TemplateModelException {
Set<String> exKeys = null;
if (model != null) {
if (model instanceof BeanModel && ((BeanModel) model).getWrappedObject() instanceof Set) {
// WARN: bypasses auto-escaping
exKeys = UtilGenerics.cast(((BeanModel) model).getWrappedObject());
}
else if (model instanceof TemplateCollectionModel) {
exKeys = new HashSet<String>();
TemplateModelIterator keysIt = ((TemplateCollectionModel) model).iterator();
while(keysIt.hasNext()) {
exKeys.add(getAsStringNonEscaping((TemplateScalarModel) keysIt.next()));
}
}
else if (model instanceof TemplateSequenceModel) {
TemplateSequenceModel seqModel = (TemplateSequenceModel) model;
exKeys = new HashSet<String>(seqModel.size());
for(int i=0; i < seqModel.size(); i++) {
exKeys.add(getAsStringNonEscaping((TemplateScalarModel) seqModel.get(i)));
}
}
else {
throw new TemplateModelException("Include/exclude keys argument not a collection or set of strings");
}
}
return exKeys;
}
public static void addToSimpleList(SimpleSequence dest, TemplateCollectionModel source) throws TemplateModelException {
TemplateModelIterator it = source.iterator();
while(it.hasNext()) {
dest.add(it.next());
}
}
public static void addToSimpleList(SimpleSequence dest, TemplateSequenceModel source) throws TemplateModelException {
for(int i=0; i < source.size(); i++) {
dest.add(source.get(0));
}
}
public static void addToSimpleList(SimpleSequence dest, TemplateModel source) throws TemplateModelException {
if (source instanceof TemplateCollectionModel) {
addToSimpleList(dest, (TemplateCollectionModel) source);
}
else if (source instanceof TemplateSequenceModel) {
addToSimpleList(dest, (TemplateSequenceModel) source);
}
else {
throw new TemplateModelException("Can't add to simple list from source type (non-list type): " + source.getClass());
}
}
/**
* Puts all values in hash into FTL variables, decided by a varHandler.
* <p>
* TODO: replace tests with a filter class similar to FtlVarHandler.
* <p>
* @see #copyMapToSimple
*/
public static void varsPutAll(TemplateHashModel hashModel, Set<String> inExKeys, Boolean include,
FtlVarHandler varHandler, Environment env) throws TemplateModelException {
if (include == Boolean.TRUE) {
if (inExKeys == null) {
inExKeys = new HashSet<String>();
}
for(String key : inExKeys) {
TemplateModel valueModel = hashModel.get(key);
if (inExKeys.contains(key)) {
varHandler.setVariable(key, valueModel);
}
}
}
else if (include == null || inExKeys == null || inExKeys.isEmpty()) {
if (!(hashModel instanceof TemplateHashModelEx)) {
throw new TemplateModelException("Hash to copy does not support ?keys");
}
TemplateCollectionModel keys = ((TemplateHashModelEx) hashModel).keys();
TemplateModelIterator keysIt = keys.iterator();
while(keysIt.hasNext()) {
String key = getAsStringNonEscaping((TemplateScalarModel) keysIt.next());
varHandler.setVariable(key, hashModel.get(key));
}
}
else {
if (!(hashModel instanceof TemplateHashModelEx)) {
throw new TemplateModelException("Hash to copy does not support ?keys");
}
TemplateCollectionModel keys = ((TemplateHashModelEx) hashModel).keys();
TemplateModelIterator keysIt = keys.iterator();
while(keysIt.hasNext()) {
String key = getAsStringNonEscaping((TemplateScalarModel) keysIt.next());
TemplateModel valueModel = hashModel.get(key);
if (!inExKeys.contains(key)) {
varHandler.setVariable(key, valueModel);
}
}
}
}
/**
* Puts all values in hash into FTL globals (#global).
* <p>
* @see #copyMapToSimple
*/
public static void globalsPutAll(TemplateHashModel hashModel, Set<String> inExKeys, Boolean include, Environment env) throws TemplateModelException {
varsPutAll(hashModel, inExKeys, include, new GlobalFtlVarHandler(env), env);
}
/**
* Puts all values in hash into FTL globals (#global).
* <p>
* @see #copyMapToSimple
*/
public static void globalsPutAll(TemplateHashModelEx hashModel, Environment env) throws TemplateModelException {
varsPutAll(hashModel, null, null, new GlobalFtlVarHandler(env), env);
}
/**
* Puts all values in hash into FTL current namespace vars (#assign).
* <p>
* @see #copyMapToSimple
*/
public static void varsPutAll(TemplateHashModel hashModel, Set<String> inExKeys, Boolean include, Environment env) throws TemplateModelException {
varsPutAll(hashModel, inExKeys, include, new CurrentFtlVarHandler(env), env);
}
public static void varsPutAll(TemplateHashModelEx hashModel, Environment env) throws TemplateModelException {
varsPutAll(hashModel, null, null, new CurrentFtlVarHandler(env), env);
}
/**
* Puts all values in hash into FTL locals (#local).
* <p>
* @see #copyMapToSimple
*/
public static void localsPutAll(TemplateHashModel hashModel, Set<String> inExKeys, Boolean include, Environment env) throws TemplateModelException {
varsPutAll(hashModel, inExKeys, include, new LocalFtlVarHandler(env), env);
}
public static void localsPutAll(TemplateHashModelEx hashModel, Environment env) throws TemplateModelException {
varsPutAll(hashModel, null, null, new LocalFtlVarHandler(env), env);
}
/**
* Returns the given model as string, bypassing auto-escaping done by EscapingModels.
* <p>
* WARN (TODO?: REVIEW?): this can crash when model is CollectionModel or MapModel, childs of TemplateScalarModel.
* we let it crash because non-strict typing may be dangerous and hide errors...
*
* @see EscapingModel
*/
public static String getAsStringNonEscaping(TemplateScalarModel model) throws TemplateModelException {
if (model instanceof EscapingModel) {
return (String) ((EscapingModel) model).getWrappedObject();
} else {
return model.getAsString();
}
}
/**
* Returns the given model as string, optionally bypassing auto-escaping done by EscapingModels.
*
* @see EscapingModel
*/
public static String getAsString(TemplateScalarModel model, boolean nonEscaping) throws TemplateModelException {
if (nonEscaping && (model instanceof EscapingModel)) {
return (String) ((EscapingModel) model).getWrappedObject();
} else {
return model.getAsString();
}
}
/**
* Returns the given model as string, optionally bypassing auto-escaping done by EscapingModels;
* if not a string, calles getAsString on it.
* <p>
* NOTE: this behaves similar to {@link #toRawString}, but returns a String.
* They are almost the same.
*
* @see EscapingModel
*/
public static String getAsOrToString(TemplateScalarModel model, boolean nonEscaping) throws TemplateModelException {
if (nonEscaping && (model instanceof EscapingModel)) {
Object value = ((EscapingModel) model).getWrappedObject();
if (value instanceof String || value == null) {
return (String) value;
} else {
return model.getAsString();
}
} else {
return model.getAsString();
}
}
/**
* Standard/"dumb" (re-)wrapping method. will deep-unwrap the value if necessary (if TemplateModel).
* <p>
* Failure to unwrap TemplateModel first will throw exception.
*/
public static TemplateModel wrapObjectStd(Object value, String wrapper, Environment env) throws TemplateModelException {
return ObjectWrapperUtil.getObjectWrapperByName(wrapper, env).wrap(unwrap(value));
}
public static TemplateModel wrapObjectStd(Object value, String wrapper) throws TemplateModelException {
return wrapObjectStd(value, wrapper, FreeMarkerWorker.getCurrentEnvironment());
}
/**
* Full-featured rewrapObject implementation. Rewraps objects with different wrappers and options.
* <p>
* TODO: 2016-10-20: this is currently very limited to the cases which we currently have in scipio,
* but other cases may pop up anytime.
* The non-deep is currently mainly handled by toSimpleMap in an imperfect way.
*/
public static Object rewrapObject(TemplateModel model, WrappingOptions opts, Environment env) throws TemplateModelException {
if (opts.rewrapMode.deep) {
if (opts.rewrapMode.always) {
// simplest case (default), just fully unwrap and rewrap
Object unwrapped = LangFtlUtil.unwrapAlways(model);
return opts.targetWrapper.wrap(unwrapped);
} else {
// TODO
throw new UnsupportedOperationException();
}
} else {
if (opts.rewrapMode.always) {
// TODO
throw new UnsupportedOperationException();
} else {
// TODO
throw new UnsupportedOperationException();
}
}
}
/* obsoleted, remove later when rewrapObject is fully implemented
public static Object rewrapMap(TemplateHashModel model, WrappingOptions opts, Environment env) throws TemplateModelException {
RewrapMode mode = opts.rewrapMode;
if (mode.always) {
return alwaysRewrapMap(model, opts, env);
} else {
if (mode.deep) {
// WARN: Here we make one VERY delicate optimization:
// if the map is a simple hash, we do nothing to it.
// In theory this is WRONG but it works in most practical cases.
// Caller can force if it doesn't work right in his case.
if (model instanceof SimpleHash) {
return model;
}
// FIXME: Here we are forced to rewrap in most cases because Freemarker interface
// does not allow inspecting which object wrapper an object is using!
return alwaysRewrapMap(model, mode, env, curObjectWrapper);
} else {
// Shallow re-wrap to simple, non-(necessarily-)raw map.
// This is the rare case we can currently optimize...
return toSimpleMap(model, mode.copy, curObjectWrapper);
}
}
}
public static Object alwaysRewrapMap(TemplateHashModel model, WrappingOptions opts, Environment env) throws TemplateModelException {
Map<?, ?> unwrapped = (Map<?, ?>) LangFtlUtil.unwrapAlways(model);
return
if (opts.rewrapMode.raw) {
if (opts.rewrapMode.deep) {
ObjectWrapper modelWrapper = mode.copy ?
LangFtlUtil.getSimpleTypeCopyingNonEscapingObjectWrapper(curObjectWrapper) : LangFtlUtil.getSimpleTypeNonEscapingObjectWrapper(curObjectWrapper);
return modelWrapper.wrap(unwrapped);
} else {
// Can't do raw without doing deep
throw new TemplateModelException("Scipio: rewrapMap mode unsupported");
}
} else {
if (opts.rewrapMode.deep) {
// TODO: This mode is desirable but it requires implementing a new DefaultObjectWrapper
// that would preserve the wrapping mode curObjectWrapper is doing.
// as-is, to do deep, you must also do raw.
throw new TemplateModelException("Scipio: rewrapMap mode not yet implemented");
} else {
return opts.targetWrapper.wrap(unwrapped);
}
}
}
*/
public static boolean isNullOrEmptyString(TemplateModel model) throws TemplateModelException {
// this doesn't work out: TemplateScalarModel.EMPTY_STRING.equals(model)
return (model == null || (model instanceof TemplateScalarModel && ((TemplateScalarModel) model).getAsString().isEmpty()));
}
public static Locale getLocale(TemplateModel model) throws TemplateModelException {
if (isNullOrEmptyString(model)) {
return null;
}
if (!(model instanceof WrapperTemplateModel)) {
throw new TemplateModelException("Invalid locale object (not WrapperTemplateModel)");
}
return (Locale) ((WrapperTemplateModel) model).getWrappedObject();
}
public static TimeZone getTimeZone(TemplateModel model) throws TemplateModelException {
if (isNullOrEmptyString(model)) {
return null;
}
if (!(model instanceof WrapperTemplateModel)) {
throw new TemplateModelException("Invalid locale object (not WrapperTemplateModel)");
}
return (TimeZone) ((WrapperTemplateModel) model).getWrappedObject();
}
public static Template makeFtlCodeTemplate(String ftlCode) throws TemplateModelException {
Reader templateReader = new StringReader(ftlCode);
try {
return new Template(new UID().toString(), templateReader, FreeMarkerWorker.getDefaultOfbizConfig());
} catch (IOException e) {
throw new TemplateModelException(e);
} finally {
try {
templateReader.close();
} catch (IOException e) {
Debug.logError(e, module); // don't propagate
}
}
}
public static void execFtlCode(Template ftlCode, Environment env) throws TemplateModelException {
try {
FreeMarkerWorker.includeTemplate(ftlCode, env);
} catch (TemplateException e) {
throw new TemplateModelException(e);
} catch (IOException e) {
throw new TemplateModelException(e);
}
}
/**
* WARN: extremely slow, should be avoided! decompose into makeTemplate + executeFtlCode and cache the template instead.
*/
public static void execFtlCode(String ftlCode, Environment env) throws TemplateModelException {
execFtlCode(makeFtlCodeTemplate(ftlCode), env);
}
/**
* Executes an arbitrary FTL built-in.
*/
public static TemplateModel execBuiltIn(String builtInName, TemplateModel value, TemplateModel[] builtInArgs, Environment env) throws TemplateModelException {
final int argCount = (builtInArgs != null) ? builtInArgs.length : 0;
return execBuiltIn(getBuiltInCall(builtInName, argCount, env), value, builtInArgs, env);
}
/**
* Executes an arbitrary FTL built-in.
*/
public static TemplateModel execBuiltIn(String builtInName, TemplateModel value, Environment env) throws TemplateModelException {
return execBuiltIn(builtInName, value, null, env);
}
/**
* Gets an arbitrary FTL built-in call - non-abstracted version (for optimization only!).
*/
public static Template getBuiltInCall(String builtInName, int argCount, Environment env) throws TemplateModelException {
final String cacheKey = builtInName + ":" + argCount;
Template builtInCall = builtInCalls.get(cacheKey);
if (builtInCall == null) {
// NOTE: there's no _real_ need to synchronize on this. if two templates are built for one builtin its ok, temporary only.
if (argCount > 0) {
String argVarsStr = "";
for(int i=0; i < argCount; i++) {
argVarsStr += ",_scpEbiArg"+i;
}
builtInCall = makeFtlCodeTemplate("<#assign _scpEbiRes = _scpEbiVal?" + builtInName + "(" + argVarsStr.substring(1) + ")>");
} else {
builtInCall = makeFtlCodeTemplate("<#assign _scpEbiRes = _scpEbiVal?" + builtInName + ">");
}
builtInCalls.put(cacheKey, builtInCall);
}
return builtInCall;
}
/**
* Executes an arbitrary FTL built-in - non-abstracted version (for optimization only!).
*/
public static TemplateModel execBuiltIn(Template builtInCall, TemplateModel value, TemplateModel[] builtInArgs, Environment env) throws TemplateModelException {
final int argCount = (builtInArgs != null) ? builtInArgs.length : 0;
env.setVariable("_scpEbiVal", value);
for(int i=0; i < argCount; i++) {
env.setVariable("_scpEbiArg"+i, builtInArgs[i]);
}
execFtlCode(builtInCall, env);
return env.getVariable("_scpEbiRes");
}
public static TemplateScalarModel execStringBuiltIn(TemplateModel value, Environment env) throws TemplateModelException {
if (stringBuiltInCall == null) {
// NOTE: no real need for synchronize here
stringBuiltInCall = getBuiltInCall("string", 0, env);
}
return (TemplateScalarModel) execBuiltIn(stringBuiltInCall, value, null, env);
}
/**
* Executes an arbitrary FTL function.
*/
public static TemplateModel execFunction(String functionName, TemplateModel[] args, Environment env) throws TemplateModelException {
final int argCount = (args != null) ? args.length : 0;
return execFunction(getFunctionCall(functionName, argCount, env), args, env);
}
/**
* Executes an arbitrary FTL function.
*/
public static TemplateModel execFunction(String functionName, Environment env) throws TemplateModelException {
return execFunction(getFunctionCall(functionName, 0, env), null, env);
}
/**
* Gets an arbitrary FTL function call - non-abstracted version (for optimization only!).
*/
public static Template getFunctionCall(String functionName, int argCount, Environment env) throws TemplateModelException {
final String cacheKey = functionName + ":" + argCount;
Template functionCall = functionCalls.get(cacheKey);
if (functionCall == null) {
String argVarsStr = "";
for(int i=0; i < argCount; i++) {
argVarsStr += ",_scpEfnArg"+i;
}
if (argCount > 0) {
argVarsStr = argVarsStr.substring(1);
}
functionCall = makeFtlCodeTemplate("<#assign _scpEfnRes = " + functionName + "(" + argVarsStr + ")>");
functionCalls.put(cacheKey, functionCall);
}
return functionCall;
}
/**
* Executes an arbitrary FTL function - non-abstracted version (for optimization only!).
*/
public static TemplateModel execFunction(Template functionCall, TemplateModel[] args, Environment env) throws TemplateModelException {
final int argCount = (args != null) ? args.length : 0;
for(int i=0; i < argCount; i++) {
env.setVariable("_scpEfnArg"+i, args[i]);
}
execFtlCode(functionCall, env);
return env.getVariable("_scpEfnRes");
}
/**
* Executes an arbitrary FTL function - non-abstracted version (for optimization only!).
*/
public static TemplateModel execFunction(Template functionCall, Environment env) throws TemplateModelException {
return execFunction(functionCall, null, env);
}
/**
* Executes an arbitrary FTL macro.
*/
public static void execMacro(String macroName, Map<String, TemplateModel> args, Environment env) throws TemplateModelException {
execMacro(getMacroCall(macroName, (args != null ? args.keySet() : null), env), args, env);
}
/**
* Executes an arbitrary FTL macro.
*/
public static void execMacro(String macroName, Environment env) throws TemplateModelException {
execMacro(getMacroCall(macroName, null, env), null, env);
}
/**
* Gets an arbitrary FTL macro call - non-abstracted version (for optimization only!).
*/
public static Template getMacroCall(String macroName, Collection<String> argNames, Environment env) throws TemplateModelException {
final String cacheKey = macroName + (argNames != null ? ":" + StringUtils.join(argNames, ":") : "");
Template macroCall = macroCalls.get(cacheKey);
if (macroCall == null) {
String argVarsStr = "";
if (argNames != null) {
for(String argName : argNames) {
argVarsStr += " " + argName + "=_scpEfnArg_"+argName;
}
}
macroCall = makeFtlCodeTemplate("<@" + macroName + argVarsStr + "/>");
macroCalls.put(cacheKey, macroCall);
}
return macroCall;
}
/**
* Executes an arbitrary FTL macro - non-abstracted version (for optimization only!).
*/
public static void execMacro(Template macroCall, Map<String, TemplateModel> args, Environment env) throws TemplateModelException {
if (args != null) {
for(Map.Entry<String, TemplateModel> entry : args.entrySet()) {
env.setVariable("_scpEfnArg_"+entry.getKey(), entry.getValue());
}
}
execFtlCode(macroCall, env);
}
/**
* Executes an arbitrary FTL macro - non-abstracted version (for optimization only!).
*/
public static void execMacro(Template macroCall, Environment env) throws TemplateModelException {
execMacro(macroCall, null, env);
}
/**
* Gets a var from main namespace with fallback on globals/data-model, or null if doesn't exit or null.
* <p>
* Avoids local variables and emulates a simple Freemarker var read in the main namespace.
* <p>
* Similar to {@link freemarker.core.Environment#getVariable(String)} but skips local
* variables and always main namespace instead of current namespace.
* <p>
* NOTE: This probably makes the most sense to call from transforms as a means to read
* "context/global" variables (using term loosely, while providing possibility for
* templates to override using both #assign and #global directives.
*/
public static TemplateModel getMainNsOrGlobalVar(String name, Environment env) throws TemplateModelException {
TemplateModel result = env.getMainNamespace().get(name);
if (result == null) {
result = env.getGlobalVariable(name);
}
return result;
}
/**
* Gets a var from current namespace with fallback on globals/data-model, or null if doesn't exit or null.
* <p>
* Avoids local variables and emulates a simple Freemarker var read in the current namespace.
* <p>
* Similar to {@link freemarker.core.Environment#getVariable(String)} but skips local
* variables.
*/
public static TemplateModel getCurrentNsOrGlobalVar(String name, Environment env) throws TemplateModelException {
TemplateModel result = env.getCurrentNamespace().get(name);
if (result == null) {
result = env.getGlobalVariable(name);
}
return result;
}
public static TemplateBooleanModel toBooleanModel(boolean value, Environment env) throws TemplateModelException {
return value ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
/**
* Performs the logical raw string operation on a single value.
*/
public static TemplateScalarModel toRawString(TemplateModel value, Environment env) throws TemplateModelException {
if (value instanceof TemplateScalarModel) {
TemplateScalarModel strModel = (TemplateScalarModel) value;
String str = getAsStringNonEscaping(strModel);
return new SimpleScalar(str); // Emulates Freemarker ?string built-in
} else {
return execStringBuiltIn(value, env);
}
}
/**
* Performs the logical {@link #toRawString(TemplateModel, Environment)} operation on a single value, but returns as String type instead
* of template model.
*/
public static String toRawJavaString(TemplateModel value, Environment env) throws TemplateModelException {
if (!(value instanceof TemplateScalarModel)) {
value = execStringBuiltIn(value, env);
}
return getAsStringNonEscaping((TemplateScalarModel) value);
}
/**
* Performs the logical raw string operation on multiple values, concatenating the result.
*/
public static TemplateScalarModel toRawString(Collection<TemplateModel> values, Environment env) throws TemplateModelException {
StringBuilder sb = new StringBuilder();
for(TemplateModel value: values) {
if (value instanceof TemplateScalarModel) {
sb.append(getAsStringNonEscaping((TemplateScalarModel) value));
} else {
sb.append(execStringBuiltIn(value, env).getAsString());
}
}
return new SimpleScalar(sb.toString());
}
/**
* Adapts a hash model to a Map adapter.
* <p>
* NOTE: At current time (2019-01-28), this is best-effort and does not guarantee
* copies won't be made - see implementation (incomplete). In other words,
* avoid using anything but the Map.get method.
*/
public static <V extends TemplateModel> Map<String, V> adaptAsMap(TemplateHashModelEx hash) {
return new TemplateModelContainer.TemplateModelMap<>(hash);
}
/**
* Gets the wrapped hash's map OR adapts a hash model to a Map adapter if not a wrapper (SimpleHash).
* <p>
* NOTE: At current time (2019-01-28), this is best-effort and does not guarantee
* copies won't be made - see implementation (incomplete). In other words,
* avoid using anything but the Map.get method.
*/
@SuppressWarnings("unchecked")
public static <V extends TemplateModel> Map<String, V> getWrappedOrAdaptAsMap(TemplateHashModelEx hash) {
if (hash instanceof AdapterTemplateModel) {
return (Map<String, V>) ((AdapterTemplateModel) hash).getAdaptedObject(Map.class);
}
if (hash instanceof WrapperTemplateModel) {
return (Map<String, V>) ((WrapperTemplateModel) hash).getWrappedObject();
}
return adaptAsMap(hash);
}
}