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

com.google.common.collect.StandardTable Maven / Gradle / Ivy

/*
 * Copyright (C) 2008 The Guava Authors
 *
 * 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.google.common.collect;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.alwaysTrue;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Maps.safeContainsKey;
import static com.google.common.collect.Maps.safeGet;

import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Maps.ViewCachingAbstractMap;
import com.google.common.collect.Sets.ImprovedAbstractSet;

import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.Nullable;

/**
 * {@link Table} implementation backed by a map that associates row keys with
 * column key / value secondary maps. This class provides rapid access to
 * records by the row key alone or by both keys, but not by just the column key.
 *
 * 

The views returned by {@link #column}, {@link #columnKeySet()}, and {@link * #columnMap()} have iterators that don't support {@code remove()}. Otherwise, * all optional operations are supported. Null row keys, columns keys, and * values are not supported. * *

Lookups by row key are often faster than lookups by column key, because * the data is stored in a {@code Map>}. A method call like {@code * column(columnKey).get(rowKey)} still runs quickly, since the row key is * provided. However, {@code column(columnKey).size()} takes longer, since an * iteration across all row keys occurs. * *

Note that this implementation is not synchronized. If multiple threads * access this table concurrently and one of the threads modifies the table, it * must be synchronized externally. * * @author Jared Levy */ @GwtCompatible class StandardTable extends AbstractTable implements Serializable { @GwtTransient final Map> backingMap; @GwtTransient final Supplier> factory; StandardTable(Map> backingMap, Supplier> factory) { this.backingMap = backingMap; this.factory = factory; } // Accessors @Override public boolean contains( @Nullable Object rowKey, @Nullable Object columnKey) { return rowKey != null && columnKey != null && super.contains(rowKey, columnKey); } @Override public boolean containsColumn(@Nullable Object columnKey) { if (columnKey == null) { return false; } for (Map map : backingMap.values()) { if (safeContainsKey(map, columnKey)) { return true; } } return false; } @Override public boolean containsRow(@Nullable Object rowKey) { return rowKey != null && safeContainsKey(backingMap, rowKey); } @Override public boolean containsValue(@Nullable Object value) { return value != null && super.containsValue(value); } @Override public V get(@Nullable Object rowKey, @Nullable Object columnKey) { return (rowKey == null || columnKey == null) ? null : super.get(rowKey, columnKey); } @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public int size() { int size = 0; for (Map map : backingMap.values()) { size += map.size(); } return size; } // Mutators @Override public void clear() { backingMap.clear(); } private Map getOrCreate(R rowKey) { Map map = backingMap.get(rowKey); if (map == null) { map = factory.get(); backingMap.put(rowKey, map); } return map; } @Override public V put(R rowKey, C columnKey, V value) { checkNotNull(rowKey); checkNotNull(columnKey); checkNotNull(value); return getOrCreate(rowKey).put(columnKey, value); } @Override public V remove( @Nullable Object rowKey, @Nullable Object columnKey) { if ((rowKey == null) || (columnKey == null)) { return null; } Map map = safeGet(backingMap, rowKey); if (map == null) { return null; } V value = map.remove(columnKey); if (map.isEmpty()) { backingMap.remove(rowKey); } return value; } private Map removeColumn(Object column) { Map output = new LinkedHashMap(); Iterator>> iterator = backingMap.entrySet().iterator(); while (iterator.hasNext()) { Entry> entry = iterator.next(); V value = entry.getValue().remove(column); if (value != null) { output.put(entry.getKey(), value); if (entry.getValue().isEmpty()) { iterator.remove(); } } } return output; } private boolean containsMapping( Object rowKey, Object columnKey, Object value) { return value != null && value.equals(get(rowKey, columnKey)); } /** Remove a row key / column key / value mapping, if present. */ private boolean removeMapping(Object rowKey, Object columnKey, Object value) { if (containsMapping(rowKey, columnKey, value)) { remove(rowKey, columnKey); return true; } return false; } // Views /** * Abstract set whose {@code isEmpty()} returns whether the table is empty and * whose {@code clear()} clears all table mappings. */ private abstract class TableSet extends ImprovedAbstractSet { @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public void clear() { backingMap.clear(); } } /** * {@inheritDoc} * *

The set's iterator traverses the mappings for the first row, the * mappings for the second row, and so on. * *

Each cell is an immutable snapshot of a row key / column key / value * mapping, taken at the time the cell is returned by a method call to the * set or its iterator. */ @Override public Set> cellSet() { return super.cellSet(); } @Override Iterator> cellIterator() { return new CellIterator(); } private class CellIterator implements Iterator> { final Iterator>> rowIterator = backingMap.entrySet().iterator(); Entry> rowEntry; Iterator> columnIterator = Iterators.emptyModifiableIterator(); @Override public boolean hasNext() { return rowIterator.hasNext() || columnIterator.hasNext(); } @Override public Cell next() { if (!columnIterator.hasNext()) { rowEntry = rowIterator.next(); columnIterator = rowEntry.getValue().entrySet().iterator(); } Entry columnEntry = columnIterator.next(); return Tables.immutableCell( rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); } @Override public void remove() { columnIterator.remove(); if (rowEntry.getValue().isEmpty()) { rowIterator.remove(); } } } @Override public Map row(R rowKey) { return new Row(rowKey); } class Row extends ViewCachingAbstractMap { final R rowKey; Row(R rowKey) { this.rowKey = checkNotNull(rowKey); } Map backingRowMap; Map backingRowMap() { return (backingRowMap == null || (backingRowMap.isEmpty() && backingMap.containsKey(rowKey))) ? backingRowMap = computeBackingRowMap() : backingRowMap; } Map computeBackingRowMap() { return backingMap.get(rowKey); } // Call this every time we perform a removal. void maintainEmptyInvariant() { if (backingRowMap() != null && backingRowMap.isEmpty()) { backingMap.remove(rowKey); backingRowMap = null; } } @Override public boolean containsKey(Object key) { Map backingRowMap = backingRowMap(); return (key != null && backingRowMap != null) && Maps.safeContainsKey(backingRowMap, key); } @Override public V get(Object key) { Map backingRowMap = backingRowMap(); return (key != null && backingRowMap != null) ? Maps.safeGet(backingRowMap, key) : null; } @Override public V put(C key, V value) { checkNotNull(key); checkNotNull(value); if (backingRowMap != null && !backingRowMap.isEmpty()) { return backingRowMap.put(key, value); } return StandardTable.this.put(rowKey, key, value); } @Override public V remove(Object key) { Map backingRowMap = backingRowMap(); if (backingRowMap == null) { return null; } V result = Maps.safeRemove(backingRowMap, key); maintainEmptyInvariant(); return result; } @Override public void clear() { Map backingRowMap = backingRowMap(); if (backingRowMap != null) { backingRowMap.clear(); } maintainEmptyInvariant(); } @Override protected Set> createEntrySet() { return new RowEntrySet(); } private final class RowEntrySet extends Maps.EntrySet { @Override Map map() { return Row.this; } @Override public int size() { Map map = backingRowMap(); return (map == null) ? 0 : map.size(); } @Override public Iterator> iterator() { final Map map = backingRowMap(); if (map == null) { return Iterators.emptyModifiableIterator(); } final Iterator> iterator = map.entrySet().iterator(); return new Iterator>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Entry next() { final Entry entry = iterator.next(); return new ForwardingMapEntry() { @Override protected Entry delegate() { return entry; } @Override public V setValue(V value) { return super.setValue(checkNotNull(value)); } @Override public boolean equals(Object object) { // TODO(user): identify why this affects GWT tests return standardEquals(object); } }; } @Override public void remove() { iterator.remove(); maintainEmptyInvariant(); } }; } } } /** * {@inheritDoc} * *

The returned map's views have iterators that don't support * {@code remove()}. */ @Override public Map column(C columnKey) { return new Column(columnKey); } private class Column extends ViewCachingAbstractMap { final C columnKey; Column(C columnKey) { this.columnKey = checkNotNull(columnKey); } @Override public V put(R key, V value) { return StandardTable.this.put(key, columnKey, value); } @Override public V get(Object key) { return StandardTable.this.get(key, columnKey); } @Override public boolean containsKey(Object key) { return StandardTable.this.contains(key, columnKey); } @Override public V remove(Object key) { return StandardTable.this.remove(key, columnKey); } /** * Removes all {@code Column} mappings whose row key and value satisfy the * given predicate. */ boolean removeFromColumnIf(Predicate> predicate) { boolean changed = false; Iterator>> iterator = backingMap.entrySet().iterator(); while (iterator.hasNext()) { Entry> entry = iterator.next(); Map map = entry.getValue(); V value = map.get(columnKey); if (value != null && predicate.apply(Maps.immutableEntry(entry.getKey(), value))) { map.remove(columnKey); changed = true; if (map.isEmpty()) { iterator.remove(); } } } return changed; } @Override Set> createEntrySet() { return new EntrySet(); } private class EntrySet extends ImprovedAbstractSet> { @Override public Iterator> iterator() { return new EntrySetIterator(); } @Override public int size() { int size = 0; for (Map map : backingMap.values()) { if (map.containsKey(columnKey)) { size++; } } return size; } @Override public boolean isEmpty() { return !containsColumn(columnKey); } @Override public void clear() { removeFromColumnIf(alwaysTrue()); } @Override public boolean contains(Object o) { if (o instanceof Entry) { Entry entry = (Entry) o; return containsMapping(entry.getKey(), columnKey, entry.getValue()); } return false; } @Override public boolean remove(Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; return removeMapping(entry.getKey(), columnKey, entry.getValue()); } return false; } @Override public boolean retainAll(Collection c) { return removeFromColumnIf(not(in(c))); } } private class EntrySetIterator extends AbstractIterator> { final Iterator>> iterator = backingMap.entrySet().iterator(); @Override protected Entry computeNext() { while (iterator.hasNext()) { final Entry> entry = iterator.next(); if (entry.getValue().containsKey(columnKey)) { return new AbstractMapEntry() { @Override public R getKey() { return entry.getKey(); } @Override public V getValue() { return entry.getValue().get(columnKey); } @Override public V setValue(V value) { return entry.getValue().put(columnKey, checkNotNull(value)); } }; } } return endOfData(); } } @Override Set createKeySet() { return new KeySet(); } private class KeySet extends Maps.KeySet { KeySet() { super(Column.this); } @Override public boolean contains(Object obj) { return StandardTable.this.contains(obj, columnKey); } @Override public boolean remove(Object obj) { return StandardTable.this.remove(obj, columnKey) != null; } @Override public boolean retainAll(final Collection c) { return removeFromColumnIf(Maps.keyPredicateOnEntries(not(in(c)))); } } @Override Collection createValues() { return new Values(); } private class Values extends Maps.Values { Values() { super(Column.this); } @Override public boolean remove(Object obj) { return obj != null && removeFromColumnIf(Maps.valuePredicateOnEntries(equalTo(obj))); } @Override public boolean removeAll(final Collection c) { return removeFromColumnIf(Maps.valuePredicateOnEntries(in(c))); } @Override public boolean retainAll(final Collection c) { return removeFromColumnIf(Maps.valuePredicateOnEntries(not(in(c)))); } } } @Override public Set rowKeySet() { return rowMap().keySet(); } private transient Set columnKeySet; /** * {@inheritDoc} * *

The returned set has an iterator that does not support {@code remove()}. * *

The set's iterator traverses the columns of the first row, the * columns of the second row, etc., skipping any columns that have * appeared previously. */ @Override public Set columnKeySet() { Set result = columnKeySet; return (result == null) ? columnKeySet = new ColumnKeySet() : result; } private class ColumnKeySet extends TableSet { @Override public Iterator iterator() { return createColumnKeyIterator(); } @Override public int size() { return Iterators.size(iterator()); } @Override public boolean remove(Object obj) { if (obj == null) { return false; } boolean changed = false; Iterator> iterator = backingMap.values().iterator(); while (iterator.hasNext()) { Map map = iterator.next(); if (map.keySet().remove(obj)) { changed = true; if (map.isEmpty()) { iterator.remove(); } } } return changed; } @Override public boolean removeAll(Collection c) { checkNotNull(c); boolean changed = false; Iterator> iterator = backingMap.values().iterator(); while (iterator.hasNext()) { Map map = iterator.next(); // map.keySet().removeAll(c) can throw a NPE when map is a TreeMap with // natural ordering and c contains a null. if (Iterators.removeAll(map.keySet().iterator(), c)) { changed = true; if (map.isEmpty()) { iterator.remove(); } } } return changed; } @Override public boolean retainAll(Collection c) { checkNotNull(c); boolean changed = false; Iterator> iterator = backingMap.values().iterator(); while (iterator.hasNext()) { Map map = iterator.next(); if (map.keySet().retainAll(c)) { changed = true; if (map.isEmpty()) { iterator.remove(); } } } return changed; } @Override public boolean contains(Object obj) { return containsColumn(obj); } } /** * Creates an iterator that returns each column value with duplicates * omitted. */ Iterator createColumnKeyIterator() { return new ColumnKeyIterator(); } private class ColumnKeyIterator extends AbstractIterator { // Use the same map type to support TreeMaps with comparators that aren't // consistent with equals(). final Map seen = factory.get(); final Iterator> mapIterator = backingMap.values().iterator(); Iterator> entryIterator = Iterators.emptyIterator(); @Override protected C computeNext() { while (true) { if (entryIterator.hasNext()) { Entry entry = entryIterator.next(); if (!seen.containsKey(entry.getKey())) { seen.put(entry.getKey(), entry.getValue()); return entry.getKey(); } } else if (mapIterator.hasNext()) { entryIterator = mapIterator.next().entrySet().iterator(); } else { return endOfData(); } } } } /** * {@inheritDoc} * *

The collection's iterator traverses the values for the first row, * the values for the second row, and so on. */ @Override public Collection values() { return super.values(); } private transient Map> rowMap; @Override public Map> rowMap() { Map> result = rowMap; return (result == null) ? rowMap = createRowMap() : result; } Map> createRowMap() { return new RowMap(); } class RowMap extends ViewCachingAbstractMap> { @Override public boolean containsKey(Object key) { return containsRow(key); } // performing cast only when key is in backing map and has the correct type @SuppressWarnings("unchecked") @Override public Map get(Object key) { return containsRow(key) ? row((R) key) : null; } @Override public Map remove(Object key) { return (key == null) ? null : backingMap.remove(key); } @Override protected Set>> createEntrySet() { return new EntrySet(); } class EntrySet extends TableSet>> { @Override public Iterator>> iterator() { return Maps.asMapEntryIterator(backingMap.keySet(), new Function>() { @Override public Map apply(R rowKey) { return row(rowKey); } }); } @Override public int size() { return backingMap.size(); } @Override public boolean contains(Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; return entry.getKey() != null && entry.getValue() instanceof Map && Collections2.safeContains(backingMap.entrySet(), entry); } return false; } @Override public boolean remove(Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; return entry.getKey() != null && entry.getValue() instanceof Map && backingMap.entrySet().remove(entry); } return false; } } } private transient ColumnMap columnMap; @Override public Map> columnMap() { ColumnMap result = columnMap; return (result == null) ? columnMap = new ColumnMap() : result; } private class ColumnMap extends ViewCachingAbstractMap> { // The cast to C occurs only when the key is in the map, implying that it // has the correct type. @SuppressWarnings("unchecked") @Override public Map get(Object key) { return containsColumn(key) ? column((C) key) : null; } @Override public boolean containsKey(Object key) { return containsColumn(key); } @Override public Map remove(Object key) { return containsColumn(key) ? removeColumn(key) : null; } @Override public Set>> createEntrySet() { return new ColumnMapEntrySet(); } @Override public Set keySet() { return columnKeySet(); } @Override Collection> createValues() { return new ColumnMapValues(); } class ColumnMapEntrySet extends TableSet>> { @Override public Iterator>> iterator() { return Maps.asMapEntryIterator(columnKeySet(), new Function>() { @Override public Map apply(C columnKey) { return column(columnKey); } }); } @Override public int size() { return columnKeySet().size(); } @Override public boolean contains(Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; if (containsColumn(entry.getKey())) { // The cast to C occurs only when the key is in the map, implying // that it has the correct type. @SuppressWarnings("unchecked") C columnKey = (C) entry.getKey(); return get(columnKey).equals(entry.getValue()); } } return false; } @Override public boolean remove(Object obj) { if (contains(obj)) { Entry entry = (Entry) obj; removeColumn(entry.getKey()); return true; } return false; } @Override public boolean removeAll(Collection c) { /* * We can't inherit the normal implementation (which calls * Sets.removeAllImpl(Set, *Collection*) because, under some * circumstances, it attempts to call columnKeySet().iterator().remove, * which is unsupported. */ checkNotNull(c); return Sets.removeAllImpl(this, c.iterator()); } @Override public boolean retainAll(Collection c) { checkNotNull(c); boolean changed = false; for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { if (!c.contains(Maps.immutableEntry(columnKey, column(columnKey)))) { removeColumn(columnKey); changed = true; } } return changed; } } private class ColumnMapValues extends Maps.Values> { ColumnMapValues() { super(ColumnMap.this); } @Override public boolean remove(Object obj) { for (Entry> entry : ColumnMap.this.entrySet()) { if (entry.getValue().equals(obj)) { removeColumn(entry.getKey()); return true; } } return false; } @Override public boolean removeAll(Collection c) { checkNotNull(c); boolean changed = false; for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { if (c.contains(column(columnKey))) { removeColumn(columnKey); changed = true; } } return changed; } @Override public boolean retainAll(Collection c) { checkNotNull(c); boolean changed = false; for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { if (!c.contains(column(columnKey))) { removeColumn(columnKey); changed = true; } } return changed; } } } private static final long serialVersionUID = 0; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy