Add Association data type

This commit is contained in:
Andrea Cavalli 2020-11-10 22:37:49 +01:00
parent 4b11b7fd94
commit c755b57e0c
6 changed files with 714 additions and 0 deletions

View File

@ -0,0 +1,107 @@
package org.warp.commonutils.type;
import java.util.Optional;
import java.util.Set;
/**
* One to many relationship (not overlapping)
*
* o ------- o
* +--- o
* o ---+--- o
* +--- o
* o ------- o
* o ------- o
* o ---+--- o
* +--- o
*
* @param <T> Source type
* @param <U> Destination type
*/
public interface Association<T, U> {
/**
* Link source to dest
* @param src Source
* @param dest Destination
* @return true if linked, false if it was already linked with that destination
*/
boolean link(T src, U dest);
/**
* Unlink only if src is linked with dest
* @param src Source
* @param dest Destination
* @return true if unlinked, false if not present
*/
boolean unlink(T src, U dest);
/**
* Unlink
* @param src Source
* @return previous linked destinations
*/
Set<U> unlink(T src);
/**
* Unlink
* @param dest Destination
* @return previous linked source
*/
Optional<T> unlinkFromSource(U dest);
/**
* Check if link exists
* @param src Source
* @return true if source it's linked with at least 1 destination
*/
default boolean hasAnyLink(T src) {
return !getLinks(src).isEmpty();
}
/**
* Check if link exists
* @param dest Destination
* @return true if destination is linked with a source
*/
default boolean hasLinkSource(U dest) {
return getLinkSource(dest).isPresent();
}
/**
* Check if link exists
* @param src Source
* @param dest Destination
* @return true if source and destination are linked together
*/
boolean hasLink(T src, U dest);
/**
* Get a link destination
* @param src Source
* @return Existing linked destinations
*/
Set<U> getLinks(T src);
/**
* Get a link source
* @param dest Source
* @return Source if the link exists
*/
Optional<T> getLinkSource(U dest);
/**
* Delete all links
*/
void clear();
/**
* Get the count of existing links
* @return size
*/
int size();
static <T, U> Association<T, U> synchronize(Association<T, U> association) {
return new SynchronizedAssociation(association);
}
}

View File

@ -0,0 +1,106 @@
package org.warp.commonutils.type;
import java.util.Optional;
/**
* One to one relationship
*
* o ------- o
* o ------- o
* o ------- o
* o ------- o
* o ------- o
* o ------- o
* o ------- o
* o ------- o
*
* @param <T> Source type
* @param <U> Destination type
*/
public interface BiAssociation<T, U> {
/**
* Link source to dest
* @param src Source
* @param dest Destination
* @return previous value if it was already linked
*/
Optional<U> link(T src, U dest);
/**
* Unlink only if src is linked with dest
* @param src Source
* @param dest Destination
* @return true if unlinked, false if not present
*/
boolean unlink(T src, U dest);
/**
* Unlink
* @param src Source
* @return previous linked destination
*/
Optional<U> unlink(T src);
/**
* Unlink
* @param dest Destination
* @return previous linked source
*/
Optional<T> unlinkFromSource(U dest);
/**
* Check if link exists
* @param src Source
* @return true if source it's linked with a destination
*/
default boolean hasLink(T src) {
return getLink(src).isPresent();
}
/**
* Check if link exists
* @param dest Destination
* @return true if destination is linked with a source
*/
default boolean hasLinkSource(U dest) {
return getLinkSource(dest).isPresent();
}
/**
* Check if link exists
* @param src Source
* @param dest Destination
* @return true if source and destination are linked together
*/
boolean hasLink(T src, U dest);
/**
* Get a link destination
* @param src Source
* @return Destination if the link exists
*/
Optional<U> getLink(T src);
/**
* Get a link source
* @param dest Source
* @return Source if the link exists
*/
Optional<T> getLinkSource(U dest);
/**
* Delete all links
*/
void clear();
/**
* Get the count of existing links
* @return size
*/
int size();
static <T, U> BiAssociation<T, U> synchronize(BiAssociation<T, U> association) {
return new SynchronizedBiAssociation(association);
}
}

View File

