Scala de la muerte

¡Cuanto tiempo!  A punto de comenzar de nuevo la universidad he estado haciendo ciertos cursos en los que estoy aprendiendo Scala y Python.  Hoy os voy a poner un ejercicio que he tenido que hacer en Scala.

Tenemos que implementar de alguna manera los conjuntos. Es decir, secuencias finitas o infinitas de números agrupados por una función.
Ejemplos:  El conjunto V = { 1,3,5,7}  es un conjunto que me acabo de inventar. Tiene 4 números primos como podemos ver pero es finito por lo que no son todos los números primos. Otro conjunto sería los pares, osea “Para todo x € Z | x % 2 == 0”, esto es { …,-4 -2, 0, 2, 4…} y por tanto infinito.

Así que la primera duda será: ¿ Cómo logramos definir algo infinito en un ordeandor? La pregunta no es sencilla pero la respuesta sí: Con un booleano. Si nos fijamos los pares cumplen la condición x % 2 == 0, así que el conjunto será dado un entero, ¿Esté pertenece o no al conjunto?

type Set = Int => Boolean

Y así definimos el tipo conjunto (Set).
Ahora deberíamos pensar en como hacer conjuntos finitos. Para esto no podemos usar simplemente una expresión booleana sino que tendremos que añadirlos uno a uno. Así que definimos una función tal que pasado un elemento nos devuelva un set con sólo ese elemento:

  def singletonSet(elem: Int): Set = a => a == elem

Muy bien, ahora usando

val s1 = singletonSet(1)

podemos crear un set de un sólo elemento. ¿Y cómo añadimos más? Para eso podemos ver en la wikipedia las operaciones posibles. *** NOTA: Operaciones entre conjuntos DAN como resultado conjuntos ***

Para unir conjuntos tenemos que usar la unión de ellos, es decir, dado dos conjuntos un elemento esta en uno o en el otro:

def union(s: Set, t: Set): Set = a => s(a) || t(a)

¿Y para saber cuales elementos tienen en común? La intersección:

  def intersect(s: Set, t: Set): Set = a => s(a) && t(a)

¿Y si no queremos que en un conjunto haya elementos del otro? La diferencia:

  def diff(s: Set, t: Set): Set = a => s(a) && !t(a)

Podemos hace más operaciones así. Pero ahora nos piden otra cosa. Para un conjunto S, ¿Qué elementos de ese conjunto cumplen la propiedad p?

  def filter(s: Set, p: Int => Boolean): Set = a => s(a) && p(a)

Fácil, ¿No?

¿Y si queremos comprobar que TODOS los elementos en S cumplen la propiedad p? Para conjuntos infinitos eso puede ser una tarea… eh… infinita. Así que nos vemos obligados(para esta implementación, sé que hay gente lo ha hecho de otra manera) a poner un límite.

val bound = 1000

Una vez que tenemos el límite ya podemos iterar sobre cada elemento, uno a uno, y comprobar que satisface o no p:

  def forall(s: Set, p: Int => Boolean): Boolean = {
    def iter(a: Int): Boolean = {
      if (a > bound) true
      else if (s(a) && !p(a)) false
      else iter(a + 1)
    }
    
    iter(-bound)
  }

Notar que empezamos en -bound, es decir, desde -1000 hasta 1000.

Ya casi para terminar podemos exigirle al conjunto que nos diga si almenos un elemento, osea, existe un elemento en S que cumpla P. Al menos uno, no todos ni ninguno, con que haya uno nos vale.
De nuevo, ¡Wikipedia al rescate!

Si nos fijamos en la relación que tienen:
Relacion
Se lee: Para todo x € S se cumple P ES EQUIVALENTE A que NO hay al menos un elemento que NO cumpla P. Es decir que todos lo cumplen.
Podemos cambiarla de tal manera que el “Para todos” este en el lugar de “Existe” y viceversa. Con lo cual se leería: Existe x € S que cumple P ES EQUIVALENTE A que NO todos x€S NO cumplen P. Es decir que si de todos los elementos de S al decir que no cumplen P devuelve Falso, entonces es que hay al menos uno que SI lo cumple.

 def exists(s: Set, p: Int => Boolean): Boolean = !forall(s, x => !p(x))

