/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.jena.atlas.json ;
import java.math.BigDecimal ;
import java.util.ArrayDeque ;
import java.util.Deque ;
import java.util.Objects;
import org.apache.jena.atlas.logging.Log ;
/* Builder pattern for JSON.
* The JsonValue built can be an array or object at the outmost leve, but not a atomic value.
*/
public class JsonBuilder {
// If not an array or object.
private JsonValue builtValue = null ;
private static final String NoMarker = "" ;
private Deque<String> markers = new ArrayDeque<>() ;
private Deque<JsonArray> arrays = new ArrayDeque<>() ;
private Deque<JsonObject> objects = new ArrayDeque<>() ;
private static enum State {
ARRAY, OBJECT
}
private Deque<State> stack = new ArrayDeque<>() ;
// The depth of this stack is the object depth. key: { key: ... }
private Deque<String> keys = new ArrayDeque<>() ;
public static JsonBuilder create() { return new JsonBuilder() ; }
public JsonBuilder() {
}
public JsonValue build() {
if ( builtValue == null ) {
if ( objects.isEmpty() && arrays.isEmpty() )
throw new JsonException("Alignment error: no object or array started") ;
throw new JsonException("Alignment error: unfinished outer object or array") ;
}
return builtValue ;
}
public void reset() {
builtValue = null ;
stack.clear() ;
objects.clear() ;
keys.clear();
arrays.clear();
}
public JsonBuilder startObject() { return startObject(NoMarker) ; }
public JsonBuilder startObject(String startMarker) {
markers.push(startMarker);
objects.push(new JsonObject()) ;
stack.push(State.OBJECT) ;
return this ;
}
public JsonBuilder finishObject() { return finishObject(NoMarker) ; }
public JsonBuilder finishObject(String finishMarker) {
if ( stack.isEmpty() )
throw new JsonException("Alignment error : already built outer-most object or array") ;
State state = stack.pop() ;
if ( state != State.OBJECT )
throw new JsonException("JSON build error : not in an object") ;
JsonValue value = objects.pop() ;
maybeObjectOrArray(value) ;
if ( stack.isEmpty() )
builtValue = value ;
String startMarker = markers.pop();
if ( ! Objects.equals(startMarker, finishMarker) )
throw new JsonException("JSON build error : start/finish alignment error: start="+startMarker+" finish="+finishMarker) ;
return this ;
}
public JsonBuilder startArray() {
arrays.push(new JsonArray()) ;
stack.push(State.ARRAY) ;
return this ;
}
public JsonBuilder finishArray() {
if ( stack.isEmpty() )
throw new JsonException("Alignment error : already built outer-most object or array") ;
State state = stack.pop() ;
if ( state != State.ARRAY )
throw new JsonException("JSON build error : not in an array") ;
JsonValue value = arrays.pop() ;
maybeObjectOrArray(value) ;
if ( stack.isEmpty() )
builtValue = value ;
return this ;
}
public JsonBuilder key(String key) {
State state = stack.peek() ;
if ( state != State.OBJECT )
throw new JsonException("JSON build error : not in an object") ;
keys.push(key) ;
return this ;
}
private void maybeObjectOrArray(JsonValue value) {
if ( stack.size() == 0 )
// Error.
return ;
switch (stack.peek()) {
case OBJECT : {
String k = keys.pop() ;
JsonObject obj = objects.peek() ;
if ( obj.hasKey(k) )
Log.warn(this, "Duplicate key '" + k + "' for object") ;
obj.put(k, value) ;
return ;
}
case ARRAY : {
arrays.peek().add(value) ;
return ;
}
}
}
public JsonBuilder value(JsonValue v) {
maybeObjectOrArray(v) ;
return this ;
}
public JsonBuilder value(boolean b) {
JsonValue value = new JsonBoolean(b) ;
maybeObjectOrArray(value) ;
return this ;
}
public JsonBuilder value(BigDecimal decimal) {
JsonValue value = JsonNumber.value(decimal) ;
maybeObjectOrArray(value) ;
return this ;
}
public JsonBuilder value(double d) {
JsonValue value = JsonNumber.value(d) ;
maybeObjectOrArray(value) ;
return this ;
}
public JsonBuilder value(long val) {
JsonValue value = JsonNumber.value(val) ;
maybeObjectOrArray(value) ;
return this ;
}
public JsonBuilder valueNull() {
JsonValue value = JsonNull.instance ;
maybeObjectOrArray(value) ;
return this ;
}
public JsonBuilder value(String string) {
JsonValue value = new JsonString(string) ;
maybeObjectOrArray(value) ;
return this ;
}
}