@ -0,0 +1,166 @@
package org.warp.commonutils.type;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
public class HashAssociation<T, U> implements Association<T, U> {
private final Object2ObjectOpenHashMap<T, ObjectOpenHashSet<U>> associations;
private final Object2ObjectOpenHashMap<U, T> inverseAssociations;
public HashAssociation() {
this.associations = new Object2ObjectOpenHashMap<>();
this.inverseAssociations = new Object2ObjectOpenHashMap<>();
}
private HashAssociation(Object2ObjectOpenHashMap<T, ObjectOpenHashSet<U>> associations,
Object2ObjectOpenHashMap<U, T> inverseAssociations) {
this.associations = associations;
this.inverseAssociations = inverseAssociations;
}
@Override
public boolean link(T src, U dest) {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
// If the destination is associated to null
var previousSrc = inverseAssociations.get(dest);
// Return if the association already exists
if (src.equals(previousSrc)) {
return false;
}
// Remove the previous association if present
if (previousSrc != null) {
inverseAssociations.remove(dest);
var destinations = associations.get(previousSrc);
destinations.remove(dest);
if (destinations.isEmpty()) {
associations.remove(previousSrc);
}
}
if (!associations.computeIfAbsent(src, s -> new ObjectOpenHashSet<>(0)).add(dest)) {
throw new IllegalStateException("Association was partially linked");
}
return true;
}
@Override
public boolean unlink(T src, U dest) {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
var linkedSrc = inverseAssociations.get(dest);
// Check if the link is different
if (!Objects.equals(src, linkedSrc)) {
return false;
}
inverseAssociations.remove(dest);
var destinations = associations.get(src);
if (!destinations.remove(dest)) {
throw new IllegalStateException("Association was partially linked");
}
if (destinations.isEmpty()) {
associations.remove(src);
}
return true;
}
@Override
public Set<U> unlink(T src) {
Objects.requireNonNull(src);
var destinations = associations.remove(src);
if (destinations == null) {
return Set.of();
}
for (U destination : destinations) {
if (!Objects.equals(src, inverseAssociations.remove(destination))) {
throw new IllegalStateException("Association was partially linked");
}
}
return destinations;
}
@Override
public Optional<T> unlinkFromSource(U dest) {
Objects.requireNonNull(dest);
var previousSrc = inverseAssociations.remove(dest);
if (previousSrc == null) {
return Optional.empty();
}
var destinations = associations.get(previousSrc);
destinations.remove(dest);
if (destinations.isEmpty()) {
associations.remove(previousSrc);
}
return Optional.of(previousSrc);
}
@Override
public boolean hasAnyLink(T src) {
Objects.requireNonNull(src);
var destinations = associations.get(src);
return destinations != null && !destinations.isEmpty();
}
@Override
public boolean hasLinkSource(U dest) {
Objects.requireNonNull(dest);
return inverseAssociations.containsKey(dest);
}
@Override
public boolean hasLink(T src, U dest) {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
return Objects.equals(src, inverseAssociations.get(dest));
}
@Override
public Set<U> getLinks(T src) {
Objects.requireNonNull(src);
return associations.get(src).clone();
}
@Override
public Optional<T> getLinkSource(U dest) {
Objects.requireNonNull(dest);
return Optional.ofNullable(inverseAssociations.get(dest));
}
@Override
public void clear() {
associations.clear();
inverseAssociations.clear();
}
@Override
public int size() {
return inverseAssociations.size();
}
@SuppressWarnings("MethodDoesntCallSuperMethod")
@Override
public HashAssociation<T, U> clone() {
var associationsClone = associations.clone();
associationsClone.replaceAll((item, destinations) -> destinations.clone());
return new HashAssociation<>(associationsClone, inverseAssociations.clone());
}
}

View File

@ -0,0 +1,142 @@
package org.warp.commonutils.type;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Objects;
import java.util.Optional;
public class HashBiAssociation<T, U> implements BiAssociation<T, U> {
private final Object2ObjectOpenHashMap<T, U> associations;
private final Object2ObjectOpenHashMap<U, T> inverseAssociations;
public HashBiAssociation() {
this.associations = new Object2ObjectOpenHashMap<>();
this.inverseAssociations = new Object2ObjectOpenHashMap<>();
}
private HashBiAssociation(Object2ObjectOpenHashMap<T, U> associations,
Object2ObjectOpenHashMap<U, T> inverseAssociations) {
this.associations = associations;
this.inverseAssociations = inverseAssociations;
}
@Override
public Optional<U> link(T src, U dest) {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
var previousSrc = inverseAssociations.put(dest, src);
// Return immediately if the link already exists
if (Objects.equals(src, previousSrc)) {
return Optional.of(dest);
}
// Remove the previous association
if (previousSrc != null) {
associations.remove(previousSrc);
}
var previousDest = associations.put(src, dest);
// Remove the previous association
if (previousDest != null) {
inverseAssociations.remove(previousDest);
}
return Optional.ofNullable(previousDest);
}
@Override
public boolean unlink(T src, U dest) {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
if (!Objects.equals(dest, associations.get(src))) {
return false;
}
associations.remove(src);
inverseAssociations.remove(dest);
return true;
}
@Override
public Optional<U> unlink(T src) {
Objects.requireNonNull(src);
var dest = associations.remove(src);
if (dest != null) {
inverseAssociations.remove(dest);
}
return Optional.ofNullable(dest);
}
@Override
public Optional<T> unlinkFromSource(U dest) {
Objects.requireNonNull(dest);
var src = inverseAssociations.remove(dest);
if (src != null) {
associations.remove(src);
}
return Optional.ofNullable(src);
}
@Override
public boolean hasLink(T src) {
Objects.requireNonNull(src);
return associations.containsKey(src);
}
@Override
public boolean hasLinkSource(U dest) {
Objects.requireNonNull(dest);
return inverseAssociations.containsKey(dest);
}
@Override
public boolean hasLink(T src, U dest) {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
return Objects.equals(dest, associations.get(src));
}
@Override
public Optional<U> getLink(T src) {
Objects.requireNonNull(src);
return Optional.ofNullable(associations.get(src));
}
@Override
public Optional<T> getLinkSource(U dest) {
Objects.requireNonNull(dest);
return Optional.ofNullable(inverseAssociations.get(dest));
}
@Override
public void clear() {
associations.clear();
inverseAssociations.clear();
}
@Override
public int size() {
return inverseAssociations.size();
}
@SuppressWarnings("MethodDoesntCallSuperMethod")
@Override
public HashBiAssociation<T, U> clone() {
return new HashBiAssociation<>(associations.clone(), inverseAssociations.clone());
}
}

