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.6
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, 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);
	}
}