Para terminar el más complicado de todos y con el que más ayuda me hizo falta porque no lo entendía.
Dado un conjunto aplicarle una función. Por ejemplo, si nos dan el conjunto de los pares y le aplicamos la funcion (x + 1), tenemos el conjunto de los impares. Si tenemos los números primos y la función (x * 2) tendremos el doble de los números primos.

 def map(s: Set, f: Int => Int): Set =  elem => exists(s, x => f(x) == elem)

Lo voy a leer literalmente: Dado un elemento elem pertenece al conjunto si existe en el conjunto S una x que al aplicarle la función sea igual al elemento. ¿Tiene su lógica no? Pues fue difícil y creo que sin ayuda de mi amigo no lo hubiese logrado >.<

Sin más esto es todo por hoy. Como siempre el proyecto estará en la carpeta de dropbox.

Un saludo!

Cifrado Vigenère

El cifrado Vigenère es un cifrado basado en diferentes series de caracteres o letras del cifrado César formando estos caracteres una tabla, llamada tabla de Vigenère, que se usa como clave. El cifrado de Vigenère es un cifrado de sustitución simple polialfabético. – Wikipedia

¡Buenos días! Como habréis podido notar hoy vamos a hacer una implementación del cifrado de Vigenènere. Este cifrado polialfabético nos da un cifrado muy simple con el que intercambiar mensajes con nuestros amigos o proponerlo de reto a alguien en una carta. Además contiene la operación modulo que vimos en el anterior post.

Pero, ¿Qué quiere decir que es polialfabético? Quiere decir que para sustituir una letra por otra no solo se usa el alfabeto normal y un padding como en el cifrado cesar, sino que el alfabeto va cambiando según un criterio. En concreto nosotros tendremos 26 alfabetos. Por tanto lo primero será declarar un mapa que nos guarde esta relación de alfabeto-letraclave.

private final static Map<Character, List<Character>> _alfabeto = new TreeMap<>();

¿Y por qué un mapa de carácteres a lista de carácteres? En el cifrado Vigenère ciframos según una clave dada. Por ejemplo : ROSA. Para cada letra de nuestro texto en claro tomaremos una letra(iterativamente) de la clave y la sustituiremos con la que le corresponda en la lista de carácteres. La posición a sustituir es donde iría normalmente esa letra. Si tenemos una E (la quinta del abecedario), para la letra R de nuestra clave miraremos la quinta posición, que es la V. Por tanto la E se cifra con la V. Para la siguiente letra no usaremos la R sino la siguiente, la O, luego la S, luego la A, luego la R… y así con todo el texto con lo que obtenemos protección contra una análisis de frecuencia. Además, si la clave es igual de largo que el texto obtenemos cifrado de secreto perfecto.

¿Cuál es entonces la función que nos genera este mapa?

	static
	{
		List<Character> list;
		
		for (int i = 65, j = 0; i < 91; ++i, ++j){	
			list = new ArrayList<Character>();
			
			// Siempre de 0 a 26 + un padding j
			// Si nos pasamos de 26 al sumar el padding entonces volvemos al 0
			// Y a eso le sumamos 65, que es la letra 'A'
			// Por tanto siempre iremos de A hasta Z
			for(int x = 0; x < 26; ++x)
				list.add((char)(((x + j) % 26) + 65));
			
			_alfabeto.put((char) (i), list);
		}
	}

Como véis nos movemos de la A a la Z(sin la Ñ) generando una lista que empieza por la propia letra y ,de manera circular, va añadiendo una a una las subsiguientes a la lista. Seguro que muchos ya sabéis cómo se consigue moverse de manera circular,¿Verdad? Es justo esta linea:

list.add((char)(((x + j) % 26) + 65));

Aquí tenéis el resultado:

Key: A
[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]

Key: B
[B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A]

Key: C
[C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B]

Key: D
[D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C]

Key: E
[E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D]

Key: F
[F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E]

Key: G
[G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F]

Key: H
[H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G]

Key: I
[I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H]

Key: J
[J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I]

Key: K
[K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J]

Key: L
[L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K]

Key: M
[M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L]

Key: N
[N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M]

Key: O
[O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N]

Key: P
[P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O]

Key: Q
[Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P]

Key: R
[R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q]

Key: S
[S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R]

Key: T
[T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S]

Key: U
[U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T]

Key: V
[V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U]

Key: W
[W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V]

