001    /**
002     * =========================================
003     * LibFormula : a free Java formula library
004     * =========================================
005     *
006     * Project Info:  http://reporting.pentaho.org/libformula/
007     *
008     * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
009     *
010     * This library is free software; you can redistribute it and/or modify it under the terms
011     * of the GNU Lesser General Public License as published by the Free Software Foundation;
012     * either version 2.1 of the License, or (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016     * See the GNU Lesser General Public License for more details.
017     *
018     * You should have received a copy of the GNU Lesser General Public License along with this
019     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020     * Boston, MA 02111-1307, USA.
021     *
022     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023     * in the United States and other countries.]
024     *
025     *
026     * ------------
027     * $Id: Term.java 3521 2007-10-16 10:55:14Z tmorgner $
028     * ------------
029     * (C) Copyright 2006-2007, by Pentaho Corporation.
030     */
031    package org.jfree.formula.lvalues;
032    
033    import java.util.ArrayList;
034    
035    import org.jfree.formula.EvaluationException;
036    import org.jfree.formula.FormulaContext;
037    import org.jfree.formula.operators.InfixOperator;
038    
039    /**
040     * An term is a list of LValues connected by operators. For the sake of
041     * efficiency, this is not stored as tree. We store the term as a list in the
042     * following format: (headValue)(OP value)* ...
043     *
044     * @author Thomas Morgner
045     */
046    public class Term extends AbstractLValue
047    {
048      private LValue optimizedHeadValue;
049      private LValue headValue;
050      private ArrayList operators;
051      private ArrayList operands;
052      private InfixOperator[] operatorArray;
053      private LValue[] operandsArray;
054      private boolean initialized;
055      private static final LValue[] EMPTY_L_VALUE = new LValue[0];
056      private static final InfixOperator[] EMPTY_OPERATOR = new InfixOperator[0];
057    
058      public Term(final LValue headValue)
059      {
060        if (headValue == null)
061        {
062          throw new NullPointerException();
063        }
064    
065        this.headValue = headValue;
066      }
067    
068      public TypeValuePair evaluate() throws EvaluationException
069      {
070        TypeValuePair result = optimizedHeadValue.evaluate();
071        for (int i = 0; i < operandsArray.length; i++)
072        {
073          final LValue value = operandsArray[i];
074          final InfixOperator op = operatorArray[i];
075          result = op.evaluate(getContext(), result, value.evaluate());
076        }
077        return result;
078      }
079    
080      public void add(final InfixOperator operator, final LValue operand)
081      {
082        if (operator == null)
083        {
084          throw new NullPointerException();
085        }
086        if (operand == null)
087        {
088          throw new NullPointerException();
089        }
090    
091        if (operands == null || operators == null)
092        {
093          this.operands = new ArrayList();
094          this.operators = new ArrayList();
095        }
096    
097        operands.add(operand);
098        operators.add(operator);
099        initialized = false;
100      }
101    
102      public void initialize(final FormulaContext context) throws EvaluationException
103      {
104        super.initialize(context);
105        if (operands == null || operators == null)
106        {
107          this.optimizedHeadValue = headValue;
108          this.optimizedHeadValue.initialize(context);
109          this.operandsArray = EMPTY_L_VALUE;
110          this.operatorArray = EMPTY_OPERATOR;
111          return;
112        }
113    
114        if (initialized)
115        {
116          optimizedHeadValue.initialize(context);
117          for (int i = 0; i < operandsArray.length; i++)
118          {
119            final LValue lValue = operandsArray[i];
120            lValue.initialize(context);
121          }
122          return;
123        }
124    
125        optimize(context);
126      }
127    
128      private void optimize(final FormulaContext context) throws EvaluationException
129      {
130        final ArrayList operators = (ArrayList) this.operators.clone();
131        final ArrayList operands = (ArrayList) this.operands.clone();
132        this.optimizedHeadValue = headValue;
133    
134        while (true)
135        {
136          // now start to optimize everything.
137          // first, search the operator with the highest priority..
138          final InfixOperator op = (InfixOperator) operators.get(0);
139          int level = op.getLevel();
140          boolean moreThanOne = false;
141          for (int i = 1; i < operators.size(); i++)
142          {
143            final InfixOperator operator = (InfixOperator) operators.get(i);
144            final int opLevel = operator.getLevel();
145            if (opLevel != level)
146            {
147              moreThanOne = true;
148              level = Math.min(opLevel, level);
149            }
150          }
151    
152          if (moreThanOne == false)
153          {
154            // No need to optimize the operators ..
155            break;
156          }
157    
158          // There are at least two op-levels in this term.
159          Term subTerm = null;
160          for (int i = 0; i < operators.size(); i++)
161          {
162            final InfixOperator operator = (InfixOperator) operators.get(i);
163            if (operator.getLevel() != level)
164            {
165              subTerm = null;
166              continue;
167            }
168    
169            if (subTerm == null)
170            {
171              if (i == 0)
172              {
173                subTerm = new Term(optimizedHeadValue);
174                optimizedHeadValue = subTerm;
175              }
176              else
177              {
178                final LValue lval = (LValue) operands.get(i - 1);
179                subTerm = new Term(lval);
180                operands.set(i - 1, subTerm);
181              }
182            }
183    
184            // OK, now a term exists, and we should join it.
185            final LValue operand = (LValue) operands.get(i);
186            subTerm.add(operator, operand);
187            operands.remove(i);
188            operators.remove(i);
189            // Rollback the current index ..
190            //noinspection AssignmentToForLoopParameter
191            i -= 1;
192          }
193        }
194    
195        this.operatorArray = (InfixOperator[])
196            operators.toArray(new InfixOperator[operators.size()]);
197        this.operandsArray = (LValue[])
198            operands.toArray(new LValue[operands.size()]);
199        this.optimizedHeadValue.initialize(context);
200        for (int i = 0; i < operandsArray.length; i++)
201        {
202          final LValue value = operandsArray[i];
203          value.initialize(context);
204        }
205    
206      }
207    
208      /**
209       * Returns any dependent lvalues (parameters and operands, mostly).
210       *
211       * @return
212       */
213      public LValue[] getChildValues()
214      {
215        final LValue[] values = new LValue[operandsArray.length + 1];
216        values[0] = headValue;
217        System.arraycopy(operandsArray, 0, values, 1, operandsArray.length);
218        return values;
219      }
220    
221    
222      public String toString()
223      {
224        final StringBuffer b = new StringBuffer();
225    
226        b.append("(");
227        b.append(headValue);
228        if (operands != null && operators != null)
229        {
230          for (int i = 0; i < operands.size(); i++)
231          {
232            final InfixOperator op = (InfixOperator) operators.get(i);
233            final LValue value = (LValue) operands.get(i);
234            b.append(op);
235            b.append(value);
236          }
237        }
238        b.append(")");
239    //
240    //    b.append(";OPTIMIZED(");
241    //    b.append(optimizedHeadValue);
242    //    if (operandsArray != null && operatorArray != null)
243    //    {
244    //      for (int i = 0; i < operandsArray.length; i++)
245    //      {
246    //        final InfixOperator op = operatorArray[i];
247    //        final LValue value = operandsArray[i];
248    //        b.append(op);
249    //        b.append(value);
250    //      }
251    //    }
252    //    b.append(")");
253    
254        return b.toString();
255      }
256    
257      /**
258       * Checks, whether the LValue is constant. Constant lvalues always return the
259       * same value.
260       *
261       * @return
262       */
263      public boolean isConstant()
264      {
265        if (headValue.isConstant() == false)
266        {
267          return false;
268        }
269    
270        for (int i = 0; i < operands.size(); i++)
271        {
272          final LValue value = (LValue) operands.get(i);
273          if (value.isConstant() == false)
274          {
275            return false;
276          }
277        }
278        return true;
279      }
280    
281      public Object clone() throws CloneNotSupportedException
282      {
283        final Term o = (Term) super.clone();
284        if (operands != null)
285        {
286          o.operands = (ArrayList) operands.clone();
287        }
288        if (operators != null)
289        {
290          o.operators = (ArrayList) operators.clone();
291        }
292        o.headValue = (LValue) headValue.clone();
293        o.optimizedHeadValue = null;
294        o.operandsArray = null;
295        o.operatorArray = null;
296        o.initialized = false;
297        return o;
298      }
299    
300      public InfixOperator[] getOperands ()
301      {
302        return (InfixOperator[]) operands.toArray(new InfixOperator[operands.size()]);
303      }
304    
305      public LValue[] getOperators ()
306      {
307        return (LValue[]) operators.toArray(new LValue[operators.size()]);
308      }
309    
310      public LValue getHeadValue()
311      {
312        return headValue;
313      }
314      
315      /**
316       * Allows access to the post optimized head value
317       * note that without the optimization, it's difficult to traverse
318       * libformula's object model.
319       *
320       * @return optimized head value
321       */
322      public LValue getOptimizedHeadValue()
323      {
324        return optimizedHeadValue;
325      }
326      
327      /**
328       * Allows access to the post optimized operator array
329       * 
330       * @return optimized operator array
331       */
332      public InfixOperator[] getOptimizedOperators()
333      {
334        return operatorArray;
335      }
336      
337      /**
338       * Allows access to the post optimized operand array
339       *
340       * @return optimized operand array
341       */
342      public LValue[] getOptimizedOperands()
343      {
344        return operandsArray;
345      }
346    }