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

is.codion.framework.model.DefaultEntityTableConditionModel Maven / Gradle / Ivy

There is a newer version: 0.18.22
Show newest version
/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see .
 *
 * Copyright (c) 2016 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.model;

import is.codion.common.Conjunction;
import is.codion.common.Operator;
import is.codion.common.event.Event;
import is.codion.common.event.EventObserver;
import is.codion.common.model.table.ColumnConditionModel;
import is.codion.common.model.table.TableConditionModel;
import is.codion.common.value.Value;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.EntityDefinition;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.attribute.Attribute;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import is.codion.framework.domain.entity.condition.ColumnCondition;
import is.codion.framework.domain.entity.condition.Condition;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static is.codion.common.model.table.TableConditionModel.tableConditionModel;
import static is.codion.framework.domain.entity.condition.Condition.all;
import static is.codion.framework.domain.entity.condition.Condition.combination;
import static java.util.Objects.requireNonNull;

/**
 * A default EntityTableConditionModel implementation
 */
final class DefaultEntityTableConditionModel implements EntityTableConditionModel {

	private static final Supplier NULL_CONDITION_SUPPLIER = () -> null;

	private final EntityDefinition entityDefinition;
	private final EntityConnectionProvider connectionProvider;
	private final TableConditionModel> conditionModel;
	private final Event conditionChangedEvent = Event.event();
	private final Value> additionalWhere = Value.builder()
					.nonNull(NULL_CONDITION_SUPPLIER)
					.build();
	private final Value> additionalHaving = Value.builder()
					.nonNull(NULL_CONDITION_SUPPLIER)
					.build();
	private final NoneAggregatePredicate noneAggregatePredicate = new NoneAggregatePredicate();
	private final AggregatePredicate aggregatePredicate = new AggregatePredicate();

	DefaultEntityTableConditionModel(EntityType entityType, EntityConnectionProvider connectionProvider,
																	 ColumnConditionModel.Factory> conditionModelFactory) {
		this.entityDefinition = connectionProvider.entities().definition(requireNonNull(entityType, "entityType"));
		this.connectionProvider = requireNonNull(connectionProvider, "connectionProvider");
		this.conditionModel = tableConditionModel(createConditionModels(entityType, conditionModelFactory));
		bindEvents();
	}

	@Override
	public EntityType entityType() {
		return entityDefinition.entityType();
	}

	@Override
	public EntityConnectionProvider connectionProvider() {
		return connectionProvider;
	}

	@Override
	public  boolean setEqualConditionValue(Attribute attribute, T value) {
		requireNonNull(attribute);
		boolean aggregateColumn = attribute instanceof Column && entityDefinition.columns().definition((Column) attribute).aggregate();
		Condition condition = aggregateColumn ? having(Conjunction.AND) : where(Conjunction.AND);
		ColumnConditionModel, T> columnConditionModel =
						(ColumnConditionModel, T>) conditionModel.conditionModels().get(attribute);
		if (columnConditionModel != null) {
			columnConditionModel.operator().set(Operator.EQUAL);
			columnConditionModel.setEqualValue(value);
			columnConditionModel.enabled().set(value != null);
		}
		return !condition.equals(aggregateColumn ? having(Conjunction.AND) : where(Conjunction.AND));
	}

	@Override
	public  boolean setInConditionValues(Attribute attribute, Collection values) {
		requireNonNull(attribute);
		requireNonNull(values);
		boolean aggregateColumn = attribute instanceof Column && entityDefinition.columns().definition((Column) attribute).aggregate();
		Condition condition = aggregateColumn ? having(Conjunction.AND) : where(Conjunction.AND);
		ColumnConditionModel, T> columnConditionModel =
						(ColumnConditionModel, T>) conditionModel.conditionModels().get(attribute);
		if (columnConditionModel != null) {
			columnConditionModel.operator().set(Operator.IN);
			columnConditionModel.setInValues(values);
			columnConditionModel.enabled().set(!values.isEmpty());
		}
		return !condition.equals(aggregateColumn ? having(Conjunction.AND) : where(Conjunction.AND));
	}

	@Override
	public Condition where(Conjunction conjunction) {
		requireNonNull(conjunction);
		Collection conditions = conditions(noneAggregatePredicate, additionalWhere.get().get());

		return conditions.isEmpty() ? all(entityDefinition.entityType()) : combination(conjunction, conditions);
	}

	@Override
	public Condition having(Conjunction conjunction) {
		requireNonNull(conjunction);
		Collection conditions = conditions(aggregatePredicate, additionalHaving.get().get());

		return conditions.isEmpty() ? all(entityDefinition.entityType()) : combination(conjunction, conditions);
	}

	@Override
	public Value> additionalWhere() {
		return additionalWhere;
	}

	@Override
	public Value> additionalHaving() {
		return additionalHaving;
	}

	@Override
	public Map, ColumnConditionModel, ?>> conditionModels() {
		return conditionModel.conditionModels();
	}

	@Override
	public  ColumnConditionModel, T> conditionModel(Attribute identifier) {
		return conditionModel.conditionModel(identifier);
	}

	@Override
	public  ColumnConditionModel, T> attributeModel(Attribute attribute) {
		return conditionModel(attribute);
	}

	@Override
	public boolean enabled() {
		return conditionModel.enabled();
	}

	@Override
	public boolean enabled(Attribute identifier) {
		return conditionModel.enabled(identifier);
	}

	@Override
	public EventObserver conditionChangedEvent() {
		return conditionModel.conditionChangedEvent();
	}

	@Override
	public void clear() {
		conditionModel.clear();
	}

	private Collection conditions(Predicate> conditionModelTypePredicate, Condition additionalCondition) {
		List conditions = conditionModel.conditionModels().values().stream()
						.filter(model -> model.enabled().get())
						.filter(conditionModelTypePredicate)
						.map(DefaultEntityTableConditionModel::condition)
						.collect(Collectors.toCollection(ArrayList::new));
		if (additionalCondition != null) {
			conditions.add(additionalCondition);
		}

		return conditions;
	}

	private void bindEvents() {
		conditionModel.conditionModels().values().forEach(columnConditionModel ->
						columnConditionModel.conditionChangedEvent().addListener(conditionChangedEvent));
		additionalWhere.addListener(conditionChangedEvent);
		additionalHaving.addListener(conditionChangedEvent);
	}

	private Collection, ?>> createConditionModels(EntityType entityType,
																																									ColumnConditionModel.Factory> conditionModelFactory) {
		Collection, ?>> models = new ArrayList<>();
		EntityDefinition definition = connectionProvider.entities().definition(entityType);
		definition.columns().definitions().forEach(columnDefinition ->
						conditionModelFactory.createConditionModel(columnDefinition.attribute())
										.ifPresent(models::add));
		definition.foreignKeys().definitions().forEach(foreignKeyDefinition ->
						conditionModelFactory.createConditionModel(foreignKeyDefinition.attribute())
										.ifPresent(models::add));

		return models.stream()
						.map(model -> (ColumnConditionModel, ?>) model)
						.collect(Collectors.toList());
	}

	private static Condition condition(ColumnConditionModel conditionModel) {
		if (conditionModel.identifier() instanceof ForeignKey) {
			return foreignKeyCondition((ColumnConditionModel) conditionModel);
		}

		return columnCondition(conditionModel);
	}

	private static Condition foreignKeyCondition(ColumnConditionModel conditionModel) {
		ForeignKey foreignKey = (ForeignKey) conditionModel.identifier();
		Entity equalValue = conditionModel.equalValue().get();
		Collection inValues = conditionModel.inValues().get();
		switch (conditionModel.operator().get()) {
			case EQUAL:
				return equalValue == null ? foreignKey.isNull() : foreignKey.equalTo(equalValue);
			case IN:
				return inValues.isEmpty() ? foreignKey.isNull() : foreignKey.in(inValues);
			case NOT_EQUAL:
				return equalValue == null ? foreignKey.isNotNull() : foreignKey.notEqualTo(equalValue);
			case NOT_IN:
				return inValues.isEmpty() ? foreignKey.isNotNull() : foreignKey.notIn(inValues);
			default:
				throw new IllegalArgumentException("Unsupported operator: " + conditionModel.operator().get() + " for foreign key condition");
		}
	}

	private static  ColumnCondition columnCondition(ColumnConditionModel conditionModel) {
		Column column = (Column) conditionModel.identifier();
		switch (conditionModel.operator().get()) {
			case EQUAL:
				return equalCondition(conditionModel, column);
			case NOT_EQUAL:
				return notEqualCondition(conditionModel, column);
			case LESS_THAN:
				return column.lessThan(conditionModel.getUpperBound());
			case LESS_THAN_OR_EQUAL:
				return column.lessThanOrEqualTo(conditionModel.getUpperBound());
			case GREATER_THAN:
				return column.greaterThan(conditionModel.getLowerBound());
			case GREATER_THAN_OR_EQUAL:
				return column.greaterThanOrEqualTo(conditionModel.getLowerBound());
			case BETWEEN_EXCLUSIVE:
				return column.betweenExclusive(conditionModel.getLowerBound(), conditionModel.getUpperBound());
			case BETWEEN:
				return column.between(conditionModel.getLowerBound(), conditionModel.getUpperBound());
			case NOT_BETWEEN_EXCLUSIVE:
				return column.notBetweenExclusive(conditionModel.getLowerBound(), conditionModel.getUpperBound());
			case NOT_BETWEEN:
				return column.notBetween(conditionModel.getLowerBound(), conditionModel.getUpperBound());
			case IN:
				return inCondition(conditionModel, column);
			case NOT_IN:
				return notInCondition(conditionModel, column);
			default:
				throw new IllegalArgumentException("Unknown operator: " + conditionModel.operator().get());
		}
	}

	private static  ColumnCondition equalCondition(ColumnConditionModel conditionModel,
																											 Column column) {
		T equalValue = conditionModel.getEqualValue();
		if (equalValue == null) {
			return column.isNull();
		}
		if (column.type().isString()) {
			return singleStringEqualCondition(conditionModel, column, (String) equalValue);
		}
		if (column.type().isCharacter()) {
			return singleCharacterEqualCondition(conditionModel, column, (Character) equalValue);
		}

		return column.equalTo(equalValue);
	}

	private static  ColumnCondition notEqualCondition(ColumnConditionModel conditionModel,
																													Column column) {
		T equalValue = conditionModel.getEqualValue();
		if (equalValue == null) {
			return column.isNotNull();
		}
		if (column.type().isString()) {
			return singleStringNotEqualCondition(conditionModel, column, (String) equalValue);
		}
		if (column.type().isCharacter()) {
			return singleCharacterNotEqualCondition(conditionModel, column, (Character) equalValue);
		}

		return column.notEqualTo(equalValue);
	}

	private static  ColumnCondition singleStringEqualCondition(ColumnConditionModel conditionModel,
																																	 Column column, String value) {
		boolean caseSensitive = conditionModel.caseSensitive().get();
		if (containsWildcards(value)) {
			return (ColumnCondition) (caseSensitive ? column.like(value) : column.likeIgnoreCase(value));
		}

		return caseSensitive ? column.equalTo((T) value) : (ColumnCondition) column.equalToIgnoreCase(value);
	}

	private static  ColumnCondition singleCharacterEqualCondition(ColumnConditionModel conditionModel,
																																			Column column, Character value) {
		return conditionModel.caseSensitive().get() ? column.equalTo((T) value) : (ColumnCondition) column.equalToIgnoreCase(value);
	}

	private static  ColumnCondition singleStringNotEqualCondition(ColumnConditionModel conditionModel,
																																			Column column, String value) {
		boolean caseSensitive = conditionModel.caseSensitive().get();
		if (containsWildcards(value)) {
			return (ColumnCondition) (caseSensitive ? column.notLike(value) : column.notLikeIgnoreCase(value));
		}

		return caseSensitive ? column.notEqualTo((T) value) : (ColumnCondition) column.notEqualToIgnoreCase(value);
	}

	private static  ColumnCondition singleCharacterNotEqualCondition(ColumnConditionModel conditionModel,
																																				 Column column, Character value) {
		return conditionModel.caseSensitive().get() ? column.notEqualTo((T) value) : (ColumnCondition) column.notEqualToIgnoreCase(value);
	}

	private static  ColumnCondition inCondition(ColumnConditionModel conditionModel, Column column) {
		if (column.type().isString()) {
			Column stringColumn = (Column) column;
			Collection inValues = (Collection) conditionModel.getInValues();

			return (ColumnCondition) (conditionModel.caseSensitive().get() ?
							stringColumn.in(inValues) :
							stringColumn.inIgnoreCase(inValues));
		}

		return column.in(conditionModel.getInValues());
	}

	private static  ColumnCondition notInCondition(ColumnConditionModel conditionModel, Column column) {
		if (column.type().isString()) {
			Column stringColumn = (Column) column;
			Collection inValues = (Collection) conditionModel.getInValues();

			return (ColumnCondition) (conditionModel.caseSensitive().get() ?
							stringColumn.notIn(inValues) :
							stringColumn.notInIgnoreCase(inValues));
		}

		return column.notIn(conditionModel.getInValues());
	}

	private static boolean containsWildcards(String value) {
		return value != null && (value.contains("%") || value.contains("_"));
	}

	private final class AggregatePredicate implements Predicate> {

		@Override
		public boolean test(ColumnConditionModel conditionModel) {
			return (conditionModel.identifier() instanceof Column) &&
							entityDefinition.columns().definition((Column) conditionModel.identifier()).aggregate();
		}
	}

	private final class NoneAggregatePredicate implements Predicate> {

		@Override
		public boolean test(ColumnConditionModel conditionModel) {
			return !(conditionModel.identifier() instanceof Column) ||
							!entityDefinition.columns().definition((Column) conditionModel.identifier()).aggregate();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy