9.10 El código de FreqControl.java

/*  
 * Crea una ventana redimensionable en la que aparece un JSlider y un  
 * JFormatterdTextField. El slider se utiliza para indicar la  
 * frecuencia usando el ratón. El textField para indicarla  
 * numéricamente. Ambos objetos están sincronizados, es decir, si  
 * modificamos el estado de uno automáticamente se modifica el estado  
 * del otro.  
 *  
 * Esta clase contiene además un thread que escucha un canal multicast  
 * para conocer la frecuencia establecida en el servidor por otro  
 * objeto FreqControl. Cuando se recibe una frecuencia distinta de la  
 * actual, el slider y el textField son actualizados.  
 *  
 * La comunicación con el servidor se realiza mediante las clases  
 * externas OutToServer y InFromServer.  
 *  
 * gse. 2006.  
 */  
 
/* Evento de manipulación de excepciones de entrada/salida. */  
import java.io.IOException;  
 
/* En Java, un JFrame es una ventana independiente. */  
import javax.swing.JFrame;  
 
/* Organiza objetos gráficos en una ventana. */  
import javax.swing.JPanel;  
 
/* Un campo con texto que cuando se modifica genera un evento. */  
import javax.swing.JFormattedTextField;  
 
/* Permite construir "potenciometros" lineales. */  
import javax.swing.JSlider;  
 
/* Etiquetas de texto */  
import javax.swing.JLabel;  
 
/* Permite organizar unos objetos gráficos con respecto a otros. */  
import javax.swing.BoxLayout;  
 
/* Genera un evento cuando pulsamos una tecla. */  
import javax.swing.KeyStroke;  
 
/* Una clase abstracta para los objetos que tratan con acciones. Las  
 * clases abstractas no pueden ser instanciadas, sólo pueden ser  
 * heredadas. */  
import javax.swing.AbstractAction;  
 
/* Da un formato concreto a un número. Por ejemplo, esta clase se  
 * puede utilizar para hacer que aparezca el punto del millar ... */  
import javax.swing.text.NumberFormatter;  
 
/* Establece un borde alrededor de un objeto gráfico. */  
import javax.swing.BorderFactory;  
 
/* Define un objeto que escucha los eventos de cambio. */  
import javax.swing.event.ChangeListener;  
 
/* Se utiliza para avisar a otros objetos interesados en que ha  
 * cambiado el estado en una fuente de eventos. */  
import javax.swing.event.ChangeEvent;  
 
/* Espeficica que se ha ocurrido un evento en un componente. */  
import java.awt.event.ActionEvent;  
 
/* Escucha a los eventos de ventana. */  
import java.awt.event.WindowListener;  
 
/* Es una clase que contiene una serie de métodos independientes de la  
 * plataforma (por ejemplo, el que genera un "beep"). */  
import java.awt.Toolkit;  
 
/* Interface (una clase abstracta pura, sin métodos definidos) usado  
 * para escribir manejadores de escucha de eventos. */  
import java.beans.PropertyChangeListener;  
 
/* Objeto lanzado cuando se produce un cambio en las propiedades de un  
 * objeto. */  
import java.beans.PropertyChangeEvent;  
 
/* Un componente en Java es un objeto gráfico que puede interactuar  
 * con el usuario. */  
import java.awt.Component;  
 
/* El evento de ventana. */  
import java.awt.event.WindowEvent;  
 
/* El evento de teclado. */  
import java.awt.event.KeyEvent;  
 
/* Interface usado cuando deseamos escuchar los cambios de estado que  
 * se producen en los componentes. */  
import java.awt.event.ComponentListener;  
 
/* El evento de cambio del estado de una componente. */  
import java.awt.event.ComponentEvent;  
 
