toks) throws SDLParseException {
valuesStartIndex = 3;
} else {
- tag = new Tag(t0.getText());
+ tag = SDL.tag(t0.getText()).build();
}
// read values
diff --git a/sdlang/src/main/java/com/singingbush/sdl/SDL.java b/sdlang/src/main/java/com/singingbush/sdl/SDL.java
index d39b15b..91186fb 100644
--- a/sdlang/src/main/java/com/singingbush/sdl/SDL.java
+++ b/sdlang/src/main/java/com/singingbush/sdl/SDL.java
@@ -19,12 +19,15 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.io.*;
+import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.List;
+import java.util.Optional;
import java.util.SortedMap;
import java.util.Base64;
@@ -35,7 +38,7 @@
*/
public class SDL {
- public static final SdlValue NULL = new SdlValue<>(null, SdlType.NULL);
+ public static final SdlValue> NULL = new SdlValue<>(null, SdlType.NULL);
/**
* The SDL standard date format "yyyy/MM/dd" or "y/M/d"
@@ -222,7 +225,7 @@ private static String escape(Character ch) {
* type
*/
// @SuppressWarnings("unchecked")
-// public static SdlValue coerceOrFail(final SdlValue value) {
+// public static SdlValue> coerceOrFail(final SdlValue> value) {
// if(value == null)
// return null;
//
@@ -346,6 +349,52 @@ public static SdlValue value(@NotNull final String value, final boolean
new SdlValue<>(Parser.parseString(String.format("\"%s\"", value)), SdlType.STRING);
}
+ /**
+ *
+ * @param value
+ * @param sdlType should be a literal type: STRING, STRING_MULTILINE, CHARACTER, BOOLEAN, NUMBER, DATE, DATETIME, DURATION, BINARY, NULL
+ * @return
+ * @since 2.3.0
+ */
+ // experimental (keep for internal use for now)
+ static SdlValue> value(@NotNull final Object value, final SdlType sdlType) {
+ // STRING, STRING_MULTILINE, CHARACTER, BOOLEAN, NUMBER, DATE, DATETIME, DURATION, BINARY, NULL
+ switch (sdlType) {
+ case STRING:
+ return value(String.valueOf(value), false);
+ case STRING_MULTILINE:
+ return value(String.valueOf(value), true);
+ case CHARACTER:
+ return value((char) value);
+ case BOOLEAN:
+ return value((boolean) value);
+ case NUMBER:
+ // handle number types: int, long, float, double
+ if(Integer.class.isAssignableFrom(value.getClass())) {
+ return value((int) value);
+ } else if(Long.class.isAssignableFrom(value.getClass())) {
+ return value((long) value);
+ } else if(Float.class.isAssignableFrom(value.getClass())) {
+ return value((float) value);
+ } else if(Double.class.isAssignableFrom(value.getClass())) {
+ return value((double) value);
+ } else {
+ throw new IllegalArgumentException(String.format("SdlType was NUMBER but value of type %s could not be assigned", value.getClass()));
+ }
+ case DATE:
+ return value((LocalDate) value);
+ case DATETIME:
+ return value((LocalDateTime) value);
+ case DURATION:
+ return value((Duration) value);
+ // case BINARY:
+ // return value();
+ // break;
+ default:
+ throw new IllegalArgumentException("Should be a literal type");
+ }
+ }
+
/**
* @param value text to be converted to SDL
* @return an SDL char
@@ -441,7 +490,7 @@ public static SdlValue value(@NotNull final Duration value) {
* @return an SDL binary
* @since 2.1.0
*/
- public static SdlValue value(final byte[] value) {
+ public static SdlValue> value(final byte[] value) {
return new SdlValue<>(value, SdlType.BINARY);
}
@@ -455,7 +504,7 @@ public static SdlValue value(final byte[] value) {
* @throws NumberFormatException If the text represents a malformed number.
*/
@Deprecated
- public static SdlValue value(String literal) {
+ public static SdlValue> value(String literal) {
if(literal==null) {
throw new IllegalArgumentException("literal argument to SDL.value(String) cannot be null");
}
@@ -515,13 +564,16 @@ public static SdlValue value(String literal) {
* @throws IllegalArgumentException If the string is null or contains
* literals that cannot be parsed
*/
- public static List list(@NotNull final String valueList) {
+ public static List> list(@NotNull final String valueList) {
if(valueList==null) {
throw new IllegalArgumentException("valueList argument to SDL.list(String) cannot be null");
}
try {
- return new Tag("root").read(valueList).getChild("content").getValues();
+ return SDL.tag("root").build()
+ .read(valueList)
+ .getChild("content")
+ .getValues();
} catch(SDLParseException e) {
throw new IllegalArgumentException(e.getMessage());
}
@@ -555,13 +607,13 @@ public static List list(@NotNull final String valueList) {
* @throws IllegalArgumentException If the string is null or contains
* literals that cannot be parsed or the map is malformed
*/
- public static SortedMap map(@NotNull final String attributeString) {
+ public static SortedMap> map(@NotNull final String attributeString) {
if(attributeString==null) {
throw new IllegalArgumentException("attributeString argument to SDL.map(String) cannot be null");
}
try {
- return new Tag("root")
+ return SDL.tag("root").build()
.read("atts " + attributeString)
.getChild("atts")
.getAttributes();
@@ -569,4 +621,56 @@ public static SortedMap map(@NotNull final String attributeStri
throw new IllegalArgumentException(e.getMessage());
}
}
+
+ /**
+ *
+ * @param obj a pojo that's annotated with Tag
+ * @param out an output stream to write the serialised data to
+ * @throws IOException if an I/O error occurs
+ * @since 2.3.0
+ * @see com.singingbush.sdl.annotations.Tag
+ */
+ public static void toSDL(@NotNull Object obj, @NotNull final OutputStream out) throws IOException {
+ out.write(convert(obj).toString().getBytes());
+ out.flush();
+ }
+
+ /**
+ *
+ * @param obj a pojo that's annotated with Tag
+ * @return a string of SDL that represents the annotated pojo
+ * @since 2.3.0
+ * @see com.singingbush.sdl.annotations.Tag
+ */
+ public static String toSDL(@NotNull Object obj) {
+ return convert(obj).toString();
+ }
+
+
+ // todo: should this live somewhere else??
+ private static Tag convert(@NotNull final Object obj) {
+ if(obj.getClass().isAnnotationPresent(com.singingbush.sdl.annotations.Tag.class)) {
+ final SdlAnnotationProcessor processor = new SdlAnnotationProcessor(obj);
+ return processor.process();
+ }
+ throw new IllegalArgumentException(obj.getClass().getSimpleName() + " does not have @Tag annotation");
+ }
+
+
+ public static Optional fromSDL(@NotNull final String sdl, @NotNull final Class clazz) throws SDLParseException, IOException {
+ return SDL.fromSDL(new StringReader(sdl), clazz);
+ }
+
+// public static Optional fromSDL(@NotNull final String sdl, @NotNull final Type typeOfT) throws SDLParseException {
+// return SDL.fromSDL(new StringReader(sdl), typeOfT);
+// }
+
+ public static Optional fromSDL(@NotNull final Reader reader, @NotNull final Class clazz) throws SDLParseException, IOException {
+ return new Parser(reader).parse(clazz);
+ }
+
+// public static Optional fromSDL(@NotNull final Reader reader, @NotNull final Type typeOfT) {
+// return new Parser(reader).parse(typeOfT);
+// }
+
}
diff --git a/sdlang/src/main/java/com/singingbush/sdl/SdlAnnotationProcessor.java b/sdlang/src/main/java/com/singingbush/sdl/SdlAnnotationProcessor.java
new file mode 100644
index 0000000..eb1a586
--- /dev/null
+++ b/sdlang/src/main/java/com/singingbush/sdl/SdlAnnotationProcessor.java
@@ -0,0 +1,144 @@
+package com.singingbush.sdl;
+
+import com.singingbush.sdl.annotations.Attribute;
+import com.singingbush.sdl.annotations.Value;
+
+import java.lang.reflect.Field;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+
+/*
+* Similar to com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector
+*
+*/
+public class SdlAnnotationProcessor {
+
+ private final Object obj;
+
+ public SdlAnnotationProcessor(Object obj) {
+ this.obj = obj;
+ if(!obj.getClass().isAnnotationPresent(com.singingbush.sdl.annotations.Tag.class)) {
+ throw new IllegalArgumentException(obj.getClass().getSimpleName() + " does not have @Tag annotation");
+ }
+ }
+
+ public Tag process() {
+ final com.singingbush.sdl.annotations.Tag tagAnn = obj.getClass().getAnnotation(com.singingbush.sdl.annotations.Tag.class);
+
+ final TagBuilder tagBuilder = SDL.tag(tagAnn.value()).withNamespace(tagAnn.namespace());
+
+ Arrays.stream(obj.getClass().getDeclaredFields())
+ .forEach(field -> {
+
+ if(field.isAnnotationPresent(Value.class)) {
+ processValueField(tagBuilder, field);
+ }
+
+ if(field.isAnnotationPresent(Attribute.class)) {
+ processAttributeField(tagBuilder, field);
+ }
+ });
+
+
+ return tagBuilder.build();
+ }
+
+ private void processValueField(final TagBuilder tagBuilder, final Field field) {
+ field.setAccessible(true);
+ //final Value valAnn = field.getAnnotation(Value.class);
+
+ try {
+ // SDL only supports a limited amount of literal types:
+ // STRING, STRING_MULTILINE, CHARACTER, BOOLEAN, NUMBER, DATE, DATETIME, DURATION, BINARY, NULL
+ // which are supported via the following Java types:
+ // String, Character, Boolean, Long, Float, Double, Integer, LocalDate, LocalDateTime, ZonedDateTime, Duration
+ // todo: handle objects that are also pojos annotated with @Tag
+ switch (field.getType().getSimpleName()) {
+ case "String":
+ tagBuilder.withValue(SDL.value(String.valueOf(field.get(obj)), false));
+ break;
+ case "Character":
+ tagBuilder.withValue(SDL.value(Character.class.cast(field.get(obj))));
+ break;
+ case "Boolean":
+ tagBuilder.withValue(SDL.value(Boolean.class.cast(field.get(obj))));
+ break;
+ case "Long":
+ tagBuilder.withValue(SDL.value(Long.class.cast(field.get(obj))));
+ break;
+ case "Float":
+ tagBuilder.withValue(SDL.value(Float.class.cast(field.get(obj))));
+ break;
+ case "Double":
+ tagBuilder.withValue(SDL.value(Double.class.cast(field.get(obj))));
+ break;
+ case "Integer":
+ tagBuilder.withValue(SDL.value(Integer.class.cast(field.get(obj))));
+ break;
+ case "LocalDate":
+ tagBuilder.withValue(SDL.value(LocalDate.class.cast(field.get(obj))));
+ break;
+ case "LocalDateTime":
+ tagBuilder.withValue(SDL.value(LocalDateTime.class.cast(field.get(obj))));
+ break;
+ case "ZonedDateTime":
+ tagBuilder.withValue(SDL.value(ZonedDateTime.class.cast(field.get(obj))));
+ break;
+ case "Duration":
+ tagBuilder.withValue(SDL.value(Duration.class.cast(field.get(obj))));
+ break;
+ }
+ } catch (IllegalAccessException e) {
+ e.printStackTrace(); // todo: use slf4j-api to log error, also perhaps throw an Exception??
+ }
+ }
+
+ private void processAttributeField(final TagBuilder tagBuilder, final Field field) {
+ field.setAccessible(true);
+ final Attribute attrAnn = field.getAnnotation(Attribute.class);
+ final String name = !attrAnn.value().isEmpty() ? attrAnn.value() : field.getName();
+
+ try {
+ switch (field.getType().getSimpleName()) {
+ case "String":
+ tagBuilder.withAttribute(name, SDL.value(String.valueOf(field.get(obj)), false));
+ break;
+ case "Character":
+ tagBuilder.withAttribute(name, SDL.value(Character.class.cast(field.get(obj))));
+ break;
+ case "Boolean":
+ tagBuilder.withAttribute(name, SDL.value(Boolean.class.cast(field.get(obj))));
+ break;
+ case "Long":
+ tagBuilder.withAttribute(name, SDL.value(Long.class.cast(field.get(obj))));
+ break;
+ case "Float":
+ tagBuilder.withAttribute(name, SDL.value(Float.class.cast(field.get(obj))));
+ break;
+ case "Double":
+ tagBuilder.withAttribute(name, SDL.value(Double.class.cast(field.get(obj))));
+ break;
+ case "Integer":
+ tagBuilder.withAttribute(name, SDL.value(Integer.class.cast(field.get(obj))));
+ break;
+ case "LocalDate":
+ tagBuilder.withAttribute(name, SDL.value(LocalDate.class.cast(field.get(obj))));
+ break;
+ case "LocalDateTime":
+ tagBuilder.withAttribute(name, SDL.value(LocalDateTime.class.cast(field.get(obj))));
+ break;
+ case "ZonedDateTime":
+ tagBuilder.withAttribute(name, SDL.value(ZonedDateTime.class.cast(field.get(obj))));
+ break;
+ case "Duration":
+ tagBuilder.withAttribute(name, SDL.value(Duration.class.cast(field.get(obj))));
+ break;
+ }
+ } catch (IllegalAccessException e) {
+ e.printStackTrace(); // todo: use slf4j-api to log error, also perhaps throw an Exception??
+ }
+ }
+}
diff --git a/sdlang/src/main/java/com/singingbush/sdl/Tag.java b/sdlang/src/main/java/com/singingbush/sdl/Tag.java
index 8050d82..aa334fb 100644
--- a/sdlang/src/main/java/com/singingbush/sdl/Tag.java
+++ b/sdlang/src/main/java/com/singingbush/sdl/Tag.java
@@ -20,8 +20,6 @@
import org.jetbrains.annotations.Nullable;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
@@ -30,6 +28,8 @@
import java.io.StringReader;
import java.io.Writer;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
@@ -342,14 +342,11 @@ public class Tag implements Serializable {
private final String name;
private String comment;
- private List values = new ArrayList();
- private List valuesView = Collections.unmodifiableList(values);
+ private LineCommentStyle commentStyle;
+ private List> values = new ArrayList<>();
private Map attributeToNamespace = new HashMap<>();
- //private Map attributeToNamespaceView = Collections.unmodifiableMap(attributeToNamespace);
- private SortedMap attributes = new TreeMap<>();
- //private SortedMap attributesView = Collections.unmodifiableSortedMap(attributes);
+ private SortedMap> attributes = new TreeMap<>();
private List children = new ArrayList<>();
- private List childrenView = Collections.unmodifiableList(children);
/**
* Creates an empty tag.
@@ -398,7 +395,7 @@ public Tag(@NotNull final String namespace, @NotNull final String name) {
*
* @param child The child to add
*/
- public void addChild(Tag child) {
+ public void addChild(final Tag child) {
children.add(child);
}
@@ -408,7 +405,7 @@ public void addChild(Tag child) {
* @param child The child to remove
* @return true if the child exists and is removed
*/
- public boolean removeChild(Tag child) {
+ public boolean removeChild(final Tag child) {
return children.remove(child);
}
@@ -419,7 +416,7 @@ public boolean removeChild(Tag child) {
* @param value The value to be set.
* @throws IllegalArgumentException if the value is not a legal SDL type
*/
- public void setValue(SdlValue value) {
+ public void setValue(SdlValue> value) {
if(values.isEmpty()) {
addValue(value);
} else {
@@ -434,7 +431,7 @@ public void setValue(SdlValue value) {
* @since 2.0.0
*/
@Nullable
- public SdlValue getSdlValue() {
+ public SdlValue> getSdlValue() {
return values.isEmpty() ? null : values.get(0);
}
@@ -612,13 +609,13 @@ public List getChildrenForNamespace(String namespace, boolean recursive) {
* @param name The name of the children from which values are retrieved
* @return A list of values (or lists of values)
*/
- public List getChildrenValues(final String name) {
- final ArrayList results = new ArrayList();
+ public List