/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.querydsl.core;
import static com.querydsl.core.util.CollectionUtils.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.querydsl.core.types.*;
/**
* {@code DefaultQueryMetadata} is the default implementation of the {@link QueryMetadata} interface.
*
* <p>{@code DefaultQueryMetadata} is mutable, but {@link DefaultQueryMetadata#clone()} can be used to
* created deep copies to refine the state without modifying the initial instance.</p>
*
* @author tiwe
*/
public class DefaultQueryMetadata implements QueryMetadata, Cloneable {
private static final long serialVersionUID = 317736313966701232L;
private boolean distinct;
private Set<Expression<?>> exprInJoins = ImmutableSet.of();
private List<Expression<?>> groupBy = ImmutableList.of();
@Nullable
private Predicate having;
private List<JoinExpression> joins = ImmutableList.of();
private Expression<?> joinTarget;
private JoinType joinType;
@Nullable
private Predicate joinCondition;
private Set<JoinFlag> joinFlags = ImmutableSet.of();
private QueryModifiers modifiers = QueryModifiers.EMPTY;
private List<OrderSpecifier<?>> orderBy = ImmutableList.of();
@Nullable
private Expression<?> projection;
// NOTE : this is not necessarily serializable
private Map<ParamExpression<?>,Object> params = ImmutableMap.of();
private boolean unique;
@Nullable
private Predicate where;
private Set<QueryFlag> flags = ImmutableSet.of();
private boolean extractParams = true;
private boolean validate = false;
private ValidatingVisitor validatingVisitor = ValidatingVisitor.DEFAULT;
private static Predicate and(Predicate lhs, Predicate rhs) {
if (lhs == null) {
return rhs;
} else {
return ExpressionUtils.and(lhs, rhs);
}
}
/**
* Create an empty DefaultQueryMetadata instance
*/
public DefaultQueryMetadata() { }
/**
* Disable validation
*
* @return the current object
*/
public DefaultQueryMetadata noValidate() {
validate = false;
return this;
}
@Override
public void addFlag(QueryFlag flag) {
flags = addSorted(flags, flag);
}
@Override
public void addJoinFlag(JoinFlag flag) {
joinFlags = addSorted(joinFlags, flag);
}
@Override
public void addGroupBy(Expression<?> o) {
// group by elements can't be validated, since they can refer to projection elements
// that are declared later
groupBy = add(groupBy, o);
}
@Override
public void addHaving(Predicate e) {
if (e == null) {
return;
}
e = (Predicate) ExpressionUtils.extract(e);
if (e != null) {
// having elements can't be validated, since they can refer to projection elements
// that are declared later
having = and(having, e);
}
}
private void addLastJoin() {
if (joinTarget != null) {
joins = add(joins, new JoinExpression(joinType, joinTarget, joinCondition, joinFlags));
joinType = null;
joinTarget = null;
joinCondition = null;
joinFlags = ImmutableSet.of();
}
}
@Override
public void addJoin(JoinType joinType, Expression<?> expr) {
addLastJoin();
if (!exprInJoins.contains(expr)) {
if (expr instanceof Path && ((Path<?>) expr).getMetadata().isRoot()) {
exprInJoins = add(exprInJoins, expr);
} else {
validate(expr);
}
this.joinType = joinType;
this.joinTarget = expr;
} else if (validate) {
throw new IllegalStateException(expr + " is already used");
}
}
@Override
public void addJoinCondition(Predicate o) {
validate(o);
joinCondition = and(joinCondition, o);
}
@Override
public void addOrderBy(OrderSpecifier<?> o) {
// order specifiers can't be validated, since they can refer to projection elements
// that are declared later
orderBy = add(orderBy, o);
}
@Override
public void setProjection(Expression<?> o) {
validate(o);
projection = o;
}
@Override
public void addWhere(Predicate e) {
if (e == null) {
return;
}
e = (Predicate) ExpressionUtils.extract(e);
if (e != null) {
validate(e);
where = and(where, e);
}
}
@Override
public void clearOrderBy() {
orderBy = ImmutableList.of();
}
@Override
public void clearWhere() {
where = new BooleanBuilder();
}
@Override
public QueryMetadata clone() {
try {
DefaultQueryMetadata clone = (DefaultQueryMetadata) super.clone();
clone.exprInJoins = copyOf(exprInJoins);
clone.groupBy = copyOf(groupBy);
clone.having = having;
clone.joins = copyOf(joins);
clone.joinTarget = joinTarget;
clone.joinCondition = joinCondition;
clone.joinFlags = copyOf(joinFlags);
clone.joinType = joinType;
clone.modifiers = modifiers;
clone.orderBy = copyOf(orderBy);
clone.projection = projection;
clone.params = copyOf(params);
clone.where = where;
clone.flags = copyOfSorted(flags);
return clone;
} catch (CloneNotSupportedException e) {
throw new QueryException(e);
}
}
@Override
public List<Expression<?>> getGroupBy() {
return groupBy;
}
@Override
public Predicate getHaving() {
return having;
}
@Override
public List<JoinExpression> getJoins() {
if (joinTarget == null) {
return joins;
} else {
List<JoinExpression> j = Lists.newArrayList(joins);
j.add(new JoinExpression(joinType, joinTarget, joinCondition, joinFlags));
return j;
}
}
@Override
public QueryModifiers getModifiers() {
return modifiers;
}
@Override
public Map<ParamExpression<?>,Object> getParams() {
return params;
}
@Override
public List<OrderSpecifier<?>> getOrderBy() {
return orderBy;
}
@Override
public Expression<?> getProjection() {
return projection;
}
@Override
public Predicate getWhere() {
return where;
}
@Override
public boolean isDistinct() {
return distinct;
}
@Override
public boolean isUnique() {
return unique;
}
@Override
public void reset() {
params = ImmutableMap.of();
modifiers = QueryModifiers.EMPTY;
}
@Override
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
@Override
public void setLimit(Long limit) {
if (modifiers == null || modifiers.getOffset() == null) {
modifiers = QueryModifiers.limit(limit);
} else {
modifiers = new QueryModifiers(limit, modifiers.getOffset());
}
}
@Override
public void setModifiers(QueryModifiers restriction) {
if (restriction == null) {
throw new NullPointerException();
}
this.modifiers = restriction;
}
@Override
public void setOffset(Long offset) {
if (modifiers == null || modifiers.getLimit() == null) {
modifiers = QueryModifiers.offset(offset);
} else {
modifiers = new QueryModifiers(modifiers.getLimit(), offset);
}
}
@Override
public void setUnique(boolean unique) {
this.unique = unique;
}
@Override
public <T> void setParam(ParamExpression<T> param, T value) {
params = put(params, param, value);
}
@Override
public Set<QueryFlag> getFlags() {
return flags;
}
@Override
public boolean hasFlag(QueryFlag flag) {
return flags.contains(flag);
}
@Override
public void removeFlag(QueryFlag flag) {
flags = removeSorted(flags, flag);
}
private void validate(Expression<?> expr) {
if (extractParams) {
expr.accept(ParamsVisitor.DEFAULT, this);
}
if (validate) {
exprInJoins = expr.accept(validatingVisitor, exprInJoins);
}
}
@Override
public void setValidate(boolean v) {
this.validate = v;
}
public void setValidatingVisitor(ValidatingVisitor visitor) {
this.validatingVisitor = visitor;
}
@Override
public boolean equals(Object o) {
if (o instanceof QueryMetadata) {
QueryMetadata q = (QueryMetadata) o;
return q.getFlags().equals(flags)
&& q.getGroupBy().equals(groupBy)
&& Objects.equal(q.getHaving(), having)
&& q.isDistinct() == distinct
&& q.isUnique() == unique
&& q.getJoins().equals(getJoins())
&& q.getModifiers().equals(modifiers)
&& q.getOrderBy().equals(orderBy)
&& q.getParams().equals(params)
&& Objects.equal(q.getProjection(), projection)
&& Objects.equal(q.getWhere(), where);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(flags, groupBy, having, getJoins(), modifiers,
orderBy, params, projection, unique, where);
}
}