com.xlrit.gears.base.meta.Relation Maven / Gradle / Ivy
package com.xlrit.gears.base.meta;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import com.xlrit.gears.base.collection.CollectionRef;
import com.xlrit.gears.base.collection.CollectionRefFromRelation;
import lombok.RequiredArgsConstructor;
import static java.util.Objects.requireNonNull;
// TODO can be replace List with Collection?
@RequiredArgsConstructor
public abstract class Relation implements Function, HasAbsentValue {
protected final Class elementType;
protected final Function getter;
protected final BiConsumer setter;
protected final Function oppositeGetter;
protected final BiConsumer oppositeSetter;
public Class getElementType() { return elementType; }
public ValueB get(EntityA owner) {
// TODO handle null, support optionality
return getter.apply(owner);
}
public abstract void set(EntityA owner, ValueB value);
@Override
public ValueB apply(EntityA entityA) {
return get(entityA);
}
@Override
public abstract ValueB absentValue();
// === *-to-One relations === //
public static abstract class ToOne extends Relation {
protected ToOne(Class elementType, Function getter, BiConsumer setter, Function oppositeGetter, BiConsumer oppositeSetter) {
super(elementType, getter, setter, oppositeGetter, oppositeSetter);
}
@Override
public EntityB absentValue() {
return null;
}
}
/** Represents a unidirectional one/many-to-one relation. */
public static class UniToOne extends ToOne {
protected UniToOne(Class elementType, Function getter, BiConsumer setter) {
super(elementType, requireNonNull(getter), requireNonNull(setter), null, null);
}
@Override
public void set(A owner, B value) {
setter.accept(owner, value);
}
}
/** Represents a bidirectional one-to-one relation. */
public static class OneToOne extends ToOne {
public OneToOne(Class elementType, Function getter, BiConsumer setter, Function oppositeGetter, BiConsumer oppositeSetter) {
super(elementType, requireNonNull(getter), requireNonNull(setter), requireNonNull(oppositeGetter), requireNonNull(oppositeSetter));
}
@Override
public void set(A owner, B newElement) {
B oldElement = getter.apply(owner);
if (oldElement == newElement) return;
if (oldElement != null) oppositeSetter.accept(oldElement, null);
if (newElement != null) oppositeSetter.accept(newElement, owner);
setter.accept(owner, newElement);
}
}
/** Represents a bidirectional many-to-one relation. */
public static class ManyToOne extends ToOne> {
public ManyToOne(Class elementType, Function getter, BiConsumer setter, Function> oppositeGetter, BiConsumer> oppositeSetter) {
super(elementType, requireNonNull(getter), requireNonNull(setter), requireNonNull(oppositeGetter), requireNonNull(oppositeSetter));
}
@Override
public void set(A owner, B newElement) {
B oldElement = getter.apply(owner);
if (oldElement == newElement) return;
if (oldElement != null) oppositeGetter.apply(oldElement).remove(owner);
if (newElement != null) oppositeGetter.apply(newElement).add(owner);
setter.accept(owner, newElement);
}
}
// === *-to-Many relations === //
public static abstract class ToMany extends Relation> {
protected ToMany(Class elementType, Function> getter, BiConsumer> setter, Function oppositeGetter, BiConsumer oppositeSetter) {
super(elementType, getter, setter, oppositeGetter, oppositeSetter);
}
@Override
public List absentValue() {
return List.of();
}
public CollectionRef collectionRef(AE owner) {
return new CollectionRefFromRelation<>(owner, this);
}
public abstract void add(AE owner, Collection extraElements);
public abstract void remove(AE owner, Collection superfluousElements);
}
public static class UniToMany extends ToMany {
protected UniToMany(Class elementType, Function> getter, BiConsumer> setter) {
super(elementType, requireNonNull(getter), requireNonNull(setter), null, null);
}
@Override
public void set(A owner, List value) {
setter.accept(owner, value);
}
@Override
public void add(A owner, Collection extraElements) {
getter.apply(owner).addAll(extraElements);
}
@Override
public void remove(A owner, Collection superfluousElements) {
getter.apply(owner).removeAll(superfluousElements);
}
}
public static class OneToMany extends ToMany {
protected OneToMany(Class elementType, Function> getter, BiConsumer> setter, Function oppositeGetter, BiConsumer oppositeSetter) {
super(elementType, requireNonNull(getter), requireNonNull(setter), requireNonNull(oppositeGetter), requireNonNull(oppositeSetter));
}
@Override
public void set(A owner, List newElements) {
if (newElements == null) newElements = new ArrayList<>();
for (B oldElement : getter.apply(owner)) oppositeSetter.accept(oldElement, null);
for (B newElement : newElements) oppositeSetter.accept(newElement, owner);
setter.accept(owner, newElements);
}
@Override
public void add(A owner, Collection extraElements) {
getter.apply(owner).addAll(extraElements);
for (B extraElement : extraElements) oppositeSetter.accept(extraElement, owner);
}
@Override
public void remove(A owner, Collection superfluousElements) {
getter.apply(owner).removeAll(superfluousElements);
for (B superfluousElement : superfluousElements) oppositeSetter.accept(superfluousElement, null);
}
}
public static class ManyToMany extends ToMany> {
protected ManyToMany(Class elementType, Function> getter, BiConsumer> setter, Function> oppositeGetter, BiConsumer> oppositeSetter) {
super(elementType, requireNonNull(getter), requireNonNull(setter), requireNonNull(oppositeGetter), requireNonNull(oppositeSetter));
}
@Override
public void set(A owner, List newElements) {
if (newElements == null) newElements = new ArrayList<>();
for (B oldElement : getter.apply(owner)) oppositeGetter.apply(oldElement).remove(owner);
for (B newElement : newElements) oppositeGetter.apply(newElement).add(owner);
setter.accept(owner, newElements);
}
@Override
public void add(A owner, Collection extraElements) {
List currentElements = getter.apply(owner);
for (B extraElement : extraElements) {
currentElements.add(extraElement);
oppositeGetter.apply(extraElement).add(owner);
}
}
@Override
public void remove(A owner, Collection superfluousElements) {
List currentElements = getter.apply(owner);
for (B superfluousElement : superfluousElements) {
if (currentElements.remove(superfluousElement))
oppositeGetter.apply(superfluousElement).remove(owner);
}
}
}
// === factory methods === //
// bidirectional one-to-one
public static OneToOne oneToOne(Class elementType, Function getter, BiConsumer setter, Function oppositeGetter, BiConsumer oppositeSetter) {
return new OneToOne<>(elementType, getter, setter, oppositeGetter, oppositeSetter);
}
// bidirectional many-to-one
public static ManyToOne manyToOne(Class elementType, Function getter, BiConsumer setter, Function> oppositeGetter, BiConsumer> oppositeSetter) {
return new ManyToOne<>(elementType, getter, setter, oppositeGetter, oppositeSetter);
}
// bidirectional one-to-many
public static OneToMany oneToMany(Class elementType, Function> getter, BiConsumer> setter, Function oppositeGetter, BiConsumer oppositeSetter) {
return new OneToMany<>(elementType, getter, setter, oppositeGetter, oppositeSetter);
}
// bidirectional many-to-many
public static Relation.ManyToMany manyToMany(Class elementType, Function> getter, BiConsumer> setter, Function> oppositeGetter, BiConsumer> oppositeSetter) {
return new ManyToMany<>(elementType, getter, setter, oppositeGetter, oppositeSetter);
}
// unidirectional many-to-one
public static UniToOne manyToOne(Class elementType, Function getter, BiConsumer setter) {
return new UniToOne<>(elementType, getter, setter);
}
// unidirectional one-to-many
public static UniToMany oneToMany(Class elementType, Function> getter, BiConsumer> setter) {
return new UniToMany<>(elementType, getter, setter);
}
// unidirectional many-to-many
public static Relation.UniToMany manyToMany(Class elementType, Function> getter, BiConsumer> setter) {
return new UniToMany<>(elementType, getter, setter);
}
}