/*
* 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.types.dsl;
import java.lang.reflect.Array;
import java.util.AbstractMap.SimpleEntry;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.PathMetadataFactory;
/**
* {@code PathBuilder} is an extension to {@link EntityPathBase} for dynamic path construction
*
* <p>Usage example:</p>
*
* <pre>{@code
* PathBuilder<User> user = new PathBuilder<User>(User.class, "user");
* Predicate filter = user.getString("firstName").eq("Bob");
* List<User> users = query.from(user).where(filter).select(user).fetch();
* }</pre>
*
* @author tiwe
*
* @param <T> expression type
*/
public class PathBuilder<T> extends EntityPathBase<T> {
private static final long serialVersionUID = -1666357914232685088L;
private final ConcurrentHashMap<SimpleEntry<String, Class<?>>, PathBuilder<?>> properties = new ConcurrentHashMap<SimpleEntry<String, Class<?>>, PathBuilder<?>>();
private final ConcurrentHashMap<Path<?>, Object> propertyMetadata = new ConcurrentHashMap<Path<?>, Object>();
private final PathBuilderValidator validator;
/**
* Creates a new PathBuilder instance
*
* @param type type of the expression
* @param pathMetadata path metadata
* @param validator validator for property creation
*/
public PathBuilder(Class<? extends T> type, PathMetadata pathMetadata, PathBuilderValidator validator) {
super(type, pathMetadata);
this.validator = validator;
}
/**
* Creates a new PathBuilder instance
*
* @param type type of the expression
* @param pathMetadata path metadata
*/
public PathBuilder(Class<? extends T> type, PathMetadata pathMetadata) {
this(type, pathMetadata, PathBuilderValidator.DEFAULT);
}
/**
* Creates a new PathBuilder instance
*
* @param type type of the expression
* @param variable variable name
* @param validator validator for property creation
*/
public PathBuilder(Class<? extends T> type, String variable, PathBuilderValidator validator) {
this(type, PathMetadataFactory.forVariable(variable), validator);
}
/**
* Creates a new PathBuilder instance
*
* @param type type of the expression
* @param variable variable name
*/
public PathBuilder(Class<? extends T> type, String variable) {
this(type, PathMetadataFactory.forVariable(variable), PathBuilderValidator.DEFAULT);
}
private <P extends Path<?>> P addMetadataOf(P newPath, Path<?> path) {
if (path.getMetadata().getParent() instanceof EntityPath) {
EntityPath<?> parent = (EntityPath<?>) path.getMetadata().getParent();
if (parent.getMetadata(path) != null) {
propertyMetadata.putIfAbsent(newPath, parent.getMetadata(path));
}
}
return newPath;
}
@SuppressWarnings("unchecked")
protected <A> Class<? extends A> validate(String property, Class<A> propertyType) {
Class<A> validatedType = (Class<A>) validator.validate(getType(), property, propertyType);
if (validatedType != null) {
return validatedType;
} else {
throw new IllegalArgumentException("Illegal property " + property);
}
}
@Override
public Object getMetadata(Path<?> property) {
return propertyMetadata.get(property);
}
/**
* Create a PathBuilder instance for the given property
*
* @param property property name
* @return property path
*/
@SuppressWarnings("unchecked")
public PathBuilder<Object> get(String property) {
SimpleEntry<String, Class<?>> entry = new SimpleEntry<String, Class<?>>(property, Object.class);
PathBuilder<Object> path = (PathBuilder<Object>) properties.get(entry);
PathBuilder<?> existingPath = null;
if (path == null) {
Class<?> vtype = validate(property, Object.class);
path = new PathBuilder<Object>(vtype, forProperty(property), validator);
existingPath = properties.putIfAbsent(entry, path);
}
return existingPath == null ? path : (PathBuilder<Object>) existingPath;
}
/**
* Create a PathBuilder for the given property with the given type
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
@SuppressWarnings("unchecked")
public <A> PathBuilder<A> get(String property, Class<A> type) {
SimpleEntry<String, Class<?>> entry = new SimpleEntry<String, Class<?>>(property, type);
PathBuilder<A> path = (PathBuilder<A>) properties.get(entry);
PathBuilder<?> existingPath = null;
if (path == null) {
Class<? extends A> vtype = validate(property, type);
path = new PathBuilder<A>(vtype, forProperty(property), validator);
existingPath = properties.putIfAbsent(entry, path);
}
return existingPath == null ? path : (PathBuilder<A>) existingPath;
}
/**
* Create a ArrayPath instance for the given property and the given array type
*
* @param <A>
* @param <E>
* @param property property name
* @param type property type
* @return property path
*/
public <A, E> ArrayPath<A, E> getArray(String property, Class<A> type) {
validate(property, Array.newInstance(type, 0).getClass());
return super.createArray(property, type);
}
/**
* Create a new Boolean typed path
*
* @param path existing path
* @return property path
*/
public BooleanPath get(BooleanPath path) {
BooleanPath newPath = getBoolean(toString(path));
return addMetadataOf(newPath, path);
}
/**
* Create a new Boolean typed path
*
* @param propertyName property name
* @return property path
*/
public BooleanPath getBoolean(String propertyName) {
validate(propertyName, Boolean.class);
return super.createBoolean(propertyName);
}
/**
* Create a new Collection typed path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
public <A> CollectionPath<A, PathBuilder<A>> getCollection(String property, Class<A> type) {
return this.<A, PathBuilder<A>>getCollection(property, type, PathBuilder.class);
}
/**
* Create a new Collection typed path
*
* @param <A>
* @param <E>
* @param property property name
* @param type property type
* @param queryType expression type
* @return property path
*/
public <A, E extends SimpleExpression<A>> CollectionPath<A, E> getCollection(String property, Class<A> type, Class<? super E> queryType) {
validate(property, Collection.class);
return super.createCollection(property, type, queryType, PathInits.DIRECT);
}
/**
* Create a new Comparable typed path
*
* @param <A>
* @param path existing path
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> ComparablePath<A> get(ComparablePath<A> path) {
ComparablePath<A> newPath = getComparable(toString(path), (Class<A>) path.getType());
return addMetadataOf(newPath, path);
}
/**
* Create a new Comparable typed path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> ComparablePath<A> getComparable(String property, Class<A> type) {
Class<? extends A> vtype = validate(property, type);
return super.createComparable(property, (Class<? super A>) vtype);
}
/**
* Create a new Date path
*
* @param <A>
* @param path existing path
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> DatePath<A> get(DatePath<A> path) {
DatePath<A> newPath = getDate(toString(path), (Class<A>) path.getType());
return addMetadataOf(newPath, path);
}
/**
* Create a new Date path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> DatePath<A> getDate(String property, Class<A> type) {
Class<? extends A> vtype = validate(property, type);
return super.createDate(property, (Class<? super A>) vtype);
}
/**
* Create a new DateTime path
*
* @param <A>
* @param path existing path
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> DateTimePath<A> get(DateTimePath<A> path) {
DateTimePath<A> newPath = getDateTime(toString(path), (Class<A>) path.getType());
return addMetadataOf(newPath, path);
}
/**
* Create a new DateTime path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> DateTimePath<A> getDateTime(String property, Class<A> type) {
Class<? extends A> vtype = validate(property, type);
return super.createDateTime(property, (Class<? super A>) vtype);
}
/**
* Create a new Enum path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
public <A extends Enum<A>> EnumPath<A> getEnum(String property, Class<A> type) {
validate(property, type);
return super.createEnum(property, type);
}
/**
* Create a new Enum path
*
* @param <A>
* @param path existing path
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Enum<A>> EnumPath<A> get(EnumPath<A> path) {
EnumPath<A> newPath = getEnum(toString(path), (Class<A>) path.getType());
return addMetadataOf(newPath, path);
}
/**
* Create a new List typed path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
public <A> ListPath<A, PathBuilder<A>> getList(String property, Class<A> type) {
return this.<A, PathBuilder<A>>getList(property, type, PathBuilder.class);
}
/**
* Create a new List typed path
*
* @param <A>
* @param <E>
* @param property property name
* @param type property type
* @param queryType expression type
* @return property path
*/
public <A, E extends SimpleExpression<A>> ListPath<A, E> getList(String property, Class<A> type, Class<? super E> queryType) {
validate(property, List.class);
return super.createList(property, type, queryType, PathInits.DIRECT);
}
/**
* Create a new Map typed path
*
* @param <K>
* @param <V>
* @param property property name
* @param key key type
* @param value value type
* @return property path
*/
public <K, V> MapPath<K, V, PathBuilder<V>> getMap(String property, Class<K> key, Class<V> value) {
return this.<K, V, PathBuilder<V>>getMap(property, key, value, PathBuilder.class);
}
/**
* Create a new Map typed path
*
* @param <K>
* @param <V>
* @param <E>
* @param property property name
* @param key key type
* @param value value type
* @param queryType vaue expression type
* @return property path
*/
public <K, V, E extends SimpleExpression<V>> MapPath<K, V, E> getMap(String property, Class<K> key, Class<V> value, Class<? super E> queryType) {
validate(property, Map.class);
return super.createMap(property, key, value, queryType);
}
/**
* Create a new Number typed path
*
* @param <A>
* @param path existing path
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Number & Comparable<?>> NumberPath<A> get(NumberPath<A> path) {
NumberPath<A> newPath = getNumber(toString(path), (Class<A>) path.getType());
return addMetadataOf(newPath, path);
}
/**
* Create a new Number typed path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Number & Comparable<?>> NumberPath<A> getNumber(String property, Class<A> type) {
Class<? extends A> vtype = validate(property, type);
return super.createNumber(property, (Class<? super A>) vtype);
}
/**
* Create a new Set typed path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
public <A> SetPath<A, PathBuilder<A>> getSet(String property, Class<A> type) {
return this.<A, PathBuilder<A>>getSet(property, type, PathBuilder.class);
}
/**
* Create a new Set typed path
*
* @param <A>
* @param <E>
* @param property property name
* @param type property type
* @param queryType expression type
* @return property path
*/
public <A, E extends SimpleExpression<A>> SetPath<A, E> getSet(String property, Class<A> type, Class<? super E> queryType) {
validate(property, Set.class);
return super.createSet(property, type, queryType, PathInits.DIRECT);
}
/**
* Create a new Simple path
*
* @param <A>
* @param path existing path
* @return property path
*/
@SuppressWarnings("unchecked")
public <A> SimplePath<A> get(Path<A> path) {
SimplePath<A> newPath = getSimple(toString(path), (Class<A>) path.getType());
return addMetadataOf(newPath, path);
}
/**
* Create a new Simple path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
@SuppressWarnings("unchecked")
public <A> SimplePath<A> getSimple(String property, Class<A> type) {
Class<? extends A> vtype = validate(property, type);
return super.createSimple(property, (Class<? super A>) vtype);
}
/**
* Create a new String typed path
*
* @param path existing path
* @return property path
*/
public StringPath get(StringPath path) {
StringPath newPath = getString(toString(path));
return addMetadataOf(newPath, path);
}
/**
* Create a new String typed path
*
* @param property property name
* @return property path
*/
public StringPath getString(String property) {
validate(property, String.class);
return super.createString(property);
}
/**
* Create a new Time typed path
*
* @param <A>
* @param path existing path
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> TimePath<A> get(TimePath<A> path) {
TimePath<A> newPath = getTime(toString(path), (Class<A>) path.getType());
return addMetadataOf(newPath, path);
}
/**
* Create a new Time typed path
*
* @param <A>
* @param property property name
* @param type property type
* @return property path
*/
@SuppressWarnings("unchecked")
public <A extends Comparable<?>> TimePath<A> getTime(String property, Class<A> type) {
Class<? extends A> vtype = validate(property, type);
return super.createTime(property, (Class<? super A>) vtype);
}
/**
* Get the String representation of the last path element
*
* @param path path
* @return String representation
*/
private String toString(Path<?> path) {
return path.getMetadata().getElement().toString();
}
}