#566 - Refactor internals - Initial tidy on OrmQueryDetail and OrmQueryDetailProperties

This commit is contained in:
Robin Bygrave
2016-02-17 10:52:55 +13:00
parent 69bf1758bc
commit 2fcaab44c6
21 changed files with 833 additions and 313 deletions
@@ -92,7 +92,7 @@ public class PathProperties {
/**
* Get the properties for a given path.
*/
public Set<String> get(String path) {
public LinkedHashSet<String> get(String path) {
Props props = pathMap.get(path);
return props == null ? null : props.getProperties();
}
@@ -109,7 +109,7 @@ public class PathProperties {
/**
* Set the properties for a given path.
*/
public void put(String path, Set<String> properties) {
public void put(String path, LinkedHashSet<String> properties) {
pathMap.put(path, new Props(this, null, path, properties));
}
@@ -160,9 +160,9 @@ public class PathProperties {
private final String parentPath;
private final String path;
private final Set<String> propSet;
private final LinkedHashSet<String> propSet;
private Props(PathProperties owner, String parentPath, String path, Set<String> propSet) {
private Props(PathProperties owner, String parentPath, String path, LinkedHashSet<String> propSet) {
this.owner = owner;
this.path = path;
this.parentPath = parentPath;
@@ -195,7 +195,7 @@ public class PathProperties {
/**
* Return the properties for this property set.
*/
public Set<String> getProperties() {
public LinkedHashSet<String> getProperties() {
return propSet;
}
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.api;
import java.util.Set;
/**
* Used to build HashQueryPlan instances.
*/
@@ -33,6 +35,21 @@ public class HashQueryPlanBuilder {
return this;
}
/**
* Add the set with order being important.
*/
public HashQueryPlanBuilder addOrdered(Set<?> set) {
if (set == null) {
add(false);
} else {
add(true);
for (Object o : set) {
add(o);
}
}
return this;
}
/**
* Add an integer to the hash calculation.
*/
@@ -1038,7 +1038,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return findId(query, t);
}
private <T> SpiOrmQueryRequest<T> createQueryRequest(Type type, Query<T> query, Transaction t) {
<T> SpiOrmQueryRequest<T> createQueryRequest(Type type, Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(type);
@@ -73,9 +73,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -347,7 +347,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final BeanDescriptorJsonHelp<T> jsonHelp;
private final String defaultSelectClause;
private final Set<String> defaultSelectClauseSet;
private final LinkedHashSet<String> defaultSelectClauseSet;
private SpiEbeanServer ebeanServer;
@@ -856,7 +856,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
/**
* Return the default select clause already parsed into an ordered Set.
*/
public Set<String> getDefaultSelectClauseSet() {
public LinkedHashSet<String> getDefaultSelectClauseSet() {
return defaultSelectClauseSet;
}
@@ -33,7 +33,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Describes Beans including their deployment information.
@@ -763,7 +762,7 @@ public class DeployBeanDescriptor<T> {
/**
* Parse the include separating by comma or semicolon.
*/
public Set<String> parseDefaultSelectClause(String rawList) {
public LinkedHashSet<String> parseDefaultSelectClause(String rawList) {
if (rawList == null) {
return null;
@@ -780,7 +779,7 @@ public class DeployBeanDescriptor<T> {
set.add(temp);
}
}
return Collections.unmodifiableSet(set);
return set;
}
/**
@@ -1,5 +1,8 @@
package com.avaje.ebeaninternal.server.expression;
import java.util.Collection;
import java.util.Iterator;
/**
* Utility to help isSame methods.
*/
@@ -19,6 +22,26 @@ public class Same {
return v1 == null ? v2 == null : v1.equals(v2);
}
/**
* Return true if both collections are the same by value and order is taken into account.
*/
public static boolean sameByValue(Collection<?> v1, Collection<?> v2) {
if (v1 == null) {
return v2 == null;
}
if (v2 == null || v1.size() != v2.size()) {
return false;
}
Iterator<?> thisIt = v1.iterator();
Iterator<?> thatIt = v2.iterator();
while (thisIt.hasNext() && thatIt.hasNext()) {
if (!thisIt.next().equals(thatIt.next())) {
return false;
}
}
return true;
}
/**
* Null safe check by sameByValue or sameByNull based on byValue.
*/
@@ -25,7 +25,6 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest;
import javax.persistence.PersistenceException;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Generates the SQL SELECT statements taking into account the physical
@@ -354,8 +353,7 @@ public class CQueryBuilder {
// transfer PathProperties into OrmQueryDetail
for (String path : pathProps.getPaths()) {
Set<String> props = pathProps.get(path);
detail.getChunk(path, true).setDefaultProperties(null, props);
detail.getChunk(path, true).setDefaultProperties(null, pathProps.get(path));
}
// check if @Id property included in RawSql
@@ -7,6 +7,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SplitName;
import org.jetbrains.annotations.NotNull;
import javax.persistence.PersistenceException;
import java.io.Serializable;
@@ -18,7 +19,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents the internal structure of an Object Relational query.
@@ -38,6 +38,7 @@ public class OrmQueryDetail implements Serializable {
/**
* Root level properties.
*/
@NotNull
private OrmQueryProperties baseProps = new OrmQueryProperties();
/**
@@ -60,17 +61,17 @@ public class OrmQueryDetail implements Serializable {
return copy;
}
public int queryPlanHash() {
HashQueryPlanBuilder builder = new HashQueryPlanBuilder();
queryPlanHash(builder);
return builder.getPlanHash();
}
/**
* Calculate the hash for the query plan.
*/
public void queryPlanHash(HashQueryPlanBuilder builder) {
if (baseProps == null) {
builder.add(false);
} else {
builder.add(true);
baseProps.queryPlanHash(builder);
}
baseProps.queryPlanHash(builder);
if (fetchPaths != null) {
for (OrmQueryProperties p : fetchPaths.values()) {
p.queryPlanHash(builder);
@@ -79,11 +80,10 @@ public class OrmQueryDetail implements Serializable {
}
/**
* Return true if equal in terms of autoTune (select and fetch).
* Return true if the details are the same for query plan purposes.
*/
public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) {
if (!isSame(baseProps, otherDetail.baseProps)) {
public boolean isSameByPlan(OrmQueryDetail otherDetail) {
if (!isSameByPlan(baseProps, otherDetail.baseProps)) {
return false;
}
if (fetchPaths == null) {
@@ -92,10 +92,16 @@ public class OrmQueryDetail implements Serializable {
if (fetchPaths.size() != otherDetail.fetchPaths.size()) {
return false;
}
Set<Map.Entry<String, OrmQueryProperties>> entries = fetchPaths.entrySet();
for (Map.Entry<String, OrmQueryProperties> entry : entries) {
OrmQueryProperties chunk = otherDetail.getChunk(entry.getKey(), false);
if (!isSame(entry.getValue(), chunk)) {
// check with ordering being important
Iterator<Map.Entry<String, OrmQueryProperties>> thisIt = fetchPaths.entrySet().iterator();
Iterator<Map.Entry<String, OrmQueryProperties>> thatIt = otherDetail.fetchPaths.entrySet().iterator();
while (thisIt.hasNext() && thatIt.hasNext()) {
Map.Entry<String, OrmQueryProperties> thisEntry = thisIt.next();
Map.Entry<String, OrmQueryProperties> thatEntry = thatIt.next();
if (!thisEntry.getKey().equals(thatEntry.getKey())) {
return false;
}
if (!thisEntry.getValue().isSameByPlan(thatEntry.getValue())) {
return false;
}
}
@@ -103,21 +109,54 @@ public class OrmQueryDetail implements Serializable {
return true;
}
private boolean isSame(OrmQueryProperties p1, OrmQueryProperties p2) {
if (p1 == null) {
return p2 == null;
/**
* Return true if equal in terms of autoTune (select and fetch without property ordering).
*/
public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) {
if (!isSameByAutoTune(baseProps, otherDetail.baseProps)) {
return false;
}
return p1.isSame(p2);
if (fetchPaths == null) {
return otherDetail.fetchPaths == null;
}
if (fetchPaths.size() != otherDetail.fetchPaths.size()) {
return false;
}
// check without regard to ordering
for (Map.Entry<String, OrmQueryProperties> entry : fetchPaths.entrySet()) {
OrmQueryProperties chunk = otherDetail.getChunk(entry.getKey(), false);
if (!isSameByAutoTune(entry.getValue(), chunk)) {
return false;
}
}
return true;
}
private boolean isSameByAutoTune(OrmQueryProperties p1, OrmQueryProperties p2) {
return p1 == null ? p2 == null : p1.isSameByAutoTune(p2);
}
private boolean isSameByPlan(OrmQueryProperties p1, OrmQueryProperties p2) {
return p1 == null ? p2 == null : p1.isSameByPlan(p2);
}
public String toString() {
return asString();
}
/**
* Return the detail in string form.
*/
public String asString() {
StringBuilder sb = new StringBuilder();
if (baseProps != null) {
sb.append("select ").append(baseProps);
if (!baseProps.isEmpty()) {
baseProps.append("select ", sb);
}
if (fetchPaths != null) {
for (OrmQueryProperties join : fetchPaths.values()) {
sb.append(" fetch ").append(join);
join.append(" fetch ", sb);
}
}
return sb.toString();
@@ -135,7 +174,7 @@ public class OrmQueryDetail implements Serializable {
}
public boolean containsProperty(String property) {
return baseProps == null || baseProps.isIncluded(property);
return baseProps.isIncluded(property);
}
/**
@@ -399,9 +438,6 @@ public class OrmQueryDetail implements Serializable {
public void setDefaultSelectClause(BeanDescriptor<?> desc) {
if (desc.hasDefaultSelectClause() && !hasSelectClause()) {
if (baseProps == null) {
baseProps = new OrmQueryProperties();
}
baseProps.setDefaultProperties(desc.getDefaultSelectClause(), desc.getDefaultSelectClauseSet());
}
@@ -417,14 +453,14 @@ public class OrmQueryDetail implements Serializable {
}
public boolean hasSelectClause() {
return (baseProps != null && baseProps.hasSelectClause());
return (baseProps.hasSelectClause());
}
/**
* Return true if the query detail has neither select properties specified or any joins defined.
*/
public boolean isEmpty() {
return fetchPaths.isEmpty() && (baseProps == null || !baseProps.hasProperties());
return fetchPaths.isEmpty() && (!baseProps.hasProperties());
}
/**
@@ -29,6 +29,8 @@ public class OrmQueryDetailParser {
public OrmQueryDetail parse() throws PersistenceException {
if (parser.isEmpty()) return detail;
parser.nextWord();
processInitial();
return detail;
@@ -155,7 +157,7 @@ public class OrmQueryDetailParser {
private void readSelect() {
String path = null;
String props = parser.nextWord();
if (props.startsWith("(")) {
if (props != null && props.startsWith("(")) {
props = props.substring(1, props.length() - 1);
OrmQueryProperties base = new OrmQueryProperties(path, props);
detail.setBase(base);
@@ -10,13 +10,12 @@ import com.avaje.ebeaninternal.api.SpiExpressionFactory;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.expression.FilterExprPath;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.expression.FilterExpressionList;
import com.avaje.ebeaninternal.server.expression.Same;
import com.avaje.ebeaninternal.server.query.SplitName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
@@ -32,8 +31,7 @@ public class OrmQueryProperties implements Serializable {
private String parentPath;
private String path;
private String properties;
private String rawProperties;
private String trimmedProperties;
/**
@@ -57,7 +55,7 @@ public class OrmQueryProperties implements Serializable {
* Note this SHOULD be a LinkedHashSet to preserve order of the properties. This is to make using
* SqlSelect easier with predictable property/column ordering.
*/
private Set<String> included;
private LinkedHashSet<String> included;
/**
* Included bean joins.
@@ -92,8 +90,10 @@ public class OrmQueryProperties implements Serializable {
this.parentPath = SplitName.parent(path);
}
/**
* Construct for root so path (and parentPath) are null.
*/
public OrmQueryProperties() {
this(null);
}
/**
@@ -137,25 +137,17 @@ public class OrmQueryProperties implements Serializable {
* This can include the +query and +lazy type hints.
* </p>
*/
public void setProperties(String properties) {
this.properties = properties;
this.trimmedProperties = properties;
parseProperties();
public void setProperties(String rawProperties) {
if (!isAllProperties()) {
Set<String> parsed = parseIncluded(trimmedProperties);
if (parsed.contains("*")) {
this.included = null;
} else {
this.included = parsed;
}
} else {
this.included = null;
}
}
OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties);
private boolean isAllProperties() {
return (trimmedProperties == null) || (trimmedProperties.length() == 0) || "*".equals(trimmedProperties);
this.rawProperties = rawProperties;
this.trimmedProperties = response.properties;
this.included = response.included;
this.lazyFetchBatch = response.lazyFetchBatch;
this.queryFetchBatch = response.queryFetchBatch;
this.cache = response.cache;
this.readOnly = response.readOnly;
}
/**
@@ -204,8 +196,8 @@ public class OrmQueryProperties implements Serializable {
/**
* Set the properties from deployment default FetchTypes.
*/
public void setDefaultProperties(String properties, Set<String> included) {
this.properties = properties;
public void setDefaultProperties(String properties, LinkedHashSet<String> included) {
this.rawProperties = properties;
this.trimmedProperties = properties;
this.included = included;
}
@@ -215,7 +207,7 @@ public class OrmQueryProperties implements Serializable {
*/
public void setTunedProperties(OrmQueryProperties tunedProperties) {
if (tunedProperties.hasProperties()) {
this.properties = tunedProperties.properties;
this.rawProperties = tunedProperties.rawProperties;
this.trimmedProperties = tunedProperties.trimmedProperties;
this.included = tunedProperties.included;
this.queryFetchBatch = Math.max(queryFetchBatch, tunedProperties.queryFetchBatch);
@@ -264,7 +256,7 @@ public class OrmQueryProperties implements Serializable {
OrmQueryProperties copy = new OrmQueryProperties();
copy.parentPath = parentPath;
copy.path = path;
copy.properties = properties;
copy.rawProperties = rawProperties;
copy.trimmedProperties = trimmedProperties;
copy.cache = cache;
copy.readOnly = readOnly;
@@ -273,7 +265,7 @@ public class OrmQueryProperties implements Serializable {
copy.lazyFetchBatch = lazyFetchBatch;
copy.filterMany = filterMany;
if (included != null) {
copy.included = new HashSet<String>(included);
copy.included = new LinkedHashSet<String>(included);
}
if (includedBeanJoin != null) {
copy.includedBeanJoin = new HashSet<String>(includedBeanJoin);
@@ -290,17 +282,28 @@ public class OrmQueryProperties implements Serializable {
return included != null;
}
/**
* Return true if the properties and configuration are empty.
*/
public boolean isEmpty() {
return rawProperties == null || rawProperties.isEmpty();
}
public String toString() {
String s = "";
StringBuilder sb = new StringBuilder(40);
append("", sb);
return sb.toString();
}
public String append(String prefix, StringBuilder sb) {
sb.append(prefix);
if (path != null) {
s += path + " ";
sb.append(path).append(" ");
}
if (properties != null) {
s += "(" + properties + ") ";
} else if (included != null) {
s += "(" + included + ") ";
if (!isEmpty()) {
sb.append("(").append(rawProperties).append(") ");
}
return s;
return sb.toString();
}
public boolean isChild(OrmQueryProperties possibleChild) {
@@ -318,35 +321,22 @@ public class OrmQueryProperties implements Serializable {
}
/**
* Calculate the query plan hash.
* Return the raw properties.
*/
@SuppressWarnings("unchecked")
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(path);
if (properties != null) {
builder.add(properties);
} else {
builder.add(included);
}
builder.add(filterMany != null);
if (filterMany != null) {
filterMany.queryPlanHash(builder);
}
builder.add(lazyFetchBatch);
builder.add(queryFetchBatch);
builder.add(queryFetchAll);
}
public String getProperties() {
return properties;
return rawProperties;
}
/**
* Return true if this has properties.
* Return true if this has properties. Returning false means this part of the
* path is a partial object.
*/
public boolean hasProperties() {
return properties != null || included != null;
return included != null;
}
public boolean allProperties() {
return included == null;
}
/**
@@ -370,10 +360,6 @@ public class OrmQueryProperties implements Serializable {
includedBeanJoin.add(propertyName);
}
public boolean allProperties() {
return included == null;
}
/**
* This excludes the bean joined properties.
* <p>
@@ -386,7 +372,7 @@ public class OrmQueryProperties implements Serializable {
return included;
}
LinkedHashSet<String> temp = new LinkedHashSet<String>(secondaryQueryJoins.size() + included.size());
LinkedHashSet<String> temp = new LinkedHashSet<String>(2 * (secondaryQueryJoins.size() + included.size()));
temp.addAll(included);
temp.addAll(secondaryQueryJoins);
return temp;
@@ -400,30 +386,10 @@ public class OrmQueryProperties implements Serializable {
}
/**
* Return all the properties including the bean joins. This is the set that will be used by
* EntityBeanIntercept to determine if a property needs to be lazy loaded.
* Return the property set.
*/
public Set<String> getAllIncludedProperties() {
if (included == null) {
return null;
}
if (includedBeanJoin == null && secondaryQueryJoins == null) {
return new LinkedHashSet<String>(included);
}
LinkedHashSet<String> s = new LinkedHashSet<String>(2 * (included.size() + 5));
if (included != null) {
s.addAll(included);
}
if (includedBeanJoin != null) {
s.addAll(includedBeanJoin);
}
if (secondaryQueryJoins != null) {
s.addAll(secondaryQueryJoins);
}
return s;
protected Set<String> getIncluded() {
return included;
}
public boolean isIncluded(String propName) {
@@ -480,96 +446,47 @@ public class OrmQueryProperties implements Serializable {
return path;
}
private void parseProperties() {
if (trimmedProperties == null) {
return;
}
int pos = trimmedProperties.indexOf("+readonly");
if (pos > -1) {
trimmedProperties = StringHelper.replaceString(trimmedProperties, "+readonly", "");
this.readOnly = true;
}
pos = trimmedProperties.indexOf("+cache");
if (pos > -1) {
trimmedProperties = StringHelper.replaceString(trimmedProperties, "+cache", "");
this.cache = true;
}
pos = trimmedProperties.indexOf("+query");
if (pos > -1) {
queryFetchBatch = parseBatchHint(pos, "+query");
}
pos = trimmedProperties.indexOf("+lazy");
if (pos > -1) {
lazyFetchBatch = parseBatchHint(pos, "+lazy");
}
trimmedProperties = trimmedProperties.trim();
while (trimmedProperties.startsWith(",")) {
trimmedProperties = trimmedProperties.substring(1).trim();
}
}
private int parseBatchHint(int pos, String option) {
int startPos = pos + option.length();
int endPos = findEndPos(startPos, trimmedProperties);
if (endPos == -1) {
trimmedProperties = StringHelper.replaceString(trimmedProperties, option, "");
return 0;
} else {
String batchParam = trimmedProperties.substring(startPos + 1, endPos);
if (endPos + 1 >= trimmedProperties.length()) {
trimmedProperties = trimmedProperties.substring(0, pos);
} else {
trimmedProperties = trimmedProperties.substring(0, pos) + trimmedProperties.substring(endPos + 1);
}
return Integer.parseInt(batchParam);
}
}
private int findEndPos(int pos, String props) {
if (pos < props.length()) {
if (props.charAt(pos) == '(') {
int endPara = props.indexOf(')', pos + 1);
if (endPara == -1) {
String m = "Error could not find ')' in " + props + " after position " + pos;
throw new RuntimeException(m);
}
return endPara;
}
}
return -1;
}
/**
* Parse the include separating by comma or semicolon.
* Return true if the properties are the same for autoTune purposes.
*/
private static Set<String> parseIncluded(String rawList) {
String[] res = rawList.split(",");
LinkedHashSet<String> set = new LinkedHashSet<String>(res.length + 3);
String temp;
for (int i = 0; i < res.length; i++) {
temp = res[i].trim();
if (temp.length() > 0) {
set.add(temp);
}
}
return Collections.unmodifiableSet(set);
}
public boolean isSame(OrmQueryProperties p2) {
public boolean isSameByAutoTune(OrmQueryProperties p2) {
if (included == null) {
return p2.included == null;
}
return included.equals(p2.included);
}
/**
* Properties are the same for query plan purposes.
*/
public boolean isSameByPlan(OrmQueryProperties p2) {
if (!Same.sameByValue(secondaryQueryJoins, p2.secondaryQueryJoins)) return false;
if (!Same.sameByValue(included, p2.included)) return false;
if (!Same.sameByNull(filterMany, p2.filterMany)) return false;
if (filterMany != null && !filterMany.isSameByPlan(p2.filterMany)) return false;
return lazyFetchBatch == p2.lazyFetchBatch
&& queryFetchBatch == p2.queryFetchBatch
&& queryFetchAll == p2.queryFetchAll;
}
/**
* Calculate the query plan hash.
*/
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(path);
builder.addOrdered(included);
builder.add(secondaryQueryJoins);
builder.add(filterMany != null);
if (filterMany != null) {
filterMany.queryPlanHash(builder);
}
builder.add(lazyFetchBatch);
builder.add(queryFetchBatch);
builder.add(queryFetchAll);
}
}
@@ -0,0 +1,183 @@
package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import java.util.LinkedHashSet;
/**
* Parses the path properties string.
*/
public class OrmQueryPropertiesParser {
private static Response EMPTY = new Response();
/**
* Immutable response of the parsed properties and options.
*/
public static class Response {
final boolean readOnly;
final boolean cache;
final int queryFetchBatch;
final int lazyFetchBatch;
final String properties;
final LinkedHashSet<String> included;
public Response(boolean readOnly, boolean cache, int queryFetchBatch, int lazyFetchBatch, String properties, LinkedHashSet<String> included) {
this.readOnly = readOnly;
this.cache = cache;
this.queryFetchBatch = queryFetchBatch;
this.lazyFetchBatch = lazyFetchBatch;
this.properties = properties;
this.included = included;
}
public Response() {
this.readOnly = false;
this.cache = false;
this.queryFetchBatch = -1;
this.lazyFetchBatch = -1;
this.properties = "";
this.included = null;
}
}
/**
* Parses the path properties string returning the parsed properties and options.
* In general it is comma delimited with some special strings like +lazy(20).
*/
public static Response parse(String rawProperties) {
return new OrmQueryPropertiesParser(rawProperties).parse();
}
private String inputProperties;
private String outputProperties = "";
private boolean allProperties;
private boolean readOnly;
private boolean cache;
private int queryFetchBatch = -1;
private int lazyFetchBatch = -1;
private OrmQueryPropertiesParser(String inputProperties) {
this.inputProperties = inputProperties;
}
/**
* Parse the raw string properties input.
*/
private Response parse() {
if (inputProperties == null || inputProperties.isEmpty()) {
return EMPTY;
}
int pos = inputProperties.indexOf("+readonly");
if (pos > -1) {
inputProperties = StringHelper.replaceString(inputProperties, "+readonly", "");
readOnly = true;
}
pos = inputProperties.indexOf("+cache");
if (pos > -1) {
inputProperties = StringHelper.replaceString(inputProperties, "+cache", "");
cache = true;
}
pos = inputProperties.indexOf("+query");
if (pos > -1) {
queryFetchBatch = parseBatchHint(pos, "+query");
}
pos = inputProperties.indexOf("+lazy");
if (pos > -1) {
lazyFetchBatch = parseBatchHint(pos, "+lazy");
}
LinkedHashSet<String> included = parseIncluded();
String properties = (allProperties) ? "*" : outputProperties;
return new Response(readOnly, cache, queryFetchBatch, lazyFetchBatch, properties, included);
}
/**
* Parse the include separating by comma or semicolon.
*/
private LinkedHashSet<String> parseIncluded() {
inputProperties = inputProperties.trim();
if (inputProperties.isEmpty()) {
// default properties
return null;
}
if (inputProperties.equals("*")) {
// explicit all properties
allProperties = true;
return null;
}
String[] res = inputProperties.split(",");
StringBuilder sb = new StringBuilder(70);
LinkedHashSet<String> propertySet = new LinkedHashSet<String>(res.length * 2);
int count = 0;
String temp;
for (int i = 0; i < res.length; i++) {
temp = res[i].trim();
if (temp.length() > 0) {
if (count > 0) {
sb.append(",");
}
sb.append(temp);
propertySet.add(temp);
count++;
}
}
if (propertySet.isEmpty()) {
// default properties
return null;
}
if (propertySet.contains("*")) {
// explicit all properties
allProperties = true;
return null;
}
// partial properties
outputProperties = sb.toString();
return propertySet;
}
private int parseBatchHint( int pos, String option) {
int startPos = pos + option.length();
int endPos = findEndPos(startPos, inputProperties);
if (endPos == -1) {
inputProperties = StringHelper.replaceString(inputProperties, option, "");
return 0;
} else {
String batchParam = inputProperties.substring(startPos + 1, endPos);
if (endPos + 1 >= inputProperties.length()) {
inputProperties = inputProperties.substring(0, pos);
} else {
inputProperties = inputProperties.substring(0, pos) + inputProperties.substring(endPos + 1);
}
return Integer.parseInt(batchParam);
}
}
private int findEndPos(int pos, String props) {
if (pos < props.length()) {
if (props.charAt(pos) == '(') {
int endPara = props.indexOf(')', pos + 1);
if (endPara == -1) {
throw new RuntimeException("Error could not find ')' in " + props + " after position " + pos);
}
return endPara;
}
}
return -1;
}
}
@@ -16,6 +16,10 @@ public class SimpleTextParser {
this.eof = oql.length();
}
public boolean isEmpty() {
return oql.isEmpty();
}
public int getPos() {
return pos;
}