View File

@ -0,0 +1,97 @@
package org.warp.commonutils.type;
import java.util.Optional;
import java.util.Set;
public class SynchronizedAssociation<T, U> implements Association<T, U> {
private final Association<T,U> association;
private final Object lock;
public SynchronizedAssociation(Association<T, U> association) {
this.association = association;
this.lock = new Object();
}
public SynchronizedAssociation(Association<T, U> association, Object lock) {
this.association = association;
this.lock = lock;
}
@Override
public boolean link(T src, U dest) {
synchronized (lock) {
return association.link(src, dest);
}
}
@Override
public boolean unlink(T src, U dest) {
synchronized (lock) {
return association.unlink(src, dest);
}
}
@Override
public Set<U> unlink(T src) {
synchronized (lock) {
return association.unlink(src);
}
}
@Override
public Optional<T> unlinkFromSource(U dest) {
synchronized (lock) {
return association.unlinkFromSource(dest);
}
}
@Override
public boolean hasAnyLink(T src) {
synchronized (lock) {
return association.hasAnyLink(src);
}
}
@Override
public boolean hasLinkSource(U dest) {
synchronized (lock) {
return association.hasLinkSource(dest);
}
}
@Override
public boolean hasLink(T src, U dest) {
synchronized (lock) {
return association.hasLink(src, dest);
}
}
@Override
public Set<U> getLinks(T src) {
synchronized (lock) {
return association.getLinks(src);
}
}
@Override
public Optional<T> getLinkSource(U dest) {
synchronized (lock) {
return association.getLinkSource(dest);
}
}
@Override
public void clear() {
synchronized (lock) {
association.clear();
}
}
@Override
public int size() {
synchronized (lock) {
return association.size();
}
}
}

View File

@ -0,0 +1,96 @@
package org.warp.commonutils.type;
import java.util.Optional;
public class SynchronizedBiAssociation<T, U> implements BiAssociation<T, U> {
private final BiAssociation<T,U> association;
private final Object lock;
public SynchronizedBiAssociation(BiAssociation<T, U> association) {
this.association = association;
this.lock = new Object();
}
public SynchronizedBiAssociation(BiAssociation<T, U> association, Object lock) {
this.association = association;
this.lock = lock;
}
@Override
public Optional<U> link(T src, U dest) {
synchronized (lock) {
return association.link(src, dest);
}
}
@Override
public boolean unlink(T src, U dest) {
synchronized (lock) {
return association.unlink(src, dest);
}
}
@Override
public Optional<U> unlink(T src) {
synchronized (lock) {
return association.unlink(src);
}
}
@Override
public Optional<T> unlinkFromSource(U dest) {
synchronized (lock) {
return association.unlinkFromSource(dest);
}
}
@Override
public boolean hasLink(T src) {
synchronized (lock) {
return association.hasLink(src);
}
}
@Override
public boolean hasLinkSource(U dest) {
synchronized (lock) {
return association.hasLinkSource(dest);
}
}
@Override
public boolean hasLink(T src, U dest) {
synchronized (lock) {
return association.hasLink(src, dest);
}
}
@Override
public Optional<U> getLink(T src) {
synchronized (lock) {
return association.getLink(src);
}
}
@Override
public Optional<T> getLinkSource(U dest) {
synchronized (lock) {
return association.getLinkSource(dest);
}
}
@Override
public void clear() {
synchronized (lock) {
association.clear();
}
}
@Override
public int size() {
synchronized (lock) {
return association.size();
}
}
}