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