blob: b82f3f213546c36bfa678de2954e318b6cc6ed37 [file] [log] [blame]
/*
* Copyright (c) 2020 Kentyou.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Kentyou - initial API and implementation
*/
package org.eclipse.sensinact.gateway.util.json;
import org.eclipse.sensinact.gateway.util.CastUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A JSONObject builder
*
* @author <a href="mailto:christophe.munilla@cea.fr">Christophe Munilla</a>
*/
public class JSONMapper {
static final Pattern PATH_PATTERN = Pattern.compile("\\/?([^\\/]+)");
static final Pattern PATH_ELEMENT_PATTERN = Pattern.compile("([^\\[\\]]+)?(?:\\[([^\\[\\]]+)(?:\\]))?");
static final Pattern PATH_CONDITION_PATTERN = Pattern.compile("([^\\(\\)]+)|((\\()\\1(\\)))");
static final Pattern PATH_EXPRESSION_PATTERN = Pattern.compile("(((&|\\|)?([^&\\|]+))?)");
static final Pattern PATH_CONSTRAINT_PATTERN = Pattern.compile("([^=<>!]+)((=|<|>|(!=))([^=<>!]+))");
private Map<String, JSONPath> mapping;
private Map<String, Object> patterns;
private ClassLoader classloader;
/**
* Constructor
*/
private JSONMapper() {
//{
//"mykey1" : [
//"55s",
//"aaaa" ,
//"9",
//["tre", "44"],
//{"myembeddedkey1" : "myembeddedvalue1","myembeddedkey2" : "myembeddedvalue2"},
//12
//],
//"mykey2" : "myvalue2",
//"mykey3" : {"myembeddedkey3" : "myembeddedvalue3","myembeddedkey4" : "myembeddedvalue4"},
//"mykey4" : 88.5}
//("/*/$(deep:1))"
//("/mykey1/$(index:3))"
//("/mykey1)"
//("/$(pattern:mykey[2|4]))"
//("/mykey3/myembeddedkey4)"
this(Thread.currentThread().getContextClassLoader());
}
/**
* Constructor
*/
private JSONMapper(ClassLoader classloader) {
this.mapping = new HashMap<String, JSONPath>();
this.patterns = new HashMap<String, Object>();
this.classloader = classloader;
}
/**
* Constructor
*
* @param mapping
*/
public JSONMapper(ClassLoader classloader, JSONObject mapping) {
this(classloader);
int length = mapping == null ? 0 : mapping.length();
if (length > 0) {
int index = 0;
String[] names = JSONObject.getNames(mapping);
for (; index < length; index++) {
this.mapping.put(names[index], new JSONPath(mapping.getString(names[index])));
}
}
}
/**
* Constructor
*
* @param mapping
*/
public JSONMapper(/*Mediator mediator,*/ Map<String, String> mapping) {
this(/*mediator*/);
int length = mapping == null ? 0 : mapping.size();
if (length > 0) {
String key = null;
for (Iterator<String> iterator = mapping.keySet().iterator(); iterator.hasNext(); key = iterator.next()) {
this.mapping.put(key, new JSONPath(mapping.get(key)));
}
}
}
/**
* Rebuilds and returns a JSONObject whose keys are the
* ones of this JSONMapper and the values are the ones
* extracted from the JSON object (JSONObject or JSONArray)
* passed as parameter.
*
* @param json the JSON object (JSONObject or JSONArray) from which
* the values of the returned JSONObject are extracted
* @return the builds JSONObject mapping the keys of this
* JSONMapper to the values of the specified JSON object
*/
public JSONObject parse(Object json) {
String pattern = null;
JSONObject result = new JSONObject();
for (Iterator<String> iterator = this.mapping.keySet().iterator(); iterator.hasNext(); ) {
String key = iterator.next();
JSONPath jsonPath = this.mapping.get(key);
Iterator<String> patternIterator = this.patterns.keySet().iterator();
while (patternIterator.hasNext()) {
pattern = patternIterator.next();
Object current = null;
if (pattern != null) {
Object subject = this.patterns.get(pattern);
subject = subject == null ? json : subject;
current = this.mapping.get(key).extract(subject);
this.patterns.put(pattern, current);
} else {
current = this.mapping.get(key).extract(json);
}
result.put(key, current);
}
}
return result;
}
/**
*
*/
final class JSONPath {
List<JSONPathElement> pathElements;
JSONPath(String path) {
pathElements = new LinkedList<JSONPathElement>();
Matcher matcher = JSONMapper.PATH_PATTERN.matcher(path);
while (matcher.find()) {
this.pathElements.add(new JSONPathElement(matcher.group()));
}
}
/**
* Extracts the object from the JSON one passed as parameter
* (JSONObject or JSONArray) according to this path.
*
* @param json the JSON object (JSONObject or JSONArray) from which
* to extract the search element(s)
* @param json
* @return the extracted object
*/
private Object extract(Object json) {
if (json == null || this.pathElements.size() == 0) {
return null;
}
Object currentObject = json;
Iterator<JSONPathElement> iterator = this.pathElements.iterator();
while (iterator.hasNext()) {
currentObject = iterator.next().extract(currentObject);
}
return currentObject;
}
}
/**
*
*/
final class JSONPathElement {
String element;
JSONPathCondition condition;
JSONPathElement(String pathElement) {
Matcher matcher = null;
if (!(matcher = JSONMapper.PATH_ELEMENT_PATTERN.matcher(pathElement)).matches()) {
return;
}
element = matcher.group(1);
String condition = null;
if ((condition = matcher.group(2)) != null) {
//for now only '=' operator available
//if no operator the condition is evaluated as a one of presence
//TODO : build a real condition/use constraint
this.condition = new JSONPathCondition(condition);
}
}
/**
* Extracts the object from the JSON one passed as parameter
* (JSONObject or JSONArray) according to this path element.
*
* @param json the JSON object (JSONObject or JSONArray) from which
* to extract the search element(s)
* @return the extracted object
*/
private Object extract(Object json) {
if (json == null) {
return null;
}
if (JSONArray.class.isAssignableFrom(json.getClass())) {
JSONArray operand = (JSONArray) json;
if (this.element != null) {
JSONArray resultArray = new JSONArray();
int index = 0;
int length = operand.length();
for (; index < length; index++) {
Object result = extract(operand.get(index));
if (result != null) {
resultArray.put(result);
}
}
return resultArray;
}
if (this.condition != null) {
return this.condition.apply(json);
}
} else if (JSONObject.class.isAssignableFrom(json.getClass())) {
if (this.element != null) {
Object object = ((JSONObject) json).opt(this.element);
if (this.condition != null) {
return this.condition.apply(object);
} else {
return object;
}
}
if (this.condition != null) {
this.condition.apply(json);
}
}
return null;
}
private class JSONPathCondition {
String key = null;
String value = null;
JSONPathCondition(String condition) {
char[] conditionChars = condition == null ? new char[0] : condition.toCharArray();
int index = 0;
int length = conditionChars.length;
length += length > 0 ? 1 : 0;
char[] chars = new char[length];
if (length > 0) {
System.arraycopy(conditionChars, 0, chars, 0, conditionChars.length);
chars[length - 1] = '\0';
}
StringBuilder sb = new StringBuilder();
for (; ; ) {
if (index == chars.length) {
break;
}
char c = chars[index++];
switch (c) {
case '=':
key = sb.toString();
sb = new StringBuilder();
break;
case ' ':
break;
case '\0':
value = sb.toString();
break;
default:
sb.append(c);
}
}
}
public Object apply(Object json) {
if (JSONArray.class.isAssignableFrom(json.getClass())) {
JSONArray jsonArray = (JSONArray) json;
if (key == null) {
if (value == null) {
return json;
}
try {
return jsonArray.get(Integer.parseInt(value));
} catch (NumberFormatException e) {
return null;
} catch (ArrayIndexOutOfBoundsException e) {
return null;
}
}
JSONArray resultArray = new JSONArray();
int index = 0;
int length = jsonArray.length();
for (; index < length; index++) {
Object result = apply(jsonArray.get(index));
if (result != null) {
resultArray.put(result);
}
}
return resultArray;
} else if (JSONObject.class.isAssignableFrom(json.getClass())) {
JSONObject jsonObject = (JSONObject) json;
if (key == null) {
if (value == null || jsonObject.opt(value) != null) {
return json;
}
return null;
}
Object result = null;
try {
if ((result = jsonObject.opt(key)) != null && result.equals(CastUtils.cast(JSONMapper.this.classloader, result.getClass(), value))) {
return json;
}
} catch (ClassCastException e) {
return null;
}
}
return null;
}
}
}
}