View Javadoc

1   /* (c) Copyright 2003 Caleigo AB, All rights reserved. 
2    * 
3    * This library is free software; you can redistribute it and/or
4    * modify it under the terms of the GNU Lesser General Public
5    * License as published by the Free Software Foundation; either
6    * version 2.1 of the License, or (at your option) any later version.
7    * 
8    * This library is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   * Lesser General Public License for more details.
12   * 
13   * You should have received a copy of the GNU Lesser General Public
14   * License along with this library; if not, write to the Free Software
15   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   *  
17   */
18  
19  package org.caleigo.core;
20  
21  import java.util.*;
22  
23  import org.caleigo.core.event.*;
24  import org.caleigo.core.exception.*;
25  import org.caleigo.toolkit.log.*;
26  
27  /*** Selection is an entity collection class that can store zero or more 
28   * IEntity objects. The entities stored by this interface are type specified 
29   * and must be defined by a single IEntityDescriptor. 
30   *
31   * The Selection class is ordered and contained entities can be accesed 
32   * by index. The class does not accept duplicate entities if contained 
33   * entity has identity fields.
34   *
35   * @author  Dennis Zikovic
36   * @version 1.00
37   * 
38   *//* 
39   *
40   * WHEN        WHO               WHY & WHAT
41   * ------------------------------------------------------------------------------
42   * 2001-10-10  Dennis Zikovic    Creation
43   */
44  public class Selection implements ISelection
45  {
46      // Data members ------------------------------------------------------------
47      private List mEntityList;
48      private IEntityDescriptor mEntityDescriptor;
49      
50      private transient EntityRelayListener mEntityRelayListener;
51      private transient ISelectionListener mSelectionListener;
52      private transient IEntityListener mEntityListener;
53      private transient IEntityChangeListener mEntityChangeListener;
54  
55      // Constructors ------------------------------------------------------------
56      
57      public Selection(IEntityDescriptor entityDescriptor)
58      {
59          mEntityDescriptor = entityDescriptor;
60          mEntityList = new ArrayList();
61      }
62      
63      /*** This is a copy constructor that makes a shallow copy of the contained 
64       * entities in the provided collection.
65       */
66      public Selection(ISelection selection)
67      {
68          mEntityDescriptor = selection.getEntityDescriptor();
69          mEntityList = new ArrayList(selection.asList());
70      }
71      
72      // ISelection implementation ----------------------------------------
73      
74      /*** Return true if any (one or more) of the collections's contained 
75       * entities has the DIRTY flag set to true that is have unsaved changes.
76       */
77      public boolean isDirty()
78      {
79          boolean dirty = false;
80          for(int j=0; !dirty && j<this.size(); j++)
81              dirty = this.getEntity(j).isDirty();
82          return dirty;
83      }
84      
85      /*** Stores all contained entities that have the DIRTY flag set to true.
86       * The entities are stored in a single transaction meaning that if one
87       * store fails then all fails.
88       */
89      public void storeAll()
90      {
91          // Break if no dirty entity exists.
92          if(!this.isDirty())
93              return;
94          
95          // Extract default service and break if none is defined.
96          if(this.getEntityDescriptor().getDataSourceDescriptor().getDefaultDataSource()==null)
97              throw new DataServiceNotFoundException("No default data service has been set for data source: "+this.getEntityDescriptor().getDataSourceDescriptor().getCodeName());
98          IDataService service = this.getEntityDescriptor().getDataSourceDescriptor().getDefaultDataSource().getDataService();
99         
100         // Store all entities in a single transaction
101         IDataTransaction trans = service.newTransaction();
102         for(int j=0; j<this.size(); j++)
103             if(this.getEntity(j).isDirty())
104                 trans.addStore(this.getEntity(j));
105         trans.commit();
106     }
107     
108     /*** Deletes all contained entities. The entities are deleted in a single 
109      * transaction meaning that if one delete fails then all fails.
110      * USE THIS METHOD WITH CAUTION!
111      */
112     public void deleteAll()
113     {
114         // Extract default service and break if none is defined.
115         if(this.getEntityDescriptor().getDataSourceDescriptor().getDefaultDataSource()==null)
116             throw new DataServiceNotFoundException("No default data service has been set for data source: "+this.getEntityDescriptor().getDataSourceDescriptor().getCodeName());
117         IDataService service = this.getEntityDescriptor().getDataSourceDescriptor().getDefaultDataSource().getDataService();
118        
119         // Store all entities in a single transaction
120         IDataTransaction trans = service.newTransaction();
121         for(int j=0; j<this.size(); j++)
122             trans.addDelete(this.getEntity(j));
123         trans.commit();
124     }
125     
126     /*** Performs a refresh on all contained entities. The refresh is batched
127      * to save performance.
128      */
129     public void refreshAll()
130     {
131         // Extract default service and break if none is defined.
132         if(this.getEntityDescriptor().getDataSourceDescriptor().getDefaultDataSource()==null)
133             throw new DataServiceNotFoundException("No default data service has been set for data source: "+this.getEntityDescriptor().getDataSourceDescriptor().getCodeName());
134         IDataService service = this.getEntityDescriptor().getDataSourceDescriptor().getDefaultDataSource().getDataService();
135        
136         // Store all entities in a single transaction
137         IDataTransaction trans = service.newTransaction();
138         for(int j=0; j<this.size(); j++)
139             trans.addRefresh(this.getEntity(j));
140         trans.commit();
141     }
142     
143     /*** Adds the provided IEntity object to the end of selection object. 
144      * If an entity with the same identity already exists in the selection the
145      * requeat is ignored and false is returned.
146      */
147     public boolean addEntity(IEntity entity)
148     {
149         if(this.doesAccept(entity))
150         {
151             // Add the entity.
152             boolean res = mEntityList.add(entity);
153             
154             // Add listeners to entity if neaded.
155             this.registerListeners(entity);
156             
157             // Fire add event.
158             if(res)
159                 this.fireEntityAdded(entity, mEntityList.size());
160             
161             return res;
162         }
163         else
164             return false;
165     }
166     
167     /*** Adds the provided IEntity object to at the specified index in the 
168      * selection object. If an entity with the same identity already exists 
169      * in the selection the requeat is ignored and false is returned.
170      */
171     public boolean addEntity(int index, IEntity entity)
172     {
173         if(this.doesAccept(entity))
174         {
175             // Add the entity.
176             mEntityList.add(index, entity);
177             
178             // Add listeners to entity if neaded.
179             this.registerListeners(entity);
180             
181             // Fire add event.
182             this.fireEntityAdded(entity, index);
183             
184             return true;
185         }
186         else
187             return false;
188     }
189     
190     /*** Access method that returns the contained IEntity object with the 
191      * specified index.
192      */
193     public IEntity getEntity(int index)
194     {
195         return (IEntity)mEntityList.get(index);
196     }
197     
198     /*** Mutation method that removes the provided entity from the selection.
199      * Returns true if the entity was found and removed otherwise false is 
200      * returned.
201      */
202     public boolean removeEntity(IEntity entity)
203     {
204         int index = mEntityList.indexOf(entity);
205         if(index>=0)
206             this.removeEntity(index);
207         return index>=0;
208     }
209     
210     /*** Mutation method that removes the indexed entity from the selection.
211      * Returns the indexed entity if it was found and removed otherwise null  
212      * is returned. The entities are not effecte in any other way.
213      */
214     public IEntity removeEntity(int index)
215     {
216         IEntity oldEntity = (IEntity)mEntityList.remove(index);
217             
218         // Remove entity listeners from old entity if neaded.
219         this.unregisterListeners(oldEntity);
220 
221         // Fire remove event.
222         if(oldEntity!=null)
223             this.fireEntityRemoved(oldEntity, index);
224         
225         return oldEntity;
226     }
227     
228     /*** Returns a java.util.Iterator object that iterates over all entities
229      * in the selection. The iterator should be read only and should not 
230      * support the remove method. The entities are not effected in any other way.
231      */
232     public Iterator iterator()
233     {
234         return mEntityList.listIterator();
235     }
236     
237     /*** Mutation method the removes all entities currently stored in the 
238      * selection. The entities are not effected in any other way.
239      */
240     public void clear()
241     {
242         // Remove entity listeners from the listed entities if neaded.
243         this.unregisterEntityListener();
244         this.unregisterEntityChangeListener();
245 
246         // Remove all entities from the list.
247         mEntityList.clear();
248         
249         // Fire remove event.
250         this.fireContentsChanged();
251     }
252     
253     /*** Access method that reurns the number entities currently contained in
254      * the selection object.
255      */
256     public int size()
257     {
258         return mEntityList.size();
259     }
260     
261     /*** Boolean access method that return true if the selection is empty.
262      */
263     public boolean isEmpty()
264     {
265         return mEntityList.isEmpty();
266     }
267     
268     /*** Access method that views the selection objct as a grid where row is
269      * the entity index and column is the field index for the stored entities.
270      */
271     public Object getData(int row, int column)
272     {
273         if(row<this.size() && column<this.getEntityDescriptor().getFieldCount())
274             return ((IEntity)mEntityList.get(row)).getData(this.getEntityDescriptor().getFieldDescriptor(column));
275         else
276             return null;
277     }
278     
279     /*** Mutation method that views the selection objct as a grid where row is
280      * the entity index and column is the field index for the stored entities.
281      */
282     public void setData(int row, int column, Object dataValue)
283     {
284         if(row<this.size() && column<this.getEntityDescriptor().getFieldCount())
285             ((IEntity)mEntityList.get(row)).setData(this.getEntityDescriptor().getFieldDescriptor(column), dataValue);
286     }
287     
288     /*** Access method that returns the IEntityDescriptor for the selection.
289      * The selection object will only support entities of that type.
290      */ 
291     public IEntityDescriptor getEntityDescriptor()
292     {
293         return mEntityDescriptor;
294     }
295     
296     /*** Creates a sub selection with the indexed entities in the called 
297      * selection. The created selection that should be independant of changes 
298      * in the source/called selection after the time of creation. If any index
299      * in the array is out of bounds it will be ignored and no exception will
300      * be thrown.
301      */
302     public ISelection createSubSelection(int[] indexArray)
303     {
304         ISelection selection = new Selection(this.getEntityDescriptor());
305         for(int j=0; j<indexArray.length; j++)
306             if(indexArray[j]>=0 && indexArray[j]<selection.size())
307                 selection.addEntity(this.getEntity(indexArray[j]));
308         return selection;
309     }
310     
311     /*** Creates a sub selection with all qualified entities in the called 
312      * selection. The created selection that should be independant of changes 
313      * in the source/called selection after the time of creation.
314      */
315     public ISelection createSubSelection(Qualifier qualifier)
316     {
317         ISelection selection = new Selection(this.getEntityDescriptor());
318         for(int j=0; j<this.size(); j++)
319             if(qualifier.doesQualify(this.getEntity(j)))
320                 selection.addEntity(this.getEntity(j));
321         return selection;
322     }
323 
324     /*** Help method that returns true if the provided IEntity object exists in
325      * the selection otherwise false is returned.
326      */
327     public boolean contains(IEntity entity)
328     {
329         return mEntityList.contains(entity);
330     }
331     
332     /*** Help method that returns the index of the provided IEntity object in 
333      * the selection if it exists othewise a negative value is returned.
334      */
335     public int indexOf(IEntity entity)
336     {
337         return mEntityList.indexOf(entity);
338     }
339     
340     /*** Help method that returns the index of the the first entity object in 
341      * the selection with the specified field set to the specified value.
342      */
343     public int indexOf(IFieldDescriptor fieldDescriptor, Object fieldData)
344     {
345         if(fieldDescriptor==null || !this.getEntityDescriptor().contains(fieldDescriptor))
346             throw new InvalidFieldException("The field "+fieldDescriptor+" is not a part of "+this.getEntityDescriptor()+"!");
347         int fieldIndex = this.getEntityDescriptor().getFieldIndex(fieldDescriptor);
348         
349         int index = -1;
350         for(int j=0; index<0 && j<this.size(); j++)
351             if(DataType.isDataEqual(this.getData(j, fieldIndex), fieldData))
352                 index=j;
353         return index;
354     }
355             
356     /*** Help method that returns true if the provided IEntity object will be
357      * accepted by the selection if added or inserted to it. Reasons for not 
358      * accepting an entity is wrong type (IEntityDescriptor), already included 
359      * (valid only if the entity descripror has identity fields) or if the 
360      * selection is qualified the entity may fail qualification.
361      */
362     public boolean doesAccept(IEntity entity)
363     {
364         boolean hasIdentityFields = false;
365         for(int j=0; !hasIdentityFields && j<mEntityDescriptor.getFieldCount(); j++)
366             hasIdentityFields = mEntityDescriptor.getFieldDescriptor(j).isIdentityField();
367         return mEntityDescriptor.equals(entity.getEntityDescriptor()) && !(hasIdentityFields && this.contains(entity));
368     }
369     
370     /*** This method sorts the called selection using the provided comparator.
371      * One single content change event will be fired when this method is called.
372      * Note that if the provided Comparator does not support IEntity objects
373      * an exeption will be thrown.
374      * @see EntityCollator
375      */
376     public void sort(Comparator comparator)
377     {
378         Collections.sort(mEntityList, comparator);
379         this.fireContentsChanged();
380     }
381 
382     /*** Returns a Set that acts as a wrapper for the selection. The Set object 
383      * should reflect changes to and from the wrapped selection.
384      */
385     public Set asSet()
386     {
387         return new EntityPoolSet();
388     }
389     
390     /*** Returns a List that acts as a wrapper for the selection. The List  
391      * object should reflect changes to and from the wrapped selection.
392      */
393     public List asList()
394     {
395         return new SelectionList();
396     }
397     
398     /*** Adds an ISelectionListener to receive notifiactions of changes
399      * in the entity content of the collection object. 
400      */
401     public void addSelectionListener(ISelectionListener listener)
402     {
403         mSelectionListener = (ISelectionListener)CELEventMulticaster.add(mSelectionListener, listener);
404     }
405     
406     /*** Removes an ISelectionListener from the collection object.
407      */
408     public void removeSelectionListener(ISelectionListener listener)
409     {
410         mSelectionListener = (ISelectionListener)CELEventMulticaster.remove(mSelectionListener, listener);
411     }
412     
413     /*** Adds IEntityListener to receive notifications of performed 
414      * data operations on all entities contained in the collection object.
415      */
416     public void addEntityListener(IEntityListener listener)
417     {
418         if(mEntityListener==null)
419             this.registerEntityListener();
420         mEntityListener = (IEntityListener)CELEventMulticaster.add(mEntityListener, listener);
421     }
422     
423     /*** Removes the specified IEntityListener from the collection object.
424      */
425     public void removeEntityListener(IEntityListener listener)
426     {
427         mEntityListener = (IEntityListener)CELEventMulticaster.remove(mEntityListener, listener);
428         if(mEntityChangeListener==null)
429             this.unregisterEntityListener();
430     }
431     
432     /*** Adds IEntityChangeListener to receive notifications of performed 
433      * data operations on all entities contained in the collection object. 
434      * Note that changes can in specific situations like during end-user 
435      * editation be very frequent. 
436      */
437     public void addEntityChangeListener(IEntityChangeListener listener)
438     {
439         if(mEntityChangeListener==null)
440             this.registerEntityChangeListener();
441         mEntityChangeListener = (IEntityChangeListener)CELEventMulticaster.add(mEntityChangeListener, listener);
442     }
443     
444     /*** Removes the specified IEntityListener from the collection object.
445      */
446     public void removeEntityChangeListener(IEntityChangeListener listener)    
447     {
448         mEntityChangeListener = (IEntityChangeListener)CELEventMulticaster.remove(mEntityChangeListener, listener);
449         if(mEntityChangeListener==null)
450             this.unregisterEntityChangeListener();
451     }
452         
453     // Help methods ------------------------------------------------------------
454     
455     /*** Help method to fire a SelectionEvent to all registered
456      * SelectionListeners for notification of generally changed contents.
457      */
458     protected void fireContentsChanged()
459     {
460         if(mSelectionListener!=null)
461             mSelectionListener.contentsChanged(new SelectionEvent(this));
462     }
463     
464     /*** Help method to fire a SelectionEvent to all registered
465      * SelectionListeners for notification of an added entity.
466      */
467     protected void fireEntityAdded(IEntity entity, int row)
468     {
469         if(mSelectionListener!=null)
470             mSelectionListener.entityAdded(new SelectionEvent(this, SelectionEvent.ENTITY_ADDED, entity, row));
471     }
472     
473     /*** Help method to fire a SelectionEvent to all registered
474      * SelectionListeners for notification of an removed entity.
475      */
476     protected void fireEntityRemoved(IEntity entity, int row)
477     {
478         if(mSelectionListener!=null)
479             mSelectionListener.entityRemoved(new SelectionEvent(this, SelectionEvent.ENTITY_REMOVED, entity, row));
480     }
481     
482     /*** Adds listeners relaying events from the specified entity to relevant
483      * listeners registered in the collection object. Both IEntityListener and
484      * IEntityChangeListener are handled by the method.
485      */
486     protected void registerListeners(IEntity entity)
487     {
488         if(mEntityListener!=null)
489             entity.addEntityListener(mEntityRelayListener);
490         if(mEntityChangeListener!=null)
491             entity.addEntityChangeListener(mEntityRelayListener);
492     }
493     
494     /*** Removes listeners relaying events from the specified entity to relevant
495      * listeners registered in the collection object. Both IEntityListener and
496      * IEntityChangeListener are handled by the method.
497      */
498     protected void unregisterListeners(IEntity entity)
499     {
500         if(mEntityListener!=null)
501             entity.removeEntityListener(mEntityRelayListener);
502         if(mEntityChangeListener!=null)
503             entity.removeEntityChangeListener(mEntityRelayListener);
504     }
505     
506     /*** Adds a IEntityListener to all contained entities that relays events  
507      * to IEntityListener registered in the collection object.
508      */
509     protected void registerEntityListener()
510     {
511         if(mEntityRelayListener==null)
512             mEntityRelayListener = new EntityRelayListener();
513         for(int j=0; j<this.size(); j++)
514             this.getEntity(j).addEntityListener(mEntityRelayListener);
515     }
516     
517     /*** Removes the IEntityListener from  all contained entities that relays   
518      * events to IEntityListener registered in the collection object.
519      */
520     protected void unregisterEntityListener()
521     {
522         for(int j=0; j<this.size(); j++)
523             this.getEntity(j).removeEntityListener(mEntityRelayListener);
524     }
525     
526     /*** Adds a IEntityChangeListener to all contained entities that relays   
527      * events to IEntityChangeListener registered in the collection object.
528      */
529     protected void registerEntityChangeListener()
530     {
531         if(mEntityRelayListener==null)
532             mEntityRelayListener = new EntityRelayListener();
533         for(int j=0; j<this.size(); j++)
534             this.getEntity(j).addEntityChangeListener(mEntityRelayListener);
535     }
536     
537     /*** Removes the IEntityChangeListener from all contained entities that relays   
538      * events to IEntityChangeListener registered in the collection object.
539      */
540     protected void unregisterEntityChangeListener()
541     {
542         for(int j=0; j<this.size(); j++)
543             this.getEntity(j).removeEntityChangeListener(mEntityRelayListener);
544     }
545     
546     /*** Help method that logs text information from the collection object using
547      * the log package. This method only logs identity field data.
548      */
549     public void log(Object source)
550     {
551         this.log(source, null);
552     }
553     
554     /*** Help method that logs text information from the collection object using
555      * the log package. This method logs data from all specified fields.
556      */
557     public void log(Object source, IFieldDescriptor[] fields)
558     {
559         for(int j=0; j<this.size(); j++)
560             Log.print(source, AbstractEntity.makeLogString(this.getEntity(j), fields));        
561     }
562         
563     // Nested classes ----------------------------------------------------------
564     protected class EntityPoolSet extends AbstractSet
565     {
566         public boolean add(Object obj)
567         {
568             if(obj instanceof IEntity)
569                 return Selection.this.addEntity((IEntity)obj);
570             else
571                 return false;
572         }
573         
574         public int size()
575         {
576             return Selection.this.size();
577         }
578         
579         public Iterator iterator()
580         {
581             return Selection.this.iterator();
582         }
583     }
584     
585     protected class SelectionList extends AbstractList
586     {
587         public int size()
588         {
589             return Selection.this.size();
590         }
591         
592         public Object get(int index)
593         {
594             return Selection.this.getEntity(index);
595         }
596                 
597         public Object set(int index, Object obj)
598         {
599             if(obj instanceof IEntity)
600             {
601                 IEntity entity = Selection.this.removeEntity(index);
602                 Selection.this.addEntity(index, (IEntity)obj);
603                 return entity;                
604             }
605             else
606                 return null;
607         }
608         
609         public boolean add(Object obj)
610         {
611             if(obj instanceof IEntity)
612                 return Selection.this.addEntity((IEntity)obj);
613             else
614                 return false;
615         }
616         
617         public void add(int index, Object obj)
618         {
619             if(obj instanceof IEntity)
620                 Selection.this.addEntity(index, (IEntity)obj);
621         }
622         
623         public boolean remove(Object obj)
624         {
625             if(obj instanceof IEntity)
626                 return Selection.this.removeEntity((IEntity)obj);
627             else
628                 return false;
629         }
630         
631         public Object remove(int index)
632         {
633             return Selection.this.removeEntity(index);
634         }
635     }
636     
637     protected class EntityRelayListener implements IEntityListener, IEntityChangeListener
638     {
639         // IEntityListener implemntation ---------------------------------
640         public void storePerformed(EntityEvent event)
641         {
642             mEntityListener.storePerformed(event);
643         }
644         
645         public void deletePerformed(EntityEvent event)
646         {
647             mEntityListener.deletePerformed(event);
648         }
649         
650         public void refreshPerformed(EntityEvent event)
651         {
652             mEntityListener.refreshPerformed(event);
653         }
654         
655         // IEntityChangeListener implemntation ---------------------------------
656         public void dataChanged(EntityChangeEvent event)
657         {
658             mEntityChangeListener.dataChanged(event);
659         }
660         
661         public void statusChanged(EntityChangeEvent event)
662         {
663             mEntityChangeListener.statusChanged(event);
664         }        
665     }
666 }