Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 54 additions & 33 deletions src/main/java/ch/njol/skript/variables/FlatFileStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -528,53 +529,73 @@ static byte[] decode(String hex) {
return decoded;
}

/**
* A regex pattern of a line in a CSV file.
* <ul>
* <li>{@code (?<=^|,)}: assert that the match is preceded by the start of the line or a comma</li>
* <li>{@code (?:([^",]*)|"((?:[^"]+|"")*)")}: match either a quoted or unquoted value</li>
* <ul>
* <li>- {@code ([^",]*)}: match an unquoted value</li>
* <li>- {@code "((?:[^"]+|"")*)"}: match a quoted value</li>
* </ul>
* <li>{@code (?:,|$)}: match either a comma or the end of the line</li>
* </ul>
*/
private static final Pattern CSV_LINE_PATTERN = Pattern.compile("(?<=^|,)\\s*(?:([^\",]*)|\"((?:[^\"]+|\"\")*)\")\\s*(?:,|$)");

/**
* Splits the given CSV line into its values.
* Supports quoted fields (which may contain commas) and doubled-quote
* escaping ("" -> ") inside quoted fields. Unquoted values are trimmed;
* quoted values are taken verbatim (aside from unescaping).
*
* @param line the CSV line.
* @return the array of values.
*
* @see #CSV_LINE_PATTERN
* @return the array of values, or null if the line is malformed.
*/
@Nullable
static String[] splitCSV(String line) {
Matcher matcher = CSV_LINE_PATTERN.matcher(line);

int lastEnd = 0;
ArrayList<String> result = new ArrayList<>();
int n = line.length();
int pos = 0;
List<String> result = new ArrayList<>();

while (true) {
// skip leading whitespace before a value
while (pos < n && Character.isWhitespace(line.charAt(pos)))
pos++;

if (pos < n && line.charAt(pos) == '"') {
// quoted value
pos++; // consume opening quote
StringBuilder value = new StringBuilder();
boolean closed = false;
while (pos < n) {
char c = line.charAt(pos);
if (c == '"') {
if (pos + 1 < n && line.charAt(pos + 1) == '"') {
value.append('"'); // escaped quote
pos += 2;
} else {
pos++; // consume closing quote
closed = true;
break;
}
} else {
value.append(c);
pos++;
}
}
if (!closed)
return null; // unterminated quoted value

while (matcher.find()) {
if (lastEnd != matcher.start())
return null; // other stuff in between finds
// skip trailing whitespace after the closing quote
while (pos < n && Character.isWhitespace(line.charAt(pos)))
pos++;

if (matcher.group(1) != null) {
// unquoted, leave as is
result.add(matcher.group(1).trim());
result.add(value.toString());
} else {
// quoted, remove quotes
result.add(matcher.group(2).replace("\"\"", "\""));
// unquoted value: read until comma, rejecting stray quotes
int start = pos;
while (pos < n && line.charAt(pos) != ',') {
if (line.charAt(pos) == '"')
return null; // quote in the middle of an unquoted value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might be allowed (quotes in things like var names?) Please double-check.

pos++;
}
result.add(line.substring(start, pos).trim());
}

lastEnd = matcher.end();
if (pos >= n)
break; // end of line, done
if (line.charAt(pos) != ',')
return null; // expected a comma here, found something else
pos++; // consume the comma and parse the next value
}

if (lastEnd != line.length())
return null; // other stuff after last find

return result.toArray(new String[0]);
}

Expand Down
8 changes: 2 additions & 6 deletions src/main/java/ch/njol/skript/variables/VariablesMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ void setVariable(String name, @Nullable Object value) {
parent.put(childNodeName, childNode);
parent = (TreeMap<String, Object>) childNode;
} else {
// Want to set variable to null, bu variable is already null
// Want to set variable to null, but variable is already null
break;
}
} else if (childNode instanceof TreeMap) {
Expand Down Expand Up @@ -297,11 +297,7 @@ void setVariable(String name, @Nullable Object value) {
// Ran into leaf node
if (i == split.length - 1) {
// If we arrived at the end of the variable name, update parent
if (value == null)
parent.remove(childNodeName);
else
parent.put(childNodeName, value);

parent.compute(childNodeName, (k, v) -> value);
break;
} else if (value != null) {
// Need to continue iteration, create new child node and put old value in it
Expand Down
93 changes: 30 additions & 63 deletions src/main/java/ch/njol/yggdrasil/DefaultYggdrasilInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// x(): read info & data (e.g. content type, contents) [i.e. no tag]
// _x(): read data only (e.g. contents)
public final class DefaultYggdrasilInputStream extends YggdrasilInputStream {

private final InputStream in;
private final short version;

Expand All @@ -32,12 +32,12 @@ public DefaultYggdrasilInputStream(Yggdrasil yggdrasil, InputStream in) throws I
}

/**
* @throws EOFException If the end of the stream is reached
* @throws FastEOFException If the end of the stream is reached
*/
private int read() throws IOException {
int b = in.read();
if (b < 0)
throw new EOFException();
throw new FastEOFException();
return b;
}

Expand All @@ -51,7 +51,7 @@ private void readFully(byte[] buf, int startLength) throws IOException {
while (length > 0) {
int n = in.read(buf, offset, length);
if (n < 0)
throw new EOFException("Expected " + startLength + " bytes, but could only read " + (startLength - length));
throw new FastEOFException("Expected " + startLength + " bytes, but could only read " + (startLength - length));
offset += n;
length -= n;
}
Expand All @@ -65,7 +65,7 @@ private String readShortString() throws IOException {
int i = version <= 1 ? readInt() : readUnsignedInt();
if (i < 0 || i > readShortStrings.size())
throw new StreamCorruptedException("Invalid short string reference " + i);
return "" + readShortStrings.get(i);
return readShortStrings.get(i);
}
byte[] d = new byte[length];
readFully(d);
Expand Down Expand Up @@ -112,12 +112,12 @@ private int readUnsignedInt() throws IOException {
return (b & ~0x80) << 8 | read();
return b << 24 | read() << 16 | read() << 8 | read();
}

private long readLong() throws IOException {
return (long) in.read() << 56 | (long) in.read() << 48
| (long) in.read() << 40 | (long) in.read() << 32
| (long) in.read() << 24 | (long) in.read() << 16
| (long) in.read() << 8 | read();
return (long) read() << 56 | (long) read() << 48
| (long) read() << 40 | (long) read() << 32
| (long) read() << 24 | (long) read() << 16
| (long) read() << 8 | read();
}

private float readFloat() throws IOException {
Expand All @@ -143,26 +143,17 @@ else if (r == 1)

@Override
protected Object readPrimitive(Tag type) throws IOException {
switch (type) {
case T_BYTE:
return readByte();
case T_SHORT:
return readShort();
case T_INT:
return readInt();
case T_LONG:
return readLong();
case T_FLOAT:
return readFloat();
case T_DOUBLE:
return readDouble();
case T_CHAR:
return readChar();
case T_BOOLEAN:
return readBoolean();
default:
throw new YggdrasilException("Internal error; " + type);
}
return switch (type) {
case T_BYTE -> readByte();
case T_SHORT -> readShort();
case T_INT -> readInt();
case T_LONG -> readLong();
case T_FLOAT -> readFloat();
case T_DOUBLE -> readDouble();
case T_CHAR -> readChar();
case T_BOOLEAN -> readBoolean();
default -> throw new YggdrasilException("Internal error; " + type);
};
}

@Override
Expand Down Expand Up @@ -205,40 +196,16 @@ protected Class<?> readClass() throws IOException {
int dimensions = 0;
while ((type = readTag()) == T_ARRAY)
dimensions++;
Class<?> clazz;
switch (type) {
case T_OBJECT:
case T_ENUM:
clazz = yggdrasil.getClass(readShortString());
break;
case T_BOOLEAN:
case T_BOOLEAN_OBJ:
case T_BYTE:
case T_BYTE_OBJ:
case T_CHAR:
case T_CHAR_OBJ:
case T_DOUBLE:
case T_DOUBLE_OBJ:
case T_FLOAT:
case T_FLOAT_OBJ:
case T_INT:
case T_INT_OBJ:
case T_LONG:
case T_LONG_OBJ:
case T_SHORT:
case T_SHORT_OBJ:
case T_CLASS:
case T_STRING:
Class<?> clazz = switch (type) {
case T_OBJECT, T_ENUM -> yggdrasil.getClass(readShortString());
case T_BOOLEAN, T_BOOLEAN_OBJ, T_BYTE, T_BYTE_OBJ, T_CHAR, T_CHAR_OBJ, T_DOUBLE, T_DOUBLE_OBJ, T_FLOAT,
T_FLOAT_OBJ, T_INT, T_INT_OBJ, T_LONG, T_LONG_OBJ, T_SHORT, T_SHORT_OBJ, T_CLASS, T_STRING -> {
assert type.type != null;
clazz = type.type;
break;
case T_NULL:
case T_REFERENCE:
throw new StreamCorruptedException("unexpected tag " + type);
case T_ARRAY:
default:
throw new YggdrasilException("Internal error; " + type);
}
yield type.type;
}
case T_NULL, T_REFERENCE -> throw new StreamCorruptedException("unexpected tag " + type);
default -> throw new YggdrasilException("Internal error; " + type);
};
while (dimensions-- > 0)
clazz = CollectionUtils.arrayType(clazz);
return clazz;
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/ch/njol/yggdrasil/FastEOFException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ch.njol.yggdrasil;

import java.io.EOFException;

public class FastEOFException extends EOFException {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

javadocs for why this exists would be good


public FastEOFException() {
super();
}

public FastEOFException(String message) {
super(message);
}

@Override
public synchronized Throwable fillInStackTrace() {
return this;
}

}
6 changes: 1 addition & 5 deletions src/main/java/ch/njol/yggdrasil/Fields.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public Class<?> getType() {
if (value == null)
return null;
Class<?> type = value.getClass();
assert type != null;
return isPrimitiveValue ? Tag.getPrimitiveFromWrapper(type).type : type;
}

Expand Down Expand Up @@ -134,9 +133,8 @@ public boolean equals(@Nullable Object object) {
return true;
if (object == null)
return false;
if (!(object instanceof FieldContext))
if (!(object instanceof FieldContext other))
return false;
FieldContext other = (FieldContext) object;
return id.equals(other.id);
}

Expand Down Expand Up @@ -198,7 +196,6 @@ public Fields(Object object) throws NotSerializableException {
public Fields(Object object, @Nullable Yggdrasil yggdrasil) throws NotSerializableException {
this.yggdrasil = yggdrasil;
Class<?> type = object.getClass();
assert type != null;
for (Field field : getFields(type)) {
assert field != null;
try {
Expand Down Expand Up @@ -312,7 +309,6 @@ public void setFields(Object object) throws StreamCorruptedException, NotSeriali
throw new YggdrasilException("");
Set<FieldContext> excessive = new HashSet<>(fields.values());
Class<?> type = object.getClass();
assert type != null;
for (Field field : getFields(type)) {
assert field != null;
String id = Yggdrasil.getID(field);
Expand Down
28 changes: 14 additions & 14 deletions src/main/java/ch/njol/yggdrasil/JRESerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,18 @@ public Fields serialize(Object object) {
if (!SUPPORTED_CLASSES.contains(object.getClass()))
throw new IllegalArgumentException();
Fields fields = new Fields();
if (object instanceof Collection) {
Collection<?> collection = ((Collection<?>) object);
fields.putObject("values", collection.toArray());
} else if (object instanceof Map) {
Map<?, ?> map = ((Map<?, ?>) object);
fields.putObject("keys", map.keySet().toArray());
fields.putObject("values", map.values().toArray());
} else if (object instanceof UUID) {
fields.putPrimitive("mostSigBits", ((UUID) object).getMostSignificantBits());
fields.putPrimitive("leastSigBits", ((UUID) object).getLeastSignificantBits());
switch (object) {
case Collection<?> collection -> fields.putObject("values", collection.toArray());
case Map<?, ?> map -> {
fields.putObject("keys", map.keySet().toArray());
fields.putObject("values", map.values().toArray());
}
case UUID uuid -> {
fields.putPrimitive("mostSigBits", uuid.getMostSignificantBits());
fields.putPrimitive("leastSigBits", uuid.getLeastSignificantBits());
}
default -> {
}
}
assert fields.size() > 0 : object;
return fields;
Expand Down Expand Up @@ -83,15 +85,13 @@ public <T> T newInstance(Class<T> type) {
@Override
public void deserialize(Object object, Fields fields) throws StreamCorruptedException {
try {
if (object instanceof Collection) {
Collection<?> collection = ((Collection<?>) object);
if (object instanceof Collection<?> collection) {
Object[] values = fields.getObject("values", Object[].class);
if (values == null)
throw new StreamCorruptedException();
collection.addAll((Collection) Arrays.asList(values));
return;
} else if (object instanceof Map) {
Map<?, ?> map = ((Map<?, ?>) object);
} else if (object instanceof Map<?, ?> map) {
Object[] keys = fields.getObject("keys", Object[].class), values = fields.getObject("values", Object[].class);
if (keys == null || values == null || keys.length != values.length)
throw new StreamCorruptedException();
Expand Down
Loading
Loading