Key: X
[X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W]

Key: Y
[Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X]

Key: Z
[Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y]

¡Ahora ya podemos empezar a codificar nuestros mensajes!

	public static String codificar(String texto, String clave)
	{
		texto = texto.replaceAll("(\\r|\\n| )", "").toUpperCase();
		clave = clave.replaceAll("(\\r|\\n| )", "").toUpperCase();
		
		StringBuilder sb = new StringBuilder();
		int diff;
		List<Character> list;
	
		char[] keys = clave.toCharArray();
		char[] txt = texto.toCharArray();
		
		for(int i = 0, j = 0; i < txt.length; ++i, j = (j+1) % keys.length)
		{
			diff = txt[i] - 65;
			list = _alfabeto.get(keys[j]);
			sb.append(list.get(diff));
		}
		
		return sb.toString();
	}

Las dos primeras lineas quitan los espacios y saltos de linea del texto en claro y de nuestra clave, ya que no estan contempladas en nuestro mapa.
Quizás lo más destacable de este simple código es esta línea:

j = (j+1) % keys.length

¡Pero esto no es más que un movimiento circular por la clave! Y a lo mejor alguien se pregunta por qué restamos 65 a la letra. ¡Simplemente para tener una relación de 0 a 26(Siendo 0 la A)! Mucho más cómodo de trabajar, ¿No?

¿Y para descifrar nuestros mensajes? No mucho más difícil que sumar 65 en vez de restar:

	public static String decodificar(String texto, String clave)
	{
		texto = texto.replaceAll("(\\r|\\n| )", "").toUpperCase();
		clave = clave.replaceAll("(\\r|\\n| )", "").toUpperCase();
		
		StringBuilder sb = new StringBuilder();
		List<Character> list;
	
		char[] keys = clave.toCharArray();
		char[] txt = texto.toCharArray();
		
		int aux;
		
		for(int i = 0, j = 0; i < txt.length; ++i, j = (j+1) % keys.length)
		{
			list = _alfabeto.get(keys[j]);
			
			aux = list.indexOf(txt[i]);
			sb.append((char)(65 + aux));
		}
		
		return sb.toString();
	}

Como resultado final tenemos:

Clave : ROSA
Texto en claro: El enemigo atacara al anochecer

Cifrado: VZWNVAAGFOLATOJARZSNFQZETSJ
Descifrado: ELENEMIGOATACARAALANOCHECER

Pues fijaós si es simple este cifrado que ya hemos acabado. Seguro que algunos queréis sacar algo más a este cifrado, como el uso de minusculas, tildes, etc… No es mucho más difícil y esta resuelto en dropbox junto a este ejercicio.

Un saludo.

Operación módulo

A estas alturas todos deberíamos saber bastante bien para que sirve la operación módulo( osea, %), pero como mis dos siguientes post trataran con esta operación de manera activa, vamos a explicar  un gran truco que nos ofrece y que a veces pasa por alto.

El uso principal que nos ofrece % es simplemente obtener el resto de una división.
Por ejemplo :
9 / 5 = 1 y 9 % 5 = 4
10/5 = 2 y 10 % 5 = 0

Si hacemos un pequeño for que nos muestre los módulos obtenemos:

for(int i = 0; i < 12; ++i)
       System.out.println(i + " tiene modulo " + i%5);

0 tiene modulo 0
1 tiene modulo 1
2 tiene modulo 2
3 tiene modulo 3
4 tiene modulo 4
5 tiene modulo 0
6 tiene modulo 1
7 tiene modulo 2
8 tiene modulo 3
9 tiene modulo 4
10 tiene modulo 0
11 tiene modulo 1
etc…

Por tanto x % 5 = 0..4

Y ¿Cómo podemos aplicar esto de otra manera? Si nos fijamos la operación % tiene una naturaleza circular y esto es muy útil en los arrays o listas circulares o para moverse por una variable de esta manera.

¿Cuántos de vosotros no habéis hecho esto?

int i = 0;

while(Condición){
   Operaciónes

   ++i; // Para evitar la sobre asignación.

   if( i == Límite)
        i = 0;
}

Con ese código nos queremos mover desde 0..Límite, por tanto podemos usar la operación módulo.

int i = 0;

while(Condición){
   Operaciónes
   i = (++i) % Límite;
}

Con lo que nos quitamos una condición y todo queda más compacto. Es muy importante que sea ++i y no i++, ya que la manera en la que estos funcionan es diferente(¡Y aquí es donde comprobamos su utilidad!).

¿Y con los arrays? Supongo que todos ya sabéis que es lo que va a pasar.
Modulo y arrays

Con lo cual es una manera sencilla y efectiva de recorrer un sistema circularmente sin pasarnos de la ralla por despiste.

Como dije antes, esta operación es realmente útil y tiene que tenerse en cuenta.Lo veremos más tarde en los ejemplos que desarrollaré.

Un saludo.

anotherdropboxfolder

Hola!

Hoy mientras limpiaba mi workspace, como cada fin de curso, de eclipse y netbeans he visto que tenía muchas cosas interesantes que merecian la pena ser compartidas.
La mayoria no tiene explicación alguna pero merecen la pena que les echeis un vistazo por si encontrais algo que os sea de utilidad.
Además, a partir de ahora este enlace será donde coloque todos los ejemplos que vaya haciendo en la página. Como suelo usar eclipse y netbeans a la vez lo que haré será especificar para que ide es el contenido poniendo ec ó nt para eclipse y netbeans respectivamente. Espero que así no os cause ninguna confusión.

Sin más el link: https://www.dropbox.com/sh/l75c4f3cxe2af30/tJF7Po-mWh

Dentro de poco haré un par de post más que tengo planeados! Uno con arrays circulares(estoy buscando algo para darle más chicha) y el cifrado de Vigenére.

Sin más un saludo.

Go lang en Windows

Debido a que he tenido ciertas dificultades, y en que la pagina oficial esta explicado para Linux o Mac, hoy vamos a ver como instalar Go, compilar  algun programa,  los paquetes, dar formato, generar documentación  y hacer test sobre nuetros programas escritos en Go(vaya, es bastante!) sobre Windows.

El primero paso no es, como muchos podrían esperar, instalar Go ni sus compiladores. El primer paso en Go es hacer el tutorial. Realmente imprescindible ya que el idioma es bastante “complicado” de primeras. Así que todos aquellos que no lo hayan hecho, por favor dirijanse a partirse la cabeza un rato con el idioma y ver si les gusta o prefieren seguir programando en HTML: http://tour.golang.org/#1

El segundo paso, ya sí, es instalar Go en nuestro ordenador:  http://golang.org/doc/install

Una vez instalado nos tenemos que asegurar de que las variables del entorno estan bien definidas(y aquí es donde yo he tenido los mayores problemas).
Resulta ser que Go se empeña en usar cierto tipo de arquitectura a la hora de organizar los programas, me refiero al workspace. Nos obligan a tener un workspace con cierta jerarquía para que funcione todo bien, lo cual al principio me chocaba cuando lo único que quería era compilar un archivo  y comprobar que funcionaba.

Así que el tercer paso será crearnos nuestro workspace. Yo por ejemplo lo tengo en el escritorio y lo he llamado “Go”.  Dentro de esta carpeta tiene que haber otra carpeta llamada “src” y que es obligatoria para que funcionen las cosas.

Así pues tendremos una carpeta “Go” y dentro otra llamada “src”.

Ahora nos vamos a las propiedades del sistema de  Windows(botón derecho sobre Mi Equipo -> Propiedades)  y luego accedemos a las “Opciones avanzadas”  -> “Variables de entorno”. Una vez allí nos fijamos en las variables del sistema, que deben contener un par de variables: GOROOT con valor vuestro directorio de instalación de Go ( por defecto C:\Go\) y luego en la variable  Path debe estar incluida la cadena C:\Go\bin.  Ahora vamos a definir una nueva variable GOPATH con valor el directorio de vuestro workspace, en mi caso C:\Users\Ouro\Desktop\Go\workspace.

Una vez definidas las variables ya podemos empezar a programar en Go.

Abrimos el cmd y accedemos a la carpeta C:\Users\Ouro\Desktop\Go\workspace\src donde crearemos con mkdir un directorio hello y accedemos a ella.

Ahora con nuestro editor preferido crearemos el siguiente código y lo guardaremos como hello.go

package main

import "fmt"

func main() {
    fmt.Println("Hola anotherprogrammingblog!")
}

Para testearlo simplemente escribimos go run hello.go y vemos que la salida es correcta.
Una vez hecho esto nos preguntamos ¿Dónde diantres esta el ejecutable? Pues bien, si  has hecho todo lo de las variables del sistema correctamente ahora simplemente tienes que escribir go install y nos creará un ejecutable en C:\Users\Ouro\Desktop\Go\workspace\bin

Bien, ya tenemos lo básico, así que ahora vamos a ver como crear un paquete. Para ello creamos con mkdir una nueva carpeta llamada string y como antes, crearemos un archivo string.go con el siguiente código:

/*
 Package string implementa una librería simple para
 realizar operaciones sobre strings

 */

package string

// Le da la vuelta a una string
func Reverse(s string) string{
 b := []byte(s) // Creamos un array de bytes de la string
 for i := 0; i < len(b)/2; i++ { // Ahora tenemos que cambiar la primera mitad
 j := len(b) -i - 1 // Por la segunda mitad
 b[i], b[j] = b[j], b[i]
 }

 return string(b)
}

Como veís, he escrito comentarios y algunas cosas mal indexadas para ahora realizar un par de cositas:
Primero vamos a ver si el formato es correcto, si ejecutamos gofmt string.go nos debería salir  una salida con “el formato correcto según Go  en mi caso:

/*
 Package string implementa una librería simple para
 realizar operaciones sobre strings
*/
package string

// Le da la vuelta a una string
func Reverse(s string) string {
 b := []byte(s) // Creamos un array de bytes de la string
 for i := 0; i < len(b)/2; i++ { // Ahora tenemos que cambiar la primera mitad
 j := len(b) - i - 1 // Por la segunda mitad
 b[i], b[j] = b[j], b[i]
 }

 return string(b)
}

Tenéis que tener cuidado con los comentarios, ya que si lo copiais desde cmd las cosas suelen salir mal, pero la idea detrás de gofmt se entiende no? Nos proporciona el formato que consideran mejor para nuestro código.

Si ahora ejecutamos godoc string nos saldrá lo siguiente:
PACKAGE DOCUMENTATION

package string
import “string”

Package string implementa una librería simple para
realizar operaciones sobre strings

FUNCTIONS

func Reverse(s string) string
Le da la vuelta a una string

Es decir, godoc nos devuelve la “documentación” que hemos escrito en el programa, esto es útil para averiguar que hacia cierto archivo sin tener que abrirlo y mirar el código.

Ahora sigamos con la parte principal de nuestra tarea, crear el paquete, para ello escribimos go build seguido de un go install. Esto nos creara un nuevo directorio pkg en C:\Users\Ouro\Desktop\Go\workspace\ con un subdirectorio dependiendo de nuestra arquitectura, en mi caso es C:\Users\Ouro\Desktop\Go\workspace\pkg\windows_amd64

Y allí adentro vemos un archivo string.a que es nuestro paquete instalado.

Vamos a usar este paquete en el programa hello. Editamos hello.go de la siguiente manera:

package main

import (
"fmt"
"string"
)

func main() {
fmt.Println(string.Reverse("Hola anotherprogrammingblog!"))
}

y volvemos a hacer go install. Si todo ha salido bien al ejecutar el programa nos saldrá:

!golbgnimmargorprehtona aloH

Ahora vamos a enseñar a hacer tests. Los test son cosas muy importante a la hora de programar ya que nos permite asegurarnos un poco más de la calidad de nuestro programa.

Accedemos a la carpeta de string y creamos un nuevo archivo string_test.go con el siguiente código:

package string

import "testing"

func Test(t *testing.T) {
	var tests = []struct {
		s, want string
		}{
			{"Backward","drawkcaB"},
			{"Hello, 世界", "界世, olleH"},
			{"",""},
		}

		for _, c := range tests {
			got := Reverse(c.s)

			if got != c.want {
				t.Errorf("Reverse(%q) == %q, el correcto es %q", c.s, got, c.want)
			}
		}
}

Y una vez que guardemos ejecutamos go test, que nos lanzará un error debido a que 世界  no es devuelto de manera correcta. Eso es debido a que nuestra función Reverse no maneja bien los  códigos Unicode o UTF. Si cambiamos la función Reverse  de la siguiente manera debería funcionar:

