package com.gmail.dpierron.calibre.datamodel.filter;
import com.gmail.dpierron.calibre.datamodel.DataModel;
import com.gmail.dpierron.calibre.datamodel.calibrequerylanguage.CalibreQueryLexer;
import com.gmail.dpierron.calibre.datamodel.calibrequerylanguage.CalibreQueryParser;
import com.gmail.dpierron.calibre.error.CalibreSavedSearchInterpretException;
import com.gmail.dpierron.calibre.error.CalibreSavedSearchNotFoundException;
import com.gmail.dpierron.tools.Helper;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonErrorNode;
import org.antlr.runtime.tree.Tree;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Locale;
public class CalibreQueryInterpreter {
private final static Logger logger = LogManager.getLogger(CalibreQueryInterpreter.class);
// recognized lexical elements
private static final String WORD_TAG = "tags:";
private static final String WORD_TAG_TRUE = "tags:true";
private static final String WORD_TAG_FALSE = "tags:false";
private static final String WORD_LANGUAGE = "languages:";
private static final String WORD_LANGUAGE_TRUE = "languages:true";
private static final String WORD_LANGUAGE_FALSE = "languages:false";
private static final String WORD_RATING = "rating:";
private static final String WORD_RATING_TRUE = "rating:true";
private static final String WORD_RATING_FALSE = "rating:false";
private static final String WORD_AUTHOR = "authors:";
private static final String WORD_AUTHOR_TRUE = "authors:true";
private static final String WORD_AUTHOR_FALSE = "authors:false";
private static final String WORD_SERIES = "series:";
private static final String WORD_SERIES_TRUE = "series:true";
private static final String WORD_SERIES_FALSE = "series:false";
private static final String WORD_FORMAT = "formats:";
private static final String WORD_FORMAT_TRUE = "formats:true";
private static final String WORD_FORMAT_FALSE = "formats:false";
private static final String WORD_PUBLISHER = "publisher:"; // there is no 's', it's correct
private static final String WORD_PUBLISHER_TRUE = "publisher:true";
private static final String WORD_PUBLISHER_FALSE = "publisher:false";
// unary negation operator
private static final String WORD_NOT = "not";
// binary boolean operators
private static final String WORD_AND = "and";
private static final String WORD_OR = "or";
private final String calibreQuery;
public CalibreQueryInterpreter(String calibreQuery) {
this.calibreQuery = calibreQuery;
}
private BookFilter getFilterForNode(Tree node) throws CalibreSavedSearchInterpretException {
if (node instanceof CommonErrorNode) {
// an error occured during parsing
throw new CalibreSavedSearchInterpretException(node.toString(), ((CommonErrorNode) node).trappedException);
}
String template = node.getText();
if (logger.isTraceEnabled()) logger.trace("template=" + template);
/* Tags */
if (template.toLowerCase().equals(WORD_TAG_TRUE)) {
if (logger.isTraceEnabled()) logger.trace("found tag present filter");
return new TagPresenceFilter(true);
} else if (template.toLowerCase().equals(WORD_TAG_FALSE)) {
if (logger.isTraceEnabled()) logger.trace("found tag not present filter");
return new TagPresenceFilter(false);
} else if (template.toLowerCase().startsWith(WORD_TAG)) {
boolean isEqualsPresent = template.substring(WORD_TAG.length() + 1, WORD_TAG.length() + 2).equals("=");
String tag = template.substring(WORD_TAG.length() + (isEqualsPresent ? 2 : 1), template.length() - 1); // skip the "= and the "
if (logger.isTraceEnabled()) logger.trace("found tag filter: " + tag);
return new TagFilter(tag, !isEqualsPresent);
}
/* Languages */
else if (template.toLowerCase().equals(WORD_LANGUAGE_TRUE)) {
if (logger.isTraceEnabled()) logger.trace("found language present filter");
return new LanguagePresenceFilter(true);
} else if (template.toLowerCase().equals(WORD_LANGUAGE_FALSE)) {
if (logger.isTraceEnabled()) logger.trace("found language not present filter");
return new LanguagePresenceFilter(false);
} else if (template.toLowerCase().startsWith(WORD_LANGUAGE)) {
String lang = template.substring(WORD_LANGUAGE.length() + 2, template.length() - 1); // skip the "= and the "
if (logger.isTraceEnabled()) logger.trace("found language filter: " + lang);
return new LanguageFilter(lang);
}
/* Ratings */
else if (template.toLowerCase().equals(WORD_RATING_TRUE)) {
if (logger.isTraceEnabled()) logger.trace("found rating present filter");
return new RatingPresenceFilter(true);
} else if (template.toLowerCase().equals(WORD_RATING_FALSE)) {
if (logger.isTraceEnabled()) logger.trace("found rating not present filter");
return new RatingPresenceFilter(false);
} else if (template.toLowerCase().startsWith(WORD_RATING)) {
/* optionally remove the quotes */
String parameter = optionallyRemoveQuotes(template, WORD_RATING);
char comparator = parameter.charAt(0);
char rating;
if (comparator == '<' || comparator == '>' || comparator == '=')
rating = parameter.charAt(1);
else {
rating = parameter.charAt(0);
comparator = '=';
}
if (logger.isTraceEnabled()) logger.trace("found rating filter: " + comparator + " " + rating);
return new RatingFilter(comparator, rating);
}
/* Authors */
if (template.toLowerCase().equals(WORD_AUTHOR_TRUE)) {
if (logger.isTraceEnabled()) logger.trace("found author present filter");
return new AuthorPresenceFilter(true);
} else if (template.toLowerCase().equals(WORD_AUTHOR_FALSE)) {
if (logger.isTraceEnabled()) logger.trace("found author not present filter");
return new AuthorPresenceFilter(false);
} else if (template.toLowerCase().startsWith(WORD_AUTHOR)) {
boolean isEqualsPresent = template.substring(WORD_AUTHOR.length() + 1, WORD_AUTHOR.length() + 2).equals("=");
String tag = template.substring(WORD_AUTHOR.length() + (isEqualsPresent ? 2 : 1), template.length() - 1); // skip the "= and the "
if (logger.isTraceEnabled()) logger.trace("found author filter: " + tag);
return new AuthorFilter(tag, !isEqualsPresent);
}
/* Series */
if (template.toLowerCase().equals(WORD_SERIES_TRUE)) {
if (logger.isTraceEnabled()) logger.trace("found series present filter");
return new SeriesPresenceFilter(true);
} else if (template.toLowerCase().equals(WORD_SERIES_FALSE)) {
if (logger.isTraceEnabled()) logger.trace("found series not present filter");
return new SeriesPresenceFilter(false);
} else if (template.toLowerCase().startsWith(WORD_SERIES)) {
boolean isEqualsPresent = template.substring(WORD_SERIES.length() + 1, WORD_SERIES.length() + 2).equals("=");
String tag = template.substring(WORD_SERIES.length() + (isEqualsPresent ? 2 : 1), template.length() - 1); // skip the "= and the "
if (logger.isTraceEnabled()) logger.trace("found series filter: " + tag);
return new SeriesFilter(tag, !isEqualsPresent);
}
/* Formats */
if (template.toLowerCase().equals(WORD_FORMAT_TRUE)) {
if (logger.isTraceEnabled()) logger.trace("found format present filter");
return new FormatPresenceFilter(true);
} else if (template.toLowerCase().equals(WORD_FORMAT_FALSE)) {
if (logger.isTraceEnabled()) logger.trace("found format not present filter");
return new FormatPresenceFilter(false);
} else if (template.toLowerCase().startsWith(WORD_FORMAT)) {
boolean isEqualsPresent = template.substring(WORD_FORMAT.length() + 1, WORD_FORMAT.length() + 2).equals("=");
String tag = template.substring(WORD_FORMAT.length() + (isEqualsPresent ? 2 : 1), template.length() - 1); // skip the "= and the "
if (logger.isTraceEnabled()) logger.trace("found format filter: " + tag);
return new FormatFilter(tag, !isEqualsPresent);
}
/* Publisher */
if (template.toLowerCase().equals(WORD_PUBLISHER_TRUE)) {
if (logger.isTraceEnabled()) logger.trace("found publisher present filter");
return new PublisherPresenceFilter(true);
} else if (template.toLowerCase().equals(WORD_PUBLISHER_FALSE)) {
if (logger.isTraceEnabled()) logger.trace("found publisher not present filter");
return new PublisherPresenceFilter(false);
} else if (template.toLowerCase().startsWith(WORD_PUBLISHER)) {
boolean isEqualsPresent = template.substring(WORD_PUBLISHER.length() + 1, WORD_PUBLISHER.length() + 2).equals("=");
String tag = template.substring(WORD_PUBLISHER.length() + (isEqualsPresent ? 2 : 1), template.length() - 1); // skip the "= and the "
if (logger.isTraceEnabled()) logger.trace("found publisher filter: " + tag);
return new PublisherFilter(tag, !isEqualsPresent);
}
/* Boolean operators */
else if (template.equalsIgnoreCase(WORD_NOT)) {
// descend into the parsing with a negation filter
if (logger.isTraceEnabled()) logger.trace("found not filter!");
return new NotFilter(getFilterForNode(node.getChild(0)));
} else if (template.equalsIgnoreCase(WORD_OR)) {
// descend into the parsing with a boolean OR filter
if (logger.isTraceEnabled()) logger.trace("found OR filter!");
return new BooleanOrFilter(getFilterForNode(node.getChild(0)), getFilterForNode(node.getChild(1)));
} else if (template.equalsIgnoreCase(WORD_AND)) {
// descend into the parsing with a boolean AND filter
if (logger.isTraceEnabled()) logger.trace("found AND filter!");
return new BooleanAndFilter(getFilterForNode(node.getChild(0)), getFilterForNode(node.getChild(1)));
}
/* Error ! */
else {
logger.warn("found unsupported filter! " + template);
return new PassthroughFilter();
}
}
private String optionallyRemoveQuotes(String template, String word) {
char quote = template.charAt(word.length());
if (quote == '"') {
return template.substring(word.length() + 1, template.length() - 1);
} else {
return template.substring(word.length());
}
}
/**
* @return a filter, possibly composite, built as a parse tree of the calibreQuery string
* @throws com.gmail.dpierron.calibre.error.CalibreSavedSearchInterpretException
* when something failed
*/
public BookFilter interpret() throws CalibreSavedSearchInterpretException {
CharStream cs = new ANTLRStringStream(calibreQuery);
CalibreQueryLexer lexer = new CalibreQueryLexer(cs);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CalibreQueryParser parser = new CalibreQueryParser(tokens);
CalibreQueryParser.expr_return result;
try {
result = parser.expr();
return getFilterForNode((Tree) result.getTree());
} catch (RecognitionException e) {
throw new CalibreSavedSearchInterpretException(calibreQuery, e);
} catch (CalibreSavedSearchInterpretException e) {
throw new CalibreSavedSearchInterpretException(calibreQuery, e);
}
}
/**
*
* @param calibreSearchQueryOrName
* @return
* @throws CalibreSavedSearchInterpretException
* @throws CalibreSavedSearchNotFoundException
*/
public static BookFilter interpret(String calibreSearchQueryOrName) throws CalibreSavedSearchInterpretException, CalibreSavedSearchNotFoundException {
if (logger.isTraceEnabled())
logger.trace("CalibreQueryInterpreter.interpret:" + calibreSearchQueryOrName);
BookFilter filter = null;
String calibreQuery = calibreSearchQueryOrName;
if (Helper.isNotNullOrEmpty(calibreSearchQueryOrName)) {
String calibreSavedSearchName = null;
if (calibreSearchQueryOrName.toUpperCase(Locale.ENGLISH).startsWith("SAVED:")) {
calibreSavedSearchName = calibreSearchQueryOrName.substring(6);
if (logger.isTraceEnabled())
logger.trace("searching for saved search " + calibreSavedSearchName);
calibreQuery = DataModel.getMapOfSavedSearches().get(calibreSavedSearchName);
if (Helper.isNullOrEmpty(calibreQuery))
calibreQuery = DataModel.getMapOfSavedSearches().get(calibreSavedSearchName.toUpperCase());
if (Helper.isNullOrEmpty(calibreQuery))
throw new CalibreSavedSearchNotFoundException(calibreSavedSearchName);
} else {
if (logger.isDebugEnabled())
logger.trace("interpreting " + calibreQuery);
CalibreQueryInterpreter interpreter = new CalibreQueryInterpreter(calibreQuery);
filter = interpreter.interpret();
}
}
return filter;
}
}