All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.thymeleaf.context.WebVariablesMap Maven / Gradle / Ivy

/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2014, The THYMELEAF team (http://www.thymeleaf.org)
 * 
 *   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 org.thymeleaf.context;

import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.thymeleaf.inline.IInliner;
import org.thymeleaf.inline.NoOpInliner;
import org.thymeleaf.util.Validate;

/**
 *
 * @author Daniel Fernández
 *
 * @since 3.0.0
 *
 */
public final class WebVariablesMap
        implements IWebVariablesMap, ILocalVariableAwareVariablesMap {

    /*
     * ---------------------------------------------------------------------------
     * THIS MAP FORWARDS ALL OPERATIONS TO THE UNDERLYING REQUEST, EXCEPT
     * FOR THE param (request parameters), session (session attributes) AND
     * application (servlet context attributes) VARIABLES.
     *
     * NOTE that, even if attributes are leveled so that above level 0 they are
     * considered local and thus disappear after lowering the level, attributes
     * directly set on the request object are considered global and therefore
     * valid even when the level decreased (though they can be overridden). This
     * is so for better simulating the effect of directly working against the
     * request object, and for better integration with JSP.
     * ---------------------------------------------------------------------------
     */

    private static final String PARAM_VARIABLE_NAME = "param";
    private static final String SESSION_VARIABLE_NAME = "session";
    private static final String APPLICATION_VARIABLE_NAME = "application";

    private final Locale locale;

    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final HttpSession session;
    private final ServletContext servletContext;

    private final RequestAttributesVariablesMap requestAttributesVariablesMap;
    private final IVariablesMap requestParametersVariablesMap;
    private final IVariablesMap sessionAttributesVariablesMap;
    private final IVariablesMap applicationAttributesVariablesMap;




    /*
     * There is no reason for a user to directly create an instance of this - they should create Context or
     * WebContext instances instead.
     */
    WebVariablesMap(
            final HttpServletRequest request, final HttpServletResponse response,
            final ServletContext servletContext,
            final Locale locale, final Map variables) {

        super();

        Validate.notNull(request, "Request cannot be null in web variables map");
        Validate.notNull(response, "Response cannot be null in web variables map");
        Validate.notNull(servletContext, "Servlet Context cannot be null in web variables map");
        Validate.notNull(locale, "Locale cannot be null in web variables map");

        this.locale = locale;

        this.request = request;
        this.response = response;
        this.session = request.getSession(false);
        this.servletContext = servletContext;

        this.requestAttributesVariablesMap = new RequestAttributesVariablesMap(this.request, this.locale, variables);
        this.requestParametersVariablesMap = new RequestParametersVariablesMap(this.request, this.locale);
        this.applicationAttributesVariablesMap = new ServletContextAttributesVariablesMap(this.servletContext, this.locale);
        this.sessionAttributesVariablesMap = (this.session == null ? null : new SessionAttributesVariablesMap(this.session, this.locale));

    }


    public Locale getLocale() {
        return this.locale;
    }




    public HttpServletRequest getRequest() {
        return this.request;
    }


    public HttpServletResponse getResponse() {
        return this.response;
    }


    public HttpSession getSession() {
        return this.session;
    }


    public ServletContext getServletContext() {
        return this.servletContext;
    }


    public boolean containsVariable(final String name) {
        if (SESSION_VARIABLE_NAME.equals(name)) {
            return this.sessionAttributesVariablesMap != null;
        }
        if (PARAM_VARIABLE_NAME.equals(name)) {
            return true;
        }
        if (APPLICATION_VARIABLE_NAME.equals(name)) {
            return true;
        }
        return this.requestAttributesVariablesMap.containsVariable(name);
    }


    public Object getVariable(final String key) {
        if (SESSION_VARIABLE_NAME.equals(key)) {
            return this.sessionAttributesVariablesMap;
        }
        if (PARAM_VARIABLE_NAME.equals(key)) {
            return this.requestParametersVariablesMap;
        }
        if (APPLICATION_VARIABLE_NAME.equals(key)) {
            return this.applicationAttributesVariablesMap;
        }
        return this.requestAttributesVariablesMap.getVariable(key);
    }


    public Set getVariableNames() {
        // Note this set will NOT include 'param', 'session' or 'application', as they are considered special
        // ways to access attributes/parameters in these Servlet API structures
        return this.requestAttributesVariablesMap.getVariableNames();
    }


    public void put(final String key, final Object value) {
        if (SESSION_VARIABLE_NAME.equals(key) ||
                PARAM_VARIABLE_NAME.equals(key) ||
                APPLICATION_VARIABLE_NAME.equals(key)) {
            throw new IllegalArgumentException(
                    "Cannot set variable called '" + key + "' into web variables map: such name is a reserved word");
        }
        this.requestAttributesVariablesMap.put(key, value);
    }


    public void putAll(final Map map) {
        // We will not delegate to requestAttributesVariablesMap because we need to perform the reserved-name check
        // for each variable being set.
        if (map == null) {
            return;
        }
        for (final Map.Entry entry : map.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }


    public void remove(final String key) {
        if (SESSION_VARIABLE_NAME.equals(key) ||
                PARAM_VARIABLE_NAME.equals(key) ||
                APPLICATION_VARIABLE_NAME.equals(key)) {
            throw new IllegalArgumentException(
                    "Cannot remove variable called '" + key + "' in web variables map: such name is a reserved word");
        }
        this.requestAttributesVariablesMap.remove(key);
    }


    public boolean isVariableLocal(final String name) {
        return this.requestAttributesVariablesMap.isVariableLocal(name);
    }


    public boolean hasSelectionTarget() {
        return this.requestAttributesVariablesMap.hasSelectionTarget();
    }


    public Object getSelectionTarget() {
        return this.requestAttributesVariablesMap.getSelectionTarget();
    }


    public void setSelectionTarget(final Object selectionTarget) {
        this.requestAttributesVariablesMap.setSelectionTarget(selectionTarget);
    }




    public IInliner getInliner() {
        return this.requestAttributesVariablesMap.getInliner();
    }

    public void setInliner(final IInliner inliner) {
        this.requestAttributesVariablesMap.setInliner(inliner);
    }




    public int level() {
        return this.requestAttributesVariablesMap.level();
    }


    public void increaseLevel() {
        this.requestAttributesVariablesMap.increaseLevel();
    }


    public void decreaseLevel() {
        this.requestAttributesVariablesMap.decreaseLevel();
    }




    public String getStringRepresentationByLevel() {
        // Request parameters, session and servlet context can be safely ignored here
        return this.requestAttributesVariablesMap.getStringRepresentationByLevel();
    }




    @Override
    public String toString() {
        // Request parameters, session and servlet context can be safely ignored here
        return this.requestAttributesVariablesMap.toString();
    }





    private static final class SessionAttributesVariablesMap implements IVariablesMap {

        private final HttpSession session;
        private final Locale locale;

        SessionAttributesVariablesMap(final HttpSession session, final Locale locale) {
            super();
            this.session = session;
            this.locale = locale;
        }

        public Locale getLocale() {
            return this.locale;
        }

        public Object getVariable(final String key) {
            return this.session.getAttribute(key);
        }

        public boolean containsVariable(final String name) {
            return existsInEnumeration(this.session.getAttributeNames(), name);
        }


        public Set getVariableNames() {

            final Set variableNames = new HashSet();
            final Enumeration attributeNamesEnum = this.session.getAttributeNames();
            while (attributeNamesEnum.hasMoreElements()) {
                variableNames.add(attributeNamesEnum.nextElement());
            }
            return variableNames;

        }

        public boolean hasSelectionTarget() {
            return false;
        }

        public Object getSelectionTarget() {
            return null;
        }

        public IInliner getInliner() {
            return null;
        }

    }




    private static final class ServletContextAttributesVariablesMap implements IVariablesMap {

        private final ServletContext servletContext;
        private final Locale locale;

        ServletContextAttributesVariablesMap(final ServletContext servletContext, final Locale locale) {
            super();
            this.servletContext = servletContext;
            this.locale = locale;
        }

        public Locale getLocale() {
            return this.locale;
        }

        public Object getVariable(final String key) {
            return this.servletContext.getAttribute(key);
        }

        public boolean containsVariable(final String name) {
            // For most implementations of ServletContext, trying to get a value instead of iterating the
            // names Enumeration seems faster as a way to know if something exists (in the cases when we are checking
            // for existing keys a good % of the total times).
            if (this.servletContext.getAttribute(name) != null) {
                return true;
            }
            return existsInEnumeration(this.servletContext.getAttributeNames(), name);
        }


        public Set getVariableNames() {

            final Set variableNames = new HashSet();
            final Enumeration attributeNamesEnum = this.servletContext.getAttributeNames();
            while (attributeNamesEnum.hasMoreElements()) {
                variableNames.add(attributeNamesEnum.nextElement());
            }
            return variableNames;

        }

        public boolean hasSelectionTarget() {
            return false;
        }

        public Object getSelectionTarget() {
            return null;
        }

        public IInliner getInliner() {
            return null;
        }

    }




    private static final class RequestParametersVariablesMap implements IVariablesMap {

        private final HttpServletRequest request;
        private final Locale locale;

        RequestParametersVariablesMap(final HttpServletRequest request, final Locale locale) {
            super();
            this.request = request;
            this.locale = locale;
        }

        public Locale getLocale() {
            return this.locale;
        }

        public Object getVariable(final String key) {
            // We will always be returning the String[] version of the parameters
            return this.request.getParameterValues(key);
        }

        public boolean containsVariable(final String name) {
            // For most implementations of HttpServletRequest, trying to get a value instead of iterating the
            // names Enumeration or the ParameterMap keys (which might be locked but will be created each time if not)
            // seems faster as a way to know if something exists (in the cases when we are checking
            // for existing keys a good % of the total times).
            if (this.request.getParameterValues(name) != null) {
                return true;
            }
            // We can still be fast if this parameter map has been locked so that we don't receive a new instance
            // each time we call it (see e.g. org.apache.catalina.connector.Request)
            return this.request.getParameterMap().containsKey(name);
        }

        public Set getVariableNames() {
            // If the parameter map is locked (see e.g. org.apache.catalina.connector.Request) this can be quite fast
            return this.request.getParameterMap().keySet();
        }

        public boolean hasSelectionTarget() {
            return false;
        }

        public Object getSelectionTarget() {
            return null;
        }

        public IInliner getInliner() {
            return null;
        }

    }




    private static final class RequestAttributesVariablesMap implements IVariablesMap, ILocalVariableAwareVariablesMap {

        private static final int DEFAULT_LEVELS_SIZE = 3;
        private static final int DEFAULT_LEVELARRAYS_SIZE = 5;

        private final Locale locale;

        private final HttpServletRequest request;

        private int level = 0;
        private int index = 0;
        private int[] levels;

        private String[][] names;
        private Object[][] oldValues;
        private Object[][] newValues;
        private int[] levelSizes;
        private SelectionTarget[] selectionTargets;
        private IInliner[] inliners;

        private static final Object NON_EXISTING = new Object() {
            @Override
            public String toString() {
                return "(*removed*)";
            }
        };


        RequestAttributesVariablesMap(final HttpServletRequest request, final Locale locale, final Map variables) {

            super();

            this.request = request;
            this.locale = locale;

            this.levels = new int[DEFAULT_LEVELS_SIZE];
            this.names = new String[DEFAULT_LEVELS_SIZE][];
            this.oldValues = new Object[DEFAULT_LEVELS_SIZE][];
            this.newValues = new Object[DEFAULT_LEVELS_SIZE][];
            this.levelSizes = new int[DEFAULT_LEVELS_SIZE];
            this.selectionTargets = new SelectionTarget[DEFAULT_LEVELS_SIZE];
            this.inliners = new IInliner[DEFAULT_LEVELS_SIZE];
            Arrays.fill(this.levels, Integer.MAX_VALUE);
            Arrays.fill(this.names, null);
            Arrays.fill(this.oldValues, null);
            Arrays.fill(this.newValues, null);
            Arrays.fill(this.levelSizes, 0);
            Arrays.fill(this.selectionTargets, null);
            Arrays.fill(this.inliners, null);
            this.levels[0] = 0;

            if (variables != null) {
                putAll(variables);
            }

        }

        public Locale getLocale() {
            return this.locale;
        }

        public boolean containsVariable(final String name) {

            // --------------------------
            // Note this method relies on HttpServletRequest#getAttributeNames(), which is an extremely slow and
            // inefficient method in implementations like Apache Tomcat's. So the uses of this method should be
            // very controlled and reduced to the minimum. Specifically, any call that executes e.g. for every
            // expression evaluation should be disallowed. Only sporadic uses like e.g. from the put() method should
            // be done.
            // Note also it would not be a good idea to cache the attribute names coming from the request if we
            // want to keep complete independence of the HttpServletRequest object, so that it can be modified
            // from the outside (e.g. from other libraries like Tiles) with Thymeleaf perfectly integrating with
            // those modifications.
            // --------------------------


            // For most implementations of HttpServletRequest, trying to get a value instead of iterating the
            // names Enumeration seems faster as a way to know if something exists (in the cases when we are checking
            // for existing keys a good % of the total times).
            if (this.request.getAttribute(name) != null) {
                return true;
            }

            return existsInEnumeration(this.request.getAttributeNames(), name);

        }


        public Object getVariable(final String key) {
            return this.request.getAttribute(key);
        }


        public Set getVariableNames() {

            final Set variableNames = new HashSet(10);
            final Enumeration attributeNamesEnum = this.request.getAttributeNames();
            while (attributeNamesEnum.hasMoreElements()) {
                variableNames.add(attributeNamesEnum.nextElement());
            }
            return variableNames;

        }


        private int searchNameInIndex(final String name, final int idx) {
            int n = this.levelSizes[idx];
            if (name == null) {
                while (n-- != 0) {
                    if (this.names[idx][n] == null) {
                        return n;
                    }
                }
                return -1;
            }
            while (n-- != 0) {
                if (name.equals(this.names[idx][n])) {
                    return n;
                }
            }
            return -1;
        }




        public void put(final String key, final Object value) {
            put(key, value, null);
        }

        private void put(final String key, final Object value, final Set alreadyContainedNames) {

            ensureLevelInitialized();

            if (this.level > 0) {
                // We will only take care of new/old values if we are not on level 0

                int levelIndex = searchNameInIndex(key,this.index);
                if (levelIndex >= 0) {

                    // There already is a registered movement for this key - we should modify it instead of creating a new one
                    this.newValues[this.index][levelIndex] = value;

                } else {

                    if (this.names[this.index].length == this.levelSizes[this.index]) {
                        // We need to grow the arrays for this level

                        final String[] newNames = new String[this.names[this.index].length + DEFAULT_LEVELARRAYS_SIZE];
                        final Object[] newNewValues = new Object[this.newValues[this.index].length + DEFAULT_LEVELARRAYS_SIZE];
                        final Object[] newOldValues = new Object[this.oldValues[this.index].length + DEFAULT_LEVELARRAYS_SIZE];
                        Arrays.fill(newNames, null);
                        Arrays.fill(newNewValues, null);
                        Arrays.fill(newOldValues, null);
                        System.arraycopy(this.names[this.index], 0, newNames, 0, this.names[this.index].length);
                        System.arraycopy(this.newValues[this.index], 0, newNewValues, 0, this.newValues[this.index].length);
                        System.arraycopy(this.oldValues[this.index], 0, newOldValues, 0, this.oldValues[this.index].length);
                        this.names[this.index] = newNames;
                        this.newValues[this.index] = newNewValues;
                        this.oldValues[this.index] = newOldValues;

                    }

                    levelIndex = this.levelSizes[this.index]; // We will add at the end

                    this.names[this.index][levelIndex] = key;
                    // By checking the 'alreadyContainedNames' argument, we try to save calls to the very slow
                    // HttpServletRequest#getAttributeNames() in the case we are calling this from putAll(...)
                    if ((alreadyContainedNames == null && containsVariable(key)) ||
                            (alreadyContainedNames != null && alreadyContainedNames.contains(key))) {
                        this.oldValues[this.index][levelIndex] = this.request.getAttribute(key);
                    } else {
                        this.oldValues[this.index][levelIndex] = NON_EXISTING;
                    }
                    this.newValues[this.index][levelIndex] = value;

                    this.levelSizes[this.index]++;

                }

            }

            if (value == NON_EXISTING) {
                this.request.removeAttribute(key);
            } else {
                this.request.setAttribute(key, value);
            }

        }


        public void putAll(final Map map) {
            if (map == null || map.isEmpty()) {
                return;
            }
            // By precomputing here a 'alreadyContainedNames' set, we try to save calls to the very slow
            // HttpServletRequest#getAttributeNames() in our calls to put(), which has to determine whether a
            // variable already exists or not (and would otherwise call "containsVariable()")
            // Also note this is only required when level > 0
            final Set alreadyContainedNames = (this.level > 0? getVariableNames() : null);
            for (final Map.Entry entry : map.entrySet()) {
                put(entry.getKey(), entry.getValue(), alreadyContainedNames);
            }
        }


        public void remove(final String key) {
            if (containsVariable(key)) {
                put(key, NON_EXISTING);
            }
        }




        public boolean isVariableLocal(final String name) {

            if (this.level == 0) {
                // We are at level 0, so we cannot have local variables at all
                return false;
            }

            int n = this.index + 1;
            while (n-- > 1) { // variables at n == 0 are not local!
                final int idx = searchNameInIndex(name, n);
                if (idx >= 0) {
                    return this.newValues[n][idx] != NON_EXISTING;
                }
            }

            return false;

        }




        public boolean hasSelectionTarget() {
            int n = this.index + 1;
            while (n-- != 0) {
                if (this.selectionTargets[n] != null) {
                    return true;
                }
            }
            return false;
        }


        public Object getSelectionTarget() {
            int n = this.index + 1;
            while (n-- != 0) {
                if (this.selectionTargets[n] != null) {
                    return this.selectionTargets[n].selectionTarget;
                }
            }
            return null;
        }


        public void setSelectionTarget(final Object selectionTarget) {
            ensureLevelInitialized();
            this.selectionTargets[this.index] = new SelectionTarget(selectionTarget);
        }




        public IInliner getInliner() {
            int n = this.index + 1;
            while (n-- != 0) {
                if (this.inliners[n] != null) {
                    if (this.inliners[n] == NoOpInliner.INSTANCE) {
                        return null;
                    }
                    return this.inliners[n];
                }
            }
            return null;
        }


        public void setInliner(final IInliner inliner) {
            ensureLevelInitialized();
            // We use NoOpInliner.INSTACE in order to signal when inlining has actually been disabled
            this.inliners[this.index] = (inliner == null? NoOpInliner.INSTANCE : inliner);
        }



        private void ensureLevelInitialized() {

            // First, check if the current index already signals the current level (in which case, everything is OK)
            if (this.levels[this.index] != this.level) {

                // The current level still had no index assigned -- we must do it, and maybe even grow structures

                this.index++; // This new index will be the one for our level

                if (this.levels.length == this.index) {
                    final int[] newLevels = new int[this.levels.length + DEFAULT_LEVELS_SIZE];
                    final String[][] newNames = new String[this.names.length + DEFAULT_LEVELS_SIZE][];
                    final Object[][] newNewValues = new Object[this.newValues.length + DEFAULT_LEVELS_SIZE][];
                    final Object[][] newOldValues = new Object[this.oldValues.length + DEFAULT_LEVELS_SIZE][];
                    final int[] newLevelSizes = new int[this.levelSizes.length + DEFAULT_LEVELS_SIZE];
                    final SelectionTarget[] newSelectionTargets = new SelectionTarget[this.selectionTargets.length + DEFAULT_LEVELS_SIZE];
                    final IInliner[] newInliners = new IInliner[this.inliners.length + DEFAULT_LEVELS_SIZE];
                    Arrays.fill(newLevels, Integer.MAX_VALUE);
                    Arrays.fill(newNames, null);
                    Arrays.fill(newNewValues, null);
                    Arrays.fill(newOldValues, null);
                    Arrays.fill(newLevelSizes, 0);
                    Arrays.fill(newSelectionTargets, null);
                    Arrays.fill(newInliners, null);
                    System.arraycopy(this.levels, 0, newLevels, 0, this.levels.length);
                    System.arraycopy(this.names, 0, newNames, 0, this.names.length);
                    System.arraycopy(this.newValues, 0, newNewValues, 0, this.newValues.length);
                    System.arraycopy(this.oldValues, 0, newOldValues, 0, this.oldValues.length);
                    System.arraycopy(this.levelSizes, 0, newLevelSizes, 0, this.levelSizes.length);
                    System.arraycopy(this.selectionTargets, 0, newSelectionTargets, 0, this.selectionTargets.length);
                    System.arraycopy(this.inliners, 0, newInliners, 0, this.inliners.length);
                    this.levels = newLevels;
                    this.names = newNames;
                    this.newValues = newNewValues;
                    this.oldValues = newOldValues;
                    this.levelSizes = newLevelSizes;
                    this.selectionTargets = newSelectionTargets;
                    this.inliners = newInliners;
                }

                this.levels[this.index] = this.level;

            }

            if (this.level > 0) {
                // We will only take care of new/old values if we are not on level 0

                if (this.names[this.index] == null) {
                    // the arrays for this level have still not ben created

                    this.names[this.index] = new String[DEFAULT_LEVELARRAYS_SIZE];
                    Arrays.fill(this.names[this.index], null);

                    this.newValues[this.index] = new Object[DEFAULT_LEVELARRAYS_SIZE];
                    Arrays.fill(this.newValues[this.index], null);

                    this.oldValues[this.index] = new Object[DEFAULT_LEVELARRAYS_SIZE];
                    Arrays.fill(this.oldValues[this.index], null);

                    this.levelSizes[this.index] = 0;

                }

            }

        }




        public int level() {
            return this.level;
        }


        public void increaseLevel() {
            this.level++;
        }


        public void decreaseLevel() {
            Validate.isTrue(this.level > 0, "Cannot decrease variable map level below 0");
            if (this.levels[this.index] == this.level) {

                this.levels[this.index] = Integer.MAX_VALUE;

                if (this.names[this.index] != null && this.levelSizes[this.index] > 0) {
                    // There were movements at this level, so we have to revert them

                    int n = this.levelSizes[this.index];
                    while (n-- != 0) {
                        final String name = this.names[this.index][n];
                        final Object newValue = this.newValues[this.index][n];
                        final Object oldValue = this.oldValues[this.index][n];
                        if (newValue == NON_EXISTING) {
                            if (!containsVariable(name)) {
                                // Only if not contained, in order to avoid modifying values that have been set directly
                                // into the request.
                                if (oldValue != NON_EXISTING) {
                                    this.request.setAttribute(name,oldValue);
                                }
                            }
                        } else if (newValue == this.request.getAttribute(name)) {
                            // Only if the value matches, in order to avoid modifying values that have been set directly
                            // into the request.
                            if (oldValue == NON_EXISTING) {
                                this.request.removeAttribute(name);
                            } else {
                                this.request.setAttribute(name,oldValue);
                            }
                        }
                    }
                    this.levelSizes[this.index] = 0;

                }

                this.selectionTargets[this.index] = null;
                this.inliners[this.index] = null;

                this.index--;

            }
            this.level--;
        }




        public String getStringRepresentationByLevel() {

            final StringBuilder strBuilder = new StringBuilder();
            strBuilder.append('{');
            final Map oldValuesSum = new LinkedHashMap();
            int n = this.index + 1;
            while (n-- != 1) {
                final Map levelVars = new LinkedHashMap();
                if (this.names[n] != null && this.levelSizes[n] > 0) {
                    for (int i = 0; i < this.levelSizes[n]; i++) {
                        final String name = this.names[n][i];
                        final Object newValue = this.newValues[n][i];
                        final Object oldValue = this.oldValues[n][i];
                        if (newValue == oldValue) {
                            // This is a no-op!
                            continue;
                        }
                        if (!oldValuesSum.containsKey(name)) {
                            // This means that, either the value in the request is the same as the newValue, or it was modified
                            // directly at the request and we need to discard this entry.
                            if (newValue == NON_EXISTING) {
                                if (containsVariable(name)) {
                                    continue;
                                }
                            } else {
                                if (newValue != this.request.getAttribute(name)) {
                                    continue;
                                }
                            }
                        } else {
                            // This means that, either the old value in the map is the same as the newValue, or it was modified
                            // directly at the request and we need to discard this entry.
                            if (newValue != oldValuesSum.get(name)) {
                                continue;
                            }
                        }
                        levelVars.put(name, newValue);
                        oldValuesSum.put(name, oldValue);
                    }
                }
                if (!levelVars.isEmpty() || this.selectionTargets[n] != null || this.inliners[n] != null) {
                    if (strBuilder.length() > 1) {
                        strBuilder.append(',');
                    }
                    strBuilder.append(this.levels[n] + ":");
                    if (!levelVars.isEmpty() || n == 0) {
                        strBuilder.append(levelVars);
                    }
                    if (this.selectionTargets[n] != null) {
                        strBuilder.append("<" + this.selectionTargets[n].selectionTarget + ">");
                    }
                    if (this.inliners[n] != null) {
                        strBuilder.append("[" + this.inliners[n].getName() + "]");
                    }
                }
            }
            final Map requestAttributes = new LinkedHashMap();
            final Enumeration attrNames = this.request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                final String name = attrNames.nextElement();
                if (oldValuesSum.containsKey(name)) {
                    final Object oldValue = oldValuesSum.get(name);
                    if (oldValue != NON_EXISTING) {
                        requestAttributes.put(name, oldValuesSum.get(name));
                    }
                    oldValuesSum.remove(name);
                } else {
                    requestAttributes.put(name, this.request.getAttribute(name));
                }
            }
            for (Map.Entry oldValuesSumEntry : oldValuesSum.entrySet()) {
                final String name = oldValuesSumEntry.getKey();
                if (!requestAttributes.containsKey(name)) {
                    final Object oldValue = oldValuesSumEntry.getValue();
                    if (oldValue != NON_EXISTING) {
                        requestAttributes.put(name, oldValue);
                    }
                }
            }
            if (strBuilder.length() > 1) {
                strBuilder.append(',');
            }
            strBuilder.append(this.levels[n] + ":");
            strBuilder.append(requestAttributes.toString());
            if (this.selectionTargets[0] != null) {
                strBuilder.append("<" + this.selectionTargets[0].selectionTarget + ">");
            }
            if (this.inliners[0] != null) {
                strBuilder.append("[" + this.inliners[0].getName() + "]");
            }
            strBuilder.append("}[");
            strBuilder.append(this.level);
            strBuilder.append(']');
            return strBuilder.toString();

        }




        @Override
        public String toString() {

            final Map equivalentMap = new LinkedHashMap();
            final Enumeration attributeNamesEnum = this.request.getAttributeNames();
            while (attributeNamesEnum.hasMoreElements()) {
                final String name = attributeNamesEnum.nextElement();
                equivalentMap.put(name, this.request.getAttribute(name));
            }
            final String textInliningStr = (getInliner() != null? "[" + getInliner().getName() + "]" : "" );
            return equivalentMap.toString() + (hasSelectionTarget()? "<" + getSelectionTarget() + ">" : "") + textInliningStr;

        }




        /*
         * This class works as a wrapper for the selection target, in order to differentiate whether we
         * have set a selection target, we have not, or we have set it but it's null
         */
        private static final class SelectionTarget {

            final Object selectionTarget;

            SelectionTarget(final Object selectionTarget) {
                super();
                this.selectionTarget = selectionTarget;
            }

        }


    }




    private static boolean existsInEnumeration(final Enumeration enumeration, final String value) {
        if (value == null) {
            while (enumeration.hasMoreElements()) {
                if (enumeration.nextElement() == null) {
                    return true;
                }
            }
            return false;
        }
        while (enumeration.hasMoreElements()) {
            if (value.equals(enumeration.nextElement())) {
                return true;
            }
        }
        return false;
    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy