001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy
006     * of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations
014     * under the License.
015     */
016    package org.springframework.rules.closure.support;
017    
018    import org.springframework.rules.closure.Closure;
019    import org.springframework.rules.constraint.Constraint;
020    import org.springframework.rules.closure.ElementGenerator;
021    
022    /**
023     * Base superclass for process templates.
024     *
025     * @author Keith Donald
026     */
027    public abstract class AbstractElementGenerator implements ElementGenerator {
028    
029            /** Wrapping instance for internal usage. */
030            private ElementGenerator wrappedGenerator;
031    
032            /** <code>true</code> if this ElementGenerator may run once. */
033            private boolean runOnce = false;
034    
035            /** Current status of this ElementGenerator. */
036            private volatile ProcessStatus status = ProcessStatus.CREATED;
037    
038            /**
039             * Default constructor.
040             */
041            protected AbstractElementGenerator() {
042            }
043    
044            /**
045             * Constructor.
046             *
047             * @param runOnce <code>true</code> if this ElementGenerator may only be
048             * run once (will throw an Exception if called more than once).
049             */
050            protected AbstractElementGenerator(boolean runOnce) {
051                    this.runOnce = runOnce;
052            }
053    
054            /**
055             * Create an wrapper instance for internal usage.
056             *
057             * @param wrappedTemplate
058             */
059            private AbstractElementGenerator(ElementGenerator wrappedTemplate) {
060                    this.wrappedGenerator = wrappedTemplate;
061            }
062    
063            /**
064             * @return the wrapped ElementGenerator or <code>null</code>.
065             */
066            protected ElementGenerator getWrappedTemplate() {
067                    return wrappedGenerator;
068            }
069    
070            /**
071             * {@inheritDoc}
072             */
073            public boolean allTrue(Constraint constraint) {
074                    WhileTrueController controller = new WhileTrueController(this, constraint);
075                    run(controller);
076                    return controller.allTrue();
077            }
078    
079            /**
080             * {@inheritDoc}
081             */
082            public boolean anyTrue(Constraint constraint) {
083                    return findFirst(constraint, null) != null;
084            }
085    
086            /**
087             * {@inheritDoc}
088             */
089            public ElementGenerator findAll(final Constraint constraint) {
090                    return new AbstractElementGenerator(this) {
091                            public void run(final Closure closure) {
092                                    getWrappedTemplate().run(new IfBlock(constraint, closure));
093                            }
094                    };
095            }
096    
097            /**
098             * {@inheritDoc}
099             */
100            public Object findFirst(Constraint constraint) {
101                    return findFirst(constraint, null);
102            }
103    
104            /**
105             * {@inheritDoc}
106             */
107            public Object findFirst(Constraint constraint, Object defaultIfNoneFound) {
108                    ObjectFinder finder = new ObjectFinder(this, constraint);
109                    run(finder);
110                    return (finder.foundObject() ? finder.getFoundObject() : defaultIfNoneFound);
111            }
112    
113            /**
114             * @return <code>true</code> if the generator is stopped.
115             */
116            public boolean isStopped() {
117                    return this.status == ProcessStatus.STOPPED;
118            }
119    
120            /**
121             * @return <code>true</code> if the generator has completed it's task.
122             */
123            public boolean isFinished() {
124                    return this.status == ProcessStatus.COMPLETED;
125            }
126    
127            /**
128             * @return <code>true</code> if the generator is still running.
129             */
130            public boolean isRunning() {
131                    return this.status == ProcessStatus.RUNNING;
132            }
133    
134            /**
135             * Stop the generator.
136             */
137            public void stop() throws IllegalStateException {
138                    if (this.wrappedGenerator != null) {
139                            wrappedGenerator.stop();
140                    }
141                    this.status = ProcessStatus.STOPPED;
142            }
143    
144            /**
145             * Run a block of code (Closure) until a specific test (Constraint) is
146             * passed.
147             */
148            public void runUntil(Closure templateCallback, final Constraint constraint) {
149                    run(new UntilTrueController(this, templateCallback, constraint));
150            }
151    
152            /**
153             * Reset the ElementGenerator if possible.
154             *
155             * @throws UnsupportedOperationException if this ElementGenerator was a
156             * runOnce instance.
157             */
158            protected void reset() {
159                    if (this.status == ProcessStatus.STOPPED || this.status == ProcessStatus.COMPLETED) {
160                            if (this.runOnce) {
161                                    throw new UnsupportedOperationException("This process template can only safely execute once; "
162                                                    + "instantiate a new instance per request");
163                            }
164    
165                            this.status = ProcessStatus.RESET;
166                    }
167            }
168    
169            /**
170             * Set status running.
171             */
172            protected void setRunning() {
173                    this.status = ProcessStatus.RUNNING;
174            }
175    
176            /**
177             * Set status completed.
178             */
179            protected void setCompleted() {
180                    this.status = ProcessStatus.COMPLETED;
181            }
182    
183            /**
184             * {@inheritDoc}
185             */
186            public abstract void run(Closure templateCallback);
187    
188            /**
189             * When the passed object returns false for the given constraint, the
190             * ElementGenerator will be stopped. Afterwards the {@link #allTrue()}
191             * method can be used to check if all objects passed the test.
192             */
193            private static class WhileTrueController extends Block {
194                    private static final long serialVersionUID = 1L;
195    
196                    /**
197                     * The ElementGenerator that spawns the elements and that will be
198                     * stopped when the constraint returns false.
199                     */
200                    private ElementGenerator template;
201    
202                    /** The constraint to test against. */
203                    private Constraint constraint;
204    
205                    /**
206                     * Stores the outcome: true if all handled objects passed the
207                     * constraint.
208                     */
209                    private boolean allTrue = true;
210    
211                    /**
212                     * Constructor.
213                     *
214                     * @param template the ElementGenerator that spawns the elements and
215                     * that will be stopped if an object fails the constraint.
216                     * @param constraint the constraint to test against.
217                     */
218                    public WhileTrueController(ElementGenerator template, Constraint constraint) {
219                            this.template = template;
220                            this.constraint = constraint;
221                    }
222    
223                    /**
224                     * {@inheritDoc}
225                     */
226                    protected void handle(Object o) {
227                            if (!this.constraint.test(o)) {
228                                    this.allTrue = false;
229                                    this.template.stop();
230                            }
231                    }
232    
233                    /**
234                     * @return <code>true</code> if all objects passed the constraint.
235                     */
236                    public boolean allTrue() {
237                            return allTrue;
238                    }
239            }
240    
241            /**
242             * Each passed object will be tested against a Constraint. If returning
243             * true, the ElementGenerator will be stopped. If false, the given Closure
244             * will be executed with the object and the ElementGenerator will continue.
245             */
246            private static class UntilTrueController extends Block {
247                    private static final long serialVersionUID = 1L;
248    
249                    /**
250                     * The ElementGenerator that spawns the element and that will be stopped
251                     * when the constraint returns true.
252                     */
253                    private ElementGenerator template;
254    
255                    /**
256                     * The closure that has to be executed on all objects until the
257                     * constraint returns true.
258                     */
259                    private Closure templateCallback;
260    
261                    /**
262                     * The constraint used to test the objects.
263                     */
264                    private Constraint constraint;
265    
266                    /**
267                     * Constructor.
268                     *
269                     * @param template ElementGenerator that spawns the elements and that
270                     * will be stopped if the constraint returns true.
271                     * @param templateCallback Closure-code executed on each passed object.
272                     * @param constraint constraint that will stop the ElementGenerator if
273                     * returning true.
274                     */
275                    public UntilTrueController(ElementGenerator template, Closure templateCallback, Constraint constraint) {
276                            this.template = template;
277                            this.templateCallback = templateCallback;
278                            this.constraint = constraint;
279                    }
280    
281                    /**
282                     * {@inheritDoc}
283                     */
284                    protected void handle(Object o) {
285                            if (this.constraint.test(o)) {
286                                    this.template.stop();
287                            }
288                            else {
289                                    this.templateCallback.call(o);
290                            }
291                    }
292            }
293    
294            /**
295             * Will check each passed object against a constraint and call stop if the
296             * constraint returns true. The Object which passed the test will be the
297             * saved for retrieval.
298             */
299            private static class ObjectFinder extends Block {
300                    private static final long serialVersionUID = 1L;
301    
302                    /**
303                     * The generator that spawns the elements and that will be stopped when
304                     * the specified element is found.
305                     */
306                    private ElementGenerator generator;
307    
308                    /** Constraint to test against. */
309                    private Constraint constraint;
310    
311                    /**
312                     * The object that passed the test in the constraint or null if not
313                     * found.
314                     */
315                    private Object foundObject;
316    
317                    /**
318                     * Constructor.
319                     *
320                     * @param generator the generator which spawns the elements and that
321                     * must be stopped when the element is found.
322                     * @param constraint the constraint to test against.
323                     */
324                    public ObjectFinder(ElementGenerator generator, Constraint constraint) {
325                            this.generator = generator;
326                            this.constraint = constraint;
327                    }
328    
329                    /**
330                     * {@inheritDoc}
331                     */
332                    protected void handle(Object o) {
333                            if (this.constraint.test(o)) {
334                                    foundObject = o;
335                                    generator.stop();
336                            }
337                    }
338    
339                    /**
340                     * @return <code>true</code> if the object was found.
341                     */
342                    public boolean foundObject() {
343                            return foundObject != null;
344                    }
345    
346                    /**
347                     * @return the object that complies with the constraint or
348                     * <code>null</code> if not found.
349                     */
350                    public Object getFoundObject() {
351                            return foundObject;
352                    }
353            }
354    
355    }