JDIL.java

/*
 * Copyright (c) 2007-2017 MetaSolutions AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.entrystore.rest.util.jdil;

import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.LinkedHashModel;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

import static org.eclipse.rdf4j.model.util.Values.*;

/**
 * 
 * @author Matthias Palmer , Eric Johansson
 *
 */
public class JDIL {

	private Namespaces namespaces;
	static Logger log = LoggerFactory.getLogger(JDIL.class);

	public JDIL(Namespaces namespaces) {
		this.namespaces = namespaces;
	}

	HashMap<String, String> globalNamespaces;

	public String getAbbr(String uri) {
		return namespaces.abbreviate(uri); 
	}
	
	/**
	 * Converts a JDIL-JSON-object to a RDF graph. 
	 * Example JDIL-JSON: 
	 * {"metadata": {"dcterms:description":"nice data"}} 
	 * @param jsonObject the JSONobject with the JDIL-JSON
	 * @return a new Graph
	 */
	public Model importJDILtoGraph(JSONObject jsonObject) {
		try {
			Model graph = new LinkedHashModel();
			JDILParser.removeJDILStar(jsonObject);
			this.JDILtoGraph(jsonObject, graph, null, new HashMap<String, BNode>());
			return graph;
		} catch (JSONException e) {
			e.printStackTrace();
		} 
		return null; 
	}

	private void JDILtoGraph(JSONObject jsonObject, Model graph, Resource subject, Map<String, BNode> bnodeResolver) throws JSONException {
		if (subject == null) { //Assume there is a @id.
			subject = iri(namespaces.expand(jsonObject.getString("@id")));
		}

		Iterator keyIt = jsonObject.keys();
		while(keyIt.hasNext()) {
			String key = keyIt.next().toString();
			if (key.startsWith("@")) {
				//One of the builtin that should be ignored, e.g. @id or @namespaces
				continue;
			}

			IRI spred = iri(namespaces.expand(key));
			Object obj= jsonObject.opt(key);

			// Recurse (base pattern)
			if (obj instanceof JSONObject) {
				//single property
				extract(graph, subject, spred, obj, bnodeResolver);
			} else if (obj instanceof JSONArray) {
				//Repeated properties as array
				JSONArray array = (JSONArray) obj; 
				for (int i = 0; i < array.length(); i++) {
					obj = array.get(i);
					extract(graph, subject, spred, obj, bnodeResolver);
				}
			} else {
				//Take care of literal.
				graph.add(subject, spred, literal(obj.toString()));
			}
		}
	}

	private void extract(Model graph, Resource subject, IRI spred, Object obj, Map<String, BNode> bnodeResolver) throws JSONException {
		if (((JSONObject) obj).has("@id") && !((JSONObject) obj).has("@isBlank")) {
			//Take care of object
			IRI sobj = iri(namespaces.expand(((JSONObject)obj).getString("@id")));
			graph.add(subject, spred, sobj);
			JDILtoGraph((JSONObject)obj, graph, sobj, bnodeResolver);
		} else {
			if (((JSONObject) obj).has("@isBlank")) {
				String bid = ((JSONObject)obj).getString("@id");
				BNode sobj = null;
				if (bid == null) {
					sobj = bnode();
				} else if (bnodeResolver.containsKey(bid)){
					sobj = bnodeResolver.get(bid);
				} else {
					sobj = bnode();
					bnodeResolver.put(bid, sobj);
				}
				graph.add(subject, spred, sobj);
				JDILtoGraph((JSONObject)obj, graph, sobj, bnodeResolver);
			} else {
				//Take care of literal as object.
				exportLiteral((JSONObject) obj, graph, subject, spred);
			}
		}
	}

	private void exportLiteral(JSONObject obj, Model graph, Resource subject, IRI predicate) {
		Object objVal= obj.opt("@value");
		Object objLang= obj.opt("@language");
		Object objType= obj.opt("@datatype");

		if(objLang != null) { //value + language
			graph.add(subject, predicate, literal((String)objVal,(String)objLang));
		} else if(objType != null) { //value + type
			graph.add(subject, predicate, literal((String) objVal, iri(namespaces.expand((String)objType))));
		} else { //value
			graph.add(subject, predicate, literal((String)objVal));
		}
	}


	/**
	 * pseudo code
	 *  
	 * @param graph
	 * @return
	 */
	public JSONObject exportRelationGraphToJSON(Model graph) {
		JSONObject result = new JSONObject(); 
		//HashMap<String, HashMap<String, ArrayList<String>>> objRelations = new HashMap<String, HashMap<String,ArrayList<String>>>();   
		//HashMap<String, ArrayList<String>> subjRelations = new HashMap<String,ArrayList<String>>();   
		//ArrayList<String> predRelations = new ArrayList<String>();   

		// { Obj1 : { subj1: pred1, subj2: [pred2, pred4] }
		// 
		// { "res1" : { subj1: pred1, subj2: [pred2, pred4] },
		for (Statement statement : graph) {
			String obj = namespaces.abbreviate(statement.getObject().stringValue());
			String subj = namespaces.abbreviate(statement.getSubject().stringValue());
			String pred = namespaces.abbreviate(statement.getPredicate().stringValue());

			JSONObject objJSON = null;
			try {
				objJSON = result.getJSONObject(obj);
			} catch (JSONException e) {
				objJSON = new JSONObject();
				try {
					result.put(obj, objJSON);
				} catch (JSONException e1) {
				}
			}

			try {
				objJSON.append(subj, pred);
			} catch (JSONException e1) {

			}
		}

		return result;
	}


