initial commit
This commit is contained in:
commit
aceaac08d6
37 changed files with 2607 additions and 0 deletions
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
/data
|
||||
/work
|
||||
/logs
|
||||
/.idea
|
||||
/target
|
||||
.DS_Store
|
||||
/.settings
|
||||
/.classpath
|
||||
/.project
|
||||
/.gradle
|
||||
build
|
||||
out
|
||||
*~
|
||||
*.iml
|
34
build.gradle
Normal file
34
build.gradle
Normal 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')
|
3
datastructures-common/src/main/java/module-info.java
Normal file
3
datastructures-common/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,3 @@
|
|||
module org.xbib.datastructures.common {
|
||||
exports org.xbib.datastructures.common;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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<>();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
13
datastructures-tiny/NOTICE.txt
Normal file
13
datastructures-tiny/NOTICE.txt
Normal 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
|
3
datastructures-tiny/build.gradle
Normal file
3
datastructures-tiny/build.gradle
Normal file
|
@ -0,0 +1,3 @@
|
|||
dependencies {
|
||||
api project(':datastructures-common')
|
||||
}
|
4
datastructures-tiny/src/main/java/module-info.java
Normal file
4
datastructures-tiny/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,4 @@
|
|||
module org.xbib.datastructures.tiny {
|
||||
exports org.xbib.datastructures.tiny;
|
||||
requires transitive org.xbib.datastructures.common;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.xbib.datastructures.tiny;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface IndexedSet<T> extends Set<T>, IndexedCollection<T> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Classes for testing tiny map.
|
||||
*/
|
||||
package org.xbib.datastructures.tiny;
|
5
gradle.properties
Normal file
5
gradle.properties
Normal file
|
@ -0,0 +1,5 @@
|
|||
group = org.xbib
|
||||
name = datastructures
|
||||
version = 0.0.1
|
||||
|
||||
gradle.wrapper.version = 6.6.1
|
45
gradle/compile/java.gradle
Normal file
45
gradle/compile/java.gradle
Normal 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')
|
||||
}
|
55
gradle/documentation/asciidoc.gradle
Normal file
55
gradle/documentation/asciidoc.gradle
Normal 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
13
gradle/ide/idea.gradle
Normal 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")
|
||||
}
|
66
gradle/publishing/publication.gradle
Normal file
66
gradle/publishing/publication.gradle
Normal 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)
|
||||
}
|
12
gradle/publishing/sonatype.gradle
Normal file
12
gradle/publishing/sonatype.gradle
Normal 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
27
gradle/test/junit5.gradle
Normal 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
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
185
gradlew
vendored
Executable 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
89
gradlew.bat
vendored
Normal 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
2
settings.gradle
Normal file
|
@ -0,0 +1,2 @@
|
|||
include 'datastructures-common'
|
||||
include 'datastructures-tiny'
|
Loading…
Reference in a new issue