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

com.adobe.granite.ui.components.AttrBuilder Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2012 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.adobe.granite.ui.components;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import com.adobe.granite.xss.XSSAPI;

/**
 * A builder to generate HTML attributes. This builder is designed to be secured
 * using {@link XSSAPI}. It will encode the value automatically. If the value is
 * null, it will be ignored.
 */
public class AttrBuilder {
    private final HttpServletRequest req;

    private final XSSAPI xssAPI;

    private Map data = new LinkedHashMap();

    private Map encodings = new LinkedHashMap<>();

    private Set classes = new HashSet();

    private enum Encoding {
        HREF, HTML_ATTR
    }

    public AttrBuilder(HttpServletRequest req, XSSAPI xssAPI) {
        this.req = req;
        this.xssAPI = xssAPI;
    }

    /**
     * Gets the raw {@link Map} of attributes, with un-encoded values
     *
     * @return {@link Map} of attributes
     */
    public Map getData() {
        return data;
    }

    /**
     * Adds relationship. Currently it is implemented as class
     * attribute.
     * @param value the relationship to add
     */
    public void addRel(String value) {
        addClass(value);
    }

    /**
     * Adds class attribute with the given value.
     * @param value the class attribute to add
     */
    public void addClass(String value) {
        if (value == null || value.length() == 0) return;

        if (classes.add(value)) {
            add("class", value);
        }
    }

    /**
     * Adds an attribute that behave like href attribute. i.e. the
     * value will be prepended with context path (if absolute path) and checked
     * using {@link XSSAPI#getValidHref(String)}.
     * @param name the name of the attribute to add
     * @param value the value of the specified attribute
     */
    public void addHref(String name, String value) {
        if (value == null || value.length() == 0) return;

        if (value.startsWith("/")) {
            value = req.getContextPath() + value;
        }

        data.put(name, value);
        encodings.put(name, Encoding.HREF);
    }

    /**
     * Adds {@code disabled} attribute.
     * @param disabled the boolean value of the {@code disabled} attribute
     */
    public void addDisabled(boolean disabled) {
        this.addBoolean("disabled", disabled);
    }

    /**
     * Adds {@code checked} attribute.
     * @param checked the boolean value of the {@code checked} attribute
     */
    public void addChecked(boolean checked) {
        this.addBoolean("checked", checked);
    }

    /**
     * Adds {@code selected} attribute.
     * @param selected the boolean value of the {@code selected} attribute
     */
    public void addSelected(boolean selected) {
        this.addBoolean("selected", selected);
    }

    /**
     * Adds {@code multiple} attribute.
     * @param multiple the boolean value of the {@code multiple} attribute
     */
    public void addMultiple(boolean multiple) {
        this.addBoolean("multiple", multiple);
    }

    /**
     * Adds boolean attribute (behaves like disabled) for the given name.
     * When the given value is true, it will be printed as "disabled=''", instead of "disabled='true'".
     * When the given value is false, it will NOT be printed, instead of "disabled='false'".
     * @param name the name of the boolean attribute to add
     * @param value the boolean value of the attribute
     */
    public void addBoolean(String name, boolean value) {
        if (!value) return;
        add(name, "");
    }

    /**
     * Adds the given name as data-* attribute.
     * @param name the name of the data-* attribute to add
     * @param value the value of the attribute
     */
    public void addOther(String name, String value) {
        add("data-" + xssAPI.encodeForHTML(name), value);
    }

    /**
     * Adds the given data as data-* attributes. Entries with keys specified in
     * exclusions parameter or having namespace (e.g. jcr:primaryType) will be
     * excluded.
     * @param data the map containing key/value pairs to add as data-* attributes
     * @param exclusions the keys which must not be added as data-* attributes
     */
    public void addOthers(Map data, String... exclusions) {
        List blacklisted = Arrays.asList(exclusions);

        for (Entry e : data.entrySet()) {
            String key = e.getKey();

            if (key.indexOf(":") >= 0) continue;
            if (blacklisted.indexOf(key) >= 0) continue;

            Object value = e.getValue();

            if (value.getClass().isArray()) {
                for (Object o : (Object[]) value) {
                    addOther(key, o.toString());
                }
            } else {
                addOther(key, value.toString());
            }
        }
    }

    /**
     * Adds attribute with the given name. The value will be added to existing attribute using space-delimited convention.
     * e.g. class="class1 class2"
     * @param name the name of the attribute to add
     * @param value the boolean value of the attribute
     */
    public void add(String name, Boolean value) {
        if (value == null || name == null || name.length() == 0) return;

        addNoCheck(name, value.toString());
    }

    /**
     * Adds attribute with the given name. The value will be added to existing attribute using space-delimited convention.
     * e.g. class="class1 class2"
     * @param name the name of the attribute to add
     * @param value the integer value of the attribute
     */
    public void add(String name, Integer value) {
        if (value == null || name == null || name.length() == 0) return;

        addNoCheck(name, value.toString());
    }

    /**
     * Adds attribute with the given name. The value will be added to existing attribute using space-delimited convention.
     * e.g. class="class1 class2"
     * @param name the name of the attribute to add
     * @param value the double value of the attribute
     */
    public void add(String name, Double value) {
        if (value == null || name == null || name.length() == 0) return;

        addNoCheck(name, value.toString());
    }

    /**
     * Adds attribute with the given name. The value will be added to existing attribute using space-delimited convention.
     * e.g. class="class1 class2"
     * @param name the name of the attribute to add
     * @param value the string value of the attribute
     */
    public void add(String name, String value) {
        if (value == null || name == null || name.length() == 0) return;
        addNoCheck(name, value);
        encodings.put(name, Encoding.HTML_ATTR);
    }

    /**
     * Sets attribute with the given name. Existing value previously set will be replaced by the given value.
     * @param name the name of the attribute to set or replace (if exists)
     * @param value the string value of the attribute
     */
    public void set(String name, String value) {
        if (value == null || name == null || name.length() == 0) return;
        data.put(name, value);
        encodings.put(name, Encoding.HTML_ATTR);
    }

    private void addNoCheck(String name, String v) {
        if (data.containsKey(name)) {
            v = data.get(name) + " " + v;
        }

        data.put(name, v);
    }

    /**
     * Returns {@code true} if there is no attribute in this builder, {@code false} otherwise.
     * @return {@code true} if there is no attribute in this builder, {@code false} otherwise
     */
    public boolean isEmpty() {
        return data.isEmpty();
    }

    /**
     * Builds the attributes in the form of {@code =''*}.
     * @return the string containing the built attributes
     */
    public String build() {
        try {
            StringWriter out = new StringWriter();
            build(out);
            return out.toString();
        } catch (IOException impossible) {
            throw new RuntimeException(impossible);
        }
    }

    /**
     * Builds the attributes in the form of {@code =''*}*.
     * @param out the writer
     * @throws IOException in case there's an error when appending to the writer
     */
    public void build(Writer out) throws IOException {
        for (Entry e : data.entrySet()) {
            String key = e.getKey();
            String value = e.getValue();
            Encoding encoding = encodings.get(e.getKey());
            if (encoding != null && value != null && value.length() > 0) {
                switch (encoding) {
                    case HREF:
                        value = xssAPI.getValidHref(value);
                        break;
                    case HTML_ATTR:
                        value = xssAPI.encodeForHTMLAttr(value);
                        break;
                }
            }
            out.append(" ").append(key).append("=\"").append(value).append("\"");
        }
    }

    @Override
    public String toString() {
        return build();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy