package com.thinkaurelius.faunus; import com.thinkaurelius.titan.diskstorage.ReadBuffer; import com.thinkaurelius.titan.diskstorage.StaticBuffer; import com.thinkaurelius.titan.diskstorage.util.ByteBufferUtil; import com.thinkaurelius.titan.diskstorage.util.ReadByteBuffer; import com.thinkaurelius.titan.graphdb.database.serialize.kryo.KryoSerializer; import com.tinkerpop.blueprints.Element; import com.tinkerpop.blueprints.util.ElementHelper; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; import org.apache.hadoop.io.WritableUtils; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Marko A. Rodriguez (http://markorodriguez.com) */ public abstract class FaunusElement implements Element, WritableComparable<FaunusElement> { static { WritableComparator.define(FaunusElement.class, new Comparator()); } protected static final KryoSerializer serialize = new KryoSerializer(); protected static final Map<String, String> TYPE_MAP = new HashMap<String, String>() { @Override public final String get(final Object object) { final String label = (String) object; final String existing = super.get(label); if (null == existing) { super.put(label, label); return label; } else { return existing; } } }; protected long id; protected Map<String, Object> properties = null; protected List<List<MicroElement>> paths = null; private MicroElement microVersion = null; protected boolean pathEnabled = false; protected long pathCounter = 0; public FaunusElement(final long id) { this.id = id; } protected FaunusElement reuse(final long id) { this.id = id; this.properties = null; this.clearPaths(); return this; } @Override public void remove() throws UnsupportedOperationException { //TODO: should this be supported? throw new UnsupportedOperationException(); } public void enablePath(final boolean enablePath) { this.pathEnabled = enablePath; if (this.pathEnabled) { if (null == this.microVersion) this.microVersion = (this instanceof FaunusVertex) ? new FaunusVertex.MicroVertex(this.id) : new FaunusEdge.MicroEdge(this.id); if (null == this.paths) this.paths = new ArrayList<List<MicroElement>>(); } // TODO: else make pathCounter = paths.size()? } public void addPath(final List<MicroElement> path, final boolean append) throws IllegalStateException { if (this.pathEnabled) { if (append) path.add(this.microVersion); this.paths.add(path); } else { throw new IllegalStateException("Path calculations are not enabled"); } } public void addPaths(final List<List<MicroElement>> paths, final boolean append) throws IllegalStateException { if (this.pathEnabled) { if (append) { for (final List<MicroElement> path : paths) { this.addPath(path, append); } } else this.paths.addAll(paths); } else { throw new IllegalStateException("Path calculations are not enabled"); } } public List<List<MicroElement>> getPaths() throws IllegalStateException { if (this.pathEnabled) return this.paths; else throw new IllegalStateException("Path calculations are not enabled"); } public void getPaths(final FaunusElement element, final boolean append) { if (this.pathEnabled) { this.addPaths(element.getPaths(), append); } else { this.pathCounter = this.pathCounter + element.pathCount(); } } public long incrPath(final long amount) throws IllegalStateException { if (this.pathEnabled) throw new IllegalStateException("Path calculations are enabled -- use addPath()"); else this.pathCounter = this.pathCounter + amount; return this.pathCounter; } public boolean hasPaths() { if (this.pathEnabled) return !this.paths.isEmpty(); else return this.pathCounter > 0; } public void clearPaths() { if (this.pathEnabled) { this.paths = new ArrayList<List<MicroElement>>(); this.microVersion = (this instanceof FaunusVertex) ? new FaunusVertex.MicroVertex(this.id) : new FaunusEdge.MicroEdge(this.id); } else this.pathCounter = 0; } public long pathCount() { if (this.pathEnabled) return this.paths.size(); else return this.pathCounter; } public void startPath() { if (this.pathEnabled) { this.clearPaths(); final List<MicroElement> startPath = new ArrayList<MicroElement>(); startPath.add(this.microVersion); this.paths.add(startPath); } else { this.pathCounter = 1; } } public void setProperty(final String key, final Object value) { ElementHelper.validateProperty(this, key, value); if (key.equals(Tokens._COUNT)) throw new IllegalArgumentException("_count is a reserved property"); if (null == this.properties) this.properties = new HashMap<String, Object>(); this.properties.put(TYPE_MAP.get(key), value); } public <T> T removeProperty(final String key) { return null == this.properties ? null : (T) this.properties.remove(key); } public <T> T getProperty(final String key) { if (key.equals(Tokens._COUNT)) return (T) Long.valueOf(this.pathCount()); return null == this.properties ? null : (T) this.properties.get(key); } public Set<String> getPropertyKeys() { return null == this.properties ? (Set) Collections.emptySet() : this.properties.keySet(); } public Map<String, Object> getProperties() { return null == this.properties ? this.properties = new HashMap<String, Object>() : this.properties; } public Object getId() { return this.id; } public long getIdAsLong() { return this.id; } public void readFields(final DataInput in) throws IOException { this.id = WritableUtils.readVLong(in); this.pathEnabled = in.readBoolean(); if (this.pathEnabled) { this.paths = ElementPaths.readFields(in); this.microVersion = (this instanceof FaunusVertex) ? new FaunusVertex.MicroVertex(this.id) : new FaunusEdge.MicroEdge(this.id); } else this.pathCounter = WritableUtils.readVLong(in); this.properties = ElementProperties.readFields(in); } public void write(final DataOutput out) throws IOException { WritableUtils.writeVLong(out, this.id); out.writeBoolean(this.pathEnabled); if (this.pathEnabled) ElementPaths.write(this.paths, out); else WritableUtils.writeVLong(out, this.pathCounter); ElementProperties.write(this.properties, out); } @Override public boolean equals(final Object other) { return this.getClass().equals(other.getClass()) && this.id == ((FaunusElement) other).getIdAsLong(); } @Override public int hashCode() { return ((Long) this.id).hashCode(); } public int compareTo(final FaunusElement other) { return new Long(this.id).compareTo((Long) other.getId()); } public static class ElementProperties { public static void write(final Map<String, Object> properties, final DataOutput out) throws IOException { if (null == properties || properties.size() == 0) WritableUtils.writeVInt(out, 0); else { WritableUtils.writeVInt(out, properties.size()); final com.thinkaurelius.titan.graphdb.database.serialize.DataOutput o = serialize.getDataOutput(128, true); for (final Map.Entry<String, Object> entry : properties.entrySet()) { o.writeObject(entry.getKey(), String.class); o.writeClassAndObject(entry.getValue()); } final StaticBuffer buffer = o.getStaticBuffer(); WritableUtils.writeVInt(out, buffer.length()); out.write(ByteBufferUtil.getArray(buffer.asByteBuffer())); } } public static Map<String, Object> readFields(final DataInput in) throws IOException { final int numberOfProperties = WritableUtils.readVInt(in); if (numberOfProperties == 0) return null; else { final Map<String, Object> properties = new HashMap<String, Object>(); byte[] bytes = new byte[WritableUtils.readVInt(in)]; in.readFully(bytes); final ReadBuffer buffer = new ReadByteBuffer(bytes); for (int i = 0; i < numberOfProperties; i++) { final String key = serialize.readObject(buffer, String.class); final Object valueObject = serialize.readClassAndObject(buffer); properties.put(TYPE_MAP.get(key), valueObject); } return properties; } } } public static class ElementPaths { public static void write(final List<List<MicroElement>> paths, final DataOutput out) throws IOException { if (null == paths) { WritableUtils.writeVInt(out, 0); } else { WritableUtils.writeVInt(out, paths.size()); for (final List<MicroElement> path : paths) { WritableUtils.writeVInt(out, path.size()); for (MicroElement element : path) { if (element instanceof FaunusVertex.MicroVertex) out.writeChar('v'); else out.writeChar('e'); WritableUtils.writeVLong(out, element.getId()); } } } } public static List<List<MicroElement>> readFields(final DataInput in) throws IOException { int pathsSize = WritableUtils.readVInt(in); if (pathsSize == 0) return new ArrayList<List<MicroElement>>(); else { final List<List<MicroElement>> paths = new ArrayList<List<MicroElement>>(pathsSize); for (int i = 0; i < pathsSize; i++) { int pathSize = WritableUtils.readVInt(in); final List<MicroElement> path = new ArrayList<MicroElement>(pathSize); for (int j = 0; j < pathSize; j++) { char type = in.readChar(); if (type == 'v') path.add(new FaunusVertex.MicroVertex(WritableUtils.readVLong(in))); else path.add(new FaunusEdge.MicroEdge(WritableUtils.readVLong(in))); } paths.add(path); } return paths; } } } public static class Comparator extends WritableComparator { public Comparator() { super(FaunusElement.class); } @Override public int compare(final byte[] element1, final int start1, final int length1, final byte[] element2, final int start2, final int length2) { try { return Long.valueOf(readVLong(element1, start1)).compareTo(readVLong(element2, start2)); } catch (IOException e) { return -1; } } @Override public int compare(final WritableComparable a, final WritableComparable b) { if (a instanceof FaunusElement && b instanceof FaunusElement) return ((Long) (((FaunusElement) a).getIdAsLong())).compareTo(((FaunusElement) b).getIdAsLong()); else return super.compare(a, b); } } public static abstract class MicroElement { protected final long id; public MicroElement(final long id) { this.id = id; } public long getId() { return this.id; } public int hashCode() { return Long.valueOf(this.id).hashCode(); } public boolean equals(final Object object) { return (object.getClass().equals(this.getClass()) && this.id == ((MicroElement) object).getId()); } } }