
org.infinispan.functional.impl.MetaParams Maven / Gradle / Ivy
package org.infinispan.functional.impl;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.stream.Stream;
import org.infinispan.functional.MetaParam;
import org.infinispan.commons.util.Experimental;
import net.jcip.annotations.NotThreadSafe;
/**
* Represents a {@link MetaParam} collection.
*
* In {@link Params}, the internal array where each parameter was
* stored is indexed by an integer. This worked fine because the available
* parameters are exclusively controlled by the Infinispan. This is not the
* case with {@link MetaParam} instances where users are expected to add their
* own types. So, for {@link MetaParams}, an array is still used but the lookup
* is done sequentially comparing the type of the {@link MetaParam} looked for
* against the each individual {@link MetaParam} instance stored in
* {@link MetaParams}.
*
*
Having sequential {@link MetaParam} lookups over an array is O(n),
* but this is not problematic since the number of {@link MetaParam} to be
* stored with each cached entry is expected to be small, less than 10 per
* {@link MetaParams} collection. So, the performance impact is quite small.
*
*
Storing {@link MetaParam} instances in an array adds the least
* amount of overhead to keeping a collection of {@link MetaParam} in memory
* along with each cached entry, while retaining flexibility to add or remove
* {@link MetaParam} instances.
*
*
This {@link MetaParams} collection is not thread safe because
* it is expected that any updates will be done having acquired write locks
* on the entire {@link org.infinispan.container.entries.CacheEntry} which
* references the {@link MetaParams} collection. Hence, any updates could be
* done without the need to keep {@link MetaParams} concurrently safe.
* Also, although users can retrieve or update individual {@link MetaParam}
* instances, they cannot act on the globally at the {@link MetaParams} level,
* and hence there is no risk of users misusing {@link MetaParams}.
*
* This class should not be accessible from user code, therefore it is package-protected.
*
* @since 8.0
*/
@NotThreadSafe
@Experimental
final class MetaParams implements Iterable> {
private static final MetaParam>[] EMPTY_ARRAY = {};
private MetaParam>[] metas;
private int length;
private MetaParams(MetaParam>[] metas, int length) {
this.metas = metas;
this.length = length;
assert checkLength();
}
public boolean isEmpty() {
return length == 0;
}
public int size() {
return length;
}
public MetaParams copy() {
if (length == 0) {
return empty();
} else {
return new MetaParams(Arrays.copyOf(metas, metas.length), length);
}
}
public Optional find(Class type) {
return Optional.ofNullable(findNullable(type));
}
@SuppressWarnings("unchecked")
private T findNullable(Class type) {
for (MetaParam> meta : metas) {
if (meta != null && meta.getClass().isAssignableFrom(type))
return (T) meta;
}
return null;
}
public void add(MetaParam meta) {
assert meta != null;
if (metas.length == 0) {
metas = new MetaParam[]{meta};
length = 1;
} else {
int hole = -1;
for (int i = 0; i < metas.length; i++) {
MetaParam> m = metas[i];
if (m == null) {
hole = i;
} else if (m.getClass().isAssignableFrom(meta.getClass())) {
metas[i] = meta;
assert checkLength();
return;
}
}
++length;
if (hole < 0) {
MetaParam>[] newMetas = Arrays.copyOf(metas, metas.length + 1);
newMetas[newMetas.length - 1] = meta;
metas = newMetas;
} else {
metas[hole] = meta;
}
assert checkLength();
}
}
private boolean checkLength() {
int l = 0;
for (MetaParam meta : metas) {
if (meta != null) ++l;
}
return l == length;
}
public void addMany(MetaParam... metaParams) {
if (metas.length == 0) {
// Arrays in Java are covariant, therefore someone could pass MetaParam.Writable[] and
// we could try to store non-writable meta param in that array (or its copy) later.
if (metaParams.getClass().getComponentType() == MetaParam.class) {
metas = metaParams;
} else {
metas = Arrays.copyOf(metaParams, metaParams.length, MetaParam[].class);
}
length = (int) Stream.of(metas).filter(Objects::nonNull).count();
} else {
List> notFound = new ArrayList<>(metaParams.length);
for (MetaParam newMeta : metaParams) {
updateExisting(newMeta, notFound);
}
if (!notFound.isEmpty()) {
MetaParam>[] newMetas = Arrays.copyOf(metas, metas.length + notFound.size());
int i = metas.length;
for (MetaParam> meta : notFound) {
newMetas[i++] = meta;
}
metas = newMetas;
}
}
assert checkLength();
}
private void updateExisting(MetaParam newMeta, List> notFound) {
int hole = -1;
for (int i = 0; i < metas.length; i++) {
MetaParam> m = metas[i];
if (m == null) {
hole = i;
} else if (m.getClass().isAssignableFrom(newMeta.getClass())) {
metas[i] = newMeta;
return;
}
}
++length;
if (hole < 0) {
notFound.add(newMeta);
} else {
metas[hole] = newMeta;
}
}
public void remove(Class type) {
for (int i = 0; i < metas.length; ++i) {
MetaParam> m = metas[i];
if (m != null && m.getClass().isAssignableFrom(type)) {
metas[i] = null;
--length;
assert checkLength();
return;
}
}
}
public void replace(Class type, Function f) {
int hole = -1;
for (int i = 0; i < metas.length; ++i) {
MetaParam> m = metas[i];
if (m == null) {
hole = i;
} else if (m.getClass().isAssignableFrom(type)) {
T newMeta = f.apply((T) m);
assert newMeta == null || type.isInstance(newMeta);
if (newMeta == null) {
--length;
}
metas[i] = newMeta;
assert checkLength();
return;
}
}
T newMeta = f.apply(null);
if (newMeta == null) {
assert checkLength();
return;
} else if (hole < 0) {
++length;
MetaParam>[] newMetas = Arrays.copyOf(metas, metas.length + 1);
newMetas[newMetas.length - 1] = newMeta;
metas = newMetas;
} else {
++length;
metas[hole] = newMeta;
}
assert checkLength();
}
@Override
public String toString() {
return "MetaParams{length=" + length + ", metas=" + Arrays.toString(metas) + '}';
}
/**
* Construct a collection of {@link MetaParam} instances. If multiple
* instances of the same {@link MetaParam} are present, the last value is
* only considered since there can only be one instance per type in the
* {@link MetaParams} collection.
*
* @param metas Meta parameters to create the collection with
* @return a collection of meta parameters without type duplicates
*/
static MetaParams of(MetaParam... metas) {
metas = filterDuplicates(metas);
return new MetaParams(metas, metas.length);
}
static MetaParams of(MetaParam meta) {
assert meta != null;
return new MetaParams(new MetaParam[]{meta}, 1);
}
static MetaParams empty() {
return new MetaParams(EMPTY_ARRAY, 0);
}
private static MetaParam[] filterDuplicates(MetaParam... metas) {
Map, MetaParam>> all = new HashMap<>();
for (MetaParam meta : metas) {
if (meta == null) continue;
all.put(meta.getClass(), meta);
}
return all.values().toArray(new MetaParam[all.size()]);
}
void merge(MetaParams other) {
Map, MetaParam>> all = new HashMap<>();
// Add other's metadata first, because we don't want to override those already present
for (MetaParam meta : other.metas) {
if (meta == null) continue;
all.put(meta.getClass(), meta);
}
for (MetaParam meta : metas) {
if (meta == null) continue;
all.put(meta.getClass(), meta);
}
metas = all.values().toArray(new MetaParam[all.size()]);
}
@Override
public Iterator> iterator() {
return new It();
}
@Override
public Spliterator> spliterator() {
return Spliterators.spliterator(iterator(), length, Spliterator.DISTINCT | Spliterator.NONNULL);
}
public static MetaParams readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
int length = input.readInt();
MetaParam[] metas = new MetaParam[length];
for (int i = 0; i < length; i++)
metas[i] = (MetaParam) input.readObject();
return new MetaParams(metas, metas.length);
}
public static void writeTo(ObjectOutput output, MetaParams params) throws IOException {
output.writeInt(params.size());
for (MetaParam meta : params) output.writeObject(meta);
}
private class It implements Iterator> {
private int i = 0;
public It() {
skipNulls();
}
private void skipNulls() {
while (i < metas.length && metas[i] == null) ++i;
}
@Override
public boolean hasNext() {
return i < metas.length;
}
@Override
public MetaParam> next() {
if (i >= metas.length) {
throw new NoSuchElementException();
}
MetaParam> meta = metas[i++];
skipNulls();
return meta;
}
}
}