org.wildfly.security.authz.Attributes Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2015 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.wildfly.security.authz;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import org.wildfly.common.Assert;
/**
* A collection of string attributes.
*
*
By default, this interface provides a default implementation for all methods that perform writes to the collection. The default
* implementation will always throw a {@link UnsupportedOperationException}, which means the collection is read-only by default.
*
*
If an implementation needs to also support writes it must override and implement all these default methods.
*
* @author David M. Lloyd
*/
public interface Attributes {
/**
* Empty, read-only attribute collection.
*/
Attributes EMPTY = new Attributes() {
@Override
public Collection entries() {
return Collections.emptySet();
}
@Override
public int size(final String key) {
return 0;
}
@Override
public boolean remove(final String key) {
return false;
}
@Override
public String get(final String key, final int idx) {
return null;
}
@Override
public Entry get(final String key) {
return new SimpleAttributesEntry(this, key);
}
@Override
public int size() {
return 0;
}
};
/**
* Get the entry collection. Changes to the entry collection will modify this attribute collection, if it is
* writable. The returned entries will remain up to date with the state of this collection.
*
* @return the entry collection
*/
Collection entries();
/**
* Get the number of values mapped to the given key.
*
* @param key the key
* @return the number of mapped values
*/
int size(String key);
/**
* Get the collection of values for the given key. The result may implement {@link SetEntry} if the values
* are distinct (for example, a role or group set).
*
* @param key the attribute name
* @return the (possibly empty) attribute value collection
*/
Entry get(String key);
/**
* Get the mapping for the given key at the given position.
*
* @param key the key
* @param idx the index
* @return the mapping value
* @throws IndexOutOfBoundsException if {@code idx} is less than 0 or greater than or equal to {@code size(key)}
*/
String get(String key, int idx);
/**
* Get the number of keys in this attribute collection.
*
* @return the number of keys
*/
int size();
/**
* Remove all values for the given key from this collection.
*
* @param key the key
* @return {@code true} if the key was found, {@code false} otherwise
* @throws UnsupportedOperationException if this method is not implemented and the operation is not supported
*/
default boolean remove(String key) {
throw Assert.unsupported();
}
/**
* Add a mapping for the given key at the given position.
*
* @param key the key
* @param idx the index
* @param value the mapping value
* @throws IndexOutOfBoundsException if {@code idx} is less than 0 or greater than or equal to {@code size(key)}
* @throws UnsupportedOperationException if this method is not implemented and the operation is not supported
*/
default void add(String key, int idx, String value) {
throw Assert.unsupported();
}
/**
* Modify the mapping for the given key at the given position.
*
* @param key the key
* @param idx the index
* @param value the mapping value
* @return the previous mapping value
* @throws IndexOutOfBoundsException if {@code idx} is less than 0 or greater than or equal to {@code size(key)}
* @throws UnsupportedOperationException if this method is not implemented and the operation is not supported
*/
default String set(String key, int idx, String value) {
throw Assert.unsupported();
}
/**
* Remove and return the mapping for the given key at the given position. All later entries for that key are shifted
* up to fill in the gap left by the removed element.
*
* @param key the key
* @param idx the index
* @return the previous mapping value
* @throws IndexOutOfBoundsException if {@code idx} is less than 0 or greater than or equal to {@code size(key)}
* @throws UnsupportedOperationException if this method is not implemented and the operation is not supported
*/
default String remove(String key, int idx) {
throw Assert.unsupported();
}
/**
* Clear this collection, resetting its size to zero.
*
* @throws UnsupportedOperationException if this method is not implemented and the operation is not supported
*/
default void clear() {
throw Assert.unsupported();
}
/**
* Remove all values for the given key from this collection, copying the values into a list which is returned.
*
* @param key the key
* @return the values as a list (not {@code null})
*/
default List copyAndRemove(String key) {
final Entry values = get(key);
List copy = values.isEmpty() ? Collections.emptyList() : new ArrayList<>(values);
remove(key);
return copy;
}
/**
* Get all the values of all the keys in this collection. The returned collection can be used to modify this
* attributes collection.
*
* @return the collection of all values (not {@code null})
*/
default Collection values() {
return new AbstractCollection() {
public Iterator iterator() {
final Iterator entries = entries().iterator();
return new Iterator() {
private Iterator values;
public boolean hasNext() {
for (;;) {
if (values == null) {
if (! entries.hasNext()) {
return false;
}
values = entries.next().iterator();
} else if (values.hasNext()) {
return true;
} else {
values = null;
}
}
}
public String next() {
if (! hasNext()) throw new NoSuchElementException();
return values.next();
}
public void remove() {
final Iterator values = this.values;
if (values == null) {
throw new IllegalStateException();
}
values.remove();
}
};
}
public void clear() {
Attributes.this.clear();
}
public boolean removeAll(final Collection> c) {
boolean changed = false;
for (Entry entries : entries()) {
changed = entries.removeAll(c) || changed;
}
return changed;
}
public boolean retainAll(final Collection> c) {
boolean changed = false;
for (Entry entries : entries()) {
changed = entries.retainAll(c) || changed;
}
return changed;
}
public boolean isEmpty() {
for (Entry entries : entries()) {
if (! entries.isEmpty()) {
return false;
}
}
return true;
}
public int size() {
int size = 0;
for (Entry entries : entries()) {
size += entries.size();
}
return size;
}
};
}
/**
* Get a set comprised of all the keys in this collection. The returned set can be used to modify this attributes
* collection.
*
* @return the set of all keys (not {@code null})
*/
default Set keySet() {
return new AbstractSet() {
public Iterator iterator() {
final Iterator entries = entries().iterator();
return new Iterator() {
public boolean hasNext() {
return entries.hasNext();
}
public String next() {
return entries.next().getKey();
}
public void remove() {
entries.remove();
}
};
}
public boolean contains(final Object o) {
return o instanceof String && Attributes.this.containsKey((String) o);
}
public boolean remove(final Object o) {
return o instanceof String && Attributes.this.remove((String) o);
}
public void clear() {
Attributes.this.clear();
}
public int size() {
return Attributes.this.size();
}
};
}
/**
* Conditionally set a specific value of a given key to a new value, if the existing value matches the {@code expect}
* parameter.
*
* @param key the key
* @param idx the index
* @param expect the expected value
* @param update the value to set
* @return {@code true} if the actual value matched the expected value and was updated, {@code false} otherwise
* @throws IndexOutOfBoundsException if {@code idx} is less than 0 or greater than or equal to {@code size(key)}
*/
default boolean set(String key, int idx, String expect, String update) {
Assert.checkNotNullParam("update", update);
if (expect == null || idx < 0 || idx >= size(key) || ! get(key, idx).equals(expect)) {
return false;
}
set(key, idx, update);
return true;
}
/**
* Get the index of the first occurrence of the given value at the given key, if any.
*
* @param key the key
* @param value the value
* @return the index, or -1 if the value was not found at the given key
*/
default int indexOf(String key, String value) {
final int size = size(key);
for (int i = 0; i < size; i ++) {
if (get(key, i).equals(value)) {
return i;
}
}
return -1;
}
/**
* Get the index of the last occurrence of the given value at the given key, if any.
*
* @param key the key
* @param value the value
* @return the index, or -1 if the value was not found at the given key
*/
default int lastIndexOf(String key, String value) {
final int size = size(key);
for (int i = size - 1; i >= 0; i --) {
if (get(key, i).equals(value)) {
return i;
}
}
return -1;
}
/**
* Remove all the values for the given key between the {@code from} index (inclusive) and the {@code to} index
* (exclusive).
*
* @param key the key
* @param from the start index (inclusive)
* @param to the end index (exclusive)
* @throws IndexOutOfBoundsException if {@code idx} is less than 0 or greater than or equal to {@code size(key)}
*/
default void removeRange(String key, int from, int to) {
for (int i = to - 1; i >= from; i --) {
remove(key, i);
}
}
/**
* Get the first value mapped to the given key.
*
* @param key the key
* @return the value
* @throws IndexOutOfBoundsException if there are no values for the given key
*/
default String getFirst(String key) {
return get(key, 0);
}
/**
* Get the last value mapped to the given key.
*
* @param key the key
* @return the value
* @throws IndexOutOfBoundsException if there are no values for the given key
*/
default String getLast(String key) {
return get(key, size(key) - 1);
}
/**
* Add a value before the first mapping for the given key.
*
* @param key the key
* @param value the value
*/
default void addFirst(String key, String value) {
add(key, 0, value);
}
/**
* Add a value after the last mapping for the given key.
*
* @param key the key
* @param value the value
*/
default void addLast(String key, String value) {
add(key, size(key), value);
}
/**
* Remove the first value mapped to the given key.
*
* @param key the key
* @return the value
* @throws IndexOutOfBoundsException if there are no values for the given key
*/
default String removeFirst(String key) {
return remove(key, 0);
}
/**
* Remove the last value mapped to the given key.
*
* @param key the key
* @return the value
* @throws IndexOutOfBoundsException if there are no values for the given key
*/
default String removeLast(String key) {
return remove(key, size(key) - 1);
}
/**
* Remove the mapping for the given key at the given position if it matches the given existing value. All later
* entries for that key are shifted up to fill in the gap left by the removed element.
*
* @param key the key
* @param idx the index
* @param value the expected previous mapping value
* @return {@code true} if the value matched and was removed, {@code false} otherwise
* @throws IndexOutOfBoundsException if {@code idx} is less than 0 or greater than or equal to {@code size(key)}
*/
default boolean remove(String key, int idx, String value) {
if (get(key, idx).equals(value)) {
remove(key, idx);
return true;
} else {
return false;
}
}
/**
* Remove the first occurrence of the given value under the given key, if any.
*
* @param key the key
* @param value the value to remove
* @return {@code true} if the value was found and removed, {@code false} otherwise
*/
default boolean removeFirst(String key, String value) {
final int idx = indexOf(key, value);
return idx >= 0 && remove(key, idx, value);
}
/**
* Remove the last occurrence of the given value under the given key, if any.
*
* @param key the key
* @param value the value to remove
* @return {@code true} if the value was found and removed, {@code false} otherwise
*/
default boolean removeLast(String key, String value) {
final int idx = lastIndexOf(key, value);
return idx >= 0 && remove(key, idx, value);
}
/**
* Remove the all occurrences of the given value under the given key, if any.
*
* @param key the key
* @param value the value to remove
* @return {@code true} if the value was found and removed, {@code false} otherwise
*/
default boolean removeAll(String key, String value) {
int idx = lastIndexOf(key, value);
if (idx == -1) return false;
while (idx >= 0) {
remove(key, idx, value);
idx = lastIndexOf(key, value);
}
return true;
}
/**
* Add all the values from the given map to this attributes collection.
*
* @param map the map to copy from
* @return {@code true} if elements were added, {@code false} otherwise
*/
default boolean addAll(Map> map) {
Assert.checkNotNullParam("map", map);
boolean changed = false;
for (Map.Entry> entry : map.entrySet()) {
final Collection value = entry.getValue();
if (value != null && ! value.isEmpty()) {
final String key = entry.getKey();
changed = addAll(key, value) || changed;
}
}
return changed;
}
/**
* Add all the values from the given collection to the value collection for the given key.
*
* @param key the key
* @param values the values to add
* @return {@code true} if elements were added, {@code false} otherwise
*/
default boolean addAll(String key, Collection values) {
Assert.checkNotNullParam("key", key);
Assert.checkNotNullParam("values", values);
boolean changed = false;
for (String s : values) {
if (s != null) {
addLast(key, s);
changed = true;
}
}
return changed;
}
/**
* Determine if the given key has values in this collection.
*
* @param key the key
* @return {@code true} if the key has values, {@code false} otherwise
*/
default boolean containsKey(String key) {
return key != null && size(key) > 0;
}
/**
* Determine if the given key has a mapping for the given value in this collection.
*
* @param key the key
* @param value the value
* @return {@code true} if the key has a mapping to the given value, {@code false} otherwise
*/
default boolean containsValue(String key, String value) {
return key != null && value != null && indexOf(key, value) >= 0;
}
/**
* Replace the mapping for the given key with the values copied from the given collection.
*
* @param key the key
* @param values the new values
* @return a list containing the previously mapped values
*/
default List copyAndReplace(String key, Collection values) {
final List old = copyAndRemove(key);
addAll(key, values);
return old;
}
/**
* Determine if this collection is empty.
*
* @return {@code true} if the collection is empty, {@code false} otherwise
*/
default boolean isEmpty() {
return size() == 0;
}
/**
* Returns a read-only instance of this instance.
*
* @return a read-only instance of this instance.
*/
default Attributes asReadOnly() {
return new Attributes() {
private Map entryCache = new HashMap<>();
private Collection cachedEntries;
@Override
public Collection entries() {
if (this.cachedEntries != null) {
return this.cachedEntries;
}
return this.cachedEntries = new AbstractCollection() {
@Override
public Iterator iterator() {
Iterator iterator = Attributes.this.keySet().iterator();
return new Iterator() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Entry next() {
return get(iterator.next());
}
};
}
@Override
public int size() {
return Attributes.this.keySet().size();
}
};
}
@Override
public int size(String key) {
return Attributes.this.size(key);
}
@Override
public String get(String key, int idx) {
return Attributes.this.get(key, idx);
}
@Override
public int size() {
return Attributes.this.size();
}
@Override
public Entry get(String key) {
return this.entryCache.computeIfAbsent(key, s -> new SimpleAttributesEntry(this, s));
}
};
}
/**
* The entry collection for a mapping.
*/
interface Entry extends List {
/**
* Get the mapping key.
*
* @return the mapping key
*/
String getKey();
/**
* Remove all the values for the given key between the {@code from} index (inclusive) and the {@code to} index
* (exclusive).
*
* @param from the start index (inclusive)
* @param to the end index (exclusive)
* @throws IndexOutOfBoundsException if {@code from} or {@code to} is outside of the allowed range
*/
void removeRange(int from, int to);
/**
* Create a spliterator over the elements of this ordered and non-null collection.
*
* @return the spliterator
*/
default Spliterator spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.NONNULL);
}
}
/**
* The entry collection for a mapping whose values are a distinct set.
*/
interface SetEntry extends Entry, Set {
/**
* Create a spliterator over the elements of this distinct, ordered, and non-null collection.
*
* @return the spliterator
*/
default Spliterator spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.NONNULL);
}
}
}