Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

Cómo ejecutar otras aplicaciones desde Java

Por regla general los programas, ya sean aquellos incluidos en el sistema operativo o instalados sobre este a posteriori, son iniciados a demanda del usuario, siendo este el que se comunica directamente con ellos. No obstante, en ocasiones puede interesar ejecutar un programa desde otro, enviándole información y recuperando su respuesta. Las aplicaciones de esta posibilidad son muchas.

Imaginemos que queremos contar las palabras que tiene un cierto texto almacenado en un archivo, a fin de informar al usuario. No tenemos que cargar dicho texto y procesarlo desde Java. En su lugar, asumiendo que estuviésemos trabajando sobre GNU/Linux, podríamos ejecutar la utilidad wc y recoger el resultado. Posiblemente nos cueste mucho menos trabajo, como programadores, y además sea más rápido si el texto es extenso.

Java nos ofrece en el paquete java.util una clase, llamada ProcessBuilder, que es todo lo que necesitamos conocer para ejecutar cualquier programa externo.

Veamos los pasos a seguir para utilizar dicha clase...

Establecer el programa y ruta de trabajo

Tras crear un objeto de la clase ProcessBuilder debemos indicar qué programas queremos ejecutar y cuál será la ruta del directorio de trabajo, aquél que actuará como carpeta por defecto para el programa ejecutado. Con este fin invocaremos a los métodos siguientes:

  • command() - Acepta como parámetro una cadena o una lista de cadenas, conteniendo el comando a ejecutar y sus parámetros. En caso de que el programa no se encuentre en la ruta de búsqueda del sistema operativo, será necesario preceder su nombre de dicha ruta.

  • directory() - En caso de que el programa deba ejecutarse sobre un cierto directorio, este método nos permite especificar la ruta del mismo. Precisa como parámetro un objeto File vinculado a esa ruta.

Los métodos command() y directory() devuelven como resultado una referencia al mismo objeto ProcessBuilder sobre el que actúan, por lo que es posible encadenarlos como se muestra a continuación:

ProcessBuilder pb = new ProcessBuilder();
pb.command(Arrays.asList("javac", "ejemplo.java"))
          .directory(new File("D:\\EjerciciosJava"));

El objetivo, en este caso, sería compilar un programa en Java desde el programa Java que está ejecutándose. El código de ejemplo.java incluso podría haber sido generado por el propio programa que a continuación lo compila.

Ejecutar el programa

Configurado el comando, junto con sus parámetros, y establecido el directorio desde el que se ejecutará el programa, la creación del proceso encargado de ejecutarlo se lleva a cabo mediante el método start() de la clase ProcessBuilder. Este puede producir una excepción de tipo IOException si no se encuentra el ejecutable.

Antes de lanzar la ejecución es posible establecer el entorno de ejecución del proceso, usando el método environment() de ProcessBuilder para recuperar las variables existentes en dicho entorno y modificarlas o agregar otras. Dicho método devuelve un diccionario, un objeto de tipo Map<String,String>, con cuyo método put() accederíamos a las variables de entorno.

Si el método start() puede iniciar satisfactoriamente el proceso, devolverá un objeto Process que nos permitirá comunicarnos con él. En la siguiente imagen aparecen varios de los métodos ofrecidos por dicha clase: isAlive() devuelve true si el proceso está aún en ejecución o false en caso contrario, caso este en que podríamos usar exitValue() para recuperar el valor de retorno.

ProcessMethods

Si necesitamos esperar a que el programa concluya, no es necesario que estemos comprobando constantemente el valor devuelto por isAlive(). En su lugar, basta con usar el método waitFor() para detener el programa actual hasta que el invocado termine. Opcionalmente puede establecerse un tiempo límite para dicha espera.

Comunicación con el proceso ejecutado

La buena parte de las utilidades que incorpora el sistema operativo, así como muchas otras aplicaciones, emplean la entrada y salida estándar para comunicarse con el usuario. Ambos canales están conectados por defecto a la consola, de forma que la salida estándar se envía a la pantalla y la entrada estándar lee del teclado. El programa que va a lanzar el proceso secundario puede acceder a la entrada/salida de este básicamente a través de dos vías:

  • Redireccionamiento a archivos: antes de lanzar la ejecución del proceso, es posible conectar la entrada estándar del programa a ejecutar, así como la salida estándar y la de errores, mediante los métodos redirectInput(), redirectOutput() y redirectError() de ProcessBuilder.
    pb
      .redirectInput(new File("entrada.txt")).
      .redirectOutput(new File("salida.txt"));
  • Conexión de entrada/salidas con el proceso que invoca: en este caso tendríamos que utilizar los métodos getOutputStream(), getInputStream() y getErrorStream() de Process. El primero devuelve un OutputStream en el que el programa puede escribir, información que se enviará a la entrada estándar del proceso invocado. Los otros dos facilitan como resultado un InputStream, mediante los cuales leeríamos la salida estándar y de errores de dicho proceso.

Un ejemplo práctico

Supongamos que estamos desarrollando una interfaz gráfica de usuario para git con Java. Git es un software de control de versiones de archivos disponible para múltiples sistemas operativos. Nuestra GUI, al haberse creado con Java, también sería multiplataforma. Una de las funciones de esta GUI sería examinar los últimos cambios efectuados por un cierto usuario, por ejemplo mostrándolos en una tabla.

Para obtener un resumen de los últimos commit efectuados en un cierto repositorio, asumiendo que nos encontramos en su directorio raíz o alguno de los subdirectorios que contenga, usaríamos un comando similar al que aparece en la parte superior de la siguiente imagen. El resultado, en este caso, se compone de una línea por commit con tres datos separados por punto y coma: el nombre del autor, el momento en que se efectuó y la descripción asociada:

GitOutput

El programa para ejecutar este comando git y procesar el resultado devuelto, mostrándolo en una tabla alojada en una ventana, es el siguiente:

String carpeta = "ruta\\de\\la\\carpeta\\bajo\\control\\git";
List<String> comando = Arrays.asList(
        "git",
        "log",
        "--pretty=format:\"%an;%ar;%s\"",
        "-20");
ProcessBuilder pb = new ProcessBuilder()
        .command(comando)
        .directory(new File(carpeta));
try {
    Process p = pb.start();
    p.waitFor(1, TimeUnit.MINUTES);
    Stream<String> lineas = (new BufferedReader(
            new InputStreamReader(p.getInputStream()))).lines();
    Object[][] datos = lineas
            .filter(l -> l.contains("Charte"))
            .map(l -> ((Object[]) l.split(";")))
            .toArray(size -> new Object[size][3]);
    String[] columnas = {"Autor", "Hace", "Mensaje"};
    JTable tabla = new JTable(datos, columnas);
    JFrame ventana = new JFrame("Datos sobre últimos commits");
    ventana.add(new JScrollPane(tabla));
    ventana.setDefaultCloseOperation(EXIT_ON_CLOSE);
    ventana.pack();
    ventana.setVisible(true);
} catch (IOException | InterruptedException ex) {
    System.out.println("La ejecución ha fallado");
}

Como puede apreciarse, en las primeras líneas se prepara la ruta en la que está el repositorio git y el comando para obtener los últimos 20 commits. A continuación se crea el ProcessBuilder, se invoca a start() y se espera durante un minuto como máximo a que la ejecución se complete. En las líneas 13-14 se recupera la salida estándar del proceso ejecutado y se conecta a BufferedReader a fin de recuperar el resultado, convirtiendo este en un Stream de cadenas. Usando la API Stream de Java 8 se filtran las entradas, buscando las que contienen un cierto nombre, y después se convierten en una matriz bidimensional de elementos Object. Esta se facilita como entrada al constructor de JTable, para crear una tabla que se muestra en una sencilla interfaz de usuario Swing.

Como se aprecia en la línea 22, la ventana tiene un contenedor JScrollPane en el que se introduce la tabla. Esto permitirá mostrar más información de la que es posible visualizar de una vez en la ventana, con la correspondiente barra de desplazamiento vertical. Al ejecutar el programa, asumiendo que tenemos git instalado en el sistema y se opera sobre un directorio asociado a un repositorio, obtendríamos una salida como la de la imagen siguiente:

Puedes descargar el código usado en este ejemplo desde este enlace.

Francisco Charte Francisco lleva más de 30 años dedicado a la enseñanza en informática, tanto en centros privados y públicos como a través de sus libros y artículos. Autor de más de 120 libros y varios centenares de artículos en revistas nacionales e internacionales. Charte es Doctor en Tecnologías de la información y la comunicación por la Universidad de Granada. Puedes seguirlo en Twitter en @fcharte Ver todos los posts de Francisco Charte
Archivado en: Lenguajes y plataformas

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Comentarios (1) -

sebastian
sebastian

hola francisco interesante articulo ... resulta que yo estoy intentando hacer  andar una impresora fiscal mediante jni (java native interface). es un tema complejo ...en algunas impresora lo he logrado hacer andar en otras no...at raves jni c++.
pero me he quedado atascado con un codigo que me paso epson para integrarlo a java. se me ha ocurrido la idea en ves de usar jni y c++  .. usar un proceso en de una application desde java que se comunica con el dispositivo fiscal pasandole parámetros.
se trata de una simple aplicacion la idea es que funcione en background, que la aplicación exe devuelva parámetros a java para que sea presentado en gui de java el estado del dispositivo.
la gran pregunta es: si en java se puede usar el mismo id de proceso de una aplicación exe en modo background para que reciva y devuelva parámetros.

Responder

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.