1 package org.apache.maven.werkz;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.HashSet;
70 import java.util.Iterator;
71 import java.util.List;
72 import java.util.Set;
73
74 /**
75 * Abstract Goal to attain.
76 *
77 * <p>
78 * A <code>Goal</code> embodies both an action and the precursors for that action. A <code>Goal</code>'s precursors
79 * will be satisfied before attempting to perform the target <code>Goal</code>'s action. There may be a case that
80 * once precursors have been satisfied there is no further action required to be perform for a particular
81 * <code>Goal</code>.
82 * </p>
83 *
84 * <p>
85 * A <code>Goal</code>'s postcursors are also tracked so that if a <code>Goal</code>'s state has been changed and
86 * the <code>Goal</code>s ahead of it in the hierarchy need to be notified, it can <code>percolate</code> forward
87 * and have it's postcursors satisfied.
88 * </p>
89 *
90 * @see WerkzProject
91 * @see Action
92 *
93 * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
94 */
95 public class Goal
96 {
97
98
99
100
101 /** Empty <code>Goal</code> array. */
102 public static final Goal[] EMPTY_ARRAY = new Goal[0];
103
104
105
106
107
108 /** Unique name. */
109 private String name;
110
111 /** Ordered list of precursor <code>Goal</code>s. */
112 private List precursors;
113
114 /** Ordered list of postcursor <code>Goal</code>s. */
115 private List postcursors;
116
117 /** Action to perform. */
118 private Action action;
119
120 /** Pre-goal callbacks. */
121 private List preGoalCallbacks;
122
123 /** Post-goal callbacks. */
124 private List postGoalCallbacks;
125
126 /** Pre-action callbacks. */
127 private List preActionCallbacks;
128
129 /** Post-action callbacks. */
130 private List postActionCallbacks;
131
132 /** Description of the goal (for auto-documenting). */
133 private String description;
134
135
136
137
138
139 /**
140 * Construct a new <code>Goal</code> with the specified name.
141 *
142 * @param name
143 * The name of the <code>Goal</code>.
144 */
145 public Goal( String name )
146 {
147 this.name = name;
148 this.precursors = Collections.EMPTY_LIST;
149 this.postcursors = Collections.EMPTY_LIST;
150
151 this.preGoalCallbacks = Collections.EMPTY_LIST;
152 this.postGoalCallbacks = Collections.EMPTY_LIST;
153 this.preActionCallbacks = Collections.EMPTY_LIST;
154 this.postActionCallbacks = Collections.EMPTY_LIST;
155 }
156
157 /**
158 * Construct a new <code>Goal</code> with the specified name and <code>Action</code>.
159 *
160 * @param name
161 * The name of the <code>Goal</code>.
162 * @param action
163 * The <code>Action</code> for this <code>Goal</code>.
164 */
165 public Goal( String name, Action action )
166 {
167 this( name );
168
169 setAction( action );
170 }
171
172
173
174
175
176 /**
177 * Retrieve the name of this <code>Goal</code>.
178 *
179 * @return This <code>Goal</code>'s name.
180 */
181 public String getName()
182 {
183 return this.name;
184 }
185
186 /**
187 * Retrieve the description of this <code>Goal</code>.
188 *
189 * @return The description of this goal.
190 */
191 public String getDescription()
192 {
193 return this.description;
194 }
195
196 /**
197 * Set the description for this <code>Goal</code>.
198 *
199 * @param description
200 * The description of this goal.
201 */
202 public void setDescription( String description )
203 {
204 this.description = description;
205 }
206
207 /**
208 * Retrieve the <code>Action</code> of this <code>Goal</code>.
209 *
210 * @return The <code>Action</code> of this <code>Goal</code>.
211 */
212 public Action getAction()
213 {
214 return this.action;
215 }
216
217 /**
218 * Set the <code>Action</code> of this <code>Goal</code>.
219 *
220 * @param action
221 * The <code>Action</code> of this <code>Goal</code>.
222 */
223 public void setAction( Action action )
224 {
225 this.action = action;
226 }
227
228
229
230
231
232 /**
233 * Add a <b>pre-goal</b> callback to this <code>Goal</code>.
234 *
235 * @param callback
236 * The callback to add.
237 */
238 public void addPreGoalCallback( PreGoalCallback callback )
239 {
240 if ( Collections.EMPTY_LIST.equals( this.preGoalCallbacks ) )
241 {
242 this.preGoalCallbacks = Collections.synchronizedList( new ArrayList( 3 ) );
243 }
244
245 this.preGoalCallbacks.add( callback );
246 }
247
248 /**
249 * Remove all occurences of a <b>pre-goal</b> callback from this <code>Goal</code>.
250 *
251 * @param callback
252 * The callback to remove.
253 */
254 public void removePreGoalCallback( PreGoalCallback callback )
255 {
256 while ( this.preGoalCallbacks.remove( callback ) )
257 {
258
259 }
260 }
261
262 /**
263 * Retrieve an unmodifiable list of the <b>pre-goal</b> callbacks.
264 *
265 * @return An unmodifiable <code>List</code> of <code>PreGoalCallback</code>s.
266 */
267 public List getPreGoalCallbacks()
268 {
269 return Collections.unmodifiableList( this.preGoalCallbacks );
270 }
271
272
273
274
275
276 /**
277 * Add a <b>post-goal</b> callback to this <code>Goal</code>.
278 *
279 * @param callback
280 * The callback to add.
281 */
282 public void addPostGoalCallback( PostGoalCallback callback )
283 {
284 if ( Collections.EMPTY_LIST.equals( this.postGoalCallbacks ) )
285 {
286 this.postGoalCallbacks = Collections.synchronizedList( new ArrayList( 3 ) );
287 }
288
289 this.postGoalCallbacks.add( callback );
290 }
291
292 /**
293 * Remove all occurences of a <b>post-goal</b> callback from this <code>Goal</code>.
294 *
295 * @param callback
296 * The callback to remove.
297 */
298 public void removePostGoalCallback( PostGoalCallback callback )
299 {
300 while ( this.postGoalCallbacks.remove( callback ) )
301 {
302
303 }
304 }
305
306 /**
307 * Retrieve an unmodifiable list of the <b>post-goal</b> callbacks.
308 *
309 * @return An unmodifiable <code>List</code> of <code>PostGoalCallback</code>s.
310 */
311 public List getPostGoalCallbacks()
312 {
313 return Collections.unmodifiableList( this.postGoalCallbacks );
314 }
315
316
317
318
319
320 /**
321 * Add a <b>pre-action</b> callback to this <code>Goal</code>.
322 *
323 * @param callback
324 * The callback to add.
325 */
326 public void addPreActionCallback( PreActionCallback callback )
327 {
328 if ( this.preActionCallbacks.equals( Collections.EMPTY_LIST ) )
329 {
330 this.preActionCallbacks = Collections.synchronizedList( new ArrayList( 3 ) );
331 }
332
333 this.preActionCallbacks.add( callback );
334 }
335
336 /**
337 * Remove all occurences of a <b>pre-action</b> callback from this <code>Goal</code>.
338 *
339 * @param callback
340 * The callback to remove.
341 */
342 public void removePreActionCallback( PreActionCallback callback )
343 {
344 while ( this.preActionCallbacks.remove( callback ) )
345 {
346
347 }
348 }
349
350 /**
351 * Retrieve an unmodifiable list of the <b>pre-action</b> callbacks.
352 *
353 * @return An unmodifiable <code>List</code> of <code>PreActionCallback</code>s.
354 */
355 public List getPreActionCallbacks()
356 {
357 return Collections.unmodifiableList( this.preActionCallbacks );
358 }
359
360
361
362
363
364 /**
365 * Add a <b>post-action</b> callback to this <code>Goal</code>.
366 *
367 * @param callback
368 * The callback to add.
369 */
370 public void addPostActionCallback( PostActionCallback callback )
371 {
372 if ( this.postActionCallbacks.equals( Collections.EMPTY_LIST ) )
373 {
374 this.postActionCallbacks = Collections.synchronizedList( new ArrayList( 3 ) );
375 }
376
377 this.postActionCallbacks.add( callback );
378 }
379
380 /**
381 * Remove all occurences of a <b>post-action</b> callback from this <code>Goal</code>.
382 *
383 * @param callback
384 * The callback to remove.
385 */
386 public void removePostActionCallback( PostActionCallback callback )
387 {
388 while ( this.postActionCallbacks.remove( callback ) )
389 {
390
391 }
392 }
393
394 /**
395 * Retrieve an unmodifiable list of the <b>post-action</b> callbacks.
396 *
397 * @return An unmodifiable <code>List</code> of <code>PostActionCallback</code>s.
398 */
399 public List getPostActionCallbacks()
400 {
401 return Collections.unmodifiableList( this.postActionCallbacks );
402 }
403
404
405
406
407 /**
408 * Determine if this <code>Goal</code> has been satisfied for the specified <code>Session</code>.
409 *
410 * @param session
411 * The <code>Session</code> context in which to test for goal satisfaction.
412 *
413 * @return <code>true</code> if this <code>Goal</code> has been satisfied within the <code>Session</code>,
414 * otherwise <code>false</code>.
415 */
416 public boolean isSatisfied( Session session )
417 {
418 return session.isGoalSatisfied( this );
419 }
420
421 /**
422 * Add a precursor <code>Goal</code> to this <code>Goal</code>.
423 *
424 * @param precursor
425 * The precursor <code>Goal</code> that must be satisfied before performing this <code>Goal</code>.
426 *
427 * @throws CyclicGoalChainException
428 * if adding the precursor would result in a cyclic dependency.
429 */
430 public void addPrecursor( Goal precursor ) throws CyclicGoalChainException
431 {
432 if ( Collections.EMPTY_LIST.equals( this.precursors ) )
433 {
434 this.precursors = Collections.synchronizedList( new ArrayList() );
435 }
436
437 this.precursors.add( precursor );
438
439 try
440 {
441 checkForCycles();
442
443 precursor.addInternalPostcursor( this );
444 }
445 catch ( CyclicGoalChainException e )
446 {
447 e.fillInStackTrace();
448 this.precursors.remove( precursor );
449 throw e;
450 }
451 }
452
453 /**
454 * Add a postcursor <code>Goal</code> to this <code>Goal</code>.
455 *
456 * @param postcursor
457 * The postcursor <code>Goal</code> that depends on this <code>Goal</code>.
458 *
459 * @throws CyclicGoalChainException
460 * if adding the postcursor would result in a cyclic dependency.
461 */
462 public void addPostcursor( Goal postcursor ) throws CyclicGoalChainException
463 {
464 postcursor.addPrecursor( this );
465 }
466
467 /**
468 * Adds a postcursor <code>Goal</code> without looping back to the <code>postcursor.addPrecursor</code> and
469 * entering an infinite loop.
470 *
471 * @param postcursor
472 * The postcursor <code>Goal</code> that is already tracking this
473 * <code>Goal</goal> as a precursor and is letting this
474 * <code>Goal</code> know that it is a postcursor.
475 */
476 private void addInternalPostcursor( Goal postcursor )
477 {
478 if ( Collections.EMPTY_LIST.equals( this.postcursors ) )
479 {
480 this.postcursors = Collections.synchronizedList( new ArrayList() );
481 }
482
483 this.postcursors.add( postcursor );
484 }
485
486 /**
487 * Retrieve an unmodifiable <code>List</code> of this <code>Goal</code>'s precursor <code>Goal</code>s.
488 *
489 * @return The <code>List<code> of precursor <code>Goal</code>s.
490 */
491 public List getPrecursors()
492 {
493 return Collections.unmodifiableList( this.precursors );
494 }
495
496 /**
497 * Retrive an unmodifiable <code>List</code> of this <code>Goal</code>'s postcursor <code>Goal</code>s.
498 *
499 * @return The <code>List</code> of postcursor <code>Goal</code>s.
500 */
501 public List getPostcursors()
502 {
503 return Collections.unmodifiableList( this.postcursors );
504 }
505
506 /**
507 * Attempt to attain this <code>Goal</code>'s precursor <code>Goal</code>s.
508 *
509 * @param session
510 * The context in which to attain goals.
511 *
512 * @throws UnattainableGoalException
513 * if unable to satisfy a precursor.
514 * @throws NoActionDefinitionException
515 * if this goal contains no action definition.
516 */
517 void attainPrecursors( Session session ) throws UnattainableGoalException, NoActionDefinitionException
518 {
519 Iterator precursorIter = getPrecursors().iterator();
520 Goal eachPrereq = null;
521
522 while ( precursorIter.hasNext() )
523 {
524 eachPrereq = (Goal) precursorIter.next();
525
526 eachPrereq.attain( session );
527 }
528 }
529
530 /**
531 * Attempt to attain this <code>Goal</code>'s postcursor <code>Goal</code>s.
532 *
533 * @param session
534 * The context in which to attain goals.
535 *
536 * @throws UnattainableGoalException
537 * if unable to satisfy a postcursor.
538 * @throws NoActionDefinitionException
539 * if this goal contains no action definition.
540 */
541 void percolatePostcursors( Session session ) throws UnattainableGoalException, NoActionDefinitionException
542 {
543 Iterator postreqIter = getPostcursors().iterator();
544 Goal eachPostreq = null;
545
546 while ( postreqIter.hasNext() )
547 {
548 eachPostreq = (Goal) postreqIter.next();
549
550 eachPostreq.percolate( session );
551 }
552 }
553
554 /**
555 * Perform a cyclic dependency check.
556 *
557 * @throws CyclicGoalChainException
558 * if a dependency cycle is discovered.
559 */
560 void checkForCycles() throws CyclicGoalChainException
561 {
562 Set visited = new HashSet();
563
564 checkForCycles( this, visited );
565 }
566
567 /**
568 * Perform a cyclic dependency check.
569 *
570 * @param initialGoal
571 * The root <code>Goal</code> initiating the cycle check.
572 * @param visited
573 * The <code>Set</code> of all <code>Goal</code>s visited between the root <code>initialGoal</code>
574 * and this <code>Goal</code>.
575 *
576 * @throws CyclicGoalChainException
577 * if a cyclic dependency is detected.
578 */
579 void checkForCycles( Goal initialGoal, Set visited ) throws CyclicGoalChainException
580 {
581 if ( visited.contains( this ) )
582 {
583 throw new CyclicGoalChainException( initialGoal );
584 }
585
586 visited.add( this );
587
588 Iterator precursorIter = getPrecursors().iterator();
589 Goal eachPrereq = null;
590 while ( precursorIter.hasNext() )
591 {
592 eachPrereq = (Goal) precursorIter.next();
593
594 eachPrereq.checkForCycles( initialGoal, visited );
595 visited.remove( eachPrereq );
596 }
597 }
598
599 /**
600 * Attempt to attain this <code>Goal</code>.
601 *
602 * @param session
603 * The context in which to attain goals.
604 *
605 * @throws UnattainableGoalException
606 * if unable to attain this <code>Goal</code> or one of its precursor <code>Goal</code>s.
607 * @throws NoActionDefinitionException
608 * if this goal contains no action definition.
609 */
610 public final void attain( Session session ) throws UnattainableGoalException, NoActionDefinitionException
611 {
612 if ( session.isGoalSatisfied( this ) )
613 {
614
615
616 return;
617 }
618
619
620
621 attainPrecursors( session );
622
623 fire( session );
624 }
625
626 /**
627 * Attempt to percolate this <code>Goal</code> through to its Postcursors.
628 *
629 * @param session
630 * The context in which to percolate goals.
631 *
632 * @throws UnattainableGoalException
633 * if unable to attain this <code>Goal</code> or one of its precursor <code>Goal</code>s.
634 * @throws NoActionDefinitionException
635 * if this goal contains no action definition.
636 */
637 public final void percolate( Session session ) throws UnattainableGoalException, NoActionDefinitionException
638 {
639 if ( session.isGoalSatisfied( this ) )
640 {
641 return;
642 }
643
644 fire( session );
645
646 percolatePostcursors( session );
647 }
648
649 /**
650 * Fires pre-goal callbacks, the <code>Goal</code>'s action, if need be, and the post-goal callbacks.
651 *
652 * @param session
653 * The context in which to fire the goal.
654 *
655 * @throws UnattainableGoalException
656 * if unable to attain this <code>Goal</code> or one of its precursor <code>Goal</code>s.
657 * @throws NoActionDefinitionException
658 * if this goal contains no action definition.
659 */
660 private final void fire( Session session ) throws UnattainableGoalException, NoActionDefinitionException
661 {
662 session.info( getName() + ":" );
663
664
665
666 try
667 {
668
669 firePreGoalCallbacks();
670 }
671 catch ( Exception e )
672 {
673 throw new UnattainableGoalException( getName(), e );
674 }
675
676 Action action = getAction();
677
678 if ( action == null )
679 {
680 throw new NoActionDefinitionException( this );
681 }
682
683 if ( action.requiresAction() )
684 {
685 try
686 {
687
688
689 firePreActionCallbacks();
690
691 getAction().performAction( session );
692
693 firePostActionCallbacks();
694
695 }
696 catch ( Exception e )
697 {
698 throw new UnattainableGoalException( getName(), e );
699 }
700 }
701
702 try
703 {
704
705 firePostGoalCallbacks();
706 }
707 catch ( Exception e )
708 {
709 throw new UnattainableGoalException( getName(), e );
710 }
711
712
713
714
715
716 session.addSatisfiedGoal( this );
717
718 session.info( "" );
719 }
720
721 /**
722 * Fire the pre-goal callbacks.
723 *
724 * @throws Exception
725 * if an error occurs while firing a callback.
726 */
727 void firePreGoalCallbacks() throws Exception
728 {
729 Iterator callbackIter = this.preGoalCallbacks.iterator();
730 PreGoalCallback eachCallback = null;
731
732 if ( !callbackIter.hasNext() )
733 {
734 return;
735 }
736
737
738
739 while ( callbackIter.hasNext() )
740 {
741 eachCallback = (PreGoalCallback) callbackIter.next();
742 eachCallback.firePreGoal( this );
743 }
744
745
746 }
747
748 /**
749 * Fire the post-goal callbacks.
750 *
751 * @throws Exception
752 * if an error occurs while firing a callback.
753 */
754 void firePostGoalCallbacks() throws Exception
755 {
756 Iterator callbackIter = this.postGoalCallbacks.iterator();
757 PostGoalCallback eachCallback = null;
758
759 while ( callbackIter.hasNext() )
760 {
761 eachCallback = (PostGoalCallback) callbackIter.next();
762
763 eachCallback.firePostGoal( this );
764 }
765 }
766
767 /**
768 * Fire the pre-action callbacks.
769 *
770 * @throws Exception
771 * if an error occurs while firing a callback.
772 */
773 void firePreActionCallbacks() throws Exception
774 {
775 Iterator callbackIter = this.preActionCallbacks.iterator();
776 PreActionCallback eachCallback = null;
777
778 while ( callbackIter.hasNext() )
779 {
780 eachCallback = (PreActionCallback) callbackIter.next();
781
782 eachCallback.firePreAction( this );
783 }
784 }
785
786 /**
787 * Fire the post-action callbacks.
788 *
789 * @throws Exception
790 * if an error occurs while firing a callback.
791 */
792 void firePostActionCallbacks() throws Exception
793 {
794 Iterator callbackIter = this.postActionCallbacks.iterator();
795 PostActionCallback eachCallback = null;
796
797 while ( callbackIter.hasNext() )
798 {
799 eachCallback = (PostActionCallback) callbackIter.next();
800
801 eachCallback.firePostAction( this );
802 }
803 }
804
805
806
807
808
809 /**
810 * Retrieve the hash-code of this object.
811 *
812 * <p>
813 * The hash-code is derrived <b>only</b> from the name of the <code>Goal</code> as returned by {@link #getName}.
814 * </p>
815 *
816 * @return The hash-code of this object.
817 */
818 public int hashCode()
819 {
820 return getName().hashCode();
821 }
822
823 /**
824 * Determine if two <code>Goal</code> objects are equivelant.
825 *
826 * <p>
827 * Equivelancy is determined <b>only</b> from the names of the <code>Goal</code>s as return by {@link #getName}.
828 * </p>
829 *
830 * @param thatObj
831 * The object to compare to this object.
832 *
833 * @return The hash-code of this object.
834 */
835 public boolean equals( Object thatObj )
836 {
837 Goal that = (Goal) thatObj;
838
839 return this.getName().equals( that.getName() );
840 }
841
842 /**
843 * Produce a textual representation suitable for debugging.
844 *
845 * @return A textual representation suitable for debugging.
846 */
847 public String toString()
848 {
849 return "[Goal: name=\"" + getName() + "\"]" + "; precursor=" + getPrecursors() + "]";
850 }
851
852 }