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);
|
||||