PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/java/com/github/jsonldjava/core/JSONLD.java

http://github.com/tristan/jsonld-java
Java | 632 lines | 393 code | 71 blank | 168 comment | 110 complexity | 58be7381608516996548291fc58761d0 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. package com.github.jsonldjava.core;
  2. import static com.github.jsonldjava.core.JSONLDUtils.compactIri;
  3. import static com.github.jsonldjava.core.JSONLDUtils.createNodeMap;
  4. import static com.github.jsonldjava.core.JSONLDUtils.isArray;
  5. import static com.github.jsonldjava.core.JSONLDUtils.isObject;
  6. import static com.github.jsonldjava.core.JSONLDUtils.isString;
  7. import static com.github.jsonldjava.core.JSONLDUtils.removePreserve;
  8. import static com.github.jsonldjava.core.JSONLDUtils.resolveContextUrls;
  9. import java.util.ArrayList;
  10. import java.util.LinkedHashMap;
  11. import java.util.List;
  12. import java.util.Map;
  13. import com.github.jsonldjava.core.JSONLDProcessingError.Error;
  14. import com.github.jsonldjava.impl.NQuadRDFParser;
  15. import com.github.jsonldjava.impl.NQuadTripleCallback;
  16. import com.github.jsonldjava.impl.TurtleRDFParser;
  17. import com.github.jsonldjava.impl.TurtleTripleCallback;
  18. public class JSONLD {
  19. /**
  20. * Performs JSON-LD compaction.
  21. *
  22. * @param input
  23. * the JSON-LD input to compact.
  24. * @param ctx
  25. * the context to compact with.
  26. * @param [options] options to use: [base] the base IRI to use. [strict] use
  27. * strict mode (default: true). [compactArrays] true to compact
  28. * arrays to single values when appropriate, false not to (default:
  29. * true). [graph] true to always output a top-level graph (default:
  30. * false). [skipExpansion] true to assume the input is expanded and
  31. * skip expansion, false not to, defaults to false. [loadContext(url,
  32. * callback(err, url, result))] the context loader.
  33. * @param callback
  34. * (err, compacted, ctx) called once the operation completes.
  35. */
  36. public static Object compact(Object input, Object ctx, Options opts)
  37. throws JSONLDProcessingError {
  38. // nothing to compact
  39. if (input == null) {
  40. return null;
  41. }
  42. // NOTE: javascript does this check before input check
  43. if (ctx == null) {
  44. throw new JSONLDProcessingError("The compaction context must not be null.")
  45. .setType(JSONLDProcessingError.Error.COMPACT_ERROR);
  46. }
  47. // set default options
  48. if (opts.base == null) {
  49. opts.base = "";
  50. }
  51. if (opts.strict == null) {
  52. opts.strict = true;
  53. }
  54. if (opts.compactArrays == null) {
  55. opts.compactArrays = true;
  56. }
  57. if (opts.graph == null) {
  58. opts.graph = false;
  59. }
  60. if (opts.skipExpansion == null) {
  61. opts.skipExpansion = false;
  62. }
  63. // JSONLDProcessor p = new JSONLDProcessor(opts);
  64. // expand input then do compaction
  65. Object expanded;
  66. try {
  67. if (opts.skipExpansion) {
  68. expanded = input;
  69. } else {
  70. expanded = JSONLD.expand(input, opts);
  71. }
  72. } catch (final JSONLDProcessingError e) {
  73. throw new JSONLDProcessingError("Could not expand input before compaction.").setType(
  74. JSONLDProcessingError.Error.COMPACT_ERROR).setDetail("cause", e);
  75. }
  76. // process context
  77. ActiveContext activeCtx = new ActiveContext(opts);
  78. try {
  79. activeCtx = JSONLD.processContext(activeCtx, ctx, opts);
  80. } catch (final JSONLDProcessingError e) {
  81. throw new JSONLDProcessingError("Could not process context before compaction.")
  82. .setType(JSONLDProcessingError.Error.COMPACT_ERROR).setDetail("cause", e);
  83. }
  84. // do compaction
  85. Object compacted = new JSONLDProcessor(opts).compact(activeCtx, null, expanded);
  86. // cleanup
  87. if (opts.compactArrays && !opts.graph && isArray(compacted)) {
  88. // simplify to a single item
  89. if (((List<Object>) compacted).size() == 1) {
  90. compacted = ((List<Object>) compacted).get(0);
  91. }
  92. // simplify to an empty object
  93. else if (((List<Object>) compacted).size() == 0) {
  94. compacted = new LinkedHashMap<String, Object>();
  95. }
  96. }
  97. // always use array if graph option is on
  98. else if (opts.graph && isObject(compacted)) {
  99. final List<Object> tmp = new ArrayList<Object>();
  100. tmp.add(compacted);
  101. compacted = tmp;
  102. }
  103. // follow @context key
  104. if (isObject(ctx) && ((Map<String, Object>) ctx).containsKey("@context")) {
  105. ctx = ((Map<String, Object>) ctx).get("@context");
  106. }
  107. // build output context
  108. ctx = JSONLDUtils.clone(ctx);
  109. if (!isArray(ctx)) {
  110. final List<Object> lctx = new ArrayList<Object>();
  111. lctx.add(ctx);
  112. ctx = lctx;
  113. }
  114. // remove empty contexts
  115. final List<Object> tmp = (List<Object>) ctx;
  116. ctx = new ArrayList<Object>();
  117. for (final Object i : tmp) {
  118. if (!isObject(i) || ((Map) i).size() > 0) {
  119. ((List<Object>) ctx).add(i);
  120. }
  121. }
  122. // remove array if only one context
  123. final boolean hasContext = ((List) ctx).size() > 0;
  124. if (((List) ctx).size() == 1) {
  125. ctx = ((List) ctx).get(0);
  126. }
  127. // add context and/or @graph
  128. if (isArray(compacted)) {
  129. final String kwgraph = compactIri(activeCtx, "@graph");
  130. final Object graph = compacted;
  131. compacted = new LinkedHashMap<String, Object>();
  132. if (hasContext) {
  133. ((Map<String, Object>) compacted).put("@context", ctx);
  134. }
  135. ((Map<String, Object>) compacted).put(kwgraph, graph);
  136. } else if (isObject(compacted) && hasContext) {
  137. // reorder keys so @context is first
  138. final Map<String, Object> graph = (Map<String, Object>) compacted;
  139. compacted = new LinkedHashMap<String, Object>();
  140. ((Map) compacted).put("@context", ctx);
  141. for (final String key : graph.keySet()) {
  142. ((Map<String, Object>) compacted).put(key, graph.get(key));
  143. }
  144. }
  145. // frame needs the value of the compaction result's activeCtx
  146. opts.compactResultsActiveCtx = activeCtx;
  147. return compacted;
  148. }
  149. public static Object compact(Object input, Map<String, Object> ctx)
  150. throws JSONLDProcessingError {
  151. return compact(input, ctx, new Options("", true));
  152. }
  153. /**
  154. * Performs JSON-LD expansion.
  155. *
  156. * @param input
  157. * the JSON-LD input to expand.
  158. * @param [options] the options to use: [base] the base IRI to use.
  159. * [keepFreeFloatingNodes] true to keep free-floating nodes, false
  160. * not to, defaults to false.
  161. * @return the expanded result as a list
  162. */
  163. public static List<Object> expand(Object input, Options opts) throws JSONLDProcessingError {
  164. if (opts.base == null) {
  165. opts.base = "";
  166. }
  167. if (opts.keepFreeFloatingNodes == null) {
  168. opts.keepFreeFloatingNodes = false;
  169. }
  170. // resolve all @context URLs in the input
  171. input = JSONLDUtils.clone(input);
  172. JSONLDUtils.resolveContextUrls(input);
  173. // do expansion
  174. final JSONLDProcessor p = new JSONLDProcessor(opts);
  175. Object expanded = p.expand(new ActiveContext(opts), null, input, false);
  176. // optimize away @graph with no other properties
  177. if (isObject(expanded) && ((Map) expanded).containsKey("@graph")
  178. && ((Map) expanded).size() == 1) {
  179. expanded = ((Map<String, Object>) expanded).get("@graph");
  180. } else if (expanded == null) {
  181. expanded = new ArrayList<Object>();
  182. }
  183. // normalize to an array
  184. if (!isArray(expanded)) {
  185. final List<Object> tmp = new ArrayList<Object>();
  186. tmp.add(expanded);
  187. expanded = tmp;
  188. }
  189. return (List<Object>) expanded;
  190. }
  191. public static List<Object> expand(Object input) throws JSONLDProcessingError {
  192. return expand(input, new Options(""));
  193. }
  194. /**
  195. * Performs JSON-LD flattening.
  196. *
  197. * @param input
  198. * the JSON-LD to flatten.
  199. * @param ctx
  200. * the context to use to compact the flattened output, or null.
  201. * @param [options] the options to use: [base] the base IRI to use.
  202. * [loadContext(url, callback(err, url, result))] the context loader.
  203. * @param callback
  204. * (err, flattened) called once the operation completes.
  205. * @throws JSONLDProcessingError
  206. */
  207. public static Object flatten(Object input, Object ctx, Options opts)
  208. throws JSONLDProcessingError {
  209. // set default options
  210. if (opts.base == null) {
  211. opts.base = "";
  212. }
  213. // expand input
  214. List<Object> _input;
  215. try {
  216. _input = expand(input, opts);
  217. } catch (final JSONLDProcessingError e) {
  218. throw new JSONLDProcessingError("Could not expand input before flattening.").setType(
  219. JSONLDProcessingError.Error.FLATTEN_ERROR).setDetail("cause", e);
  220. }
  221. final Object flattened = new JSONLDProcessor(opts).flatten(_input);
  222. if (ctx == null) {
  223. return flattened;
  224. }
  225. // compact result (force @graph option to true, skip expansion)
  226. opts.graph = true;
  227. opts.skipExpansion = true;
  228. try {
  229. final Object compacted = compact(flattened, ctx, opts);
  230. return compacted;
  231. } catch (final JSONLDProcessingError e) {
  232. throw new JSONLDProcessingError("Could not compact flattened output.").setType(
  233. JSONLDProcessingError.Error.FLATTEN_ERROR).setDetail("cause", e);
  234. }
  235. }
  236. public static Object flatten(Object input, Object ctxOrOptions) throws JSONLDProcessingError {
  237. if (ctxOrOptions instanceof Options) {
  238. return flatten(input, null, (Options) ctxOrOptions);
  239. } else {
  240. return flatten(input, ctxOrOptions, new Options(""));
  241. }
  242. }
  243. public static Object flatten(Object input) throws JSONLDProcessingError {
  244. return flatten(input, null, new Options(""));
  245. }
  246. /**
  247. * Performs JSON-LD framing.
  248. *
  249. * @param input
  250. * the JSON-LD input to frame.
  251. * @param frame
  252. * the JSON-LD frame to use.
  253. * @param [options] the framing options. [base] the base IRI to use. [embed]
  254. * default @embed flag (default: true). [explicit] default @explicit
  255. * flag (default: false). [omitDefault] default @omitDefault flag
  256. * (default: false). [loadContext(url, callback(err, url, result))]
  257. * the context loader.
  258. * @param callback
  259. * (err, framed) called once the operation completes.
  260. * @throws JSONLDProcessingError
  261. */
  262. public static Object frame(Object input, Map<String, Object> frame, Options options)
  263. throws JSONLDProcessingError {
  264. // set default options
  265. if (options.base == null) {
  266. options.base = "";
  267. }
  268. if (options.embed == null) {
  269. options.embed = true;
  270. }
  271. if (options.explicit == null) {
  272. options.explicit = false;
  273. }
  274. if (options.omitDefault == null) {
  275. options.omitDefault = false;
  276. }
  277. // TODO: add sanity checks for input and throw JSONLDProcessingErrors
  278. // when incorrect input is used
  279. // preserve frame context
  280. final Object ctx = frame.containsKey("@context") ? frame.get("@context")
  281. : new LinkedHashMap<String, Object>();
  282. // expand input
  283. Object expanded;
  284. try {
  285. expanded = JSONLD.expand(input, options);
  286. } catch (final JSONLDProcessingError e) {
  287. throw new JSONLDProcessingError("Could not expand input before framing.").setType(
  288. JSONLDProcessingError.Error.FRAME_ERROR).setDetail("cause", e);
  289. }
  290. // expand frame
  291. Object expandedFrame;
  292. final Options opts = options.clone();
  293. opts.keepFreeFloatingNodes = true;
  294. try {
  295. expandedFrame = JSONLD.expand(frame, opts);
  296. } catch (final JSONLDProcessingError e) {
  297. throw new JSONLDProcessingError("Could not expand frame before framing.").setType(
  298. JSONLDProcessingError.Error.FRAME_ERROR).setDetail("cause", e);
  299. }
  300. // do framing
  301. final Object framed = new JSONLDProcessor(opts).frame(expanded, expandedFrame);
  302. // compact results (force @graph option to true, skip expansion)
  303. opts.graph = true;
  304. opts.skipExpansion = true;
  305. try {
  306. final Object compacted = compact(framed, ctx, opts);
  307. // get resulting activeCtx
  308. final ActiveContext actx = opts.compactResultsActiveCtx;
  309. // get graph alias
  310. final String graph = compactIri(actx, "@graph");
  311. ((Map<String, Object>) compacted).put(graph,
  312. removePreserve(actx, ((Map<String, Object>) compacted).get(graph), opts));
  313. return compacted;
  314. } catch (final JSONLDProcessingError e) {
  315. throw new JSONLDProcessingError("Could not compact framed output.").setType(
  316. JSONLDProcessingError.Error.FRAME_ERROR).setDetail("cause", e);
  317. }
  318. }
  319. public static Object frame(Object input, Map<String, Object> frame)
  320. throws JSONLDProcessingError {
  321. return frame(input, frame, new Options(""));
  322. }
  323. /**
  324. * Processes a local context, resolving any URLs as necessary, and returns a
  325. * new active context in its callback.
  326. *
  327. * @param activeCtx
  328. * the current active context.
  329. * @param localCtx
  330. * the local context to process.
  331. * @param [options] the options to use: [loadContext(url, callback(err, url,
  332. * result))] the context loader.
  333. * @param callback
  334. * (err, ctx) called once the operation completes.
  335. */
  336. private static ActiveContext processContext(ActiveContext activeCtx, Object localCtx,
  337. Options opts) throws JSONLDProcessingError {
  338. // set default options
  339. if (opts.base == null) {
  340. opts.base = "";
  341. }
  342. // return initial context early for null context
  343. if (localCtx == null) {
  344. return new ActiveContext(opts);
  345. }
  346. // retrieve URLs in localCtx
  347. localCtx = JSONLDUtils.clone(localCtx);
  348. if (isString(localCtx)
  349. || (isObject(localCtx) && !((Map<String, Object>) localCtx).containsKey("@context"))) {
  350. final Map<String, Object> tmp = new LinkedHashMap<String, Object>();
  351. tmp.put("@context", localCtx);
  352. localCtx = tmp;
  353. }
  354. resolveContextUrls(localCtx);
  355. return new JSONLDProcessor(opts).processContext(activeCtx, localCtx);
  356. }
  357. /**
  358. * Performs RDF dataset normalization on the given JSON-LD input. The output
  359. * is an RDF dataset unless the 'format' option is used.
  360. *
  361. * @param input
  362. * the JSON-LD input to normalize.
  363. * @param [options] the options to use: [base] the base IRI to use. [format]
  364. * the format if output is a string: 'application/nquads' for
  365. * N-Quads. [loadContext(url, callback(err, url, result))] the
  366. * context loader.
  367. * @param callback
  368. * (err, normalized) called once the operation completes.
  369. * @throws JSONLDProcessingError
  370. */
  371. public static Object normalize(Object input, Options options) throws JSONLDProcessingError {
  372. if (options.base == null) {
  373. options.base = "";
  374. }
  375. final Options opts = options.clone();
  376. opts.format = null;
  377. RDFDataset dataset;
  378. try {
  379. dataset = (RDFDataset) toRDF(input, opts);
  380. } catch (final JSONLDProcessingError e) {
  381. throw new JSONLDProcessingError(
  382. "Could not convert input to RDF dataset before normalization.").setType(
  383. JSONLDProcessingError.Error.NORMALIZE_ERROR).setDetail("cause", e);
  384. }
  385. return new JSONLDProcessor(options).normalize(dataset);
  386. }
  387. public static Object normalize(Object input) throws JSONLDProcessingError {
  388. return normalize(input, new Options(""));
  389. }
  390. /**
  391. * Outputs the RDF dataset found in the given JSON-LD object.
  392. *
  393. * @param input
  394. * the JSON-LD input.
  395. * @param callback
  396. * A callback that is called when the input has been converted to
  397. * Quads (null to use options.format instead).
  398. * @param [options] the options to use: [base] the base IRI to use. [format]
  399. * the format to use to output a string: 'application/nquads' for
  400. * N-Quads (default). [loadContext(url, callback(err, url, result))]
  401. * the context loader.
  402. * @param callback
  403. * (err, dataset) called once the operation completes.
  404. */
  405. public static Object toRDF(Object input, JSONLDTripleCallback callback, Options options)
  406. throws JSONLDProcessingError {
  407. if (options.base == null) {
  408. options.base = "";
  409. }
  410. Object expanded;
  411. try {
  412. expanded = JSONLD.expand(input, options);
  413. } catch (final JSONLDProcessingError e) {
  414. throw new JSONLDProcessingError("Could not expand input before conversion to RDF.")
  415. .setType(JSONLDProcessingError.Error.RDF_ERROR).setDetail("cause", e);
  416. }
  417. final UniqueNamer namer = new UniqueNamer("_:b");
  418. final Map<String, Object> nodeMap = new LinkedHashMap<String, Object>() {
  419. {
  420. put("@default", new LinkedHashMap<String, Object>());
  421. }
  422. };
  423. createNodeMap(expanded, nodeMap, "@default", namer);
  424. // output RDF dataset
  425. final RDFDataset dataset = new JSONLDProcessor(options).toRDF(nodeMap);
  426. // generate namespaces from context
  427. if (options.useNamespaces) {
  428. List<Map<String, Object>> _input;
  429. if (isArray(input)) {
  430. _input = (List<Map<String, Object>>) input;
  431. } else {
  432. _input = new ArrayList<Map<String, Object>>();
  433. _input.add((Map<String, Object>) input);
  434. }
  435. for (final Map<String, Object> e : _input) {
  436. if (e.containsKey("@context")) {
  437. dataset.parseContext((Map<String, Object>) e.get("@context"));
  438. }
  439. }
  440. }
  441. if (callback != null) {
  442. return callback.call(dataset);
  443. }
  444. if (options.format != null) {
  445. if ("application/nquads".equals(options.format)) {
  446. return new NQuadTripleCallback().call(dataset);
  447. } else if ("text/turtle".equals(options.format)) {
  448. return new TurtleTripleCallback().call(dataset);
  449. } else {
  450. throw new JSONLDProcessingError("Unknown output format.").setType(
  451. JSONLDProcessingError.Error.UNKNOWN_FORMAT).setDetail("format",
  452. options.format);
  453. }
  454. }
  455. return dataset;
  456. }
  457. public static Object toRDF(Object input, Options options) throws JSONLDProcessingError {
  458. return toRDF(input, null, options);
  459. }
  460. public static Object toRDF(Object input, JSONLDTripleCallback callback)
  461. throws JSONLDProcessingError {
  462. return toRDF(input, callback, new Options(""));
  463. }
  464. public static Object toRDF(Object input) throws JSONLDProcessingError {
  465. return toRDF(input, new Options(""));
  466. }
  467. /**
  468. * a registry for RDF Parsers (in this case, JSONLDSerializers) used by
  469. * fromRDF if no specific serializer is specified and options.format is set.
  470. */
  471. private static Map<String, RDFParser> rdfParsers = new LinkedHashMap<String, RDFParser>() {
  472. {
  473. // automatically register nquad serializer
  474. put("application/nquads", new NQuadRDFParser());
  475. put("text/turtle", new TurtleRDFParser());
  476. }
  477. };
  478. public static void registerRDFParser(String format, RDFParser parser) {
  479. rdfParsers.put(format, parser);
  480. }
  481. public static void removeRDFParser(String format) {
  482. rdfParsers.remove(format);
  483. }
  484. /**
  485. * Converts an RDF dataset to JSON-LD.
  486. *
  487. * @param dataset
  488. * a serialized string of RDF in a format specified by the format
  489. * option or an RDF dataset to convert.
  490. * @param [options] the options to use: [format] the format if input is not
  491. * an array: 'application/nquads' for N-Quads (default). [useRdfType]
  492. * true to use rdf:type, false to use @type (default: false).
  493. * [useNativeTypes] true to convert XSD types into native types
  494. * (boolean, integer, double), false not to (default: true).
  495. *
  496. * @param callback
  497. * (err, output) called once the operation completes.
  498. */
  499. public static Object fromRDF(Object dataset, Options options) throws JSONLDProcessingError {
  500. // handle non specified serializer case
  501. RDFParser parser = null;
  502. if (options.format == null && dataset instanceof String) {
  503. // attempt to parse the input as nquads
  504. options.format = "application/nquads";
  505. }
  506. if (rdfParsers.containsKey(options.format)) {
  507. parser = rdfParsers.get(options.format);
  508. } else {
  509. throw new JSONLDProcessingError("Unknown input format.").setType(
  510. JSONLDProcessingError.Error.UNKNOWN_FORMAT).setDetail("format", options.format);
  511. }
  512. // convert from RDF
  513. return fromRDF(dataset, options, parser);
  514. }
  515. public static Object fromRDF(Object dataset) throws JSONLDProcessingError {
  516. return fromRDF(dataset, new Options(""));
  517. }
  518. /**
  519. * Uses a specific serializer.
  520. *
  521. */
  522. public static Object fromRDF(Object input, Options options, RDFParser parser)
  523. throws JSONLDProcessingError {
  524. if (options.useRdfType == null) {
  525. options.useRdfType = false;
  526. }
  527. if (options.useNativeTypes == null) {
  528. options.useNativeTypes = true;
  529. }
  530. final RDFDataset dataset = parser.parse(input);
  531. // convert from RDF
  532. final Object rval = new JSONLDProcessor(options).fromRDF(dataset);
  533. // re-process using the generated context if outputForm is set
  534. if (options.outputForm != null) {
  535. if ("expanded".equals(options.outputForm)) {
  536. return rval;
  537. } else if ("compacted".equals(options.outputForm)) {
  538. return compact(rval, dataset.getContext(), options);
  539. } else if ("flattened".equals(options.outputForm)) {
  540. return flatten(rval, dataset.getContext(), options);
  541. } else {
  542. throw new JSONLDProcessingError("Unknown value for output form").setType(
  543. Error.INVALID_INPUT).setDetail("outputForm", options.outputForm);
  544. }
  545. }
  546. return rval;
  547. }
  548. public static Object fromRDF(Object input, RDFParser parser) throws JSONLDProcessingError {
  549. return fromRDF(input, new Options(""), parser);
  550. }
  551. public static Object simplify(Object input, Options opts) throws JSONLDProcessingError {
  552. // TODO Auto-generated method stub
  553. if (opts.base == null) {
  554. opts.base = "";
  555. }
  556. return new JSONLDProcessor(opts).simplify(input);
  557. }
  558. public static Object simplify(Object input) throws JSONLDProcessingError {
  559. return simplify(input, new Options(""));
  560. }
  561. }