001    package edu.nrao.sss.electronics;
002    
003    /**
004     * A connector for moving a signal from one device to another.
005     * <p>
006     * A pipe has two ends, one for input and one for output.
007     * The input of a pipe may be either unconnected or connected to a
008     * {@link SignalSource signal source}.
009     * The output may likewise be either unconnected or connected to a
010     * {@link SignalProcessor signal processor}.</p>
011     * <p>
012     * Executing a pipe is the same as executing its output processor,
013     * provided it has one.  Executing a pipe with a disconnected
014     * output does nothing.  Executing a pipe with a disconnected input
015     * does whatever the processor connected to its output does when it
016     * has no signal.</p>
017     * <p>
018     * The connections of a pipe are normally forged in such a way
019     * that they may be unconnected and then reconnected to the same
020     * devices, or to different devices.  Via the static factory methods,
021     * however, a pipe may be constructed with one of its ends <i>welded</i>
022     * to a device.  A welded connection is permanent and may not be
023     * disconnected or connected to another device.  Devices tend to
024     * create their pipes so that they are welded to themselves.  Clients
025     * of these devices then connect their outputs to the inputs of
026     * other devices.</p>
027     * <p>
028     * <b>Version Info:</b>
029     * <table style="margin-left:2em">
030     *   <tr><td>$Revision: 1269 $</td></tr>
031     *   <tr><td>$Date: 2008-05-05 14:39:39 -0600 (Mon, 05 May 2008) $</td></tr>
032     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
033     * </table></p>
034     * 
035     * @author David M. Harland
036     * @since 2007-10-25
037     */
038    public class SignalPipe
039      implements SignalSource, SignalProcessor
040    {
041      private static final String DEFAULT_NAME = "[unnamed pipe]";
042      
043      private String name;
044    
045      SignalSource    input;
046      SignalProcessor output;
047      
048      private boolean inputIsWelded;
049      private boolean outputIsWelded;
050    
051      //============================================================================
052      // OBJECT CREATION
053      //============================================================================
054    
055      /**
056       * Creates a new unnamed pipe that is not connected on either of its ends.
057       */
058      public SignalPipe()
059      {
060        this(null);
061      }
062    
063      /**
064       * Creates a new named pipe that is not connected on either of its ends.
065       * 
066       * @param pipeName
067       *   an optional name for this pipe.  If this value is <i>null</i> a
068       *   default name will be used in its place.
069       */
070      public SignalPipe(String pipeName)
071      {
072        name = (pipeName == null) ? DEFAULT_NAME : pipeName;
073        
074        input  = null;
075        output = null;
076        
077        inputIsWelded  = false;
078        outputIsWelded = false;
079      }
080    
081      /**
082       * Creates a new pipe, welds its input to {@code source}, and returns it.
083       * If {@code source} is a pipe whose output is welded to another processor,
084       * the returned pipe will be <i>null</i>.
085       * 
086       * @param source
087       *   a source to be welded to the input of the returned pipe.
088       *   
089       * @return a new pipe whose input is welded to {@code source},
090       *         or <i>null</i> if the weld could not be made.
091       */
092      public static SignalPipe makeAndWeldInputTo(SignalSource source)
093      {
094        SignalPipe newPipe = new SignalPipe();
095        
096        boolean success = newPipe.connectInputTo(source);
097        
098        if (success)
099        {
100          newPipe.inputIsWelded = true;
101          
102          if (source instanceof SignalPipe)
103            ((SignalPipe)source).outputIsWelded = true;
104        }
105        else
106        {
107          newPipe = null;
108        }
109        
110        return newPipe;
111      }
112    
113      /**
114       * Creates a new pipe, welds its output to {@code processor}, and returns it.
115       * If {@code processor} is a pipe whose input is welded to another source,
116       * the returned pipe will be <i>null</i>.
117       * 
118       * @param processor
119       *   a processor to be welded to the output of the returned pipe.
120       *   
121       * @return a new pipe whose output is welded to {@code processor},
122       *         or <i>null</i> if the weld could not be made.
123       */
124      public static SignalPipe makeAndWeldOutputTo(SignalProcessor processor)
125      {
126        SignalPipe newPipe = new SignalPipe();
127        
128        boolean success = newPipe.connectOutputTo(processor);
129        
130        if (success)
131        {
132          newPipe.outputIsWelded = true;
133          
134          if (processor instanceof SignalPipe)
135            ((SignalPipe)processor).inputIsWelded = true;
136        }
137        else
138        {
139          newPipe = null;
140        }
141        
142        return newPipe;
143      }
144    
145      //============================================================================
146      // NAMING
147      //============================================================================
148    
149      /**
150       * Sets a new name for this pipe.
151       * Clients are not required to name their pipes.
152       * <p>
153       * If {@code newName} is <i>null</i> or the empty string
154       * (<tt>""</tt>), the request to change the name will be
155       * denied and the current name will remain in place.</p>
156       * 
157       * @param newName
158       *   the new name of this pipe.
159       */
160      public void setName(String newName)
161      {
162        if (newName != name && newName.length() > 0)
163          name = newName;
164      }
165      
166      /**
167       * Returns the name of this pipe.
168       * The returned value will be either a client-specified name or a default
169       * name.  It will never be <i>null</i>.
170       * 
171       * @return the name of this pipe.
172       */
173      public String getName()
174      {
175        return name;
176      }
177      
178      //============================================================================
179      // CONNECTING AND DISCONNECTING
180      //============================================================================
181    
182      /**
183       * Returns <i>true</i> if the input of this pipe may be connected
184       * to {@code source}.
185       * <p>
186       * This method will return <i>true</i> if, and only if,</p>
187       * <ol>
188       *   <li>the input of this pipe is not welded to another source, and</li>
189       *   <li>{@code source} is not a pipe whose output is welded to another
190       *       processor.</li>
191       * </ol>
192       * 
193       * @param source
194       *   the device to be tested for connectability to input of this pipe.
195       *   
196       * @return <i>true</i> if the input of this pipe may be connected
197       *         to {@code source}.
198       */
199      public boolean canConnectInputTo(SignalSource source)
200      {
201        //This pipe can connect so long as its input has not been welded
202        boolean canConnect = !inputIsWelded;
203        
204        //If the source is a pipe, and if that pipe's output
205        //has been welded, then we cannot connect.
206        if (canConnect && (source instanceof SignalPipe))
207          canConnect = !((SignalPipe)source).outputIsWelded;
208          
209        return canConnect;
210      }
211    
212      /**
213       * Attempts to connect the input of this pipe to {@code source} and returns
214       * <i>true</i> if successful.  If the attempt was unsuccessful, this pipe
215       * will be in the same state it was in just prior to the call to this method.
216       * 
217       * @param source
218       *   the source to which the input of this pipe should be connected.
219       *   
220       * @return <i>true</i> if the attempt to connect was successful.
221       */
222      public boolean connectInputTo(SignalSource source)
223      {
224        boolean success = canConnectInputTo(source);
225        
226        if (success)
227        {
228          //This sets our input to null, and if our old source was a pipe,
229          //it also sets its output to null.
230          success = disconnectInput();
231    
232          if (success)
233          {
234            this.input = source;
235    
236            //The call to canConnectInputTo told us that the source pipe,
237            //if the source is a pipe, has an unwelded output.
238            if (source instanceof SignalPipe)
239              ((SignalPipe)source).output = this;
240          }
241        }
242        
243        return success;
244      }
245      
246      /**
247       * Attempts to disconnect the input of this pipe and returns
248       * <i>true</i> if successful.  The attempt will be unsuccessful
249       * if the input of this pipe is welded to its device.
250       * If the attempt was unsuccessful, this pipe will be in the
251       * same state it was in just prior to the call to this method.
252       * 
253       * @return <i>true</i> if the attempt to disconnect was successful.
254       */
255      public boolean disconnectInput()
256      {
257        //This pipe is OK so long as its input has not been welded
258        boolean canDisconnect = !inputIsWelded;
259        
260        if (canDisconnect)
261        {
262          //If our input is not welded, and if our source is a pipe,
263          //tell that pipe that its output is being disconnected.
264          if (input instanceof SignalPipe)
265          {
266            SignalPipe inputPipe = (SignalPipe)input;
267            
268            assert inputPipe.output == this : "Output of my input was not me.";
269            
270            assert !inputPipe.outputIsWelded : "Inconsistent welds.";
271            
272            inputPipe.output = null;
273          }
274          
275          //This was really all we were trying to do:
276          this.input = null;
277        }
278        
279        return canDisconnect;
280      }
281      
282      /**
283       * Returns <i>true</i> if the output of this pipe may be connected
284       * to {@code processor}.
285       * <p>
286       * This method will return <i>true</i> if, and only if,</p>
287       * <ol>
288       *   <li>the output of this pipe is not welded to another processor, and</li>
289       *   <li>{@code processor} is not a pipe whose input is welded to another
290       *       source.</li>
291       * </ol>
292       * 
293       * @param processor
294       *   the device to be tested for connectability to the output of this pipe.
295       *   
296       * @return <i>true</i> if the input of this pipe may be connected
297       *         to {@code source}.
298       */
299      public boolean canConnectOutputTo(SignalProcessor processor)
300      {
301        //This pipe can connect so long as its output has not been welded
302        boolean canConnect = !outputIsWelded;
303        
304        //If the processor is a pipe, and if that pipe's input
305        //has been welded, then we cannot connect.
306        if (canConnect && (processor instanceof SignalPipe))
307          canConnect = !((SignalPipe)processor).inputIsWelded;
308          
309        return canConnect;
310      }
311    
312      /**
313       * Attempts to connect the output of this pipe to {@code processor}
314       * and returns <i>true</i> if successful.
315       * If the attempt was unsuccessful, this pipe will be in the same
316       * state it was in just prior to the call to this method.
317       * 
318       * @param processor
319       *   the processor to which the output of this pipe should be connected.
320       *   
321       * @return <i>true</i> if the attempt to connect was successful.
322       */
323      public boolean connectOutputTo(SignalProcessor processor)
324      {
325        boolean success = canConnectOutputTo(processor);
326        
327        if (success)
328        {
329          //This sets our output to null, and if our old processor was a pipe,
330          //it also sets its input to null.
331          success = disconnectOutput();
332    
333          if (success)
334          {
335            this.output = processor;
336    
337            //The call to canConnectInputTo told us that the source pipe,
338            //if the source is a pipe, has an unwelded output.
339            if (processor instanceof SignalPipe)
340              ((SignalPipe)processor).input = this;
341          }
342        }
343        
344        return success;
345      }
346      
347      /**
348       * Attempts to disconnect the output of this pipe and returns
349       * <i>true</i> if successful.  The attempt will be unsuccessful
350       * if the output of this pipe is welded to its device.
351       * If the attempt was unsuccessful, this pipe will be in the
352       * same state it was in just prior to the call to this method.
353       * 
354       * @return <i>true</i> if the attempt to disconnect was successful.
355       */
356      public boolean disconnectOutput()
357      {
358        //This pipe is OK so long as its output has not been welded
359        boolean canDisconnect = !outputIsWelded;
360        
361        if (canDisconnect)
362        {
363          //If our output is not welded, and if our processor is a pipe,
364          //tell that pipe that its input is being disconnected.
365          if (output instanceof SignalPipe)
366          {
367            SignalPipe outputPipe = (SignalPipe)output;
368            
369            assert outputPipe.input == this : "Input of my output was not me.";
370            
371            assert !outputPipe.inputIsWelded : "Inconsistent welds.";
372            
373            outputPipe.input = null;
374          }
375          
376          //This was really all we were trying to do:
377          this.output = null;
378        }
379        
380        return canDisconnect;
381      }
382      
383      //============================================================================
384      // INTERFACE SignalSource
385      //============================================================================
386    
387      /**
388       * Returns the signal of the source connected to the input of this pipe.
389       * If nothing is connected to this pipe, or if the connected
390       * source has a <i>null</i> signal, <i>null</i> will be returned.
391       */
392      public Signal getSignal()
393      {
394        if (input instanceof SignalSwitch)
395        {
396          SignalSwitch ss = (SignalSwitch)input;
397          return ss.getSignal(this);
398        }
399        else
400        {
401          return input == null ? null : input.getSignal(); //TODO should this clone the signal?
402        }
403      }
404      
405      //============================================================================
406      // INTERFACE SignalProcessor
407      //============================================================================
408    
409      /**
410       * Executes the processor connected to the output of this pipe, if any.
411       */
412      public void execute()
413      {
414        if (outputIsConnected())
415          output.execute();
416      }
417      
418      public void executeUpTo(SignalProcessor firstUnexecutedDevice)
419      {
420        if (outputIsConnected() && firstUnexecutedDevice != this)
421          output.executeUpTo(firstUnexecutedDevice);
422      }
423    
424      public void executeFromStartOfChainUpTo(SignalProcessor firstUnexecutedDevice)
425      {
426        if (inputIsConnected() && input instanceof SignalProcessor)
427        {
428          ((SignalProcessor)input).executeFromStartOfChainUpTo(firstUnexecutedDevice);
429        }
430        else
431        {
432          executeUpTo(firstUnexecutedDevice);
433        }
434      }
435      
436      private boolean outputIsConnected()
437      {
438        //If output is not null, momentarily consider it to be connected
439        boolean connected = (output != null);
440    
441        //If output is to a sig switch, make sure we are its selected input
442        if (connected && output instanceof SignalSwitch)
443          connected = (((SignalSwitch)output).getSelectedInput() == this);
444        
445        return connected;
446      }
447      
448      private boolean inputIsConnected()
449      {
450        //If input is not null, momentarily consider it to be connected
451        boolean connected = (input != null);
452    
453        //If input is from a sig switch, make sure we are its selected output
454        if (connected && input instanceof SignalSwitch)
455          connected = (((SignalSwitch)input).getSelectedOutput() == this);
456        
457        return connected;
458      }
459    }