Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.github.jsonldjava.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* An IdentifierIssuer issues unique identifiers, keeping track of any
* previously issued identifiers. Used by the URDNA2015 normalization algorithm.
*/
public class IdentifierIssuer implements Cloneable {

private String prefix;
private int counter;
private Map<String, String> existing;
private List<String> order;

public IdentifierIssuer(String prefix) {
this.prefix = prefix;
this.counter = 0;
this.existing = new HashMap<>();
this.order = new ArrayList<>();
}

/**
* Gets the new identifier for the given old identifier, where if no old
* identifier is given a new identifier will be generated.
*
* @param old the old identifier to get the new identifier for (can be null)
* @return the new identifier
*/
public String getId() {
return this.getId(null);
}

public String getId(String old) {
if (old != null && existing.containsKey(old)) {
return existing.get(old);
}

String id = this.prefix + Integer.toString(counter);
this.counter += 1;

if (old != null) {
this.existing.put(old, id);
this.order.add(old);
}

return id;
}

/**
* Returns True if the given old identifier has already been assigned a
* new identifier.
*
* @param old the old identifier to check
* @return True if the old identifier has been assigned a new identifier,
* False if not
*/
public boolean hasID(String old) {
return this.existing.containsKey(old);
}

public List<String> getOrder() {
return this.order;
}

public String getPrefix() {
return this.prefix;
}

@Override
public Object clone() {
try {
IdentifierIssuer cloned = (IdentifierIssuer) super.clone();
cloned.existing = new HashMap<>(this.existing);
cloned.order = new ArrayList<>(this.order);
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}

67 changes: 57 additions & 10 deletions core/src/main/java/com/github/jsonldjava/core/JsonLdApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -2185,15 +2185,50 @@ public RDFDataset toRDF() throws JsonLdError {
* If there was an error while normalizing.
*/
public Object normalize(Map<String, Object> dataset) throws JsonLdError {
return normalize(dataset, opts);
}

/**
* Performs RDF normalization on the given JSON-LD input.
*
* @param dataset
* the expanded JSON-LD object to normalize.
* @param options
* the JSON-LD options containing the algorithm to use.
* @return The normalized JSON-LD object
* @throws JsonLdError
* If there was an error while normalizing.
*/
public Object normalize(Map<String, Object> dataset, JsonLdOptions options) throws JsonLdError {
if (options.getAlgorithm().equals(JsonLdOptions.URGNA2012)) {
return normalizeURGN2012(dataset);
} else if (options.getAlgorithm().equals(JsonLdOptions.URDNA2015)) {
return normalizeURDNA2015(dataset, options);
}

return null;
}

/**
* Normalizes using the URGNA2012 algorithm.
*
* @param dataset
* the expanded JSON-LD object to normalize.
* @return The normalized JSON-LD object
* @throws JsonLdError
* If there was an error while normalizing.
*/
public Object normalizeURGN2012(Map<String, Object> dataset) throws JsonLdError {
// create quads and map bnodes to their associated quads
final List<Object> quads = new ArrayList<Object>();
final Map<String, Object> bnodes = newMap();

for (String graphName : dataset.keySet()) {
final List<Map<String, Object>> triples = (List<Map<String, Object>>) dataset
.get(graphName);
final List<Map<String, Object>> triples = (List<Map<String, Object>>) dataset.get(graphName);
if (JsonLdConsts.DEFAULT.equals(graphName)) {
graphName = null;
}

for (final Map<String, Object> quad : triples) {
if (graphName != null) {
if (graphName.indexOf("_:") == 0) {
Expand All @@ -2212,28 +2247,40 @@ public Object normalize(Map<String, Object> dataset) throws JsonLdError {

final String[] attrs = new String[] { "subject", "object", "name" };
for (final String attr : attrs) {
if (quad.containsKey(attr) && "blank node"
.equals(((Map<String, Object>) quad.get(attr)).get("type"))) {
final String id = (String) ((Map<String, Object>) quad.get(attr))
.get("value");
if (quad.containsKey(attr)
&& "blank node".equals(((Map<String, Object>) quad.get(attr)).get("type"))) {
final String id = (String) ((Map<String, Object>) quad.get(attr)).get("value");
if (!bnodes.containsKey(id)) {
bnodes.put(id, new LinkedHashMap<String, List<Object>>() {
{
put("quads", new ArrayList<Object>());
}
});
}
((List<Object>) ((Map<String, Object>) bnodes.get(id)).get("quads"))
.add(quad);
((List<Object>) ((Map<String, Object>) bnodes.get(id)).get("quads")).add(quad);
}
}
}
}

// mapping complete, start canonical naming
final NormalizeUtils normalizeUtils = new NormalizeUtils(quads, bnodes,
new UniqueNamer("_:c14n"), opts);
final NormalizeUtils normalizeUtils = new NormalizeUtils(quads, bnodes, new UniqueNamer("_:c14n"), opts);
return normalizeUtils.hashBlankNodes(bnodes.keySet());
}

/**
* Normalizes using the URDNA2015 algorithm.
*
* @param dataset
* the expanded JSON-LD object to normalize.
* @param options
* the JSON-LD options to use.
* @return The normalized JSON-LD object
* @throws JsonLdError
* If there was an error while normalizing.
*/
public Object normalizeURDNA2015(Map<String, Object> dataset, JsonLdOptions options) throws JsonLdError {
return new Urdna2015(dataset, options).normalize();
}

}
16 changes: 16 additions & 0 deletions core/src/main/java/com/github/jsonldjava/core/JsonLdOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public class JsonLdOptions {

public static final boolean DEFAULT_COMPACT_ARRAYS = true;

// Normalization algorithm constants
public static final String URGNA2012 = "URGNA2012";
public static final String URDNA2015 = "URDNA2015";

/**
* Constructs an instance of JsonLdOptions using an empty base.
*/
Expand Down Expand Up @@ -61,6 +65,7 @@ public JsonLdOptions copy() {
copy.setUseRdfType(useRdfType);
copy.setUseNativeTypes(useNativeTypes);
copy.setProduceGeneralizedRdf(produceGeneralizedRdf);
copy.setAlgorithm(algorithm);
copy.format = format;
copy.useNamespaces = useNamespaces;
copy.outputForm = outputForm;
Expand Down Expand Up @@ -110,6 +115,9 @@ public JsonLdOptions copy() {
Boolean useNativeTypes = false;
private boolean produceGeneralizedRdf = false;

// Normalization algorithm option
private String algorithm = URGNA2012;

public String getEmbed() {
switch (this.embed) {
case ALWAYS:
Expand Down Expand Up @@ -294,6 +302,14 @@ public void setDocumentLoader(DocumentLoader documentLoader) {
this.documentLoader = documentLoader;
}

public String getAlgorithm() {
return algorithm;
}

public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}

// TODO: THE FOLLOWING ONLY EXIST SO I DON'T HAVE TO DELETE A LOT OF CODE,
// REMOVE IT WHEN DONE
public String format = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,85 @@ private static String encodeHex(final byte[] data) {
return rval;
}

/**
* Helper methods for URDNA2015 algorithm
*/

/**
* Sorts map keys and returns them as a list
*/
public static List<String> sortMapKeys(Map<String, ?> map) {
List<String> keyList = new ArrayList<>(map.keySet());
Collections.sort(keyList);
return keyList;
}

/**
* Sorts a list of maps by their keys
*/
public static List<Map<String, Object>> sortMapList(List<Map<String, Object>> mapList) {
return sortMapList(mapList, true);
}

/**
* Sorts a list of maps by their keys, with optional recursion
*/
public static List<Map<String, Object>> sortMapList(List<Map<String, Object>> mapList, boolean recursion) {
List<Map<String, Object>> sortedMapsList = new ArrayList<>();
for (Map<String, Object> map : mapList) {
Map<String, Object> newMap = new LinkedHashMap<>();
List<String> keyList = new ArrayList<>(map.keySet());
Collections.sort(keyList);

for (String key : keyList) {
newMap.put(key, map.get(key));
}
sortedMapsList.add(newMap);
}
if (recursion) {
return sortMapList(sortedMapsList, false);
}
return sortedMapsList;
}

/**
* SHA-256 hash for a list of N-Quads strings
*/
public static String sha256HashnQuads(List<String> nquads) {
StringBuilder stringToHash = new StringBuilder();
for (String nquad : nquads) {
stringToHash.append(nquad);
}
return sha256Hash(stringToHash.toString().getBytes());
}

/**
* SHA-256 hash for a string
*/
public static String sha256Hash(String string) {
return sha256Hash(string.getBytes());
}

/**
* SHA-256 hash for byte array
*/
public static String sha256Hash(byte[] bytes) {
return encodeHex(sha256Raw(bytes));
}

/**
* SHA-256 raw hash for byte array
*/
public static byte[] sha256Raw(byte[] bytes) {
try {
MessageDigest sha = MessageDigest.getInstance("SHA-256");
sha.update(bytes);
return sha.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

/**
* A helper function that gets the blank node name from an RDF quad node
* (subject or object). If the node is a blank node and its value does not
Expand All @@ -498,7 +577,7 @@ private static String getAdjacentBlankNodeName(Map<String, Object> node, String
: null;
}

private static class Permutator {
public static class Permutator {

private final List<String> list;
private boolean done;
Expand Down
Loading