001    /*
002     * Channel.java
003     *
004     * Developed for the "Rethinking CS101" project. See http://www.cs101.org, the
005     * CS101 homepage or email las@olin.edu.
006     *
007     * Please do not redistribute without obtaining permission.
008     */
009    
010    package nodenet;
011    
012    import java.io.IOException;
013    import java.io.Serializable;
014    import java.io.ObjectInputStream;
015    
016    import java.awt.Color;
017    import java.awt.Point;
018    import java.awt.Polygon;
019    import java.awt.Graphics;
020    import java.awt.FlowLayout;
021    
022    import java.awt.event.ActionEvent;
023    import java.awt.event.WindowEvent;
024    import java.awt.event.WindowAdapter;
025    import java.awt.event.ActionListener;
026    
027    import javax.swing.JFrame;
028    import javax.swing.JPanel;
029    import javax.swing.JLabel;
030    import javax.swing.JButton;
031    import javax.swing.JTextField;
032    
033    import cs101.util.queue.Queue;
034    import cs101.util.queue.DefaultQueue;
035    
036    /**
037     * A Channel object is a NodeNetElement that connects two Nodes.  A
038     * channel implements both the <code>InputChannel</code> and
039     * <code>OutputChannel</code> interfaces.  Channels have an assocated
040     * capacity and latency.  The capacity of a channel indicates how many
041     * packets may be present before a <code>ChannelFullException</code>
042     * is thrown.  The latency of a channel is a measure of how long (in
043     * ms) a packet takes to travel from the input to the output.<p>
044     *
045     *
046     * @author Todd C. Parnell, tparnell@ai.mit.edu
047     * @author Patrick G. Heck, gus.heck@olin.edu
048     * @version $Id: Channel.java,v 1.9 2004/01/14 21:43:17 gus Exp $
049     */
050    public class Channel implements NodeNetElement, Resettable,
051    InputChannel, OutputChannel, Serializable {
052        
053        /** the maximum allowable latency */
054        public final static int MAX_LATENCY = 5000;
055        /** the maximum allowable capacity */
056        public final static int MAX_CAPACITY = 50;
057        
058        private static ChannelConfigFrame cf;
059        private static int ID;
060        
061        // JDK 1.2.2 appears to have a bug, wherein the .jar contains a
062        // java.awt.Queue (although it doesn't exist in the documentation).
063        // Explicitly labelling it cs101.util.Queue will pacify for now.
064        transient private final int myID = Channel.ID++;
065        transient private cs101.util.queue.Queue channel_objects = new DefaultQueue(),
066        timeStamps = new DefaultQueue();
067        transient private float mvt_constant = 0;
068        private int capacity = 10;
069        private int latency = 0;
070        private String name;
071        private Node startNode, stopNode;
072        transient boolean selected, running;
073        private boolean enabled = true, destroyed;
074        
075        // lock toprevent concurrent reads and writes.
076        transient private Object lock = new Object();
077        
078        // for painting performance
079        private Point p1, p2;
080        private Polygon poly1, poly2;
081        private int x_diff, y_diff;
082        
083        //
084        // Constructors
085        //
086        
087        /**
088         * Creates a new Channel with the given start and end nodes.  If
089         * start and end are the same (as determined by
090         * <code>equals</code>), throws SameNodeException.
091         */
092        public Channel(Node start, Node end) throws SameNodeException {
093            if (start.equals(end)) throw(new SameNodeException());
094            
095            this.name = "**UNNAMED**";
096            this.startNode = start;
097            this.stopNode = end;
098            this.channel_objects = new DefaultQueue();
099            this.timeStamps = new DefaultQueue();
100            this.startNode.addOutputChannel((OutputChannel)this);
101            this.stopNode.addInputChannel((InputChannel)this);
102            
103            this.p1 = startNode.getPos();
104            this.p2 = stopNode.getPos();
105            if (this.p1 == null || this.p2 == null) return;
106            int mid_x = (this.p1.x + this.p2.x) / 2;
107            int mid_y = (this.p1.y + this.p2.y) / 2;
108            this.x_diff = this.p2.x - this.p1.x;
109            this.y_diff = this.p2.y - this.p1.y;
110            float dist = (int)Math.sqrt(this.x_diff * this.x_diff +
111            this.y_diff * this.y_diff);
112            int x_ratio = (int)Math.round((this.y_diff * 3) / dist);
113            int y_ratio = (int)-Math.round((this.x_diff * 3) / dist);
114            
115            this.poly1 = new Polygon();
116            this.poly1.addPoint(this.p1.x - x_ratio, this.p1.y - y_ratio);
117            this.poly1.addPoint(this.p1.x + x_ratio, this.p1.y + y_ratio);
118            this.poly1.addPoint(this.p2.x + x_ratio, this.p2.y + y_ratio);
119            this.poly1.addPoint(this.p2.x - x_ratio, this.p2.y - y_ratio);
120            
121            this.poly2 = new Polygon();
122            this.poly2.addPoint(mid_x - 3 * x_ratio, mid_y - 3 * y_ratio);
123            this.poly2.addPoint(mid_x + 3 * x_ratio, mid_y + 3 * y_ratio);
124            this.poly2.addPoint(p1.x + x_diff * 7 / 10, p1.y + y_diff * 7 / 10);
125        }
126        
127        /**
128         * Build a channel that is fully configured. This is used for recreation
129         * of networks from a template file.
130         */
131        public Channel(Node start, Node end, boolean enabled,
132        int latency, int capacity) throws SameNodeException {
133            this(start, end);
134            setEnabled(enabled);
135            setLatency(latency);
136            setCapacity(capacity);
137        }
138        
139        //
140        // Public instance methods
141        //
142        
143        /**
144         * Insert an object into the channel.
145         *
146         * @param    o   The object to be inserted.
147         * @throws   ChannelFullException   if the number of objects in the
148         * channel already equals or exceeds the capacity.
149         * @throws   ChannelDisabledException  if the channel is currently
150         * disabled
151         */
152        public void writeObject(Object o) throws ChannelFullException, ChannelDisabledException {
153            if (o == null) {
154                System.err.println("Warning: You just wrote null to a channel!");
155                return;
156            }
157            synchronized (this.lock) {
158                if (this.isFull()) {
159                    throw(new ChannelFullException());
160                }
161                if (!this.enabled) {
162                    throw(new ChannelDisabledException());
163                }
164                
165                this.channel_objects.enqueue(o);
166                this.timeStamps.enqueue(new Long(System.currentTimeMillis()));
167                this.mvt_constant = 0;
168                
169            }
170        }
171        
172        /**
173         * Read an object from the channel.
174         *
175         * @return the oldest (in terms of insertion time) object in the channel
176         *
177         * @throws ChannelEmptyException if no objects are present in the
178         * channel or the oldest object has not propigated through the
179         * channel yet (due to a non-zero latency).
180         *
181         * @throws ChannelDisabledException if the channel is diabled
182         */
183        public Object readObject() throws ChannelEmptyException, ChannelDisabledException {
184            synchronized (this.lock) {
185                if (!this.enabled) throw new ChannelDisabledException();
186                if (this.channel_objects.size() == 0) throw new ChannelEmptyException();
187                // temporarily get the timestamp for current packet
188                long time = ((Long)this.timeStamps.peek()).longValue();
189                // check if packet's been around long enough
190                if (System.currentTimeMillis() - time <= this.latency) {
191                    throw new ChannelEmptyException();
192                }
193                this.mvt_constant = 0.8f;
194                // remove the timestamp
195                this.timeStamps.dequeue();
196                return(this.channel_objects.dequeue());
197            }
198        }
199        
200        public int getCapacity() {
201            return this.capacity;
202        }
203        
204        public void setCapacity(int cap) {
205            if (cap > MAX_CAPACITY) cap = 50;
206            if (cap < 0)  cap = 0;
207            this.capacity = cap;
208        }
209        
210        public int getLatency() {
211            return this.latency;
212        }
213        
214        public void setLatency(int lat) {
215            if (lat > MAX_LATENCY) lat = 50;
216            if (lat < 0) lat = 0;
217            this.latency = lat;
218        }
219        
220        public void setSelected(boolean b) { this.selected = b; }
221        public boolean isSelected() { return(this.selected); }
222        
223        public void setEnabled(boolean b) { this.enabled = b; }
224        public boolean isEnabled() { return(this.enabled); }
225        
226        public boolean contains(int x, int y) { return(this.poly1.contains(x, y)); }
227        public boolean contains(Point p) { return(this.contains(p.x, p.y)); }
228        
229        public void setName(String n) { this.name = n; }
230        public String getName() { return this.name; }
231        
232        /**
233         * Destroy the Channel.  <code>destroy()</code> should be called
234         * whenever a Channel will no longer be used.  When destroyed,
235         * Channels notify all connected Nodes of the impending destruction.
236         * <I>Note: <code>Channel.finalize()</code> automatically calls
237         * <code>destroy()</code></I>.
238         */
239        public void destroy() {
240            this.stop();
241            if (this.destroyed) return;
242            this.destroyed = true;
243            this.startNode.notifyOfDestruction(this);
244            this.stopNode.notifyOfDestruction(this);
245        }
246        
247        public boolean isDestroyed() { return this.destroyed; }
248        
249        /**
250         * Notify this that a NodeNetElement has been destroyed.  Typically,
251         * this method will be called by one of the two connected Nodes.  If
252         * that is the case, the channel destroys itself.
253         */
254        public void notifyOfDestruction(NodeNetElement nne) {
255            if (nne.equals(this.startNode) || nne.equals(this.stopNode)) {
256                this.destroy();
257            }
258        }
259        
260        public boolean isEditable() { return true; }
261        public void configure() {
262            if (Channel.cf == null) Channel.cf = new ChannelConfigFrame();
263            Channel.cf.setChannel(this);
264            Channel.cf.show();
265        }
266        
267        private boolean isFull() {
268            return this.channel_objects.size() >= this.capacity;
269        }
270        
271        public void paint(Graphics g) {
272            if (this.destroyed) return;
273            if (this.enabled && this.running) this.mvt_constant += .1f;
274            if (this.mvt_constant >= 1) this.mvt_constant = 0;
275            
276            if (!this.enabled) g.setColor(Color.gray);
277            else if (this.isFull()) g.setColor(Color.red);
278            else g.setColor(Color.black);
279            g.fillPolygon(this.poly1);
280            g.fillPolygon(this.poly2);
281            if (this.selected) {
282                g.setColor(Color.red);
283                g.drawPolygon(this.poly2);
284            }
285            
286            if (!this.enabled)  g.setColor(Color.black);
287            else if (this.isFull()) g.setColor(Color.pink);
288            else /* std case*/  g.setColor(Color.red);
289            int j = this.channel_objects.size();
290            for (int i = 1; (i <= j) && ( i <= 6); i++) {
291                int start_x = this.p1.x +
292                (int)(this.x_diff * (i + 2 + this.mvt_constant) / 12);
293                int start_y = this.p1.y +
294                (int)(this.y_diff * (i + 2 + this.mvt_constant) / 12);
295                g.fillOval(start_x, start_y, 8, 8);
296            }
297        }
298        
299        public void start() {
300            this.running = true;
301        }
302        public void stop()  { this.running = false; }
303        public void run() {}
304        
305        /** Determines if two Channels are equal. */
306        public boolean equals(Object o) {
307            if (!(o instanceof Channel)) return false;
308            Channel c = (Channel)o;
309            return (c.myID == this.myID);
310        }
311        
312        public String templateXML() {
313            return
314            "<channel start=\"node" + getStartNode().getMyID() +
315            "\" end=\"node" + getEndNode().getMyID() +
316            "\" enabled=\"" + isEnabled() +
317            "\" latency=\"" + getLatency() +
318            "\" capacity=\"" + getCapacity() +
319            "\"/>";
320        }
321        
322        //
323        // Package instance methods
324        //
325        
326        Node getStartNode() { return this.startNode; }
327        Node getEndNode() { return this.stopNode; }
328        
329        
330        //
331        // Protected instance methods
332        //
333        
334        protected void finalize() throws IOException {
335            this.destroy();
336        }
337        
338        //
339        // Private instance methods
340        //
341        
342        /**
343         * Custom de-serialization.  Get NodeBehavior & color, then start
344         * Thread.
345         *
346         * private void readObject(ObjectInputStream in)
347         * throws IOException, ClassNotFoundException {
348         * in.defaultReadObject();
349         * this.channel_objects = new DefaultQueue();
350         * this.timeStamps = new DefaultQueue();
351         * for (int i=0; i<this.numberQueueObjects; i++) {
352         * Object o = new Object();
353         * this.channel_objects.enqueue(o);
354         * this.timeStamps.enqueue(new Long(System.currentTimeMillis()));
355         * }
356         * this.lock = new Object();
357         * }
358         */
359        
360        public void reset() {
361            while (!this.channel_objects.isEmpty()) {
362                this.channel_objects.dequeue();
363            }
364            while (!this.timeStamps.isEmpty()) {
365                this.timeStamps.dequeue();
366            }
367            
368        }
369        
370        /**
371         * A configuration frame for Channels.
372         */
373        private class ChannelConfigFrame extends JFrame implements Serializable {
374            
375            transient Channel myChannel;
376            JTextField bandwidth, latency;
377            JButton ok, cancel;
378            
379            public ChannelConfigFrame() {
380                
381                setTitle("Channel Configuration");
382                JPanel p = new JPanel();
383                p.setLayout(new FlowLayout());
384                p.add(new JLabel("Capacity [0-" + Channel.MAX_CAPACITY + "]:"));
385                bandwidth = new JTextField(4);
386                p.add(bandwidth);
387                p.add(new JLabel("Latency [0-" + Channel.MAX_LATENCY + "]:"));
388                latency = new JTextField(4);
389                p.add(latency);
390                this.getContentPane().add("Center", p);
391                
392                JPanel q = new JPanel();
393                q.setLayout(new FlowLayout());
394                this.ok = new JButton("OK");
395                this.ok.addActionListener(new ActionListener() {
396                    public void actionPerformed(ActionEvent e) {
397                        configureChannel();
398                        ChannelConfigFrame.this.setVisible(false);
399                    }
400                });
401                this.cancel = new JButton("Cancel");
402                this.cancel.addActionListener(new ActionListener() {
403                    public void actionPerformed(ActionEvent e) {
404                        ChannelConfigFrame.this.setVisible(false);
405                    }
406                });
407                q.add(this.ok);
408                q.add(this.cancel);
409                this.getContentPane().add("South", q);
410                
411                this.pack();
412                
413                this.addWindowListener(new WindowAdapter() {
414                    public void windowClosing(WindowEvent e) {
415                        ChannelConfigFrame.this.setVisible(false);
416                    }
417                });
418            }
419            
420            void setChannel(Channel newChannel) {
421                this.myChannel = newChannel;
422                setTextFields();
423            }
424            
425            void setTextFields() {
426                bandwidth.setText(Integer.toString(getBandwidth()));
427                latency.setText(Integer.toString(getLatency()));
428            }
429            
430            void configureChannel() {
431                if (this.myChannel == null) return;
432                try {
433                    this.myChannel.setCapacity(Integer.parseInt(this.bandwidth.getText().trim()));
434                    this.myChannel.setLatency(Integer.parseInt(this.latency.getText().trim()));
435                }
436                catch(NumberFormatException e) {}
437            }
438            
439            int getBandwidth() {
440                if (this.myChannel == null) return 0;
441                return this.myChannel.getCapacity();
442            }
443            
444            int getLatency() {
445                if (this.myChannel == null) return 0;
446                return this.myChannel.getLatency();
447            }
448        } // class ChannelConfigFrame
449    } // class Channel
450    
451    /*
452     * $Log: Channel.java,v $
453     * Revision 1.9  2004/01/14 21:43:17  gus
454     * more javadoc, plus reformat
455     *
456     * Revision 1.8  2004/01/14 20:23:21  gus
457     * Javadoc and comment cleanup
458     *
459     * Revision 1.7  2004/01/13 19:35:27  gus
460     * Simulation Panel, channel and node are all Resettable now.
461     * CountingNodeBehavior refactored into Counter
462     *
463     * Revision 1.6  2004/01/09 20:12:54  gus
464     * Switch to JLabels for a more consistant look
465     *
466     * Revision 1.5  2004/01/09 19:58:19  gus
467     * Channels now do nothing and complain when you write null to them (bug 150)
468     *
469     * Revision 1.4  2004/01/08 15:44:01  gus
470     * Create xml template and a convenience constructor to match.
471     *
472     * Revision 1.3  2003/02/19 15:39:30  gus
473     * Reformated
474     *
475     * Revision 1.2  2002/06/13 19:32:43  gus
476     * Commented out com.sun.java.swing package references, and updated obsolete
477     * imorts and references into cs101 package.
478     *
479     * Revision 1.1  2002/06/13 17:33:21  gus
480     * Moved all java files into the nodenet directory (who let them out anyway?)
481     *
482     * Revision 1.1.1.1  2002/06/05 21:56:35  root
483     * CS101 comes to Olin finally.
484     *
485     * Revision 1.3  2000/05/09 06:03:54  mharder
486     * Changed packagename from nodeNet to nodenet.
487     *
488     * Revision 1.2  1999/07/30 21:36:39  jsmthng
489     * Updated names of files to reflect change from 'BinSort' to 'nodeNet';
490     * added InputChannelVector and OutputChannelVector classes (moving from
491     * Vectors to ChannelVectors, unless we move back).
492     *
493     * Revision 1.1  1999/07/30 01:09:18  jsmthng
494     * Renaming BinSort package to nodeNet; moving directories and files as
495     * necessary.
496     *
497     * Revision 1.6  1999/07/27 16:57:44  las
498     * Updating BinSort.Channel.java to use the new spelling of
499     * cs101.util.Queue.peek() :o)
500     *
501     * Revision 1.5  1999/01/25 19:31:51  tparnell
502     * Fixed packages.  cs101.util.Queue is now an interface, replaced with DefaultQueue
503     *
504     * Revision 1.4  1999/01/21 20:57:05  tparnell
505     * it worked
506     *
507     * Revision 1.3  1998/08/12 19:29:28  tparnell
508     * Another pass after comments from las & natashao.  Added support to
509     * dynamically add NodeBehaviors.  Add keyboard shortcuts to menus.  Added
510     * workaround to jdk bug relating to lighweight components.  Misc other
511     * bugfixes.
512     *
513     * Revision 1.3  1998/08/10 17:45:50  tparnell
514     * Revision to use JDK1.2 and Swing.  Redesign of GUI.  Removed old kludge
515     * for file I/O and replaced with object serialization.  Channel no longer
516     * requires animacy.  Removed unnessary dependencies between classes.
517     * Added ability to configure channel's latency and capacity.  Added
518     * javadoc to all files.  General code cleanup.
519     *
520     */