Compare commits

..

10 commits

Author SHA1 Message Date
Sander Hautvast
3a756a1dad version bump 2021-02-18 22:36:56 +01:00
Sander Hautvast
2bf298ed1e added tests and fixed others 2020-08-29 14:48:41 +02:00
Sander Hautvast
2cc05e3b3a added parser tests 2020-08-14 19:41:02 +02:00
Sander Hautvast
6599c3a3e1
Create README.md 2020-08-10 14:25:17 +02:00
Sander Hautvast
59fbbe904a got arrays working 2020-08-06 15:50:57 +02:00
Sander Hautvast
c650834d3b some cleanup and added javadocs, upgrade to junit5 2020-08-05 11:53:14 +02:00
Sander Hautvast
a8a364b557 added java bean support 2020-08-05 08:58:31 +02:00
Sander Hautvast
75e40ae502 added missing parameter list in ClassObject/Method 2020-07-28 16:52:10 +02:00
Sander Hautvast
e22d507c30 added final for methodlocal vars 2020-07-28 15:39:53 +02:00
Sander Hautvast
caf43cfb19 added java14 support 2020-07-17 17:33:44 +02:00
64 changed files with 1878 additions and 229 deletions

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# JsonToy-2
my second (or third) go at writing a java json reader/writer
Toy project, aiming for a low-latency tool, optimizing on memory usage

View file

@ -59,9 +59,9 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<version>4.13</version> <version>5.6.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -80,7 +80,7 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.10.3</version> <version>2.10.5.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -0,0 +1,157 @@
package nl.sander.jsontoy2;
import javassist.*;
import nl.sander.jsontoy2.javassist.ClassCreationException;
import nl.sander.jsontoy2.javassist.Javassist;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Responsible for generating classes that parse into java beans.
* <p>
* TODO remove javassist both for analysing java beans and for generating bytecode
*/
public class JavaObjectReaderFactory {
public static final String ROOT_PACKAGE = "serializer.";
private final static CtClass DESERIALIZER_BASE = Javassist.getTypeDefinition(JsonValueReader.class);
private static final CtClass[] NO_EXCEPTIONS = {};
private static final CtClass[] PARSER_PARAM = {Javassist.getTypeDefinition(Parser.class)};
private static final Class<?>[] NO_PARAMS = {};
private static final CtClass[] CT_NO_PARAMS = {};
private final static CtClass OBJECT_CLASS = Javassist.getTypeDefinition(Object.class);
@SuppressWarnings("unchecked")
static <T> JsonValueReader<T> createReaderInstance(Class<T> type) {
try {
Class<?> jsonTypeReader = createReaderClass(type);
return (JsonValueReader<T>) jsonTypeReader.getDeclaredConstructor(NO_PARAMS).newInstance(new Object[]{});
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new JsonParseException(e);
}
}
private static Class<?> createReaderClass(Class<?> type) {
String name = createDeserializerName(type);
CtClass deserializerClass = Javassist.createClass(name, DESERIALIZER_BASE);
try {
deserializerClass.addConstructor(CtNewConstructor.make(CT_NO_PARAMS, NO_EXCEPTIONS, "{super();}", deserializerClass));
deserializerClass.addMethod(createReadJsonMethod(deserializerClass, type));
return deserializerClass.toClass();
} catch (CannotCompileException e) {
throw new ClassCreationException(e);
}
}
private static CtMethod createReadJsonMethod(CtClass serializerClass, Class<?> type) {
try {
String readMethodBodySource = createReadMethodBodySource(type);
// System.out.println(readMethodBodySource);
return CtNewMethod.make(Modifier.PUBLIC, OBJECT_CLASS, "read", PARSER_PARAM, NO_EXCEPTIONS, readMethodBodySource, serializerClass);
} catch (CannotCompileException e) {
throw new ClassCreationException(e);
}
}
private static String createReadMethodBodySource(Class<?> type) {
String source = "{";
String typeName = type.getName();
if (ReaderFactory.readerSuppliers.containsKey(type)) {
source += "return " + JsonReader.class.getName() + ".read(" + typeName + ".class, $1);";
} else {
source += "java.util.Map object=" + JsonReader.class.getName() + ".readJavaObject(" + typeName + ".class,$1);\n";
source += typeName + " instance = new " + typeName + "();\n";
for (Field field : type.getDeclaredFields()) {
source += "instance.set" + capitalize(field.getName())
+ "(" + getSourceForGetValueFromObject(field) + ");\n";
}
source += "return instance;";
}
source += "}\n";
return source;
}
@NotNull
private static String getSourceForGetValueFromObject(Field field) {
Class<?> fieldType = field.getType();
String fieldName = field.getName();
if (fieldType == boolean.class) {
return "getBoolean(\"" + fieldName + "\", object)";
} else if (fieldType == int.class) {
return "getInt(\"" + fieldName + "\", object)";
} else if (fieldType == long.class) {
return "getLong(\"" + fieldName + "\",object)";
} else if (fieldType == short.class) {
return "getShort(\"" + fieldName + "\",object)";
} else if (fieldType == byte.class) {
return "getByte(\"" + fieldName + "\",object)";
} else if (fieldType == float.class) {
return "getFloat(\"" + fieldName + "\",object)";
} else if (fieldType == double.class) {
return "getDouble(\"" + fieldName + "\",object)";
} else {
String fieldTypeName = field.getType().getName();
if (Set.class.isAssignableFrom(fieldType)) {
return "(" + fieldTypeName + ")getSet(\"" + fieldName + "\"," + fieldTypeName + ".class,object)";
} else if (List.class.isAssignableFrom(fieldType)) {
return "(" + fieldTypeName + ")getList(\"" + fieldName + "\"," + fieldTypeName + ".class,object)";
} else if (fieldType.isArray()) {
return "(" + format(fieldTypeName) + "[])getArray(\"" + fieldName + "\"," + format(fieldTypeName) + ".class,object)";
} else {
return "(" + fieldTypeName + ")(object.get(\"" + fieldName + "\"))";
}
}
}
private static String format(String arrayTypeExpr) {
return arrayTypeExpr.substring(2).substring(0, arrayTypeExpr.length() - 3);
}
//should be reinstated
private static String genericType(CtField field) {
try {
if (!Javassist.isCollection(field.getType())) {
return "";
} else {
String genericSignature = field.getGenericSignature(); // java.util.List<java.lang.String;>;
Pattern p = Pattern.compile("(.+?)<(.+?);>;");
Matcher matcher = p.matcher(genericSignature);
if (matcher.find()) {
String[] genericTypes = matcher.group(2).substring(1).replaceAll("/", ".").split(";L");
for (int i = 0; i < genericTypes.length; i++) {
genericTypes[i] += ".class";
}
return String.join(",", genericTypes);
}
throw new ClassCreationException("Generic type not ok");
}
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
private static String capitalize(String lowercase) {
return lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
}
/*
* custom root package is prepended to avoid the java.lang class in which it's illegal to create new classes
*
* Array marks ( '[]' ) are replaced by the 'Array', Otherwise the SerializerClassName would be syntactically incorrect
*/
private static String createDeserializerName(Class<?> type) {
return ROOT_PACKAGE + type.getName().replaceAll("\\[]", "Array") + "Deserializer";
}
}

View file

@ -1,32 +1,15 @@
package nl.sander.jsontoy2; package nl.sander.jsontoy2;
import nl.sander.jsontoy2.readers.*;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
/** /**
* public api * public api
*/ */
public class JsonReader { public class JsonReader {
private static final ConcurrentMap<Class<?>, Supplier<JsonValueReader<?>>> readSuppliers = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, JsonValueReader<?>> readers = new ConcurrentHashMap<>();
private final static ThreadLocal<SoftReference<Parser>> PARSERS = new ThreadLocal<>(); private JsonReader() {
static {
registerPrimitiveTypeReaders();
} }
/** /**
@ -43,21 +26,12 @@ public class JsonReader {
* array => List * array => List
*/ */
public static Object read(InputStream inputStream) { public static Object read(InputStream inputStream) {
InputStream in = ensureBuffered(inputStream); final InputStream in = ensureBufferedStream(inputStream);
try (Parser parser = getParser(in)) { try (Parser parser = ParserFactory.getParser(in)) {
return read(parser); return read(parser);
} }
} }
private static InputStream ensureBuffered(InputStream inputStream) {
if (inputStream instanceof BufferedInputStream) {
return inputStream;
} else {
return new BufferedInputStream(inputStream);
}
}
/** /**
* Reads a value from a string for a type that is not known beforehand * Reads a value from a string for a type that is not known beforehand
* *
@ -65,29 +39,7 @@ public class JsonReader {
* @return @see read(InputStream stream) * @return @see read(InputStream stream)
*/ */
public static Object read(String jsonString) { public static Object read(String jsonString) {
return read(getParser(jsonString)); return read(ParserFactory.getParser(jsonString));
}
private static Parser getParser(String jsonString) {
return getParser(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)));
}
private static Parser getParser(InputStream inputStream) {
Objects.requireNonNull(inputStream, "File not found");
Parser parser;
SoftReference<Parser> parserReference = PARSERS.get();
if (parserReference == null || (parser = parserReference.get()) == null) {
parser = new Parser(inputStream);
parserReference = new SoftReference<>(parser);
PARSERS.set(parserReference);
} else {
parser.init(inputStream);
}
return parser;
}
static Object read(Parser parser) {
return parser.parseAny();
} }
/** /**
@ -99,7 +51,7 @@ public class JsonReader {
* @return Object the specified type * @return Object the specified type
*/ */
public static <T> T read(Class<T> type, InputStream inputStream) { public static <T> T read(Class<T> type, InputStream inputStream) {
Parser parser = getParser(inputStream); Parser parser = ParserFactory.getParser(inputStream);
T value = read(type, parser); T value = read(type, parser);
parser.close(); parser.close();
return value; return value;
@ -114,44 +66,34 @@ public class JsonReader {
* @return Object the specified type * @return Object the specified type
*/ */
public static <T> T read(Class<T> type, String jsonString) { public static <T> T read(Class<T> type, String jsonString) {
return read(type, getParser(jsonString)); return read(type, ParserFactory.getParser(jsonString));
} }
static Object read(Parser parser) {
return parser.parseAny();
}
//TODO should not be public
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static <T> T read(Class<T> type, Parser parser) { public static <T> T read(Class<T> type, Parser parser) {
return (T) getReader(type).read(parser); return (T) ReaderFactory.getReader(type).read(parser);
// class.cast() does not work well for primitives; // class.cast() does not work well for primitives;
} }
private static <T> JsonValueReader<?> getReader(Class<T> type) { @SuppressWarnings("unused")
return readers.computeIfAbsent(type, k -> readSuppliers.get(k).get()); public static Map<?, ?> readJavaObject(Class<?> type, Parser parser) {
return parser.parseObject(type);
} }
private static <T> void register(Class<T> type, Supplier<JsonValueReader<?>> objectReader) {
readSuppliers.put(type, objectReader); private static InputStream ensureBufferedStream(InputStream inputStream) {
if (inputStream instanceof BufferedInputStream) {
return inputStream;
} else {
return new BufferedInputStream(inputStream);
}
} }
private static void registerPrimitiveTypeReaders() {
register(Boolean.class, BooleanReader::new);
register(boolean.class, BooleanReader::new);
register(Integer.class, IntegerReader::new);
register(int.class, IntegerReader::new);
register(Long.class, LongReader::new);
register(long.class, LongReader::new);
register(Byte.class, ByteReader::new);
register(byte.class, ByteReader::new);
register(Short.class, ShortReader::new);
register(short.class, ShortReader::new);
register(Double.class, DoubleReader::new);
register(double.class, DoubleReader::new);
register(Float.class, FloatReader::new);
register(float.class, FloatReader::new);
register(Date.class, DateReader::new);
register(Character.class, CharReader::new);
register(char.class, CharReader::new);
register(String.class, StringReader::new);
register(LocalDateTime.class, LocalDateTimeReader::new);
register(List.class, ListReader::new);
register(Map.class, MapReader::new);
}
} }

View file

@ -1,5 +1,112 @@
package nl.sander.jsontoy2; package nl.sander.jsontoy2;
public interface JsonValueReader<T> { import java.lang.reflect.Array;
T read(Parser parser); import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* Base class for generated readers
*
* @param <T> the type that the reader produces
*/
@SuppressWarnings("unused") /* these methods will be called from generated code*/
public abstract class JsonValueReader<T> {
public abstract T read(Parser parser);
protected boolean getBoolean(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? false : (Boolean) value;
}
protected int getInt(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0 : (Integer) value;
}
protected long getLong(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0L : (Long) value;
}
protected short getShort(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? (short) 0 : (Short) value;
}
protected byte getByte(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? (byte) 0 : (Byte) value;
}
protected float getFloat(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0F : (Float) value;
}
protected double getDouble(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0D : (Double) value;
}
/*
* Creates a Set type for any implementation of it (that the containing bean needs),
* unless it has no constructor using a java.util.Collection as single parameter.
*
* returntype is generic Set, object needs cast afterwards in generated code
*/
protected Set<?> getSet(String fieldName, Class<? extends Set<?>> type, Map<String, ?> values) {
List<?> value = (List<?>) values.get(fieldName);
if (value == null) {
return null;
} else {
try {
if (type.equals(Set.class)) {
return new HashSet<>(value);
} else {
Constructor<? extends Set<?>> constructor = type.getConstructor(Collection.class);
return constructor.newInstance(value);
}
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new JsonParseException(e);
}
}
}
/*
* Creates a List type for any implementation of it (that the containing bean needs),
* unless it has no constructor using a java.util.Collection as single parameter.
*
* returntype is generic List, needs cast afterwards in generated code
*/
protected List<?> getList(String fieldName, Class<?> listImplType, Map<String, ?> values) {
List<?> value = (List<?>) values.get(fieldName);
if (value == null) {
return null;
} else if (listImplType == ArrayList.class || listImplType.equals(List.class)) {
return value;
} else {
try {
Constructor<?> constructor = listImplType.getConstructor(Collection.class);
return (List<?>) constructor.newInstance(value);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new JsonParseException(e);
}
}
}
protected Object getArray(String fieldName, Class<?> arrayType, Map<String, ?> values) {
List<?> value = (List<?>) values.get(fieldName);
if (value == null) {
return new Object[]{};
} else {
Object[] array = (Object[]) Array.newInstance(arrayType, value.size());
int index = 0;
for (Object element : value) {
array[index] = value.get(index++);
}
//TODO this can probably be done smarter
return array;
}
}
} }

View file

@ -37,6 +37,10 @@ public class Lexer implements AutoCloseable {
} }
} }
void eatUntil(char until) {
eatUntil(new char[]{until});
}
void eatUntil(char... until) { void eatUntil(char... until) {
while (current > -1 && (!contains(until, current) | Character.isWhitespace(current))) { while (current > -1 && (!contains(until, current) | Character.isWhitespace(current))) {
advance(); advance();
@ -67,4 +71,9 @@ public class Lexer implements AutoCloseable {
throw new JsonParseException(e); throw new JsonParseException(e);
} }
} }
@SuppressWarnings("unused")
public byte current() {
return current;
}
} }

