/*
* © Copyright FOCONIS AG, 2014
*
* 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 org.openntf.formula;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.openntf.formula.parse.AtFormulaParserImpl;
import org.openntf.formula.parse.ParseException;
import org.openntf.formula.parse.TokenMgrError;
/**
* This class adds additonal functionality to the AtFormulaParserImpl (which is auto generated).
*
* @author Roland Praml, Foconis AG
*
*/
public abstract class FormulaParser {
/** the formatter to format/parse date time values while parsing the formula */
protected Formatter formatter;
/** the functionFactory */
protected FunctionFactory functionFactory;
/** the includeProvider for {@literal @}include function */
protected FormulaProvider<ASTNode> includeProvider;
protected Map<String, Function> customFunc;
protected boolean parsing = false;
public static final int MAX_FORMULA_CACHESIZE = 512;
public static final int REDUCE_FORMULA_CACHESIZE_TO = 256;
protected class FormulaCache {
class FormulaCacheEntry {
ASTNode node;
int usageCount;
FormulaCacheEntry(final ASTNode n) {
node = n;
usageCount = 1;
}
}
class FCMapEntryComparator implements Comparator<Object> {
public int compare(final Object ent1, final Object ent2) {
if (!(ent1 instanceof Map.Entry) || !(ent2 instanceof Map.Entry))
throw new IllegalArgumentException("FCMapEntryComparator");
@SuppressWarnings("unchecked")
int us1 = ((Map.Entry<String, FormulaCacheEntry>) ent1).getValue().usageCount;
@SuppressWarnings("unchecked")
int us2 = ((Map.Entry<String, FormulaCacheEntry>) ent2).getValue().usageCount;
return (us1 > us2) ? 1 : (us1 < us2) ? -1 : 0;
}
}
private Map<String, FormulaCacheEntry> cacheMap = new HashMap<String, FormulaCacheEntry>();
ASTNode get(final String key) {
FormulaCacheEntry fce = cacheMap.get(key);
if (fce == null)
return null;
fce.usageCount++;
return fce.node;
}
void reset() {
cacheMap.clear();
}
@SuppressWarnings("unchecked")
void put(final String key, final ASTNode node) {
if (cacheMap.size() > MAX_FORMULA_CACHESIZE) {
Object[] arr = cacheMap.entrySet().toArray();
Arrays.sort(arr, new FCMapEntryComparator());
int numToThrow = cacheMap.size() - REDUCE_FORMULA_CACHESIZE_TO;
String[] toThrow = new String[numToThrow];
for (int i = 0; i < numToThrow; i++)
toThrow[i] = ((Map.Entry<String, FormulaCacheEntry>) arr[i]).getKey();
for (int i = 0; i < numToThrow; i++)
cacheMap.remove(toThrow[i]);
Set<Map.Entry<String, FormulaCacheEntry>> cacheSet = cacheMap.entrySet();
for (Map.Entry<String, FormulaCacheEntry> ent : cacheSet)
ent.getValue().usageCount = 1;
}
cacheMap.put(key, new FormulaCacheEntry(node));
}
}
protected FormulaCache ntfFormulaCache = new FormulaCache();
protected FormulaCache focFormulaCache = new FormulaCache();
/**
* Returns a the Formatter for this parser
*
* @return the formatter
*/
public Formatter getFormatter() {
return formatter;
}
/**
* If you parse multiple formulas, you should call "reset" to clear predefined formulas!
*/
public void reset() {
customFunc = new HashMap<String, Function>();
ntfFormulaCache.reset();
focFormulaCache.reset();
}
/**
* Querys the functionFactory for a function. You can declare custom functions. These functions overrides implemented functions (if it
* is not an AST-Function)
*
* @param funcName
* the functionName (lowercase)
* @return the function or null
*/
public Function getFunctionLC(final String funcName) {
Function func = customFunc.get(funcName);
if (func != null) {
return func;
}
return functionFactory.getFunction(funcName);
}
/**
* Declares a new function. This mehtod is called by the <code>@Function(methodDecl ; [variables])</code> formula
*
* @param func
* the function to declare
*/
public void declareFunction(final Function func) {
String funcName = func.getImage();
customFunc.put(funcName.toLowerCase(), func);
}
/**
* Parses the given formoula
*
* @param reader
* the reader where the formula is read
* @param useFocFormula
* parses the formula in a special mode needed by Foconis. (You can inline formulas in normal text)
* @return an AST-node that can be evaluated
* @throws ParseException
* if the formula contains errors
*/
final public ASTNode parse(final Reader reader, final boolean useFocFormula) throws FormulaParseException {
if (parsing) {
return getCopy().parse(reader, useFocFormula);
}
parsing = true;
try {
ReInit(reader);
if (useFocFormula) {
return parseFocFormula();
} else {
return parseFormula();
}
} catch (TokenMgrError e) {
throw new ParseException(e.getMessage(), e);
} finally {
parsing = false;
}
}
/**
* Parses the given formula from an inputStream
*
* @param sr
* the input stream where the formula is read
* @param encoding
* encoding of the stream
* @param useFocFormula
* see: {@link #parse(Reader, boolean)}
* @return see: {@link #parse(Reader, boolean)}
* @throws ParseException
* see: {@link #parse(Reader, boolean)}
*/
final public ASTNode parse(final InputStream sr, final String encoding, final boolean useFocFormula) throws FormulaParseException {
if (parsing) {
return getCopy().parse(sr, useFocFormula);
}
parsing = true;
try {
ReInit(sr, encoding);
if (useFocFormula) {
return parseFocFormula();
} else {
return parseFormula();
}
} catch (TokenMgrError e) {
throw new ParseException(e.getMessage());
} finally {
parsing = false;
}
}
/**
* return a copy of the current parser. This is needed for include, because we cannot start a new parse task unless the old one is
* terminated
*
* @return
*/
private FormulaParser getCopy() {
AtFormulaParserImpl parser = new AtFormulaParserImpl(new java.io.StringReader(""));
parser.reset();
parser.formatter = formatter;
parser.functionFactory = functionFactory;
parser.includeProvider = includeProvider;
parser.customFunc = customFunc;
parser.focFormulaCache = focFormulaCache;
parser.ntfFormulaCache = ntfFormulaCache;
return parser;
}
/**
* Parses the given formula from an inputStream with default encoding
*
* @param sr
* the input stream where the formula is read
* @param useFocFormula
* see: {@link #parse(Reader, boolean)}
* @return see: {@link #parse(Reader, boolean)}
* @throws ParseException
* see: {@link #parse(Reader, boolean)}
*/
final public ASTNode parse(final InputStream sr, final boolean useFocFormula) throws FormulaParseException {
return parse(sr, null, useFocFormula);
}
/**
* Parses the given formula from an inputStream
*
* @param formula
* String with formula
* @param useFocFormula
* see: {@link #parse(Reader, boolean)}
* @return see: {@link #parse(Reader, boolean)}
* @throws ParseException
* see: {@link #parse(Reader, boolean)}
*/
final public ASTNode parse(final String formula, final boolean useFocFormula) throws FormulaParseException {
FormulaCache formulaCache = useFocFormula ? focFormulaCache : ntfFormulaCache;
ASTNode node = formulaCache.get(formula);
if (node == null) {
StringReader sr = new java.io.StringReader(formula);
node = parse(sr, useFocFormula);
node.setFormula(formula);
formulaCache.put(formula, node);
}
return node;
}
/**
* Parses the given formula from an inputStream
*
* @param formula
* String with formul
* @return see: {@link #parse(Reader, boolean)}
* @throws ParseException
* see: {@link #parse(Reader, boolean)}
*/
final public ASTNode parse(final String formula) throws FormulaParseException {
ASTNode node = parse(formula, false);
node.setFormula(formula);
return node;
}
/**
* Reinits the parser
*
* @param sr
* reader
*/
protected abstract void ReInit(Reader reader);
/**
* Reinits the parser
*
* @param stream
* stream with formula
* @param encoding
* encoding of the stream
*/
protected abstract void ReInit(java.io.InputStream stream, String encoding);
/**
* Parses the formula
*
* @return AST-Node
* @throws ParseException
* if formula contains errors
*/
abstract public ASTNode parseFormula() throws FormulaParseException;
/**
* Parses the formula in Foconis-mode (inline formulas are supported)
*
* @return AST-Node
* @throws ParseException
* if formula contains errors
*/
abstract public ASTNode parseFocFormula() throws FormulaParseException;
public void setIncludeProvider(final FormulaProvider<ASTNode> prov) {
includeProvider = prov;
}
/**
* get a node to include
*
*/
public ASTNode getInclude(final String key) {
if (includeProvider != null) {
return includeProvider.get(key);
}
return null;
}
}