001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.formmodel;
018:
019: import java.util.Iterator;
020: import java.util.List;
021:
022: import org.apache.cocoon.forms.datatype.Datatype;
023: import org.apache.cocoon.forms.datatype.convertor.ConversionResult;
024: import org.apache.cocoon.forms.event.RepeaterEvent;
025: import org.apache.cocoon.forms.event.RepeaterListener;
026: import org.apache.cocoon.forms.event.ValueChangedEvent;
027: import org.apache.cocoon.forms.event.ValueChangedListener;
028: import org.apache.cocoon.forms.event.ValueChangedListenerEnabled;
029: import org.apache.cocoon.forms.util.WidgetFinder;
030:
031: import com.ibm.icu.math.BigDecimal;
032:
033: /**
034: * A field which calculates its value.
035: *
036: * <p>A calculated field is useful to create fields containing a sum, or a percentage, or any other
037: * value derived from other fields in the form.</p>
038: *
039: * <p>The way the field calculates its value is determined by its
040: * {@link org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm}.
041: * The algorithm is also responsible for determining which other form widgets will trigger
042: * a value calculation for this field.
043: * </p>
044: *
045: * @version $Id: CalculatedField.java 449149 2006-09-23 03:58:05Z crossley $
046: */
047: public class CalculatedField extends Field {
048:
049: // private CalculatedFieldDefinition definition;
050: private CalculatedFieldAlgorithm algorithm = null;
051:
052: private WidgetFinder finder = null;
053: private RecalculateValueListener mockListener = new RecalculateValueListener();
054:
055: private boolean needRecaulculate = false;
056: // private boolean initialized = false;
057: private boolean calculating = false;
058:
059: /**
060: * @param definition
061: */
062: protected CalculatedField(CalculatedFieldDefinition definition) {
063: super (definition);
064:
065: // this.definition = definition;
066: this .algorithm = definition.getAlgorithm();
067: }
068:
069: public void initialize() {
070: super .initialize();
071: Iterator triggers = this .algorithm.getTriggerWidgets();
072: this .finder = new WidgetFinder(this .getParent(), triggers, true);
073: this .finder.addRepeaterListener(new InstallHandlersListener());
074: installHandlers();
075:
076: // this.initialized = true;
077: }
078:
079: /**
080: * Installs handlers on other widgets. This both forces other widget to
081: * submit the form when their values change, and also gives this field
082: * a good optimization on calls to its algorithm.
083: */
084: protected void installHandlers() {
085: List adds = this .finder.getNewAdditions();
086: for (Iterator iter = adds.iterator(); iter.hasNext();) {
087: Widget widget = (Widget) iter.next();
088: if (widget instanceof ValueChangedListenerEnabled) {
089: ((ValueChangedListenerEnabled) widget)
090: .addValueChangedListener(mockListener);
091: }
092: }
093: }
094:
095: protected void readFromRequest(String newEnteredValue) {
096: // Never read a calculated field from request.
097: }
098:
099: public Object getValue() {
100: // Need to calculate if the following is true.
101: // - We are not already calculating (to avoid stack overflow)
102: // - We need to recaulculate.
103: if (!calculating && needRecaulculate) {
104: calculating = true;
105: try {
106: super .setValue(recalculate());
107: } finally {
108: calculating = false;
109: }
110: }
111: return super .getValue();
112: }
113:
114: /**
115: * Calls the algorithm to perform a recaulculation.
116: * @return The calculated value for this field.
117: */
118: protected Object recalculate() {
119: Object ret = this .algorithm.calculate(this .getForm(), this
120: .getParent(), this .getDatatype());
121: needRecaulculate = false;
122: try {
123: ret = convert(ret, this .getDatatype());
124: } catch (Exception e) {
125: // FIXME : log the conversion error
126: }
127: return ret;
128: }
129:
130: /**
131: * Tries to convert the return value of the algorithm to the right value for this field datatype.
132: * @param ret The return value fo the algorithm.
133: * @param datatype The target datatype.
134: * @return A converted value, or the given ret value if no conversion was possible.
135: */
136: protected Object convert(Object ret, Datatype datatype)
137: throws Exception {
138: // First try object to object conversion
139: Class target = datatype.getTypeClass();
140: if (ret instanceof Number) {
141: // Try to convert the number back to what expected
142: Number number = (Number) ret;
143: if (target.equals(BigDecimal.class)) {
144: return number;
145: } else if (target.equals(Double.class)) {
146: ret = new Double(number.doubleValue());
147: } else if (target.equals(Float.class)) {
148: ret = new Float(number.floatValue());
149: } else if (target.equals(Integer.class)) {
150: ret = new Integer(number.intValue());
151: } else if (target.equals(Long.class)) {
152: ret = new Long(number.longValue());
153: } else if (target.equals(String.class)) {
154: ret = number.toString();
155: }
156: return ret;
157: } else if (ret instanceof String) {
158: if (Number.class.isAssignableFrom(target)) {
159: // Try to build a new number parsing the string.
160: ret = target.getConstructor(
161: new Class[] { String.class }).newInstance(
162: new Object[] { ret });
163: }
164: return ret;
165: }
166: // Finally try to use the convertor
167: ConversionResult result = this .getDatatype().convertFromString(
168: ret.toString(), getForm().getLocale());
169: if (result.isSuccessful()) {
170: ret = result.getResult();
171: }
172: return ret;
173: }
174:
175: /**
176: * This listener is added to trigger fields, so that we know when they have been modified AND they are
177: * automatically submitted.
178: */
179: class RecalculateValueListener implements ValueChangedListener {
180: public void valueChanged(ValueChangedEvent event) {
181: needRecaulculate = true;
182: getValue();
183: }
184: }
185:
186: /**
187: * This listener is installed on the WidgetFinder to know when some repeater
188: * involved in our calculations gets modified.
189: */
190: class InstallHandlersListener implements RepeaterListener {
191: public void repeaterModified(RepeaterEvent event) {
192: needRecaulculate = true;
193: installHandlers();
194: getValue();
195: }
196: }
197:
198: /**
199: * @return Returns the algorithm.
200: */
201: public CalculatedFieldAlgorithm getAlgorithm() {
202: return algorithm;
203: }
204: }
|