public class FreqControl  
    extends JPanel  
    implements Runnable,  
               WindowListener,  
               ChangeListener,  
               PropertyChangeListener,  
               ComponentListener {  
 
    /* Rango de frecuencias posible. */  
    static final int MIN_FREQ = 0;  
    static final int MAX_FREQ = 22050;  
 
    /* Dimensiones iniciales de la ventana. */  
    static final int WINDOW_WIDTH = 512;  
    static final int WINDOW_HEIGHT = 128;  
 
    JFormattedTextField textField;  
    JSlider slider;  
 
    /* La frecuencia leída del servidor. */  
    int freq;  
 
    /* El canal TCP hacia el servidor. */  
    OutToServer outToServer;  
 
    /* El canal multicast UDP desde el servidor. */  
    InFromServer inFromServer;  
 
    /**  
     * Constructor. Crea la ventana con todos los componentes y  
     * establece los cockes de entrada y salida.  
     */  
    public FreqControl(String serverName) throws IOException {  
 
        /* Colocaremos el textField y el slider uno encima del otro. */  
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));  
 
        /* Creamos el label del textField. */  
        JLabel textFieldLabel = new JLabel("Frecuencia (Hz): ", JLabel.CENTER);  
        textFieldLabel.setAlignmentX(Component.CENTER_ALIGNMENT);  
 
        /* Creamos el textField con su formato. */  
        java.text.NumberFormat numberFormat  
            = java.text.NumberFormat.getIntegerInstance();  
        NumberFormatter formatter = new NumberFormatter(numberFormat);  
        formatter.setMinimum(new Double((double)MIN_FREQ));  
        formatter.setMaximum(new Double((double)MAX_FREQ));  
        textField = new JFormattedTextField(formatter);  
        textField.setValue(new Integer(MIN_FREQ));  
        textField.setColumns(4);  
        textField.addPropertyChangeListener(this);  
 
        /* El textField comprobará el valor introducido por teclado  
         * una vez que hallamos pulsado la techa <Enter>. */  
        textField.getInputMap().  
            put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"check");  
        textField.getActionMap().put("check", new AbstractAction() {  
            public void actionPerformed(ActionEvent e) {  
                if (!textField.isEditValid()) { // Texto inválido.  
                    Toolkit.getDefaultToolkit().beep();  
                    textField.selectAll();  
                } else try {                    // Texto válido,  
                    textField.commitEdit();     // úsalo.  
                } catch (java.text.ParseException exc) { }  
            }  
        });  
 
        /* Creamos el slider y definimos su apariencia. */  
        slider = new JSlider(JSlider.HORIZONTAL, MIN_FREQ, MAX_FREQ, MIN_FREQ);  
        slider.addChangeListener(this);  
        slider.setMajorTickSpacing(1000);  
        slider.setMinorTickSpacing(1000);  
        slider.setPaintTicks(true);  
        slider.setPaintLabels(true);  
        //slider.setSnapToTicks(true);  
        slider.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));  
 
        /* Redefinimos las etiquetas del slider. */  
        java.util.Dictionary labelTable = slider.getLabelTable();  
        JLabel[] l = new JLabel[(MAX_FREQ-MIN_FREQ)/1000+1]; {  
            int i;  
            for(i=MIN_FREQ; i<MAX_FREQ; i+= 1000) {  
                l[i/1000] = new JLabel(i/1000 + "");  
                labelTable.put(new Integer(i), l[i/1000]);  
            }  
        }  
        slider.setLabelTable(labelTable);  
        /*java.util.Hashtable labelTable = new java.util.Hashtable();  
        JLabel[] l = new JLabel[(MAX_FREQ-MIN_FREQ)/1000+1]; {  
            int i;  
            for(i=MIN_FREQ; i<MAX_FREQ; i+= 1000) {  
                l[i/1000] = new JLabel(i/1000 + "");  
                labelTable.put(new Integer(i), l[i/1000]);  
            }  
        }  
        slider.setLabelTable(labelTable);*/  
        //slider.setPaintLabels(true);  
 
        /* Creamos una estructura subpanel para el textField y su  
         * etiqueta. */  
        JPanel labelAndTextField = new JPanel();  
        labelAndTextField.add(textFieldLabel);  
        labelAndTextField.add(textField);  
 
        /* Insertamos en la ventana "FreqControl" el anterior JPanel y  
         * el slider. */  
        add(labelAndTextField);  
        add(slider);  
 
        /* Distancia del contenido de la ventana a sus bordes. */  
        setBorder(BorderFactory.createEmptyBorder(10,10,10,10));  
 
        /* Queremos una ventana "bonita" con la decoración y  
         * comportamiento por defecto en Java. */  
        JFrame.setDefaultLookAndFeelDecorated(true);  
 
        /* Creamos la ventana y establecemos su tamaño. */  
        JFrame frame = new JFrame("FreqControl");  
 
        /* Le indicamos el tamaño. */  
        frame.setPreferredSize(new java.awt.Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));  
        /* Espeficicamos lo que hace cuando lo cerramos: finalizar. */  
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
 
        /* Insertamos el "pane" en la ventana actual. Un pane es la  
         * estructura de objetos creada cuando hemos ido añadiendo los  
         * diferentes objetos usando el método "add()". */  
        //this.setOpaque(true); //content panes must be opaque  
        frame.setContentPane(this);  
 
        /* Hace que la ventana tenga el tamaño especificado por el  
         * método "setPreferredSize()". */  
        frame.pack();  
 
        /* Mostramos la ventana. */  
        frame.setVisible(true);  
 
        /* Nos conectamos al servidor */  
        outToServer = new OutToServer(serverName);  
        inFromServer = new InFromServer();  
 
        /* Hacemos que la ventana escuche a los eventos de ventana. */  
        frame.addWindowListener(this);  
 
        /* Hacemos que la ventana escuche a los eventos provocamos por  
         * sus componentes. */  
        frame.addComponentListener(this);  
 
        /* Creamos y lanzamos el hilo que está pendiente de las  
         * frecuencias especificadas por los demás clientes. */  
        new Thread(this).start();  
   }  
 
    /**  
     * Este método es el que ejecuta el hilo que escucha las  
     * frecuencias de los demás clientes. Básicamente es un lazo sin  
     * fin que espera a recibir un paquete del canal multicast. Cuando  
     * lo recibe extrae de él la frecuencia y actualiza la variable  
     * global del método "freq".  
     */  
    public void run() {  
        for(;;) {  
            try {  
                freq = inFromServer.receive();  
            } catch (IOException e) {  
                System.out.println("FreqControl: run: unable to receive data");  
            }  
            //System.out.println("freq = " + freq);  
 
            /* Cuando recibimos una frecuencia actualizamos el campo  
             * textField. */  
            textField.setValue(new Integer(freq));  
        }  
    }  
 
    /**  
     * Método que se invoca cuando la ventana se abre.  
     */  
    public void windowOpened(WindowEvent e) {}  
 
    /**  
     * Método que se invoca cuando la ventana se iconiza.  
     */  
    public void windowIconified(WindowEvent e) {}  
 
    /**  
     * Método que se invoca cuando la ventana se des-iconiza.  
     */  
    public void windowDeiconified(WindowEvent e) {}  
 
    /**  
     * Método que se invoca cuando la ventana se activa (cuando se  
     * selecciona).  
     */  
    public void windowActivated(WindowEvent e) {}  
 
    /**  
     * Método que se invoca cuando la ventana se des-activa.  
     */  
    public void windowDeactivated(WindowEvent e) {}  
 
    /**  
     * Método que se invoca cuando la ventana se va cerrar.  
     */  
    public void windowClosing(WindowEvent e) {  
        try {  
            /* Enviamos una frecuencia negativa indicando al servidor  
             * que desemoa cerrar la conexión TCP. */  
            outToServer.sendString("-1\n");  
        } catch (IOException ioe) {  
            System.out.println("FreqControl::windowClosing: unable to send data");  
        }  
        try {  
            /* Abandonamos el grupo multicast. */  
            inFromServer.leaveGroup();  
        } catch (IOException ioe) {  
            System.out.println("FreqControl::windowClosing: unable to leave the multicast group");  
        }  
        /* Cerramos el socket de recepción (canal multicast). */  
        inFromServer.close();  
    }  
 
    /**  
     * Método que se invoca cuando la ventana ya se ha cerrado.  
     */  
    public void windowClosed(WindowEvent e) {}  
 
    /**  
     * Método que se invoca cuando el slider cambia de estado.  
     */  
    public void stateChanged(ChangeEvent e) {  
        JSlider source = (JSlider)e.getSource();  
        int local_freq = (int)source.getValue();  
        if (!source.getValueIsAdjusting()) { // done adjusting  
            textField.setValue(new Integer(local_freq));  
        } else { //value is adjusting; just set the text  
            textField.setText(String.valueOf(local_freq));  
        }  
        /* Si la frecuencia leída a través del canal multicast es  
         * diferente de la que indica el slider, es que estamos  
         * interactuando con el slider. Por tanto enviamos la que  
         * espeficica este hacia al servidor. */  
        if(freq!=local_freq) {  
            try {  
                //System.out.println(freq + " " + local_freq);  
                outToServer.sendString(String.valueOf(local_freq) + ’\n’);  
            } catch (IOException ioe) {  
                System.out.println("FreqControl::stateChanged: unable to send data");  
            }  
        }  
    }  
 
    /**  
     * Escucha al textField. Este método detecta cuando el valor del  
     * texField cambia (por el motivo que sea).  
     */  
    public void propertyChange(PropertyChangeEvent e) {  
        if ("value".equals(e.getPropertyName())) {  
            Number value = (Number)e.getNewValue();  
            if (slider != null && value != null) {  
                slider.setValue(value.intValue());  
            }  
        }  
    }  
 
    /**  
     * Método invocado si los componentes de la ventana son escondidos.  
     */  
    public void componentHidden(ComponentEvent e) {}  
 
    /**  
     * Método que se invoca si los componentes de la ventana son  
     * movidos.  
     */  
    public void componentMoved(ComponentEvent e) {}  
 
    /**  
     * Método invocado cuando los componentes cambian de tamaño.  
     */  
    public void componentResized(ComponentEvent e) {}  
 
    /**  
     * Método invocado cuando los componentes son mostrados.  
     */  
    public void componentShown(ComponentEvent e) {}  
 
    /**  
     * El método main() es el primero que se ejecuta cuando se lanza  
     * un objeto de la clase FreqControl. Su función principal es la  
     * de tratar los argumentos proporcionados desde el shell a través  
     * de la línea de comandos y de crear el objeto en cuestión.  
     */  
    public static void main(String args[]) throws Exception {  
        if(args.length <1) {  
            System.out.println("Uso: java FreqControl servidor");  
        } else {  
            new FreqControl(args[0]);  
        }  
    }  
}  
/* Manipula los paquetes UDP. */  
import java.net.DatagramPacket;  
 
/* Socket Multicast. */  
import java.net.MulticastSocket;  
 
/* Evento de manipulación de excepciones de entrada/salida. */  
import java.io.IOException;  
 
/* Manipula las direcciones IP. */  
import java.net.InetAddress;  
 
public class InFromServer {  
 
    /* Puerto de entrada de los paquetes. */  
    static final int PORT = 6789;  
 
    /* Socket Multicast para leer del servidor. */  
    MulticastSocket inFromServer;  
 
    /* Paquete UDP leído del servidor. */  
    DatagramPacket receivedPacket;  
 
    /* Dir IP del grupo multicast. */  
    InetAddress mCastIA;  
 
    /* Constructor. */  
    public InFromServer()  
        throws  
            IOException {  
 
        /* Crea un socket para recibir paquetes UDP. */  
        inFromServer = new MulticastSocket(PORT);  
 
          /* Obtenemos la dir IP del grupo multicast. */  
        mCastIA = InetAddress.getByName("224.0.0.1");  
 
        /* Nos unimos al grupo multicast. */  
        inFromServer.joinGroup(mCastIA);  
 
         /* Creamos el paquete UDP que recibiremos del servidor. */  
        receivedPacket = new DatagramPacket(new byte[2], 2);  
    }  
 
    /* Leemos la frecuencia. */  
    public int receive() throws IOException {  
 
        try {  
            inFromServer.receive(receivedPacket);  
        } catch (IOException e) {  
            System.out.println("InFromServer: receive: unable to receive data");  
            throw new IOException();  
        }  
 
        int freq = ((int)receivedPacket.getData()[0] & 0xFF) * 256 +  
            ((int)receivedPacket.getData()[1] & 0xFF);  
 
        return freq;  
    }  
 