func Reverse(s string) string {
        b := []rune(s)                  // Creamos un array de bytes de la string
        for i := 0; i < len(b)/2; i++ { // Ahora tenemos que cambiar la primera mitad
                j := len(b) - i - 1 // Por la segunda mitad
                b[i], b[j] = b[j], b[i]
        }

        return string(b)
}

(En realidad, al usar el cmd de Win no te funcionará ya que no reconoce estos carácteres…)

Los ejemplos los he sacado de http://www.youtube.com/watch?v=XCsL89YtqCs

Espero que os haya servido de ayuda  este post tan largo y disfruteís programando en Go, que es un idioma muy bonito y chungo a la vez.

Runtime, Process y Git

Hoy voy a intentar explicar cómo comunicar nuestros programas Java con cualquier proceso, y al  final pondré un ejemplo con Git.

En Java podemos lanzar procesos con la clase Process, cosa que viene perfectamente explicada en esta página:

http://www.chuidiang.com/java/ejemplos/Runtime/runtime.php

Como se explica en esa web podemos, además de lanzar un proceso, leer sus salidas.

Así que vamos a  darle una estructura mejor para poder usarlo de manera fácil y modular.

Primero vamos a hacer una clase para leer las salidas del proceso. Ahora mismo no he probado como responder a peticiones que nos haga un proceso pero lo estudiaré más adelante. Aclarar también que tenemos que ver las entradas y las salidas desde el punto de vista del proceso, quiero decir: Para nosotros cuando un evento nos  devuelve algo(por ejemplo un syso) es un output. Sin embargo desde la vista del proceso, esa salida de información le llega desde el programa al que llamamos, así que es una entrada, un Inputstream.

También tenemos que pensar en “cuando un proceso” ha  terminado, o cuando su Inputstream ha terminado de leer la información que le llegaba, por tanto tendremos que implementar 2 variables sincronizadas (ya que estamos trabajando con hilos) para saber cuando el proceso termina.

   private class ProcessStreamReader
            extends Thread
    {
        private BufferedReader _reader;
        private int _lvl;

        public ProcessStreamReader(InputStream is, int lvl)
        {
            _reader = new BufferedReader(new InputStreamReader(is));
            _lvl = lvl;
            if (_lvl == 0) // Encendenmos los semáforos. También lo podemos hacer con unos set fuera del
            // método si no vamos a usarlo inmediatamente.
            {
                _okSemaforo = true;
            }
            else
            {
                _errorSemaforo = true;
            }
        }

        public void run()
        {
            StringBuilder sb = null;
            try
            {
                String line = _reader.readLine();
                sb = new StringBuilder(line);

                while (line != null && sb.toString() != null)
                {
                    // Aquí podemos hacer operaciones, como mostrarlo en una ventana o procesar el texto.
                    line = _reader.readLine();
                    sb.append("\n");
                    sb.append(line);
                }
            }
            catch (IOException e)
            {
            }
            catch (NullPointerException e)
            {
            }
            finally // Finalmente tenemos que cerrar los buffers y apagar los semáforos.
            {
                if (_lvl == 0)
                {
                    if (sb != null)
                    {
                        _okLecture = sb.toString();
                    }
                    else
                    {
                        _okLecture = null;
                    }
                }
                else
                {
                    if (sb != null)
                    {
                        _errorLecture = sb.toString();
                    }
                    else
                    {
                        _errorLecture = null;
                    }
                }

                if (_lvl == 0)
                {
                    _okSemaforo = false;
                }
                else
                {
                    _errorSemaforo = false;
                }
                try
                {
                    _reader.close();
                }
                catch (IOException ex)
                {
                }
            }
        }
    }

Como vemos tenemos un flag lvl para distinguir entre el errorInputStream y el InputStream normal.

Ahora vamos a implementar el propio proceso.
Sólo vamos a añadir una consideración más, normalmente queremos que nuestro proceso se lance en la misma carpeta que  estamos trabajando, así que tendremos que pasarle una variable dir que será donde se ejecute el proceso.

  private class ProcessExecuter
            extends Thread
    {
        private String _process;
        private String _dir;

        public ProcessExecuter(String p, String dir)
        {
            _process = p;
            _dir = dir;
        }

        public void run()
        {
            try
            {
                Process p = Runtime.getRuntime().exec(_process, null, new File(_dir));

                new ProcessStreamReader(p.getInputStream(), 0).start();
                new ProcessStreamReader(p.getErrorStream(), 1).start();

                while (isOkSemaforo() || isErrorSemaforo())
                {
                    try // Dormimos mientras se esta llevando a cabo el proceso.
                    {
                        sleep(200);
                    }
                    catch (InterruptedException ex)
                    {
                    }
                }

            }
            catch (IOException ex)
            {
                // Manejamos las excepciones
            }
            finally
            {
                // Al finalizar tendremos que realizar alguna acción,
                // por ejemplo continuar a la siguiente fase
                _stage++;
                continueStage();
            }
        }
    }

