All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xlrit.gears.base.meta.Relation Maven / Gradle / Ivy

There is a newer version: 1.17.5
Show newest version
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 {
    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 BV get(AE owner) {
        // TODO handle null, support optionality
        return getter.apply(owner);
    }
    public abstract void set(AE owner, BV value);

    @Override
    public BV apply(AE ae) {
        return get(ae);
    }

    // === *-to-One relations === //

    /** Represents a unidirectional one/many-to-one relation. */
    public static class UniToOne extends Relation {
        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 Relation {
        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 Relation,B> {
        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);
        }
        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);
    }
}