    /* Dejamos el grupo multicast. */  
    public void leaveGroup() throws IOException {  
        try {  
            inFromServer.leaveGroup(mCastIA);  
        } catch (IOException ioe) {  
            System.out.println("InFromServer: leaveGroup: unable to leave the multicast group");  
            throw new IOException();  
        }  
    }  
 
    /* Cerramos el socket. */  
    public void close() {  
        inFromServer.close();  
    }  
 
 }  
/*  
 * Envía la frecuencia al servidor a través de un socket TCP.  
 */  
 
/* Permite escribir tipos de datos primitivos (byte, int, float,  
 * ...). */  
import java.io.DataOutputStream;  
 
/* Puerto de comunicación entre procesos. */  
import java.net.Socket;  
 
/* Evento de manipulación de excepciones de entrada/salida. */  
import java.io.IOException;  
 
/* Evento lanzado cuando nos conectamos a un host inexistente. */  
import java.net.UnknownHostException;  
 
public class OutToServer {  
 
    /* Flujo hacia el servidor. */  
    DataOutputStream outToServer;  
 
    /* Puerto de escucha del servidor. */  
    static final int PORT = 6789;  
 
    /* Constructor. */  
    public OutToServer(String serverName)  
        throws  
            UnknownHostException,  
            IOException {  
 
        Socket clientSocket = null;  
 
        /* Consulta al DNS y establece una conexión TCP. */  
        try {  
            clientSocket = new Socket(serverName, PORT);  
        } catch (UnknownHostException e) {  
            System.out.println("OutToServer: unknown host " +  
                               "\"" + serverName + "\"");  
            throw new UnknownHostException();  
        }  
 
        /* Conectamos clientSocket con outToServer. */  
        try {  
            outToServer = new DataOutputStream(clientSocket.getOutputStream());  
        } catch (IOException e) {  
            System.out.println("OutToServer: unable to connect to " +  
                               "\"" + serverName + "\"");  
            throw new IOException();  
        }  
    }  
 
    /* Envía una cadena al servidor. */  
    public void sendString(String s) throws IOException {  
        try {  
            outToServer.writeBytes(s);  
        } catch (IOException ioe) {  
            System.out.println("OutToServer: unable to sendString" +  
                               "\"" +  s + "\"");  
            throw new IOException();  
        }  
    }  
}