diff --git a/src/main/java/net/elytrium/serializer/language/reader/AbstractReader.java b/src/main/java/net/elytrium/serializer/language/reader/AbstractReader.java index b7eed74..78cd4f9 100644 --- a/src/main/java/net/elytrium/serializer/language/reader/AbstractReader.java +++ b/src/main/java/net/elytrium/serializer/language/reader/AbstractReader.java @@ -254,23 +254,24 @@ public Object readByType(@Nullable Field owner, @Nullable Object holder, Type ty @SuppressWarnings("unchecked") @SuppressFBWarnings("NP_LOAD_OF_KNOWN_NULL_VALUE") - private Collection readCollectionByType(Field owner, Type type, Class clazz) { - Type collectionEntryType = GenericUtils.getParameterType(Collection.class, type, 0); + private Map readMapByType(Field owner, Type type) { + Type mapKeyType = GenericUtils.getParameterType(Map.class, type, 0); + Type mapValueType = GenericUtils.getParameterType(Map.class, type, 1); if (owner != null) { - CollectionType collectionType = owner.getAnnotation(CollectionType.class); - if (collectionType != null) { + MapType mapType = owner.getAnnotation(MapType.class); + if (mapType != null) { try { - var constructor = collectionType.value().getDeclaredConstructor(); + var constructor = mapType.value().getDeclaredConstructor(); constructor.setAccessible(true); - return this.readCollection(owner, (Collection) constructor.newInstance(), collectionEntryType); + return this.readMap(owner, (Map) constructor.newInstance(), mapKeyType, mapValueType); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new SerializableReadException(e); } - } else if (Collection.class.isAssignableFrom(owner.getType())) { + } else if (Map.class.isAssignableFrom(owner.getType())) { try { Constructor constructor = owner.getType().getDeclaredConstructor(); constructor.setAccessible(true); - return this.readCollection(owner, (Collection) constructor.newInstance(), collectionEntryType); + return this.readMap(owner, (Map) constructor.newInstance(), mapKeyType, mapValueType); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new SerializableReadException(e); } catch (NoSuchMethodException e) { @@ -279,31 +280,28 @@ private Collection readCollectionByType(Field owner, Type type, Class } } - return Set.class.isAssignableFrom(clazz) ? this.readSet(owner, collectionEntryType) - : Queue.class.isAssignableFrom(clazz) ? this.readDeque(owner, collectionEntryType) - : this.readList(owner, collectionEntryType); + return this.readMap(owner, mapKeyType, mapValueType); } @SuppressWarnings("unchecked") @SuppressFBWarnings("NP_LOAD_OF_KNOWN_NULL_VALUE") - private Map readMapByType(Field owner, Type type) { - Type mapKeyType = GenericUtils.getParameterType(Map.class, type, 0); - Type mapValueType = GenericUtils.getParameterType(Map.class, type, 1); + private Collection readCollectionByType(Field owner, Type type, Class clazz) { + Type collectionEntryType = GenericUtils.getParameterType(Collection.class, type, 0); if (owner != null) { - MapType mapType = owner.getAnnotation(MapType.class); - if (mapType != null) { + CollectionType collectionType = owner.getAnnotation(CollectionType.class); + if (collectionType != null) { try { - var constructor = mapType.value().getDeclaredConstructor(); + var constructor = collectionType.value().getDeclaredConstructor(); constructor.setAccessible(true); - return this.readMap(owner, (Map) constructor.newInstance(), mapKeyType, mapValueType); + return this.readCollection(owner, (Collection) constructor.newInstance(), collectionEntryType); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new SerializableReadException(e); } - } else if (Map.class.isAssignableFrom(owner.getType())) { + } else if (Collection.class.isAssignableFrom(owner.getType())) { try { Constructor constructor = owner.getType().getDeclaredConstructor(); constructor.setAccessible(true); - return this.readMap(owner, (Map) constructor.newInstance(), mapKeyType, mapValueType); + return this.readCollection(owner, (Collection) constructor.newInstance(), collectionEntryType); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new SerializableReadException(e); } catch (NoSuchMethodException e) { @@ -312,7 +310,9 @@ private Map readMapByType(Field owner, Type type) { } } - return this.readMap(owner, mapKeyType, mapValueType); + return Set.class.isAssignableFrom(clazz) ? this.readSet(owner, collectionEntryType) + : Queue.class.isAssignableFrom(clazz) ? this.readDeque(owner, collectionEntryType) + : this.readList(owner, collectionEntryType); } public Object readGuessingType() { @@ -343,28 +343,6 @@ public > C readMap(C result, Type keyType, Type va public abstract > C readMap(@Nullable Field owner, C result, Type keyType, Type valueType); - public > C readCollection(@Nullable Field owner, C result) { - return this.readCollection(owner, result, Object.class); - } - - public > C readCollection(C result, Type type) { - return this.readCollection(null, result, type); - } - - public abstract > C readCollection(@Nullable Field owner, C result, Type type); - - public List readList(@Nullable Field owner) { - return this.readList(owner, Object.class); - } - - public List readList(Type type) { - return this.readList(null, type); - } - - public List readList(@Nullable Field owner, Type type) { - return this.readCollection(owner, new ArrayList<>(), type); - } - public Set readSet(@Nullable Field owner) { return this.readSet(owner, Object.class); } @@ -389,6 +367,28 @@ public Deque readDeque(@Nullable Field owner, Type type) { return this.readCollection(owner, new ArrayDeque<>(), type); } + public List readList(@Nullable Field owner) { + return this.readList(owner, Object.class); + } + + public List readList(Type type) { + return this.readList(null, type); + } + + public List readList(@Nullable Field owner, Type type) { + return this.readCollection(owner, new ArrayList<>(), type); + } + + public > C readCollection(@Nullable Field owner, C result) { + return this.readCollection(owner, result, Object.class); + } + + public > C readCollection(C result, Type type) { + return this.readCollection(null, result, type); + } + + public abstract > C readCollection(@Nullable Field owner, C result, Type type); + public String readString() { return this.readString(null); } @@ -458,8 +458,8 @@ public void skipNode(Class clazz) { public void skipNode(@Nullable Field owner, Class clazz) { if (Map.class.isAssignableFrom(clazz)) { this.skipMap(owner); - } else if (List.class.isAssignableFrom(clazz)) { - this.skipList(owner); + } else if (Collection.class.isAssignableFrom(clazz)) { + this.skipCollection(owner); } else if (clazz.isEnum() || String.class.isAssignableFrom(clazz) || Boolean.class.isAssignableFrom(clazz) || boolean.class.isAssignableFrom(clazz) @@ -477,11 +477,11 @@ public void skipMap() { public abstract void skipMap(@Nullable Field owner); - public void skipList() { - this.skipList(null); + public void skipCollection() { + this.skipCollection(null); } - public abstract void skipList(@Nullable Field owner); + public abstract void skipCollection(@Nullable Field owner); public void skipString() { this.skipString(null); diff --git a/src/main/java/net/elytrium/serializer/language/reader/YamlReader.java b/src/main/java/net/elytrium/serializer/language/reader/YamlReader.java index f6a5974..d5d1038 100644 --- a/src/main/java/net/elytrium/serializer/language/reader/YamlReader.java +++ b/src/main/java/net/elytrium/serializer/language/reader/YamlReader.java @@ -210,7 +210,7 @@ private void updatePlaceholders(Field node, Object value) throws ReflectiveOpera public String readNodeName(@Nullable Field owner) { synchronized (this) { char marker = this.readRawIgnoreEmptyAndNewLines(); - return marker == '\0' ? null : this.readNodeNameByMarker(owner, marker); + return marker == '\0' ? null : this.readNodeNameFromMarker(owner, marker); } } @@ -238,7 +238,7 @@ public boolean readEndSerializableObject(@Nullable Field owner) { @Override public Object readGuessingType(@Nullable Field owner) { synchronized (this) { - return this.readGuessingTypeByMarker(owner, this.readRawIgnoreEmpty()); + return this.readGuessingTypeFromMarker(owner, this.readRawIgnoreEmpty()); } } @@ -251,14 +251,14 @@ public > C readMap(@Nullable Field owner, C result this.setTempRestoreNewLine(); } - return this.readMapByMarker(owner, result, keyType, valueType, marker); + return this.readMapFromMarker(owner, result, keyType, valueType, marker); } } @Override public > C readCollection(@Nullable Field owner, C result, Type type) { synchronized (this) { - return this.readCollectionByMarker(owner, result, type, this.readRawIgnoreEmpty()); + return this.readCollectionFromMarker(owner, result, type, this.readRawIgnoreEmpty()); } } @@ -269,104 +269,6 @@ public Character readCharacter(@Nullable Field owner) { } } - private Character readCharacterFromMarker(@Nullable Field owner, char marker) { - Character result = null; - switch (marker) { - case '"' -> { - while ((marker = this.readRaw()) != '"') { - if (result == null) { - if (marker == '\\') { - char[] characters = Character.toChars(this.readEscapedCharacter()); - if (characters.length != 1) { - throw new IllegalStateException("Supplementary char cannot be stored in Character"); - } - - result = characters[0]; - } else { - result = marker; - } - } else if (marker == '\\') { - this.readRaw(); // To ensure the reading doesn't stop at \" - } - } - } - case '\'' -> { - while (true) { - marker = this.readRaw(); - if (marker == '\'') { - if (this.readRaw() == '\'') { // 'text1 ''text2'' text3' reads as "text 'text2' text3". - if (result == null) { - result = '\''; - } - } else { - this.setReuseBuffer(); - break; - } - } else if (result == null) { - result = marker; - } - } - } - default -> { - if (this.isNullSkippedByMarker(marker)) { - return null; - } - - while (!this.isEndMarker(marker) && (marker != ',' || this.bracketOpened) && !this.skipComments(owner, marker, false)) { - if (result == null) { // Set the result only after we ensure we not at end of line/field. - result = marker; - } - - marker = this.readRaw(); - } - } - } - - return result; - } - - private int readEscapedCharacter() { - synchronized (this) { - char marker; - return switch (marker = this.readRaw()) { - case '0' -> '\0'; - case 'a' -> '\u0007'; - case 'b' -> '\b'; - case 't' -> '\t'; - case 'n' -> '\n'; - case 'v' -> '\u000B'; - case 'f' -> '\f'; - case 'r' -> '\r'; - case 'e' -> '\u001B'; - case ' ' -> ' '; - case '"' -> '\"'; - case '\\' -> '\\'; - case 'N' -> '\u0085'; - case '_' -> '\u00A0'; - case 'L' -> '\u2028'; - case 'P' -> '\u2029'; - case 'x' -> this.readHexChar(2); - case 'u' -> this.readHexChar(4); - case 'U' -> this.readHexChar(8); - default -> throw new IllegalStateException("Invalid escape character: \\" + marker); - }; - } - } - - private int readHexChar(int size) { - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < size; ++i) { - char character = this.readRaw(); - if (this.isEndMarker(character)) { - throw new IllegalStateException("Got new line while reading hex char"); - } - - hex.append(character); - } - - return Integer.valueOf(hex.toString(), 16); - } - @Override public Boolean readBoolean(@Nullable Field owner) { synchronized (this) { @@ -390,69 +292,124 @@ public Long readLong(@Nullable Field owner) throws NumberFormatException { } } - private List readListByMarker(@Nullable Field owner, char marker) { - return this.readCollectionByMarker(owner, new ArrayList<>(), Object.class, marker); + @Override + public String readString(@Nullable Field owner) { + synchronized (this) { + return this.readStringFromMarker(owner, this.readRawIgnoreEmpty(), false); + } } - @SuppressFBWarnings("SA_FIELD_SELF_COMPARISON") - private > C readCollectionByMarker(@Nullable Field owner, C result, Type type, char marker) { - if (this.skipComments(owner, marker, false)) { - marker = AbstractReader.NEW_LINE; + private String readNodeNameFromMarker(@Nullable Field owner, char marker) { + synchronized (this) { + this.nodeIndent = this.currentIndent; } - switch (marker) { - case '[': { - char nextMarker = this.readRawIgnoreEmptyAndNewLines(); - while (nextMarker != ']') { - result.add(this.readByType0(owner, type)); - nextMarker = this.readRawIgnoreEmptyAndNewLines(); + while (true) { + if (this.skipComments(owner, marker, false) || this.skipComments(owner, this.readRawIgnoreEmpty(), true)) { + marker = this.readRaw(); + } else { + break; + } + } + + String nodeName; + if (marker == '"' || marker == '\'') { + nodeName = this.readStringFromMarker(owner, marker, true); + } else { + StringBuilder result = new StringBuilder(12); + while (true) { + while (marker != ':') { + if (this.isEndMarker(marker)) { + throw new IllegalStateException("Got a new line in node name: " + result); + } + + result.append(marker); + marker = this.readRaw(); } - break; + marker = this.readRaw(); + if (Character.isWhitespace(marker)) { + this.setReuseBuffer(); + break; + } + + result.append(':'); } - case AbstractReader.NEW_LINE: { - this.skipComments(owner, this.readRawIgnoreEmpty(), true); + + nodeName = result.toString(); + } + + return nodeName; + } + + private Object readGuessingTypeFromMarker(@Nullable Field owner, char marker) { + return switch (marker) { + case AbstractReader.NEW_LINE -> { char nextMarker = this.readRawIgnoreEmpty(); - if (nextMarker != '-') { - throw new IllegalStateException("Got unknown marker when reading list: " + nextMarker); - } - } // Got '-' after newline, fall through here. - case '-': { - this.nodeIndent = this.currentIndent; - char nextMarker = '-'; - int correctIndent = this.currentIndent; - while (nextMarker == '-' && correctIndent == this.currentIndent) { - this.setTempRestoreNewLine(); - result.add(this.readByType0(null, type)); - this.unsetTempRestoreNewLine(); - nextMarker = this.readRawIgnoreEmptyAndNewLines(); - if (this.skipComments(owner, nextMarker, false)) { - nextMarker = this.readRawIgnoreEmptyAndNewLines(); + this.setReuseBuffer(); + yield nextMarker == '-' ? this.readListFromMarker(owner, marker) : this.readMapFromMarker(owner, marker); + } + case '-' -> { + this.setReuseBuffer(); + this.setSeek(); + String string = this.readString(owner); + if (string != null) { + try { + try { + yield Long.parseLong(string); + } catch (NumberFormatException e) { + yield Double.parseDouble(string); + } + } catch (NumberFormatException ignored) { + // Exception ignored as the string doesn't contain negative number here, but contains a list. } } - this.setReuseBuffer(); - break; + this.unsetSeek(); + yield this.readListFromMarker(owner, AbstractReader.NEW_LINE); } - default: { - if (this.readGuessingTypeByMarker(null, marker) == null) { - return null; + case '[' -> this.readListFromMarker(owner, marker); + case '{' -> this.readMapFromMarker(owner, marker); + case '"', '\'', '>', '|' -> this.readStringFromMarker(owner, marker, false); + default -> { + if (this.isNullSkippedFromMarker(marker)) { + yield null; + } + + this.setSeekFromMarker(marker); + String string = this.readStringFromMarker(owner, marker, false); + if (string == null) { + yield null; + } + + if (string.endsWith(":") || string.endsWith(": ") || string.contains(": ")) { + this.unsetSeek(); + this.unsetTempRestoreNewLine(); + yield this.readMapFromMarker(owner, AbstractReader.NEW_LINE); } else { - throw new IllegalStateException("Got unknown marker when reading list: " + marker); + this.clearSeek(); + try { + try { + yield Long.parseLong(string); + } catch (NumberFormatException e) { + yield Double.parseDouble(string); + } + } catch (NumberFormatException e) { + yield string; + } } } - } - - return result; + }; } - private Map readMapByMarker(@Nullable Field owner, char marker) { - return this.readMapByMarker(owner, new LinkedHashMap<>(), Object.class, Object.class, marker); + private Map readMapFromMarker(@Nullable Field owner, char marker) { + return this.readMapFromMarker(owner, new LinkedHashMap<>(), Object.class, Object.class, marker); } + @SuppressWarnings("DuplicatedCode") @SuppressFBWarnings("SA_FIELD_SELF_COMPARISON") - private > C readMapByMarker(@Nullable Field owner, C result, Type keyType, Type valueType, char marker) { - if (this.skipComments(owner, marker, false)) { + private > C readMapFromMarker(@Nullable Field owner, C result, Type keyType, Type valueType, char marker) { + if (this.skipComments(owner, marker, false)) { // TODO сделать чтобы map:#comm не читалось marker = AbstractReader.NEW_LINE; } @@ -470,7 +427,7 @@ private > C readMapByMarker(@Nullable Field owner, case '{' -> { this.bracketOpened = true; while (nextMarker != '}') { - this.readMapEntry(owner, keyType, valueType, this.readNodeNameByMarker(null, nextMarker), result); + this.readMapEntry(owner, keyType, valueType, this.readNodeNameFromMarker(null, nextMarker), result); nextMarker = this.readRawIgnoreEmptyAndNewLines(); } @@ -480,7 +437,7 @@ private > C readMapByMarker(@Nullable Field owner, this.bracketOpened = false; int correctIndent = this.currentIndent; while (nextMarker != '\0' && correctIndent == this.currentIndent) { - this.readMapEntry(owner, keyType, valueType, this.readNodeNameByMarker(null, nextMarker), result); + this.readMapEntry(owner, keyType, valueType, this.readNodeNameFromMarker(null, nextMarker), result); nextMarker = this.readRawIgnoreEmptyAndNewLines(); if (this.skipComments(owner, nextMarker, false)) { nextMarker = this.readRawIgnoreEmptyAndNewLines(); @@ -491,7 +448,7 @@ private > C readMapByMarker(@Nullable Field owner, this.bracketOpened = previousBracketOpened; } default -> { - if (this.readGuessingTypeByMarker(null, marker) == null) { + if (this.isNullSkippedFromMarker(marker)) { return null; } else { throw new IllegalStateException("Got unknown marker when reading map: " + marker); @@ -551,7 +508,7 @@ private void readMapEntry(@Nullable Field owner, Type keyType, Type valueType, S throw new IllegalStateException("Class " + keyClazz + " for map key are not supported yet!"); } - if (Map.class.isAssignableFrom(clazz) || List.class.isAssignableFrom(clazz)) { + if (Map.class.isAssignableFrom(clazz) || Collection.class.isAssignableFrom(clazz)) { throw new IllegalStateException("Class " + clazz + " for map key is not supported!"); } @@ -564,134 +521,13 @@ private void readMapEntry(@Nullable Field owner, Type keyType, Type valueType, S result.put(key, this.readByType0(owner, valueType)); } - private Object readByType0(@Nullable Field owner, Type type) { - Object result = this.readByType(owner, type); - if (type == Integer.class || type == int.class) { - return ((Long) result).intValue(); - } else if (type == Short.class || type == short.class) { - return ((Long) result).shortValue(); - } else if (type == Byte.class || type == byte.class) { - return ((Long) result).byteValue(); - } else if (type == Float.class || type == float.class) { - return ((Double) result).floatValue(); - } else { - return result; - } - } - - private Object readGuessingTypeByMarker(@Nullable Field owner, char marker) { - return switch (marker) { - case AbstractReader.NEW_LINE -> { - char nextMarker = this.readRawIgnoreEmpty(); - this.setReuseBuffer(); - yield nextMarker == '-' ? this.readListByMarker(owner, marker) : this.readMapByMarker(owner, marker); - } - case '-' -> { - this.setReuseBuffer(); - this.setSeek(); - String string = this.readString(owner); - if (string != null) { - try { - try { - yield Long.parseLong(string); - } catch (NumberFormatException e) { - yield Double.parseDouble(string); - } - } catch (NumberFormatException ignored) { - // Exception ignored as the string doesn't contain negative number here, but contains a list. - } - } - - this.unsetSeek(); - yield this.readListByMarker(owner, AbstractReader.NEW_LINE); - } - case '[' -> this.readListByMarker(owner, marker); - case '{' -> this.readMapByMarker(owner, marker); - case '"', '\'', '>', '|' -> this.readStringFromMarker(owner, marker, false); - default -> { - if (this.isNullSkippedByMarker(marker)) { - yield null; - } - - this.setSeekFromMarker(marker); - String string = this.readStringFromMarker(owner, marker, false); - if (string == null) { - yield null; - } - - if (string.endsWith(":") || string.endsWith(": ") || string.contains(": ")) { - this.unsetSeek(); - this.unsetTempRestoreNewLine(); - yield this.readMapByMarker(owner, AbstractReader.NEW_LINE); - } else { - this.clearSeek(); - try { - try { - yield Long.parseLong(string); - } catch (NumberFormatException e) { - yield Double.parseDouble(string); - } - } catch (NumberFormatException e) { - yield string; - } - } - } - }; - } - - @Override - public void skipGuessingType(@Nullable Field owner) { - synchronized (this) { - this.skipGuessingTypeByMarker(owner, this.readRawIgnoreEmpty()); - } - } - - private void skipGuessingTypeByMarker(@Nullable Field owner, char marker) { - switch (marker) { - case AbstractReader.NEW_LINE -> { - char nextMarker = this.readRawIgnoreEmpty(); - this.setReuseBuffer(); - if (nextMarker == '-') { - this.skipListByMarker(owner, marker); - } else { - this.skipMapByMarker(owner, marker); - } - } - case '-' -> { - this.setReuseBuffer(); - this.skipListByMarker(owner, NEW_LINE); - } - case '[' -> this.skipListByMarker(owner, marker); - case '{' -> this.skipMapByMarker(owner, marker); - case '"', '\'', '>', '|' -> this.skipStringFromMarker(owner, marker, false); - default -> { - if (this.isNullSkippedByMarker(marker)) { - return; - } - - this.setSeekFromMarker(marker); - String string = this.readStringFromMarker(owner, marker, false); - if (string != null && (string.endsWith(":") || string.endsWith(": ") || string.contains(": "))) { - this.unsetSeek(); - this.unsetTempRestoreNewLine(); - this.skipMapByMarker(owner, NEW_LINE); - } else { - this.clearSeek(); - } - } - } - } - - @Override - public void skipList(@Nullable Field owner) { - synchronized (this) { - this.skipListByMarker(owner, this.readRawIgnoreEmpty()); - } + private List readListFromMarker(@Nullable Field owner, char marker) { + return this.readCollectionFromMarker(owner, new ArrayList<>(), Object.class, marker); } @SuppressFBWarnings("SA_FIELD_SELF_COMPARISON") - private void skipListByMarker(@Nullable Field owner, char marker) { - if (this.skipComments(owner, marker, false)) { + private > C readCollectionFromMarker(@Nullable Field owner, C result, Type type, char marker) { + if (this.skipComments(owner, marker, false)) { // TODO сделать чтобы collection:#comm не читалось marker = AbstractReader.NEW_LINE; } @@ -699,7 +535,7 @@ private void skipListByMarker(@Nullable Field owner, char marker) { case '[': { char nextMarker = this.readRawIgnoreEmptyAndNewLines(); while (nextMarker != ']') { - this.skipGuessingType(owner); + result.add(this.readByType0(owner, type)); nextMarker = this.readRawIgnoreEmptyAndNewLines(); } @@ -718,7 +554,7 @@ private void skipListByMarker(@Nullable Field owner, char marker) { int correctIndent = this.currentIndent; while (nextMarker == '-' && correctIndent == this.currentIndent) { this.setTempRestoreNewLine(); - this.skipGuessingType(owner); + result.add(this.readByType0(null, type)); this.unsetTempRestoreNewLine(); nextMarker = this.readRawIgnoreEmptyAndNewLines(); if (this.skipComments(owner, nextMarker, false)) { @@ -730,88 +566,129 @@ private void skipListByMarker(@Nullable Field owner, char marker) { break; } default: { - if (this.isNullSkippedByMarker(marker)) { + if (this.isNullSkippedFromMarker(marker)) { + return null; + } else { throw new IllegalStateException("Got unknown marker when reading list: " + marker); } } } - } - - @Override - public void skipMap(@Nullable Field owner) { - boolean startOfFile = this.startOfFile; - synchronized (this) { - char marker = this.readRawIgnoreEmpty(); - if (startOfFile) { - this.setTempRestoreNewLine(); - } - this.skipMapByMarker(owner, marker); - } + return result; } - @SuppressFBWarnings("SA_FIELD_SELF_COMPARISON") - private void skipMapByMarker(@Nullable Field owner, char marker) { - if (this.skipComments(owner, marker, false)) { - marker = AbstractReader.NEW_LINE; - } + private Object readByType0(@Nullable Field owner, Type type) { + Object result = this.readByType(owner, type); + return type == Integer.class || type == int.class ? Integer.valueOf(((Long) result).intValue()) + : type == Short.class || type == short.class ? Short.valueOf(((Long) result).shortValue()) + : type == Byte.class || type == byte.class ? Byte.valueOf(((Long) result).byteValue()) + : type == Float.class || type == float.class ? Float.valueOf(((Double) result).floatValue()) + : result; // Long || Double + } - char nextMarker = this.readRawIgnoreEmptyAndNewLines(); + private Character readCharacterFromMarker(@Nullable Field owner, char marker) { + Character result = null; switch (marker) { - case '{' -> { - while (nextMarker != '}') { - this.skipStringFromMarker(owner, nextMarker, true); - this.skipGuessingType(owner); - nextMarker = this.readRawIgnoreEmptyAndNewLines(); + case '"' -> { + while ((marker = this.readRaw()) != '"') { + if (result == null) { + if (marker == '\\') { + int character = this.readEscapedCharacter(); + if (Character.isBmpCodePoint(character)) { + result = (char) character; + } else { + throw new IllegalStateException("Supplementary char cannot be stored in Character"); + } + } else { + result = marker; + } + } else if (marker == '\\') { + this.readRaw(); // To ensure the reading doesn't stop at \" + } } } - case AbstractReader.NEW_LINE -> { - int correctIndent = this.currentIndent; - while (nextMarker != '\0' && correctIndent == this.currentIndent) { - this.skipStringFromMarker(owner, nextMarker, true); - this.skipGuessingType(owner); - nextMarker = this.readRawIgnoreEmptyAndNewLines(); - if (this.skipComments(owner, nextMarker, false)) { - nextMarker = this.readRawIgnoreEmptyAndNewLines(); + case '\'' -> { + while (true) { + marker = this.readRaw(); + if (marker == '\'') { + if (this.readRaw() == '\'') { // 'text1 ''text2'' text3' reads as "text 'text2' text3". + if (result == null) { + result = '\''; + } + } else { + this.setReuseBuffer(); + break; + } + } else if (result == null) { + result = marker; + } + } + } + default -> { + if (this.isNullSkippedFromMarker(marker)) { + return null; + } + + while (!this.isEndMarker(marker) && (marker != ',' || this.bracketOpened) && !this.skipComments(owner, marker, false)) { + if (result == null) { // Set the result only after we ensure we not at end of line/field. + result = marker; } - } - this.setReuseBuffer(); - } - default -> { - if (this.isNullSkippedByMarker(marker)) { - throw new IllegalStateException("Got unknown marker when reading map: " + marker); + marker = this.readRaw(); } } } + + return result; } - private boolean isNullSkippedByMarker(char marker) { - this.setSeek(); - if (marker == 'n' && this.readRaw() == 'u' && this.readRaw() == 'l' && this.readRaw() == 'l') { - char endMarker = this.readRawIgnoreEmpty(); - if (this.isEndMarker(endMarker) || (endMarker == ',' && this.bracketOpened)) { - this.clearSeek(); - return true; - } + private int readEscapedCharacter() { + synchronized (this) { + char marker; + return switch (marker = this.readRaw()) { + case '0' -> '\0'; + case 'a' -> '\u0007'; + case 'b' -> '\b'; + case 't' -> '\t'; + case 'n' -> '\n'; + case 'v' -> '\u000B'; + case 'f' -> '\f'; + case 'r' -> '\r'; + case 'e' -> '\u001B'; + case ' ' -> ' '; + case '"' -> '\"'; + case '\\' -> '\\'; + case 'N' -> '\u0085'; + case '_' -> '\u00A0'; + case 'L' -> '\u2028'; + case 'P' -> '\u2029'; + case 'x' -> this.readHexChar(2); + case 'u' -> this.readHexChar(4); + case 'U' -> this.readHexChar(8); + default -> throw new IllegalStateException("Invalid escape character: \\" + marker); + }; } - - this.unsetSeek(); - return false; } - @Override - public String readString(@Nullable Field owner) { - synchronized (this) { - return this.readStringFromMarker(owner, this.readRawIgnoreEmpty(), false); + private int readHexChar(int size) { + StringBuilder hex = new StringBuilder(size); + for (int i = 0; i < size; ++i) { + char character = this.readRaw(); + if (this.isEndMarker(character)) { + throw new IllegalStateException("Got new line while reading hex char"); + } + + hex.append(character); } + + return Integer.parseInt(hex.toString(), 16); } private String readStringFromMarker(@Nullable Field owner, char marker, boolean nodeName) { StringBuilder result = new StringBuilder(); switch (marker) { case '"' -> { - if (!nodeName && this.yamlSerializable != null && owner != null) { + if (owner != null && !nodeName && this.yamlSerializable != null) { this.yamlSerializable.saveStringStyle(owner, YamlWriter.StringStyle.DOUBLE_QUOTED); } @@ -837,8 +714,15 @@ private String readStringFromMarker(@Nullable Field owner, char marker, boolean newLineCount = 0; if (marker == '\\') { - for (char character : Character.toChars(this.readEscapedCharacter())) { - result.append(character); + // Inlined Character.toChars() + int character = this.readEscapedCharacter(); + if (Character.isBmpCodePoint(character)) { + result.append((char) character); + } else if (Character.isValidCodePoint(character)) { + result.append(Character.highSurrogate(character)); + result.append(Character.lowSurrogate(character)); + } else { + throw new IllegalArgumentException(String.format("Not a valid Unicode code point: 0x%X", character)); } } else { result.append(marker); @@ -854,7 +738,7 @@ private String readStringFromMarker(@Nullable Field owner, char marker, boolean } } case '\'' -> { - if (!nodeName && this.yamlSerializable != null && owner != null) { + if (owner != null && !nodeName && this.yamlSerializable != null) { this.yamlSerializable.saveStringStyle(owner, YamlWriter.StringStyle.SINGLE_QUOTED); } @@ -901,13 +785,13 @@ private String readStringFromMarker(@Nullable Field owner, char marker, boolean } } default -> { - if (!nodeName && (marker == '|' || marker == '>')) { - this.readMultilineStringFromMarker(owner, marker, result); + if (marker == '#') { + this.skipComments(owner, marker, false); break; } - if (marker == '#') { - this.skipComments(owner, marker, false); + if (!nodeName && (marker == '|' || marker == '>')) { + this.readMultilineStringFromMarker(owner, marker, result); break; } @@ -1057,6 +941,168 @@ private void readMultilineStringFromMarker(@Nullable Field owner, char marker, S this.setReuseBuffer(); } + @Override + public void skipGuessingType(@Nullable Field owner) { + synchronized (this) { + this.skipGuessingTypeFromMarker(owner, this.readRawIgnoreEmpty()); + } + } + + private void skipGuessingTypeFromMarker(@Nullable Field owner, char marker) { + switch (marker) { + case AbstractReader.NEW_LINE -> { + char nextMarker = this.readRawIgnoreEmpty(); + this.setReuseBuffer(); + if (nextMarker == '-') { + this.skipCollectionFromMarker(owner, marker); + } else { + this.skipMapFromMarker(owner, marker); + } + } + case '-' -> { + this.setReuseBuffer(); + this.skipCollectionFromMarker(owner, NEW_LINE); + } + case '[' -> this.skipCollectionFromMarker(owner, marker); + case '{' -> this.skipMapFromMarker(owner, marker); + case '"', '\'', '>', '|' -> this.skipStringFromMarker(owner, marker, false); + default -> { + if (this.isNullSkippedFromMarker(marker)) { + return; + } + + this.setSeekFromMarker(marker); + String string = this.readStringFromMarker(owner, marker, false); + if (string != null && (string.endsWith(":") || string.endsWith(": ") || string.contains(": "))) { + this.unsetSeek(); + this.unsetTempRestoreNewLine(); + this.skipMapFromMarker(owner, NEW_LINE); + } else { + this.clearSeek(); + } + } + } + } + + @Override + public void skipCollection(@Nullable Field owner) { + synchronized (this) { + this.skipCollectionFromMarker(owner, this.readRawIgnoreEmpty()); + } + } + + @SuppressFBWarnings("SA_FIELD_SELF_COMPARISON") + private void skipCollectionFromMarker(@Nullable Field owner, char marker) { + if (this.skipComments(owner, marker, false)) { + marker = AbstractReader.NEW_LINE; + } + + switch (marker) { + case '[': { + char nextMarker = this.readRawIgnoreEmptyAndNewLines(); + while (nextMarker != ']') { + this.skipGuessingType(owner); + nextMarker = this.readRawIgnoreEmptyAndNewLines(); + } + + break; + } + case AbstractReader.NEW_LINE: { + this.skipComments(owner, this.readRawIgnoreEmpty(), true); + char nextMarker = this.readRawIgnoreEmpty(); + if (nextMarker != '-') { + throw new IllegalStateException("Got unknown marker when reading list: " + nextMarker); + } + } // Got '-' after newline, fall through here. + case '-': { + this.nodeIndent = this.currentIndent; + char nextMarker = '-'; + int correctIndent = this.currentIndent; + while (nextMarker == '-' && correctIndent == this.currentIndent) { + this.setTempRestoreNewLine(); + this.skipGuessingType(owner); + this.unsetTempRestoreNewLine(); + nextMarker = this.readRawIgnoreEmptyAndNewLines(); + if (this.skipComments(owner, nextMarker, false)) { + nextMarker = this.readRawIgnoreEmptyAndNewLines(); + } + } + + this.setReuseBuffer(); + break; + } + default: { + if (!this.isNullSkippedFromMarker(marker)) { + throw new IllegalStateException("Got unknown marker when reading list: " + marker); + } + } + } + } + + @Override + public void skipMap(@Nullable Field owner) { + boolean startOfFile = this.startOfFile; + synchronized (this) { + char marker = this.readRawIgnoreEmpty(); + if (startOfFile) { + this.setTempRestoreNewLine(); + } + + this.skipMapFromMarker(owner, marker); + } + } + + @SuppressWarnings("DuplicatedCode") + @SuppressFBWarnings("SA_FIELD_SELF_COMPARISON") + private void skipMapFromMarker(@Nullable Field owner, char marker) { + if (this.skipComments(owner, marker, false)) { + marker = AbstractReader.NEW_LINE; + } + + char nextMarker; + if (this.tempRestoreNewLine) { + nextMarker = marker; + marker = AbstractReader.NEW_LINE; + this.unsetTempRestoreNewLine(); + } else { + nextMarker = this.readRawIgnoreEmptyAndNewLines(); + } + + boolean previousBracketOpened = this.bracketOpened; + switch (marker) { + case '{' -> { + this.bracketOpened = true; + while (nextMarker != '}') { + this.skipStringFromMarker(owner, nextMarker, true); + this.skipGuessingType(owner); + nextMarker = this.readRawIgnoreEmptyAndNewLines(); + } + + this.bracketOpened = previousBracketOpened; + } + case AbstractReader.NEW_LINE -> { + this.bracketOpened = false; + int correctIndent = this.currentIndent; + while (nextMarker != '\0' && correctIndent == this.currentIndent) { + this.skipStringFromMarker(owner, nextMarker, true); + this.skipGuessingType(owner); + nextMarker = this.readRawIgnoreEmptyAndNewLines(); + if (this.skipComments(owner, nextMarker, false)) { + nextMarker = this.readRawIgnoreEmptyAndNewLines(); + } + } + + this.setReuseBuffer(); + this.bracketOpened = previousBracketOpened; + } + default -> { + if (!this.isNullSkippedFromMarker(marker)) { + throw new IllegalStateException("Got unknown marker when reading map: " + marker); + } + } + } + } + @Override public void skipString(@Nullable Field owner) { synchronized (this) { @@ -1242,47 +1288,18 @@ public char readRaw() { } } - private String readNodeNameByMarker(@Nullable Field owner, char marker) { - synchronized (this) { - this.nodeIndent = this.currentIndent; - } - - while (true) { - if (this.skipComments(owner, marker, false) || this.skipComments(owner, this.readRawIgnoreEmpty(), true)) { - marker = this.readRaw(); - } else { - break; - } - } - - String nodeName; - if (marker == '"' || marker == '\'') { - nodeName = this.readStringFromMarker(owner, marker, true); - } else { - StringBuilder result = new StringBuilder(); - while (true) { - while (marker != ':') { - if (this.isEndMarker(marker)) { - throw new IllegalStateException("Got a new line in node name: " + result); - } - - result.append(marker); - marker = this.readRaw(); - } - - marker = this.readRaw(); - if (Character.isWhitespace(marker)) { - this.setReuseBuffer(); - break; - } - - result.append(':'); + private boolean isNullSkippedFromMarker(char marker) { + this.setSeek(); + if (marker == 'n' && this.readRaw() == 'u' && this.readRaw() == 'l' && this.readRaw() == 'l') { + char endMarker = this.readRawIgnoreEmpty(); + if (this.isEndMarker(endMarker) || (endMarker == ',' && this.bracketOpened)) { + this.clearSeek(); + return true; } - - nodeName = result.toString(); } - return nodeName; + this.unsetSeek(); + return false; } private boolean skipChar(char expected) { diff --git a/src/main/java/net/elytrium/serializer/language/writer/AbstractWriter.java b/src/main/java/net/elytrium/serializer/language/writer/AbstractWriter.java index 376ccfa..742380b 100644 --- a/src/main/java/net/elytrium/serializer/language/writer/AbstractWriter.java +++ b/src/main/java/net/elytrium/serializer/language/writer/AbstractWriter.java @@ -293,7 +293,7 @@ public void writeNode(@Nullable Field owner, Object value, Comment[] comments) { if (value instanceof Map) { this.writeMap(owner, (Map) value, comments); } else if (value instanceof Collection) { - this.writeList(owner, (Collection) value, comments); + this.writeCollection(owner, (Collection) value, comments); } else if (value instanceof String) { this.writeString(owner, (String) value); } else if (value instanceof Character) { @@ -370,72 +370,70 @@ public void writeEndMap() { public abstract void writeEndMap(@Nullable Field owner); - public void writeList(Collection value, Comment[] comments) { - this.writeList(null, value, comments); + public void writeCollection(Collection value, Comment[] comments) { + this.writeCollection(null, value, comments); } - public void writeList(@Nullable Field owner, Collection value, Comment[] comments) { + public void writeCollection(@Nullable Field owner, Collection value, Comment[] comments) { synchronized (this) { if (value.isEmpty()) { - this.writeEmptyList(owner); + this.writeEmptyCollection(owner); this.writeComments(owner, comments, Comment.At.SAME_LINE, true); } else { - this.writeBeginList(owner); + this.writeBeginCollection(owner); this.writeComments(owner, comments, Comment.At.SAME_LINE, true); int counter = 0; int entriesAmount = value.size(); for (Object entry : value) { - this.writeListEntry(owner, entry); + this.writeCollectionEntry(owner, entry); if (++counter != entriesAmount) { - this.writeListEntryJoin(owner); + this.writeCollectionEntryJoin(owner); } - this.writeListEntryEnd(owner); + this.writeCollectionEntryEnd(owner); } - this.writeEndList(owner); + this.writeEndCollection(owner); } } } - public void writeEmptyList() { - this.writeEmptyList(null); + public void writeEmptyCollection() { + this.writeEmptyCollection(null); } - public abstract void writeEmptyList(@Nullable Field owner); + public abstract void writeEmptyCollection(@Nullable Field owner); - public void writeBeginList() { - this.writeBeginList(null); + public void writeBeginCollection() { + this.writeBeginCollection(null); } - public abstract void writeBeginList(@Nullable Field owner); + public abstract void writeBeginCollection(@Nullable Field owner); - public void writeListEntry(Object entry) { - this.writeListEntry(null, entry); + public void writeCollectionEntry(Object entry) { + this.writeCollectionEntry(null, entry); } - public abstract void writeListEntry(@Nullable Field owner, Object entry); + public abstract void writeCollectionEntry(@Nullable Field owner, Object entry); - public void writeListEntryJoin() { - this.writeListEntryJoin(null); + public void writeCollectionEntryJoin() { + this.writeCollectionEntryJoin(null); } - public abstract void writeListEntryJoin(@Nullable Field owner); + public abstract void writeCollectionEntryJoin(@Nullable Field owner); - public void writeListEntryEnd() { - this.writeListEntryEnd(null); + public void writeCollectionEntryEnd() { + this.writeCollectionEntryEnd(null); } - public abstract void writeListEntryEnd(@Nullable Field owner); + public abstract void writeCollectionEntryEnd(@Nullable Field owner); - public void writeEndList() { - this.writeEndList(null); + public void writeEndCollection() { + this.writeEndCollection(null); } - public abstract void writeEndList(@Nullable Field owner); - - public abstract void writeLine(); + public abstract void writeEndCollection(@Nullable Field owner); public void writeString(String value) { this.writeString(null, value); @@ -479,6 +477,8 @@ public void writeNumber(@Nullable Field owner, Number value) { } } + public abstract void writeLine(); + public void writeRaw(String value) { try { this.writer.write(value); diff --git a/src/main/java/net/elytrium/serializer/language/writer/JsonWriter.java b/src/main/java/net/elytrium/serializer/language/writer/JsonWriter.java index 006afd9..599aa2a 100644 --- a/src/main/java/net/elytrium/serializer/language/writer/JsonWriter.java +++ b/src/main/java/net/elytrium/serializer/language/writer/JsonWriter.java @@ -36,10 +36,6 @@ public JsonWriter(BufferedWriter writer) { super(writer); } - public void setSingleIndent(String singleIndent) { - this.singleIndent = singleIndent; - } - @Override public void writeCommentStart(@Nullable Field owner, Comment.At at) { synchronized (this) { @@ -108,7 +104,7 @@ public void writeEndMap(@Nullable Field owner) { } @Override - public void writeBeginList(@Nullable Field owner) { + public void writeBeginCollection(@Nullable Field owner) { synchronized (this) { this.addIndent(); this.writeRaw('['); @@ -117,7 +113,7 @@ public void writeBeginList(@Nullable Field owner) { } @Override - public void writeListEntry(@Nullable Field owner, Object entry) { + public void writeCollectionEntry(@Nullable Field owner, Object entry) { synchronized (this) { this.writeIndent(); this.writeNode(entry, null); @@ -125,21 +121,21 @@ public void writeListEntry(@Nullable Field owner, Object entry) { } @Override - public void writeListEntryJoin(@Nullable Field owner) { + public void writeCollectionEntryJoin(@Nullable Field owner) { synchronized (this) { this.writeRaw(','); } } @Override - public void writeListEntryEnd(@Nullable Field owner) { + public void writeCollectionEntryEnd(@Nullable Field owner) { synchronized (this) { this.writeLine(); } } @Override - public void writeEndList(@Nullable Field owner) { + public void writeEndCollection(@Nullable Field owner) { synchronized (this) { this.removeIndent(); this.writeIndent(); @@ -154,6 +150,10 @@ public void writeLine() { } } + public void setSingleIndent(String singleIndent) { + this.singleIndent = singleIndent; + } + private void writeIndent() { this.writeRaw(this.currentIndent); } diff --git a/src/main/java/net/elytrium/serializer/language/writer/YamlWriter.java b/src/main/java/net/elytrium/serializer/language/writer/YamlWriter.java index 8022e40..4a791a3 100644 --- a/src/main/java/net/elytrium/serializer/language/writer/YamlWriter.java +++ b/src/main/java/net/elytrium/serializer/language/writer/YamlWriter.java @@ -54,10 +54,6 @@ public YamlWriter(BufferedWriter writer) { this.yamlSerializable = null; } - public void setSingleIndent(String singleIndent) { - this.singleIndent = singleIndent; - } - @Override public void writeCommentStart(@Nullable Field owner, Comment.At at) { synchronized (this) { @@ -135,21 +131,21 @@ public void writeEndMap(@Nullable Field owner) { } @Override - public void writeEmptyList(@Nullable Field owner) { + public void writeEmptyCollection(@Nullable Field owner) { synchronized (this) { this.writeRaw("[]"); } } @Override - public void writeBeginList(@Nullable Field owner) { + public void writeBeginCollection(@Nullable Field owner) { synchronized (this) { this.writeBeginCommon(); } } @Override - public void writeListEntry(@Nullable Field owner, Object entry) { + public void writeCollectionEntry(@Nullable Field owner, Object entry) { synchronized (this) { this.writeIndent(); @@ -161,31 +157,24 @@ public void writeListEntry(@Nullable Field owner, Object entry) { } @Override - public void writeListEntryJoin(@Nullable Field owner) { + public void writeCollectionEntryJoin(@Nullable Field owner) { synchronized (this) { this.writeLine(); } } @Override - public void writeListEntryEnd(@Nullable Field owner) { + public void writeCollectionEntryEnd(@Nullable Field owner) { } @Override - public void writeEndList(@Nullable Field owner) { + public void writeEndCollection(@Nullable Field owner) { synchronized (this) { this.removeIndent(); } } - @Override - public void writeLine() { - synchronized (this) { - super.writeRaw(this.config.getLineSeparator()); - } - } - @Override public void writeString(@Nullable Field owner, String value) { synchronized (this) { @@ -196,7 +185,7 @@ public void writeString(@Nullable Field owner, String value) { @Override public void writeCharacter(@Nullable Field owner, char value) { synchronized (this) { - boolean shouldUseQuotes = this.shouldUseQuotes(value, true); + boolean shouldUseQuotes = YamlWriter.shouldUseQuotes(value, true, false); if (shouldUseQuotes) { this.writeRaw('"'); } @@ -207,20 +196,11 @@ public void writeCharacter(@Nullable Field owner, char value) { } } - private boolean shouldUseQuotes(char[] characters, boolean avoidSpecial) { - for (char character : characters) { - if (this.shouldUseQuotes(character, avoidSpecial)) { - return true; - } + @Override + public void writeLine() { + synchronized (this) { + super.writeRaw(this.config.getLineSeparator()); } - - return false; - } - - private boolean shouldUseQuotes(char value, boolean avoidSpecial) { - return (avoidSpecial && (value == ' ' || value == '#' || value == '"')) - || value == '\0' || value == '\u0007' || value == '\b' || value == '\t' || value == '\n' || value == '\u000B' || value == '\f' || value == '\r' - || value == '\u001B' || value == '\'' || value == '\\' || value == '\u0085' || value == '\u00A0' || value == '\u2028' || value == '\u2029'; } private void writeString0(@Nullable Field owner, String value, boolean nodeName) { @@ -267,10 +247,10 @@ private void writeString0(@Nullable Field owner, String value, boolean nodeName) } private void writeCharacters(char[] characters) { - this.writeCharacters(characters, true, true, false, true); + this.writeCharacters(characters, false, false, true, true, true); } - private void writeCharacters(char[] characters, boolean escapeNewLine, boolean doubleNewLine, boolean firstNewLine, boolean escapeSpecial) { + private void writeCharacters(char[] characters, boolean firstNewLine, boolean singleQuotes2Double, boolean escapeNewLine, boolean doubleNewLine, boolean escapeSpecial) { this.addIndent(); if (firstNewLine) { @@ -282,40 +262,45 @@ private void writeCharacters(char[] characters, boolean escapeNewLine, boolean d char highSurrogate = 0; for (char character : characters) { - if (!escapeNewLine) { - if (character == AbstractWriter.NEW_LINE || character == lineSeparatorChars[lineSeparatorCharsCaught]) { - if (character == AbstractWriter.NEW_LINE || ++lineSeparatorCharsCaught == lineSeparatorChars.length) { - lineSeparatorCharsCaught = 0; - this.writeLineAndIndent(); - - if (doubleNewLine) { + if (singleQuotes2Double && character == '\'') { + this.writeRaw("''"); + } else { + if (!escapeNewLine) { + if (character == AbstractWriter.NEW_LINE || character == lineSeparatorChars[lineSeparatorCharsCaught]) { + if (character == AbstractWriter.NEW_LINE || ++lineSeparatorCharsCaught == lineSeparatorChars.length) { + lineSeparatorCharsCaught = 0; this.writeLineAndIndent(); + + if (doubleNewLine) { + this.writeLineAndIndent(); + } } - } - continue; - } else { - for (int i = 0; i < lineSeparatorCharsCaught; i++) { - this.writeCharacter0(lineSeparatorChars[i], escapeSpecial); - } + continue; + } else { + for (int i = 0; i < lineSeparatorCharsCaught; ++i) { + this.writeCharacter0(lineSeparatorChars[i], escapeSpecial); + } - lineSeparatorCharsCaught = 0; + lineSeparatorCharsCaught = 0; + } } - } - if (highSurrogate != 0) { - this.writeCharacter0(Character.toCodePoint(highSurrogate, character), escapeSpecial); - highSurrogate = 0; - } else if (Character.isHighSurrogate(character)) { - highSurrogate = character; - } else { - this.writeCharacter0(character, escapeSpecial); + if (highSurrogate != 0) { + this.writeCharacter0(Character.toCodePoint(highSurrogate, character), escapeSpecial); + highSurrogate = 0; + } else if (Character.isHighSurrogate(character)) { + highSurrogate = character; + } else { + this.writeCharacter0(character, escapeSpecial); + } } } if (highSurrogate != 0) { this.writeCharacter0(highSurrogate, escapeSpecial); } + this.removeIndent(); } @@ -348,22 +333,20 @@ private void writeCharacter0(int value, boolean escapeSpecial) { this.writeRaw("\\x"); String result = "0" + Integer.toString(value, 16); this.writeRaw(result.substring(result.length() - 2)); - break; } else if (Character.charCount(value) == 2) { this.writeRaw("\\U"); String result = "000" + Long.toHexString(value); this.writeRaw(result.substring(result.length() - 8)); - break; } else { this.writeRaw("\\u"); String result = "000" + Integer.toString(value, 16); this.writeRaw(result.substring(result.length() - 4)); - break; } - } - - for (char character : Character.toChars(value)) { - this.writeRaw(character); + } else if (Character.isBmpCodePoint(value)) { // Inlined Character.toChars() + this.writeRaw((char) value); + } else { + this.writeRaw(Character.highSurrogate(value)); + this.writeRaw(Character.lowSurrogate(value)); } } } @@ -433,6 +416,27 @@ public void writeRaw(char value) { super.writeRaw(value); } + public void setSingleIndent(String singleIndent) { + this.singleIndent = singleIndent; + } + + private static boolean shouldUseQuotes(char[] characters, boolean avoidSpecial, boolean ignoreSingleQuotes) { + for (char character : characters) { + if (YamlWriter.shouldUseQuotes(character, avoidSpecial, ignoreSingleQuotes)) { + return true; + } + } + + return false; + } + + private static boolean shouldUseQuotes(char value, boolean avoidSpecial, boolean ignoreSingleQuotes) { + return (avoidSpecial && (value == ' ' || value == '#' || value == '"')) + || (!ignoreSingleQuotes && value == '\'') + || value == '\0' || value == '\u0007' || value == '\b' || value == '\t' || value == '\n' || value == '\u000B' || value == '\f' || value == '\r' + || value == '\u001B' || value == '\\' || value == '\u0085' || value == '\u00A0' || value == '\u2028' || value == '\u2029'; + } + public enum StringStyle { /** @@ -441,7 +445,7 @@ public enum StringStyle { */ NOT_QUOTED((writer, string) -> { char[] characters = string.toCharArray(); - boolean shouldUseQuotes = writer.shouldUseQuotes(characters, true); + boolean shouldUseQuotes = YamlWriter.shouldUseQuotes(characters, true, false); if (shouldUseQuotes) { writer.writeRaw('"'); @@ -459,28 +463,17 @@ public enum StringStyle { */ SINGLE_QUOTED((writer, string) -> { char[] characters = string.toCharArray(); - boolean shouldUseQuotes = writer.shouldUseQuotes(characters, false); - - if (shouldUseQuotes) { - writer.writeRaw('"'); - } else { - writer.writeRaw('\''); - } - - writer.writeCharacters(string.replace("'", "''").toCharArray(), false, true, false, shouldUseQuotes); - - if (shouldUseQuotes) { - writer.writeRaw('"'); - } else { - writer.writeRaw('\''); - } + boolean shouldUseQuotes = YamlWriter.shouldUseQuotes(characters, false, true); + writer.writeRaw(shouldUseQuotes ? '"' : '\''); + writer.writeCharacters(characters, false, true, false, true, shouldUseQuotes); + writer.writeRaw(shouldUseQuotes ? '"' : '\''); }), /** * Preferably single line, line separator will be escaped, quoted with double quote {@code "}. */ DOUBLE_QUOTED((writer, string) -> { writer.writeRaw('"'); - writer.writeCharacters(string.toCharArray(), true, true, false, true); + writer.writeCharacters(string.toCharArray(), false, false, true, true, true); writer.writeRaw('"'); }), /** @@ -488,7 +481,7 @@ public enum StringStyle { */ DOUBLE_QUOTED_MULTILINE((writer, string) -> { writer.writeRaw('"'); - writer.writeCharacters(string.toCharArray(), false, true, false, true); + writer.writeCharacters(string.toCharArray(), false, false, false, true, true); writer.writeRaw('"'); }), /** @@ -503,10 +496,10 @@ public enum StringStyle { } else if (string.endsWith(writer.config.getDoubledLineSeparator())) { writer.writeRaw(">+"); } else { - writer.writeRaw(">"); + writer.writeRaw('>'); } - writer.writeCharacters(string.toCharArray(), false, true, true, false); + writer.writeCharacters(string.toCharArray(), true, false, false, true, false); }), /** * Preferably multi line, quoted with {@code >-}, new lines will be replaced with spaces, @@ -518,7 +511,7 @@ public enum StringStyle { } writer.writeRaw(">-"); - writer.writeCharacters(string.toCharArray(), false, true, true, false); + writer.writeCharacters(string.toCharArray(), true, false, false, true, false); }), /** * Preferably multi line, quoted with {@code >+}, new lines will be replaced with spaces, @@ -526,13 +519,8 @@ public enum StringStyle { * but {@code >-} will be used if new lines were found at the end of the string. */ MULTILINE_FOLDED_AUTO_KEPT((writer, string) -> { - if (!string.endsWith(writer.config.getLineSeparator())) { - writer.writeRaw(">-"); - } else { - writer.writeRaw(">+"); - } - - writer.writeCharacters(string.toCharArray(), false, true, true, false); + writer.writeRaw(string.endsWith(writer.config.getLineSeparator()) ? ">+" : ">-"); + writer.writeCharacters(string.toCharArray(), true, false, false, true, false); }), /** * Preferably multi line, quoted with {@code |}, single new line at the end will be kept, @@ -544,10 +532,10 @@ public enum StringStyle { } else if (string.endsWith(writer.config.getDoubledLineSeparator())) { writer.writeRaw("|+"); } else { - writer.writeRaw("|"); + writer.writeRaw('|'); } - writer.writeCharacters(string.toCharArray(), false, false, true, false); + writer.writeCharacters(string.toCharArray(), true, false, false, false, false); }), /** * Preferably multi line, quoted with {@code |}, @@ -559,20 +547,15 @@ public enum StringStyle { } writer.writeRaw("|-"); - writer.writeCharacters(string.toCharArray(), false, false, true, false); + writer.writeCharacters(string.toCharArray(), true, false, false, false, false); }), /** * Preferably multi line, quoted with {@code |+}, all new lines from end will be kept, * but {@code |-} will be used if no new lines were found at the end of the string. */ MULTILINE_LITERAL_AUTO_KEPT((writer, string) -> { - if (!string.endsWith(writer.config.getLineSeparator())) { - writer.writeRaw("|- "); - } else { - writer.writeRaw("|+ "); - } - - writer.writeCharacters(string.toCharArray(), false, false, true, false); + writer.writeRaw(string.endsWith(writer.config.getLineSeparator()) ? "|+ " : "|- "); + writer.writeCharacters(string.toCharArray(), true, false, false, false, false); }); private final BiConsumer writeFunction; diff --git a/src/test/java/net/elytrium/serializer/SerializerTest.java b/src/test/java/net/elytrium/serializer/SerializerTest.java index a398953..5dc4bc4 100644 --- a/src/test/java/net/elytrium/serializer/SerializerTest.java +++ b/src/test/java/net/elytrium/serializer/SerializerTest.java @@ -21,6 +21,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongArrayList; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; @@ -111,7 +112,7 @@ void testConfig() throws IOException { Assertions.assertEquals("\uD83D\uDD25 final value", settings.finalField); Assertions.assertEquals("regular \"value\"", settings.regularField); - Assertions.assertEquals("regular \"value\"", settings.regularField2); + Assertions.assertEquals("regular ''value1'' \"value2\"", settings.regularField2); Assertions.assertEquals("regular \n\"value\"", settings.regularField3); Assertions.assertEquals((float) Math.PI, settings.regularFloatField); Assertions.assertEquals(Math.E, settings.regularDoubleField); @@ -147,6 +148,7 @@ void testConfig() throws IOException { Assertions.assertEquals(HashMap.class, settings.int2StringMap.getClass()); Assertions.assertEquals(Int2ObjectLinkedOpenHashMap.class, settings.int2StringMapFastUtil.getClass()); Assertions.assertEquals(Int2ObjectLinkedOpenHashMap.class, settings.int2StringMapFastUtil2.getClass()); + Assertions.assertEquals(LongArrayList.class, settings.int2LongListMapFastUtil.get(-15555).getClass()); settings.int2StringMap.forEach((key, value) -> { Assertions.assertEquals(Integer.class, key.getClass()); @@ -308,7 +310,7 @@ public String deserialize(String from) { public String regularField = "regular \"value\""; @YamlStringStyle(YamlWriter.StringStyle.SINGLE_QUOTED) - public String regularField2 = "regular \"value\""; + public String regularField2 = "regular ''value1'' \"value2\""; @YamlStringStyle(YamlWriter.StringStyle.MULTILINE_LITERAL_AUTO_KEPT) public String regularField3 = "regular \n\"value\""; @@ -344,7 +346,12 @@ public String deserialize(String from) { public Int2ObjectLinkedOpenHashMap int2StringMapFastUtil = new Int2ObjectLinkedOpenHashMap<>(new int[] { 1, 15555, 44 }, new String[] { "v1", "v2", "v3" }); @MapType(Int2ObjectLinkedOpenHashMap.class) - public Int2ObjectMap int2StringMapFastUtil2 = new Int2ObjectLinkedOpenHashMap<>(new int[] {1, 15555, 44 }, new String[] {"v1", "v2", "v3" }); + public Int2ObjectMap int2StringMapFastUtil2 = new Int2ObjectLinkedOpenHashMap<>(new int[] { 1, 15555, 44 }, new String[] { "v1", "v2", "v3" }); + + public final/*TODO non final*/ Int2ObjectLinkedOpenHashMap int2LongListMapFastUtil = new Int2ObjectLinkedOpenHashMap<>( + new int[] { 1, -15555 }, + new LongArrayList[] { LongArrayList.of(1, 2, -3, 4), LongArrayList.of(3, 2, -3, 4) } + ); public final List objectListWithMaps = Arrays.asList( SerializerTest.map("test", SerializerTest.map("test2", "test")), diff --git a/src/test/resources/config.yml b/src/test/resources/config.yml index 7d3b01e..e97322b 100644 --- a/src/test/resources/config.yml +++ b/src/test/resources/config.yml @@ -3,7 +3,7 @@ final-field: "🔥 final value" regular-field: "regular \"value\"" -regular-field-2: 'regular "value"' +regular-field-2: 'regular ''''value1'''' "value2"' regular-field-3: |- regular "value" @@ -40,6 +40,17 @@ int-2-string-map-fast-util-2: 1: "v1" 15555: "v2" 44: "v3" +int-2-long-list-map-fast-util: + 1: + - 1 + - 2 + - -3 + - 4 + -15555: + - 3 + - 2 + - -3 + - 4 object-list-with-maps: - test: test2: "test"