initial commit

This commit is contained in:
Jörg Prante 2020-09-12 22:42:03 +02:00
commit aceaac08d6
37 changed files with 2607 additions and 0 deletions

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
/data
/work
/logs
/.idea
/target
.DS_Store
/.settings
/.classpath
/.project
/.gradle
build
out
*~
*.iml

34
build.gradle Normal file
View file

@ -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')

View file

@ -0,0 +1,3 @@
module org.xbib.datastructures.common {
exports org.xbib.datastructures.common;
}

View file

@ -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 <K> the key type parameter
* @param <V> the value type parameter
*/
public abstract class AbstractMultiMap<K, V> implements MultiMap<K, V> {
private final Map<K, Collection<V>> map;
public AbstractMultiMap() {
this(null);
}
public AbstractMultiMap(MultiMap<K, V> 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<K> keySet() {
return map.keySet();
}
@Override
public boolean put(K key, V value) {
Collection<V> 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<V> values) {
if (values == null) {
return;
}
Collection<V> set = map.computeIfAbsent(key, k -> newValues());
for (V v : values) {
set.add(v);
}
}
@Override
public Collection<V> get(K key) {
return map.get(key);
}
@Override
public Collection<V> remove(K key) {
return map.remove(key);
}
@Override
public boolean remove(K key, V value) {
Collection<V> set = map.get(key);
return set != null && set.remove(value);
}
@Override
public void putAll(MultiMap<K, V> map) {
if (map != null) {
for (K key : map.keySet()) {
putAll(key, map.get(key));
}
}
}
@Override
public Map<K, Collection<V>> asMap() {
return map;
}
@Override
public String getString(K key) {
Collection<V> v = get(key);
return v != null ? v.iterator().next().toString() : null;
}
@Override
public String getString(K key, String defaultValue) {
Collection<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> collection = get(key);
Iterator<V> 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<V> newValues();
protected abstract Map<K, Collection<V>> newMap();
}

View file

@ -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 <K> the key type parameter
* @param <V> the value type parameter
*/
public class LinkedHashSetMultiMap<K, V> extends AbstractMultiMap<K, V> {
public LinkedHashSetMultiMap() {
super();
}
public LinkedHashSetMultiMap(MultiMap<K, V> map) {
super(map);
}
@Override
protected Collection<V> newValues() {
return new LinkedHashSet<>();
}
@Override
protected Map<K, Collection<V>> newMap() {
return new LinkedHashMap<>();
}
}

View file

@ -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<String, Object> deepMerge(Map<String, Object> map, Map<String, Object> newMap) {
for (Map.Entry<String, Object> 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<Object>) originalValue).addAll((Collection<Object>) value);
} else if (originalValue instanceof Map && value instanceof Map) {
deepMerge((Map<String, Object>) originalValue, (Map<String, Object>) 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> T get(Map<?, ?> map, String key) {
return get(map, key.split("\\."));
}
@SuppressWarnings("unchecked")
private static <T> 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;
}
}

View file

@ -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 <K> the key type parameter
* @param <V> the value type parameter
*/
public interface MultiMap<K, V> {
void clear();
int size();
boolean isEmpty();
boolean containsKey(K key);
Collection<V> get(K key);
Set<K> keySet();
boolean put(K key, V value);
void putAll(K key, Iterable<V> values);
void putAll(MultiMap<K, V> map);
Collection<V> remove(K key);
boolean remove(K key, V value);
Map<K, Collection<V>> 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);
}

View file

@ -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<String, Object> m1 = new LinkedHashMap<>();
Map<String, Object> m2 = new LinkedHashMap<>();
Map<String, Object> m3 = new LinkedHashMap<>();
Map<String, Object> 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());
}
}

View file

@ -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

View file

@ -0,0 +1,3 @@
dependencies {
api project(':datastructures-common')
}

View file

@ -0,0 +1,4 @@
module org.xbib.datastructures.tiny {
exports org.xbib.datastructures.tiny;
requires transitive org.xbib.datastructures.common;
}

View file

