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

com.ning.http.client.FluentCaseInsensitiveStringsMap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2010 Ning, Inc.
 *
 * Ning licenses this file to you 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 com.ning.http.client;

import static com.ning.http.util.MiscUtils.isNonEmpty;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * An implementation of a {@code String -> List} map that adds a fluent interface, i.e. methods that
 * return this instance. This class differs from {@link FluentStringsMap} in that keys are treated in an
 * case-insensitive matter, i.e. case of the key doesn't matter when retrieving values or changing the map.
 * However, the map preserves the key case (of the first insert or replace) and returns the keys in their
 * original case in the appropriate methods (e.g. {@link FluentCaseInsensitiveStringsMap#keySet()}).
 */
public class FluentCaseInsensitiveStringsMap implements Map>, Iterable>> {
    private final Map> values = new LinkedHashMap<>();
    private final Map keyLookup = new LinkedHashMap<>();

    public FluentCaseInsensitiveStringsMap() {
    }

    public FluentCaseInsensitiveStringsMap(FluentCaseInsensitiveStringsMap src) {
        if (src != null) {
            for (Map.Entry> header : src) {
                add(header.getKey(), header.getValue());
            }
        }
    }

    public FluentCaseInsensitiveStringsMap(Map> src) {
        if (src != null) {
            for (Map.Entry> header : src.entrySet()) {
                add(header.getKey(), header.getValue());
            }
        }
    }

    public FluentCaseInsensitiveStringsMap add(String key, String value) {
        if (key != null) {
            String lcKey = key.toLowerCase(Locale.ENGLISH);
            String realKey = keyLookup.get(lcKey);
            
            List curValues = null;
            if (realKey == null) {
                keyLookup.put(lcKey, key);
                curValues = new ArrayList<>();
                values.put(key, curValues);
            } else {
                curValues = values.get(realKey);
            }

            String nonNullValue = value != null? value : "";
            curValues.add(nonNullValue);
        }
        return this;
    }

    /**
     * Adds the specified values and returns this object.
     *
     * @param key    The key
     *  @param values The value(s); if the array is null then this method has no effect. Individual null values are turned into empty strings
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap add(String key, String... values) {
    	if (isNonEmpty(values)) {
            add(key, Arrays.asList(values));
        }
        return this;
    }

    private List fetchValues(Collection values) {
        List result = null;

        if (values != null) {
            for (String value : values) {
                if (value == null) {
                    value = "";
                }
                if (result == null) {
                    // lazy initialization
                    result = new ArrayList<>();
                }
                result.add(value);
            }
        }
        return result;
    }

    /**
     * Adds the specified values and returns this object.
     *
     * @param key    The key
     * @param values The value(s); if null then this method has no effect. Use an empty collection
     *               to generate an empty value
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap add(String key, Collection values) {
        if (key != null) {
            List nonNullValues = fetchValues(values);

            if (nonNullValues != null) {
                String lcKey = key.toLowerCase(Locale.ENGLISH);
                String realKey = keyLookup.get(lcKey);
                List curValues = null;

                if (realKey == null) {
                    realKey = key;
                    keyLookup.put(lcKey, key);
                } else {
                    curValues = this.values.get(realKey);
                }

                if (curValues == null) {
                    curValues = new ArrayList<>();
                    this.values.put(realKey, curValues);
                }
                curValues.addAll(nonNullValues);
            }
        }
        return this;
    }

    /**
     * Adds all key-values pairs from the given object to this object and returns this object.
     *
     * @param src The source object
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap addAll(FluentCaseInsensitiveStringsMap src) {
        if (src != null) {
            for (Map.Entry> header : src) {
                add(header.getKey(), header.getValue());
            }
        }
        return this;
    }

    /**
     * Adds all key-values pairs from the given map to this object and returns this object.
     *
     * @param src The source map
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap addAll(Map> src) {
        if (src != null) {
            for (Map.Entry> header : src.entrySet()) {
                add(header.getKey(), header.getValue());
            }
        }
        return this;
    }

    /**
     * Replaces the values for the given key with the given values.
     *
     * @param key    The key
     * @param values The new values
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap replaceWith(final String key, final String... values) {
        return replaceWith(key, Arrays.asList(values));
    }

    /**
     * Replaces the values for the given key with the given values.
     *
     * @param key    The key
     * @param values The new values
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap replaceWith(final String key, final Collection values) {
        if (key != null) {
            List nonNullValues = fetchValues(values);
            String lcKkey = key.toLowerCase(Locale.ENGLISH);
            String realKey = keyLookup.get(lcKkey);

            if (nonNullValues == null) {
                keyLookup.remove(lcKkey);
                if (realKey != null) {
                    this.values.remove(realKey);
                }
            } else {
                if (!key.equals(realKey)) {
                    keyLookup.put(lcKkey, key);
                    this.values.remove(realKey);
                }
                this.values.put(key, nonNullValues);
            }
        }
        return this;
    }

    /**
     * Replace the values for all keys from the given map that are also present in this object, with the values from the given map.
     * All key-values from the given object that are not present in this object, will be added to it.
     *
     * @param src The source object
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap replaceAll(FluentCaseInsensitiveStringsMap src) {
        if (src != null) {
            for (Map.Entry> header : src) {
                replaceWith(header.getKey(), header.getValue());
            }
        }
        return this;
    }

    /**
     * Replace the values for all keys from the given map that are also present in this object, with the values from the given map.
     * All key-values from the given object that are not present in this object, will be added to it.
     *
     * @param src The source map
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap replaceAll(Map> src) {
        if (src != null) {
            for (Map.Entry> header : src.entrySet()) {
                replaceWith(header.getKey(), header.getValue());
            }
        }
        return this;
    }

    @Override
    public List put(String key, List value) {
        if (key == null) {
            throw new NullPointerException("Null keys are not allowed");
        }

        List oldValue = get(key);

        replaceWith(key, value);
        return oldValue;
    }

    @Override
    public void putAll(Map> values) {
        replaceAll(values);
    }

    /**
     * Removes the values for the given key if present and returns this object.
     *
     * @param key The key
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap delete(String key) {
        if (key != null) {
            String lcKey = key.toLowerCase(Locale.ENGLISH);
            String realKey = keyLookup.remove(lcKey);

            if (realKey != null) {
                values.remove(realKey);
            }
        }
        return this;
    }

    /**
     * Removes the values for the given keys if present and returns this object.
     *
     * @param keys The keys
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap deleteAll(String... keys) {
        if (keys != null) {
            for (String key : keys) {
                remove(key);
            }
        }
        return this;
    }

    /**
     * Removes the values for the given keys if present and returns this object.
     *
     * @param keys The keys
     * @return This object
     */
    public FluentCaseInsensitiveStringsMap deleteAll(Collection keys) {
        if (keys != null) {
            for (String key : keys) {
                remove(key);
            }
        }
        return this;
    }

    @Override
    public List remove(Object key) {
        if (key == null) {
            return null;
        } else {
            List oldValues = get(key.toString());

            delete(key.toString());
            return oldValues;
        }
    }

    @Override
    public void clear() {
        keyLookup.clear();
        values.clear();
    }

    @Override
    public Iterator>> iterator() {
        return Collections.unmodifiableSet(values.entrySet()).iterator();
    }

    @Override
    public Set keySet() {
        return new LinkedHashSet<>(keyLookup.values());
    }

    @Override
    public Set>> entrySet() {
        return values.entrySet();
    }

    @Override
    public int size() {
        return values.size();
    }

    @Override
    public boolean isEmpty() {
        return values.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return key == null ? false : keyLookup.containsKey(key.toString().toLowerCase(Locale.ENGLISH));
    }

    @Override
    public boolean containsValue(Object value) {
        return values.containsValue(value);
    }

    /**
     * Returns the value for the given key. If there are multiple values for this key,
     * then only the first one will be returned.
     *
     * @param key The key
     * @return The first value
     */
    public String getFirstValue(String key) {
        List values = get(key);

        if (values.isEmpty()) {
            return null;
        } else {
            return values.get(0);
        }
    }

    /**
     * Returns the values for the given key joined into a single string using the given delimiter.
     *
     * @param key The key
     * @return The value as a single string
     */
    public String getJoinedValue(String key, String delimiter) {
        List values = get(key);

        if (values.isEmpty()) {
            return null;
        } else if (values.size() == 1) {
            return values.get(0);
        } else {
            StringBuilder result = new StringBuilder();

            for (String value : values) {
                if (result.length() > 0) {
                    result.append(delimiter);
                }
                result.append(value);
            }
            return result.toString();
        }
    }

    @Override
    public List get(Object key) {
        if (key == null)
            return Collections.emptyList();

        String lcKey = key.toString().toLowerCase(Locale.ENGLISH);
        String realKey = keyLookup.get(lcKey);

        return realKey != null ? values.get(realKey) : Collections. emptyList();
    }

    @Override
    public Collection> values() {
        return values.values();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }

        final FluentCaseInsensitiveStringsMap other = (FluentCaseInsensitiveStringsMap) obj;

        if (values == null) {
            if (other.values != null) {
                return false;
            }
        } else if (!values.equals(other.values)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        return values == null ? 0 : values.hashCode();
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();

        for (Map.Entry> entry : values.entrySet()) {
            if (result.length() > 0) {
                result.append("; ");
            }
            result.append("\"");
            result.append(entry.getKey());
            result.append("=");

            boolean needsComma = false;

            for (String value : entry.getValue()) {
                if (needsComma) {
                    result.append(", ");
                } else {
                    needsComma = true;
                }
                result.append(value);
            }
            result.append("\"");
        }
        return result.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy