is.codion.framework.model.DefaultEntityConditionModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of codion-framework-model Show documentation
Show all versions of codion-framework-model Show documentation
Codion Application Framework
/*
* 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.model.condition.ConditionModel;
import is.codion.common.model.condition.ConditionModel.Operands;
import is.codion.common.model.condition.TableConditionModel;
import is.codion.common.observer.Observer;
import is.codion.common.state.StateObserver;
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.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
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;
import static java.util.stream.Collectors.toList;
/**
* A default {@link EntityConditionModel} implementation
*/
final class DefaultEntityConditionModel implements EntityConditionModel {
private final EntityDefinition entityDefinition;
private final EntityConnectionProvider connectionProvider;
private final TableConditionModel> tableConditionModel;
private final Event> conditionChangedEvent = Event.event();
private final NoneAggregateColumn noneAggregateColumn = new NoneAggregateColumn();
private final AggregateColumn aggregateColumn = new AggregateColumn();
DefaultEntityConditionModel(EntityType entityType, EntityConnectionProvider connectionProvider,
ConditionModelFactory> conditionModelFactory) {
this.entityDefinition = connectionProvider.entities().definition(requireNonNull(entityType, "entityType"));
this.connectionProvider = requireNonNull(connectionProvider, "connectionProvider");
this.tableConditionModel = TableConditionModel.tableConditionModel(createConditionModels(entityType, conditionModelFactory));
bindEvents();
}
@Override
public EntityType entityType() {
return entityDefinition.entityType();
}
@Override
public EntityConnectionProvider connectionProvider() {
return connectionProvider;
}
@Override
public boolean setEqualOperand(Attribute attribute, T operand) {
requireNonNull(attribute);
boolean aggregate = attribute instanceof Column && entityDefinition.columns().definition((Column>) attribute).aggregate();
Condition condition = aggregate ? having(Conjunction.AND) : where(Conjunction.AND);
tableConditionModel.optional(attribute)
.ifPresent(conditionModel -> {
conditionModel.operator().set(Operator.EQUAL);
conditionModel.operands().equal().set(operand);
conditionModel.enabled().set(operand != null);
});
return !condition.equals(aggregate ? having(Conjunction.AND) : where(Conjunction.AND));
}
@Override
public boolean setInOperands(Attribute attribute, Collection operands) {
requireNonNull(attribute);
requireNonNull(operands);
boolean aggregate = attribute instanceof Column && entityDefinition.columns().definition((Column>) attribute).aggregate();
Condition condition = aggregate ? having(Conjunction.AND) : where(Conjunction.AND);
tableConditionModel.optional(attribute)
.map(conditionModel -> (ConditionModel) conditionModel)
.ifPresent(conditionModel -> {
conditionModel.operator().set(Operator.IN);
conditionModel.operands().in().set(operands);
conditionModel.enabled().set(!operands.isEmpty());
});
return !condition.equals(aggregate ? having(Conjunction.AND) : where(Conjunction.AND));
}
@Override
public Condition where(Conjunction conjunction) {
return createCondition(conjunction, noneAggregateColumn);
}
@Override
public Condition having(Conjunction conjunction) {
return createCondition(conjunction, aggregateColumn);
}
@Override
public Map, ConditionModel>> get() {
return tableConditionModel.get();
}
@Override
public ConditionModel get(Attribute> identifier) {
return tableConditionModel.get(requireNonNull(identifier));
}
@Override
public Optional> optional(Attribute> identifier) {
return tableConditionModel.optional(requireNonNull(identifier));
}
@Override
public ConditionModel attribute(Attribute attribute) {
return get(attribute);
}
@Override
public StateObserver enabled() {
return tableConditionModel.enabled();
}
@Override
public Observer> changed() {
return tableConditionModel.changed();
}
@Override
public void clear() {
tableConditionModel.clear();
}
private Condition createCondition(Conjunction conjunction, Predicate> columnType) {
List conditions = tableConditionModel.get().entrySet().stream()
.filter(entry -> columnType.test(entry.getKey()))
.filter(entry -> entry.getValue().enabled().get())
.map(entry -> condition(entry.getValue(), entry.getKey()))
.collect(toList());
switch (conditions.size()) {
case 0:
return all(entityDefinition.entityType());
case 1:
return conditions.get(0);
default:
return combination(conjunction, conditions);
}
}
private void bindEvents() {
tableConditionModel.get().values()
.forEach(conditionModel -> conditionModel.changed().addListener(conditionChangedEvent));
}
private Map, ConditionModel>> createConditionModels(EntityType entityType,
ConditionModelFactory> conditionModelFactory) {
Map, ConditionModel>> models = new HashMap<>();
EntityDefinition definition = connectionProvider.entities().definition(entityType);
definition.columns().definitions().forEach(columnDefinition ->
conditionModelFactory.create(columnDefinition.attribute())
.ifPresent(conditionModel -> models.put(columnDefinition.attribute(), conditionModel)));
definition.foreignKeys().definitions().forEach(foreignKeyDefinition ->
conditionModelFactory.create(foreignKeyDefinition.attribute())
.ifPresent(conditionModel -> models.put(foreignKeyDefinition.attribute(), conditionModel)));
return models;
}
private static Condition condition(ConditionModel> conditionModel, Attribute> identifier) {
if (identifier instanceof ForeignKey) {
return foreignKeyCondition((ConditionModel) conditionModel, (ForeignKey) identifier);
}
return columnCondition(conditionModel, identifier);
}
private static Condition foreignKeyCondition(ConditionModel conditionModel, ForeignKey foreignKey) {
Entity equalOperand = conditionModel.operands().equal().get();
Collection inOperands = conditionModel.operands().in().get();
switch (conditionModel.operator().get()) {
case EQUAL:
return equalOperand == null ? foreignKey.isNull() : foreignKey.equalTo(equalOperand);
case IN:
return inOperands.isEmpty() ? foreignKey.isNull() : foreignKey.in(inOperands);
case NOT_EQUAL:
return equalOperand == null ? foreignKey.isNotNull() : foreignKey.notEqualTo(equalOperand);
case NOT_IN:
return inOperands.isEmpty() ? foreignKey.isNotNull() : foreignKey.notIn(inOperands);
default:
throw new IllegalArgumentException("Unsupported operator: " + conditionModel.operator().get() + " for foreign key condition");
}
}
private static ColumnCondition columnCondition(ConditionModel conditionModel, Attribute> identifier) {
Column column = (Column) identifier;
Operands operands = conditionModel.operands();
switch (conditionModel.operator().get()) {
case EQUAL:
return equalCondition(conditionModel, column);
case NOT_EQUAL:
return notEqualCondition(conditionModel, column);
case LESS_THAN:
return column.lessThan(operands.upperBound().get());
case LESS_THAN_OR_EQUAL:
return column.lessThanOrEqualTo(operands.upperBound().get());
case GREATER_THAN:
return column.greaterThan(operands.lowerBound().get());
case GREATER_THAN_OR_EQUAL:
return column.greaterThanOrEqualTo(operands.lowerBound().get());
case BETWEEN_EXCLUSIVE:
return column.betweenExclusive(operands.lowerBound().get(), operands.upperBound().get());
case BETWEEN:
return column.between(operands.lowerBound().get(), operands.upperBound().get());
case NOT_BETWEEN_EXCLUSIVE:
return column.notBetweenExclusive(operands.lowerBound().get(), operands.upperBound().get());
case NOT_BETWEEN:
return column.notBetween(operands.lowerBound().get(), operands.upperBound().get());
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(ConditionModel conditionModel,
Column column) {
T equalOperand = conditionModel.operands().equal().get();
if (equalOperand == null) {
return column.isNull();
}
if (column.type().isString()) {
return singleStringEqualCondition(conditionModel, column, (String) equalOperand);
}
if (column.type().isCharacter()) {
return singleCharacterEqualCondition(conditionModel, column, (Character) equalOperand);
}
return column.equalTo(equalOperand);
}
private static ColumnCondition notEqualCondition(ConditionModel conditionModel,
Column column) {
T equalOperand = conditionModel.operands().equal().get();
if (equalOperand == null) {
return column.isNotNull();
}
if (column.type().isString()) {
return singleStringNotEqualCondition(conditionModel, column, (String) equalOperand);
}
if (column.type().isCharacter()) {
return singleCharacterNotEqualCondition(conditionModel, column, (Character) equalOperand);
}
return column.notEqualTo(equalOperand);
}
private static ColumnCondition singleStringEqualCondition(ConditionModel 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(ConditionModel conditionModel,
Column column, Character value) {
return conditionModel.caseSensitive().get() ? column.equalTo((T) value) : (ColumnCondition) column.equalToIgnoreCase(value);
}
private static ColumnCondition singleStringNotEqualCondition(ConditionModel 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(ConditionModel conditionModel,
Column column, Character value) {
return conditionModel.caseSensitive().get() ? column.notEqualTo((T) value) : (ColumnCondition) column.notEqualToIgnoreCase(value);
}
private static ColumnCondition inCondition(ConditionModel conditionModel, Column column) {
if (column.type().isString()) {
Column stringColumn = (Column) column;
Collection inOperands = (Collection) conditionModel.operands().in().get();
return (ColumnCondition) (conditionModel.caseSensitive().get() ?
stringColumn.in(inOperands) :
stringColumn.inIgnoreCase(inOperands));
}
return column.in(conditionModel.operands().in().get());
}
private static ColumnCondition notInCondition(ConditionModel conditionModel, Column column) {
if (column.type().isString()) {
Column stringColumn = (Column) column;
Collection inOperands = (Collection) conditionModel.operands().in().get();
return (ColumnCondition) (conditionModel.caseSensitive().get() ?
stringColumn.notIn(inOperands) :
stringColumn.notInIgnoreCase(inOperands));
}
return column.notIn(conditionModel.operands().in().get());
}
private static boolean containsWildcards(String value) {
return value != null && (value.contains("%") || value.contains("_"));
}
private final class AggregateColumn implements Predicate> {
@Override
public boolean test(Attribute> attribute) {
return (attribute instanceof Column) &&
entityDefinition.columns().definition((Column>) attribute).aggregate();
}
}
private final class NoneAggregateColumn implements Predicate> {
@Override
public boolean test(Attribute> attribute) {
return !(attribute instanceof Column) ||
!entityDefinition.columns().definition((Column>) attribute).aggregate();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy