All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jkschneider.pappus.MapToVertexMapper Maven / Gradle / Ivy

The newest version!
package com.github.jkschneider.pappus;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;

import org.jibx.schema.codegen.extend.DefaultNameConverter;
import org.jibx.schema.codegen.extend.NameConverter;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.TransactionalGraph;
import com.tinkerpop.blueprints.Vertex;

public class MapToVertexMapper {
	String typeProperty = "_type";
	String indexProperty = "_index";
	String hashProperty = "_hash";
	String keyProperty = "_key";
	
	Cache hashToVertexId = CacheBuilder.newBuilder().maximumSize(8192).build();
	
	NameConverter nameTools = new DefaultNameConverter();
	RecursiveMapDecorator hasher = new RecursiveMapDecorator();
	Graph g;
	
	public MapToVertexMapper(Graph g) {
		this.g = g;
	}

	@SuppressWarnings("unchecked")
	public Vertex toGraph(Map map, Class c) {
		Long hash = hasher.hash(map, c);
		Vertex v;
		
		Object id = hashToVertexId.getIfPresent(hash);
		if(id != null)
			return g.getVertex(id);
		Iterator vIter = g.query().has(hashProperty, map.get(hashProperty)).vertices().iterator();
		if(vIter.hasNext()) {
			v = vIter.next();
			hashToVertexId.put(hash, v.getId());
			commitIfNecessary();
			return v;
		}
		v = g.addVertex(null);
		v.setProperty(typeProperty, c.getName());
		
		for(Entry e : ((Map) map).entrySet()) {
			String fieldName = e.getKey().toString();

			if(typeProperty.equals(fieldName)) {
				v.setProperty(fieldName, ((Class) e.getValue()).getName());
			}
			else if(Map.class.isAssignableFrom(e.getValue().getClass())) {
				// this field is a complex type, which will be mapped to a subgraph
				Map e2 = (Map) e.getValue();
				Class fieldType = (Class) e2.get("_type");
				
				if(fieldType == null) {
					// the field type itself is a map
					if(e2.isEmpty())
						continue;
					Class mapValueType = e2.values().iterator().next().getClass();
					
					if(Map.class.isAssignableFrom(mapValueType)) {
						for(Entry entry : e2.entrySet()) {
							Vertex v2 = toGraph((Map) entry.getValue(), mapValueType);
							Edge edge = v.addEdge(nameTools.depluralize(fieldName), v2);
							edge.setProperty(keyProperty, entry.getKey().toString());
						}
					}
					else
						v.setProperty(fieldName, e.getValue());
				}
				else {
					// the field is a complex type
					Vertex v2 = toGraph(e2, fieldType);
					v.addEdge(fieldName, v2);
				}
			}
			else if(Collection.class.isAssignableFrom(e.getValue().getClass())) {
				// this field represents a collection of objects
				Collection e2 = (Collection) e.getValue();
				if(e2.isEmpty())
					continue;
				Class collType = e2.iterator().next().getClass();
				
				if(Map.class.isAssignableFrom(collType)) {
					// the collection contains complex types that will be mapped to individual subgraphs
					int i = 0;
					for(Object e3 : e2) {
						Map e4 = (Map) e3;
						Vertex v2 = toGraph(e4, (Class) e4.get("_type"));
						Edge edge = v.addEdge(nameTools.depluralize(fieldName), v2);
						edge.setProperty(indexProperty, i++);
					}
				}
				else {
					// the collection contains primitive types... we will store the whole collection on a single property
					v.setProperty(fieldName, e.getValue());
				}
			}
			else
				v.setProperty(fieldName, e.getValue());
		}
		
		commitIfNecessary();
		return v;
	}
	
	private void commitIfNecessary() {
		if(TransactionalGraph.class.isAssignableFrom(g.getClass()))
			((TransactionalGraph) g).commit();
	}

	public Map fromGraph(Vertex v) {
		Map map = new HashMap<>();
		fromGraph(v, map);
		return map;
	}
	
	Comparator edgeSorter = new Comparator() {
		@Override
		public int compare(Edge edge1, Edge edge2) {
			if(edge1.getLabel().equals(edge2.getLabel())) {
				
				if(edge2.getProperty(indexProperty) != null) {
					// descending order by indexProperty
					return (int) edge2.getProperty(indexProperty) - (int) edge1.getProperty(indexProperty);
				}
				if(edge2.getProperty(keyProperty) != null) {
					// descending order by keyProperty
					String key2 = edge2.getProperty(keyProperty), key1 = edge1.getProperty(keyProperty);
					return key2.compareTo(key1);
				}
			}
			return edge1.getLabel().compareTo(edge2.getLabel());
		}
	};
	
	protected void fromGraph(Vertex v, Map map) {
		for(String key : v.getPropertyKeys())
			if(!hashProperty.equals(key) && !typeProperty.equals(key)) map.put(key, v.getProperty(key));

		PriorityQueue edgeQueue = new PriorityQueue(10, edgeSorter);
		for(Iterator edgeIter = v.query().direction(Direction.OUT).edges().iterator(); edgeIter.hasNext();)
			edgeQueue.add(edgeIter.next());

		String collectionLabel = null;
		String mapLabel = null;
		List> collection = null;
		Map> mapField = null;
		
		Edge e;
		while((e = edgeQueue.poll()) != null) {
			if(e.getProperty(indexProperty) != null) {
				if(!e.getLabel().equals(collectionLabel)) {
					// this edge represents the first element in a collection
					collection = new ArrayList<>((int) e.getProperty(indexProperty));
					
					// TODO don't assume that the field name is going to need to be pluralized
					map.put(nameTools.pluralize(e.getLabel()), collection);
				}
				
				Map child = new HashMap<>();
				fromGraph(e.getVertex(Direction.IN), child);
				collection.add(0, child); // edges are sorted last index first
				
				collectionLabel = e.getLabel();
			}
			else if(e.getProperty(keyProperty) != null) {
				if(!e.getLabel().equals(mapLabel)) {
					// this edge represents the first element in a map
					mapField = new HashMap<>();
					map.put(e.getLabel(), mapField);
				}
				
				Map child = new HashMap();
				fromGraph(e.getVertex(Direction.IN), child);
				mapField.put(e.getProperty(keyProperty), child);
				
				mapLabel = e.getLabel();
			}
			else {
				Map child = new HashMap<>();
				fromGraph(e.getVertex(Direction.IN), child);
				map.put(e.getLabel(), child);
			}
		}
		
		commitIfNecessary();
	}
}