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.List; import java.util.Map; import java.util.Optional; import java.util.Set; import org.infinispan.commons.api.functional.MetaParam; import org.infinispan.commons.marshall.AbstractExternalizer; import org.infinispan.commons.util.Experimental; import org.infinispan.commons.util.Util; import org.infinispan.marshall.core.Ids; import net.jcip.annotations.NotThreadSafe; /** * Represents a {@link MetaParam} collection. * * <p>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}. * * <p>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. * * <p>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. * * <p>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}. * * @since 8.0 */ @NotThreadSafe @Experimental public final class MetaParams { private static final MetaParam<?>[] EMPTY_ARRAY = {}; private MetaParam<?>[] metas; private MetaParams(MetaParam<?>[] metas) { this.metas = metas; } public boolean isEmpty() { return metas.length == 0; } public int size() { return metas.length; } public MetaParams copy() { return new MetaParams(Arrays.copyOf(metas, metas.length)); } public <T> Optional<T> find(Class<T> type) { return Optional.ofNullable(findNullable(type)); } @SuppressWarnings("unchecked") private <T> T findNullable(Class<T> type) { for (MetaParam<?> meta : metas) { if (meta.getClass().isAssignableFrom(type)) return (T) meta; } return null; } public void add(MetaParam.Writable meta) { if (metas.length == 0) metas = new MetaParam[]{meta}; else { boolean found = false; for (int i = 0; i < metas.length; i++) { if (metas[i].getClass().isAssignableFrom(meta.getClass())) { metas[i] = meta; found = true; } } if (!found) { MetaParam<?>[] newMetas = Arrays.copyOf(metas, metas.length + 1); newMetas[newMetas.length - 1] = meta; metas = newMetas; } } } public void addMany(MetaParam.Writable... metaParams) { if (metas.length == 0) metas = metaParams; else { List<MetaParam<?>> notFound = new ArrayList<>(metaParams.length); for (MetaParam.Writable newMeta : metaParams) { boolean found = false; for (int i = 0; i < metas.length; i++) { if (metas[i].getClass().isAssignableFrom(newMeta.getClass())) { metas[i] = newMeta; found = true; } } if (!found) notFound.add(newMeta); } if (!notFound.isEmpty()) { List<MetaParam<?>> allMetasList = new ArrayList<>(Arrays.asList(metas)); allMetasList.addAll(notFound); metas = allMetasList.toArray(new MetaParam[metas.length + notFound.size()]); } } } @Override public String toString() { return "MetaParams{" + "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) { return new MetaParams(filterDuplicates(metas)); } static MetaParams of(MetaParam meta) { return new MetaParams(new MetaParam[]{meta}); } static MetaParams empty() { return new MetaParams(EMPTY_ARRAY); } private static MetaParam[] filterDuplicates(MetaParam... metas) { Map<Class<?>, MetaParam<?>> all = new HashMap<>(); for (MetaParam meta : metas) all.put(meta.getClass(), meta); return all.values().toArray(new MetaParam[all.size()]); } void merge(MetaParams other) { Map<Class<?>, MetaParam<?>> all = new HashMap<>(); // Add other's metadata first, because we don't want to override those already present for (MetaParam meta : other.metas) all.put(meta.getClass(), meta); for (MetaParam meta : metas) all.put(meta.getClass(), meta); metas = all.values().toArray(new MetaParam[all.size()]); } public static final class Externalizer extends AbstractExternalizer<MetaParams> { @Override public void writeObject(ObjectOutput oo, MetaParams o) throws IOException { oo.writeInt(o.metas.length); for (Object meta : o.metas) oo.writeObject(meta); } @Override public MetaParams readObject(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 MetaParams.of(metas); } @Override public Set<Class<? extends MetaParams>> getTypeClasses() { return Util.<Class<? extends MetaParams>>asSet(MetaParams.class); } @Override public Integer getId() { return Ids.META_PARAMS; } } }