/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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 com.cinchapi.concourse.server.plugin.data; import io.atomix.catalyst.buffer.Buffer; import java.lang.ref.SoftReference; import java.util.AbstractMap; import java.util.Collections; import java.util.Map; import java.util.Set; import javax.annotation.concurrent.NotThreadSafe; import com.cinchapi.concourse.server.plugin.io.PluginSerializable; import com.google.common.base.MoreObjects; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * A data collection that associates the intersection of two keys, an * {@code entity} and {@code attribute} with a {@link Set} of values. * <p> * A {@link Dataset} may be sparse, where the number of total attributes is * vast, but the number that will apply to any given single entity is relatively * small. In this case, the Dataset is considered a sparse matrix, and this * class lends itself to space-efficient storage. * </p> * * @author Jeff Nelson */ @NotThreadSafe public abstract class Dataset<E, A, V> extends AbstractMap<E, Map<A, Set<V>>> implements PluginSerializable, Insertable<E, A, V> { /** * A mapping from each attribute to the inverted (e.g. index-oriented) view * of the index. */ private final Map<A, Map<V, Set<E>>> inverted; /** * A mapping from each entity to a primary (e.g. row-oriented) view of that * entity's data. Since this class is primarily used as a warehouse to * quickly produce {@link #invert(Object) inverted} views, each entity's * data is wrapped in a {@link SoftReference}, so care must be taken to * regenerate the row-oriented view on the fly, if necessary. */ private final Map<E, SoftReference<Map<A, Set<V>>>> rows; /** * The map returned from {@link #invertNullSafe(Object)} when the specified * attribute doesn't exist. */ private final Map<V, Set<E>> nullSafeInvertedMap = TrackingLinkedHashMultimap .create(); /** * Construct a new instance. */ public Dataset() { this.inverted = Maps.newHashMap(); this.rows = Maps.newHashMap(); } /** * Remove the association between {@code attribute} and {@code value} within * the {@code entity}. * * @param entity the entity * @param attribute the attribute * @param value the value * @return {@code true} if the associated is removed */ public boolean delete(E entity, A attribute, V value) { Map<V, Set<E>> index = inverted.get(attribute); if(index != null) { Set<E> entities = index.get(value); if(entities != null && entities.remove(entity)) { if(entities.isEmpty()) { index.remove(value); } if(index.isEmpty()) { inverted.remove(attribute); } SoftReference<Map<A, Set<V>>> ref = rows.get(entity); Map<A, Set<V>> row = null; if(ref != null && (row = ref.get()) != null) { Set<V> values = row.get(attribute); values.remove(value); if(values.isEmpty()) { row.remove(attribute); } if(row.isEmpty()) { rows.remove(entity); } } return true; } else { return false; } } else { return false; } } @Override public void deserialize(Buffer buffer) { while (buffer.hasRemaining()) { A attribute = deserializeAttribute(buffer); int count = buffer.readInt(); for (int i = 0; i < count; ++i) { V value = deserializeValue(buffer); Set<E> entities = deserializeEntities(buffer); entities.forEach((entity) -> { insert(entity, attribute, value); }); } } } @Override public Set<Entry<E, Map<A, Set<V>>>> entrySet() { Set<Entry<E, Map<A, Set<V>>>> entrySet = Sets.newLinkedHashSet(); for (Entry<E, SoftReference<Map<A, Set<V>>>> entry : rows.entrySet()) { E entity = entry.getKey(); Map<A, Set<V>> row = entry.getValue().get(); if(row == null) { row = get(entity); } entrySet.add(new SimpleEntry<E, Map<A, Set<V>>>(entity, row)); } return entrySet; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if(obj instanceof Dataset) { return inverted.equals(((Dataset<E, A, V>) obj).inverted); } else { return false; } } /** * Return all the values that are mapped from {@code attribute} within the * {@code entity}. * * @param entity the entity * @param attribute the attribute * @return the set of values that are mapped */ public Set<V> get(E entity, A attribute) { SoftReference<Map<A, Set<V>>> ref = rows.get(entity); Map<A, Set<V>> row = null; if(ref != null && (row = ref.get()) != null) { return MoreObjects.firstNonNull(row.get(attribute), Collections.emptySet()); } else { Set<V> values = Sets.newLinkedHashSet(); Map<V, Set<E>> index = MoreObjects.firstNonNull( inverted.get(attribute), Collections.<V, Set<E>> emptyMap()); for (Entry<V, Set<E>> entry : index.entrySet()) { Set<E> entities = entry.getValue(); if(entities.contains(entity)) { V value = entry.getKey(); values.add(value); } } return values; } } @SuppressWarnings("unchecked") @Override public Map<A, Set<V>> get(Object entity) { SoftReference<Map<A, Set<V>>> sref = rows.get(entity); Map<A, Set<V>> row = null; if(sref == null || (sref != null && (row = sref.get()) == null)) { row = Maps.newHashMap(); for (Entry<A, Map<V, Set<E>>> entry : inverted.entrySet()) { A attr = entry.getKey(); for (Entry<V, Set<E>> index : entry.getValue().entrySet()) { Set<E> entities = index.getValue(); if(entities.contains(entity)) { V value = index.getKey(); Set<V> stored = row.get(attr); if(stored == null) { stored = Sets.newLinkedHashSet(); row.put(attr, stored); } stored.add(value); } } } if(!row.isEmpty()) { // NOTE: Not worried about ClassCastException here because the // non-emptiness of the map guarantees that some data was added // for the key using the #put method, which performs type // checking rows.put((E) entity, new SoftReference<Map<A, Set<V>>>(row)); } } return row; } @Override public int hashCode() { return inverted.hashCode(); } /** * Add an association between {@code attribute} and {@code value} within the * {@code entity}. * * @param entity the entity * @param attribute the attribute * @param value the value * @return {@code true} if the association can be added because it didn't * previously exist */ @Override public boolean insert(E entity, A attribute, V value) { Map<V, Set<E>> index = inverted.get(attribute); if(index == null) { index = createInvertedMultimap(); inverted.put(attribute, index); } Set<E> entities = index.get(value); if(entities == null) { entities = Sets.newHashSet(); index.put(value, entities); } entities = index.get(value); // NOTE: necessary to #get the inner set // again because TrackingMultimap uses // special internal collections if(entities.add(entity)) { SoftReference<Map<A, Set<V>>> ref = rows.get(entity); if(ref == null) { ref = new SoftReference<>(get(entity)); rows.put(entity, ref); } // Attempt to also add the data to the row-oriented view, if its // currently being held in memory Map<A, Set<V>> row = null; if(ref != null && (row = ref.get()) != null) { Set<V> values = row.get(attribute); if(values == null) { values = Sets.newLinkedHashSet(); row.put(attribute, values); values = row.get(attribute); } values.add(value); } return true; } else { return false; } } /** * Return an <em>inverted</em> view of the entire dataset. * <p> * An inverted view maps each attribute to a mapping from each contained * value to the set of entities in which that value is contained for the * attribute. * </p> * * @return an inverted version of the entire dataset */ public Map<A, Map<V, Set<E>>> invert() { return inverted; } /** * Return an <em>inverted</em> view of the data contained for * {@code attribute}. * <p> * For an attribute, an inverted view maps each contained value to the set * of entities in which that value is associated with the attribute. * </p> * * @param attribute the attribute * @return an inverted version of the data for {@code attribute} */ public Map<V, Set<E>> invert(A attribute) { return inverted.get(attribute); } @Override public Map<A, Set<V>> put(E entity, Map<A, Set<V>> mappings) { Map<A, Set<V>> current = get(entity); for (Entry<A, Set<V>> entry : mappings.entrySet()) { A attribute = entry.getKey(); Set<V> values = entry.getValue(); for (V value : values) { insert(entity, attribute, value); } } return current; } @SuppressWarnings("unchecked") @Override public Map<A, Set<V>> remove(Object entity) { Map<A, Set<V>> row = Maps.newHashMap(get(entity)); // make a copy to // prevent CME for (Entry<A, Set<V>> entry : row.entrySet()) { A attribute = entry.getKey(); Set<V> values = entry.getValue(); for (V value : values) { delete((E) entity, attribute, value); } } return null; } @Override public void serialize(Buffer buffer) { invert().forEach((attribute, map) -> { serializeAttribute(attribute, buffer); buffer.writeInt(map.size()); map.forEach((value, entities) -> { serializeValue(value, buffer); serializeEntities(entities, buffer); }); }); } @Override public String toString() { return inverted.toString(); } /** * The subclass should return the proper {@link Map} from value to a * {@link Set} of entities. * * @return the proper inverted multimap */ protected abstract Map<V, Set<E>> createInvertedMultimap(); /** * Read an attribute from the {@code buffer}. * * @param buffer the buffer containing the serialized data * @return the read attribute */ protected abstract A deserializeAttribute(Buffer buffer); /** * Read a {@link Set} of entities from the {@code buffer}. * * @param buffer the buffer containing the serialized data * @return the read entities */ protected abstract Set<E> deserializeEntities(Buffer buffer); /** * Read a value from the {@code buffer}. * * @param buffer the buffer containing the serialized data * @return the read value */ protected abstract V deserializeValue(Buffer buffer); /** * Return an <em>inverted</em> view of the data contained for * {@code attribute}. If the attribute doesn't exist, return an empty map. * <p> * For an attribute, an inverted view maps each contained value to the set * of entities in which that value is associated with the attribute. * </p> * * @param attribute the attribute * @return an inverted version of the data for {@code attribute} */ protected Map<V, Set<E>> invertNullSafe(A attribute) { return MoreObjects.firstNonNull(inverted.get(attribute), nullSafeInvertedMap); } /** * Write an attribute to the {@code buffer}. * * @param buffer the buffer containing the serialized data */ protected abstract void serializeAttribute(A attribute, Buffer buffer); /** * Write a {@link Set} of entities to the {@code buffer}. * * @param buffer the buffer containing the serialized data */ protected abstract void serializeEntities(Set<E> entity, Buffer buffer); /** * Write a value to the {@code buffer}. * * @param buffer the buffer containing the serialized data */ protected abstract void serializeValue(V value, Buffer buffer); }