@ -0,0 +1,28 @@
package org.xbib.datastructures.tiny;
import java.util.Collection;
import java.util.ListIterator;
public interface IndexedCollection<T> extends Collection<T> {
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<T> iterator();
ListIterator<T> iterator(int fromIndex);
}

View file

@ -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<T> extends AbstractCollection<T> implements IndexedCollection<T> {
@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<T> iterator() {
return iterator(0);
}
@Override
public ListIterator<T> iterator(int fromIndex) {
return new CollectionIterator(fromIndex);
}
@Override
public void forEach(Consumer<? super T> 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<T> extends IndexedCollection<T> {
@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<T> extends NoAdditiveChange<T> {
@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<T> {
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);
}
}
}

View file

@ -0,0 +1,36 @@
package org.xbib.datastructures.tiny;
import java.util.Map;
public interface IndexedMap<K, V> extends Map<K, V> {
int getIndex(Object key);
K getKeyAt(int index);
V getValueAt(int index);
Entry<K, V> 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<K> keySet();
@Override
IndexedSet<Map.Entry<K, V>> entrySet();
interface Entry<K, V> extends Map.Entry<K, V> {
int getIndex();
boolean isRemoved();
}
}

View file

@ -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<K, V> implements IndexedMap<K, V> {
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<K, V> getEntryAt(int index) {
Preconditions.checkElementIndex(index, rawSize());
return new IndexedEntry(index);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> 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<? extends K, ? extends V> m) {
m.forEach(this::put);
}
@Override
public IndexedSet<K> keySet() {
return new KeysView();
}
@Override
public Collection<V> values() {
return new ValuesView();
}
@Override
public IndexedSet<Map.Entry<K, V>> 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<V> implements IndexedCollectionBase.NoAdditiveChange<V> {
@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<K> implements IndexedCollectionBase.NoAdditiveChange<K> {
@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<Map.Entry<K, V>> implements IndexedCollectionBase.NoAdditiveChange<Map.Entry<K, V>> {
@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<K, V> 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<K, V> {
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);
}
}
}

View file

@ -0,0 +1,6 @@
package org.xbib.datastructures.tiny;
import java.util.Set;
public interface IndexedSet<T> extends Set<T>, IndexedCollection<T> {
}

View file

@ -0,0 +1,34 @@
package org.xbib.datastructures.tiny;
import java.util.Objects;
import java.util.Set;
public abstract class IndexedSetBase<T> extends IndexedCollectionBase<T> implements IndexedSet<T> {
@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;
}
}

View file

@ -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();
}
}

View file

