diff --git a/src/main/java/org/warp/commonutils/type/Association.java b/src/main/java/org/warp/commonutils/type/Association.java new file mode 100644 index 0000000..6579138 --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/Association.java @@ -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 Source type + * @param Destination type + */ +public interface Association { + + /** + * 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 unlink(T src); + + /** + * Unlink + * @param dest Destination + * @return previous linked source + */ + Optional 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 getLinks(T src); + + /** + * Get a link source + * @param dest Source + * @return Source if the link exists + */ + Optional getLinkSource(U dest); + + /** + * Delete all links + */ + void clear(); + + /** + * Get the count of existing links + * @return size + */ + int size(); + + static Association synchronize(Association association) { + return new SynchronizedAssociation(association); + } +} diff --git a/src/main/java/org/warp/commonutils/type/BiAssociation.java b/src/main/java/org/warp/commonutils/type/BiAssociation.java new file mode 100644 index 0000000..f2ade49 --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/BiAssociation.java @@ -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 Source type + * @param Destination type + */ +public interface BiAssociation { + + /** + * Link source to dest + * @param src Source + * @param dest Destination + * @return previous value if it was already linked + */ + Optional 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 unlink(T src); + + /** + * Unlink + * @param dest Destination + * @return previous linked source + */ + Optional 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 getLink(T src); + + /** + * Get a link source + * @param dest Source + * @return Source if the link exists + */ + Optional getLinkSource(U dest); + + /** + * Delete all links + */ + void clear(); + + /** + * Get the count of existing links + * @return size + */ + int size(); + + static BiAssociation synchronize(BiAssociation association) { + return new SynchronizedBiAssociation(association); + } +} diff --git a/src/main/java/org/warp/commonutils/type/HashAssociation.java b/src/main/java/org/warp/commonutils/type/HashAssociation.java new file mode 100644 index 0000000..da9faeb --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/HashAssociation.java @@ -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 implements Association { + + private final Object2ObjectOpenHashMap> associations; + private final Object2ObjectOpenHashMap inverseAssociations; + + public HashAssociation() { + this.associations = new Object2ObjectOpenHashMap<>(); + this.inverseAssociations = new Object2ObjectOpenHashMap<>(); + } + + private HashAssociation(Object2ObjectOpenHashMap> associations, + Object2ObjectOpenHashMap 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 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 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 getLinks(T src) { + Objects.requireNonNull(src); + return associations.get(src).clone(); + } + + @Override + public Optional 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 clone() { + var associationsClone = associations.clone(); + associationsClone.replaceAll((item, destinations) -> destinations.clone()); + return new HashAssociation<>(associationsClone, inverseAssociations.clone()); + } +} diff --git a/src/main/java/org/warp/commonutils/type/HashBiAssociation.java b/src/main/java/org/warp/commonutils/type/HashBiAssociation.java new file mode 100644 index 0000000..87e4f95 --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/HashBiAssociation.java @@ -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 implements BiAssociation { + + private final Object2ObjectOpenHashMap associations; + private final Object2ObjectOpenHashMap inverseAssociations; + + public HashBiAssociation() { + this.associations = new Object2ObjectOpenHashMap<>(); + this.inverseAssociations = new Object2ObjectOpenHashMap<>(); + } + + private HashBiAssociation(Object2ObjectOpenHashMap associations, + Object2ObjectOpenHashMap inverseAssociations) { + this.associations = associations; + this.inverseAssociations = inverseAssociations; + } + + @Override + public Optional 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 unlink(T src) { + Objects.requireNonNull(src); + + var dest = associations.remove(src); + + if (dest != null) { + inverseAssociations.remove(dest); + } + + return Optional.ofNullable(dest); + } + + @Override + public Optional 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 getLink(T src) { + Objects.requireNonNull(src); + + return Optional.ofNullable(associations.get(src)); + } + + @Override + public Optional 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 clone() { + return new HashBiAssociation<>(associations.clone(), inverseAssociations.clone()); + } +} diff --git a/src/main/java/org/warp/commonutils/type/SynchronizedAssociation.java b/src/main/java/org/warp/commonutils/type/SynchronizedAssociation.java new file mode 100644 index 0000000..c93bb0f --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/SynchronizedAssociation.java @@ -0,0 +1,97 @@ +package org.warp.commonutils.type; + +import java.util.Optional; +import java.util.Set; + +public class SynchronizedAssociation implements Association { + + private final Association association; + private final Object lock; + + public SynchronizedAssociation(Association association) { + this.association = association; + this.lock = new Object(); + } + + public SynchronizedAssociation(Association 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 unlink(T src) { + synchronized (lock) { + return association.unlink(src); + } + } + + @Override + public Optional 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 getLinks(T src) { + synchronized (lock) { + return association.getLinks(src); + } + } + + @Override + public Optional 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(); + } + } +} diff --git a/src/main/java/org/warp/commonutils/type/SynchronizedBiAssociation.java b/src/main/java/org/warp/commonutils/type/SynchronizedBiAssociation.java new file mode 100644 index 0000000..bb0475e --- /dev/null +++ b/src/main/java/org/warp/commonutils/type/SynchronizedBiAssociation.java @@ -0,0 +1,96 @@ +package org.warp.commonutils.type; + +import java.util.Optional; + +public class SynchronizedBiAssociation implements BiAssociation { + + private final BiAssociation association; + private final Object lock; + + public SynchronizedBiAssociation(BiAssociation association) { + this.association = association; + this.lock = new Object(); + } + + public SynchronizedBiAssociation(BiAssociation association, Object lock) { + this.association = association; + this.lock = lock; + } + + @Override + public Optional 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 unlink(T src) { + synchronized (lock) { + return association.unlink(src); + } + } + + @Override + public Optional 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 getLink(T src) { + synchronized (lock) { + return association.getLink(src); + } + } + + @Override + public Optional 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(); + } + } +}