/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.services.search;
import java.util.ArrayList;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.lang.model.SourceVersion;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.text.StrMatcher;
import org.apache.commons.lang.text.StrTokenizer;
import org.eclipse.skalli.model.Expression;
public class SearchQuery {
public static final String PARAM_QUERY = "query"; //$NON-NLS-1$
public static final String PARAM_TAG = "tag"; //$NON-NLS-1$
public static final String PARAM_USER = "user"; //$NON-NLS-1$
public static final String PARAM_PROPERTY = "property"; //$NON-NLS-1$
public static final String PARAM_PATTERN = "pattern"; //$NON-NLS-1$
public static final String PARAM_IGNORE_CASE = "ignoreCase"; //$NON-NLS-1$
public static final String PARAM_EXTENSIONS = "extensions"; //$NON-NLS-1$
public static final String PARAM_START = "start"; //$NON-NLS-1$
public static final String PARAM_COUNT = "count"; //$NON-NLS-1$
public static final String PARAM_ORDER_BY = "orderBy"; //$NON-NLS-1$
public static final String DEFAULT_SHORTNAME = "project"; //$NON-NLS-1$
public static final String PROJECT_PREFIX = DEFAULT_SHORTNAME + "."; //$NON-NLS-1$
public static final String[] PARAMS = new String[] {
PARAM_QUERY, PARAM_TAG, PARAM_USER, PARAM_PROPERTY, PARAM_PATTERN, PARAM_IGNORE_CASE,
PARAM_EXTENSIONS, PARAM_START, PARAM_COUNT
};
public static final String PARAM_LIST_SEPARATOR = ","; //$NON-NLS-1$
private String query;
private String tag;
private String user;
private String[] extensions;
private String property;
private String shortName;
private boolean negate;
private Pattern pattern;
private boolean isExtension;
private StrTokenizer tokenizer = getTokenizer();
private Expression[] expressions;
private PagingInfo pagingInfo;
private SortOrder orderBy;
public SearchQuery() {
}
public SearchQuery(Map<String, String> params) throws QueryParseException {
setQuery(params.get(PARAM_QUERY));
setTag(params.get(PARAM_TAG));
setUser(params.get(PARAM_USER));
setProperty(params.get(PARAM_PROPERTY));
setOrderBy(params.get(PARAM_ORDER_BY));
String patternArg = params.get(PARAM_PATTERN);
if (StringUtils.isBlank(patternArg)) {
//return all projects that have the given property, as no pattern was provided
patternArg = ".+"; //$NON-NLS-1$
}
boolean ignoreCase = params.containsKey(PARAM_IGNORE_CASE);
setPattern(patternArg, ignoreCase);
int start = NumberUtils.toInt(params.get(PARAM_START), 0);
int count = NumberUtils.toInt(params.get(PARAM_COUNT), Integer.MAX_VALUE);
setPagingInfo(start, count);
String extensionParam = params.get(PARAM_EXTENSIONS);
if (extensionParam != null) {
setExtensions(StringUtils.split(extensionParam, PARAM_LIST_SEPARATOR));
}
}
public boolean isQueryAll() {
return "*".equals(query) || //$NON-NLS-1$
StringUtils.isBlank(query) && StringUtils.isBlank(user) && StringUtils.isBlank(tag);
}
public String getProperty() {
return property;
}
public void setProperty(String property) throws QueryParseException {
if (StringUtils.isNotBlank(property)) {
if (property.startsWith("!")) { //$NON-NLS-1$
setNegate(true);
property = property.substring(1);
}
String[] parts = split(property);
if (parts.length == 1 || StringUtils.isBlank(parts[0])) {
shortName = DEFAULT_SHORTNAME;
} else {
shortName = parts[0].trim();
isExtension = true;
}
int first = parts.length == 1? 0 : 1;
expressions = new Expression[parts.length - first];
for (int i = first, j = 0; i < parts.length; ++i, ++j) {
expressions[j] = asExpression(parts[i]);
}
}
this.property = property;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String[] getExtensions() {
return extensions;
}
public void setExtensions(String[] extensions) {
this.extensions = extensions;
}
public String[] addExtension(String shortName) {
extensions = (String[]) ArrayUtils.add(extensions, shortName);
return extensions;
}
public boolean hasExtension(String shortName) {
return ArrayUtils.contains(extensions, shortName);
}
public boolean isExtension() {
return isExtension;
}
public String getShortName() {
return shortName;
}
public String getPropertyName() {
if (expressions == null || expressions.length != 1) {
return null;
}
Expression first = expressions[0];
if (first.getArguments().length > 0) {
return null;
}
return first.getName();
}
public Expression[] getExpressions() {
return expressions;
}
public boolean isNegate() {
return negate;
}
public void setNegate(boolean negate) {
this.negate = negate;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public void setPattern(String pattern, boolean ignoreCase) throws QueryParseException {
if (StringUtils.isNotBlank(pattern)) {
try {
int flags = Pattern.DOTALL;
if (ignoreCase) {
flags = flags | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
}
setPattern(Pattern.compile(pattern, flags));
} catch (PatternSyntaxException e) {
throw new QueryParseException("Pattern has a syntax error", e);
}
}
}
public PagingInfo getPagingInfo() {
return pagingInfo;
}
public void setPagingInfo(PagingInfo pagingInfo) {
this.pagingInfo = pagingInfo;
}
public void setPagingInfo(int start, int count) {
this.pagingInfo = new PagingInfo(start, count);
}
public int getStart() {
return pagingInfo != null? pagingInfo.getStart() : 0;
}
public int getCount() {
return pagingInfo != null? pagingInfo.getCount() : Integer.MAX_VALUE;
}
public SortOrder getOrderBy() {
return orderBy != null? orderBy : SortOrder.NONE;
}
public void setOrderBy(SortOrder orderBy) {
this.orderBy = orderBy;
}
public void setOrderBy(String orderBy) {
if ("projectId".equalsIgnoreCase(orderBy)) { //$NON-NLS-1$
setOrderBy(SortOrder.PROJECT_ID);
} else if ("name".equalsIgnoreCase(orderBy)) { //$NON-NLS-1$
setOrderBy(SortOrder.PROJECT_NAME);
} else if ("uuid".equalsIgnoreCase(orderBy)) { //$NON-NLS-1$
setOrderBy(SortOrder.UUID);
} else {
setOrderBy(SortOrder.NONE);
}
}
StrTokenizer getTokenizer() {
StrTokenizer tokenizer = new StrTokenizer("", StrMatcher.commaMatcher(), StrMatcher.quoteMatcher());
tokenizer.setTrimmerMatcher(StrMatcher.trimMatcher());
return tokenizer;
}
private String[] split(String property) throws QueryParseException {
ArrayList<String> tokens = new ArrayList<String>();
char[] chars = property.toCharArray();
int last = chars.length - 1;
char quoteChar = 0;
boolean quoted = false;
boolean bracketed = false;
int nextTokenStart = 0;
int nextTokenLength = 0;
for (int i = 0; i <= last; ++i) {
char c = chars[i];
switch (c) {
case '.':
if (i == last) {
throw new QueryParseException("Property must not end with trailing dot:" + property);
}
if (!quoted && !bracketed) {
tokens.add(trimmed(chars, nextTokenStart, nextTokenLength));
nextTokenStart = i + 1;
nextTokenLength = 0;
} else {
++nextTokenLength;
}
break;
case '"':
case '\'':
if (quoted && c == quoteChar) {
if (i == last || (i < last && chars[i+1] != quoteChar)) {
quoteChar = 0;
quoted = false;
}
} else {
quoteChar = c;
quoted = true;
}
++nextTokenLength;
break;
case '(':
if (!bracketed && !quoted) {
bracketed = true;
}
++nextTokenLength;
break;
case ')':
if (bracketed && !quoted) {
bracketed = false;
}
++nextTokenLength;
break;
default:
++nextTokenLength;
}
}
if (nextTokenLength > 0) {
tokens.add(trimmed(chars, nextTokenStart, nextTokenLength));
}
return tokens.toArray(new String[tokens.size()]);
}
private String trimmed(char[] chars, int off, int len) {
if (len == 0) {
return ""; //$NON-NLS-1$
}
int first = off;
int last = off + len - 1;
int count = len;
while (first <= last && chars[first] <= ' ') { ++first; --count; }
while (last >= first && chars[last] <= ' ') { --last; --count; }
return new String(chars, first, count);
}
Expression asExpression(String s) throws QueryParseException {
int n = s.indexOf('(');
if (n < 0) {
if (!SourceVersion.isIdentifier(s)) {
throw new QueryParseException("Invalid property name :" + s);
}
return new Expression(s);
}
if (!s.endsWith(")")) { //$NON-NLS-1$
throw new QueryParseException("Invalid property expression: " + s);
}
String name = s.substring(0, n);
if (name.length() == 0) {
throw new QueryParseException("Property expression specifies no property name :" + s);
}
if (!SourceVersion.isIdentifier(name)) {
throw new QueryParseException("Invalid property name :" + s);
}
String argsList = s.substring(n+1, s.length()-1);
if (StringUtils.isBlank(argsList)) {
return new Expression(name);
}
if ("''".equals(argsList) || "\"\"".equals(argsList)) { //$NON-NLS-1$ //$NON-NLS-2$
return new Expression(name, ""); //$NON-NLS-1$
}
tokenizer.reset(argsList);
return new Expression(name, tokenizer.getTokenArray());
}
}