/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.hawkular.inventory.api.model;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.hawkular.inventory.paths.SegmentType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Represents structured data. This is used to store configuration, operation params, etc.
*
* <p>Instances of structured data are built using a builder pattern starting with the {@link #get()} method.
*
* @author Lukas Krejci
* @since 0.3.0
*/
@ApiModel(value = "JSON", description = "Just a free form JSON.")
public final class StructuredData {
public static final SegmentType SEGMENT_TYPE = SegmentType.sd;
@ApiModelProperty(hidden = true)
private final Serializable value;
public static Builder get() {
return new Builder();
}
private StructuredData(Serializable value) {
this.value = value;
}
/**
* Returns the value without much type information. If you need to specialize your behavior based on the
* type of the value, consider using {@link #accept(Visitor, Object)} method.
*
* @return the value (possibly null for an undefined structured data)
*/
@ApiModelProperty(hidden = true)
public Serializable getValue() {
return value;
}
/**
* @return true, if this data represents no value
*/
@ApiModelProperty(hidden = true)
public boolean isUndefined() {
return value == null;
}
@SuppressWarnings("unchecked")
public <R, P> R accept(Visitor<R, P> visitor, P parameter) {
if (value == null) {
return visitor.visitUndefined(parameter);
} else if (value instanceof java.util.List) {
return visitor.visitList((List<StructuredData>) value, parameter);
} else if (value instanceof java.util.Map) {
return visitor.visitMap((java.util.Map<String, StructuredData>) value, parameter);
} else if (value instanceof Boolean) {
return visitor.visitBool((Boolean) value, parameter);
} else if (value instanceof Long) {
return visitor.visitIntegral((Long) value, parameter);
} else if (value instanceof Double) {
return visitor.visitFloatingPoint((Double) value, parameter);
} else if (value instanceof String) {
return visitor.visitString((String) value, parameter);
} else {
return visitor.visitUnknown(value, parameter);
}
}
/**
* @return the value as a boolean
* @throws NullPointerException if the value is undefined
* @throws ClassCastException if the value is not a boolean
*/
public boolean bool() {
return (Boolean) value;
}
/**
* @return the value as a long
* @throws NullPointerException if the value is undefined
* @throws ClassCastException if the value is not integral
*/
public long integral() {
return (Long) value;
}
/**
* @return the value as a double
* @throws NullPointerException if the value is undefined
* @throws ClassCastException if the value is not a floating point number
*/
public double floatingPoint() {
return (Double) value;
}
/**
* @return the value as a string
* @throws NullPointerException if the value is undefined
* @throws ClassCastException if the value is not a string
*/
public String string() {
if (value == null) {
throw new NullPointerException();
}
return (String) value;
}
/**
* @return the value as a list
* @throws NullPointerException if the value is undefined
* @throws ClassCastException if the value is not a list
*/
@SuppressWarnings("unchecked")
public List<StructuredData> list() {
if (value == null) {
throw new NullPointerException();
}
return (List<StructuredData>) value;
}
/**
* @return the value as a list
* @throws NullPointerException if the value is undefined
* @throws ClassCastException if the value is not a map
*/
@SuppressWarnings("unchecked")
public Map<String, StructuredData> map() {
if (value == null) {
throw new NullPointerException();
}
return (Map<String, StructuredData>) value;
}
@ApiModelProperty(hidden = true)
public Type getType() {
return this.accept(new Visitor<Type, Void>() {
@Override
public Type visitBool(boolean value, Void parameter) {
return Type.bool;
}
@Override
public Type visitIntegral(long value, Void parameter) {
return Type.integral;
}
@Override
public Type visitFloatingPoint(double value, Void parameter) {
return Type.floatingPoint;
}
@Override
public Type visitString(String value, Void parameter) {
return Type.string;
}
@Override
public Type visitUndefined(Void parameter) {
return Type.undefined;
}
@Override
public Type visitList(List<StructuredData> value, Void parameter) {
return Type.list;
}
@Override
public Type visitMap(Map<String, StructuredData> value, Void parameter) {
return Type.map;
}
@Override
public Type visitUnknown(Serializable value, Void parameter) {
throw new AssertionError("Inconsistent StructuredData implementation -" +
" cannot determine type of value: " + value);
}
}, null);
}
/**
* This instance WILL NOT be modified, the updates will be present in the newly constructed instance.
*
* @return an updater object to create modification of the this instance
*/
public Updater update() {
return new Updater(value);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StructuredData)) return false;
StructuredData that = (StructuredData) o;
return Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return value != null ? value.hashCode() : 0;
}
@Override
public String toString() {
if (value == null) {
return "undefined";
} else {
return value.toString();
}
}
public String toJSON() {
StringWriter wrt = new StringWriter();
try {
writeJSON(wrt);
return wrt.toString();
} catch (IOException e) {
throw new AssertionError("IOException while writing to a StringWriter. This should never happen.", e);
}
}
private static final class TransferIOException extends RuntimeException {
public TransferIOException(IOException cause) {
super(cause);
}
@Override
public synchronized IOException getCause() {
return (IOException) super.getCause();
}
}
public void writeJSON(Appendable wrt) throws IOException {
try {
accept(new Visitor<Void, Void>() {
@Override
public Void visitBool(boolean value, Void parameter) {
try {
wrt.append(value ? "true" : "false");
return null;
} catch (IOException e) {
throw new TransferIOException(e);
}
}
@Override
public Void visitIntegral(long value, Void parameter) {
try {
wrt.append(Long.toString(value));
return null;
} catch (IOException e) {
throw new TransferIOException(e);
}
}
@Override
public Void visitFloatingPoint(double value, Void parameter) {
try {
wrt.append(Double.toString(value));
return null;
} catch (IOException e) {
throw new TransferIOException(e);
}
}
@Override
public Void visitString(String value, Void parameter) {
try {
wrt.append('"').append(value).append('"');
return null;
} catch (IOException e) {
throw new TransferIOException(e);
}
}
@Override
public Void visitUndefined(Void parameter) {
try {
wrt.append("null");
return null;
} catch (IOException e) {
throw new TransferIOException(e);
}
}
@Override
public Void visitList(List<StructuredData> value, Void parameter) {
try {
wrt.append("[");
Iterator<StructuredData> it = value.iterator();
if (it.hasNext()) {
it.next().accept(this, parameter);
}
while (it.hasNext()) {
wrt.append(",");
it.next().accept(this, parameter);
}
wrt.append("]");
return null;
} catch (IOException e) {
throw new TransferIOException(e);
}
}
@Override
public Void visitMap(Map<String, StructuredData> value, Void parameter) {
try {
BiFunction<String, Map.Entry<String, StructuredData>, Void> appender = (prefix, entry) -> {
String key = entry.getKey();
StructuredData d = entry.getValue();
try {
wrt.append(prefix).append('"').append(key).append("\":");
} catch (IOException e) {
throw new TransferIOException(e);
}
d.accept(this, parameter);
return null;
};
SortedMap<String, StructuredData> sorted = new TreeMap<>(value);
wrt.append("{");
Iterator<Map.Entry<String, StructuredData>> it = sorted.entrySet().iterator();
if (it.hasNext()) {
appender.apply("", it.next());
}
while (it.hasNext()) {
appender.apply(",", it.next());
}
wrt.append("}");
return null;
} catch (IOException e) {
throw new TransferIOException(e);
}
}
@Override
public Void visitUnknown(Serializable value, Void parameter) {
return null;
}
}, null);
} catch (TransferIOException e) {
throw e.getCause();
}
}
public enum Type {
bool, integral, floatingPoint, string, undefined, list, map
}
public interface Visitor<R, P> {
static <R, P> Visitor<R, P> bool(BiFunction<Boolean, P, R> handler) {
return new VisitorSpecialization<>(Boolean.class, handler);
}
static <R, P> Visitor<R, P> integral(BiFunction<Long, P, R> handler) {
return new VisitorSpecialization<>(Long.class, handler);
}
static <R, P> Visitor<R, P> floatingPoint(BiFunction<Double, P, R> handler) {
return new VisitorSpecialization<>(Double.class, handler);
}
static <R, P> Visitor<R, P> string(BiFunction<String, P, R> handler) {
return new VisitorSpecialization<>(String.class, handler);
}
static <R, P> Visitor<R, P> undefined(Function<P, R> handler) {
return new VisitorSpecialization<>(Void.class, (v, p) -> handler.apply(p));
}
static <R, P> Visitor<R, P> list(BiFunction<List<StructuredData>, P, R> handler) {
return new VisitorSpecialization<>(List.class, handler);
}
static <R, P> Visitor<R, P> map(BiFunction<Map<String, StructuredData>, P, R> handler) {
return new VisitorSpecialization<>(Map.class, handler);
}
R visitBool(boolean value, P parameter);
R visitIntegral(long value, P parameter);
R visitFloatingPoint(double value, P parameter);
R visitString(String value, P parameter);
R visitUndefined(P parameter);
R visitList(List<StructuredData> value, P parameter);
R visitMap(Map<String, StructuredData> value, P parameter);
R visitUnknown(Serializable value, P parameter);
class Simple<R, P> implements Visitor<R, P> {
protected R defaultAction(Serializable value, P parameter) {
return null;
}
@Override
public R visitBool(boolean value, P parameter) {
return defaultAction(value, parameter);
}
@Override
public R visitIntegral(long value, P parameter) {
return defaultAction(value, parameter);
}
@Override
public R visitFloatingPoint(double value, P parameter) {
return defaultAction(value, parameter);
}
@Override
public R visitString(String value, P parameter) {
return defaultAction(value, parameter);
}
@Override
public R visitUndefined(P parameter) {
return defaultAction(null, parameter);
}
@Override
public R visitUnknown(Serializable value, P parameter) {
return defaultAction(value, parameter);
}
@Override
public R visitList(List<StructuredData> value, P parameter) {
return defaultAction((Serializable) value, parameter);
}
@Override
public R visitMap(Map<String, StructuredData> value, P parameter) {
return defaultAction((Serializable) value, parameter);
}
}
}
private static class VisitorSpecialization<R, P, T> extends Visitor.Simple<R, P> {
private final Class<?> expectedType;
private final BiFunction<T, P, R> handler;
VisitorSpecialization(Class<?> expectedType, BiFunction<T, P, R> handler) {
this.expectedType = expectedType;
this.handler = handler;
}
@SuppressWarnings("unchecked")
@Override
protected R defaultAction(Serializable value, P parameter) {
if (value == null && expectedType != Void.class) {
throw new IllegalArgumentException("Expected a value of type " + expectedType.getSimpleName()
+ " but got undefined");
} else if (value != null && !expectedType.isAssignableFrom(value.getClass())) {
throw new IllegalArgumentException("Expected a value of type " + expectedType.getSimpleName()
+ " but got " + value.getClass().getSimpleName());
} else {
return handler.apply((T) value, parameter);
}
}
}
public static final class Builder {
private Builder() {
}
public StructuredData bool(Boolean value) {
return new StructuredData(value);
}
public StructuredData integral(Long value) {
return new StructuredData(value);
}
public StructuredData floatingPoint(Double value) {
return new StructuredData(value);
}
public StructuredData string(String value) {
return new StructuredData(value);
}
public StructuredData undefined() {
return new StructuredData(null);
}
public ListBuilder list() {
return new ListBuilder();
}
public MapBuilder map() {
return new MapBuilder();
}
}
public static final class ListBuilder extends AbstractListBuilder<ListBuilder> {
public ListBuilder() {
super(new ArrayList<>());
}
public StructuredData build() {
return new StructuredData(list);
}
}
public static final class MapBuilder extends AbstractMapBuilder<MapBuilder> {
public MapBuilder() {
super(new LinkedHashMap<>());
}
public StructuredData build() {
return new StructuredData(map);
}
}
public abstract static class AbstractHierarchyBuilder {
protected abstract void apply(Object context, StructuredData value);
@SuppressWarnings("unchecked")
protected <T> T castThis() {
return (T) this;
}
}
public abstract static class AbstractListBuilder<This extends AbstractListBuilder<This>>
extends AbstractHierarchyBuilder {
protected final ArrayList<StructuredData> list;
AbstractListBuilder(ArrayList<StructuredData> list) {
this.list = list;
}
public This addBool(boolean value) {
list.add(new StructuredData(value));
return castThis();
}
public This addIntegral(long value) {
list.add(new StructuredData(value));
return castThis();
}
public This addFloatingPoint(double value) {
list.add(new StructuredData(value));
return castThis();
}
public This addString(String value) {
list.add(new StructuredData(value));
return castThis();
}
public This addUndefined() {
list.add(new StructuredData(null));
return castThis();
}
public InnerListBuilder<This> addList() {
return new InnerListBuilder<>(new ArrayList<>(), castThis(), null);
}
public InnerMapBuilder<This> addMap() {
return new InnerMapBuilder<>(new LinkedHashMap<>(), castThis(), null);
}
@Override
protected void apply(Object context, StructuredData value) {
list.add(value);
}
}
public static final class InnerListBuilder<Parent extends AbstractHierarchyBuilder>
extends AbstractListBuilder<InnerListBuilder<Parent>> {
private final Parent parentBuilder;
private final Object parentContext;
InnerListBuilder(ArrayList<StructuredData> list, Parent parentBuilder, Object parentContext) {
super(list);
this.parentBuilder = parentBuilder;
this.parentContext = parentContext;
}
public Parent closeList() {
parentBuilder.apply(parentContext, new StructuredData((Serializable) Collections.unmodifiableList(list)));
return parentBuilder;
}
}
public abstract static class AbstractMapBuilder<This extends AbstractMapBuilder<This>>
extends AbstractHierarchyBuilder {
protected final LinkedHashMap<String, StructuredData> map;
AbstractMapBuilder(LinkedHashMap<String, StructuredData> map) {
this.map = map;
}
public This putBool(String key, boolean value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putIntegral(String key, long value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putFloatingPoint(String key, double value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putString(String key, String value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putUndefined(String key) {
map.put(key, new StructuredData(null));
return castThis();
}
public InnerListBuilder<This> putList(String key) {
return new InnerListBuilder<>(new ArrayList<>(), castThis(), key);
}
public InnerMapBuilder<This> putMap(String key) {
return new InnerMapBuilder<>(new LinkedHashMap<>(), castThis(), key);
}
@Override
protected void apply(Object context, StructuredData value) {
map.put((String) context, value);
}
}
public static final class InnerMapBuilder<Parent extends AbstractHierarchyBuilder>
extends AbstractMapBuilder<InnerMapBuilder<Parent>> {
private final Parent parentBuilder;
private final Object parentContext;
InnerMapBuilder(LinkedHashMap<String, StructuredData> map, Parent parentBuilder, Object parentContext) {
super(map);
this.parentBuilder = parentBuilder;
this.parentContext = parentContext;
}
public Parent closeMap() {
parentBuilder.apply(parentContext, new StructuredData((Serializable) Collections.unmodifiableMap(map)));
return parentBuilder;
}
}
public static final class Updater {
private final Serializable origValue;
private Updater(Serializable origValue) {
this.origValue = origValue;
}
public StructuredData toBool(boolean value) {
return new StructuredData(value);
}
public StructuredData toIntegral(long value) {
return new StructuredData(value);
}
public StructuredData toFloatingPoint(double value) {
return new StructuredData(value);
}
public StructuredData toString(String value) {
return new StructuredData(value);
}
public StructuredData toUndefined() {
return new StructuredData(null);
}
@SuppressWarnings("unchecked")
public ListUpdater toList() {
if (origValue instanceof List) {
return new ListUpdater(new ArrayList<>((List<StructuredData>) origValue));
} else {
return new ListUpdater(new ArrayList<>());
}
}
@SuppressWarnings("unchecked")
public MapUpdater toMap() {
if (origValue instanceof Map) {
return new MapUpdater(new LinkedHashMap<>((Map<String, StructuredData>) origValue));
} else {
return new MapUpdater(new LinkedHashMap<>());
}
}
}
public abstract static class AbstractListUpdater<This extends AbstractListUpdater<This>>
extends AbstractHierarchyBuilder {
protected final ArrayList<StructuredData> list;
private AbstractListUpdater(ArrayList<StructuredData> list) {
this.list = list;
}
public This clear() {
list.clear();
return castThis();
}
public This remove(int index) {
list.remove(index);
return castThis();
}
public This addBool(boolean value) {
list.add(new StructuredData(value));
return castThis();
}
public This setBool(int index, boolean value) {
list.set(index, new StructuredData(value));
return castThis();
}
public This addIntegral(long value) {
list.add(new StructuredData(value));
return castThis();
}
public This setIntegral(int index, long value) {
list.set(index, new StructuredData(value));
return castThis();
}
public This addFloatingPoint(double value) {
list.add(new StructuredData(value));
return castThis();
}
public This setFloatingPoint(int index, double value) {
list.set(index, new StructuredData(value));
return castThis();
}
public This addString(String value) {
list.add(new StructuredData(value));
return castThis();
}
public This setString(int index, String value) {
list.set(index, new StructuredData(value));
return castThis();
}
public This addUndefined() {
list.add(new StructuredData(null));
return castThis();
}
public This setUndefined(int index) {
list.set(index, new StructuredData(null));
return castThis();
}
public InnerListUpdater<This> addList() {
return new InnerListUpdater<>(new ArrayList<>(), castThis(), null);
}
public InnerListUpdater<This> updateList(int index) {
@SuppressWarnings("unchecked")
ArrayList<StructuredData> list = this.list.get(index).value instanceof List
? new ArrayList<>((List<StructuredData>) this.list.get(index).value)
: new ArrayList<>();
return new InnerListUpdater<>(list, castThis(), index);
}
public InnerMapUpdater<This> addMap() {
return new InnerMapUpdater<>(new LinkedHashMap<>(), castThis(), null);
}
public InnerMapUpdater<This> updateMap(int index) {
@SuppressWarnings("unchecked")
LinkedHashMap<String, StructuredData> map = this.list.get(index).value instanceof Map
? new LinkedHashMap<>((Map<String, StructuredData>) this.list.get(index).value)
: new LinkedHashMap<>();
return new InnerMapUpdater<>(map, castThis(), index);
}
@Override
protected void apply(Object context, StructuredData value) {
Integer ctx = (Integer) context;
if (ctx == null) {
list.add(value);
} else {
list.set(ctx, value);
}
}
}
private abstract static class AbstractMapUpdater<This extends AbstractMapUpdater<This>>
extends AbstractHierarchyBuilder {
protected final LinkedHashMap<String, StructuredData> map;
private AbstractMapUpdater(LinkedHashMap<String, StructuredData> map) {
this.map = map;
}
public This clear() {
this.map.clear();
return castThis();
}
public This remove(String key) {
this.map.remove(key);
return castThis();
}
public This putBool(String key, boolean value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putIntegral(String key, long value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putFloatingPoint(String key, double value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putString(String key, String value) {
map.put(key, new StructuredData(value));
return castThis();
}
public This putUndefined(String key) {
map.put(key, new StructuredData(null));
return castThis();
}
public InnerListUpdater<This> updateList(String key) {
@SuppressWarnings("unchecked")
ArrayList<StructuredData> list = this.map.get(key).value instanceof List
? new ArrayList<>((List<StructuredData>) this.map.get(key).value)
: new ArrayList<>();
return new InnerListUpdater<>(list, castThis(), key);
}
public InnerMapUpdater<This> updateMap(String key) {
@SuppressWarnings("unchecked")
LinkedHashMap<String, StructuredData> map =
null != this.map.get(key) && this.map.get(key).value instanceof Map
? new LinkedHashMap<>((Map<String, StructuredData>) this.map.get(key).value)
: new LinkedHashMap<>();
return new InnerMapUpdater<>(map, castThis(), key);
}
@Override
protected void apply(Object context, StructuredData value) {
map.put((String) context, value);
}
}
public static final class ListUpdater extends AbstractListUpdater<ListUpdater> {
public ListUpdater(ArrayList<StructuredData> list) {
super(list);
}
public StructuredData build() {
return new StructuredData((Serializable) Collections.unmodifiableList(list));
}
}
public static final class MapUpdater extends AbstractMapUpdater<MapUpdater> {
public MapUpdater(LinkedHashMap<String, StructuredData> map) {
super(map);
}
public StructuredData build() {
return new StructuredData((Serializable) Collections.unmodifiableMap(map));
}
}
public static final class InnerListUpdater<Parent extends AbstractHierarchyBuilder>
extends AbstractListUpdater<InnerListUpdater<Parent>> {
private final Parent parent;
private final Object context;
public InnerListUpdater(ArrayList<StructuredData> list, Parent parent, Object context) {
super(list);
this.parent = parent;
this.context = context;
}
public Parent closeList() {
parent.apply(context, new StructuredData((Serializable) Collections.unmodifiableList(list)));
return parent;
}
}
public static final class InnerMapUpdater<Parent extends AbstractHierarchyBuilder>
extends AbstractMapUpdater<InnerMapUpdater<Parent>> {
private final Parent parent;
private final Object context;
public InnerMapUpdater(LinkedHashMap<String, StructuredData> map, Parent parent, Object context) {
super(map);
this.parent = parent;
this.context = context;
}
public Parent closeMap() {
parent.apply(context, new StructuredData((Serializable) Collections.unmodifiableMap(map)));
return parent;
}
}
}