commit aceaac08d66eaaeaf99b1d2922b0b341c72ebe01 Author: Jörg Prante Date: Sat Sep 12 22:42:03 2020 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b9b334 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/data +/work +/logs +/.idea +/target +.DS_Store +/.settings +/.classpath +/.project +/.gradle +build +out +*~ +*.iml diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e945311 --- /dev/null +++ b/build.gradle @@ -0,0 +1,34 @@ +plugins { + id "de.marcphilipp.nexus-publish" version "0.4.0" + id "io.codearte.nexus-staging" version "0.21.1" +} + +wrapper { + gradleVersion = "${rootProject.property('gradle.wrapper.version')}" + distributionType = Wrapper.DistributionType.ALL +} + +ext { + user = 'jprante' + name = 'datastructures' + description = 'Data structures for Java' + inceptionYear = '2012' + url = 'https://github.com/' + user + '/' + name + scmUrl = 'https://github.com/' + user + '/' + name + scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git' + scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git' + issueManagementSystem = 'Github' + issueManagementUrl = ext.scmUrl + '/issues' + licenseName = 'The Apache License, Version 2.0' + licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' +} + +subprojects { + apply plugin: 'java-library' + apply from: rootProject.file('gradle/ide/idea.gradle') + apply from: rootProject.file('gradle/compile/java.gradle') + apply from: rootProject.file('gradle/test/junit5.gradle') + apply from: rootProject.file('gradle/publishing/publication.gradle') +} + +apply from: rootProject.file('gradle/publishing/sonatype.gradle') diff --git a/datastructures-common/src/main/java/module-info.java b/datastructures-common/src/main/java/module-info.java new file mode 100644 index 0000000..2a46321 --- /dev/null +++ b/datastructures-common/src/main/java/module-info.java @@ -0,0 +1,3 @@ +module org.xbib.datastructures.common { + exports org.xbib.datastructures.common; +} \ No newline at end of file diff --git a/datastructures-common/src/main/java/org/xbib/datastructures/common/AbstractMultiMap.java b/datastructures-common/src/main/java/org/xbib/datastructures/common/AbstractMultiMap.java new file mode 100644 index 0000000..d93438d --- /dev/null +++ b/datastructures-common/src/main/java/org/xbib/datastructures/common/AbstractMultiMap.java @@ -0,0 +1,207 @@ +package org.xbib.datastructures.common; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Abstract multi map. + * + * @param the key type parameter + * @param the value type parameter + */ +public abstract class AbstractMultiMap implements MultiMap { + + private final Map> map; + + public AbstractMultiMap() { + this(null); + } + + public AbstractMultiMap(MultiMap map) { + this.map = newMap(); + if (map != null) { + putAll(map); + } + } + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(K key) { + return map.containsKey(key); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public boolean put(K key, V value) { + Collection set = map.get(key); + if (set == null) { + set = newValues(); + set.add(value); + map.put(key, set); + return true; + } else { + set.add(value); + return false; + } + } + + @Override + public void putAll(K key, Iterable values) { + if (values == null) { + return; + } + Collection set = map.computeIfAbsent(key, k -> newValues()); + for (V v : values) { + set.add(v); + } + } + + @Override + public Collection get(K key) { + return map.get(key); + } + + @Override + public Collection remove(K key) { + return map.remove(key); + } + + @Override + public boolean remove(K key, V value) { + Collection set = map.get(key); + return set != null && set.remove(value); + } + + @Override + public void putAll(MultiMap map) { + if (map != null) { + for (K key : map.keySet()) { + putAll(key, map.get(key)); + } + } + } + + @Override + public Map> asMap() { + return map; + } + + @Override + public String getString(K key) { + Collection v = get(key); + return v != null ? v.iterator().next().toString() : null; + } + + @Override + public String getString(K key, String defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? v.toString() : defaultValue; + } + + @Override + public Boolean getBoolean(K key, boolean defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Boolean.parseBoolean(v.toString()) : defaultValue; + } + + @Override + public Short getShort(K key, short defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Short.parseShort(v.toString()) : defaultValue; + } + + @Override + public Integer getInteger(K key, int defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Integer.parseInt(v.toString()) : defaultValue; + } + + @Override + public Long getLong(K key, long defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Long.parseLong(v.toString()) : defaultValue; + } + + @Override + public Float getFloat(K key, float defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Float.parseFloat(v.toString()) : defaultValue; + } + + @Override + public Double getDouble(K key, double defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Double.parseDouble(v.toString()) : defaultValue; + } + + @Override + public BigDecimal getBigDecimal(K key, BigDecimal defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? new BigDecimal(v.toString()) : defaultValue; + } + + @Override + public BigInteger getBigInteger(K key, BigInteger defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? new BigInteger(v.toString()) : defaultValue; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AbstractMultiMap && map.equals(((AbstractMultiMap) obj).map); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public String toString() { + return map.toString(); + } + + protected abstract Collection newValues(); + + protected abstract Map> newMap(); +} diff --git a/datastructures-common/src/main/java/org/xbib/datastructures/common/LinkedHashSetMultiMap.java b/datastructures-common/src/main/java/org/xbib/datastructures/common/LinkedHashSetMultiMap.java new file mode 100644 index 0000000..46515bd --- /dev/null +++ b/datastructures-common/src/main/java/org/xbib/datastructures/common/LinkedHashSetMultiMap.java @@ -0,0 +1,33 @@ +package org.xbib.datastructures.common; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; + +/** + * Linked multi map. + * + * @param the key type parameter + * @param the value type parameter + */ +public class LinkedHashSetMultiMap extends AbstractMultiMap { + + public LinkedHashSetMultiMap() { + super(); + } + + public LinkedHashSetMultiMap(MultiMap map) { + super(map); + } + + @Override + protected Collection newValues() { + return new LinkedHashSet<>(); + } + + @Override + protected Map> newMap() { + return new LinkedHashMap<>(); + } +} diff --git a/datastructures-common/src/main/java/org/xbib/datastructures/common/Maps.java b/datastructures-common/src/main/java/org/xbib/datastructures/common/Maps.java new file mode 100644 index 0000000..12ad6cd --- /dev/null +++ b/datastructures-common/src/main/java/org/xbib/datastructures/common/Maps.java @@ -0,0 +1,112 @@ +package org.xbib.datastructures.common; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * + */ +public class Maps { + + private Maps() { + } + + @SuppressWarnings({"unchecked"}) + public static Map deepMerge(Map map, Map newMap) { + for (Map.Entry e : newMap.entrySet()) { + String key = e.getKey(); + Object value = e.getValue(); + if (map.containsKey(key)) { + Object originalValue = map.get(key); + if (originalValue instanceof Collection && value instanceof Collection) { + ((Collection) originalValue).addAll((Collection) value); + } else if (originalValue instanceof Map && value instanceof Map) { + deepMerge((Map) originalValue, (Map) value); + } + } else { + map.put(key, value); + } + } + return map; + } + + public static String getString(Map map, String key) { + Object object = get(map, key); + if (object instanceof List) { + return ((List) object).get(0).toString(); + } + if (object instanceof Map) { + return null; + } + return (String) object; + } + + public static Integer getInteger(Map map, String key, Integer defaultValue) { + if (map.containsKey(key)) { + try { + Object o = get(map, key); + return o == null ? null : o instanceof Integer ? (Integer) o : Integer.parseInt(o.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } else { + return defaultValue; + } + } + + public static Boolean getBoolean(Map map, String key, Boolean defaultValue) { + if (map.containsKey(key)) { + Object o = get(map, key); + return o == null ? null : o instanceof Boolean ? (Boolean) o : Boolean.parseBoolean(o.toString()); + } else { + return defaultValue; + } + } + + private static T get(Map map, String key) { + return get(map, key.split("\\.")); + } + + @SuppressWarnings("unchecked") + private static T get(Map map, String[] keys) { + if (map == null) { + return null; + } + String key = keys[0]; + Object o = map.get(key); + if (o == null) { + return null; + } + if (!(o instanceof List)) { + o = Collections.singletonList(o); + } + List list = (List) o; + if (keys.length == 1) { + return (T) list.get(0); + } + for (Object oo : list) { + if (oo instanceof Map) { + Map m = (Map) oo; + if (keys.length == 2) { + if (m.containsKey(keys[1])) { + return (T) m.get(keys[1]); + } + } else { + Object ooo = get(m, Arrays.copyOfRange(keys, 1, keys.length)); + if (ooo != null) { + return (T) ooo; + } + } + } else if (oo instanceof List) { + List l = (List) oo; + return (T) l.get(0); + } else { + return (T) oo; + } + } + return null; + } +} diff --git a/datastructures-common/src/main/java/org/xbib/datastructures/common/MultiMap.java b/datastructures-common/src/main/java/org/xbib/datastructures/common/MultiMap.java new file mode 100644 index 0000000..a924acc --- /dev/null +++ b/datastructures-common/src/main/java/org/xbib/datastructures/common/MultiMap.java @@ -0,0 +1,60 @@ +package org.xbib.datastructures.common; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * MultiMap interface. + * + * @param the key type parameter + * @param the value type parameter + */ +public interface MultiMap { + + void clear(); + + int size(); + + boolean isEmpty(); + + boolean containsKey(K key); + + Collection get(K key); + + Set keySet(); + + boolean put(K key, V value); + + void putAll(K key, Iterable values); + + void putAll(MultiMap map); + + Collection remove(K key); + + boolean remove(K key, V value); + + Map> asMap(); + + String getString(K key); + + String getString(K key, String defaultValue); + + Boolean getBoolean(K key, boolean defaultValue); + + Short getShort(K key, short defaultValue); + + Integer getInteger(K key, int defaultValue); + + Long getLong(K key, long defaultValue); + + Float getFloat(K key, float defaultValue); + + Double getDouble(K key, double defaultValue); + + BigDecimal getBigDecimal(K key, BigDecimal defaultValue); + + BigInteger getBigInteger(K key, BigInteger defaultValue); +} diff --git a/datastructures-common/src/test/java/org/xbib/datastructures/common/MapsTest.java b/datastructures-common/src/test/java/org/xbib/datastructures/common/MapsTest.java new file mode 100644 index 0000000..fc2dd9e --- /dev/null +++ b/datastructures-common/src/test/java/org/xbib/datastructures/common/MapsTest.java @@ -0,0 +1,26 @@ +package org.xbib.datastructures.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * + */ +public class MapsTest { + + @Test + public void testDeepMerge() { + Map m1 = new LinkedHashMap<>(); + Map m2 = new LinkedHashMap<>(); + Map m3 = new LinkedHashMap<>(); + Map m4 = new LinkedHashMap<>(); + m1.put("a", "b"); + m2.put("c", m1); + m3.put("d", "e"); + m4.put("e", "f"); + m3.put("c", m4); + assertEquals("{d=e, c={e=f, a=b}}", Maps.deepMerge(m3, m2).toString()); + } +} diff --git a/datastructures-tiny/NOTICE.txt b/datastructures-tiny/NOTICE.txt new file mode 100644 index 0000000..f11db3c --- /dev/null +++ b/datastructures-tiny/NOTICE.txt @@ -0,0 +1,13 @@ + +This implementation is a subset and simplified version of + +https://github.com/intelie/tinymap + +(master branch, as of 2020-09-12, Apache 2.0 license) + +- Serialization removed +- Object reuse removed +- no TinyList +- no JSON parser +- Builder classes moved to internal classes +- multi map added \ No newline at end of file diff --git a/datastructures-tiny/build.gradle b/datastructures-tiny/build.gradle new file mode 100644 index 0000000..deb931e --- /dev/null +++ b/datastructures-tiny/build.gradle @@ -0,0 +1,3 @@ +dependencies { + api project(':datastructures-common') +} \ No newline at end of file diff --git a/datastructures-tiny/src/main/java/module-info.java b/datastructures-tiny/src/main/java/module-info.java new file mode 100644 index 0000000..7f6dc03 --- /dev/null +++ b/datastructures-tiny/src/main/java/module-info.java @@ -0,0 +1,4 @@ +module org.xbib.datastructures.tiny { + exports org.xbib.datastructures.tiny; + requires transitive org.xbib.datastructures.common; +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedCollection.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedCollection.java new file mode 100644 index 0000000..edb747f --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedCollection.java @@ -0,0 +1,28 @@ +package org.xbib.datastructures.tiny; + +import java.util.Collection; +import java.util.ListIterator; + +public interface IndexedCollection extends Collection { + + int addOrGetIndex(T obj); + + void add(int index, T obj); + + T set(int index, T obj); + + int getIndex(Object key); + + T getEntryAt(int index); + + boolean removeAt(int index); + + boolean isRemoved(int index); + + int rawSize(); + + @Override + ListIterator iterator(); + + ListIterator iterator(int fromIndex); +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedCollectionBase.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedCollectionBase.java new file mode 100644 index 0000000..d334406 --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedCollectionBase.java @@ -0,0 +1,190 @@ +package org.xbib.datastructures.tiny; + +import java.util.AbstractCollection; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; + +public abstract class IndexedCollectionBase extends AbstractCollection implements IndexedCollection { + + @Override + public int getIndex(Object key) { + for (int i = 0; i < rawSize(); i++) { + if (!isRemoved(i) && Objects.equals(key, getEntryAt(i))) { + return i; + } + } + return -1; + } + + @Override + public void clear() { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return getIndex(o) >= 0; + } + + @Override + public ListIterator iterator() { + return iterator(0); + } + + @Override + public ListIterator iterator(int fromIndex) { + return new CollectionIterator(fromIndex); + } + + @Override + public void forEach(Consumer action) { + for (int i = 0; i < rawSize(); i++) { + if (!isRemoved(i)) { + action.accept(getEntryAt(i)); + } + } + } + + @Override + public boolean add(T obj) { + return addOrGetIndex(obj) < 0; + } + + @Override + public boolean remove(Object o) { + int index = getIndex(o); + if (index < 0) { + return false; + } + removeAt(index); + return true; + } + + public interface NoAdditiveChange extends IndexedCollection { + @Override + default int addOrGetIndex(T obj) { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + default void add(int index, T obj) { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + default T set(int index, T obj) { + throw new UnsupportedOperationException("modification not supported: " + this); + } + } + + public interface Immutable extends NoAdditiveChange { + @Override + default boolean removeAt(int index) { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + default boolean isRemoved(int index) { + return false; + } + + @Override + default int rawSize() { + return size(); + } + } + + private class CollectionIterator implements ListIterator { + private int current; + private int next; + private int prev; + + public CollectionIterator(int fromIndex) { + this.current = -1; + this.next = findNext(fromIndex); + this.prev = findPrev(fromIndex - 1); + } + + private int findNext(int index) { + while (index < rawSize() && isRemoved(index)) { + index++; + } + return index; + } + + private int findPrev(int index) { + while (index >= 0 && isRemoved(index)) { + index--; + } + return index; + } + + @Override + public boolean hasNext() { + return next < rawSize(); + } + + @Override + public boolean hasPrevious() { + return prev >= 0; + } + + @Override + public int nextIndex() { + return next; + } + + @Override + public int previousIndex() { + return prev; + } + + @Override + public void set(T obj) { + Preconditions.checkState(current >= 0, "no iteration occurred"); + IndexedCollectionBase.this.set(current, obj); + } + + @Override + public void add(T obj) { + IndexedCollectionBase.this.add(next++, obj); + current = -1; + } + + @Override + public void remove() { + Preconditions.checkState(current >= 0, "no iteration occurred"); + if (removeAt(current)) { + next--; + } + current = -1; + } + + @Override + public T previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + next = current = prev; + prev = findPrev(prev - 1); + return getEntryAt(current); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + prev = current = next; + next = findNext(next + 1); + return getEntryAt(current); + } + } +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedMap.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedMap.java new file mode 100644 index 0000000..e90b690 --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedMap.java @@ -0,0 +1,36 @@ +package org.xbib.datastructures.tiny; + +import java.util.Map; + +public interface IndexedMap extends Map { + + int getIndex(Object key); + + K getKeyAt(int index); + + V getValueAt(int index); + + Entry getEntryAt(int index); + + V removeAt(int index); + + V setValueAt(int index, V value); + + boolean isRemoved(int index); + + int rawSize(); + + Object getUnsafe(Object key, Object defaultValue); + + @Override + IndexedSet keySet(); + + @Override + IndexedSet> entrySet(); + + interface Entry extends Map.Entry { + int getIndex(); + + boolean isRemoved(); + } +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedMapBase.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedMapBase.java new file mode 100644 index 0000000..50ea014 --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedMapBase.java @@ -0,0 +1,352 @@ +package org.xbib.datastructures.tiny; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; + +public abstract class IndexedMapBase implements IndexedMap { + private static final Object SENTINEL = new Object(); + + @Override + public V getOrDefault(Object key, V defaultValue) { + int index = getIndex(key); + if (index < 0) { + return defaultValue; + } + return getValueAt(index); + } + + @Override + public V get(Object key) { + int index = getIndex(key); + if (index < 0) { + return null; + } + return getValueAt(index); + } + + @Override + public boolean containsKey(Object key) { + return getUnsafe(key, SENTINEL) != SENTINEL; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsValue(Object value) { + for (int i = 0; i < rawSize(); i++) { + if (!isRemoved(i) && Objects.equals(value, getValueAt(i))) { + return true; + } + } + return false; + } + + @Override + public Entry getEntryAt(int index) { + Preconditions.checkElementIndex(index, rawSize()); + return new IndexedEntry(index); + } + + @Override + public void forEach(BiConsumer action) { + int size = rawSize(); + for (int i = 0; i < size; i++) { + if (!isRemoved(i)) { + action.accept(getKeyAt(i), getValueAt(i)); + } + } + } + + @Override + public V removeAt(int index) { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + public V setValueAt(int index, V value) { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + public boolean isRemoved(int index) { + return false; + } + + @Override + public int rawSize() { + return size(); + } + + @Override + public Object getUnsafe(Object key, Object defaultValue) { + int index = getIndex(key); + if (index < 0) { + return defaultValue; + } + return getValueAt(index); + } + + @Override + public V put(K key, V value) { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + public V remove(Object key) { + int index = getIndex(key); + if (index < 0) { + return null; + } + return removeAt(index); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("modification not supported: " + this); + } + + @Override + public void putAll(Map m) { + m.forEach(this::put); + } + + @Override + public IndexedSet keySet() { + return new KeysView(); + } + + @Override + public Collection values() { + return new ValuesView(); + } + + @Override + public IndexedSet> entrySet() { + return new EntriesView(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Map) || size() != ((Map) o).size()) { + return false; + } + for (Map.Entry entry : ((Map) o).entrySet()) { + if (!Objects.equals(entry.getValue(), getUnsafe(entry.getKey(), SENTINEL))) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < rawSize(); i++) { + if (!isRemoved(i)) { + hash += Objects.hashCode(getKeyAt(i)) ^ Objects.hashCode(getValueAt(i)); + } + } + return hash; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder().append('{'); + boolean first = true; + for (int i = 0; i < rawSize(); i++) { + if (isRemoved(i)) { + continue; + } + if (!first) { + sb.append(", "); + } + first = false; + sb.append(getKeyAt(i)).append('=').append(getValueAt(i)); + } + return sb.append('}').toString(); + } + + private class ValuesView extends IndexedCollectionBase implements IndexedCollectionBase.NoAdditiveChange { + + @Override + public V getEntryAt(int index) { + return getValueAt(index); + } + + @Override + public void clear() { + IndexedMapBase.this.clear(); + } + + @Override + public boolean removeAt(int index) { + IndexedMapBase.this.removeAt(index); + return false; + } + + @Override + public boolean isRemoved(int index) { + return IndexedMapBase.this.isRemoved(index); + } + + @Override + public int rawSize() { + return IndexedMapBase.this.rawSize(); + } + + @Override + public int size() { + return IndexedMapBase.this.size(); + } + } + + private class KeysView extends IndexedSetBase implements IndexedCollectionBase.NoAdditiveChange { + + @Override + public int getIndex(Object key) { + return IndexedMapBase.this.getIndex(key); + } + + @Override + public K getEntryAt(int index) { + return getKeyAt(index); + } + + @Override + public void clear() { + IndexedMapBase.this.clear(); + } + + @Override + public boolean removeAt(int index) { + IndexedMapBase.this.removeAt(index); + return false; + } + + @Override + public boolean isRemoved(int index) { + return IndexedMapBase.this.isRemoved(index); + } + + @Override + public int rawSize() { + return IndexedMapBase.this.rawSize(); + } + + @Override + public int size() { + return IndexedMapBase.this.size(); + } + } + + private class EntriesView extends IndexedSetBase> implements IndexedCollectionBase.NoAdditiveChange> { + + @Override + public int getIndex(Object key) { + if (!(key instanceof Map.Entry)) { + return -1; + } + Map.Entry entry = (Map.Entry) key; + int index = IndexedMapBase.this.getIndex(entry.getKey()); + if (index < 0 || Objects.equals(entry.getValue(), getValueAt(index))) { + return index; + } + return -1; + } + + @Override + public Entry getEntryAt(int index) { + return IndexedMapBase.this.getEntryAt(index); + } + + @Override + public void clear() { + IndexedMapBase.this.clear(); + } + + @Override + public boolean removeAt(int index) { + IndexedMapBase.this.removeAt(index); + return false; + } + + @Override + public boolean isRemoved(int index) { + return IndexedMapBase.this.isRemoved(index); + } + + @Override + public int rawSize() { + return IndexedMapBase.this.rawSize(); + } + + @Override + public int size() { + return IndexedMapBase.this.size(); + } + } + + private class IndexedEntry implements Entry { + + private final int index; + + public IndexedEntry(int index) { + this.index = index; + } + + @Override + public K getKey() { + return getKeyAt(index); + } + + @Override + public V getValue() { + return getValueAt(index); + } + + @Override + public V setValue(V value) { + return setValueAt(index, value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry that = (Map.Entry) o; + return Objects.equals(that.getKey(), getKey()) && Objects.equals(that.getValue(), getValue()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getKeyAt(index)) ^ Objects.hashCode(getValueAt(index)); + } + + @Override + public String toString() { + return getKey() + "=" + getValue(); + } + + @Override + public int getIndex() { + return index; + } + + @Override + public boolean isRemoved() { + return IndexedMapBase.this.isRemoved(index); + } + } +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedSet.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedSet.java new file mode 100644 index 0000000..ef9dbe8 --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedSet.java @@ -0,0 +1,6 @@ +package org.xbib.datastructures.tiny; + +import java.util.Set; + +public interface IndexedSet extends Set, IndexedCollection { +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedSetBase.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedSetBase.java new file mode 100644 index 0000000..ba12938 --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/IndexedSetBase.java @@ -0,0 +1,34 @@ +package org.xbib.datastructures.tiny; + +import java.util.Objects; +import java.util.Set; + +public abstract class IndexedSetBase extends IndexedCollectionBase implements IndexedSet { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Set) || size() != ((Set) o).size()) { + return false; + } + for (Object obj : ((Set) o)) { + if (getIndex(obj) < 0) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hash = 0; + for (int i = 0; i < rawSize(); i++) { + if (!isRemoved(i)) { + hash += Objects.hashCode(getEntryAt(i)); + } + } + return hash; + } +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/Preconditions.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/Preconditions.java new file mode 100644 index 0000000..193a374 --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/Preconditions.java @@ -0,0 +1,67 @@ +package org.xbib.datastructures.tiny; + +public abstract class Preconditions { + + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + public static void checkArgument(boolean expression, + String errorMessageTemplate, + Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); + } + } + + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + public static void checkElementIndex(int index, int size) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(badElementIndex(index, size)); + } + } + + private static String badElementIndex(int index, int size) { + if (index < 0) { + return format("index (%s) must not be negative", index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { + return format("index (%s) must be less than size (%s)", index, size); + } + } + + public static String format(String template, Object... args) { + template = String.valueOf(template); + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + return builder.toString(); + } +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinyMap.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinyMap.java new file mode 100644 index 0000000..031b89e --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinyMap.java @@ -0,0 +1,176 @@ +package org.xbib.datastructures.tiny; + +import java.util.Arrays; + +public abstract class TinyMap extends IndexedMapBase { + + private final TinySet keys; + + protected TinyMap(TinySet keys) { + this.keys = keys; + } + + public static Builder builder() { + return new Builder<>(); + } + + @Override + public K getKeyAt(int index) { + return keys.getEntryAt(index); + } + + @Override + public int size() { + return keys.size(); + } + + @Override + public int getIndex(Object key) { + return keys.getIndex(key); + } + + @Override + public TinySet keySet() { + return keys; + } + + public static final Object TOMBSTONE = new Object() { + @Override + public String toString() { + return "TOMBSTONE"; + } + }; + + public static class Builder extends IndexedMapBase { + + private final TinySet.Builder keys; + + private Object[] values; + + private Builder() { + this(16); + } + + private Builder(int expectedSize) { + values = new Object[expectedSize]; + keys = new TinySet.Builder<>(expectedSize) { + + @Override + public void compact() { + if (size() == rawSize()) { + return; + } + int index = 0; + int rawSize = rawSize(); + for (int i = 0; i < rawSize; i++) { + if (values[i] == TOMBSTONE) { + continue; + } + values[index++] = values[i]; + } + Arrays.fill(values, index, rawSize, null); + super.compact(); + } + }; + } + + public void compact() { + keys.compact(); + } + + public V put(K key, V value) { + int index = keys.addOrGetIndex(key); + if (index >= 0) { + return setValueAt(index, value); + } + index = ~index; + if (index >= values.length) { + values = Arrays.copyOf(values, values.length + (values.length >> 1)); + } + values[index] = value; + return null; + } + + @Override + public int getIndex(Object key) { + return keys.getIndex(key); + } + + @Override + public K getKeyAt(int index) { + return keys.getEntryAt(index); + } + + @SuppressWarnings("unchecked") + @Override + public V getValueAt(int index) { + Preconditions.checkElementIndex(index, rawSize()); + return (V) values[index]; + } + + @SuppressWarnings("unchecked") + @Override + public V setValueAt(int index, V value) { + Preconditions.checkElementIndex(index, rawSize()); + Object old = values[index]; + values[index] = value; + return (V) old; + } + + @SuppressWarnings("unchecked") + @Override + public V removeAt(int index) { + keys.removeAt(index); + Object old = values[index]; + values[index] = TOMBSTONE; + return (V) old; + } + + @Override + public boolean isRemoved(int index) { + return keys.isRemoved(index); + } + + public int size() { + return keys.size(); + } + + @Override + public int rawSize() { + return keys.rawSize(); + } + + public TinyMap build() { + return buildWithKeys(this.keys.build()); + } + + public TinyMap buildWithKeys(TinySet keys) { + compact(); + Preconditions.checkArgument(keys.size() == size(), "Must have same size"); + return new SizeAny<>(keys, Arrays.copyOf(values, keys.size())); + } + + @Override + public void clear() { + Arrays.fill(values, 0, keys.rawSize(), null); + keys.clear(); + } + } + + public static class SizeAny extends TinyMap { + + public final Object[] values; + + public SizeAny(TinySet keys, Object[] values) { + super(keys); + Preconditions.checkArgument(keys.size() == values.length, "keys and values must have same size"); + this.values = values; + } + + @SuppressWarnings("unchecked") + @Override + public V getValueAt(int index) { + return (V) values[index]; + } + } +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinyMultiMap.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinyMultiMap.java new file mode 100644 index 0000000..b31dc2d --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinyMultiMap.java @@ -0,0 +1,33 @@ +package org.xbib.datastructures.tiny; + +import org.xbib.datastructures.common.AbstractMultiMap; +import org.xbib.datastructures.common.MultiMap; +import java.util.Collection; +import java.util.Map; + +/** + * Tiny multi map. + * + * @param the key type parameter + * @param the value type parameter + */ +public class TinyMultiMap extends AbstractMultiMap { + + public TinyMultiMap() { + super(); + } + + public TinyMultiMap(MultiMap map) { + super(map); + } + + @Override + protected Collection newValues() { + return TinySet.builder(); + } + + @Override + protected Map> newMap() { + return TinyMap.builder(); + } +} diff --git a/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinySet.java b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinySet.java new file mode 100644 index 0000000..01419a9 --- /dev/null +++ b/datastructures-tiny/src/main/java/org/xbib/datastructures/tiny/TinySet.java @@ -0,0 +1,340 @@ +package org.xbib.datastructures.tiny; + +import java.util.Arrays; +import java.util.Objects; + +public abstract class TinySet extends IndexedSetBase { + + public static int tableSize(int length) { + return Integer.highestOneBit(length * 2 - 1) * 2; + } + + private static int hash(Object key) { + int h; + return key == null ? 0 : (h = key.hashCode() * 0x85ebca6b) ^ h >>> 16; + } + + public static TinySet createUnsafe(Object[] keys) { + if (keys.length == 0) { + return new Empty<>(); + } else if (keys.length < 0xFF) { + return new Small<>(keys); + } else if (keys.length < 0xFFFF) { + return new Medium<>(keys); + } else { + return new Large<>(keys); + } + } + + public static Builder builder() { + return new Builder<>(); + } + + public static class Empty extends TinySet implements Immutable { + + @Override + public int getIndex(Object key) { + return -1; + } + + @Override + public int size() { + return 0; + } + + @Override + public T getEntryAt(int index) { + throw new ArrayIndexOutOfBoundsException(index); + } + } + + private static abstract class ArrayTableSet extends TinySet implements Immutable { + + protected final Object[] keys; + + protected final A table; + + private ArrayTableSet(Object[] keys) { + this.keys = keys; + this.table = newTable(tableSize(keys.length)); + for (int j = 0; j < keys.length; j++) { + Object key = keys[j]; + int hash = ~getIndex(key); + Preconditions.checkArgument(hash >= 0, "duplicate key: %s", key); + tableSet(table, hash, j); + } + } + + @SuppressWarnings("unchecked") + @Override + public T getEntryAt(int index) { + return (T) keys[index]; + } + + @Override + public int size() { + return keys.length; + } + + protected abstract A newTable(int size); + + protected abstract void tableSet(A table, int index, int value); + } + + public static class Small extends ArrayTableSet { + + private Small(Object[] keys) { + super(keys); + } + + @Override + protected byte[] newTable(int size) { + byte[] table = new byte[size]; + Arrays.fill(table, (byte) 0xFF); + return table; + } + + @Override + protected void tableSet(byte[] table, int index, int value) { + table[index] = (byte) value; + } + + @Override + public int getIndex(Object key) { + byte[] table = this.table; + int mask = table.length - 1; + int hash = hash(key) & mask; + int collisions = 0; + for (int i = table[hash] & 0xFF; i < 0xFF; i = table[hash = (hash + ++collisions) & mask] & 0xFF) { + if (Objects.equals(key, keys[i])) { + return i; + } + } + return ~hash; + } + } + + public static class Medium extends ArrayTableSet { + + private Medium(Object[] keys) { + super(keys); + } + + @Override + protected short[] newTable(int size) { + short[] table = new short[size]; + Arrays.fill(table, (short) 0xFFFF); + return table; + } + + @Override + protected void tableSet(short[] table, int index, int value) { + table[index] = (short) value; + } + + @Override + public int getIndex(Object key) { + short[] table = this.table; + int mask = table.length - 1; + int hash = hash(key) & mask; + int collisions = 0; + for (int i = table[hash] & 0xFFFF; i < 0xFFFF; i = table[hash = (hash + ++collisions) & mask] & 0xFFFF) { + if (Objects.equals(key, keys[i])) { + return i; + } + } + return ~hash; + } + } + + public static class Large extends ArrayTableSet { + + private Large(Object[] keys) { + super(keys); + } + + @Override + protected int[] newTable(int size) { + int[] table = new int[size]; + Arrays.fill(table, -1); + return table; + } + + @Override + protected void tableSet(int[] table, int index, int value) { + table[index] = value; + } + + @Override + public int getIndex(Object key) { + int[] table = this.table; + int mask = table.length - 1; + int hash = hash(key) & mask; + int collisions = 0; + for (int i = table[hash]; i >= 0; i = table[hash = (hash + ++collisions) & mask]) { + if (Objects.equals(key, keys[i])) { + return i; + } + } + return ~hash; + } + } + + public static class Builder extends IndexedSetBase implements IndexedCollectionBase.NoAdditiveChange { + + private static final Object TOMBSTONE = new Object() { + + @Override + public String toString() { + return "TOMBSTONE"; + } + }; + + private Object[] keys; + + private int[] inverse; + + private int[] table; + + private int rawSize = 0; + + private int size = 0; + + Builder() { + this(16); + } + + Builder(int expectedSize) { + this.keys = new Object[expectedSize]; + this.inverse = new int[expectedSize]; + forceRehash(TinySet.tableSize(expectedSize)); + } + + private static int hash(Object key) { + int h; + return key == null ? 0 : (h = key.hashCode() * 0x85ebca6b) ^ h >>> 16; + } + + private static int[] newTable(int size) { + int[] table = new int[size]; + Arrays.fill(table, -1); + return table; + } + + public void compact() { + if (rawSize == size) { + return; + } + softClearTable(); + int index = 0; + for (int i = 0; i < rawSize; i++) { + if (keys[i] == TOMBSTONE) { + continue; + } + keys[index] = keys[i]; + int hash = ~getIndex(keys[index]); + table[hash] = index; + inverse[index] = hash; + index++; + } + Arrays.fill(keys, index, rawSize, null); + this.size = index; + this.rawSize = index; + } + + private void forceRehash(int newSize) { + this.table = newTable(newSize); + this.size = 0; + compact(); + } + + private void softClearTable() { + for (int i = 0; i < rawSize; i++) { + table[inverse[i]] = -1; + } + } + + @SuppressWarnings("unchecked") + @Override + public T getEntryAt(int index) { + Preconditions.checkElementIndex(index, rawSize); + return (T) keys[index]; + } + + @Override + public int addOrGetIndex(T key) { + int index = getIndex(key); + if (index >= 0) { + return index; + } + index = checkOverflow(key, index); + int hash = ~index; + int newIndex = rawSize++; + keys[newIndex] = key; + table[hash] = newIndex; + inverse[newIndex] = hash; + size++; + return ~newIndex; + } + + private int checkOverflow(T key, int index) { + if (rawSize == keys.length) { + int newSize = keys.length + (keys.length >> 1); + keys = Arrays.copyOf(keys, newSize); + inverse = Arrays.copyOf(inverse, newSize); + } + if (2 * (rawSize + 1) > table.length) { + forceRehash(table.length * 2); + index = getIndex(key); + } + return index; + } + + @Override + public int getIndex(Object key) { + int collisions = 0; + int mask = table.length - 1; + int hash = hash(key) & mask; + for (int i = table[hash]; i >= 0; i = table[hash = (hash + ++collisions) & mask]) { + if (Objects.equals(key, keys[i])) { + return i; + } + } + return ~hash; + } + + @Override + public boolean removeAt(int index) { + Preconditions.checkElementIndex(index, rawSize); + keys[index] = TOMBSTONE; + size--; + return false; + } + + @Override + public boolean isRemoved(int index) { + return keys[index] == TOMBSTONE; + } + + public int size() { + return size; + } + + @Override + public int rawSize() { + return rawSize; + } + + public TinySet build() { + compact(); + return TinySet.createUnsafe(Arrays.copyOf(keys, size)); + } + + @Override + public void clear() { + Arrays.fill(keys, 0, rawSize, null); + softClearTable(); + size = rawSize = 0; + } + } +} diff --git a/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinyMapTest.java b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinyMapTest.java new file mode 100644 index 0000000..41b1f2b --- /dev/null +++ b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinyMapTest.java @@ -0,0 +1,179 @@ +package org.xbib.datastructures.tiny; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TinyMapTest { + + private static final Logger logger = Logger.getLogger(TinyMapTest.class.getName()); + + @Test + public void simpleTest() { + TinyMap.Builder builder = TinyMap.builder(); + assertEquals(0, builder.size()); + builder.put("a", 1); + builder.put("b", 2); + builder.put("c", 3); + logger.log(Level.INFO, builder.build().toString()); + } + + @Test + public void testBuildAndGet() { + TinyMap.Builder builder = TinyMap.builder(); + assertEquals(0, builder.size()); + builder.put("aaa", 333); + builder.put("bbb", 456.0); + builder.put("aaa", 123); + assertEquals(2, builder.size()); + TinyMap map = builder.build(); + assertEquals(123, map.get("aaa")); + assertEquals(456.0, map.get("bbb")); + assertNull(map.get("ccc")); + assertEquals("def", map.getOrDefault("ccc", "def")); + assertEquals("def", map.getOrDefault(null, "def")); + assertEquals(2, map.size()); + assertTrue(map.containsKey("aaa")); + assertTrue(map.containsKey("bbb")); + } + + @Test + public void canBuildWithDuplicateKeys() { + TinyMap.Builder builder = TinyMap.builder(); + builder.put("aaa", 123); + builder.put("aaa", 456.0); + builder.put("bbb", 789.0); + assertThat(builder.size(), equalTo(2)); + assertThat(builder.build(), equalTo(Map.of("aaa", 456.0, "bbb", 789.0))); + assertThat(builder.size(), equalTo(2)); + assertThat(builder.build(), equalTo(Map.of("aaa", 456.0, "bbb", 789.0))); + } + + @Test + public void canBuildWithNull() { + TinyMap.Builder builder = TinyMap.builder(); + builder.put(null, 123); + assertThat(builder.build(), equalTo(Collections.singletonMap(null, 123))); + } + + @Test + public void canBuildMediumWithDuplicateKeys() { + TinyMap.Builder builder = TinyMap.builder(); + builder.put("aaa", 123); + builder.put("aaa", 456.0); + for (int i = 0; i < 1000; i++) { + builder.put("aaa" + i, i); + } + assertThat(builder.build().size(), equalTo(1001)); + } + + @Test + public void canBuildLargeWithDuplicateKeys() { + TinyMap.Builder builder = TinyMap.builder(); + builder.put("aaa", 123); + builder.put("aaa", 456.0); + for (int i = 0; i < 0x10000; i++) { + builder.put("aaa" + i, i); + } + assertThat(builder.build().size(), equalTo(65537)); + } + + @Test + public void testContains() { + TinyMap.Builder builder = TinyMap.builder(); + builder.put("aaa", null); + builder.put("bbb", 456.0); + TinyMap map = builder.build(); + assertTrue(map.containsKey("aaa")); + assertFalse(map.containsKey("ccc")); + assertTrue(map.containsValue(null)); + assertFalse(map.containsValue(123.0)); + assertTrue(map.containsKey("aaa")); + assertFalse(map.containsKey("ccc")); + assertTrue(map.containsValue(null)); + assertFalse(map.containsValue(123.0)); + assertTrue(map.entrySet().contains(new AbstractMap.SimpleEntry<>("aaa", null))); + assertFalse(map.entrySet().contains(new AbstractMap.SimpleEntry<>("aaa", 123.0))); + assertFalse(map.entrySet().contains(new AbstractMap.SimpleEntry<>("ccc", null))); + assertFalse(map.entrySet().contains(new Object())); + } + + @Test + public void testBuildSmallEnough() throws Exception { + testCount(0, true); + + for (int i = 0; i <= 16; i++) { + testCount(i, false); + } + } + + @Test + public void testBuildMedium() throws Exception { + testCount(1000, false); + testCount(1000, true); + } + + @Test + public void testBuildAlmostThere() throws Exception { + testCount(255, false); + testCount(255, true); + } + + @Test + public void testBuildSmall() throws Exception { + testCount(123, false); + testCount(123, true); + } + + @Test + public void testValueArrayTwoDifferentMaps() { + TinyMap.Builder builder1 = TinyMap.builder(); + TinyMap.Builder builder2 = TinyMap.builder(); + for (int i = 0; i < 100; i++) { + builder1.put("aaa" + i, i); + builder2.put("aaa" + i, i); + } + TinyMap map1 = builder1.build(); + TinyMap map2 = builder2.build(); + assertThat(map1.keySet(), equalTo(map2.keySet())); + } + + @Test + public void testGiantShortProblem() { + TinyMap.Builder builder = TinyMap.builder(); + for (int i = 0; i < 100000; i++) { + builder.put("aaa" + i, i); + } + TinyMap map = builder.build(); + assertEquals(99999, map.get("aaa99999")); + } + + private void testCount(int count, boolean withNull) { + TinyMap.Builder builder = TinyMap.builder(); + LinkedHashMap expectedMap = new LinkedHashMap<>(); + for (int i = 0; i < count; i++) { + if (count < 1000) + builder.build(); + + builder.putAll(Collections.singletonMap("aaa" + i, i)); + expectedMap.put("aaa" + i, i); + } + if (withNull) { + builder.put(null, null); + expectedMap.put(null, null); + } + TinyMap map = builder.build(); + assertThat(expectedMap, is(map)); + } +} \ No newline at end of file diff --git a/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinyMultiMapTest.java b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinyMultiMapTest.java new file mode 100644 index 0000000..fe296c4 --- /dev/null +++ b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinyMultiMapTest.java @@ -0,0 +1,20 @@ +package org.xbib.datastructures.tiny; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.common.MultiMap; + +public class TinyMultiMapTest { + + @Test + public void testMultiMap() { + MultiMap multiMap = new TinyMultiMap<>(); + multiMap.put("a", "a"); + multiMap.put("a", "b"); + multiMap.put("a", "c"); + multiMap.put("b", "d"); + multiMap.put("b", "e"); + multiMap.put("b", "f"); + assertEquals("{a=[a, b, c], b=[d, e, f]}", multiMap.asMap().toString()); + } +} diff --git a/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinySetTest.java b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinySetTest.java new file mode 100644 index 0000000..a4162d4 --- /dev/null +++ b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/TinySetTest.java @@ -0,0 +1,129 @@ +package org.xbib.datastructures.tiny; + +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.number.OrderingComparison.lessThan; +import static org.hamcrest.MatcherAssert.assertThat; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +public class TinySetTest { + + @Test + public void testBuildAndGet() { + TinySet.Builder builder = TinySet.builder(); + assertThat(builder.size(), equalTo(0)); + builder.add("aaa"); + builder.add("bbb"); + builder.add("aaa"); + assertThat(builder.size(), equalTo(2)); + TinySet set = builder.build(); + assertThat(set.getIndex("aaa"), equalTo(0)); + assertThat(set.getIndex("bbb"), equalTo(1)); + assertThat(set.getIndex("ccc"), lessThan(0)); + assertThat(set.size(), equalTo(2)); + } + + @Test + public void canBuildWithDuplicateKeys() { + TinySet.Builder builder = TinySet.builder(); + builder.add("aaa"); + builder.add("aaa"); + builder.add("bbb"); + assertThat(builder.size(), equalTo(2)); + assertThat(builder.build(), equalTo(Set.of("aaa", "bbb"))); + assertThat(builder.size(), equalTo(2)); + assertThat(builder.build(), equalTo(Set.of("aaa", "bbb"))); + } + + @Test + public void canBuildWithNull() { + TinySet.Builder builder = TinySet.builder(); + builder.add(null); + assertThat(builder.build(), equalTo(Collections.singleton(null))); + } + + @Test + public void canBuildMediumWithDuplicateKeys() { + TinySet.Builder builder = TinySet.builder(); + builder.add("aaa"); + builder.add("aaa"); + for (int i = 0; i < 1000; i++) { + builder.add("aaa" + i); + } + assertThat(builder.build().size(), equalTo(1001)); + } + + @Test + public void canBuildLargeWithDuplicateKeys() { + TinySet.Builder builder = TinySet.builder(); + builder.add("aaa"); + builder.add("aaa"); + for (int i = 0; i < 0x10000; i++) { + builder.add("aaa" + i); + } + assertThat(builder.build().size(), equalTo(65537)); + } + + @Test + public void testBuildEmpty() throws Exception { + testCount(0, false); + testCount(0, true); + } + + @Test + public void testBuildMedium() throws Exception { + testCount(1000, false); + testCount(1000, true); + } + + @Test + public void testBuildLarge() throws Exception { + testCount(0x10000, true); + } + + @Test + public void testBuildAlmostThere() throws Exception { + testCount(255, false); + testCount(255, true); + } + + @Test + public void testBuildSmall() throws Exception { + testCount(123, false); + testCount(123, true); + } + + private void testCount(int count, boolean withNull) throws Exception { + TinySet.Builder builder = TinySet.builder(); + LinkedHashSet expectedSet = new LinkedHashSet<>(); + for (int i = 0; i < count; i++) { + if (count < 1000) { + builder.build(); + } + builder.addAll(Collections.singleton("aaa" + i)); + expectedSet.add("aaa" + i); + } + if (withNull) { + builder.add(null); + expectedSet.add(null); + } + TinySet set = builder.build(); + assertThat(expectedSet, CoreMatchers.is(set)); + } + + @Test + public void immutableIsImmutable() { + TinySet.Builder builder = TinySet.builder(); + builder.add("aaa"); + TinySet map = builder.build(); + Assertions.assertThrows(UnsupportedOperationException.class, map::clear); + Assertions.assertThrows(UnsupportedOperationException.class, () -> map.remove("aaa")); + Assertions.assertThrows(UnsupportedOperationException.class, () -> map.addAll(Collections.singleton("abc"))); + Assertions.assertThrows(UnsupportedOperationException.class, () -> map.add("abc")); + } +} diff --git a/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/package-info.java b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/package-info.java new file mode 100644 index 0000000..73ec28c --- /dev/null +++ b/datastructures-tiny/src/test/java/org/xbib/datastructures/tiny/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for testing tiny map. + */ +package org.xbib.datastructures.tiny; diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..14946aa --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +group = org.xbib +name = datastructures +version = 0.0.1 + +gradle.wrapper.version = 6.6.1 diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle new file mode 100644 index 0000000..db72f9f --- /dev/null +++ b/gradle/compile/java.gradle @@ -0,0 +1,45 @@ + +apply plugin: 'java-library' + +java { + modularity.inferModulePath.set(true) +} + +compileJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +compileTestJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +jar { + manifest { + attributes('Implementation-Title': project.name) + attributes('Implementation-Version': project.version) + attributes('Implementation-Vendor': 'Jörg Prante') + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier 'javadoc' +} + +artifacts { + archives sourcesJar, javadocJar +} + +tasks.withType(JavaCompile) { + options.compilerArgs << '-Xlint:all,-exports' +} + +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') +} diff --git a/gradle/documentation/asciidoc.gradle b/gradle/documentation/asciidoc.gradle new file mode 100644 index 0000000..87ba22e --- /dev/null +++ b/gradle/documentation/asciidoc.gradle @@ -0,0 +1,55 @@ +apply plugin: 'org.xbib.gradle.plugin.asciidoctor' + +configurations { + asciidoclet +} + +dependencies { + asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}" +} + + +asciidoctor { + backends 'html5' + outputDir = file("${rootProject.projectDir}/docs") + separateOutputDirs = false + attributes 'source-highlighter': 'coderay', + idprefix: '', + idseparator: '-', + toc: 'left', + doctype: 'book', + icons: 'font', + encoding: 'utf-8', + sectlink: true, + sectanchors: true, + linkattrs: true, + imagesdir: 'img', + stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css" +} + + +/*javadoc { +options.docletpath = configurations.asciidoclet.files.asType(List) +options.doclet = 'org.asciidoctor.Asciidoclet' +//options.overview = "src/docs/asciidoclet/overview.adoc" +options.addStringOption "-base-dir", "${projectDir}" +options.addStringOption "-attribute", + "name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}" +configure(options) { + noTimestamp = true +} +}*/ + + +/*javadoc { + options.docletpath = configurations.asciidoclet.files.asType(List) + options.doclet = 'org.asciidoctor.Asciidoclet' + options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc" + options.addStringOption "-base-dir", "${projectDir}" + options.addStringOption "-attribute", + "name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}" + options.destinationDirectory(file("${projectDir}/docs/javadoc")) + configure(options) { + noTimestamp = true + } +}*/ diff --git a/gradle/ide/idea.gradle b/gradle/ide/idea.gradle new file mode 100644 index 0000000..64e2167 --- /dev/null +++ b/gradle/ide/idea.gradle @@ -0,0 +1,13 @@ +apply plugin: 'idea' + +idea { + module { + outputDir file('build/classes/java/main') + testOutputDir file('build/classes/java/test') + } +} + +if (project.convention.findPlugin(JavaPluginConvention)) { + //sourceSets.main.output.classesDirs = file("build/classes/java/main") + //sourceSets.test.output.classesDirs = file("build/classes/java/test") +} diff --git a/gradle/publishing/publication.gradle b/gradle/publishing/publication.gradle new file mode 100644 index 0000000..a0f826e --- /dev/null +++ b/gradle/publishing/publication.gradle @@ -0,0 +1,66 @@ +import java.time.Duration + +apply plugin: "de.marcphilipp.nexus-publish" + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact sourcesJar + artifact javadocJar + pom { + name = project.name + description = rootProject.ext.description + url = rootProject.ext.url + inceptionYear = rootProject.ext.inceptionYear + packaging = 'jar' + organization { + name = 'xbib' + url = 'https://xbib.org' + } + developers { + developer { + id = 'jprante' + name = 'Jörg Prante' + email = 'joergprante@gmail.com' + url = 'https://github.com/jprante' + } + } + scm { + url = rootProject.ext.scmUrl + connection = rootProject.ext.scmConnection + developerConnection = rootProject.ext.scmDeveloperConnection + } + issueManagement { + system = rootProject.ext.issueManagementSystem + url = rootProject.ext.issueManagementUrl + } + licenses { + license { + name = rootProject.ext.licenseName + url = rootProject.ext.licenseUrl + distribution = 'repo' + } + } + } + } + } +} + +if (project.hasProperty("signing.keyId")) { + apply plugin: 'signing' + signing { + sign publishing.publications.mavenJava + } +} + +nexusPublishing { + repositories { + sonatype { + username = project.property('ossrhUsername') + password = project.property('ossrhPassword') + packageGroup = "org.xbib" + } + } + clientTimeout = Duration.ofSeconds(600) +} diff --git a/gradle/publishing/sonatype.gradle b/gradle/publishing/sonatype.gradle new file mode 100644 index 0000000..e85081d --- /dev/null +++ b/gradle/publishing/sonatype.gradle @@ -0,0 +1,12 @@ +import java.time.Duration + +if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) { + + apply plugin: 'io.codearte.nexus-staging' + + nexusStaging { + username = project.property('ossrhUsername') + password = project.property('ossrhPassword') + packageGroup = "org.xbib" + } +} diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle new file mode 100644 index 0000000..cfef972 --- /dev/null +++ b/gradle/test/junit5.gradle @@ -0,0 +1,27 @@ + +def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2' +def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2' + +dependencies { + testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}" + testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}" + testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" +} + +test { + useJUnitPlatform() + failFast = true + testLogging { + events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' + } + afterSuite { desc, result -> + if (!desc.parent) { + println "\nTest result: ${result.resultType}" + println "Test summary: ${result.testCount} tests, " + + "${result.successfulTestCount} succeeded, " + + "${result.failedTestCount} failed, " + + "${result.skippedTestCount} skipped" + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..33682bb --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..35b2f4f --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include 'datastructures-common' +include 'datastructures-tiny'