001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2018 nobark (tools4j), Marco Terzer, Anton Anufriev
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package org.tools4j.nobark.loop;
025
026import java.util.Objects;
027
028/**
029 * Extension of {@link Step} with enhanced functionality facilitating the composition of step chains.
030 */
031@FunctionalInterface
032public interface ComposableStep extends Step {
033
034    /**
035     * Returns a composable step that performs first <i><tt>this</tt></i> and then the <i><tt>next</tt></i> step.
036     * <p>
037     * The procedure eliminates {@link #NO_OP no-OP} steps, i.e. it returns <i><tt>this</tt></i> if
038     * <i><tt>(next == NO_OP)</tt></i>.
039     *
040     * @param next the next step to be performed after <i><tt>this</tt></i>
041     * @return a composite step performing <i><tt>this</tt></i> and then <i><tt>next</tt></i>
042     */
043    default ComposableStep then(final Step next) {
044        Objects.requireNonNull(next);
045        return next == NO_OP ? this : () -> perform() | next.perform();
046    }
047
048    /**
049     * Returns a composable step that performs first <i><tt>this</tt></i> step and then the <i><tt>next</tt></i> step
050     * only if the first performed any work as indicated by result of the {@link #perform()} invocation.
051     * <p>
052     * The procedure eliminates {@link #NO_OP no-OP} steps, i.e. it returns <i><tt>this</tt></i> if
053     * <i><tt>(next == NO_OP)</tt></i>.
054     *
055     * @param next the next step to be performed after <i><tt>this</tt></i> only if it performed some work
056     * @return  a composite step performing <i><tt>this</tt></i>, and if some work was performed subsequently also
057     *          <i><tt>next</tt></i>
058     */
059    default ComposableStep thenIfPerformed(final Step next) {
060        Objects.requireNonNull(next);
061        return next == NO_OP ? this : () -> perform() && (true | next.perform());//NOTE: true is needed for correctness
062    }
063
064    /**
065     * Returns a composable step that performs first <i><tt>this</tt></i> step and then the <i><tt>next</tt></i> step
066     * only if the first performed no work as indicated by result of the {@link #perform()} invocation.
067     * <p>
068     * The procedure eliminates {@link #NO_OP no-OP} steps, i.e. it returns <i><tt>this</tt></i> if
069     * <i><tt>(next == NO_OP)</tt></i>.
070     *
071     * @param next the next step to be performed after <i><tt>this</tt></i> only if it performed no work
072     * @return  a composite step performing <i><tt>this</tt></i>, and if no work was performed subsequently
073     *          <i><tt>next</tt></i>
074     */
075    default ComposableStep thenIfNotPerformed(final Step next) {
076        Objects.requireNonNull(next);
077        return next == NO_OP ? this : () -> perform() || next.perform();
078    }
079
080    /**
081     * Returns a composable step given a "normal" step.
082     * <p>
083     * The procedure preserves {@link #NO_OP no-OP} steps as well as existing instances of {@code ComposableStep},
084     * which means that such values are returned unchanged.
085     *
086     * @param step the step to be wrapped as a {@code ComposableStep}
087     * @return  a {@code ComposableStep} doing exactly the same work as <i><tt>step</tt></i> but with a richer interface
088     *          for step coposition
089     */
090    static ComposableStep create(final Step step) {
091        Objects.requireNonNull(step);
092        return step == NO_OP ? NO_OP : (step instanceof ComposableStep ? (ComposableStep)step : step::perform);
093    }
094
095    /**
096     * Returns a step that runs all given component steps;  the {@link #perform()} method of the resulting composite
097     * step returns true if any of the component steps returns true.  The procedure uses {@link #then(Step)} for the
098     * composition and eliminates {@link #NO_OP no-OP} components.
099     *
100     * @param components the component steps forming the parts of the returned step
101     * @return a new step that performs all component steps
102     * @see #NO_OP
103     */
104    static ComposableStep composite(final Step... components) {
105        Objects.requireNonNull(components);
106        ComposableStep result = NO_OP;
107        for (final Step component : components) {
108            result = result.then(component);
109        }
110        return result;
111    }
112
113    /**
114     * Step performing a no-OP; the implementation returns false indicating that no work was performed.  All composition
115     * methods are optimised to eliminate no-OP steps.
116     */
117    ComposableStep NO_OP = new ComposableStep() {
118        @Override
119        public boolean perform() {
120            return false;
121        }
122
123        @Override
124        public ComposableStep then(final Step next) {
125            return ComposableStep.create(next);
126        }
127
128        @Override
129        public ComposableStep thenIfPerformed(final Step next) {
130            Objects.requireNonNull(next);
131            return NO_OP;
132        }
133
134        @Override
135        public ComposableStep thenIfNotPerformed(final Step next) {
136            return ComposableStep.create(next);
137        }
138    };
139
140}