/* * Copyright 2013-2016 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.channel; import java.io.Serializable; import ccre.log.Logger; import ccre.verifier.FlowPhase; import ccre.verifier.SetupPhase; /** * A BooleanOutput is an interface for anything that can be turned on or off. It * can be set to true or false. * * @see BooleanInput * @author skeggsc */ public interface BooleanOutput extends Serializable { /** * A BooleanOutput that goes nowhere. All data sent here is ignored. */ BooleanOutput ignored = new BooleanOutput() { @Override public void set(boolean newValue) { // Do nothing. } // Important to the functioning of static BooleanOutput.combine @Override public BooleanOutput combine(BooleanOutput other) { if (other == null) { throw new NullPointerException(); } return other; }; }; /** * Sets the boolean value of this output. In other words, turns it on or * off. * * If any exception occurs during the propagation of the changes, it will be * passed on by <code>set</code>. * * @param value the new value to send to this output. * @see #safeSet(boolean) for a version that catches any errors that occur. */ @FlowPhase public void set(boolean value); /** * Sets the boolean value of this output. In other words, turns it on or * off. * * If any exception occurs during the propagation of the changes, * <code>safeSet</code> will catch and log it as a * {@link ccre.log.LogLevel#SEVERE} error. * * @param value the new value to send to this output. * @see #set(boolean) for a version that throws any errors that occur. */ @FlowPhase public default void safeSet(boolean value) { try { set(value); } catch (Throwable ex) { Logger.severe("Error during channel propagation", ex); } } /** * Provides a version of this BooleanOutput that is inverted: when it is set * to true, this output will be set to false, and vice versa. * * @return the inverted version of this BooleanOutput. */ @SetupPhase public default BooleanOutput invert() { BooleanOutput original = this; return new BooleanOutput() { @Override public void set(boolean value) { original.set(!value); } @Override public BooleanOutput invert() { return original; } }; } /** * Provides a BooleanOutput that controls both this BooleanOutput and * <code>other</code>. When the new BooleanOutput is set to true, both this * BooleanOutput and <code>other</code> will be set to true, and the same * for false. * * If any error occurs during propagation of changes to either * BooleanOutput, the other target will still be modified. If both throw * exceptions, then one of the exceptions will be added as a suppressed * exception to the other. * * @param other the BooleanOutput to combine this BooleanOutput with. * @return the combined BooleanOutput. */ @SetupPhase public default BooleanOutput combine(BooleanOutput other) { if (other == null) { throw new NullPointerException(); } BooleanOutput self = this; return value -> { try { self.set(value); } catch (Throwable thr) { try { other.set(value); } catch (Throwable thr2) { thr.addSuppressed(thr2); } throw thr; } other.set(value); }; } /** * Combines any number of BooleanOutputs into a single BooleanOutput, such * that when the returned BooleanOutput is set to a value, all of the * BooleanOutputs are set to that value. * * If any error occurs during propagation of values to any BooleanOutput, * the other outputs will still be modified. If multiple outputs throw * exceptions, then one of them will be chosen arbitrarily and all of the * others will be added as suppressed exceptions either to the thrown * exception or to other suppressed expressions. * * @param outputs the BooleanOutputs to include. * @return the combined version of the BooleanOutputs. */ @SetupPhase public static BooleanOutput combine(BooleanOutput... outputs) { // This works without including 'ignored' in the actual data structure // by having 'ignored' drop itself during combine. BooleanOutput out = ignored; for (BooleanOutput o : outputs) { out = out.combine(o); } return out; } /** * Provides a version of this BooleanOutput that only propagates to this * BooleanOutput when <code>update</code> is fired. * * When <code>update</code> is fired, and the provided BooleanOutput has had * a value set, the most recent value set on that BooleanOutput will be sent * to this BooleanOutput. * * The value of this BooleanOutput will not change when the provided * BooleanOutput changes. * * @param update when to pass values through. * @return the update-limited version of this BooleanOutput. */ @SetupPhase public default BooleanOutput limitUpdatesTo(EventInput update) { if (update == null) { throw new NullPointerException(); } BooleanOutput original = this; return new BooleanOutput() { private boolean lastValue, anyValue; { update.send(() -> { if (anyValue) { original.set(lastValue); } }); } @Override public void set(boolean value) { this.lastValue = value; this.anyValue = true; } }; } /** * Provides an EventOutput that, when fired, will set this BooleanOutput to * <code>value</code>. * * @param value the value to set this BooleanOutput to. * @return the EventOutput that modifies this BooleanOutput. */ @SetupPhase public default EventOutput eventSet(boolean value) { if (value) { return () -> set(true); } else { return () -> set(false); } } /** * Provides an EventOutput that, when fired, will set this BooleanOutput to * the current value of <code>value</code>. * * @param value the input to set this BooleanOutput to. * @return the EventOutput that modifies this BooleanOutput. */ @SetupPhase public default EventOutput eventSet(BooleanInput value) { if (value == null) { throw new NullPointerException(); } return () -> set(value.get()); } /** * Sets this BooleanOutput to <code>value</code> when <code>when</code> is * produced. * * @param value the value to set this BooleanOutput to. * @param when when to modify this BooleanOutput. */ @SetupPhase public default void setWhen(boolean value, EventInput when) { when.send(this.eventSet(value)); } /** * Sets this BooleanOutput to the value of <code>value</code> when * <code>when</code> fires. * * @param value the input to set this BooleanOutput to. * @param when when to modify this BooleanOutput. */ @SetupPhase public default void setWhen(BooleanInput value, EventInput when) { when.send(this.eventSet(value)); } /** * Sets this BooleanOutput to true when <code>when</code> fires. * * @param when when to modify this BooleanOutput. */ @SetupPhase public default void setTrueWhen(EventInput when) { setWhen(true, when); } /** * Sets this BooleanOutput to false when <code>when</code> fires. * * @param when when to modify this BooleanOutput. */ @SetupPhase public default void setFalseWhen(EventInput when) { setWhen(false, when); } /** * Returns a BooleanOutput, and when the value written to it changes, it * fires the associated event. This will only fire when the value changes, * and is false by default. * * Either parameter can be null, which is equivalent to passing * <code>EventOutput.ignored</code>. They cannot both be null - this will * throw a NullPointerException. * * @param toFalse if the output becomes false. * @param toTrue if the output becomes true. * @return the output that can trigger the events. */ @SetupPhase public static BooleanOutput polarize(final EventOutput toFalse, final EventOutput toTrue) { if (toFalse == null && toTrue == null) { throw new NullPointerException("Both toFalse and toTrue are null in onChange! You can only have at most one be null."); } return new BooleanOutput() { private boolean last; @Override public void set(boolean value) { if (value == last) { return; } if (value) { last = true; if (toTrue != null) { toTrue.event(); } } else { last = false; if (toFalse != null) { toFalse.event(); } } } }; } /** * Provides a version of this BooleanOutput that only propagates changes if * <code>allow</code> is currently true. * * When <code>allow</code> changes to false, the output is locked, and when * <code>allow</code> changes to true, the output is unlocked and this * BooleanOutput is set to its last received value. * * @param allow when to allow changing of the result. * @return the lockable version of this BooleanOutput. */ @SetupPhase public default BooleanOutput filter(BooleanInput allow) { BooleanOutput original = this; return new BooleanOutput() { private boolean lastValue, anyValue; { allow.onPress().send(() -> { if (anyValue) { original.set(lastValue); } }); } @Override public void set(boolean value) { lastValue = value; anyValue = true; if (allow.get()) { original.set(value); } } }; } /** * Provides a version of this BooleanOutput that only propagates changes if * <code>deny</code> is currently false. * * When <code>deny</code> changes to true, the output is locked, and when * <code>deny</code> changes to false, the output is unlocked and this * BooleanOutput is set to its last received value. * * @param deny when to deny changing of the result. * @return the lockable version of this BooleanOutput. */ @SetupPhase public default BooleanOutput filterNot(BooleanInput deny) { return this.filter(deny.not()); } /** * Returns a BooleanIO version of this value. If it is already a BooleanIO, * it will be returned directly. If it is not, a new BooleanCell will be * created around this BooleanOutput. * * @param default_value the initial value for the returned BooleanIO, which * will also be sent to this BooleanOutput immediately * @return a new IO */ @SetupPhase public default BooleanIO cell(boolean default_value) { BooleanIO bio = new BooleanCell(default_value); bio.send(this); return bio; } }