LoadingSheetData.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.ostrichemulators.semtool.poi.main;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.log4j.Logger;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.ValueFactoryImpl;
/**
* A class to encapsulate relationship loading sheet information.
*
* @author ryan
*/
public class LoadingSheetData {
private static final Logger log = Logger.getLogger( LoadingSheetData.class );
private String subjectType;
private String objectType;
private String relname;
private boolean sbjErr = false;
private boolean objErr = false;
private boolean relErr = false;
private final Set<String> propErrs = new HashSet<>();
// property name => datatype lookup
private final Map<String, URI> propcache = new LinkedHashMap<>();
private final Set<String> proplink = new HashSet<>();
private final Set<String> napcache = new HashSet<>();
private final List<LoadingNodeAndPropertyValues> data = new ArrayList<>();
private final String tabname;
protected LoadingSheetData( String tabtitle, String type ) {
this( tabtitle, type, new HashMap<>() );
}
protected LoadingSheetData( String tabtitle, String type, Map<String, URI> props ) {
this( tabtitle, type, null, null, props );
}
protected LoadingSheetData( String tabtitle, String sType, String oType,
String relname ) {
this( tabtitle, sType, oType, relname, new HashMap<>() );
}
protected LoadingSheetData( String tabtitle, String sType, String oType,
String relname, Map<String, URI> props ) {
subjectType = sType;
tabname = tabtitle;
this.objectType = oType;
this.relname = relname;
propcache.putAll( props );
}
public int rows() {
return data.size();
}
public boolean hasErrors() {
DataIterator di = iterator();
while ( di.hasNext() ) {
LoadingNodeAndPropertyValues nap = di.next();
if ( nap.hasError() ) {
return true;
}
}
return false;
}
/**
* Removes a single node from the list. This may be an expensive operation.
*
* @param nap
*/
public void remove( LoadingNodeAndPropertyValues nap ) {
data.remove( nap );
}
/**
* Removes nodes from the list. This may be an expensive operation.
*
* @param naps
*/
public void removeAll( Collection<LoadingNodeAndPropertyValues> naps ) {
data.removeAll( naps );
}
public DataIterator iterator() {
return new DataIteratorImpl();
}
public boolean hasSubjectTypeError() {
return sbjErr;
}
public void setSubjectTypeIsError( boolean sbjErr ) {
this.sbjErr = sbjErr;
}
public boolean hasObjectTypeError() {
return objErr;
}
public void setObjectTypeIsError( boolean objErr ) {
this.objErr = objErr;
}
public boolean hasRelationError() {
return relErr;
}
public void setRelationIsError( boolean relErr ) {
this.relErr = relErr;
}
public void setPropertyIsError( String errprop, boolean iserr ) {
if ( propcache.containsKey( errprop ) ) {
if ( iserr ) {
propErrs.add( errprop );
}
else {
propErrs.remove( errprop );
}
}
}
public void setSubjectType( String subjectType ) {
this.subjectType = subjectType;
}
public void setObjectType( String objectType ) {
this.objectType = objectType;
}
public void setRelname( String relname ) {
this.relname = relname;
}
public boolean propertyIsError( String prop ) {
return propErrs.contains( prop );
}
public boolean hasModelErrors() {
return ( relErr || sbjErr || objErr || !propErrs.isEmpty() );
}
/**
* Sets the URI for a given property
*
* @param prop the property name
* @param type the datatype it should be
*/
public void setPropertyDataType( String prop, URI type ) {
propcache.put( prop, type );
}
public boolean hasPropertyDataType( String prop ) {
return ( propcache.containsKey( prop )
? null != propcache.get( prop ) : false );
}
public URI getPropertyDataType( String prop ) {
return propcache.get( prop );
}
public String getObjectType() {
return objectType;
}
public String getRelname() {
return relname;
}
public String getName() {
return tabname;
}
public void addProperty( String prop ) {
addProperty( prop, null );
}
public void addProperty( String prop, URI type ) {
propcache.put( prop, type );
}
public Collection<String> getProperties() {
return propcache.keySet();
}
public boolean hasProperties() {
return !propcache.isEmpty();
}
public final void addProperties( Collection<String> props ) {
for ( String s : props ) {
addProperty( s );
}
}
/**
* Releases any resources used by this class. Technically, this function must
* only be called when {@link #isMemOnly() } returns false, but it's good
* practice to always call it
*/
public void release() {
}
public void finishLoading() {
}
/**
* Clears any stored loading data
*/
public void clear() {
data.clear();
propcache.clear();
}
public void setProperties( Map<String, URI> props ) {
propcache.clear();
propcache.putAll( props );
}
public Map<String, URI> getPropertiesAndDataTypes() {
return new HashMap<>( propcache );
}
public String getSubjectType() {
return subjectType;
}
protected void cacheNapLabel( String label ) {
napcache.add( label );
}
protected boolean isNapLabelCached( String s ) {
return napcache.contains( s );
}
protected boolean isPropLabelCached( String s ) {
return propcache.containsKey( s );
}
/**
* Gets this instance's node and property data. Changes made to the returned
* collection are NOT propagated to the internal copy
*
* @return the internal data
*/
public List<LoadingNodeAndPropertyValues> getData() {
return new ArrayList<>( data );
}
protected void commit() {
}
/**
* Notifies subclasses when a new NAP is added
*
* @param nap
*/
protected void added( LoadingNodeAndPropertyValues nap ) {
}
public void add( LoadingNodeAndPropertyValues nap ) {
data.add( nap );
added( nap );
// add this NAP's label to our cache, if we have it (we should)
cacheNapLabel( nap.getSubject() );
}
public LoadingNodeAndPropertyValues add( String slabel ) {
LoadingNodeAndPropertyValues nap = new LoadingNodeAndPropertyValues( slabel );
add( nap );
return nap;
}
public LoadingNodeAndPropertyValues add( String slabel, Map<String, Value> props ) {
LoadingNodeAndPropertyValues nap = add( slabel );
nap.putAll( props );
return nap;
}
public LoadingNodeAndPropertyValues add( String slabel, String olabel ) {
LoadingNodeAndPropertyValues nap
= new LoadingNodeAndPropertyValues( slabel, olabel );
cacheNapLabel( olabel );
add( nap );
return nap;
}
/**
* Adds a new nap to the list. This is the preferred way to construct the nap
*
* @param slabel
* @param olabel
* @param props
* @return
*/
public LoadingNodeAndPropertyValues add( String slabel, String olabel,
Map<String, Value> props ) {
LoadingNodeAndPropertyValues nap
= new LoadingNodeAndPropertyValues( slabel, olabel, props );
add( nap );
return nap;
}
public List<String> getHeaders() {
List<String> heads = new ArrayList<>();
heads.add( getSubjectType() );
if ( isRel() ) {
heads.add( getObjectType() );
}
heads.addAll( propcache.keySet() );
return heads;
}
/**
* Sets the headers of this loading sheet, including subject, object, and all
* properties. Note that the relation name cannot be changed with this
* function, nor can the type of loading sheet it is. The actual number of
* headers cannot be changed, either, but if attempted, will silently fail.
*
* @param newheads The new headers.
*/
public void setHeaders( List<String> newheads ) {
if ( newheads.size() != getHeaders().size() ) {
log.error( "cannot change header size" );
return;
}
String st = newheads.get( 0 );
setSubjectType( st );
int firstPropCol = 1;
if ( isRel() ) {
String ot = newheads.get( 1 );
setObjectType( ot );
firstPropCol = 2;
}
// properties are a little tough because we cannot change the key of the map,
// and if we add a new key, it'll mess up the iteration order
// so we'll create a new map, and add the columns in the right order
// also, worry about propagating errors
String[] oldkeys = propcache.keySet().toArray( new String[0] );
int col = 0;
Map<String, URI> newtypes = new LinkedHashMap<>();
Set<String> newerrors = new HashSet<>();
ListIterator<String> propit = newheads.listIterator( firstPropCol );
while ( propit.hasNext() ) {
String newkey = propit.next();
String oldkey = oldkeys[col++];
URI proptype = propcache.get( oldkey );
newtypes.put( newkey, proptype );
if ( this.propertyIsError( oldkey ) ) {
newerrors.add( newkey );
}
}
setProperties( newtypes );
propErrs.clear();
propErrs.addAll( newerrors );
}
public boolean isRel() {
return ( null != objectType );
}
public boolean isEmpty() {
return data.isEmpty();
}
@Override
public String toString() {
return getName() + ( isRel() ? "(rel)" : "(node)" ) + " with "
+ rows() + " naps";
}
/**
* Looks through all properties of this sheet to see if any are actually a
* link to the {@link #subjectType} of one of the given sheets
*
* @param others
*/
public void findPropertyLinks( Collection<LoadingSheetData> others ) {
proplink.clear();
for ( String propname : propcache.keySet() ) {
for ( LoadingSheetData lsd : others ) {
if ( lsd.getSubjectType().equals( propname ) ) {
proplink.add( propname );
}
}
}
}
public boolean isLink( String propname ) {
return proplink.contains( propname );
}
public boolean isMemOnly() {
return true;
}
public static LoadingSheetData copyHeadersOf( LoadingSheetData model ) {
LoadingSheetData lsd;
if ( model.isRel() ) {
lsd = new LoadingSheetData( model.getName(), model.getSubjectType(),
model.getObjectType(), model.getRelname(), model.getPropertiesAndDataTypes() );
}
else {
lsd = new LoadingSheetData( model.getName(), model.getSubjectType(),
model.getPropertiesAndDataTypes() );
}
if ( model.hasModelErrors() ) {
lsd.setSubjectTypeIsError( model.hasSubjectTypeError() );
lsd.setObjectTypeIsError( model.hasObjectTypeError() );
lsd.setRelationIsError( model.hasRelationError() );
for ( String p : model.getProperties() ) {
lsd.setPropertyIsError( p, model.propertyIsError( p ) );
}
}
return lsd;
}
public static LoadingSheetData nodesheet( String subject ) {
return nodesheet( subject, subject );
}
public static LoadingSheetData nodesheet( String tabname, String subject ) {
return nodesheet( tabname, subject, true );
}
public static LoadingSheetData nodesheet( String tabname, String subject,
boolean inMemOnly ) {
if ( !inMemOnly ) {
try {
return new DiskBackedLoadingSheetData( tabname, subject );
}
catch ( IOException ioe ) {
log.warn( "could not create file-based loading sheet", ioe );
// just fall through
}
}
return new LoadingSheetData( tabname, subject );
}
public static LoadingSheetData relsheet( String subject, String object,
String relname ) {
return relsheet( subject, object, relname, true );
}
public static LoadingSheetData relsheet( String subject, String object,
String relname, boolean inMemOnly ) {
StringBuilder sb = new StringBuilder( subject ).append( "-" );
sb.append( relname ).append( "-" ).append( object );
return relsheet( sb.toString(), subject, object, relname, inMemOnly );
}
public static LoadingSheetData relsheet( String tabname, String subject,
String object, String relname ) {
return relsheet( tabname, subject, object, relname, true );
}
public static LoadingSheetData relsheet( String tabname, String subject,
String object, String relname, boolean inMemOnly ) {
if ( !inMemOnly ) {
try {
return new DiskBackedLoadingSheetData( tabname, subject, object, relname );
}
catch ( IOException ioe ) {
log.warn( "could not create file-based loading sheet", ioe );
// just fall through
}
}
return new LoadingSheetData( tabname, subject, object, relname );
}
/**
* A Node And Properties (NAP) object
*/
public class LoadingNodeAndPropertyValues extends HashMap<String, Value> {
private String subject;
private String object;
private boolean subjectIsError = false;
private boolean objectIsError = false;
public LoadingNodeAndPropertyValues( String subj ) {
this( subj, new HashMap<>() );
}
public LoadingNodeAndPropertyValues( String subject, String object ) {
this.subject = subject;
this.object = object;
}
/**
* If at all possible, this is the ctor to use
*
* @param subject
* @param object
* @param props
*/
public LoadingNodeAndPropertyValues( String subject, String object,
Map<String, Value> props ) {
super( props );
this.subject = subject;
this.object = object;
}
public LoadingNodeAndPropertyValues( String subject, Map<String, Value> props ) {
this( subject, null, props );
}
public String getSubject() {
return subject;
}
public String getObject() {
return object;
}
public String getSheetName() {
return LoadingSheetData.this.getName();
}
public String getRelname() {
return LoadingSheetData.this.getRelname();
}
public void setSubject( String s ) {
subject = s;
}
public void setObject( String s ) {
object = s;
}
public boolean hasError() {
return ( subjectIsError || objectIsError );
}
public void setSubjectIsError( boolean iserr ) {
subjectIsError = iserr;
}
public void setObjectIsError( boolean iserr ) {
objectIsError = iserr;
}
public boolean isSubjectError() {
return subjectIsError;
}
public boolean isObjectError() {
return objectIsError;
}
public String getSubjectType() {
return LoadingSheetData.this.subjectType;
}
public String getObjectType() {
return LoadingSheetData.this.objectType;
}
public Value[] convertToValueArray( ValueFactory vf ) {
int arrsize = propcache.size() + ( isRel() ? 2 : 1 );
Value vals[] = new Value[arrsize];
vals[0] = vf.createLiteral( getSubject() );
int i = 0;
if ( isRel() ) {
vals[1] = vf.createLiteral( getObject() );
i = 1;
}
for ( String prop : propcache.keySet() ) {
vals[++i] = ( containsKey( prop ) ? get( prop ) : null );
}
return vals;
}
public boolean hasProperty( URI needle, Map<String, String> namespaces ) {
ValueFactory vf = new ValueFactoryImpl();
for ( String head : keySet() ) {
if ( head.contains( ":" ) ) {
int idx = head.indexOf( ":" );
String headns = head.substring( 0, idx );
String localname = head.substring( idx + 1 );
if ( namespaces.containsKey( headns ) ) {
URI uri = vf.createURI( namespaces.get( headns ), localname );
if ( uri.equals( needle ) ) {
return true;
}
}
}
}
return false;
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + Objects.hashCode( this.subject );
hash = 31 * hash + Objects.hashCode( this.object );
hash = 31 * hash + Objects.hashCode( this.keySet() );
return hash;
}
@Override
public boolean equals( Object obj ) {
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final LoadingNodeAndPropertyValues other = (LoadingNodeAndPropertyValues) obj;
if ( !Objects.equals( this.subject, other.subject ) ) {
return false;
}
return Objects.equals( this.object, other.object );
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder( subject );
if ( this.subjectIsError ) {
sb.append( "<e>" );
}
if ( isRel() ) {
sb.append( ";" );
sb.append( object );
if ( this.objectIsError ) {
sb.append( "<e>" );
}
sb.append( "; " ).append( relname );
}
return sb.toString();
}
}
public interface DataIterator extends Iterator<LoadingNodeAndPropertyValues> {
/**
* Releases any unused resources. This method will be called automatically
* if the iterator is exhausted, but must be called manually if the iterator
* is discarded before the iteration is complete
*/
public void release();
}
public class DataIteratorImpl implements DataIterator {
private final Iterator<LoadingNodeAndPropertyValues> iter = data.iterator();
@Override
public void release() {
// does nothing by default
}
@Override
public boolean hasNext() {
boolean hasnext = iter.hasNext();
// if we're totally empty, release anything we're still holding onto
if ( !hasnext ) {
release();
}
return hasnext;
}
@Override
public LoadingNodeAndPropertyValues next() {
return iter.next();
}
@Override
public void remove() {
iter.remove();
}
}
}