PageRenderTime 39ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/core/src/main/java/com/github/jsonldjava/impl/TurtleTripleCallback.java

http://github.com/tristan/jsonld-java
Java | 375 lines | 284 code | 35 blank | 56 comment | 71 complexity | 9b1ada7f4c25525515fd3593957a06a1 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. package com.github.jsonldjava.impl;
  2. import static com.github.jsonldjava.core.JSONLDConsts.RDF_FIRST;
  3. import static com.github.jsonldjava.core.JSONLDConsts.RDF_NIL;
  4. import static com.github.jsonldjava.core.JSONLDConsts.RDF_REST;
  5. import static com.github.jsonldjava.core.JSONLDConsts.XSD_BOOLEAN;
  6. import static com.github.jsonldjava.core.JSONLDConsts.XSD_DOUBLE;
  7. import static com.github.jsonldjava.core.JSONLDConsts.XSD_FLOAT;
  8. import static com.github.jsonldjava.core.JSONLDConsts.XSD_INTEGER;
  9. import static com.github.jsonldjava.core.JSONLDConsts.XSD_STRING;
  10. import java.util.ArrayList;
  11. import java.util.HashMap;
  12. import java.util.Iterator;
  13. import java.util.LinkedHashMap;
  14. import java.util.LinkedHashSet;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.Map.Entry;
  18. import java.util.Set;
  19. import com.github.jsonldjava.core.JSONLDTripleCallback;
  20. import com.github.jsonldjava.core.RDFDataset;
  21. public class TurtleTripleCallback implements JSONLDTripleCallback {
  22. private static final int MAX_LINE_LENGTH = 160;
  23. private static final int TAB_SPACES = 4;
  24. private static final String COLS_KEY = "..cols.."; // this shouldn't be a
  25. // valid iri/bnode i
  26. // hope!
  27. final Map<String, String> availableNamespaces = new LinkedHashMap<String, String>() {
  28. {
  29. // TODO: fill with default namespaces
  30. }
  31. };
  32. Set<String> usedNamespaces;
  33. public TurtleTripleCallback() {
  34. }
  35. @Override
  36. public Object call(RDFDataset dataset) {
  37. for (final Entry<String, String> e : dataset.getNamespaces().entrySet()) {
  38. availableNamespaces.put(e.getValue(), e.getKey());
  39. }
  40. usedNamespaces = new LinkedHashSet<String>();
  41. final int tabs = 0;
  42. final Map<String, List<Object>> refs = new LinkedHashMap<String, List<Object>>();
  43. final Map<String, Map<String, List<Object>>> ttl = new LinkedHashMap<String, Map<String, List<Object>>>();
  44. for (String graphName : dataset.keySet()) {
  45. final List<RDFDataset.Quad> triples = dataset.getQuads(graphName);
  46. if ("@default".equals(graphName)) {
  47. graphName = null;
  48. }
  49. // http://www.w3.org/TR/turtle/#unlabeled-bnodes
  50. // TODO: implement nesting for unlabled nodes
  51. // map of what the output should look like
  52. // subj (or [ if bnode) > pred > obj
  53. // > obj (set ref if IRI)
  54. // > pred > obj (set ref if bnode)
  55. // subj > etc etc etc
  56. // subjid -> [ ref, ref, ref ]
  57. String prevSubject = "";
  58. String prevPredicate = "";
  59. Map<String, List<Object>> thisSubject = null;
  60. List<Object> thisPredicate = null;
  61. for (final RDFDataset.Quad triple : triples) {
  62. final String subject = triple.getSubject().getValue();
  63. final String predicate = triple.getPredicate().getValue();
  64. if (prevSubject.equals(subject)) {
  65. if (prevPredicate.equals(predicate)) {
  66. // nothing to do
  67. } else {
  68. // new predicate
  69. if (thisSubject.containsKey(predicate)) {
  70. thisPredicate = thisSubject.get(predicate);
  71. } else {
  72. thisPredicate = new ArrayList<Object>();
  73. thisSubject.put(predicate, thisPredicate);
  74. }
  75. prevPredicate = predicate;
  76. }
  77. } else {
  78. // new subject
  79. if (ttl.containsKey(subject)) {
  80. thisSubject = ttl.get(subject);
  81. } else {
  82. thisSubject = new LinkedHashMap<String, List<Object>>();
  83. ttl.put(subject, thisSubject);
  84. }
  85. if (thisSubject.containsKey(predicate)) {
  86. thisPredicate = thisSubject.get(predicate);
  87. } else {
  88. thisPredicate = new ArrayList<Object>();
  89. thisSubject.put(predicate, thisPredicate);
  90. }
  91. prevSubject = subject;
  92. prevPredicate = predicate;
  93. }
  94. if (triple.getObject().isLiteral()) {
  95. thisPredicate.add(triple.getObject());
  96. } else {
  97. final String o = triple.getObject().getValue();
  98. if (o.startsWith("_:")) {
  99. // add ref to o
  100. if (!refs.containsKey(o)) {
  101. refs.put(o, new ArrayList<Object>());
  102. }
  103. refs.get(o).add(thisPredicate);
  104. }
  105. thisPredicate.add(o);
  106. }
  107. }
  108. }
  109. final Map<String, List<Object>> collections = new LinkedHashMap<String, List<Object>>();
  110. final List<String> subjects = new ArrayList<String>(ttl.keySet());
  111. // find collections
  112. for (final String subj : subjects) {
  113. Map<String, List<Object>> preds = ttl.get(subj);
  114. if (preds != null && preds.containsKey(RDF_FIRST)) {
  115. final List<Object> col = new ArrayList<Object>();
  116. collections.put(subj, col);
  117. while (true) {
  118. final List<Object> first = preds.remove(RDF_FIRST);
  119. final Object o = first.get(0);
  120. col.add(o);
  121. // refs
  122. if (refs.containsKey(o)) {
  123. refs.get(o).remove(first);
  124. refs.get(o).add(col);
  125. }
  126. final String next = (String) preds.remove(RDF_REST).get(0);
  127. if (RDF_NIL.equals(next)) {
  128. // end of this list
  129. break;
  130. }
  131. // if collections already contains a value for "next", add
  132. // it to this col and break out
  133. if (collections.containsKey(next)) {
  134. col.addAll(collections.remove(next));
  135. break;
  136. }
  137. preds = ttl.remove(next);
  138. refs.remove(next);
  139. }
  140. }
  141. }
  142. // process refs (nesting referenced bnodes if only one reference to them
  143. // in the whole graph)
  144. for (final String id : refs.keySet()) {
  145. // skip items if there is more than one reference to them in the
  146. // graph
  147. if (refs.get(id).size() > 1) {
  148. continue;
  149. }
  150. // otherwise embed them into the referenced location
  151. Object object = ttl.remove(id);
  152. if (collections.containsKey(id)) {
  153. object = new LinkedHashMap<String, List<Object>>();
  154. final List<Object> tmp = new ArrayList<Object>();
  155. tmp.add(collections.remove(id));
  156. ((HashMap<String, Object>) object).put(COLS_KEY, tmp);
  157. }
  158. final List<Object> predicate = (List<Object>) refs.get(id).get(0);
  159. // replace the one bnode ref with the object
  160. predicate.set(predicate.lastIndexOf(id), object);
  161. }
  162. // replace the rest of the collections
  163. for (final String id : collections.keySet()) {
  164. final Map<String, List<Object>> subj = ttl.get(id);
  165. if (!subj.containsKey(COLS_KEY)) {
  166. subj.put(COLS_KEY, new ArrayList<Object>());
  167. }
  168. subj.get(COLS_KEY).add(collections.get(id));
  169. }
  170. // build turtle output
  171. final String output = generateTurtle(ttl, 0, 0, false);
  172. String prefixes = "";
  173. for (final String prefix : usedNamespaces) {
  174. final String name = availableNamespaces.get(prefix);
  175. prefixes += "@prefix " + name + ": <" + prefix + "> .\n";
  176. }
  177. return ("".equals(prefixes) ? "" : prefixes + "\n") + output;
  178. }
  179. private String generateObject(Object object, String sep, boolean hasNext, int indentation,
  180. int lineLength) {
  181. String rval = "";
  182. String obj;
  183. if (object instanceof String) {
  184. obj = getURI((String) object);
  185. } else if (object instanceof RDFDataset.Literal) {
  186. obj = ((RDFDataset.Literal) object).getValue();
  187. final String dt = ((RDFDataset.Literal) object).getDatatype();
  188. if (dt != null) {
  189. // TODO: this probably isn't an exclusive list of all the
  190. // datatype literals that can be represented as native types
  191. if (!(XSD_DOUBLE.equals(dt) || XSD_INTEGER.equals(dt) || XSD_FLOAT.equals(dt) || XSD_BOOLEAN
  192. .equals(dt))) {
  193. obj = "\"" + obj + "\"";
  194. if (!XSD_STRING.equals(dt)) {
  195. obj += "^^" + getURI(dt);
  196. }
  197. }
  198. } else {
  199. obj = "\"" + obj + "\"";
  200. final String lang = ((RDFDataset.Literal) object).getLanguage();
  201. if (lang != null) {
  202. obj += "@" + lang;
  203. }
  204. }
  205. } else {
  206. // must be an object
  207. final Map<String, Map<String, List<Object>>> tmp = new LinkedHashMap<String, Map<String, List<Object>>>();
  208. tmp.put("_:x", (Map<String, List<Object>>) object);
  209. obj = generateTurtle(tmp, indentation + 1, lineLength, true);
  210. }
  211. final int idxofcr = obj.indexOf("\n");
  212. // check if output will fix in the max line length (factor in comma if
  213. // not the last item, current line length and length to the next CR)
  214. if ((hasNext ? 1 : 0) + lineLength + (idxofcr != -1 ? idxofcr : obj.length()) > MAX_LINE_LENGTH) {
  215. rval += "\n" + tabs(indentation + 1);
  216. lineLength = (indentation + 1) * TAB_SPACES;
  217. }
  218. rval += obj;
  219. if (idxofcr != -1) {
  220. lineLength += (obj.length() - obj.lastIndexOf("\n"));
  221. } else {
  222. lineLength += obj.length();
  223. }
  224. if (hasNext) {
  225. rval += sep;
  226. lineLength += sep.length();
  227. if (lineLength < MAX_LINE_LENGTH) {
  228. rval += " ";
  229. lineLength++;
  230. } else {
  231. rval += "\n";
  232. }
  233. }
  234. return rval;
  235. }
  236. private String generateTurtle(Map<String, Map<String, List<Object>>> ttl, int indentation,
  237. int lineLength, boolean isObject) {
  238. String rval = "";
  239. final Iterator<String> subjIter = ttl.keySet().iterator();
  240. while (subjIter.hasNext()) {
  241. final String subject = subjIter.next();
  242. final Map<String, List<Object>> subjval = ttl.get(subject);
  243. // boolean isBlankNode = subject.startsWith("_:");
  244. boolean hasOpenBnodeBracket = false;
  245. if (subject.startsWith("_:")) {
  246. // only open blank node bracket the node doesn't contain any
  247. // collections
  248. if (!subjval.containsKey(COLS_KEY)) {
  249. rval += "[ ";
  250. lineLength += 2;
  251. hasOpenBnodeBracket = true;
  252. }
  253. // TODO: according to http://www.rdfabout.com/demo/validator/
  254. // 1) collections as objects cannot contain any predicates other
  255. // than rdf:first and rdf:rest
  256. // 2) collections cannot be surrounded with [ ]
  257. // check for collection
  258. if (subjval.containsKey(COLS_KEY)) {
  259. final List<Object> collections = subjval.remove(COLS_KEY);
  260. for (final Object collection : collections) {
  261. rval += "( ";
  262. lineLength += 2;
  263. final Iterator<Object> objIter = ((List<Object>) collection).iterator();
  264. while (objIter.hasNext()) {
  265. final Object object = objIter.next();
  266. rval += generateObject(object, "", objIter.hasNext(), indentation,
  267. lineLength);
  268. lineLength = rval.length() - rval.lastIndexOf("\n");
  269. }
  270. rval += " ) ";
  271. lineLength += 3;
  272. }
  273. }
  274. // check for blank node
  275. } else {
  276. rval += getURI(subject) + " ";
  277. lineLength += subject.length() + 1;
  278. }
  279. final Iterator<String> predIter = ttl.get(subject).keySet().iterator();
  280. while (predIter.hasNext()) {
  281. final String predicate = predIter.next();
  282. rval += getURI(predicate) + " ";
  283. lineLength += predicate.length() + 1;
  284. final Iterator<Object> objIter = ttl.get(subject).get(predicate).iterator();
  285. while (objIter.hasNext()) {
  286. final Object object = objIter.next();
  287. rval += generateObject(object, ",", objIter.hasNext(), indentation, lineLength);
  288. lineLength = rval.length() - rval.lastIndexOf("\n");
  289. }
  290. if (predIter.hasNext()) {
  291. rval += " ;\n" + tabs(indentation + 1);
  292. lineLength = (indentation + 1) * TAB_SPACES;
  293. }
  294. }
  295. if (hasOpenBnodeBracket) {
  296. rval += " ]";
  297. }
  298. if (!isObject) {
  299. rval += " .\n";
  300. if (subjIter.hasNext()) { // add blank space if we have another
  301. // object below this
  302. rval += "\n";
  303. }
  304. }
  305. }
  306. return rval;
  307. }
  308. // TODO: Assert (TAB_SPACES == 4) otherwise this needs to be edited, and
  309. // should fail to compile
  310. private String tabs(int tabs) {
  311. String rval = "";
  312. for (int i = 0; i < tabs; i++) {
  313. rval += " "; // using spaces for tabs
  314. }
  315. return rval;
  316. }
  317. /**
  318. * checks the URI for a prefix, and if one is found, set used prefixes to
  319. * true
  320. *
  321. * @param predicate
  322. * @return
  323. */
  324. private String getURI(String uri) {
  325. // check for bnode
  326. if (uri.startsWith("_:")) {
  327. // return the bnode id
  328. return uri;
  329. }
  330. for (final String prefix : availableNamespaces.keySet()) {
  331. if (uri.startsWith(prefix)) {
  332. usedNamespaces.add(prefix);
  333. // return the prefixed URI
  334. return availableNamespaces.get(prefix) + ":" + uri.substring(prefix.length());
  335. }
  336. }
  337. // return the full URI
  338. return "<" + uri + ">";
  339. }
  340. }