View file

@ -37,37 +37,37 @@ public class Parser extends Lexer {
} }
public Integer parseInteger() { public Integer parseInteger() {
String value = parseNumber(); final String value = parseNumber();
return Double.valueOf(value).intValue(); return Double.valueOf(value).intValue();
} }
public Long parseLong() { public Long parseLong() {
String value = parseNumber(); final String value = parseNumber();
return Long.parseLong(value); return Long.parseLong(value);
} }
public Float parseFloat() { public Float parseFloat() {
String value = parseNumber(); final String value = parseNumber();
return Float.parseFloat(value); return Float.parseFloat(value);
} }
public Double parseDouble() { public Double parseDouble() {
String value = parseNumber(); final String value = parseNumber();
return Double.parseDouble(value); return Double.parseDouble(value);
} }
public Short parseShort() { public Short parseShort() {
String value = parseNumber(); final String value = parseNumber();
return Short.parseShort(value); return Short.parseShort(value);
} }
public Byte parseByte() { public Byte parseByte() {
String value = parseNumber(); final String value = parseNumber();
return Byte.parseByte(value); return Byte.parseByte(value);
} }
public Character parseCharacter() { public Character parseCharacter() {
String string = parseString(); final String string = parseString();
return string.charAt(0); return string.charAt(0);
} }
@ -78,7 +78,7 @@ public class Parser extends Lexer {
advance(); advance();
} }
String maybeBoolean = characterBuffer.toString(); final String maybeBoolean = characterBuffer.toString();
boolean returnValue; boolean returnValue;
if ((returnValue = maybeBoolean.equals("true")) || maybeBoolean.equals("false")) { if ((returnValue = maybeBoolean.equals("true")) || maybeBoolean.equals("false")) {
return returnValue; return returnValue;
@ -105,14 +105,18 @@ public class Parser extends Lexer {
} }
public List<?> parseArray() { public List<?> parseArray() {
return parseArray(null);
}
public List<?> parseArray(Class<?> genericType) {
skipWhitespace(); skipWhitespace();
if (current != '[') { if (current != '[') {
throw new JsonParseException("no list found"); throw new JsonParseException("no list found");
} }
List<Object> list = new ArrayList<>(); final List<Object> list = new ArrayList<>();
advance(); advance();
while (current != -1 && current != ']') { while (current != -1 && current != ']') {
Maybe<Object> maybeValue = parseValue(); final Maybe<Object> maybeValue = genericType == null ? parseValue() : parseValue(genericType);
if (!maybeValue.isPresent()) { if (!maybeValue.isPresent()) {
break; break;
} else { } else {
@ -125,7 +129,11 @@ public class Parser extends Lexer {
} }
public Map<?, ?> parseObject() { public Map<?, ?> parseObject() {
HashMap<Object, Object> map = new HashMap<>(); return parseObject(null);
}
public Map<?, ?> parseObject(Class<?> type) {
final HashMap<Object, Object> map = new HashMap<>();
skipWhitespace(); skipWhitespace();
if (current != '{') { if (current != '{') {
throw new JsonParseException("no map found"); throw new JsonParseException("no map found");
@ -134,7 +142,7 @@ public class Parser extends Lexer {
while (current != -1 && current != '}') { while (current != -1 && current != '}') {
skipWhitespace(); skipWhitespace();
if (current == '"') { if (current == '"') {
String key = parseString(); final String key = parseString();
skipWhitespace(); skipWhitespace();
if (current == ':') { if (current == ':') {
advance(); advance();
@ -142,8 +150,13 @@ public class Parser extends Lexer {
throw new JsonParseException("expected colon"); throw new JsonParseException("expected colon");
} }
skipWhitespace(); skipWhitespace();
Maybe<Object> maybeValue = parseValue(); final Maybe<Object> maybeValue;
maybeValue.ifPresent(value -> map.put(key, value)); try {
maybeValue = type == null ? parseValue() : Maybe.of(JsonReader.read(type.getDeclaredField(key).getType(), this));
maybeValue.ifPresent(value -> map.put(key, value));
} catch (NoSuchFieldException e) {
throw new JsonParseException(e);
}
} }
advance(); advance();
skipWhitespace(); skipWhitespace();
@ -152,7 +165,7 @@ public class Parser extends Lexer {
} }
public Object parseAny() { public Object parseAny() {
Maybe<Object> maybe = parseValue(); final Maybe<Object> maybe = parseValue();
if (maybe.isPresent()) { if (maybe.isPresent()) {
return maybe.get(); return maybe.get();
} else { } else {
@ -161,7 +174,7 @@ public class Parser extends Lexer {
} }
private Maybe<Object> parseValue() { private Maybe<Object> parseValue() {
Object value; final Object value;
skipWhitespace(); skipWhitespace();
switch (current) { switch (current) {
case ']': case ']':
@ -181,8 +194,8 @@ public class Parser extends Lexer {
break; break;
default: String numeric = parseNumber(); default: String numeric = parseNumber();
double doubleValue = Double.parseDouble(numeric); double doubleValue = Double.parseDouble(numeric);
if ((int) doubleValue == doubleValue) { if ((long) doubleValue == doubleValue) {
value = (int) doubleValue; value = (long) doubleValue;
} else { } else {
value = doubleValue; value = doubleValue;
} }
@ -190,6 +203,20 @@ public class Parser extends Lexer {
return Maybe.of(value); return Maybe.of(value);
} }
private Maybe<Object> parseValue(Class<?> type) {
final Object value;
skipWhitespace();
JsonValueReader<?> reader = ReaderFactory.getReader(type);
if (reader != null) {
value = reader.read(this);
} else {
value = parseObject();
}
return Maybe.of(value);
}
private Object readNull() { private Object readNull() {
expect(() -> new JsonParseException("Expected 'null', encountered " + (char) current), 'n', 'u', 'l', 'l'); expect(() -> new JsonParseException("Expected 'null', encountered " + (char) current), 'n', 'u', 'l', 'l');
return null; return null;
@ -251,8 +278,8 @@ public class Parser extends Lexer {
} }
private void parseEncoded() { private void parseEncoded() {
StringBuilder buf = new StringBuilder(); final StringBuilder buf = new StringBuilder();
char codePoint = parseCodePoint(); final char codePoint = parseCodePoint();
buf.append(codePoint); buf.append(codePoint);
if (Character.isHighSurrogate(codePoint)) { if (Character.isHighSurrogate(codePoint)) {
expect(() -> new JsonParseException("Invalid unicode codepoint at line " + linecount), '\\', 'u'); expect(() -> new JsonParseException("Invalid unicode codepoint at line " + linecount), '\\', 'u');

View file

@ -0,0 +1,38 @@
package nl.sander.jsontoy2;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Keeps a Threadlocal parser that can be (re)used by clients.
* <p>
* The parser is not kept indefinitely, but garbage collected when the JVM considers necessary.
*/
public class ParserFactory {
private final static ThreadLocal<SoftReference<Parser>> PARSERS = new ThreadLocal<>();
private ParserFactory() {
}
static Parser getParser(String jsonString) {
return getParser(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)));
}
static Parser getParser(InputStream inputStream) {
Objects.requireNonNull(inputStream, "File not found");
Parser parser;
SoftReference<Parser> parserReference = PARSERS.get();
if (parserReference == null || (parser = parserReference.get()) == null) {
parser = new Parser(inputStream);
parserReference = new SoftReference<>(parser);
PARSERS.set(parserReference);
} else {
parser.init(inputStream);
}
return parser;
}
}

View file

@ -0,0 +1,69 @@
package nl.sander.jsontoy2;
import nl.sander.jsontoy2.readers.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
public class ReaderFactory {
static final ConcurrentMap<Class<?>, Supplier<JsonValueReader<?>>> readerSuppliers = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, JsonValueReader<?>> readers = new ConcurrentHashMap<>();
private final static MapReader MAPREADER = new MapReader();
private final static ListReader LISTREADER = new ListReader();
static {
registerPrimitiveTypeReaders();
}
private ReaderFactory() {
}
public static <T> void registerCustomReader(Class<T> type, JsonValueReader<T> reader) {
readers.put(type, reader);
}
static <T> void register(Class<T> type, Supplier<JsonValueReader<?>> objectReader) {
readerSuppliers.put(type, objectReader);
}
static <T> JsonValueReader<?> getReader(Class<T> type) {
if (Map.class.isAssignableFrom(type)) {
return MAPREADER;
} else if (List.class.isAssignableFrom(type) || Set.class.isAssignableFrom(type) || type.isArray()) {
return LISTREADER;
} else {
return readers.computeIfAbsent(type, k -> {
Supplier<JsonValueReader<?>> jsonValueReaderSupplier = readerSuppliers.get(k);
return Optional.ofNullable(jsonValueReaderSupplier)
.orElseGet(() -> () -> JavaObjectReaderFactory.createReaderInstance(type))
.get();
});
}
}
private static void registerPrimitiveTypeReaders() {
register(Boolean.class, BooleanReader::new);
register(boolean.class, BooleanReader::new);
register(Integer.class, IntegerReader::new);
register(int.class, IntegerReader::new);
register(Long.class, LongReader::new);
register(long.class, LongReader::new);
register(Byte.class, ByteReader::new);
register(byte.class, ByteReader::new);
register(Short.class, ShortReader::new);
register(short.class, ShortReader::new);
register(Double.class, DoubleReader::new);
register(double.class, DoubleReader::new);
register(Float.class, FloatReader::new);
register(float.class, FloatReader::new);
register(Date.class, DateReader::new);
register(Character.class, CharReader::new);
register(char.class, CharReader::new);
register(String.class, StringReader::new);
register(LocalDateTime.class, LocalDateTimeReader::new);
}
}

View file

@ -2,17 +2,28 @@ package nl.sander.jsontoy2.java;
import nl.sander.jsontoy2.java.constantpool.ConstantPoolEntry; import nl.sander.jsontoy2.java.constantpool.ConstantPoolEntry;
import nl.sander.jsontoy2.java.constantpool.Utf8Entry; import nl.sander.jsontoy2.java.constantpool.Utf8Entry;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* Contains info from parsed bytecode
*/
public class ClassObject {
public class ClassObject<T> { private final ConstantPoolEntry[] constantPool;
private final Info[] fieldInfos;
private final Info[] methodInfos;
private ConstantPoolEntry[] constantPool; private ClassObject(ConstantPoolEntry[] constantPool, Info[] fieldInfos, Info[] methodInfos) {
private Info[] fieldInfos; this.constantPool = constantPool;
private Info[] methodInfos; this.fieldInfos = fieldInfos;
this.methodInfos = methodInfos;
}
private String getUtf8(int index) { private String getUtf8(int index) {
return ((Utf8Entry) constantPool[index - 1]).getUtf8(); return ((Utf8Entry) constantPool[index - 1]).getUtf8();
@ -26,10 +37,50 @@ public class ClassObject<T> {
public Set<Method> getMethods() { public Set<Method> getMethods() {
return Arrays.stream(methodInfos) return Arrays.stream(methodInfos)
.map(mi -> new Method(getUtf8(mi.getNameIndex()), getUtf8(mi.getDescriptorIndex()))) .map(this::createMethod)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@NotNull
private Method createMethod(Info mi) {
final String descriptor = getUtf8(mi.getDescriptorIndex());
final int split = descriptor.indexOf(')');
final List<String> parameters = getParameterTypes(descriptor.substring(0, split));
String returnType = descriptor.substring(split + 1);
returnType = returnType.substring(0, returnType.length() - 1);
return new Method(getUtf8(mi.getNameIndex()), parameters, returnType);
}
private List<String> getParameterTypes(String parameterDescriptor) {
List<String> result = new ArrayList<>();
boolean array = false;
for (int i = 1; i < parameterDescriptor.length(); i++) {
if (parameterDescriptor.charAt(i) == '[') {
array = true;
} else {
if (parameterDescriptor.charAt(i) == 'L') {
int i2 = i;
while (i2 < parameterDescriptor.length() && parameterDescriptor.charAt(i2) != ';') {
i2++;
}
result.add(getArrayIndicator(array) + parameterDescriptor.substring(i, i2));
array = false;
i = i2;
} else {
result.add(getArrayIndicator(array) + parameterDescriptor.charAt(i));
array = false;
}
}
}
return result;
}
@NotNull
private String getArrayIndicator(boolean array) {
return array ? "[" : "";
}
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (ConstantPoolEntry entry : constantPool) { for (ConstantPoolEntry entry : constantPool) {
@ -41,41 +92,44 @@ public class ClassObject<T> {
public static class Builder<T> { public static class Builder<T> {
private final ClassObject<T> classObject = new ClassObject<>();
private int constantPoolIndex = 0; private int constantPoolIndex = 0;
private int fieldInfoIndex = 0; private int fieldInfoIndex = 0;
private int methodInfoIndex = 0; private int methodInfoIndex = 0;
public ClassObject<T> build() { private ConstantPoolEntry[] constantPool;
return classObject; private Info[] fieldInfos;
private Info[] methodInfos;
public ClassObject build() {
return new ClassObject(constantPool, fieldInfos, methodInfos);
} }
public Builder<T> constantPoolCount(int constantPoolCount) { public Builder<T> constantPoolCount(int constantPoolCount) {
classObject.constantPool = new ConstantPoolEntry[constantPoolCount - 1]; constantPool = new ConstantPoolEntry[constantPoolCount - 1];
return this; return this;
} }
void constantPoolEntry(ConstantPoolEntry entry) { void constantPoolEntry(ConstantPoolEntry entry) {
classObject.constantPool[constantPoolIndex++] = entry; constantPool[constantPoolIndex++] = entry;
} }
public Builder<T> fieldInfoCount(int fieldInfoCount) { public Builder<T> fieldInfoCount(int fieldInfoCount) {
classObject.fieldInfos = new Info[fieldInfoCount]; fieldInfos = new Info[fieldInfoCount];
return this; return this;
} }
public Builder<T> fieldInfo(Info fieldInfo) { public Builder<T> fieldInfo(Info fieldInfo) {
classObject.fieldInfos[fieldInfoIndex++] = fieldInfo; fieldInfos[fieldInfoIndex++] = fieldInfo;
return this; return this;
} }
public Builder<T> methodInfoCount(int methodInfoCount) { public Builder<T> methodInfoCount(int methodInfoCount) {
classObject.methodInfos = new Info[methodInfoCount]; methodInfos = new Info[methodInfoCount];
return this; return this;
} }
public Builder<T> methodInfo(Info methodInfo) { public Builder<T> methodInfo(Info methodInfo) {
classObject.methodInfos[methodInfoIndex++] = methodInfo; methodInfos[methodInfoIndex++] = methodInfo;
return this; return this;
} }
} }

View file

@ -8,8 +8,8 @@ import java.io.IOException;
public class ClassReader extends DataReader { public class ClassReader extends DataReader {
public <T> ClassObject<T> parse(Class<T> type) { public <T> ClassObject parse(Class<T> type) {
DataInputStream in = new DataInputStream(new BufferedInputStream(type.getResourceAsStream(getResourceName(type)))); final DataInputStream in = new DataInputStream(new BufferedInputStream(type.getResourceAsStream(getResourceName(type))));
expect(in, 0xCAFEBABE); expect(in, 0xCAFEBABE);
ClassObject.Builder<T> builder = new ClassObject.Builder<>(); ClassObject.Builder<T> builder = new ClassObject.Builder<>();
@ -19,9 +19,9 @@ public class ClassReader extends DataReader {
readConstantPool(in, builder); readConstantPool(in, builder);
skip(in, 6); // u2 access_flags, u2 this_class, u2 super_class skip(in, 6); // u2 access_flags, u2 this_class, u2 super_class
int interfacesCount = readUnsignedShort(in); final int interfacesCount = readUnsignedShort(in);
skip(in, interfacesCount * 2); // interfaces[] skip(in, interfacesCount * 2); // interfaces[u2;]
readFields(in, builder); readFields(in, builder);
readMethods(in, builder); readMethods(in, builder);
@ -29,7 +29,7 @@ public class ClassReader extends DataReader {
} }
private <T> void readConstantPool(DataInputStream in, ClassObject.Builder<T> builder) { private <T> void readConstantPool(DataInputStream in, ClassObject.Builder<T> builder) {
int constantPoolCount = readUnsignedShort(in); final int constantPoolCount = readUnsignedShort(in);
builder.constantPoolCount(constantPoolCount); builder.constantPoolCount(constantPoolCount);
for (int i = 1; i < constantPoolCount; i++) { for (int i = 1; i < constantPoolCount; i++) {
builder.constantPoolEntry(readConstantPoolEntry(in)); builder.constantPoolEntry(readConstantPoolEntry(in));
@ -37,7 +37,7 @@ public class ClassReader extends DataReader {
} }
private <T> void readFields(DataInputStream in, ClassObject.Builder<T> builder) { private <T> void readFields(DataInputStream in, ClassObject.Builder<T> builder) {
int fieldInfoCount = readUnsignedShort(in); final int fieldInfoCount = readUnsignedShort(in);
builder.fieldInfoCount(fieldInfoCount); builder.fieldInfoCount(fieldInfoCount);
for (int i = 0; i < fieldInfoCount; i++) { for (int i = 0; i < fieldInfoCount; i++) {
builder.fieldInfo(readField(in)); builder.fieldInfo(readField(in));
@ -45,7 +45,7 @@ public class ClassReader extends DataReader {
} }
private <T> void readMethods(DataInputStream in, ClassObject.Builder<T> builder) { private <T> void readMethods(DataInputStream in, ClassObject.Builder<T> builder) {
int methodInfoCount = readUnsignedShort(in); final int methodInfoCount = readUnsignedShort(in);
builder.methodInfoCount(methodInfoCount); builder.methodInfoCount(methodInfoCount);
for (int i = 0; i < methodInfoCount; i++) { for (int i = 0; i < methodInfoCount; i++) {
builder.methodInfo(readMethod(in)); builder.methodInfo(readMethod(in));
@ -53,7 +53,7 @@ public class ClassReader extends DataReader {
} }
private <T> Info readField(DataInputStream in) { private <T> Info readField(DataInputStream in) {
Info fieldInfo = new Info(readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in)); final Info fieldInfo = new Info(readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in));
for (int i = 0; i < fieldInfo.getAttributesCount(); i++) { for (int i = 0; i < fieldInfo.getAttributesCount(); i++) {
fieldInfo.add(readAttribute(in)); fieldInfo.add(readAttribute(in));
} }
@ -62,7 +62,7 @@ public class ClassReader extends DataReader {
} }
private <T> Info readMethod(DataInputStream in) { private <T> Info readMethod(DataInputStream in) {
Info methodInfo = new Info(readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in)); final Info methodInfo = new Info(readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in), readUnsignedShort(in));
for (int i = 0; i < methodInfo.getAttributesCount(); i++) { for (int i = 0; i < methodInfo.getAttributesCount(); i++) {
methodInfo.add(readAttribute(in)); methodInfo.add(readAttribute(in));
} }
@ -71,9 +71,9 @@ public class ClassReader extends DataReader {
} }
private AttributeInfo readAttribute(DataInputStream in) { private AttributeInfo readAttribute(DataInputStream in) {
int attributeNameIndex = readUnsignedShort(in); final int attributeNameIndex = readUnsignedShort(in);
int attributeLength = readInt(in); final int attributeLength = readS32(in);
byte[] info; final byte[] info;
if (attributeLength > 0) { if (attributeLength > 0) {
info = new byte[attributeLength]; info = new byte[attributeLength];
try { try {
@ -87,30 +87,32 @@ public class ClassReader extends DataReader {
return new AttributeInfo(attributeNameIndex, info); return new AttributeInfo(attributeNameIndex, info);
} }
private <T> ConstantPoolEntry readConstantPoolEntry(DataInputStream in) { private ConstantPoolEntry readConstantPoolEntry(DataInputStream in) {
byte tag = readByte(in); final byte tag = readByte(in);
switch (tag) { switch (tag) {
case 1: return readUtf8Entry(in); case 1: return new Utf8Entry(readString(in, readUnsignedShort(in)));
case 2: throw new IllegalStateException("2: invalid classpool tag"); case 2: throw new IllegalStateException("2: invalid classpool tag");
case 3: return new IntEntry(readInt(in)); case 3: return new IntEntry(readS32(in));
case 4: return new FloatEntry(readFloat(in)); case 4: return new FloatEntry(readF32(in));
case 5: return new LongEntry(readLong(in)); case 5: return new LongEntry(readS64(in));
case 6: return new DoubleEntry(readDouble(in)); case 6: return new DoubleEntry(readF64(in));
case 7: return new ClassEntry(readUnsignedShort(in)); case 7: return new ClassEntry(readUnsignedShort(in));
case 8: return new StringEntry(readUnsignedShort(in)); case 8: return new StringEntry(readUnsignedShort(in));
case 9: return new FieldRefEntry(readShort(in), readShort(in)); case 9: return new FieldRefEntry(readUnsignedShort(in), readUnsignedShort(in));
case 10: return new MethodRefEntry(readUnsignedShort(in), readUnsignedShort(in)); case 10: return new MethodRefEntry(readUnsignedShort(in), readUnsignedShort(in));
case 11: return new InterfaceMethodRefEntry(readUnsignedShort(in), readUnsignedShort(in)); case 11: return new InterfaceMethodRefEntry(readUnsignedShort(in), readUnsignedShort(in));
case 12: return new NameAndTypeEntry(readUnsignedShort(in), readUnsignedShort(in)); case 12: return new NameAndTypeEntry(readUnsignedShort(in), readUnsignedShort(in));
case 15: return new MethodHandleEntry(readUnsignedShort(in), readUnsignedShort(in)); case 15: return new MethodHandleEntry(readUnsignedShort(in), readUnsignedShort(in));
case 16: return new MethodTypeEntry(readUnsignedShort(in)); case 16: return new MethodTypeEntry(readUnsignedShort(in));
case 18: return new InvokeDynamicEntry(readUnsignedShort(in), readUnsignedShort(in)); case 18: return new InvokeDynamicEntry(readUnsignedShort(in), readUnsignedShort(in));
case 19: return new ModuleEntry(readUnsignedShort(in));
case 20: return new PackageEntry(readUnsignedShort(in));
default: throw new IllegalStateException("invalid classpool"); default: throw new IllegalStateException("invalid classpool");
} }
} }
protected <T> String getResourceName(Class<T> type) { protected <T> String getResourceName(Class<T> type) {
StringBuilder typeName = new StringBuilder("/" + type.getName()); final StringBuilder typeName = new StringBuilder("/" + type.getName());
for (int i = 0; i < typeName.length(); i++) { for (int i = 0; i < typeName.length(); i++) {
if (typeName.charAt(i) == '.') { if (typeName.charAt(i) == '.') {

View file

@ -1,37 +1,16 @@
package nl.sander.jsontoy2.java; package nl.sander.jsontoy2.java;
import nl.sander.jsontoy2.java.constantpool.Utf8Entry;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/**
* basic IO operations with runtime exceptions
*/
public class DataReader { public class DataReader {
protected float readFloat(DataInputStream in) { protected long readS64(DataInputStream in) {
try {
return in.readFloat();
} catch (IOException e) {
throw new RuntimeException();
}
}
protected Utf8Entry readUtf8Entry(DataInputStream in) {
short length = readShort(in);
return new Utf8Entry(readString(in, length));
}
protected String readString(DataInputStream in, short length) {
try {
byte[] bytes = in.readNBytes(length);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException();
}
}
protected long readLong(DataInputStream in) {
try { try {
return in.readLong(); return in.readLong();
} catch (IOException e) { } catch (IOException e) {
@ -39,17 +18,17 @@ public class DataReader {
} }
} }
protected double readDouble(DataInputStream in) { protected float readF32(DataInputStream in) {
try { try {
return in.readDouble(); return in.readFloat();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException();
} }
} }
protected short readShort(DataInputStream in) { protected double readF64(DataInputStream in) {
try { try {
return in.readShort(); return in.readDouble();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -63,7 +42,7 @@ public class DataReader {
} }
} }
protected int readInt(DataInputStream in) { protected int readS32(DataInputStream in) {
try { try {
return in.readInt(); return in.readInt();
} catch (IOException e) { } catch (IOException e) {
@ -71,6 +50,15 @@ public class DataReader {
} }
} }
protected String readString(DataInputStream in, int length) {
try {
byte[] bytes = in.readNBytes(length);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException();
}
}
protected byte readByte(DataInputStream in) { protected byte readByte(DataInputStream in) {
try { try {
return in.readByte(); return in.readByte();

View file

@ -1,23 +1,28 @@
package nl.sander.jsontoy2.java; package nl.sander.jsontoy2.java;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects; import java.util.Objects;
public class Method { public class Method implements Comparable<Method> {
private final String name; private final String name;
private final String type; private final List<String> parameterTypes;
private final String returnType;
public Method(String name, String type) { public Method(String name, List<String> parameterTypes, String returnType) {
this.name = name; this.name = name;
this.type = type; this.parameterTypes = parameterTypes;
this.returnType = returnType;
} }
public String getName() { public String getName() {
return name; return name;
} }
public String getType() { public String getReturnType() {
return type; return returnType;
} }
@Override @Override
@ -26,19 +31,25 @@ public class Method {
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Method field = (Method) o; Method field = (Method) o;
return name.equals(field.name) && return name.equals(field.name) &&
type.equals(field.type); returnType.equals(field.returnType);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(name, type); return Objects.hash(name, returnType);
} }
@Override @Override
public String toString() { public String toString() {
return "Method{" + return "Method{" +
"name='" + name + '\'' + "name='" + name + '\'' +
", type='" + type + '\'' + ", parameterTypes=" + parameterTypes +
", returnType='" + returnType + '\'' +
'}'; '}';
} }
@Override
public int compareTo(@NotNull Method o) {
return name.compareTo(o.name);
}
} }

View file

@ -1,19 +1,19 @@
package nl.sander.jsontoy2.java.constantpool; package nl.sander.jsontoy2.java.constantpool;
public class FieldRefEntry extends ConstantPoolEntry { public class FieldRefEntry extends ConstantPoolEntry {
private final short classIndex; private final int classIndex;
private final short nameAndTypeIndex; private final int nameAndTypeIndex;
public FieldRefEntry(short classIndex, short nameAndTypeIndex) { public FieldRefEntry(int classIndex, int nameAndTypeIndex) {
this.classIndex = classIndex; this.classIndex = classIndex;
this.nameAndTypeIndex = nameAndTypeIndex; this.nameAndTypeIndex = nameAndTypeIndex;
} }
public short getClassIndex() { public int getClassIndex() {
return classIndex; return classIndex;
} }
public short getNameAndTypeIndex() { public int getNameAndTypeIndex() {
return nameAndTypeIndex; return nameAndTypeIndex;
} }

View file

@ -0,0 +1,11 @@
package nl.sander.jsontoy2.java.constantpool;
import nl.sander.jsontoy2.java.constantpool.ConstantPoolEntry;
public class ModuleEntry extends ConstantPoolEntry {
private final int nameIndex;
public ModuleEntry(int nameIndex) {
this.nameIndex = nameIndex;
}
}

View file

@ -0,0 +1,9 @@
package nl.sander.jsontoy2.java.constantpool;
public class PackageEntry extends ConstantPoolEntry {
private final int nameIndex;
public PackageEntry(int nameIndex) {
this.nameIndex = nameIndex;
}
}

View file

@ -0,0 +1,11 @@
package nl.sander.jsontoy2.javassist;
public class ClassCreationException extends RuntimeException {
public ClassCreationException(Throwable e) {
super(e);
}
public ClassCreationException(String message) {
super(message);
}
}

View file

@ -0,0 +1,183 @@
package nl.sander.jsontoy2.javassist;
import javassist.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.util.Arrays.asList;
/**
* Facade for javassist functions
*/
public class Javassist {
private static final String STRING = "java.lang.String";
private static final String BOOLEAN = "java.lang.Boolean";
private static final String CHARACTER = "java.lang.Character";
private static final String BYTE = "java.lang.Byte";
private static final String DOUBLE = "java.lang.Double";
private static final String FLOAT = "java.lang.Float";
private static final String LONG = "java.lang.Long";
private static final String SHORT = "java.lang.Short";
private static final String INTEGER = "java.lang.Integer";
private final static Set<String> wrappersAndString = new HashSet<>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER, STRING));
private final static Set<String> wrappers = new HashSet<>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER));
private static final String COLLECTION = "java.util.Collection";
private static final String LIST = "java.util.List";
private static final String SET = "java.util.Set";
private static final String MAP = "java.util.Map";
private final static ClassPool pool = ClassPool.getDefault();
public static CtClass getTypeDefinition(Class<?> type) {
try {
return pool.get(type.getName());
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
public static CtClass getTypeDefinition(String type) {
try {
return pool.get(type);
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
public static CtClass createClass(String className, CtClass baseClass) {
return pool.makeClass(className, baseClass);
}
public static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
}
public static boolean isBasicType(CtClass beanClass) {
return isPrimitiveOrWrapperOrString(beanClass) || isList(beanClass);
}
public static boolean isList(CtClass type) {
try {
List<CtClass> interfaces = new ArrayList<>(asList(type.getInterfaces()));
interfaces.add(type);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET)) {
return true;
}
}
return false;
} catch (NotFoundException e) {
throw new IllegalStateException(e);
}
}
public static boolean isCollection(CtClass type) {
try {
List<CtClass> interfaces = new ArrayList<>(asList(type.getInterfaces()));
interfaces.add(type);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET) || interfaze.getName().equals(MAP)) {
return true;
}
}
return false;
} catch (NotFoundException e) {
throw new IllegalStateException(e);
}
}
public static boolean isMap(CtClass type) throws NotFoundException {
List<CtClass> interfaces = new ArrayList<>(asList(type.getInterfaces()));
interfaces.add(type);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(MAP)) {
return true;
}
}
return false;
}
/*
* Retrieves getter methods from a class
*/
public static List<CtMethod> getGetters(CtClass type) {
List<CtMethod> methods = new ArrayList<>();
List<CtField> fields = getAllFields(type);
for (CtField field : fields) {
try {
CtMethod method = type.getMethod(getGetterMethod(field), getDescription(field));
if (Modifier.isPublic(method.getModifiers())) {
methods.add(method);
}
} catch (NotFoundException n) {
// ignore
}
}
return methods;
}
private static String getGetterMethod(CtField field) {
return "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
}
private static List<CtField> getAllFields(CtClass beanClass) {
try {
List<CtField> allfields = new ArrayList<>(asList(beanClass.getDeclaredFields()));
if (beanClass.getSuperclass() != null) {
return getAllFields(beanClass.getSuperclass(), allfields);
}
return allfields;
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
private static List<CtField> getAllFields(CtClass beanClass, List<CtField> allfields) {
allfields.addAll(asList(beanClass.getDeclaredFields()));
return allfields;
}
private static String getDescription(CtField field) throws NotFoundException {
if (field.getType().isArray()) {
return "()[" + innerClassName(field.getType().getName()) + ";";
} else if (!field.getType().isPrimitive()) {
return "()" + innerClassName(field.getType().getName()) + ";";
} else {
return "()" + asPrimitive(field.getType().getName());
}
}
private static String innerClassName(String name) {
return "L" + name.replaceAll("\\.", "/").replaceAll("\\[]", "");
}
private static String asPrimitive(String name) {
switch (name) {
case "int":
return "I";
case "byte":
return "B";
case "float":
return "F";
case "long":
return "J";
case "boolean":
return "Z";
case "char":
return "C";
case "double":
return "D";
case "short":
return "S";
}
return "";
}
}

View file

@ -1,5 +1,7 @@
package nl.sander.jsontoy2.readers; package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -7,7 +9,7 @@ import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.function.Supplier; import java.util.function.Supplier;
public abstract class AbstractDatesReader<T> { public abstract class AbstractDatesReader<T> extends JsonValueReader<T> {
private static final ZoneId zone = ZoneId.systemDefault(); private static final ZoneId zone = ZoneId.systemDefault();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class BooleanReader implements JsonValueReader<Boolean> { public class BooleanReader extends JsonValueReader<Boolean> {
@Override @Override
public Boolean read(Parser parser) { public Boolean read(Parser parser) {
return parser.parseBoolean(); return parser.parseBoolean();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class ByteReader implements JsonValueReader<Byte> { public class ByteReader extends JsonValueReader<Byte> {
@Override @Override
public Byte read(Parser parser) { public Byte read(Parser parser) {
return parser.parseByte(); return parser.parseByte();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class CharReader implements JsonValueReader<Character> { public class CharReader extends JsonValueReader<Character> {
@Override @Override
public Character read(Parser parser) { public Character read(Parser parser) {
return parser.parseCharacter(); return parser.parseCharacter();

View file

@ -1,12 +1,11 @@
package nl.sander.jsontoy2.readers; package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Date; import java.util.Date;
public class DateReader extends AbstractDatesReader<Date> implements JsonValueReader<Date> { public class DateReader extends AbstractDatesReader<Date> {
@Override @Override
public Date read(Parser parser) { public Date read(Parser parser) {

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class DoubleReader implements JsonValueReader<Double> { public class DoubleReader extends JsonValueReader<Double> {
@Override @Override
public Double read(Parser parser) { public Double read(Parser parser) {
return parser.parseDouble(); return parser.parseDouble();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class FloatReader implements JsonValueReader<Float> { public class FloatReader extends JsonValueReader<Float> {
@Override @Override
public Float read(Parser parser) { public Float read(Parser parser) {
return parser.parseFloat(); return parser.parseFloat();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class IntegerReader implements JsonValueReader<Integer> { public class IntegerReader extends JsonValueReader<Integer> {
@Override @Override
public Integer read(Parser parser) { public Integer read(Parser parser) {
return parser.parseInteger(); return parser.parseInteger();

View file

@ -6,7 +6,7 @@ import nl.sander.jsontoy2.Parser;
import java.util.List; import java.util.List;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class ListReader implements JsonValueReader<List> { public class ListReader extends JsonValueReader<List> {
@Override @Override
public List<?> read(Parser parser) { public List<?> read(Parser parser) {
return parser.parseArray(); return parser.parseArray();

View file

@ -1,12 +1,11 @@
package nl.sander.jsontoy2.readers; package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
public class LocalDateTimeReader extends AbstractDatesReader<LocalDateTime> implements JsonValueReader<LocalDateTime> { public class LocalDateTimeReader extends AbstractDatesReader<LocalDateTime> {
@Override @Override
public LocalDateTime read(Parser parser) { public LocalDateTime read(Parser parser) {

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class LongReader implements JsonValueReader<Long> { public class LongReader extends JsonValueReader<Long> {
@Override @Override
public Long read(Parser parser) { public Long read(Parser parser) {
return parser.parseLong(); return parser.parseLong();

View file

@ -6,7 +6,7 @@ import nl.sander.jsontoy2.Parser;
import java.util.Map; import java.util.Map;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class MapReader implements JsonValueReader<Map> { public class MapReader extends JsonValueReader<Map> {
@Override @Override
public Map read(Parser parser) { public Map read(Parser parser) {
return parser.parseObject(); return parser.parseObject();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class ShortReader implements JsonValueReader<Short> { public class ShortReader extends JsonValueReader<Short> {
@Override @Override
public Short read(Parser parser) { public Short read(Parser parser) {

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader; import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser; import nl.sander.jsontoy2.Parser;
public class StringReader implements JsonValueReader<String> { public class StringReader extends JsonValueReader<String> {
@Override @Override
public String read(Parser parser) { public String read(Parser parser) {

View file

@ -0,0 +1,33 @@
package nl.sander.jsontoy2;
import nl.sander.jsontoy2.testobjects.BooleanBean;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class BeanWithBoolean {
@Test
public void testTrue() {
// Arrange
BooleanBean booleanBean = JsonReader.read(BooleanBean.class, "{\"value\": true, \"value2\": true}");
// Assert
assertEquals(new BooleanBean(true, Boolean.TRUE), booleanBean);
}
@Test
public void testFalse() {
// Arrange
BooleanBean booleanBean = JsonReader.read(BooleanBean.class, "{\"value\": false, \"value2\": false}");
// Assert
assertEquals(new BooleanBean(false, Boolean.FALSE), booleanBean);
}
@Test
public void testIllegalValues() {
// Assert
assertThrows(JsonParseException.class, () -> JsonReader.read(BooleanBean.class, "{\"value\": true, \"value2\": True}"));
}
}

View file

@ -7,23 +7,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class Ints { public class Ints {
@Test @Test
public void testSimpleInt(){ public void testSimpleInt() {
assertEquals(1,JsonReader.read(Integer.class,"1")); assertEquals(1, JsonReader.read(Integer.class, "1"));
} }
@Test @Test
public void testSimplePrimitiveInt(){ public void testSimplePrimitiveInt() {
assertEquals(1,JsonReader.read(int.class,"1")); assertEquals(1, JsonReader.read(int.class, "1"));
} }
@Test @Test
public void testSimpleNegativeInt(){ public void testSimpleNegativeInt() {
assertEquals(-20001,JsonReader.read(Integer.class,"-20001")); assertEquals(-20001, JsonReader.read(Integer.class, "-20001"));
} }
@Test @Test
public void testSimpleNegativePrimitiveInt(){ public void testSimpleNegativePrimitiveInt() {
assertEquals(Integer.MIN_VALUE,JsonReader.read(int.class,"-2147483684")); assertEquals(Integer.MIN_VALUE, JsonReader.read(int.class, "-2147483684"));
} }
@Test @Test

View file

@ -38,23 +38,23 @@ public class Lists {
} }
@Test @Test
public void singleInt() { public void singleLong() {
List<Integer> list = JsonReader.read(List.class, " [ 1 ]"); List<Long> list = JsonReader.read(List.class, " [ 1 ]");
List<Integer> expected = List.of(1); List<Long> expected = List.of(1L);
assertEquals(expected, list); assertEquals(expected, list);
} }
@Test @Test
public void multipleInts() { public void multipleLongs() {
List<Integer> list = JsonReader.read(List.class, "[1,2]"); List<Long> list = JsonReader.read(List.class, "[1,2]");
List<Integer> expected = List.of(1, 2); List<Long> expected = List.of(1L, 2L);
assertEquals(expected, list); assertEquals(expected, list);
} }
@Test @Test
public void intDoubleBooleanString() { public void longDoubleBooleanString() {
List<Integer> list = JsonReader.read(List.class, "[1, 2.5,false, \"hello jason\"]"); List<Integer> list = JsonReader.read(List.class, "[1, 2.5,false, \"hello jason\"]");
List<?> expected = List.of(1, 2.5, false, "hello jason"); List<?> expected = List.of(1L, 2.5, false, "hello jason");
assertEquals(expected, list); assertEquals(expected, list);
} }

View file

@ -30,7 +30,7 @@ public class Maps {
Map<String, Object> map = JsonReader.read(Map.class, "{\"value1\" : \"jason\" ,\n \"value2\":1}"); Map<String, Object> map = JsonReader.read(Map.class, "{\"value1\" : \"jason\" ,\n \"value2\":1}");
Map<String, Object> expected = new HashMap<>(); Map<String, Object> expected = new HashMap<>();
expected.put("value1", "jason"); expected.put("value1", "jason");
expected.put("value2", 1); expected.put("value2", 1L);
assertEquals(expected, map); assertEquals(expected, map);
} }
@ -40,9 +40,9 @@ public class Maps {
} }
@Test @Test
public void singleInts() { public void singleLongs() {
Map<String, Integer> map = JsonReader.read(Map.class, " { \"1\":2 }"); Map<String, Long> map = JsonReader.read(Map.class, " { \"1\":2 }");
Map<String, Integer> expected = Collections.singletonMap("1", 2); Map<String, Long> expected = Collections.singletonMap("1", 2L);
assertEquals(expected, map); assertEquals(expected, map);
} }
@ -63,7 +63,7 @@ public class Maps {
public void listInMap() { public void listInMap() {
Map<String, List> list = JsonReader.read(Map.class, " { \"list\" : [ 1 ] } "); Map<String, List> list = JsonReader.read(Map.class, " { \"list\" : [ 1 ] } ");
Map<String, List> expected = new HashMap<>(); Map<String, List> expected = new HashMap<>();
expected.put("list", List.of(1)); expected.put("list", List.of(1L));
assertEquals(expected, list); assertEquals(expected, list);
} }

View file

@ -0,0 +1,34 @@
package nl.sander.jsontoy2;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ParserFactoryTest {
Parser parser1;
Parser parser2;
@Test
public void differentThreadsHaveTheirOwnParser() throws InterruptedException {
Thread t1 = new Thread(() -> parser1 = ParserFactory.getParser(""));
Thread t2 = new Thread(() -> parser2 = ParserFactory.getParser(""));
t1.start();
t2.start();
t1.join();
t2.join();
assertNotSame(parser1, parser2);
}
@Test
public void sameThreadCanReuseParser() {
Parser parser1 = ParserFactory.getParser("1");
Integer value1 = parser1.parseInteger();
Parser parser2 = ParserFactory.getParser("2");
Integer value2 = parser2.parseInteger();
assertSame(parser1, parser2);
assertEquals(1, value1);
assertEquals(2, value2);
}
}

View file

@ -0,0 +1,87 @@
package nl.sander.jsontoy2;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ParserTest {
@Test
public void testFloat() {
assertEquals(31.415927F, new Parser(getInputStream("31.415927")).parseFloat());
}
@Test
public void testDouble() {
assertEquals(3.1415927D, new Parser(getInputStream("3.1415927")).parseDouble());
}
@Test
public void testInt() {
assertEquals(31415927, new Parser(getInputStream("31415927")).parseInteger());
}
@Test
public void testShort() {
assertEquals((short) 31415, new Parser(getInputStream("31415")).parseShort());
}
@Test
public void testByte() {
assertEquals((byte) 31, new Parser(getInputStream("31")).parseByte());
}
@Test
public void testBooleanTrue() {
assertEquals(true, new Parser(getInputStream("true")).parseBoolean());
}
@Test
public void testBooleanFalse() {
assertEquals(false, new Parser(getInputStream("false")).parseBoolean());
}
@Test
public void testBooleanInvalid() {
assertThrows(JsonParseException.class, () -> new Parser(getInputStream("falsy")).parseBoolean());
}
@Test
public void testArray() {
assertEquals(List.of("3", "1", "4", "1"), new Parser(getInputStream("[\"3\",\"1\",\"4\",\"1\"]")).parseArray());
}
@Test
public void testArrayWithType() {
assertEquals(List.of(3D, 1D, 4D, 1D), new Parser(getInputStream("[3,1,4,1]")).parseArray(Double.class));
}
@Test
public void testObject() {
assertEquals(Map.of("pi", 3.1415927D), new Parser(getInputStream("{\"pi\":3.1415927}")).parseObject());
}
@Test
public void testObjectWithType() {
assertEquals(Map.of("pi", 3.1415927F), new Parser(getInputStream("{\"pi\":3.1415927}")).parseObject(PiBean.class));
}
@Test
public void testChar() {
assertEquals('3', new Parser(getInputStream("\"3\"")).parseCharacter());
}
private InputStream getInputStream(String jsonString) {
return new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8));
}
static class PiBean {
private float pi;
}
}

View file

@ -0,0 +1,169 @@
package nl.sander.jsontoy2;
import nl.sander.jsontoy2.testobjects.*;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class WrapperObjectTests {
@Test
public void testNested() {
SimpleBean bean = JsonReader.read(SimpleBean.class, "{\"data1\": \"value1\",\"data2\": \"value2\"}");
assertEquals(SimpleBean.class, bean.getClass());
assertEquals("value1", bean.getData1());
assertEquals("value2", bean.getData2());
}
@Test
public void testBoolean() {
BooleanBean trueBean = JsonReader.read(BooleanBean.class, "{\"value\": true}");
assertTrue(trueBean.isValue());
// second call to read, must not recreate class definition, (it would not compile)
// so this test implicitly tests caching function too
BooleanBean falseBean = JsonReader.read(BooleanBean.class, "{\"value2\": false}");
assertFalse(falseBean.isValue());
}
@Test
public void testString() {
StringBean stringBean = JsonReader.read(StringBean.class, "{\"value\": \"haha\"}");
assertEquals("haha", stringBean.getValue());
}
@Test
public void testInteger() {
IntegerBean integerBean = JsonReader.read(IntegerBean.class, "{\"value\": 1,\"value2\": 2}");
assertEquals(1, integerBean.getValue());
assertEquals(Integer.valueOf(2), integerBean.getValue2());
}
@Test
public void testLong() {
LongBean longBean = JsonReader.read(LongBean.class, "{\"value\": 100000000000,\"value2\": 100000000000}");
assertEquals(100000000000L, longBean.getValue());
assertEquals(Long.valueOf(100000000000L), longBean.getValue2());
}
@Test
public void testFloat() {
FloatBean floatBean = JsonReader.read(FloatBean.class, "{\"value\": 1.0,\"value2\": 100000000000}");
assertEquals(1.0F, floatBean.getValue(), 0.1F);
assertEquals(100000000000.0F, floatBean.getValue2(), 0.1F);
}
@Test
public void testDouble() {
DoubleBean doubleBean = JsonReader.read(DoubleBean.class, "{\"value\": 1.0,\"value2\": 100000000000}");
assertEquals(1.0D, doubleBean.getValue(), 0.1D);
assertEquals(100000000000.0D, doubleBean.getValue2(), 0.1D);
}
@Test
public void testShort() {
ShortBean shortBean = JsonReader.read(ShortBean.class, "{\"value\": 1,\"value2\": -11}");
assertEquals(1, shortBean.getValue());
assertEquals((short) -11, shortBean.getValue2().shortValue());
}
@Test
public void testByte() {
ByteBean byteBean = JsonReader.read(ByteBean.class, "{\"value\": 1,\"value2\": -11}");
assertEquals(1, byteBean.getValue());
assertEquals((byte) -11, byteBean.getValue2().shortValue());
}
@Test
public void testStringList() {
StringListBean listBean = JsonReader.read(StringListBean.class, "{\"value\": [\"a\",\"b\"]}");
assertEquals(Arrays.asList("a", "b"), listBean.getValue());
}
@Test
public void testStringSet() {
StringSetBean listBean = JsonReader.read(StringSetBean.class, "{\"value\": [\"a\",\"b\"]}");
assertEquals(new HashSet<>(Arrays.asList("a", "b")), listBean.getValue());
}
// @Test
//TODO enable and fix code
public void testIntegerList() {
IntegerListBean listBean = JsonReader.read(IntegerListBean.class, "{\"value\": [1,22]}");
assertEquals(Arrays.asList(1, 22), listBean.getValue());
}
// @Test
//TODO enable and fix code
public void testCharacterList() {
CharacterListBean listBean = JsonReader.read(CharacterListBean.class, "{\"value\": [\"a\", \"[\", \"^\"]}");
assertEquals(Arrays.asList('a', '[', '^'), listBean.getValue());
}
// @Test
//TODO enable and fix code
public void testShortList() {
ShortListBean listBean = JsonReader.read(ShortListBean.class, "{\"value\": [-1,0,1]}");
assertEquals(Arrays.asList((short) -1, (short) 0, (short) 1), listBean.getValue());
}
@Test
public void testBooleanList() {
BooleanListBean listBean = JsonReader.read(BooleanListBean.class, "{\"value\": [true,false]}");
assertEquals(Arrays.asList(true, false), listBean.getValue());
}
// @Test
// public void testFloatList() {
// FloatListBean listBean = JsonReader.read(FloatListBean.class, "{\"value\": [-100.156,78.0]}");
// assertEquals(Arrays.asList(-100.156F, 78.0F), listBean.getValue());
// }
//
// @Test
// public void testByteList() {
// ByteListBean listBean = JsonReader.read(ByteListBean.class, "{\"value\": [-100,78]}");
// assertEquals(Arrays.asList((byte) -100, (byte) 78), listBean.getValue());
// }
//
// @Test
//TODO enable and fix code
public void testDoubleList() {
DoubleListBean listBean = JsonReader.read(DoubleListBean.class, "{\"value\": [-100.156,78.0]}");
assertEquals(Arrays.asList(-100.156D, 78.0D), listBean.getValue());
}
@Test
public void testNestedBean() {
NestedBean nestedBean = JsonReader.read(NestedBean.class, "{\"value\": {\"value\": \"nested\"}}");
assertEquals(new NestedBean(new InnerBean("nested")), nestedBean);
}
@Test
public void testStringMap() {
StringMapBean actual = JsonReader.read(StringMapBean.class, "{\"map\": {\"a:\": \"b\", \"c:\" : \"d\" }}");
StringMapBean expected = new StringMapBean("a:", "b", "c:", "d");
assertEquals(expected, actual);
}
@Test
public void testLinkedList() {
LinkedListBean actual = JsonReader.read(LinkedListBean.class, "{\"list\": [\"a\"]}");
LinkedList<String> actualList = actual.getList();
assertEquals(List.of("a"), actualList);
}
@Test
public void testArray() {
ArrayBean actual = JsonReader.read(ArrayBean.class, "{\"array\": [\"a\"]}");
assertEquals(1, actual.getArray().length);
assertEquals("a", actual.getArray()[0]);
}
}

View file

@ -2,18 +2,30 @@ package nl.sander.jsontoy2.java;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
public class ClassReaderTest { public class ClassReaderTest {
//part of the test
public int field; public int field;
public String method(String value, int[] value2, int value3) {
return value;
}
//
@Test @Test
public void testReadClass() { public void testReadClass() {
ClassObject<ClassReaderTest> object = new ClassReader().parse(ClassReaderTest.class); ClassObject object = new ClassReader().parse(ClassReaderTest.class);
assertEquals(Set.of(new Field("field", "I")), object.getFields()); assertEquals(Set.of(new Field("field", "I")), object.getFields());
assertEquals(Set.of(new Method("<init>", "()V"), new Method("testReadClass", "()V")), object.getMethods()); assertEquals(new TreeSet<>(Set.of(
new Method("method", List.of("Ljava/lang/String", "[I", "I"), "Ljava/lang/String"),
new Method("testReadClass", null, "V"),
new Method("<init>", null, "V"))
), new TreeSet<>(object.getMethods()));
} }
} }

View file

@ -0,0 +1,69 @@
package nl.sander.jsontoy2.javassist;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import nl.sander.jsontoy2.testobjects.BooleanBean;
import nl.sander.jsontoy2.testobjects.SimpleBean;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class JavassistTest {
@Test
public void getTypeDefinitionForClass() throws NotFoundException {
// Act
CtClass typeDefinition = Javassist.getTypeDefinition(BooleanBean.class);
// Assert
assertEquals("setValue", typeDefinition.getDeclaredMethod("setValue").getName());
}
@Test
public void getTypeDefinitionForClassname() throws NotFoundException {
// Act
CtClass typeDefinition = Javassist.getTypeDefinition("nl.sander.jsontoy2.testobjects.BooleanBean");
// Assert
assertNotNull(typeDefinition);
assertEquals("BooleanBean", typeDefinition.getSimpleName());
assertEquals("nl.sander.jsontoy2.testobjects.BooleanBean", typeDefinition.getName());
assertEquals("value", typeDefinition.getDeclaredField("value").getName());
assertEquals("setValue", typeDefinition.getDeclaredMethod("setValue").getName());
}
@Test
public void createClass() {
// Act
CtClass newClass = Javassist.createClass("UberClass", Javassist.getTypeDefinition(Object.class));
// Assert
assertEquals("UberClass", newClass.getName());
}
@Test
public void isCollection() {
// Act/ Assert
assertTrue(Javassist.isCollection(Javassist.getTypeDefinition(List.class)));
assertTrue(Javassist.isCollection(Javassist.getTypeDefinition(HashMap.class)));
assertFalse(Javassist.isCollection(Javassist.getTypeDefinition(String.class)));
}
@Test
public void getGetterMethod() {
// Arrange
CtClass typeDefinition = Javassist.getTypeDefinition(SimpleBean.class);
// Act
List<CtMethod> getterMethods = Javassist.getGetters(typeDefinition);
// Assert
assertEquals(2, getterMethods.size());
assertEquals("getData1", getterMethods.get(0).getName());
assertEquals("getData2", getterMethods.get(1).getName());
}
}

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class ArrayBean {
private String[] array;
public void setArray(String[] array) {
this.array = array;
}
public String[] getArray() {
return array;
}
}

View file

@ -0,0 +1,51 @@
package nl.sander.jsontoy2.testobjects;
import java.util.Objects;
/*
* test object
*/
public class BooleanBean {
private boolean value;
private Boolean value2;
public BooleanBean() {
}
public BooleanBean(boolean value, Boolean value2) {
this.value = value;
this.value2 = value2;
}
public boolean isValue() {
return value;
}
public void setValue(boolean value) {
this.value = value;
}
public Boolean getValue2() {
return value2;
}
public void setValue2(Boolean value2) {
this.value2 = value2;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BooleanBean that = (BooleanBean) o;
return value == that.value &&
value2.equals(that.value2);
}
@Override
public int hashCode() {
return Objects.hash(value, value2);
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.testobjects;
import java.util.List;
/*
* test object
*/
public class BooleanListBean {
private List<Boolean> value;
public List<Boolean> getValue() {
return value;
}
public void setValue(List<Boolean> value) {
this.value = value;
}
@Override
public String toString() {
return "BooleanListBean{" +
"value=" + value +
'}';
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class ByteBean {
private byte value;
private Byte value2;
public ByteBean() {
}
public byte getValue() {
return value;
}
public void setValue(byte value) {
this.value = value;
}
public Byte getValue2() {
return value2;
}
public void setValue2(Byte value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.List;
/*
* test object
*/
public class ByteListBean {
private List<Byte> value;
public List<Byte> getValue() {
return value;
}
public void setValue(List<Byte> value) {
this.value = value;
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.List;
/*
* test object
*/
public class CharacterListBean {
private List<Character> value;
public List<Character> getValue() {
return value;
}
public void setValue(List<Character> value) {
this.value = value;
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class DoubleBean {
private double value;
private Double value2;
public DoubleBean() {
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
public Double getValue2() {
return value2;
}
public void setValue2(Double value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.jsontoy2.testobjects;
import nl.sander.jsontoy2.JsonReader;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*
* test object
*/
public class DoubleListBean {
private List<Double> value;
public List<Double> getValue() {
return value;
}
public void setValue(List<Double> value) {
this.value = value;
}
@Test
public void testTrue() {
assertEquals(true, JsonReader.read(Boolean.class, "true"));
}
}

View file

@ -0,0 +1,33 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class FloatBean {
private float value;
private Float value2;
public FloatBean() {
}
public FloatBean(float value, Float value2) {
this.value = value;
this.value2 = value2;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public Float getValue2() {
return value2;
}
public void setValue2(Float value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.List;
/*
* test object
*/
public class FloatListBean {
private List<Float> value;
public List<Float> getValue() {
return value;
}
public void setValue(List<Float> value) {
this.value = value;
}
}

View file

@ -0,0 +1,45 @@
package nl.sander.jsontoy2.testobjects;
import java.util.Objects;
/*
* test object
*/
public class InnerBean {
private String value;
public InnerBean() {
}
public InnerBean(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InnerBean innerBean = (InnerBean) o;
return value.equals(innerBean.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return "InnerBean{" +
"value='" + value + '\'' +
'}';
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class IntegerBean {
private int value;
private Integer value2;
public IntegerBean() {
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Integer getValue2() {
return value2;
}
public void setValue2(Integer value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.List;
/*
* test object
*/
public class IntegerListBean {
private List<Integer> value;
public List<Integer> getValue() {
return value;
}
public void setValue(List<Integer> value) {
this.value = value;
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.LinkedList;
/*
* test object
*/
public class LinkedListBean {
private LinkedList<String> list;
public LinkedList<String> getList() {
return list;
}
public void setList(LinkedList<String> list) {
this.list = list;
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class LongBean {
private long value;
private Long value2;
public LongBean() {
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
public Long getValue2() {
return value2;
}
public void setValue2(Long value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,51 @@
package nl.sander.jsontoy2.testobjects;
import java.util.Objects;
/*
* test object
*/
public class NestedBean {
private InnerBean value;
private String empty;
public NestedBean() {
}
public NestedBean(InnerBean value) {
this.value = value;
}
public InnerBean getValue() {
return value;
}
public void setValue(InnerBean value) {
this.value = value;
}
public void setEmpty(String empty) {
this.empty = empty;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NestedBean that = (NestedBean) o;
return value.equals(that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return "NestedBean{" +
"value=" + value +
'}';
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class ShortBean {
private short value;
private Short value2;
public ShortBean() {
}
public short getValue() {
return value;
}
public void setValue(short value) {
this.value = value;
}
public Short getValue2() {
return value2;
}
public void setValue2(Short value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.List;
/*
* test object
*/
public class ShortListBean {
private List<Short> value;
public List<Short> getValue() {
return value;
}
public void setValue(List<Short> value) {
this.value = value;
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class SimpleBean {
String data1;
String data2;
public String getData1() {
return data1;
}
public void setData1(String data1) {
this.data1 = data1;
}
public String getData2() {
return data2;
}
public void setData2(String data2) {
this.data2 = data2;
}
}

View file

@ -0,0 +1,16 @@
package nl.sander.jsontoy2.testobjects;
/*
* test object
*/
public class StringBean {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.List;
/*
* test object
*/
public class StringListBean {
private List<String> value;
public List<String> getValue() {
return value;
}
public void setValue(List<String> value) {
this.value = value;
}
}

View file

@ -0,0 +1,49 @@
package nl.sander.jsontoy2.testobjects;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/*
* test object
*/
public class StringMapBean {
private Map<String, String> map = new HashMap<>();
public StringMapBean() {
this.map = new HashMap<>();
}
public StringMapBean(String... keysAndValues) {
if (keysAndValues.length % 2 == 1) {
throw new IllegalArgumentException("uneven number of arguments is not allowed here");
}
for (int i = 0; i < keysAndValues.length; ) {
String key = keysAndValues[i++];
String value = keysAndValues[i++];
map.put(key, value);
}
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Map<String, String> getMap() {
return map;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StringMapBean that = (StringMapBean) o;
return map.equals(that.map);
}
@Override
public int hashCode() {
return Objects.hash(map);
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.jsontoy2.testobjects;
import java.util.Set;
/*
* test object
*/
public class StringSetBean {
private Set<String> value;
public Set<String> getValue() {
return value;
}
public void setValue(Set<String> value) {
this.value = value;
}
}