/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.jena.propertytable.impl;
import java.util.* ;
import java.util.Map.Entry;
import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.atlas.iterator.IteratorConcat;
import org.apache.jena.ext.com.google.common.collect.HashMultimap;
import org.apache.jena.ext.com.google.common.collect.SetMultimap ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.propertytable.Column;
import org.apache.jena.propertytable.PropertyTable;
import org.apache.jena.propertytable.Row;
import org.apache.jena.util.iterator.ExtendedIterator ;
import org.apache.jena.util.iterator.NullIterator ;
import org.apache.jena.util.iterator.WrappedIterator ;
/**
* A PropertyTable Implementation using HashMap.
* It contains PSO and POS indexes.
*
*/
public class PropertyTableHashMapImpl implements PropertyTable {
private Map<Node, Column> columnIndex; // Maps property Node key to Column
private List<Column> columnList; // Stores the list of columns in the table
private Map<Node, Row> rowIndex; // Maps the subject Node key to Row.
private List<Row> rowList; // Stores the list of rows in the table
// PSO index
// Maps column Node to (subject Node, value) pairs
private Map<Node, Map<Node, Node>> valueIndex;
// POS index
// Maps column Node to (value, subject Node) pairs
private Map<Node, SetMultimap<Node, Node>> valueReverseIndex;
PropertyTableHashMapImpl() {
columnIndex = new HashMap<Node, Column>();
columnList = new ArrayList<Column>();
rowIndex = new HashMap<Node, Row>();
rowList = new ArrayList<Row>();
valueIndex = new HashMap<Node, Map<Node, Node>>();
valueReverseIndex = new HashMap<Node, SetMultimap<Node, Node>>();
}
@Override
public ExtendedIterator<Triple> getTripleIterator() {
// use PSO index to scan all the table (slow)
IteratorConcat<Triple> iter = new IteratorConcat<Triple>();
for (Column column : getColumns()) {
iter.add(getTripleIterator(column));
}
return WrappedIterator.create(Iter.distinct(iter));
}
@Override
public ExtendedIterator<Triple> getTripleIterator(Column column) {
// use PSO index directly (fast)
if (column == null || column.getColumnKey() == null)
throw new NullPointerException("column is null");
ArrayList<Triple> triples = new ArrayList<Triple>();
Map<Node, Node> values = valueIndex.get(column.getColumnKey());
for (Entry<Node, Node> entry : values.entrySet()) {
Node subject = entry.getKey();
Node value = entry.getValue();
triples.add(Triple.create(subject, column.getColumnKey(), value));
}
return WrappedIterator.create(triples.iterator());
}
@Override
public ExtendedIterator<Triple> getTripleIterator(Node value) {
// use POS index ( O(n), n= column count )
if (value == null)
throw new NullPointerException("value is null");
IteratorConcat<Triple> iter = new IteratorConcat<Triple>();
for (Column column : this.getColumns()) {
ExtendedIterator<Triple> eIter = getTripleIterator(column,value);
iter.add(eIter);
}
return WrappedIterator.create(Iter.distinct(iter));
}
@Override
public ExtendedIterator<Triple> getTripleIterator(Column column, Node value) {
// use POS index directly (fast)
if (column == null || column.getColumnKey() == null)
throw new NullPointerException("column is null");
if (value == null)
throw new NullPointerException("value is null");
Node p = column.getColumnKey();
final SetMultimap<Node, Node> valueToSubjectMap = valueReverseIndex.get(p);
if ( valueToSubjectMap == null )
return NullIterator.instance() ;
final Set<Node> subjects = valueToSubjectMap.get(value);
ArrayList<Triple> triples = new ArrayList<Triple>();
for (Node subject : subjects) {
triples.add(Triple.create(subject, p, value));
}
return WrappedIterator.create(triples.iterator());
}
@Override
public ExtendedIterator<Triple> getTripleIterator(Row row) {
// use PSO index ( O(n), n= column count )
if (row == null || row.getRowKey() == null)
throw new NullPointerException("row is null");
ArrayList<Triple> triples = new ArrayList<Triple>();
for (Column column : getColumns()) {
Node value = row.getValue(column);
triples.add(Triple.create(row.getRowKey(), column.getColumnKey(), value));
}
return WrappedIterator.create(triples.iterator());
}
@Override
public Collection<Column> getColumns() {
return columnList;
}
@Override
public Column getColumn(Node p) {
if (p == null)
throw new NullPointerException("column node is null");
return columnIndex.get(p);
}
@Override
public Column createColumn(Node p) {
if (p == null)
throw new NullPointerException("column node is null");
if (columnIndex.containsKey(p))
throw new IllegalArgumentException("column already exists: '"
+ p.toString());
columnIndex.put(p, new ColumnImpl(this, p));
columnList.add(columnIndex.get(p));
valueIndex.put(p, new HashMap<Node, Node>());
valueReverseIndex.put(p, HashMultimap.create());
return getColumn(p);
}
@Override
public Row getRow(final Node s) {
if (s == null)
throw new NullPointerException("subject node is null");
Row row = rowIndex.get(s);
return row;
}
@Override
public Row createRow(final Node s){
Row row = this.getRow(s);
if (row != null)
return row;
row = new InternalRow(s);
rowIndex.put(s, row);
rowList.add(row);
return row;
}
@Override
public List<Row> getAllRows() {
return rowList;
}
@Override
public List<Node> getColumnValues(Column column) {
if (column == null || column.getColumnKey() == null)
throw new NullPointerException("column is null");
Map<Node, Node> values = valueIndex.get(column.getColumnKey());
List<Node> list = new ArrayList<Node>(values.size());
list.addAll(values.values());
return list;
}
@Override
public Collection<Row> getMatchingRows(Column column, Node value) {
if (column == null || column.getColumnKey() == null)
throw new NullPointerException("column is null");
if (value == null)
throw new NullPointerException("value is null");
Node p = column.getColumnKey();
final SetMultimap<Node, Node> valueToSubjectMap = valueReverseIndex.get(p);
if ( valueToSubjectMap == null )
return Collections.emptyList() ;
final Set<Node> subjects = valueToSubjectMap.get(value);
if ( subjects == null )
return Collections.emptyList() ;
final ArrayList<Row> matchingRows = new ArrayList<Row>();
for (Node subject : subjects) {
matchingRows.add(this.getRow(subject));
}
return matchingRows;
}
private final void setX(final Node s, final Node p, final Node value) {
if (p == null)
throw new NullPointerException("column Node must not be null.");
if (value == null)
throw new NullPointerException("value must not be null.");
Map<Node, Node> subjectToValueMap = valueIndex.get(p);
if (!columnIndex.containsKey(p) || subjectToValueMap == null)
throw new IllegalArgumentException("column: '" + p
+ "' does not yet exist.");
Node oldValue = subjectToValueMap.get(s);
subjectToValueMap.put(s, value);
addToReverseMap(p, s, oldValue, value);
}
private void addToReverseMap(final Node p, final Node s, final Node oldValue, final Node value) {
final SetMultimap<Node, Node> valueToSubjectMap = valueReverseIndex.get(p);
if ( valueToSubjectMap == null )
return ;
valueToSubjectMap.remove(oldValue, s);
valueToSubjectMap.put(value, s);
}
private void unSetX(final Node s, final Node p) {
final Map<Node, Node> subjectToValueMap = valueIndex.get(p);
if (!columnIndex.containsKey(p) || subjectToValueMap == null)
throw new IllegalArgumentException("column: '" + p
+ "' does not yet exist.");
final Node value = subjectToValueMap.get(s);
if (value == null)
return;
subjectToValueMap.remove(s);
removeFromReverseMap(p, s, value);
}
private void removeFromReverseMap(final Node p, final Node s,
final Node value) {
final SetMultimap<Node, Node> valueTokeysMap = valueReverseIndex.get(p);
if ( valueTokeysMap == null )
return ;
valueTokeysMap.remove(s, value);
}
private Node getX(final Node s, final Node p) {
final Map<Node, Node> subjectToValueMap = valueIndex.get(p);
if (!columnIndex.containsKey(p) || subjectToValueMap == null)
throw new IllegalArgumentException("column: '" + p
+ "' does not yet exist.");
return subjectToValueMap.get(s);
}
private final class InternalRow implements Row {
private final Node key;
InternalRow(final Node key) {
this.key = key;
}
@Override
public void setValue(Column column, Node value) {
if (value == null)
unSetX(key, column.getColumnKey());
else
setX(key, column.getColumnKey(), value);
}
@Override
public Node getValue(Column column) {
return getX(key, column.getColumnKey());
}
@Override
public Node getValue(Node columnKey) {
return getX(key, columnKey);
}
@Override
public PropertyTable getTable() {
return PropertyTableHashMapImpl.this;
}
@Override
public Node getRowKey() {
return key;
}
@Override
public Collection<Column> getColumns() {
// TODO Auto-generated method stub
return PropertyTableHashMapImpl.this.getColumns();
}
@Override
public ExtendedIterator<Triple> getTripleIterator() {
ArrayList<Triple> triples = new ArrayList<Triple>();
for (Column column : getColumns()) {
Node value = this.getValue(column);
triples.add(Triple.create(key, column.getColumnKey(), value));
}
return WrappedIterator.create(triples.iterator());
}
}
}