/* * Copyright (C) 2009 JavaRosa * * 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.openrosa.client.jr.core.model.condition; import java.util.Date; import org.openrosa.client.jr.core.model.Constants; import org.openrosa.client.jr.core.model.FormDef; import org.openrosa.client.jr.core.model.data.BooleanData; import org.openrosa.client.jr.core.model.data.DateData; import org.openrosa.client.jr.core.model.data.DecimalData; import org.openrosa.client.jr.core.model.data.IAnswerData; import org.openrosa.client.jr.core.model.data.IntegerData; import org.openrosa.client.jr.core.model.data.StringData; import org.openrosa.client.jr.core.model.instance.FormInstance; import org.openrosa.client.jr.core.model.instance.TreeReference; public class Recalculate extends Triggerable { public Recalculate () { } public Recalculate (IConditionExpr expr, TreeReference contextRef) { super(expr, contextRef); } public Recalculate (IConditionExpr expr, TreeReference target, TreeReference contextRef) { super(expr, contextRef); addTarget(target); } public Object eval (FormInstance model, EvaluationContext ec) { return expr.evalRaw(model, ec); } public void apply (TreeReference ref, Object result, FormInstance model, FormDef f) { int dataType = f.getInstance().resolveReference(ref).dataType; f.setAnswer(wrapData(result, dataType), ref); } public boolean canCascade () { return true; } public boolean equals (Object o) { if (o instanceof Recalculate) { Recalculate r = (Recalculate)o; if (this == r) return true; return super.equals(r); } else { return false; } } //droos 1/29/10: we need to come up with a consistent rule for whether the resulting data is determined //by the type of the instance node, or the type of the expression result. right now it's a mix and a mess //note a caveat with going solely by instance node type is that untyped nodes default to string! //for now, these are the rules: // if node type == bool, convert to boolean (for numbers, zero = f, non-zero = t; empty string = f, all other datatypes -> error) // if numeric data, convert to int if node type is int OR data is an integer; else convert to double // if string data or date data, keep as is // if NaN or empty string, null /** * convert the data object returned by the xpath expression into an IAnswerData suitable for * storage in the FormInstance * */ private static IAnswerData wrapData (Object val, int dataType) { if ((val instanceof String && ((String)val).length() == 0) || (val instanceof Double && ((Double)val).isNaN())) { return null; } if (Constants.DATATYPE_BOOLEAN == dataType || val instanceof Boolean) { //ctsims: We should really be using the boolean datatype for real, it's //necessary for backend calculations and XSD compliance boolean b; if (val instanceof Boolean) { b = ((Boolean)val).booleanValue(); } else if (val instanceof Double) { Double d = (Double)val; b = Math.abs(d.doubleValue()) > 1.0e-12 && !Double.isNaN(d); } else if (val instanceof String) { String s = (String)val; b = s.length() > 0; } else { throw new RuntimeException("unrecognized data representation while trying to convert to BOOLEAN"); } return new BooleanData(b); } else if (val instanceof Double) { double d = ((Double)val).doubleValue(); boolean isIntegral = Math.abs(d - (int)d) < 1.0e-9; if(Constants.DATATYPE_INTEGER == dataType || isIntegral) { return new IntegerData((int)d); } else { return new DecimalData(d); } } else if (val instanceof String) { return new StringData((String)val); } else if (val instanceof Date) { return new DateData((Date)val); } else { throw new RuntimeException("unrecognized data type in 'calculate' expression: " + val.getClass().getName()); } } }