Las variables que usamos:

    private boolean _okSemaforo;
    public synchronized boolean isOkSemaforo()
    {
        return _okSemaforo;
    }

    private boolean _errorSemaforo;
    public synchronized boolean isErrorSemaforo()
    {
        return _errorSemaforo;
    }

    private static int _stage;

Y ahora como ejemplo práctico pongo mi implementación de comandos para Git, en el que se puede ver como se avanza por distintas fases y se llaman a los procesos:

  public synchronized void continueStage()
    {
        try
        {
            switch (_stage)
            {
                case 0: // Inicialiamos el workspace
                    new ProcessExecuter("cmd /c git init", _root).start();
                    break;

                case 1: // Config workspace
                    new ProcessExecuter("cmd /c git config --global user.email = \"ouro_17@hotmail.com\"", _root).start();
                    break;

                case 2: // Config workspace
                    new ProcessExecuter("cmd /c git config --global user.name \"Antonio Vidal Carrasco\"", _root).start();
                    break;

                case 3: // Inicializamos el repositorio
                    new ProcessExecuter("cmd /c git --bare init", _dirGit).start();
                    break;

                case 4: // Configuramos

                    if (_okLecture != null && _okLecture.contains("Reinitialized existing shared Git repository in"))
                    {
                        // Ya esta configurado, saltamos al 6
                        _stage = 6;
                        continueStage();
                    }
                    else
                    {
                        new ProcessExecuter("cmd /c git config core.sharedrepository 1", _dirGit).start();
                    }
                    break;

                case 5: // Siguiente config
                    new ProcessExecuter("cmd /c git config receive.denyNonFastforwards true", _dirGit).start();
                    break;

                case 6: // Ligamos con el repositorio(puede que ya lo esté, ignoramos el error)
                    new ProcessExecuter("cmd /c git remote add origin " + _dirGit, _root).start();

                    break;

                case 7:
                {
                    File file = new File(_root + "\\.git\\index.lock");
                    if (file.exists())
                    {
                        file.delete();
                    }
                }
                _stage = 8;
                continueStage();
                break;

                case 8: // Estado durmiente. Vendremos aquí cada vez que no haya que hacer nada.
                {
                    File file = new File(_root + "\\.git\\index.lock");
                    if (file.exists())
                    {
                        file.delete();
                    }
                }
                TreePath parent = jTree1.getPathForRow(0);

                Enumeration paths = jTree1.getExpandedDescendants(parent);

                _fileBrowser = new FileBrowser(_root);
                jTree1.setModel(_fileBrowser);

                while (paths.hasMoreElements())
                {
                    jTree1.expandPath(paths.nextElement());
                }

                break;

                case 9: // Add
                    for (String s : _listClicked)
                    {
                        new ProcessExecuter("cmd /c git add \"" + s + "\"", _root).start();
                    }
                    _adding = true;

                    break;

                case 10: // Respuesta de add
                    _stage = 8;
                    continueStage();
                    break;

                case 11: // Commit
                {
                    File file = new File(_root + "\\.git\\index.lock");
                    if (file.exists())
                    {
                        file.delete();
                    }
                }
                VentanaCommit.main(this);
                break;
                case 12: // Respuesta de commit
                    _stage = 8;
                    continueStage();
                    break;

                case 13: // Push
                    new ProcessExecuter("cmd /c git push origin master", _root).start();
                    _commiting = false;
                    break;

                case 14: // Respuesta push
                    _stage = 8;
                    continueStage();
                    break;

                case 15: // Pull
                    new ProcessExecuter("cmd /c git pull origin master", _root).start();
                    break;

                case 16: // Respuesta de pull
                    _stage = 8;
                    continueStage();
                    break;

                case 17: // descartar
                    new ProcessExecuter("cmd /c git checkout -- .", _root).start();
                    break;

                case 18: // Respuesta de descartar
                    _stage = 8;
                    continueStage();
                    break;

                case 19: // Clone: NO FUNCIONA BIEN NO USAR
                    if (!isGitDirectory())
                    {
                        VentanaConfirmacion.main(_ventana, 0);
                    }
                    new ProcessExecuter("cmd /c git clone " + _dirGit, _root).start();

                    break;

                case 20: // Respuesta de clone
                    _stage = 8;
                    continueStage();
                    break;

                case 21: // Continue commit all
                {
                    File file = new File(_root + "\\.git\\index.lock");
                    if (file.exists())
                    {
                        file.delete();
                    }
                }
                new ProcessExecuter("cmd /c git commit -a -m \"" + _mensaje + "\"", _root).start();
                break;
                case 22: // Respuesta al commit all
                    _stage = 8;
                    continueStage();
                    break;

                default:
                    _stage = 8;
                    continueStage();
            }
        }
        catch (NullPointerException e)
        {
        }
    }

