/** * Creates a child context. * * @param parent The parent context. Required. * @param model The target value. Resolved as '.' or 'this' inside templates. Required. * @return A child context. */ private static Context child(final Context parent, final Object model) { notNull(parent, "A parent context is required."); Context child = new Context(model); child.extendedContext = new Context(new HashMap<String, Object>()); child.parent = parent; child.data = parent.data; return child; }
/** * Creates a root context. * * @param model The target value. Resolved as '.' or 'this' inside templates. Required. * @return A root context. */ private static Context root(final Object model) { Context root = new Context(model); root.extendedContext = new Context(new HashMap<String, Object>()); root.parent = null; root.data = new HashMap<String, Object>(); root.data.put(PARTIALS, new HashMap<String, Template>()); root.data.put(INVOCATION_STACK, new LinkedList<TemplateSource>()); return root; }
/** * Lookup the given key inside the context stack. * * <ul> * <li>Objects and hashes should be pushed onto the context stack. * <li>All elements on the context stack should be accessible. * <li>Multiple sections per template should be permitted. * <li>Failed context lookups should be considered falsey. * <li>Dotted names should be valid for Section tags. * <li>Dotted names that cannot be resolved should be considered falsey. * <li>Dotted Names - Context Precedence: Dotted names should be resolved against former * resolutions. * </ul> * * @param key The object key. * @return The value associated to the given key or <code>null</code> if no value is found. */ public Object get(final String key) { // '.' or 'this' if (MUSTACHE_THIS.equals(key) || THIS.equals(key)) { return model; } // '..' if (key.equals(PARENT)) { return parent == null ? null : parent.model; } // '../' if (key.startsWith(PARENT_ATTR)) { return parent == null ? null : parent.get(key.substring(PARENT_ATTR.length())); } String[] path = toPath(key); Object value = get(path); if (value == null) { // No luck, check the extended context. value = get(extendedContext, key); // No luck, check the data context. if (value == null && data != null) { String dataKey = key.charAt(0) == '@' ? key.substring(1) : key; // simple data keys will be resolved immediately, complex keys need to go down and using a // new context. value = data.get(dataKey); if (value == null && path.length > 1) { // for complex keys, a new data context need to be created per invocation, // bc data might changes per execution. Context dataContext = Context.newBuilder(data).resolver(MapValueResolver.INSTANCE).build(); // don't extend the lookup further. dataContext.data = null; value = dataContext.get(dataKey); // destroy it! dataContext.destroy(); } } // No luck, but before checking at the parent scope we need to check for // the 'this' qualifier. If present, no look up will be done. if (value == null && !path[0].equals(THIS)) { value = get(parent, key); } } return value == NULL ? null : value; }