/**
*
* Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
*
* 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 com.speedment.common.json.internal;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import static java.util.Objects.requireNonNull;
/**
* An internal class that can serialize java objects into JSON code.
*
* @author Emil Forslund
* @since 1.0.0
*/
public final class JsonSerializer {
private final OutputStream out;
private final boolean pretty;
private final int tabSize;
private int level = 0;
public JsonSerializer(OutputStream out, boolean pretty) {
this.out = requireNonNull(out);
this.pretty = pretty;
this.tabSize = pretty ? PRETTY_TAB_SIZE : 0;
}
public void print(Object unknown) throws IOException {
if (unknown == null) {
printNull();
} else {
final Class<?> type = unknown.getClass();
if (type == String.class) {
printString((String) unknown);
} else if (List.class.isAssignableFrom(type)) {
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>) unknown;
printList(list);
} else if (Map.class.isAssignableFrom(type)) {
@SuppressWarnings("unchecked")
final Map<String, Object> map = (Map<String, Object>) unknown;
printMap(map);
} else if (type == Double.class) {
printDouble((Double) unknown);
} else if (type == Float.class) {
printDouble(((Float) unknown).doubleValue());
} else if (type == Long.class) {
printLong((Long) unknown);
} else if (type == Integer.class) {
printLong(((Integer) unknown).longValue());
} else if (type == Short.class) {
printLong(((Short) unknown).longValue());
} else if (type == Byte.class) {
printLong(((Byte) unknown).longValue());
} else if (type == Boolean.class) {
printBoolean((Boolean) unknown);
} else {
throw new IllegalArgumentException(
"Can't parse unsupported type '" + type + "' into JSON."
);
}
}
}
private void printMap(Map<String, Object> map) throws IOException {
out.write(BEGIN_OBJECT); // (begin the map)
level++;
boolean first = true;
for (final Map.Entry<String, Object> entry : map.entrySet()) {
if (first) {
first = false;
} else {
out.write(SEPARATOR);
}
if (pretty) {
out.write(NEW_LINE);
printIndent();
}
printString(entry.getKey());
if (pretty) {
out.write(SPACE);
}
out.write(ASSIGN);
if (pretty) {
out.write(SPACE);
}
print(entry.getValue());
}
level--;
if (pretty && !first) {
out.write(NEW_LINE);
printIndent();
}
out.write(END_OBJECT); // (close the map)
}
private void printList(List<Object> list) throws IOException {
out.write(BEGIN_ARRAY); // (begin the list)
level++;
boolean first = true;
for (final Object object : list) {
if (first) {
first = false;
} else {
out.write(SEPARATOR);
}
if (pretty) {
out.write(NEW_LINE);
printIndent();
}
print(object);
}
level--;
if (pretty && !first) {
out.write(NEW_LINE);
printIndent();
}
out.write(END_ARRAY); // (close the list)
}
private void printString(String string) throws IOException {
out.write(BEGIN_STRING);
out.write(string.replace("\"", "\\\"").getBytes());
out.write(END_STRING);
}
private void printDouble(Double number) throws IOException {
out.write(Double.toString(number).getBytes());
}
private void printLong(Long number) throws IOException {
out.write(Long.toString(number).getBytes());
}
private void printBoolean(Boolean bool) throws IOException {
if (bool) {
out.write("true".getBytes());
} else {
out.write("false".getBytes());
}
}
private void printNull() throws IOException {
out.write(0x6E); // n
out.write(0x75); // u
out.write(0x6C); // l
out.write(0x6C); // l
}
@SuppressWarnings("fallthrough")
private void printIndent() throws IOException {
switch (tabSize * level) {
case 16 : out.write(SPACE);
case 15 : out.write(SPACE);
case 14 : out.write(SPACE);
case 13 : out.write(SPACE);
case 12 : out.write(SPACE);
case 11 : out.write(SPACE);
case 10 : out.write(SPACE);
case 9 : out.write(SPACE);
case 8 : out.write(SPACE);
case 7 : out.write(SPACE);
case 6 : out.write(SPACE);
case 5 : out.write(SPACE);
case 4 : out.write(SPACE);
case 3 : out.write(SPACE);
case 2 : out.write(SPACE);
case 1 : out.write(SPACE);
case 0 : return;
default :
final int count = tabSize * level;
for (int i = 0; i < count; i++) {
out.write(SPACE);
}
}
}
private final static int
BEGIN_OBJECT = 0x7B, // {
END_OBJECT = 0x7D, // }
BEGIN_ARRAY = 0x5B, // [
END_ARRAY = 0x5D, // ]
BEGIN_STRING = 0x22, // "
END_STRING = 0x22, // "
ASSIGN = 0x3A, // :
SEPARATOR = 0x2C, // ,
NEW_LINE = 0x0A, // new-line
SPACE = 0x20; // space
private final static int PRETTY_TAB_SIZE = 2;
}