/* * © 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.domino.xsp.formula; import java.lang.ref.SoftReference; import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.el.EvaluationException; import javax.faces.el.PropertyNotFoundException; import org.openntf.domino.xsp.model.DominoDocumentMapAdapter; import org.openntf.formula.ASTNode; import org.openntf.formula.EvaluateException; import org.openntf.formula.FormulaParseException; import org.openntf.formula.Formulas; import com.ibm.xsp.binding.ValueBindingEx; import com.ibm.xsp.exception.EvaluationExceptionEx; import com.ibm.xsp.extlib.util.ExtLibUtil; import com.ibm.xsp.model.domino.wrapped.DominoDocument; import com.ibm.xsp.util.FacesUtil; import com.ibm.xsp.util.ValueBindingUtil; /** * Creates a ValueBinding for a formula * * @author Roland Praml, FOCONIS AG * */ public class FormulaValueBinding extends ValueBindingEx { private String formulaStr; private transient SoftReference<ASTNode> astNodeCache; /** * Constructor * * @param str * the formula (without #{...} delimiters) */ public FormulaValueBinding(final String str) { this.formulaStr = (str != null ? str.intern() : null); } /** * Trivial Constructor (needed for restoreState) */ public FormulaValueBinding() { this.formulaStr = null; } /** * returns the expected type */ @Override public Class<?> getType(final FacesContext arg0) throws EvaluationException, PropertyNotFoundException { return getExpectedType(); } /** * returns the value after the formula was evaluated */ @SuppressWarnings("unchecked") @Override public Object getValue(final FacesContext ctx) throws EvaluationException, PropertyNotFoundException { boolean rootWasNull = false; if (ctx.getViewRoot() == null) { rootWasNull = true; ctx.setViewRoot(FacesUtil.getViewRoot(getComponent())); } final DominoDocument dominoDoc = (DominoDocument) ExtLibUtil.resolveVariable(ctx, "currentDocument"); Map<String, Object> dataMap = null; if (dominoDoc instanceof Map) { dataMap = (Map<String, Object>) dominoDoc; } else { dataMap = new DominoDocumentMapAdapter(dominoDoc); } List<Object> ret = null; try { FormulaContextXsp fctx = (FormulaContextXsp) Formulas.createContext(dataMap, Formulas.getParser()); fctx.init(this.getComponent(), ctx); ret = getASTNode().solve(fctx); } catch (EvaluateException e) { throw new EvaluationException(e); } catch (FormulaParseException e) { throw new EvaluationException(e); } finally { if (rootWasNull) { ctx.setViewRoot(null); } } Object firstValue = ret.size() >= 1 ? ret.get(0) : null; // RPr: this is similar to the javascript binding Class<?> expectedType = getExpectedType(); if (expectedType != null) { if (expectedType == String.class) { if (ret.isEmpty()) { return null; } if (ret.size() == 1) { return ret.get(0).toString(); } return ret.toString(); } if ((expectedType == Boolean.class) || (expectedType == Boolean.TYPE)) { return firstValue == null ? Boolean.FALSE : (Boolean) firstValue; } if ((expectedType == Character.class) || (expectedType == Character.TYPE)) { String str = firstValue == null ? "" : firstValue.toString(); if (str.length() > 0) { return new Character(str.charAt(0)); } } if ((expectedType.isPrimitive()) || (Number.class.isAssignableFrom(expectedType))) { if ((expectedType == Double.class) || (expectedType == Double.TYPE)) { return firstValue == null ? 0d : ((Number) firstValue).doubleValue(); } if ((expectedType == Integer.class) || (expectedType == Integer.TYPE)) { return firstValue == null ? 0 : ((Number) firstValue).intValue(); } if ((expectedType == Long.class) || (expectedType == Long.TYPE)) { return firstValue == null ? 0l : ((Number) firstValue).longValue(); } if ((expectedType == Byte.class) || (expectedType == Byte.TYPE)) { return firstValue == null ? (byte) 0 : ((Number) firstValue).byteValue(); } if ((expectedType == Short.class) || (expectedType == Short.TYPE)) { return firstValue == null ? (short) 0 : ((Number) firstValue).shortValue(); } if ((expectedType == Float.class) || (expectedType == Float.TYPE)) { return firstValue == null ? 0f : ((Number) firstValue).floatValue(); } } } return convertToExpectedType(ctx, ret); } /** * Our valuebindings are read only * * @return always <code>TRUE</code> */ @Override public boolean isReadOnly(final FacesContext arg0) throws EvaluationException, PropertyNotFoundException { return true; } /** * This is not supported here */ @Override public void setValue(final FacesContext arg0, final Object arg1) throws EvaluationException, PropertyNotFoundException { throw new EvaluationExceptionEx("FormulaValueBinding is read-only", this); } /** * Returns the corresponding {@link ASTNode} for the formula * * @return {@link ASTNode} * @throws FormulaParseException * if the formula was invalid */ protected ASTNode getASTNode() throws FormulaParseException { if (astNodeCache != null) { ASTNode node = astNodeCache.get(); if (node != null) return node; } ASTNode node = Formulas.getParser().parse(formulaStr); astNodeCache = new SoftReference<ASTNode>(node); return node; } // --- some methods we have to overwrite @Override public String getExpressionString() { return ValueBindingUtil.getExpressionString(FormulaBindingFactory.FORMULA, this.formulaStr, ValueBindingUtil.RUNTIME_EXPRESSION); } @Override public Object saveState(final FacesContext ctx) { Object[] arr = new Object[2]; arr[0] = super.saveState(ctx); arr[1] = this.formulaStr; return arr; } @Override public void restoreState(final FacesContext context, final Object obj) { Object[] arr = (Object[]) obj; super.restoreState(context, arr[0]); this.formulaStr = ((String) arr[1]); } }