001    package edu.nrao.sss.util;
002    
003    import java.util.EnumSet;
004    
005    /**
006     * An indicator for where a collection of events is in its life cycle.
007     * <p>
008     * This enumeration is appropriate for the status of an event that
009     * is really a collection of finer grained events.  For the most part,
010     * the statuses of an atomic event and an event that is a collection
011     * of atomic events, overlap.  The one place they differ is in their
012     * treatments of "in progress" and "under way".</p>
013     * <p>
014     * See also {@link EventStatus}.</p>
015     * <p>
016     * <b>Version Info:</b>
017     * <table style="margin-left:2em">
018     *   <tr><td>$Revision: 2184 $</td></tr>
019     *   <tr><td>$Date: 2009-04-10 15:00:07 -0600 (Fri, 10 Apr 2009) $</td></tr>
020     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
021     * </table></p>
022     * 
023     * @author David M. Harland
024     * @since 2007-08-15
025     */
026    public enum EventSetStatus
027    {
028      /**
029       * Indicates that no event in a set is ready for scheduling.
030       */
031      NOT_READY_TO_BE_SCHEDULED(false),
032      
033      /**
034       * Indicates that no event in a set has been scheduled yet.
035       */
036      NOT_YET_SCHEDULED(false),
037      
038      /**
039       * Indicates that no event in a set has begun yet, but that at least
040       * one has been scheduled.
041       */
042      SCHEDULED_BUT_NOT_STARTED(false),
043      
044      /**
045       * Indicates that at least one event in a set is currently in progress,
046       * but says nothing about the other events in that set.
047       * If any one event in a set is in progress, then the set of events
048       * is also said to be in progress.
049       * <p>
050       * This status should be used only when an activity really is in progress.
051       * One place where this is ambiguous is as follows.  Imagine that we
052       * have a container that has a collection of components.  Each component
053       * has an execution status, as does the container.  The execution status
054       * of the container, though, is derived from that of its components.
055       * Imagine that some of the components have been completed and that the
056       * rest of them have not yet begun.  One could say that the container
057       * is "in progress", as it has begun but not yet been completed.
058       * However, we would really like to see this status used only when the
059       * activity is really happening.  Had one of the above components been
060       * in progress and all others as stated above, then this status would be
061       * appropriate for the container.  However, as given in the example, a
062       * better status would be {@link #UNDER_WAY}.</p>
063       */
064      IN_PROGRESS(false),
065      
066      /**
067       * Indicates that at least one, but not all, of the events in a set
068       * has reached the end of its life cycle.
069       * This status tells you that none of the component events
070       * are <tt>IN_PROGRESS</tt> or <tt>ON_HOLD</tt>. 
071       */
072      UNDER_WAY(false),
073      
074      /**
075       * Indicates that a set of events has been completed.
076       * Note that it is possible that some of the elemental events
077       * in the set may have been canceled, rather than completed.
078       * All elemental events, though, have reached the ends of
079       * their life cycles.
080       */
081      COMPLETED(true),
082      
083      /**
084       * Indicates that at least one event in a set has been put on hold
085       * and that none are currently in progress.
086       * Unless one or more events in a set is <tt>IN_PROGRESS</tt>, if
087       * any event in a set is <tt>ON_HOLD</tt>, then the set itself is
088       * said to be on hold.
089       * <p>
090       * An actor must act to take an event out of this
091       * status.  Note that this is subtly different than postponing
092       * an event, which places its status immediately back to
093       * <tt>NOT_YET_SCHEDULED</tt>.</p>
094       */
095      ON_HOLD(false),
096      
097      /**
098       * Indicates that an entire set of events has been canceled.
099       * A canceled event will not be resumed or restarted.
100       */
101      CANCELED(true);
102      
103      private boolean isFinal;
104      
105      EventSetStatus(boolean isFinal)
106      {
107        this.isFinal = isFinal;
108      }
109      
110      /**
111       * Returns <i>true</i> if this status is a final status for an event.
112       * <tt>CANCELED</tt> and <tt>COMPLETED</tt> are examples of final
113       * statuses.
114       * 
115       * @return <i>true</i> if this status is a final status for an event.
116       */
117      public boolean isFinal()
118      {
119        return isFinal;
120      }
121    
122      /**
123       * Uses the {@code componentStatuses} to derive an <tt>EventSetStatus</tt>.
124       * This method is appropriate when your event holds a collection of events,
125       * but the events that your event holds do <i>not</i> themselves hold other
126       * events.
127       * <p>
128       * Some of the rules used by this method are as follows:</p>
129       * <ol>
130       *   <li>If the list of component statuses is <i>null</i> or empty, then the
131       *       returned status is NOT_READY_TO_BE_SCHEDULED.</li>
132       *   <li>If any of the component statuses are IN_PROGRESS, then the
133       *       returned status is IN_PROGRESS.</li>
134       *   <li>If all the component statuses are the same, then that
135       *       singular status type is returned.</li>
136       *   <p><i>We reach the next items only if none of the above resulted
137       *         in an answer.</i></p>
138       *   <li>If some of the component statuses are final statuses and some are
139       *       nonfinal, then:
140       *       <ol type="a">
141       *         <li>if there is only a single type of non-final status,
142       *             that type is returned;</li>
143       *         <li>otherwise the returned status is UNDER_WAY.</li>
144       *       </ol>
145       *   </li>
146       *   <li>If all are final, then:
147       *       <ol type="a">
148       *         <li>if one or more component statuses are COMPLETE, then the
149       *             returned status is COMPLETE;</li>
150       *         <li>otherwise the returned status is CANCELED.</li>
151       *       </ol>
152       *   </li>
153       *   <li>If all are nonfinal, then:
154       *       <ol type="a">
155       *         <li>if one or more component statuses are
156       *             SCHEDULED_BUT_NOT_STARTED, then the
157       *             returned status is SCHEDULED_BUT_NOT_STARTED,</li>
158       *         <li>otherwise the returned status is NOT_YET_SCHEDULED.</li>
159       *       </ol>
160       *   </li>
161       * </ol>
162       * 
163       * @param componentStatuses a set of event statuses from which can be derived
164       *          a single status for a set of events.
165       *          
166       * @return an <tt>EventSetStatus</tt> that represents a logic combination of
167       *         the <tt>componentStatuses</tt>.
168       */
169      public static EventSetStatus createFrom(EventStatus... componentStatuses)
170      {
171        EventSetStatus answer = null;
172        
173        if (componentStatuses == null || componentStatuses.length == 0)
174        {
175          answer = EventSetStatus.NOT_READY_TO_BE_SCHEDULED;
176        }
177        else if (componentStatuses.length == 1)
178        {
179          answer = componentStatuses[0].toEventSetStatus();
180        }
181        else //length > 1
182        {
183          //Eliminates duplicates and lets us use the contains method
184          EnumSet<EventStatus> statuses =
185            EnumSet.of(componentStatuses[0], componentStatuses);
186          
187          //If all the componentStatuses were identical return
188          if (statuses.size() == 1)
189          {
190            answer = statuses.iterator().next().toEventSetStatus();
191          }
192          
193          //From here down we know we have more than one kind of status
194          
195          //A single in-progress trumps all else
196          else if (statuses.contains(EventStatus.IN_PROGRESS))
197          {
198            answer = EventSetStatus.IN_PROGRESS;
199          }
200          //Need to look at combinations
201          else
202          {
203            EnumSet<EventStatus> finalStatuses    = EnumSet.noneOf(EventStatus.class);
204            EnumSet<EventStatus> nonFinalStatuses = EnumSet.noneOf(EventStatus.class);
205            
206            for (EventStatus eventStatus : statuses)
207            {
208              if (eventStatus.isFinal())
209                finalStatuses.add(eventStatus);
210              else
211                nonFinalStatuses.add(eventStatus);
212            }
213            
214            //Final + NonFinal = under way
215            //Final only       = completed or canceled
216            if (!finalStatuses.isEmpty())
217            {
218              if (!nonFinalStatuses.isEmpty())
219              {
220                //If we have only ONE non-final status, return it instead of UNDER_WAY
221                answer = nonFinalStatuses.size() == 1 ?
222                         nonFinalStatuses.iterator().next().toEventSetStatus() :
223                         EventSetStatus.UNDER_WAY;
224              }
225              else //only final statuses
226              {
227                answer = finalStatuses.contains(EventStatus.COMPLETED) ?
228                         EventSetStatus.COMPLETED : EventSetStatus.CANCELED;
229              }
230            }
231            //NonFinal only: need to look at the non-final types
232            else
233            {
234              //Remember that IN_PROGRESS was handled early on, so we can't have
235              //that status here.  For ON_HOLD, we've decided we won't return
236              //that status unless it is the only non-final type, so we never
237              //return it here, even if it is present.
238              if (nonFinalStatuses.contains(EventStatus.SCHEDULED_BUT_NOT_STARTED))
239              {
240                answer = EventSetStatus.SCHEDULED_BUT_NOT_STARTED;
241              }
242              else if (nonFinalStatuses.contains(EventStatus.NOT_YET_SCHEDULED))
243              {
244                answer = EventSetStatus.NOT_YET_SCHEDULED;
245              }
246              else
247              {
248                throw new RuntimeException(
249                  "Logic error: not able to handle this set of non-final statuses: "
250                  + nonFinalStatuses);
251              }
252            }
253          }
254        }
255        
256        return answer;
257      }
258    
259      /**
260       * Uses the {@code componentStatuses} to derive a single
261       * <tt>EventSetStatus</tt>.  This method is appropriate when your event
262       * holds a collection of events, each which also holds a collection of
263       * events.
264       * <p>
265       * Some of the rules used by this method are as follows:</p>
266       * <ol>
267       *   <li>If the list of component statuses is <i>null</i> or empty, then the
268       *       returned status is NOT_READY_TO_BE_SCHEDULED.</li>
269       *   <li>If any of the component statuses are IN_PROGRESS, then the
270       *       returned status is IN_PROGRESS.</li>
271       *   <li>If all the component statuses are the same, then that
272       *       singular status type is returned.</li>
273       *   <p><i>We reach the next items only if none of the above resulted
274       *         in an answer.</i></p>
275       *   <li>If some of the component statuses are final statuses and some are
276       *       nonfinal, then:
277       *       <ol type="a">
278       *         <li>if there is only a single type of non-final status,
279       *             that type is returned;</li>
280       *         <li>otherwise the returned status is UNDER_WAY.</li>
281       *       </ol>
282       *   </li>
283       *   <li>If all are final, then:
284       *       <ol type="a">
285       *         <li>if one or more component statuses are COMPLETE, then the
286       *             returned status is COMPLETE;</li>
287       *         <li>otherwise the returned status is CANCELED.</li>
288       *       </ol>
289       *   </li>
290       *   <li>If all are nonfinal, then:
291       *       <ol type="a">
292       *         <li>if one or more component statuses are
293       *             UNDER_WAY, then the
294       *             returned status is UNDER_WAY,</li>
295       *         <li>otherwise, if one or more component statuses are
296       *             SCHEDULED_BUT_NOT_STARTED, then the
297       *             returned status is SCHEDULED_BUT_NOT_STARTED,</li>
298       *         <li>otherwise the returned status is NOT_YET_SCHEDULED.</li>
299       *       </ol>
300       *   </li>
301       * </ol>
302       * 
303       * @param componentStatuses a set of event set statuses from which can be
304       *          derived a single status for a set of events.
305       *          
306       * @return an <tt>EventSetStatus</tt> that represents a logic combination of
307       *         the <tt>componentStatuses</tt>.
308       */
309      //The logic here is very similar, but not identical, to that of the
310      //other createFrom method.
311      public static EventSetStatus createFrom(EventSetStatus... componentStatuses)
312      {
313        EventSetStatus answer = null;
314        
315        if (componentStatuses == null || componentStatuses.length == 0)
316        {
317          answer = EventSetStatus.NOT_READY_TO_BE_SCHEDULED;
318        }
319        else if (componentStatuses.length == 1)
320        {
321          answer = componentStatuses[0];
322        }
323        else //length > 1
324        {
325          //Eliminates duplicates and lets us use the contains method
326          EnumSet<EventSetStatus> statuses =
327            EnumSet.of(componentStatuses[0], componentStatuses);
328          
329          //If all the componentStatuses were identical return
330          if (statuses.size() == 1)
331          {
332            answer = statuses.iterator().next();
333          }
334          
335          //From here down we know we have more than one kind of status
336          
337          //A single in-progress trumps all else
338          else if (statuses.contains(EventSetStatus.IN_PROGRESS))
339          {
340            answer = EventSetStatus.IN_PROGRESS;
341          }
342          //Need to look at combinations
343          else
344          {
345            EnumSet<EventSetStatus> finalStatuses =
346              EnumSet.noneOf(EventSetStatus.class);
347    
348            EnumSet<EventSetStatus> nonFinalStatuses =
349              EnumSet.noneOf(EventSetStatus.class);
350            
351            for (EventSetStatus setStatus : componentStatuses)
352            {
353              if (setStatus.isFinal())
354                finalStatuses.add(setStatus);
355              else
356                nonFinalStatuses.add(setStatus);
357            }
358            
359            //Final + NonFinal = under way
360            //Final only       = completed or canceled
361            if (!finalStatuses.isEmpty())
362            {
363              if (!nonFinalStatuses.isEmpty())
364              {
365                //If we have only ONE non-final status, return it instead of UNDER_WAY
366                answer = nonFinalStatuses.size() == 1 ?
367                         nonFinalStatuses.iterator().next() : EventSetStatus.UNDER_WAY;
368              }
369              else //only final statuses
370              {
371                answer = finalStatuses.contains(EventSetStatus.COMPLETED) ?
372                         EventSetStatus.COMPLETED : EventSetStatus.CANCELED;
373              }
374            }
375            //NonFinal only: need to look at the non-final types
376            else
377            {
378              //Remember that IN_PROGRESS was handled early on, so we can't have
379              //that status here.  For ON_HOLD, we've decided we won't return
380              //that status unless it is the only non-final type, so we never
381              //return it here, even if it is present.
382              if (nonFinalStatuses.contains(EventSetStatus.UNDER_WAY))
383              {
384                answer = EventSetStatus.UNDER_WAY;
385              }
386              else if (nonFinalStatuses.contains(EventSetStatus.SCHEDULED_BUT_NOT_STARTED))
387              {
388                answer = EventSetStatus.SCHEDULED_BUT_NOT_STARTED;
389              }
390              else if (nonFinalStatuses.contains(EventSetStatus.NOT_YET_SCHEDULED))
391              {
392                answer = EventSetStatus.NOT_YET_SCHEDULED;
393              }
394              else
395              {
396                throw new RuntimeException(
397                  "Logic error: not able to handle this set of non-final statuses: "
398                  + nonFinalStatuses);
399              }
400            }
401          }
402        }
403        
404        return answer;
405      }
406      
407      /**
408       * Returns a text representation of this enumeration constant.
409       * @return a text representation of this enumeration constant.
410       */
411      public String toString()
412      {
413        return EnumerationUtility.getSharedInstance().enumToString(this);
414      }
415      
416      /**
417       * Returns the status represented by {@code text}.
418       * <p>
419       * For details about the transformation, see
420       * {@link EnumerationUtility#enumFromString(Class, String)}.</p>
421       * 
422       * @param text a text representation of a status.
423       * 
424       * @return the status represented by {@code text}.
425       */
426      public static EventSetStatus fromString(String text)
427      {
428        return EnumerationUtility.getSharedInstance()
429                                 .enumFromString(EventSetStatus.class, text);
430      }
431    }