001package scpc.model.support;
002
003import java.util.HashMap;
004import java.util.Map;
005import java.util.regex.Pattern;
006import java.util.regex.Matcher;
007import javax.script.ScriptException;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010import org.springframework.expression.EvaluationContext;
011import org.springframework.expression.Expression;
012import org.springframework.expression.spel.SpelCompilerMode;
013import org.springframework.expression.spel.SpelParserConfiguration;
014import org.springframework.expression.spel.standard.SpelExpressionParser;
015import org.springframework.expression.spel.support.StandardEvaluationContext;
016import scpc.model.IChainRule;
017import scpc.model.IRule;
018import scpc.model.SingleItem;
019
020/**
021 * A Spring Expression Language formula evaluator.
022 * <br>使用Spring Expression Language做為公式解譯工具
023 *
024 * @author Kent Yeh
025 * @param <T> type of real cart item.
026 */
027public abstract class SpelEvalHelper<T> extends EvalHelper<T> {
028
029    private static final Logger logger = LoggerFactory.getLogger(SpelEvalHelper.class);
030
031    private static final EvaluationContext context = new StandardEvaluationContext();
032    private static final SpelExpressionParser sep = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, SpelEvalHelper.class.getClassLoader(), false, true, Integer.MAX_VALUE));
033    private static final Pattern escapePtn = Pattern.compile("[\\\\\\^\\$\\.\\|\\?\\*\\+\\(\\)\\[\\{]");
034    private BindHolder binder = null;
035    private IRule<T> bindRule = null;
036    private SingleItem<T> bindItem = null;
037
038    public Expression getExpression(String spel) {
039        return sep.parseExpression(spel);
040    }
041
042    private String escape(String src) {
043        Matcher m = escapePtn.matcher(src);
044        StringBuilder sb = new StringBuilder();
045        int pos = 0;
046        while (m.find()) {
047            sb.append(src.substring(pos, m.start())).append("\\").append(src.substring(m.start(), m.start() + 1));
048            pos = m.start() + 1;
049        }
050        return sb.append(src.substring(pos, src.length())).toString();
051    }
052
053    /**
054     * @see EvalHelper#bindVaribles(scpc.model.IRule, scpc.model.SingleItem)
055     * @param rule
056     * @param item
057     * @return
058     */
059    @Override
060    public EvalHelper<T> bindVaribles(IRule<T> rule, SingleItem<T> item) {
061        binder = new BindHolder(rule, item);
062        bindRule = rule;
063        bindItem = item;
064        return this;
065    }
066
067    /**
068     * @see EvalHelper#bindVarValue(java.lang.String, java.lang.Object)
069     * @param variable <br>Must be a javascript qualified variable name.</b>
070     * @param value
071     * @return
072     */
073    @Override
074    public EvalHelper<T> bindVarValue(String variable, Object value) {
075        return this;
076    }
077
078    /**
079     * @see EvalHelper#eval(java.lang.String)
080     * @param <R>
081     * @param formula
082     * @return
083     * @throws ScriptException
084     */
085    @Override
086    public <R> R eval(String formula) throws ScriptException {
087        if (binder == null) {
088            throw new ScriptException("Not any binder bind (IRule,SingleItem) yet");
089        }
090        Map<String, String> prefix = new HashMap<>();
091        Map<String, String> replacers = new HashMap<>();
092        IChainRule<T> prule = bindRule.getPrevious();
093        String rpfx = "";
094        String rrpfx = "rule";
095        while (prule != null) {
096            rpfx += getPreviousRulePrefix();
097            rrpfx += ".previous";
098            prefix.put(rpfx, rrpfx);
099            prule = prule.getPrevious();
100        }
101        for (Map.Entry<String, String> entry : prefix.entrySet()) {
102            String varname = validJSVarName(entry.getKey() + getVarContainsCount());
103            if (null != replacers.put(varname, entry.getValue() + ".containsCount")) {
104                throw new ScriptException(String.format(" 2.Variable[%s] already set!", varname));
105            }
106            varname = validJSVarName(entry.getKey() + getVarSumOfContainsRegularPrice());
107            if (null != replacers.put(varname, entry.getValue() + ".sumOfContainsRegularPrice")) {
108                throw new ScriptException(String.format(" 3.Variable[%s] already set!", varname));
109            }
110            varname = validJSVarName(entry.getKey() + getVarSumOfContainsSalePrice());
111            if (null != replacers.put(varname, entry.getValue() + ".sumOfContainsSalePrice")) {
112                throw new ScriptException(String.format(" 4.Variable[%s] already set!", varname));
113            }
114            varname = validJSVarName(entry.getKey() + getVarSumOfSerialRegularPrice());
115            if (null != replacers.put(varname, entry.getValue() + ".sumOfSerialRegularPrice")) {
116                throw new ScriptException(String.format(" 5.Variable[%s] already set!", varname));
117            }
118            varname = validJSVarName(entry.getKey() + getVarSumOfSerialSalePrice());
119            if (null != replacers.put(varname, entry.getValue() + ".sumOfSerialSalePrice")) {
120                throw new ScriptException(String.format(" 6.Variable[%s] already set!", varname));
121            }
122        }
123        if (bindRule != null) {
124            String varname = validJSVarName(getVarContainsCount());
125            if (null != replacers.put(varname, "rule.containsCount")) {
126                throw new ScriptException(String.format(" 8.Variable[%s] already set!", varname));
127            }
128            varname = validJSVarName(getVarSerialNum());
129            if (null != replacers.put(varname, "rule.serialNum")) {
130                throw new ScriptException(String.format(" 9.Variable[%s] already set!", varname));
131            }
132            varname = validJSVarName(getVarSumOfContainsRegularPrice());
133            if (null != replacers.put(varname, "rule.sumOfContainsRegularPrice")) {
134                throw new ScriptException(String.format("10.Variable[%s] already set!", varname));
135            }
136            varname = validJSVarName(getVarSumOfContainsSalePrice());
137            if (null != replacers.put(varname, "rule.sumOfContainsSalePrice")) {
138                throw new ScriptException(String.format("11.Variable[%s] already set!", varname));
139            }
140            varname = validJSVarName(getVarSumOfSerialRegularPrice());
141            if (null != replacers.put(varname, "rule.sumOfSerialRegularPrice")) {
142                throw new ScriptException(String.format("12,Variable[%s] already set!", varname));
143            }
144            varname = validJSVarName(getVarSumOfSerialSalePrice());
145            if (null != replacers.put(varname, "rule.sumOfSerialSalePrice")) {
146                throw new ScriptException(String.format("13.Variable[%s] already set!", varname));
147            }
148        }
149        if (bindItem != null) {
150            String varname = validJSVarName(getVarSalePrice());
151            if (null != replacers.put(varname, "item.salePrice")) {
152                throw new ScriptException(String.format("14.Variable[%s] already set!", varname));
153            }
154            varname = validJSVarName(getVarRegularPrice());
155            if (null != replacers.put(varname, "item.regularPrice")) {
156                throw new ScriptException(String.format("15.Variable[%s] already set!", varname));
157            }
158        }
159        Matcher m = JS_NAME_LOCATOR.matcher(formula);
160        int pos = 0;
161        StringBuilder sb = new StringBuilder();
162        while (m.find()) {
163            String rep = replacers.get(m.group());
164            if (rep != null) {
165                sb.append(formula.substring(pos, m.start())).append(rep);
166            }
167            pos = m.end();
168        }
169        formula = sb.append(formula.substring(pos)).toString();
170        logger.debug("Spel.eval(\"{}\")", formula);
171        Expression exp = sep.parseExpression(formula);
172        return (R) exp.getValue(context, binder);
173    }
174
175    @Override
176    protected void purgeBind() {
177        binder = null;
178        bindRule = null;
179        bindItem = null;
180    }
181
182    private static class BindHolder<T> {
183
184        private final IRule<T> rule;
185        private final SingleItem<T> item;
186
187        public BindHolder(IRule<T> rule, SingleItem<T> item) {
188            this.rule = rule;
189            this.item = item;
190        }
191
192        public IRule<T> getRule() {
193            return rule;
194        }
195
196        public SingleItem<T> getItem() {
197            return item;
198        }
199
200    }
201}