Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 8 additions & 0 deletions src/main/java/ch/njol/skript/lang/Variable.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import ch.njol.skript.structures.StructVariables.DefaultVariables;
import ch.njol.skript.util.StringMode;
import ch.njol.skript.util.Utils;
import org.skriptlang.skript.util.IndexTrackingTreeMap;
import ch.njol.skript.variables.Variables;
import ch.njol.util.Kleenean;
import ch.njol.util.Pair;
Expand Down Expand Up @@ -594,6 +595,13 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) thro
}
} else {
assert mode == ChangeMode.ADD;
if (map instanceof IndexTrackingTreeMap<Object> indexTrackingMap) {
for (Object value : delta) {
int index = indexTrackingMap.nextOpenIndex();
setIndex(event, String.valueOf(index), value);
}
return;
}
int i = 1;
for (Object value : delta) {
if (map != null)
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/ch/njol/skript/variables/VariablesMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ch.njol.skript.lang.Variable;
import ch.njol.util.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.util.IndexTrackingTreeMap;

import java.util.Comparator;
import java.util.HashMap;
Expand Down Expand Up @@ -216,7 +217,7 @@ void setVariable(String name, @Nullable Object value) {
break;
} else if (value != null) {
// Create child node, add it to parent and continue iteration
childNode = new TreeMap<>(VARIABLE_NAME_COMPARATOR);
childNode = new IndexTrackingTreeMap<>(VARIABLE_NAME_COMPARATOR);

parent.put(childNodeName, childNode);
parent = (TreeMap<String, Object>) childNode;
Expand Down Expand Up @@ -269,7 +270,7 @@ void setVariable(String name, @Nullable Object value) {
break;
} else if (value != null) {
// Need to continue iteration, create new child node and put old value in it
TreeMap<String, Object> newChildNodeMap = new TreeMap<>(VARIABLE_NAME_COMPARATOR);
TreeMap<String, Object> newChildNodeMap = new IndexTrackingTreeMap<>(VARIABLE_NAME_COMPARATOR);
newChildNodeMap.put(null, childNode);

// Add new child node to parent
Expand Down Expand Up @@ -334,7 +335,7 @@ public VariablesMap copy() {
*/
@SuppressWarnings("unchecked")
private static TreeMap<String, Object> copyTreeMap(TreeMap<String, Object> original) {
TreeMap<String, Object> copy = new TreeMap<>(VARIABLE_NAME_COMPARATOR);
TreeMap<String, Object> copy = new IndexTrackingTreeMap<>(VARIABLE_NAME_COMPARATOR);

for (Entry<String, Object> child : original.entrySet()) {
String key = child.getKey();
Expand Down
129 changes: 129 additions & 0 deletions src/main/java/org/skriptlang/skript/util/IndexTrackingTreeMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.skriptlang.skript.util;

import com.google.common.base.Preconditions;
import org.jetbrains.annotations.VisibleForTesting;

import java.util.*;

/**
* A {@link TreeMap} that supports automatically assigning the next available
* positive integer key, represented as a string.
*
* <p>In addition to arbitrary string keys, this map can be used with
* positive integer string keys such as {@code "1"}, {@code "2"}, and
* {@code "3"}. The {@link #add(Object)} method inserts a value using the
* next available integer key.</p>
*
* @param <V> the type of mapped values
*/
public class IndexTrackingTreeMap<V> extends TreeMap<String, V> {

@VisibleForTesting
final List<Integer> numericalIndices = new ArrayList<>();

public IndexTrackingTreeMap() {
super();
}

public IndexTrackingTreeMap(Comparator<? super String> comparator) {
super(comparator);
}

@Override
public V put(String key, V value) {
V previous = super.put(key, value);
if (previous == null && value != null && isInt(key)) {
try {
int index = Integer.parseInt(key);
int pos = Collections.binarySearch(numericalIndices, index);
numericalIndices.add(-pos - 1, index);
} catch (NumberFormatException ignore) {}
} else if (previous != null && value == null) {
handleRemove(key);
}
return previous;
}

/**
* Adds the given value under the first available positive integer key.
*
* @param value the value to add, cannot be null
*/
public void add(V value) {
Preconditions.checkNotNull(value, "value");
int index = nextOpenIndex();
super.put(String.valueOf(index), value);
numericalIndices.add(index - 1, index);
}
Comment thread
UnderscoreTud marked this conversation as resolved.

@Override
public V remove(Object key) {
V value = super.remove(key);
if (value != null && key instanceof String index)
handleRemove(index);
return value;
}

@Override
public void clear() {
super.clear();
numericalIndices.clear();
}

/**
* Finds the first available positive integer index that is not currently
* used as a key in this map.
*
* <p>This method inspects tracked numeric keys and returns the smallest
* missing index, starting at {@code 1}.</p>
*
* @return the next available positive integer index
*/
public int nextOpenIndex() {
int size = numericalIndices.size();
if (size == 0)
return 1;

if (numericalIndices.getFirst() > 1)
return 1;

int left = 0;
int right = size - 1;

while (left <= right) {
int midpoint = left + (right - left >>> 1);

if (numericalIndices.get(midpoint) == midpoint + 1) {
left = midpoint + 1;
} else {
right = midpoint - 1;
}
}

return left + 1;
}

private void handleRemove(String index) {
if (!isInt(index))
return;
int pos = Collections.binarySearch(numericalIndices, Integer.parseInt(index));
if (pos >= 0)
numericalIndices.remove(pos);
}

private boolean isInt(String string) {
if (string == null || string.isBlank() || string.charAt(0) == '0') // Don't handle leading-zero integers
return false;
for (int i = 0; i < string.length(); i++) {
if (!isDigit(string.charAt(i)))
return false;
}

return true;
}

private boolean isDigit(int codepoint) {
return codepoint >= '0' && codepoint <= '9';
}

}
Loading