Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/modama/framework/operations/AbstractOperation.java


1   /**
2    Modama project, Institute for Polymer Research Dresden
3    Copyright (C) 2003 P. Fritsche, A. Uhlig
4    http://www.modama.org
5    info@modama.org
6   
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10   (at your option) any later version.
11  
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16  
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, write to the Free Software
19   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  
21   file created: Mar 18, 2003
22  
23   */
24  package org.modama.framework.operations;
25  
26  import org.modama.framework.entities.AbstractEntity;
27  import org.modama.framework.AbstractFrameworkModel;
28  import org.modama.framework.tools.Resources;
29  import org.modama.framework.exceptions.DeleteCancelledException;
30  import org.modama.framework.world.World;
31  import org.modama.tools.BeanCloner;
32  import org.modama.Model;
33  import org.modama.gui.tools.Utils;
34  import org.apache.log4j.*;
35  
36  import java.beans.*;
37  import java.util.List;
38  import java.util.LinkedList;
39  import java.util.Iterator;
40  import java.net.URL;
41  import java.io.IOException;
42  import java.io.OutputStream;
43  import java.io.FileWriter;
44  import java.io.FileOutputStream;
45  
46  /**
47   * The abstract base class for an operation. An op consists of several inputentities,
48   * after setting needed inputs, an op can be executed to create an output entity.
49   *
50   * if you want to create a new operation extend this class, if your operation depends on the
51   * input, you can use the strategy pattern -> create innerclasses that implement Calculator, they
52   * are automaticly choosen by searching a calculator that accepts the current input
53   *
54   * in addition, if you change an input entity, the op will recalculate
55   * and set the new data to the resulting entity, what will also affect an event, etc..
56   *
57   * TODO add thread
58   * TODO test threadsafety
59  
60    changes:
61      05/10/03 - synopia - added getInputCount(), need this to start calculation, when op is ready
62    */
63  
64  public abstract class AbstractOperation extends AbstractFrameworkModel implements PropertyChangeListener
65  {
66      /**
67       * is fired when the execution thread finished
68       */
69      public static final String EVENT_EXECUTION_FINISHED = "opExecutionFinished";
70      public static final String EVENT_EXECUTION_STARTED = "opExecutionStarted";
71      public static final String EVENT_CONNECT = "opConnectWithEntity";
72      public static final String EVENT_DISCONNECT = "opDisconnectWithEntity";
73  
74      protected static Logger logger = Logger.getLogger(AbstractOperation.class);
75  
76      /**
77       * class of the entity that results by this operation
78       */
79      protected Class resultingClass;
80  
81      /**
82       * the result of the operation or null if not yet executed
83       */
84      protected AbstractEntity output;
85  
86      /**
87       * the input entities of the operation
88       */
89      protected List input;
90      /**
91       * when created an operator is inactive, by executing its acitvated and will automaticly recalc if input changes
92       */
93      protected boolean active = false;
94      /**
95       * this is a strategypattern, the concret calculation depends on the inputs and should be choosen when the inputlist changes
96       */
97      protected Calculator calculator;
98      /**
99       * an array with instances of all the inner classes that implement the Calculator interface,
100      * this is automaticly filled in the constructor
101      */
102     protected Calculator[] availabeCalculators;
103     /**
104      * interface for the strategypattern
105      */
106     interface Calculator {
107         public AbstractEntity calculate();
108         /**
109          * returns true if the current input is valid for this calculation
110          */
111         public boolean isValidInput();
112         public Class getOutputClass();
113     }
114 
115     /**
116      * create an empty operation
117      */
118     public AbstractOperation()
119     {
120         this("operation");
121     }
122 
123     /**
124      * create operation with given name
125      * @param name
126      */
127     public AbstractOperation( String name )
128     {
129         this.name = name;
130         input = new LinkedList();
131         fillAvailabeCalculatorsArray();
132         create();
133     }
134 
135     /**
136      * this has to be implemented, all the available calculators have to be inserted here
137      */
138     protected abstract void fillAvailabeCalculatorsArray();
139 /**
140  * this piece of code doesnt work, because its impossible to create innerclasses like this, another searchalgorithm could
141  * be used and the calculator transformed to static innerclasses
142  */
143 
144 /*
145     {
146         // get this classobject
147         Class me = this.getClass();
148         // get all the inner classes
149         Class[] classes = me.getClasses();
150         // count the calculatorclasses
151         int cnt = 0;
152         for (int i = 0; i < classes.length; i++)
153             if( Calculator.class.isAssignableFrom( classes[i] ) && !classes[i].isInterface() ) cnt++;
154         // if 0, return
155         if( cnt == 0 ) return;
156         // create the array
157         availabeCalculators = new Calculator[cnt];
158         // fill the array
159         cnt = 0;
160         for (int i = 0; i < classes.length; i++) {
161             if( Calculator.class.isAssignableFrom( classes[i] ) && !classes[i].isInterface() ) {
162                 try {
163                     availabeCalculators[cnt] = (Calculator) classes[i].newInstance();
164                 } catch (InstantiationException e) {
165                     e.printStackTrace();  //To change body of catch statement use Options | File Templates.
166                 } catch (IllegalAccessException e) {
167                     e.printStackTrace();  //To change body of catch statement use Options | File Templates.
168                 }
169                 cnt++;
170             }
171         }
172     }
173 */
174 
175     /**
176      * put the entity to the world
177      */
178     protected void create() {
179         World.getInstance().put( this );
180     }
181 
182     /**
183      * delete the entity from the world
184      * @throws org.modama.framework.exceptions.DeleteCancelledException
185      */
186     public void delete() throws DeleteCancelledException {
187         World.getInstance().remove( this );
188     }
189 
190     /**
191      * uses the XMLDecoder to load the object from an inputstream
192      * @param url
193      * @return
194      * @throws IOException
195      */
196     public boolean load(URL url) throws IOException {
197         World.getInstance().setAcceptObjects( false );
198         XMLDecoder xml = new XMLDecoder( url.openStream() );
199         AbstractOperation loadedobject = null;
200         try {
201             loadedobject = (AbstractOperation) xml.readObject();
202             xml.close();
203             BeanCloner.clone( loadedobject, this, this.getClass() );
204             // successfull loaded
205             state = SAVED;
206             World.getInstance().setAcceptObjects( true );
207             return true;
208         } catch (Exception e) {
209             xml.close();
210         }
211         World.getInstance().setAcceptObjects( true );
212         return false;
213     }
214 
215     /**
216      * uses the XMLEncoder to save the object
217      */
218     public void save(URL url) throws IOException {
219         World.getInstance().setAcceptObjects( false );
220         XMLEncoder xml = new XMLEncoder( new FileOutputStream( url.getFile() ) );
221         xml.writeObject( this );
222         xml.close();
223         World.getInstance().setAcceptObjects( true );
224         // successfull saved
225         state = SAVED;
226     }
227 
228 
229     /**
230      * execute the operation, if the last created output entity is of the same type as the one we will create this time,
231      * it is reused, otherwise a new one is created
232      * @return the resulting entity of the operation, null if no valid input
233      */
234     public AbstractEntity execute()
235     {
236         // check if we can at all produce an output
237         if( resultingClass == null ) return null;
238         // check if the input is not empty
239         for (Iterator iterator = input.iterator(); iterator.hasNext();) {
240             AbstractEntity entity = (AbstractEntity) iterator.next();
241             if( entity.isEmpty() ){
242                 logger.error("Cannot execute with empty input");
243                 return null;
244             }
245         }
246         // check if we have a valid output entity
247         if( output == null || output.getClass() != resultingClass /*|| output.getState() == Model.DELETED*/ ) {
248             // we have to create a new output class
249             createOutputEntity();
250         }
251         fire( EVENT_EXECUTION_STARTED );
252         output = calculate();
253         active = true;
254         fire( EVENT_EXECUTION_FINISHED, output );
255         return output;
256     }
257 
258     /**
259      * get the result of the operation
260      * @return
261      */
262     public AbstractEntity Output() {
263         return output;
264     }
265 
266     /**
267      * returns true if the operation is active, what means it listens for changes in the input and recalculates automaticly
268      * @return
269      */
270     public boolean isActive() {
271         return active;
272     }
273 
274     /**
275      * if set to false, the operation will not recalc automaticly if inputdata changes
276      * @param active
277      */
278     public void setActive(boolean active) {
279         this.active = active;
280     }
281 
282     /**
283      * an inputproperty has changed, normaly this means we have to recalculate
284      * TODO do handling for partly changes of lists, so only changed parts have to be recalculated
285      * @param evt
286      */
287     public void propertyChange(PropertyChangeEvent evt) {
288         if( canExecute() || active )
289             execute();
290     }
291 
292 
293     /**
294      * input methods
295      */
296 
297     /**
298      * add this entity to the input<br>
299      * IllegalArgumentException is thrown if the entity is not accepted (this should be tested bevor by using the method <code>accept</code>)
300      * @param entity
301      */
302     public void put( AbstractEntity entity )
303     {
304         // throw exceptions if entity not accepted, we will also not accept the output as input
305         if( !accept(entity) || entity == output ) throw new IllegalArgumentException();
306         // register me as listener
307         entity.addPropertyChangeListener( this );
308         // add entity to inputlist
309         input.add( entity );
310         // search a strategy
311         searchCalculator();
312 
313         fire( EVENT_CONNECT, entity );
314     }
315 
316     /**
317      * replaces the old entity versus the new<br>
318      * IllegalArgumentException is thrown if the entity is not accepted
319      * @param newentity
320      * @param oldentity
321      */
322     //public abstract void replace( AbstractEntity newentity, AbstractEntity oldentity );
323 
324     /**
325      * removes all input and the output
326      */
327     public void removeAll()
328     {
329         setOutput( null );
330         removeAllInput();
331     }
332 
333     /**
334      * removes all input
335      */
336     public void removeAllInput()
337     {
338         Iterator it = input.iterator();
339         // remove me from listenerlist
340         while( it.hasNext() ) {
341             AbstractEntity entity = (AbstractEntity) it.next();
342             entity.removePropertyChangeListener( this );
343             it.remove();
344         }
345         // search a strategy
346         searchCalculator();
347 
348         fire( EVENT_DISCONNECT );
349     }
350 
351     /**
352      * removes the entity from the inputlist, or removes the output if entity is the outputentity
353      * @param entity
354      */
355     public void remove( AbstractEntity entity )
356     {
357         if( entity == output )
358         {
359             setOutput( null );
360         }
361         else
362         {
363             // remove me from listenerlist
364             entity.removePropertyChangeListener( this );
365             // remove entity from list
366             input.remove( entity );
367             // search a strategy
368             searchCalculator();
369         }
370         fire( EVENT_DISCONNECT, entity);
371     }
372 
373     /**
374      * returns true if the entity will be accepted if put to the operation
375      * @param entity
376      * @return
377      */
378     public abstract boolean accept( AbstractEntity entity );
379 
380     /**
381      * end
382      * input methods
383      */
384 
385     /**
386      * here YOU can place your code for the calculation if you dont want to use the strategypattern, but in this case
387      * you also have to change the search method (because it wont find anything and so sets some values to null),
388      * so you sould use the pattern :)
389      * @return
390      */
391     protected AbstractEntity calculate(){
392         if( calculator != null ) {
393             return calculator.calculate();
394         }
395         return null;
396     }
397 
398     /**
399      * uses reflection to search for a fitting calculator<br>
400      * if one is found, that the outputClass
401      */
402     protected void searchCalculator()
403     {
404         if( availabeCalculators != null ) {
405             // for each availabe calculator
406             for (int i = 0; i < availabeCalculators.length; i++) {
407                 Calculator calc = availabeCalculators[i];
408                 // acceppts it the input?
409                 if( calc.isValidInput() ) {
410                     // that the outputclass
411                     resultingClass = calc.getOutputClass();
412                     // set the strategy
413                     calculator = calc;
414                     // return, because we found one
415                     return;
416                 }
417             }
418         }
419         // nothing found
420         resultingClass = null;
421         calculator = null;
422     }
423 
424     /**
425      * creates an instance of the resulting class
426      */
427     protected void createOutputEntity()
428     {
429         try
430         {
431             output = ( AbstractEntity )resultingClass.newInstance();
432             // fire a connections event
433             fire( EVENT_CONNECT, output );
434         } catch( InstantiationException e1 )
435         {
436             logger.error("Cannot istantiate Class "+resultingClass.getName(), e1);
437         } catch( IllegalAccessException e1 )
438         {
439             logger.error("Cannot istantiate Class "+resultingClass.getName(), e1);
440         }
441     }
442 
443     /**
444      * returns the number of inputs
445      * @return
446      */
447     public int getInputCount()
448     {
449         return input.size();
450     }
451 
452     public abstract boolean canExecute();
453 
454     public List getInputs() { return input; }
455 
456     public AbstractEntity getOutput() {
457         //if( output != null && output.getState() == Model.DELETED ) output = null;
458         return output;
459     }
460 
461     public void setOutput(AbstractEntity output) {
462         this.output = output;
463     }
464 
465     /**
466      * get a string for the classname from the resources
467      * @return
468      */
469     public String toString() {
470         return Resources.getString( Utils.getClassNameWithoutPackage( this.getClass() ) );
471     }
472 }