	public JSONObject exportGraphToJDIL(Model graph, Resource root) {
		try {
			HashMap<Resource, JSONObject> res2Jdil= new HashMap<Resource, JSONObject>();
			HashSet<Resource> notRoots = new HashSet<Resource>();

			for (Statement statement : graph) {				
				JSONObject subj = getOrCreateSubject(statement.getSubject(), res2Jdil);   
				String predicate = namespaces.abbreviate(statement.getPredicate().stringValue());
				notRoots.add(statement.getPredicate());
				Value value = statement.getObject();


				if (value instanceof Resource) {
					/*
					 * Create a new JDIL value to accumulate to the subject.
					 */
					JSONObject JDILValueObject = getOrCreateObject((Resource) value,res2Jdil); 

					subj.accumulate(predicate, JDILValueObject);
					notRoots.add((Resource) value);

				} else {
					Literal lit = (Literal) value;
					String language = lit.getLanguage().orElse(null);
					IRI datatype = lit.getDatatype();
					JSONObject object = new JSONObject();
					object.accumulate("@value", value.stringValue());
					if (language != null) {
						object.accumulate("@language", language);
					} else if (datatype != null) {
						object.accumulate("@datatype", datatype.stringValue());					
					}
					subj.accumulate(predicate, object);
				}
			}
			if (root != null) {
				JSONObject obj = res2Jdil.get(root);
				cutLoops(obj, new HashSet());
				return obj; 
			}
			HashSet<Resource> roots = new HashSet<Resource>(res2Jdil.keySet());
			roots.removeAll(notRoots);
			if (roots.size() == 1) {
				JSONObject obj = res2Jdil.get(roots.iterator().next());
				cutLoops(obj, new HashSet());
				return obj;
			} 
		} catch (JSONException jse) {
			log.error(jse.getMessage());
		}
		return null;
	}

	void cutLoops(JSONObject obj, HashSet above) {
		if (obj == null) {
			return;
		}
		above.add(obj);
		for (Iterator it = obj.keys();it.hasNext();) {
			String key = (String) it.next();
			Object child = obj.opt(key);
			if (child instanceof JSONObject) {
				if (above.contains(child)) {
					try {
						JSONObject newChild = new JSONObject();
						if (((JSONObject) child).has("@id")) {
							newChild.put("@id", ((JSONObject) child).opt("@id"));
						}
						if (((JSONObject) child).has("@isBlank")) {
							newChild.put("@isBlank", ((JSONObject) child).opt("@isBlank"));
						}
						obj.put(key, newChild);
					} catch (JSONException e) {
					}
				} else {
					cutLoops((JSONObject) child, above);
				}
			} else if (child instanceof JSONArray) {
				JSONArray arr = (JSONArray) child;
				for (int i = 0;i<arr.length(); i++) {
					try {
						Object arrChild = arr.get(i);
						if (arrChild instanceof JSONObject) {
							if (above.contains(arrChild)) {
								JSONObject newChild = new JSONObject();
								if (((JSONObject) arrChild).opt("@id") != null) {
									newChild.put("@id", ((JSONObject) arrChild).opt("@id"));
								} else {
									newChild.put("@bid", ((JSONObject) arrChild).opt("@bid"));
								}
								arr.put(i, newChild);
							} else {
								cutLoops((JSONObject) arrChild, above);
							}
						}
					} catch (JSONException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	JSONObject getOrCreateSubject(Resource resource, Map<Resource, JSONObject> res2Jdil) throws JSONException {
		JSONObject jdil = res2Jdil.get(resource);
		if (jdil == null) {
			jdil = new JSONObject();
			if (resource instanceof BNode) {
				jdil.accumulate("@id", resource.stringValue());
				jdil.accumulate("@isBlank", true);
			} else {
				jdil.accumulate("@id", namespaces.abbreviate(resource.stringValue()));
			}
			res2Jdil.put(resource, jdil);
		}
		return jdil;
	}

	JSONObject getOrCreateObject(Resource resource, Map<Resource, JSONObject> res2Jdil) throws JSONException {
		JSONObject jdil = new JSONObject();
		if (resource instanceof BNode) {
			jdil.accumulate("@id", resource.stringValue());
			jdil.accumulate("@isBlank", true);
		} else {
			jdil.accumulate("@id", namespaces.abbreviate(resource.stringValue()));
		}		
		if (res2Jdil.get(resource) == null) {
			res2Jdil.put(resource, jdil);
		} else {
			jdil = res2Jdil.get(resource); 
		}
		return jdil;
	}

	public Namespaces getNamespaces() {
		return namespaces; 
	}
}