package eu.fbk.knowledgestore.internal.rdf;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import com.google.common.base.Objects;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.datatypes.XMLDatatypeUtil;
import org.openrdf.model.impl.BNodeImpl;
import org.openrdf.model.impl.BooleanLiteralImpl;
import org.openrdf.model.impl.CalendarLiteralImpl;
import org.openrdf.model.impl.ContextStatementImpl;
import org.openrdf.model.impl.LiteralImpl;
import org.openrdf.model.impl.StatementImpl;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.XMLSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class CompactValueFactory implements ValueFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(CompactValueFactory.class);
private static final DatatypeFactory DATATYPE_FACTORY;
private static final CompactValueFactory VALUE_FACTORY;
static {
try {
DATATYPE_FACTORY = DatatypeFactory.newInstance();
VALUE_FACTORY = new CompactValueFactory();
} catch (final Throwable ex) {
throw new Error("Unexpected exception (!): " + ex.getMessage(), ex);
}
}
private final String bnodePrefix;
private final AtomicLong bnodeCounter;
private CompactValueFactory() {
final UUID uuid = UUID.randomUUID();
final StringBuilder builder = new StringBuilder(12);
long num = Math.abs(uuid.getLeastSignificantBits());
builder.append(charFor(num % 52));
num = num / 52;
for (int i = 0; i < 5; ++i) {
builder.append(charFor(num % 62));
num = num / 62;
}
num = Math.abs(uuid.getMostSignificantBits());
for (int i = 0; i < 6; ++i) {
builder.append(charFor(num % 62));
num = num / 62;
}
this.bnodePrefix = builder.toString();
this.bnodeCounter = new AtomicLong(0L);
}
private static char charFor(final long num) {
if (num < 26) {
return (char) (65 + num);
} else if (num < 52) {
return (char) (71 + num);
} else if (num < 62) {
return (char) (num - 4);
} else {
return 'x';
}
}
public static CompactValueFactory getInstance() {
return VALUE_FACTORY;
}
@SuppressWarnings("unchecked")
@Nullable
public <T> T normalize(@Nullable final T object) {
if (object instanceof Statement) {
if (!(object instanceof StatementImpl) && !(object instanceof ContextStatementImpl)) {
final Statement s = (Statement) object;
return s.getContext() == null ? (T) createStatement(s.getSubject(),
s.getPredicate(), s.getObject()) : (T) createStatement(s.getSubject(),
s.getPredicate(), s.getObject(), s.getContext());
}
} else if (object instanceof URI) {
if (!(object instanceof URIImpl)) {
return (T) createURI(((URI) object).stringValue());
}
} else if (object instanceof BNode) {
if (!(object instanceof BNodeImpl)) {
return (T) createBNode(((BNode) object).getID());
}
} else if (object instanceof Literal) {
if (!(object instanceof StringLiteral) && !(object instanceof NumberLiteral)
&& !(object instanceof BooleanLiteralImpl)
&& !(object instanceof CalendarLiteralImpl)) {
final Literal l = (Literal) object;
return l.getLanguage() != null ? (T) createLiteral(l.getLabel(), l.getLanguage())
: (T) createLiteral(l.getLabel(), l.getDatatype());
}
}
return object;
}
@Override
public URI createURI(final String uri) {
return new URIImpl(uri);
}
@Override
public URI createURI(final String namespace, final String localName) {
return new URIImpl(namespace + localName);
}
@Override
public BNode createBNode() {
return new BNodeImpl(this.bnodePrefix
+ Long.toString(this.bnodeCounter.getAndIncrement(), 32));
}
@Override
public BNode createBNode(final String nodeID) {
return new BNodeImpl(nodeID);
}
@Override
public Literal createLiteral(final String label) {
return new StringLiteral(label, (URI) null);
}
@Override
public Literal createLiteral(final String label, final String language) {
return new StringLiteral(label, language);
}
@Override
public Literal createLiteral(final String label, final URI datatype) {
try {
if (datatype == null) {
return new StringLiteral(label, (String) null);
} else if (datatype.equals(XMLSchema.STRING)) {
return new StringLiteral(label, XMLSchema.STRING);
} else if (datatype.equals(XMLSchema.BOOLEAN)) {
final boolean value = XMLDatatypeUtil.parseBoolean(label);
return value ? BooleanLiteralImpl.TRUE : BooleanLiteralImpl.FALSE;
} else if (datatype.equals(XMLSchema.INT)) {
return new LongLiteral(XMLSchema.INT, XMLDatatypeUtil.parseInt(label));
} else if (datatype.equals(XMLSchema.LONG)) {
return new LongLiteral(XMLSchema.LONG, XMLDatatypeUtil.parseLong(label));
} else if (datatype.equals(XMLSchema.SHORT)) {
return new LongLiteral(XMLSchema.SHORT, XMLDatatypeUtil.parseShort(label));
} else if (datatype.equals(XMLSchema.BYTE)) {
return new LongLiteral(XMLSchema.BYTE, XMLDatatypeUtil.parseByte(label));
} else if (datatype.equals(XMLSchema.DOUBLE)) {
return new DoubleLiteral(XMLSchema.DOUBLE, XMLDatatypeUtil.parseDouble(label));
} else if (datatype.equals(XMLSchema.FLOAT)) {
return new DoubleLiteral(XMLSchema.FLOAT, XMLDatatypeUtil.parseFloat(label));
} else if (datatype.equals(XMLSchema.DATETIME) || datatype.equals(XMLSchema.DATE)
|| datatype.equals(XMLSchema.TIME) || datatype.equals(XMLSchema.GYEARMONTH)
|| datatype.equals(XMLSchema.GMONTHDAY) || datatype.equals(XMLSchema.GYEAR)
|| datatype.equals(XMLSchema.GMONTH) || datatype.equals(XMLSchema.GDAY)) {
return createLiteral(XMLDatatypeUtil.parseCalendar(label));
} else if (datatype.equals(XMLSchema.DECIMAL)) {
return new BigDecimalLiteral(datatype, XMLDatatypeUtil.parseDecimal(label));
} else if (datatype.equals(XMLSchema.INTEGER)
|| datatype.equals(XMLSchema.NON_NEGATIVE_INTEGER)
|| datatype.equals(XMLSchema.POSITIVE_INTEGER)
|| datatype.equals(XMLSchema.NEGATIVE_INTEGER)) {
return new BigIntegerLiteral(datatype, XMLDatatypeUtil.parseInteger(label));
} else {
return new StringLiteral(label, datatype);
}
} catch (final Throwable ex) {
LOGGER.warn("Illegal literal: '" + label + "'^^<" + datatype + "> (dropping datatype)");
return createLiteral(label);
}
}
@Override
public Literal createLiteral(final boolean value) {
return value ? BooleanLiteralImpl.TRUE : BooleanLiteralImpl.FALSE;
}
@Override
public Literal createLiteral(final byte value) {
return new LongLiteral(XMLSchema.BYTE, value);
}
@Override
public Literal createLiteral(final short value) {
return new LongLiteral(XMLSchema.SHORT, value);
}
@Override
public Literal createLiteral(final int value) {
return new LongLiteral(XMLSchema.INT, value);
}
@Override
public Literal createLiteral(final long value) {
return new LongLiteral(XMLSchema.LONG, value);
}
@Override
public Literal createLiteral(final float value) {
return new DoubleLiteral(XMLSchema.FLOAT, value);
}
@Override
public Literal createLiteral(final double value) {
return new DoubleLiteral(XMLSchema.DOUBLE, value);
}
@Override
public Literal createLiteral(final XMLGregorianCalendar calendar) {
return new CalendarLiteralImpl(calendar);
}
@Override
public Literal createLiteral(final Date date) {
final GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(date);
final XMLGregorianCalendar xmlCalendar = DATATYPE_FACTORY
.newXMLGregorianCalendar(calendar);
return new CalendarLiteralImpl(xmlCalendar);
}
@Override
public Statement createStatement(final Resource subject, final URI predicate,
final Value object) {
return new StatementImpl(subject, predicate, object);
}
@Override
public Statement createStatement(final Resource subject, final URI predicate,
final Value object, final Resource context) {
return context == null ? new StatementImpl(subject, predicate, object)
: new ContextStatementImpl(subject, predicate, object, context);
}
private static final class StringLiteral extends LiteralImpl {
private static final long serialVersionUID = 1L;
StringLiteral(final String label, final String language) {
super(label, language == null ? null : language.intern());
}
StringLiteral(final String label, final URI datatype) {
super(label, datatype);
}
}
private abstract static class NumberLiteral implements Literal {
private static final long serialVersionUID = 1L;
private final URI datatype;
NumberLiteral(final URI datatype) {
this.datatype = datatype;
}
abstract Number getNumber();
boolean equalNumber(final Literal literal) {
return getNumber().equals(((NumberLiteral) literal).getNumber());
}
@Override
public String getLabel() {
return stringValue();
}
@Override
public String getLanguage() {
return null;
}
@Override
public URI getDatatype() {
return this.datatype;
}
@Override
public String stringValue() {
return getNumber().toString();
}
@Override
public byte byteValue() {
return getNumber().byteValue();
}
@Override
public short shortValue() {
return getNumber().shortValue();
}
@Override
public int intValue() {
return getNumber().intValue();
}
@Override
public long longValue() {
return getNumber().longValue();
}
@Override
public float floatValue() {
return getNumber().floatValue();
}
@Override
public double doubleValue() {
return getNumber().doubleValue();
}
@Override
public boolean booleanValue() {
return XMLDatatypeUtil.parseBoolean(getLabel());
}
@Override
public BigInteger integerValue() {
return XMLDatatypeUtil.parseInteger(getLabel());
}
@Override
public BigDecimal decimalValue() {
return XMLDatatypeUtil.parseDecimal(getLabel());
}
@Override
public XMLGregorianCalendar calendarValue() {
return XMLDatatypeUtil.parseCalendar(getLabel());
}
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (!(object instanceof Literal)) {
return false;
}
final Literal other = (Literal) object;
if (object.getClass() == this.getClass()) {
return this.datatype.equals(other.getDatatype()) && equalNumber(other);
}
if (object instanceof NumberLiteral || object instanceof BooleanLiteralImpl
|| object instanceof CalendarLiteralImpl || object instanceof StringLiteral) {
return false;
}
return other.getLanguage() == null && this.datatype.equals(other.getDatatype())
&& stringValue().equals(other.stringValue());
}
@Override
public int hashCode() {
return Objects.hashCode(getNumber(), getDatatype());
}
@Override
public String toString() {
final String label = getLabel();
final StringBuilder builder = new StringBuilder(label.length() * 2);
builder.append('"');
builder.append(label);
builder.append('"');
builder.append('^').append('^').append('<');
builder.append(this.datatype.toString());
builder.append('>');
return builder.toString();
}
}
private static final class LongLiteral extends NumberLiteral {
private static final long serialVersionUID = 1L;
private final long value;
LongLiteral(final URI datatype, final long value) {
super(datatype);
this.value = value;
}
@Override
Number getNumber() {
return this.value;
}
@Override
public String stringValue() {
return Long.toString(this.value);
}
@Override
public byte byteValue() {
return (byte) this.value;
}
@Override
public short shortValue() {
return (short) this.value;
}
@Override
public int intValue() {
return (int) this.value;
}
@Override
public long longValue() {
return this.value;
}
@Override
public float floatValue() {
return this.value;
}
@Override
public double doubleValue() {
return this.value;
}
@Override
public BigInteger integerValue() {
return BigInteger.valueOf(this.value);
}
@Override
public BigDecimal decimalValue() {
return BigDecimal.valueOf(this.value);
}
}
private static final class DoubleLiteral extends NumberLiteral {
private static final long serialVersionUID = 1L;
private final double value;
DoubleLiteral(final URI datatype, final double value) {
super(datatype);
this.value = value;
}
@Override
Number getNumber() {
return this.value;
}
@Override
public String stringValue() {
return Double.toString(this.value);
}
@Override
public byte byteValue() {
return (byte) this.value;
}
@Override
public short shortValue() {
return (short) this.value;
}
@Override
public int intValue() {
return (int) this.value;
}
@Override
public long longValue() {
return (long) this.value;
}
@Override
public float floatValue() {
return (float) this.value;
}
@Override
public double doubleValue() {
return this.value;
}
@Override
public BigDecimal decimalValue() {
return BigDecimal.valueOf(this.value);
}
}
private static final class BigIntegerLiteral extends NumberLiteral {
private static final long serialVersionUID = 1L;
private final BigInteger value;
BigIntegerLiteral(final URI datatype, final BigInteger value) {
super(datatype);
this.value = value;
}
@Override
Number getNumber() {
return this.value;
}
@Override
public BigInteger integerValue() {
return this.value;
}
@Override
public BigDecimal decimalValue() {
return new BigDecimal(this.value);
}
}
private static final class BigDecimalLiteral extends NumberLiteral {
private static final long serialVersionUID = 1L;
private final BigDecimal value;
BigDecimalLiteral(final URI datatype, final BigDecimal value) {
super(datatype);
this.value = value;
}
@Override
Number getNumber() {
return this.value;
}
@Override
public BigInteger integerValue() {
return this.value.toBigInteger();
}
@Override
public BigDecimal decimalValue() {
return this.value;
}
}
}