@ -0,0 +1,176 @@
package org.xbib.datastructures.tiny;
import java.util.Arrays;
public abstract class TinyMap<K, V> extends IndexedMapBase<K, V> {
private final TinySet<K> keys;
protected TinyMap(TinySet<K> keys) {
this.keys = keys;
}
public static <K, V> Builder<K, V> 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<K> keySet() {
return keys;
}
public static final Object TOMBSTONE = new Object() {
@Override
public String toString() {
return "TOMBSTONE";
}
};
public static class Builder<K, V> extends IndexedMapBase<K, V> {
private final TinySet.Builder<K> 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<K, V> build() {
return buildWithKeys(this.keys.build());
}
public TinyMap<K, V> buildWithKeys(TinySet<K> 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<K, V> extends TinyMap<K, V> {
public final Object[] values;
public SizeAny(TinySet<K> 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];
}
}
}

View file

@ -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 <K> the key type parameter
* @param <V> the value type parameter
*/
public class TinyMultiMap<K, V> extends AbstractMultiMap<K, V> {
public TinyMultiMap() {
super();
}
public TinyMultiMap(MultiMap<K, V> map) {
super(map);
}
@Override
protected Collection<V> newValues() {
return TinySet.builder();
}
@Override
protected Map<K, Collection<V>> newMap() {
return TinyMap.builder();
}
}

View file

@ -0,0 +1,340 @@
package org.xbib.datastructures.tiny;
import java.util.Arrays;
import java.util.Objects;
public abstract class TinySet<T> extends IndexedSetBase<T> {
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 <T> TinySet<T> 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 <T> Builder<T> builder() {
return new Builder<>();
}
public static class Empty<T> extends TinySet<T> implements Immutable<T> {
@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<T, A> extends TinySet<T> implements Immutable<T> {
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<T> extends ArrayTableSet<T, byte[]> {
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<T> extends ArrayTableSet<T, short[]> {
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<T> extends ArrayTableSet<T, int[]> {
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<T> extends IndexedSetBase<T> implements IndexedCollectionBase.NoAdditiveChange<T> {
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<T> build() {
compact();
return TinySet.createUnsafe(Arrays.copyOf(keys, size));
}
@Override
public void clear() {
Arrays.fill(keys, 0, rawSize, null);
softClearTable();
size = rawSize = 0;
}
}
}

View file

@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> builder = TinyMap.builder();
builder.put(null, 123);
assertThat(builder.build(), equalTo(Collections.singletonMap(null, 123)));
}
@Test
public void canBuildMediumWithDuplicateKeys() {
TinyMap.Builder<String, Object> 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<String, Object> 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<String, Object> builder = TinyMap.builder();
builder.put("aaa", null);
builder.put("bbb", 456.0);
TinyMap<String, Object> 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<String, Object> builder1 = TinyMap.builder();
TinyMap.Builder<String, Object> builder2 = TinyMap.builder();
for (int i = 0; i < 100; i++) {
builder1.put("aaa" + i, i);
builder2.put("aaa" + i, i);
}
TinyMap<String, Object> map1 = builder1.build();
TinyMap<String, Object> map2 = builder2.build();
assertThat(map1.keySet(), equalTo(map2.keySet()));
}
@Test
public void testGiantShortProblem() {
TinyMap.Builder<String, Object> builder = TinyMap.builder();
for (int i = 0; i < 100000; i++) {
builder.put("aaa" + i, i);
}
TinyMap<String, Object> map = builder.build();
assertEquals(99999, map.get("aaa99999"));
}
private void testCount(int count, boolean withNull) {
TinyMap.Builder<String, Object> builder = TinyMap.builder();
LinkedHashMap<String, Object> 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<String, Object> map = builder.build();
assertThat(expectedMap, is(map));
}
}

View file

@ -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<String, String> 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());
}
}

View file

@ -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<String> builder = TinySet.builder();
assertThat(builder.size(), equalTo(0));
builder.add("aaa");
builder.add("bbb");
builder.add("aaa");
assertThat(builder.size(), equalTo(2));
TinySet<String> 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<String> 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<String> builder = TinySet.builder();
builder.add(null);
assertThat(builder.build(), equalTo(Collections.singleton(null)));
}
@Test
public void canBuildMediumWithDuplicateKeys() {
TinySet.Builder<String> 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<String> 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<String> builder = TinySet.builder();
LinkedHashSet<String> 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<String> set = builder.build();
assertThat(expectedSet, CoreMatchers.is(set));
}
@Test
public void immutableIsImmutable() {
TinySet.Builder<Object> builder = TinySet.builder();
builder.add("aaa");
TinySet<Object> 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"));
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for testing tiny map.
*/
package org.xbib.datastructures.tiny;

5
gradle.properties Normal file
View file

@ -0,0 +1,5 @@
group = org.xbib
name = datastructures
version = 0.0.1
gradle.wrapper.version = 6.6.1

View file

@ -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')
}

View file

@ -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
}
}*/

13
gradle/ide/idea.gradle Normal file
View file

@ -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")
}

View file

@ -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)
}

View file

@ -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"
}
}

27
gradle/test/junit5.gradle Normal file
View file

@ -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"
}
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -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

185
gradlew vendored Executable file
View file

@ -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" "$@"

89
gradlew.bat vendored Normal file
View file

@ -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

2
settings.gradle Normal file
View file

@ -0,0 +1,2 @@
include 'datastructures-common'
include 'datastructures-tiny'