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

org.eclipse.jetty.util.Attributes Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.util;

import java.io.IOException;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jetty.util.component.Dumpable;

/**
 * Attributes.
 * Interface commonly used for storing attributes.
 */
public interface Attributes
{
    /**
     * Remove an attribute
     * @param name the attribute to remove
     * @return the value of the attribute if removed, else {@code null}
     */
    Object removeAttribute(String name);

    /**
     * Set an attribute
     * @param name the attribute to set
     * @param attribute the value to set. A null value is equivalent to removing the attribute.
     * @return the previous value of the attribute if set, else {@code null}
     */
    Object setAttribute(String name, Object attribute);

    /**
     * Get an attribute
     * @param name the attribute to get
     * @return the value of the attribute, or {@code null} if no such attribute exists
     */
    Object getAttribute(String name);

    /**
     * Get the immutable set of attribute names.
     * @return Set of attribute names, or an empty set if there are no attributes.
     */
    Set getAttributeNameSet();

    default Map asAttributeMap()
    {
        return new AbstractMap<>()
        {
            private final Set _attributeNameSet = getAttributeNameSet();
            private final AbstractSet> _entrySet  = new AbstractSet<>()
            {
                @Override
                public Iterator> iterator()
                {
                    Iterator names = _attributeNameSet.iterator();
                    return new Iterator<>()
                    {
                        @Override
                        public boolean hasNext()
                        {
                            return names.hasNext();
                        }

                        @Override
                        public Entry next()
                        {
                            String name = names.next();
                            return new SimpleEntry<>(name, getAttribute(name));
                        }
                    };
                }

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

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

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

    /**
     * Clear all attribute names
     */
    default void clearAttributes()
    {
        for (String name : getAttributeNameSet())
            removeAttribute(name);
    }

    @Override
    int hashCode();

    @Override
    boolean equals(Object o);

    /** Unwrap all  {@link Wrapper}s of the attributes
     * @param attributes The attributes to unwrap, which may be a  {@link Wrapper}.
     * @return The core attributes
     */
    static Attributes unwrap(Attributes attributes)
    {
        while (attributes instanceof Wrapper)
        {
            attributes = ((Wrapper)attributes).getWrapped();
        }
        return attributes;
    }

    static int hashCode(Attributes attributes)
    {
        int hash = 0;
        for (String name : attributes.getAttributeNameSet())
            hash = hash * 31 + Objects.hash(name, attributes.getAttribute(name));
        return hash;
    }

    static boolean equals(Attributes attributes, Object o)
    {
        if (o instanceof Attributes a)
        {
            Set ours = attributes.getAttributeNameSet();
            Set theirs = a.getAttributeNameSet();
            if (!ours.equals(theirs))
                return false;

            for (String s : ours)
            {
                if (!Objects.equals(attributes.getAttribute(s), a.getAttribute(s)))
                    return false;
            }
            return true;
        }
        return false;
    }


    /** Unwrap attributes to a specific attribute  {@link Wrapper}.
     * @param attributes The attributes to unwrap, which may be a {@link Wrapper}
     * @param target The target  {@link Wrapper} class.
     * @param  The type of the target  {@link Wrapper}.
     * @return The outermost {@link Wrapper} of the matching type of null if not found.
     */
    @SuppressWarnings("unchecked")
    static  T unwrap(Attributes attributes, Class target)
    {
        while (true)
        {
            if (target.isAssignableFrom(attributes.getClass()))
                return (T)attributes;

            if (attributes instanceof Wrapper wrapper)
                attributes = wrapper.getWrapped();
            else
                return null;
        }
    }

    /**
     * A Wrapper of attributes
     */
    class Wrapper implements Attributes
    {
        private final Attributes _wrapped;

        public Wrapper(Attributes wrapped)
        {
            _wrapped = Objects.requireNonNull(wrapped);
        }

        public Attributes getWrapped()
        {
            return _wrapped;
        }

        @Override
        public Object removeAttribute(String name)
        {
            return getWrapped().removeAttribute(name);
        }

        @Override
        public Object setAttribute(String name, Object attribute)
        {
            return getWrapped().setAttribute(name, attribute);
        }

        @Override
        public Object getAttribute(String name)
        {
            return getWrapped().getAttribute(name);
        }

        @Override
        public Set getAttributeNameSet()
        {
            return getWrapped().getAttributeNameSet();
        }

        @Override
        public void clearAttributes()
        {
            getWrapped().clearAttributes();
        }

        @Override
        public int hashCode()
        {
            return Attributes.hashCode(this);
        }

        @Override
        public boolean equals(Object o)
        {
            return o instanceof Attributes && Attributes.equals(this, o);
        }
    }

    /**
     * An Attributes implementation backed by a {@link ConcurrentHashMap}.
     */
    class Mapped implements Attributes
    {
        private final Map _map;
        private final Set _names;

        public Mapped()
        {
            this(new ConcurrentHashMap<>());
        }

        public Mapped(Map map)
        {
            _map = Objects.requireNonNull(map);
            _names = Collections.unmodifiableSet(_map.keySet());
        }

        public Mapped(Mapped attributes)
        {
            this(new ConcurrentHashMap<>(attributes._map));
        }

        @Override
        public Map asAttributeMap()
        {
            return _map;
        }

        @Override
        public Object removeAttribute(String name)
        {
            return _map.remove(name);
        }

        @Override
        public Object setAttribute(String name, Object attribute)
        {
            if (attribute == null)
                return _map.remove(name);

            return _map.put(name, attribute);
        }

        @Override
        public Object getAttribute(String name)
        {
            return _map.get(name);
        }

        @Override
        public Set getAttributeNameSet()
        {
            return _names;
        }

        @Override
        public void clearAttributes()
        {
            _map.clear();
        }

        public int size()
        {
            return _map.size();
        }

        @Override
        public String toString()
        {
            return _map.toString();
        }

        public void addAll(Attributes attributes)
        {
            for (String name : attributes.getAttributeNameSet())
                setAttribute(name, attributes.getAttribute(name));
        }

        @Override
        public int hashCode()
        {
            return Attributes.hashCode(this);
        }

        @Override
        public boolean equals(Object o)
        {
            return o instanceof Attributes && Attributes.equals(this, o);
        }
    }

    /**
     * An {@link Attributes} implementation that lazily creates a backing map iff it is actually needed.
     */
    class Lazy implements Attributes, Dumpable
    {
        private final AtomicReference> _map = new AtomicReference<>();

        public Lazy()
        {
        }

        private ConcurrentMap map()
        {
            return _map.get();
        }

        private ConcurrentMap ensureMap()
        {
            while (true)
            {
                ConcurrentMap map = map();
                if (map != null)
                    return map;
                map = new ConcurrentHashMap<>();
                if (_map.compareAndSet(null, map))
                    return map;
            }
        }

        @Override
        public Map asAttributeMap()
        {
            return ensureMap();
        }

        @Override
        public Object removeAttribute(String name)
        {
            Map map = map();
            return map == null ? null : map.remove(name);
        }

        @Override
        public Object setAttribute(String name, Object attribute)
        {
            if (attribute == null)
                return removeAttribute(name);
            return ensureMap().put(name, attribute);
        }

        @Override
        public Object getAttribute(String name)
        {
            Map map = map();
            return map == null ? null : map.get(name);
        }

        @Override
        public Set getAttributeNameSet()
        {
            return Collections.unmodifiableSet(keySet());
        }

        @Override
        public void clearAttributes()
        {
            Map map = map();
            if (map != null)
                map.clear();
        }

        public int size()
        {
            Map map = map();
            return map == null ? 0 : map.size();
        }

        @Override
        public String toString()
        {
            Map map = map();
            return map == null ? "{}" : map.toString();
        }

        private Set keySet()
        {
            Map map = map();
            return map == null ? Collections.emptySet() : map.keySet();
        }

        public void addAll(Attributes attributes)
        {
            for (String name : attributes.getAttributeNameSet())
                setAttribute(name, attributes.getAttribute(name));
        }

        @Override
        public String dump()
        {
            return Dumpable.dump(this);
        }

        @Override
        public void dump(Appendable out, String indent) throws IOException
        {
            Dumpable.dumpObjects(out, indent, String.format("%s@%x", this.getClass().getSimpleName(), hashCode()), map());
        }

        @Override
        public int hashCode()
        {
            return Attributes.hashCode(this);
        }

        @Override
        public boolean equals(Object o)
        {
            return o instanceof Attributes && Attributes.equals(this, o);
        }
    }

    /**
     * An {@link Attributes} implementation backed by another {@link Attributes} instance, which is treated as immutable, but with a
     * ConcurrentHashMap used as a mutable layer over it.
     */
    class Layer extends Wrapper
    {
        private static final Object REMOVED = new Object()
        {
            @Override
            public String toString()
            {
                return "REMOVED";
            }
        };

        private final Attributes _layer;

        public Layer(Attributes persistent)
        {
            this(persistent, new Attributes.Mapped());
        }

        public Layer(Attributes persistent, Attributes layer)
        {
            super(persistent);
            _layer = layer;
        }

        public Attributes getPersistentAttributes()
        {
            return getWrapped();
        }

        @Override
        public Object removeAttribute(String name)
        {
            Object p = super.getAttribute(name);
            try
            {
                Object v = _layer.setAttribute(name, REMOVED);
                if (v == REMOVED)
                    return null;
                if (v != null)
                    return v;
                return p;
            }
            finally
            {
                if (p == null)
                    _layer.removeAttribute(name);
            }
        }

        @Override
        public Object setAttribute(String name, Object attribute)
        {
            if (attribute == null)
                return removeAttribute(name);
            Object v = _layer.setAttribute(name, attribute);
            return v == REMOVED ? null : v;
        }

        @Override
        public Object getAttribute(String name)
        {
            Object v = _layer.getAttribute(name);
            if (v != null)
                return v == REMOVED ? null : v;
            return super.getAttribute(name);
        }

        @Override
        public Set getAttributeNameSet()
        {
            Set names = new HashSet<>(super.getAttributeNameSet());

            for (String name : _layer.getAttributeNameSet())
            {
                if (_layer.getAttribute(name) == REMOVED)
                    names.remove(name);
                else
                    names.add(name);
            }
            return Collections.unmodifiableSet(names);
        }

        @Override
        public void clearAttributes()
        {
            _layer.clearAttributes();
            for (String name : super.getAttributeNameSet())
                _layer.setAttribute(name, REMOVED);
        }
    }

    /**
     * An abstract implementation of {@link Attributes.Wrapper} that provides a mechanism
     * for synthetic attributes that can be modified or deleted.  A synthetic attribute
     * is one whose value is not stored in the normal map backing the {@link Attributes} instance,
     * but is instead calculated as needed. Modifications to synthetic attributes are maintained
     * in a separate layer and no modifications are made to the backing {@link Attributes}.
     * 

* Non-synthetic attributes are handled normally by the backing {@link Attributes} *

* Uses of this class must provide implementations for * {@link #getSyntheticNameSet()} amd {@link #getSyntheticAttribute(String)}. */ abstract class Synthetic extends Wrapper { protected static final Object REMOVED = new Object() { @Override public String toString() { return "REMOVED"; } }; private final AtomicReference> _layer = new AtomicReference<>(); protected Synthetic(Attributes base) { super(base); } /** * Get the value of a specific synthetic attribute. * @param name The name of the attribute * @return The value for the attribute, which may be computed on request, or {@code null} */ protected abstract Object getSyntheticAttribute(String name); /** * Get the list of known synthetic attribute names, including those * that currently have a null value. * @return A {@link Set} of known synthetic attributes names. */ protected abstract Set getSyntheticNameSet(); @Override public Object getAttribute(String name) { // Has the attribute been modified in the layer? Map layer = _layer.get(); if (layer != null) { // Only synthetic attributes can be in the layer. Object l = layer.get(name); // Has it been removed? if (l == REMOVED) return null; // or has it been replaced? if (l != null) return l; } // Is there a synthetic value for the attribute? We just as for the value rather than checking the name. Object s = getSyntheticAttribute(name); if (s == REMOVED) return null; if (s != null) return s; // otherwise get the attribute normally. return super.getAttribute(name); } @Override public Object setAttribute(String name, Object value) { // setting a null value is equivalent to removal if (value == null) return removeAttribute(name); // is the attribute known to be synthetic? if (getSyntheticNameSet().contains(name)) { // We will need a layer to record modifications to a synthetic attribute Map layer = _layer.updateAndGet(m -> m == null ? new HashMap<>() : m); // update the attribute in the layer Object old = layer.put(name, value); // return the old value, which if not remove and not in the layer, is the synthetic attribute itself return old == REMOVED ? null : old != null ? old : getSyntheticAttribute(name); } // handle non-synthetic attribute return super.setAttribute(name, value); } @Override public Object removeAttribute(String name) { // is the attribute known to be synthetic? if (getSyntheticNameSet().contains(name)) { // We will need a layer to record modifications to a synthetic attribute Map layer = _layer.updateAndGet(m -> m == null ? new HashMap<>() : m); // Mark the attribute as removed in the layer Object old = layer.put(name, REMOVED); // return the old value, which if not removed and not in the layer, is the synthetic attribute itself return old == REMOVED ? null : old != null ? old : getSyntheticAttribute(name); } // handle non-synthetic attribute return super.removeAttribute(name); } @Override public Set getAttributeNameSet() { // Get the non-synthetic attribute names Set names = new HashSet<>(super.getAttributeNameSet()); // Have there been any modifications to the synthetic attributes Map layer = _layer.get(); if (layer == null) { // no, so we just add the names for which there are values for (String s : getSyntheticNameSet()) if (getSyntheticAttribute(s) != null) names.add(s); } else { // otherwise for each known synthetic name for (String s : getSyntheticNameSet()) { // has the attribute been modified in the layer? Object l = layer.get(s); if (l == REMOVED) // it has been removed names.remove(s); else if (l != null) // else it was modified names.add(s); else { Object v = getSyntheticAttribute(s); if (v != null && v != REMOVED) names.add(s); } } } return Collections.unmodifiableSet(names); } @Override public void clearAttributes() { // Clear the base attributes super.clearAttributes(); // We will need a layer to remove the synthetic attributes Map layer = _layer.updateAndGet(m -> m == null ? new HashMap<>() : m); // remove all known synthetic attributes for (String s : getSyntheticNameSet()) layer.put(s, REMOVED); } } Attributes NULL = new Attributes() { @Override public Object removeAttribute(String name) { return null; } @Override public Object setAttribute(String name, Object attribute) { return null; } @Override public Object getAttribute(String name) { return null; } @Override public Set getAttributeNameSet() { return Collections.emptySet(); } @Override public void clearAttributes() { } @Override public Map asAttributeMap() { return Collections.emptyMap(); } @Override public int hashCode() { return Attributes.hashCode(this); } @Override public boolean equals(Object o) { return o instanceof Attributes && Attributes.equals(this, o); } }; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy