/**
* eAdventure (formerly <e-Adventure> and <e-Game>) is a research project of the
* <e-UCM> research group.
*
* Copyright 2005-2010 <e-UCM> research group.
*
* You can access a list of all the contributors to eAdventure at:
* http://e-adventure.e-ucm.es/contributors
*
* <e-UCM> is a research group of the Department of Software Engineering
* and Artificial Intelligence at the Complutense University of Madrid
* (School of Computer Science).
*
* C Profesor Jose Garcia Santesmases sn,
* 28040 Madrid (Madrid), Spain.
*
* For more info please visit: <http://e-adventure.e-ucm.es> or
* <http://www.e-ucm.es>
*
* ****************************************************************************
*
* This file is part of eAdventure, version 2.0
*
* eAdventure is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* eAdventure 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with eAdventure. If not, see <http://www.gnu.org/licenses/>.
*/
package es.eucm.ead.editor.model;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A query to be interpreted by the model. Mimics standard queries from
* search engines: a series of field-values, implicitly joined together with a
* "the more matches, the better" approach.
*
* Expected format is of the form
* Query ::= QueryPart (QueryPart )*
* QueryPart ::= field:value | value
*
* QueryParts are separated by spaces. Double-quotes ('"') can be used include
* spaces or colons within a field or value, by quoting the whole field or value.
* Double-quotes can be escaped with '\' to include them within the
* field or value; and escapes can themselves be escaped via additional escapes.
*/
public class ModelQuery {
private static Logger logger = LoggerFactory.getLogger(ModelQuery.class);
/**
* The parts of the query
*/
private ArrayList<QueryPart> queryParts = new ArrayList<QueryPart>();
public ModelQuery(String queryString) {
try {
parseQuery(queryString);
} catch (Exception e) {
logger.warn("Bad query {}", queryString, e);
throw new IllegalArgumentException("Illegal query string '"
+ queryString + "'", e);
}
}
private ModelQuery() {
// do nothing.
}
/**
* Tests the validity of a queryString
* @param queryString
* @return true if valid, false if errors detected
*/
public static boolean isValid(String queryString) {
ModelQuery mq = new ModelQuery();
boolean valid = true;
try {
mq.parseQuery(queryString);
} catch (Exception e) {
valid = false;
}
return valid;
}
private void parseQuery(String queryString) {
String q = queryString + " ";
queryParts.clear();
int startPos = 0; // start of next part
int colonPos = -1; // offset, within last part, of colon
int ignored = 0;
boolean inQuote = false;
boolean inEscape = false;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < q.length(); i++) {
char c = q.charAt(i);
switch (c) {
case '\\': {
if (!inEscape) {
inEscape = true;
ignored++;
} else {
sb.append(c);
inEscape = false;
}
break;
}
case '"': {
if (!inEscape) {
inQuote = !inQuote;
ignored++;
} else {
sb.append(c);
inEscape = false;
}
break;
}
case ' ': {
if (!inQuote && sb.length() > 0) {
String f = (colonPos >= 0) ? sb.substring(0, colonPos) : "";
String v = sb.substring(colonPos + 1);
queryParts.add(new QueryPart(f, v));
sb.delete(0, sb.length());
inEscape = false;
colonPos = -1;
// ignore extra spaces
while ((i + 1 < q.length()) && (q.charAt(i + 1) == ' ')) {
i++;
}
startPos = i + 1;
} else {
sb.append(c);
inEscape = false;
}
break;
}
case ':': {
if (!inQuote) {
if (colonPos != -1) {
throw new IllegalArgumentException(
"too many ':'s -- maximum is 1 per queryPart");
}
colonPos = (i - ignored - startPos);
}
sb.append(c);
inEscape = false;
break;
}
default: {
sb.append(c);
inEscape = false;
}
}
}
if (inQuote || inEscape) {
throw new IllegalArgumentException("unclosed quote or escape");
}
if (queryParts.isEmpty()) {
throw new IllegalArgumentException("expected at least 1 queryPart");
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (QueryPart p : queryParts) {
sb.append("[").append(p.field).append("] : [").append(p.value)
.append("]\n");
}
return sb.toString();
}
public ArrayList<QueryPart> getQueryParts() {
return queryParts;
}
/**
* A part of a query
*/
public static class QueryPart {
private String field;
private String value;
public QueryPart(String field, String value) {
this.field = field;
this.value = value;
if (field != null && field.equals(ModelIndex.editorIdQueryField)) {
try {
Integer.parseInt(value);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(
"Direct ID queries can only be performed on integers",
nfe);
}
}
}
public String getField() {
return field;
}
public String getValue() {
return value;
}
}
}