Acciones y más acciones

Hoy nos vamos a ocupar de las “Action” de Java aplicadas a Swing.
Las Action nos ayudan a realizar, valga la redundancia, acciones sobre distintos elementos Swing. Vamos a concentrarnos en entrada de teclado.

Primer ejemplo, lectura del carácter ESCAPE para cerrar nuestra aplicación.

Para empezar deberíamos tener la ventana principal(el JFrame sobre el que trabajamos) en un SingletonHolder.

private static VentanaPrincipal _ventana;
public static VentanaPrincipal getInstance(){ return _ventana;}

De esta manera podremos acceder a él más adelante.

Ahora vamos a asignar 2 valores al panel principal de nuestra ventana. En el constructor de nuestra clase deberíamos hacer:

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).
                put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "CERRAR");
        getRootPane().getActionMap().put("CERRAR", new AbstractAction()
        {
            @Override
            public void actionPerformed(ActionEvent ae)
            {
                _ventana.dispose();
            }
        });

Esto hará que cuando pulsemos ESCAPE se cierre la ventana. Notar que el 0 en

put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)

sirve para controlar el pulsado de teclas como ALT, CONTROL y SHIFT

Ahora voy a exponer otro ejemplo, abrir otra ventana con la combinación Control + K:

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).
                put(KeyStroke.getKeyStroke(KeyEvent.VK_K,
                    KeyEvent.CTRL_DOWN_MASK), "KONSOLE");
        getRootPane().getActionMap().put("KONSOLE", new AbstractAction()
        {
            @Override
            public void actionPerformed(ActionEvent ae)
            {
                _ventanaKonsole.setVisible(!_ventanaKonsole.isVisible());

            }
        });

Ahora siempre que pulsemos la combinación de teclas la nueva ventana se abrirá o cerrará.

¿Qué más cosas podemos hacer con las Action? Podemos usarla como Binding Keys.
Para ello tenemos que tener creado un boton:

Button jb1 = new JButton();

Una vez que lo hemos creado vamos a crear y asociar una acción.
Primero creamos una clase que implemente nuestra acción:

    private class addAction
            extends AbstractAction
    {
        public addAction(String text, String desc, Integer mnemonic)
        {
            super(text);
            putValue(SHORT_DESCRIPTION, desc);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        public void actionPerformed(ActionEvent e)
        {
            System.out.println("Add action is running");
        }
    }

Se puede observar que le añadido más cosas que antes a esta nueva Action. Le he añadido una regla mnemotécnica. Esto lo que hará será que podamos “pulsar” el botón usando la combianción “ALT + TECLA”, además de subrayar la letra asociada a la tecla si esta diponible(ahora entederás esto).

Por último creamos y asociamos la Action:

 addAction _addAction = new addAction("Add",
               "Marca un archivo para ser subido al repositorio.", new Integer(KeyEvent.VK_A));
jb1.addAction(_addAction);

Si os fijais la letra “A” será la tecla para la combinación “ALT + TECLA” antes mencionada, por lo que en nuestro botón la letra A estará subrayada.
También podemos crear la Action primero y luego usar el constructor de JButton para asociarlo:

JButton jb1 = new JButton(_addAction);

Espero que os haya servido de ayuda.