/*
* LazyMap.java
*
* Created on Jul 30, 2010, 1:08:25 PM
*
* Description: Provides a means to lazily load a Set field.
*
* Copyright (C) Jul 30, 2010, Stephen L. Reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.kb.persistence.lazy;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.jcip.annotations.NotThreadSafe;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.texai.kb.persistence.DistributedRepositoryManager;
import org.texai.kb.persistence.RDFEntityLoader;
import org.texai.kb.persistence.RDFPersistent;
import org.texai.kb.persistence.RDFProperty;
import org.texai.util.TexaiException;
/** Provides a means to lazily load a Set field.
*
* @author reed
*/
@NotThreadSafe
public class LazyMap implements Map, Serializable {
/** the serial version UID */
private static final long serialVersionUID = 1L;
/** the repository name */
private final String repositoryName;
/** the RDF instance */
private final RDFPersistent rdfEntity;
/** the RDF instance field */
private transient Field field;
/** the RDF instance field name */
private final String fieldName;
/** the RDF property */
private final RDFProperty rdfProperty;
/** the predicate values dictionary, predicate --> RDF values */
private final Map<URI, List<Value>> predicateValuesDictionary;
/** the loaded set */
private Map loadedMap;
/** the indicator that the lazy set is currently being loaded */
private boolean isLoading = false;
/** Creates a new instance of LazySet.
*
* @param repositoryConnection the repository connection
* @param rdfEntity the RDF instance
* @param field the RDF instance field
* @param rdfProperty the RDF property
* @param predicateValuesDictionary the predicate values dictionary, predicate --> RDF values
*/
public LazyMap(
final RepositoryConnection repositoryConnection,
final RDFPersistent rdfEntity,
final Field field,
final RDFProperty rdfProperty,
final Map<URI, List<Value>> predicateValuesDictionary) {
super();
//Preconditions
assert repositoryConnection != null : "repositoryConnection must not be null";
assert rdfEntity != null : "rdfInstance must not be null";
assert field != null : "field must not be null";
assert rdfProperty != null : "rdfProperty must not be null";
assert predicateValuesDictionary != null : "predicateValuesDictionary must not be null";
repositoryName = repositoryConnection.getRepository().getDataDir().getName();
this.rdfEntity = rdfEntity;
this.field = field;
this.fieldName = field.getName();
this.rdfProperty = rdfProperty;
this.predicateValuesDictionary = predicateValuesDictionary;
}
/** Gets the loaded map.
*
* @return the loaded map
*/
public synchronized Map getLoadedMap() {
if (isLoading) {
return null; // NOPMD
} else {
loadTheMap();
return loadedMap;
}
}
/** Lazily loads the set. Also replaces the lazy set with the loaded set on the RDF entity so that subsequent
* field value will directly access the set.
*/
@SuppressWarnings("unchecked") // NOPMD
private synchronized void loadTheMap() {
if (!isLoading && loadedMap == null) {
isLoading = true;
final RDFEntityLoader rdfEntityLoader = new RDFEntityLoader();
// obtain a new repository connection from the named repository
final RepositoryConnection repositoryConnection =
DistributedRepositoryManager.getInstance().getRepositoryConnectionForRepositoryName(repositoryName);
if (field == null) {
try {
field = rdfEntity.getClass().getField(fieldName);
} catch (NoSuchFieldException | SecurityException ex) {
throw new TexaiException(ex);
}
}
loadedMap = (Map) rdfEntityLoader.loadLazyRDFEntityField(
repositoryConnection,
rdfEntity,
field,
rdfProperty,
predicateValuesDictionary);
assert loadedMap != null : "loadedSet must not be null";
try {
repositoryConnection.close();
} catch (final RepositoryException ex) {
throw new TexaiException(ex);
} finally {
isLoading = false;
}
}
}
/** Returns a string representation of this object.
*
* @return a string representation of this object
*/
@Override
public String toString() {
String string;
if (loadedMap == null) {
string = "[LazyMap for " + rdfProperty + "]";
} else {
string = loadedMap.toString();
}
return string;
}
// Query Operations
/** Returns the number of key-value mappings in this map. If the
* map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of key-value mappings in this map
*/
@Override
public int size() {
int size;
loadTheMap();
if (isLoading) {
size = 0;
} else {
size = loadedMap.size();
}
return size;
}
/** Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings
*/
@Override
public boolean isEmpty() {
boolean isEmpty;
loadTheMap();
if (isLoading) {
isEmpty = true;
} else {
isEmpty = loadedMap.isEmpty();
}
return isEmpty;
}
/** Returns <tt>true</tt> if this map contains a mapping for the specified
* key. More formally, returns <tt>true</tt> if and only if
* this map contains a mapping for a key <tt>k</tt> such that
* <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be
* at most one such mapping.)
*
* @param key key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key
* @throws ClassCastException if the key is of an inappropriate type for
* this map (optional)
* @throws NullPointerException if the specified key is null and this map
* does not permit null keys (optional)
*/
@Override
public boolean containsKey(final Object key) {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.containsKey(key);
}
}
/** Returns <tt>true</tt> if this map maps one or more keys to the
* specified value. More formally, returns <tt>true</tt> if and only if
* this map contains at least one mapping to a value <tt>v</tt> such that
* <tt>(value==null ? v==null : value.equals(v))</tt>. This operation
* will probably require time linear in the map size for most
* implementations of the <tt>Map</tt> interface.
*
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value
* @throws ClassCastException if the value is of an inappropriate type for
* this map (optional)
* @throws NullPointerException if the specified value is null and this
* map does not permit null values (optional)
*/
@Override
public boolean containsValue(final Object value) {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.containsValue(value);
}
}
/** Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>If this map permits null values, then a return value of
* {@code null} does not <i>necessarily</i> indicate that the map
* contains no mapping for the key; it's also possible that the map
* explicitly maps the key to {@code null}. The {@link #containsKey
* containsKey} operation may be used to distinguish these two cases.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key
* @throws ClassCastException if the key is of an inappropriate type for
* this map (optional)
* @throws NullPointerException if the specified key is null and this map
* does not permit null keys (optional)
*/
@Override
public Object get(final Object key) {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.get(key);
}
}
// Modification Operations
/** Associates the specified value with the specified key in this map
* (optional operation). If the map previously contained a mapping for
* the key, the old value is replaced by the specified value. (A map
* <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only
* if {@link #containsKey(Object) m.containsKey(k)} would return
* <tt>true</tt>.)
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>,
* if the implementation supports <tt>null</tt> values.)
* @throws UnsupportedOperationException if the <tt>put</tt> operation
* is not supported by this map
* @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map
* @throws NullPointerException if the specified key or value is null
* and this map does not permit null keys or values
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
*/
@Override
@SuppressWarnings("unchecked")
public Object put(final Object key, final Object value) {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.put(key, value);
}
}
/** Removes the mapping for a key from this map if it is present
* (optional operation). More formally, if this map contains a mapping
* from key <tt>k</tt> to value <tt>v</tt> such that
* <code>(key==null ? k==null : key.equals(k))</code>, that mapping
* is removed. (The map can contain at most one such mapping.)
*
* <p>Returns the value to which this map previously associated the key,
* or <tt>null</tt> if the map contained no mapping for the key.
*
* <p>If this map permits null values, then a return value of
* <tt>null</tt> does not <i>necessarily</i> indicate that the map
* contained no mapping for the key; it's also possible that the map
* explicitly mapped the key to <tt>null</tt>.
*
* <p>The map will not contain a mapping for the specified key once the
* call returns.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* @throws UnsupportedOperationException if the <tt>remove</tt> operation
* is not supported by this map
* @throws ClassCastException if the key is of an inappropriate type for
* this map (optional)
* @throws NullPointerException if the specified key is null and this
* map does not permit null keys (optional)
*/
@Override
public Object remove(final Object key) {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.remove(key);
}
}
// Bulk Operations
/** Copies all of the mappings from the specified map to this map
* (optional operation). The effect of this call is equivalent to that
* of calling {@link #put(Object,Object) put(k, v)} on this map once
* for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
* specified map. The behavior of this operation is undefined if the
* specified map is modified while the operation is in progress.
*
* @param m mappings to be stored in this map
* @throws UnsupportedOperationException if the <tt>putAll</tt> operation
* is not supported by this map
* @throws ClassCastException if the class of a key or value in the
* specified map prevents it from being stored in this map
* @throws NullPointerException if the specified map is null, or if
* this map does not permit null keys or values, and the
* specified map contains null keys or values
* @throws IllegalArgumentException if some property of a key or value in
* the specified map prevents it from being stored in this map
*/
@Override
@SuppressWarnings("unchecked")
public void putAll(final Map m) {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
loadedMap.putAll(m);
}
}
/** Removes all of the mappings from this map (optional operation).
* The map will be empty after this call returns.
*
* @throws UnsupportedOperationException if the <tt>clear</tt> operation
* is not supported by this map
*/
@Override
public void clear() {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
loadedMap.clear();
}
}
// Views
/** Returns a {@link Set} view of the keys contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation), the results of
* the iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a set view of the keys contained in this map
*/
@Override
public Set keySet() {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.keySet();
}
}
/** Returns a {@link Collection} view of the values contained in this map.
* The collection is backed by the map, so changes to the map are
* reflected in the collection, and vice-versa. If the map is
* modified while an iteration over the collection is in progress
* (except through the iterator's own <tt>remove</tt> operation),
* the results of the iteration are undefined. The collection
* supports element removal, which removes the corresponding
* mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Collection.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
* support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a collection view of the values contained in this map
*/
@Override
public Collection values() {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.values();
}
}
/** Returns a {@link Set} view of the mappings contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation, or through the
* <tt>setValue</tt> operation on a map entry returned by the
* iterator) the results of the iteration are undefined. The set
* supports element removal, which removes the corresponding
* mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and
* <tt>clear</tt> operations. It does not support the
* <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a set view of the mappings contained in this map
*/
@Override
public Set entrySet() {
if (isLoading) {
throw new TexaiException("recursive call while loading lazy map");
} else {
loadTheMap();
return loadedMap.entrySet();
}
}
}