Patrones de Diseño: Builder Pattern

El patrón builder está en la categoría en los patrones de diseño de creación (object creation) y según GoF (Gang of Four) el patrón Builder es usando para “Separar o abstraer la construcción de un objeto complejo de su representación”.

Veamos un ejemplo práctico, haremos un pizza builder paso a paso

Paso 1: Crear el Objeto a Construir - Pizza

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Pizza {

private final List<Topping> _toppings;

private final BreadType _breadType;

private final Souce _souce;

public Pizza(List<Topping> toppings, BreadType breadType, Souce souce) {
super();
_toppings = toppings;
_breadType = breadType;
_souce = souce;
}

public final List<Topping> getToppings() {
return _toppings;
}

public final BreadType getBreadType() {
return _breadType;
}

public final Souce getSouce() {
return _souce;
}

// Algunas enums para los ingredientes
public enum Topping {
PARMESAN_CHEESE, MOZZARELLA_CHEESE,
FETA_CHEESE, MUSHROOMS,
PESTO, PEPPERONI,
ONIONS, HAM;
}

// Algunas enums para salsas
public enum Souce {
TOMATO, CREAM;
}

// Algunas enums para tipos de masa
public enum BreadType {
FLAT_BREAD, PIZZA_PAN;
}
}

Paso 2: Crear el Builder de Pizza

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final class PizzaBuilder {

// DEFAULT VALUES
private List<Topping> _toppings = new ArrayList<Topping>();

private BreadType _breadType = BreadType.PIZZA_PAN;

private Souce _souce = Souce.TOMATO;

public PizzaBuilder withTopping(Topping topping) {
_toppings.add(topping);
return this;
}

public PizzaBuilder withBread(BreadType breadType) {
_breadType = breadType;
return this;
}

public PizzaBuilder withSouce(Souce souce) {
_souce = souce;
return this;
}

public Pizza buildPizza() {
return new Pizza(_toppings, _breadType, _souce);
}
}

Paso 3: Usar el Builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class PizzaBuilderTest {

@Test
public void testPizzaBuilder() {
PizzaBuilder pizzaBuilder = new PizzaBuilder();
pizzaBuilder.withBread(BreadType.FLAT_BREAD).withSouce(Souce.TOMATO)
.withTopping(Topping.HAM)
.withTopping(Topping.MOZZARELLA_CHEESE)
.withTopping(Topping.ONIONS);

Pizza pizzaWithoutPepperoni = pizzaBuilder.buildPizza();

System.out.println("-------- Pizza Without Pepperoni ------");
showBuildedPizza(pizzaWithoutPepperoni);

Pizza pizzaWithPepperoni = pizzaBuilder.withTopping(Topping.PEPPERONI)
.buildPizza();

System.out.println("-------- Pizza With Pepperoni ------");
showBuildedPizza(pizzaWithPepperoni);
}

private void showBuildedPizza(Pizza pizza) {
System.out.println(pizza.getBreadType());
System.out.println(pizza.getSouce());

for (Topping topping : pizza.getToppings()) {
System.out.println("Topping [" + topping + "]");
}
}
}

Como podrán apreciar, la instancia de pizzaBuilder sirve para crear mas de un tipo de pizza sin perder los atributos ya creados (Ver líneas 11 y 16). La única diferencia entre las dos instancias de pizza, es que a la segunda se le agregó pepperoni.

Algunas características del patrón builder:

  • Contiene valores por omisión (defaults): Es altamente recomendable que el Builder contenga valores por omisión (en lo posible), de no ser así, controlar cuando se construye la instancia del Objeto para que no existan problemas de integridad. En este caso los valores por omisión son Salsa de Tomates, Masa tipo Pan Pizza y sin ingredientes.
  • No contiene atributos con el modificador final: Por diseño los atributos de un builder no pueden ser final ya que van asignando a medida que se construye el objeto. Por lo tanto los builders en general son Not thread-safe. Al usarlos se debe tener conciencia que los datos almacenados en el builder se mantienen y se pueden crear múltiples instancias del Objeto a Construir (Pizza) pero siempre con los datos que se mantienen en el builder (es una ventaja y desventaja a la vez, según el caso). Se recomienda crear nuevas instancias del builder para cada caso.
  • El orden en que se ejecutan los métodos no es determinante: El orden en que se ejecutan los métodos de un builder no modifican la instancia del Objeto a Construir (siempre y cuando contengan los mismos atributos). Por ejemplo, en el caso del PizzaBuilder, si se ejecuta withBread y withSouce en distinto orden, no implica que la instancia de Pizza cambie. Podemos sobre-escribir el método equals() y al realizar la comparación en base a los ingredientes, tipo de masa y salsa, al ejecutar dicho método debería retornar siempre true.
1
2
3
4
5
6
7
8
9
PizzaBuilder builder1 = new PizzaBuilder()
builder.withBread(BreadType.FLAT_BREAD).withSouce(Souce.TOMATO);
pizza1 = builder.buildPizza();

PizzaBuilder builder2 = new PizzaBuilder()
builder.withSouce(Souce.TOMATO).withBread(BreadType.FLAT_BREAD);
pizza2 = builder.buildPizza();

pizza1.equals(pizza2); // -----> esto debería ser true
  • Es fácil de leer

No hay mucho que decir al respecto, comparen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Con Builder
Pizza pizza = new PizzaBuilder()
.withBread(BreadType.FLAT_BREAD)
.withSouce(Souce.TOMATO)
.withTopping(Topping.HAM)
.withTopping(Topping.MOZZARELLA_CHEESE)
.buildPizza();

// Sin builder
List<Topping> toppings = new ArrayList<Topping>();
toppings.add(Topping.HAM);
toppings.add(Topping.MOZZARELLA_CHEESE);

Pizza pizza2 = new Pizza(toppings, BreadType.FLAT_BREAD, Souce.TOMATO);

Más información en:

La simplicidad de Apache Jexl

Este artículo puede ser útil para todos aquellos que alguna vez tuvieron que hacer cientos o miles de líneas de código usando reflexión para poder acceder a atributos, navegar mapas y recorrer listas en Java. Ahora bien si le agregamos que esas listas o mapas pueden tener más de lo mismo o más complejo aún, objetos que contienen listas o mapas.

Aquí Apache Jexl hace bien su trabajo utilizando un lenguaje cómodo para poder acceder a esos recónditos lugares.

Apache Jexl (Java EXpresion Language) fue inspirado por Apache Velocity y por la definición de Expresion Language para JSTL y JSP 2.0. Ademas en la versión 2.0 se agregaron funcionalidades de Unified EL.

Un pequeño ejemplo para que lo véan en acción:

1
2
3
4
5
6
7
8
public class TestDTO {

private String _name;

private Map<String, Object> _dataBag;

/** Se omiten los setter y getter **/
}

Esta es la definición de un DTO que tiene dos atributos, uno de ellos es un String y otro un Map cuya clave siempre será String y su valor puede ser un Object.

La problemática es obtener un valor desde ese DTO mediante reflexión, es decir, el ente que ejecuta la extracción del parámetro no conoce la definición a priori de esa clase. Es aquí donde entra Jexl y mediante un simple lenguaje le podemos indicar hasta donde acceder. A ese cómo y qué le llamaremos Expresión.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JexlEvaluator {

private static final JexlEngine jexl = new JexlEngine();

static {
jexl.setCache(512);
jexl.setLenient(false);
jexl.setSilent(false);
}

public Object getValue(String expresion, Object object) {
Expression e = jexl.createExpression(expresion);
JexlContext context = new MapContext();
context.set("fua", object);
return e.evaluate(context);
}
}

Esta simple clase en su bloque estático configura Apache Jexl para luego ser utilizado en el método getValue.

El método getValue recibe como parámetros, la expresión y el objeto a evaluar. Dentro del método se crea el contexto de Apache Jexl (muy similar a un HashMap) al cual se le pasa el Objeto a inspecionar y luego evalúa la expresión.

Aquí les dejo el test unitario y la salida:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class JexlEvaluatorTest {

private static Log logger = LogFactory.getLog(JexlEvaluatorTest.class);

@Test
public void testJexl() {
JexlEvaluator je = new JexlEvaluator();

/* Expresión a evaluar */
String expresion = "fua.dataBag.hola2";

Map<String, Object> map = new HashMap<String, Object>();
map.put("hola", "Hola 1 del MAP");

Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("hola2", "Hola 2 del MAP");

map.put("hola2", map2);

TestDTO testDTO = new TestDTO();
testDTO.setDataBag(map2);
testDTO.setName("DTO Interesante");

Object result = je.getValue(expresion, testDTO);

logger.info(result.toString());
}
}

La ejecución de ese test debería retornar lo siguiente:

1
[29/08/11 09:08:16:016 CLT] [ INFO] [JexlEvaluatorTest:44] - Hola 2 del MAP

Más adelante espero poder escribir sobre Spring EL, otra solución para evaluación de expresiones para Java.

Junit, ServletContextAware y MockServletContext

Se me generó el siguiente problema al hacer un test unitario sobre una clase que implementa la interfaz de Spring Framework ServletContextAware.

1
2
3
4
5
public interface ServletContextAware {

void setServletContext(ServletContext servletContext);

}

Mediante esta interfaz puedes inyectar el servletContext a la configuración de tu Bean, siempre y cuando tengas un contexto web funcionando.

He aquí el segundo problema, ¿ Cómo puedo tener el un contexto web dentro de un Junit ?. Puede que exista una mejor forma de hacerlo pero lo logré de la siguiente manera.

1
2
3
4
5
6
7
8
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemalocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd" xmlns:p="http://www.springframework.org/schema/p">

<bean class="org.springframework.mock.web.MockServletContext" />

</beans>

Con esto y usando la anotación Autowired funciona de pelos la inyección del ServletContext.

1
2
3
4
5
6
7
public class ReadFileFromWebinf {

@Autowired
private ServletContext _servletContext;

/*Se omite el resto de los métodos*/
}

Pero!!! cuando quise utilizar la interfaz ServletContextAware no funcionó y me quede con una propiedad en null.

1
2
3
4
5
6
7
8
9
10
public class ReadFileFromWebinf implement ServletContextAware {

private ServletContext _servletContext;

public void setServletContext(ServletContext servletContext){
_servletContext=servletContext;
};

/*Se omite el resto de los métodos*/
}

Buscando por todos lados encontré que a mi test unitario le faltaba un par de configuraciones para la lectura del contexto Spring.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test/test-context.xml" }
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
public class ReadFileFromWebinfTest {

@Autowired
private ReadFileFromWebinf _readFileFromWebinf;


public void testReadFile(){
String fileContent = _readFileFromWebinf.load("archivo.xml");
System.out.println(fileContent);
}
}

Las dos primeras anotaciones son para decirle a Spring que se va a utilizar Junit para ejecutar la clase y cual o cuales son los contextos a cargar.

Luego viene la anotación que me arregló el día, TestExecutionListeners y el listener asociado para la completar las inyecciones de dependencia de todos los beans. (Antes con Autowired solo hacía algunos).

Espero les sirva, cualquier duda bienvenida sea.

Spring Framework - InitializingBean

InitializingBean

Esta interfaz de Spring Framework te permite una vez configurado el contexto, ejecutar el método afterPropertiesSet(), y como su nombre lo dice, lo ejecuta luego de ejecutar los métodos setter del bean (post configurar el bean).

1
2
3
4
5
public interface InitializingBean {

void afterPropertiesSet() throws Exception;

}

Algunas aplicaciones de esta interfaz:

  • Validar que los atributos de un Bean estén correctamente inicializados.
  • A partir de los atributos ya setteados, generar otra propiedad en tiempo de configuración. Por ejemplo, se configura el bean con dos atributos de tipo int y en tiempo de configuración quiero calcular la suma de ambos y dejarlo en otra propiedad. ¿ Qué gano con esto ? que esa propiedad no se calcula cada vez que hago el getter sino que ya esta previamente calculada.

Para el primer caso, recomiendo utilizar la clase Assert ya que provee métodos con los que puedes invalidar que el contexto Spring levante si hay errores en la configuración.

Les dejo un código de ejemplo para que le echen un vistazo (le agregué la interfaz al ejemplo del post anterior)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ExampleBean implements InitializingBean {

private Integer _randomValue;

private Integer _newValue;

public final Integer getRandomValue() {
return _randomValue;
}

public final Integer getNewValue() {
return _newValue;
}

public final void setRandomValue(Integer randomValue) {
_randomValue = randomValue;
}

public void afterPropertiesSet() throws Exception {
Assert.notNull(_randomValue, "randomValue properties cannot be null");
_newValue = _randomValue * 10;
}

}

Voy a explicar un poco y a groso modo el como funciona esta clase desde el punto de vista de Spring:

  • Se lee la configuración asociada a la clase ExampleBean
  • Se crea la instancia de la clase ExampleBean
  • Luego vía inyección se le settean los atributos, entre ellos randomValue
  • Luego de la creación del bean ExampleBean se ejecuta el método afterPropertiesSet(), en donde se realiza una validación del randomValue (este NO puede ser nulo, de serlo, el contexto de Spring no quedara operativo), si eso pasa OK, se calculará el atributo newValue y que será randomValue multipliado por 10.
  • Finalmente y si no hay errores en el contexto de Spring, se deja la instancia del bean ExampleBean en el contexto, es decir, el valor randomValue y newValue quedan listos para ser usados.

Este proceso se realiza sólo una vez (salvo que el scope no sea singleton) y es en el momento de la creación del contexto.

Demás esta decir que esta clase/bean (ExampleBean) debe estar configurada en el archivo del contexto de Spring o vía las anotaciones (metadata) provistas para eso.

¿Por que FactoryBean es útil?

En este post voy a tratar de explicar porque FactoryBean es increíblemente útil para alambrar (aplicar DI) aplicaciones hechas con Spring Framework.

La interfaz de FactoryBean dice lo siguiente:

1
2
3
4
5
6
7
8
public interface FactoryBean<T> {

T getObject() throws Exception;

Class getObjectType();

boolean isSingleton();
}

La utilidad de la implementación de esta interfaz y su posterior declaración como Bean dentro del contexto de la aplicación, es que puede ser usada (como su nombre lo indica) como Factory para inyectar algún valor en alguna propiedad de otro Bean.

¿Dónde y cómo funciona? Dentro del ciclo de vida de la carga del Contexto de spring, la ejecución de la implementación de esta interfaz, esta justo antes de hacer los setters de las propiedades de un Bean. El funcionamiento es simple, siempre se ejecutará el método getObject() que retornará la instancia del tipo T.

Veamos una implementación simple y una configuración de contexto de spring:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RandomNumberFactoryBean implements FactoryBean<integer> {

public Integer getObject() throws Exception {
return Integer.valueOf(new Random().nextInt());
}

public Class getObjectType() {
return Integer.class;
}

public boolean isSingleton() {
return true;
}

}

Esta simple implementación de FactoryBean retorna un Integer en el método getObject(), simplemente eso (obviamente en este método debes poner todo tu talento para resolver el valor que andas buscando, esto es un ejemplo simple).

El método getObjectType() es usado internamente por el framework para validar que el retorno de getObject() sea correcto o que este dentro de la jerarquía de clases, esto es en caso de que quieras restringir por tipo.

El último método isSingleton() es si quieres que esta clase (no importa la cantidad de declaraciones que tengas, siempre será la misma), es un tanto dificil de entender pero les sugiero que lean sobre el concepto de Singleton que tiene SpringFramework. En el ejemplo de configuración espero que les quede claro, de lo contrario, bienvenidas las preguntas.

Esta es la definición del Bean de ejemplo, en donde se hará uso del FactoryBean:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ExampleBean {

private Integer _randomValue;

public final Integer getRandomValue() {
return _randomValue;
}

public final void setRandomValue(Integer randomValue) {
_randomValue = randomValue;
}

}

Aquí las configuraciones de contexto de spring.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemalocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="exampleBean1" class="cl.pcollaog.factory.ExampleBean">
<property name="randomValue">
<bean class="cl.pcollaog.factory.RandomNumberFactoryBean" />
</property>
</bean>

<bean id="randomNumberFactoryBean" class="cl.pcollaog.factory.RandomNumberFactoryBean"/>

<bean id="exampleBean21" class="cl.pcollaog.factory.ExampleBean">
<property ref="randomNumberFactoryBean" name="randomValue"></property>
</bean>

<bean id="exampleBean22" class="cl.pcollaog.factory.ExampleBean">
<property ref="randomNumberFactoryBean" name="randomValue"></property>
</bean>

</beans>

Ejemplo 1:

Se declara el Bean exampleBean1 y que tiene como propiedad el atributo randomValue y cuyo valor será producto de la ejecución del Bean RandomNumberFactoryBean en el momento que el contexto de Spring es cargado. En este caso se utiliza la técnica de InnerBean, es decir, no se tiene una instancia del Bean para ser reutilizada sino que esta en la misma declaración del setter del atributo.

Ejemplo 2:

Este ejemplo hace lo mismo indicado arriba, sólo que no se aplica InnerBean sino que se extrae y es reutilizado en el bean exampleBean21 y exampleBean22. En este ejemplo entra en juego el método isSingleton(), si es true, quiere decir que el valor que entregará el FactoryBean siempre será el mismo para ambos Beans. Ahora bien, si se cambia a false, por cada setter, es decir, para exampleBean21 y exampleBean22 se realizara la ejecución de RandomNumberFactoryBean 2 veces. (En este caso, nos entregará dos valores aleatorios para cada bean)

En pocas palabras para cada Bean del ejemplo 2 habrá dos instancias de RandomNumberFactoryBean que entregará cada una un Integer para los atributos de los beans declarados.

Algo interesante de las implementaciones de FactoryBean es que luego de la carga del contexto, estas instancias son desechadas.

Espero les sea útil y cualquier pregunta sera bienvenida.

Java7: null-safe y null-default

Seguramente les ha pasado y mucho que tienen que llenarse de validaciones contra NullPointerException cuando los métodos retornan null, hay una propuesta para poder manejar este tipo de problemas. Veamos uno ejemplos:

Null-Default

Hoy en dia:

1
2
3
4
5
6
7
8
9
10
String maybeNull = metodoNull();

if (null == maybeNull) {
maybeNull="Ahora no es null";
}

// Otra Opcion

String nullVar = metodoNull();
nullVar = (nullVar != null ? nullVar : "Ahora no es null");

Ahora la propuesta es la siguiente:

1
2
String maybeNull = metodoMaybeNull();
maybeNull = maybeNull ?: "Ahora no es null";

Como verán nos ahorramos unas cuantas líneas de código. Ahora veamos el otro esquema

Null-safe

Hoy en dia:

1
2
3
4
5
6
7
8
String result = null;
Foo foo = getFooMayBeNull();
if (foo != null) {
Bar bar = foo.getBarMayBeNull();
if (bar != null) {
result = bar.getResult();
}
}

Con la propuesta de null-safe quedaría así:

1
String result = getFooMayBeNull()?.getBarMayBeNull()?.getResult();

Esto hasta el dia de hoy seguía en propuesta y por lo que veo en las Características de Java7 no viene, así que a esperar. (Estoy bajando el jdk7 para probar, luego updates)

Estas modificaciones al lenguaje (sintaxis) vienen de los operadores que tiene Goovy.

Más información sobre esta propuesta de mejora al lenguaje en:

Algunas APIs Java que te pueden servir: commons-io

commons-io trae un montón de clases que te pueden servir al momento de utilizar lecturas y escrituras de archivos, entre otras cosillas.

Por si no lo sabías una de las malas practicas mas recurrentes programando con java es, no cerrar los Stream y ciertos Readers adecuadamente, o te complicas mucho con los try/catch, vamos a ver un par de ejemplos clásicos:

Primero agregamos la dependencia de maven, ojo que las versiones 2.x están escritas para Java 1.5 y las 1.x para java 1.3 y 1.4:

1
2
3
4
5
<dependency>
<groupid>commons-io</groupid>
<artifactid>commons-io</artifactid>
<version>2.0.1</version>
</dependency>

Caso 1: finally que asegura cerrar el Reader con el método close()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void closeWithFinally(String filename) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filename));
try {
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String contentFile = sb.toString();
System.out.println(contentFile);
} finally {
// Cerrar el reader
reader.close();
}
}

Caso 2: Creando la instancia de BufferedReader dentro del try y validando si es null al momento de cerrar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void closeWithFinallyNullCheck(String filename) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(filename));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String contentFile = sb.toString();
System.out.println(contentFile);
} finally {
// Cerrar el reader
if (null != reader) {
reader.close();
}
}
}

Caso 3: Usando commons-io y la clase IOUtils se reduce bastante el código y quedaría algo así:

1
2
3
4
5
6
7
8
9
public void closeWithFinallyCommonsIO(String filename) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filename));
try {
String contentFile = IOUtils.toString(reader);
System.out.println(contentFile);
} finally {
IOUtils.closeQuietly(reader);
}
}

Mas información en el maven site del proyecto:

Mas adelante escribiré un par de ejemplos con otras clases de commons-io que son bastante útiles.

Cómo iterar un Map con Java 1.5

A pedido del Sr: @perrefe les dejo un tip de performance para iterar sobre Map<K,V>:

Supongamos que nuestro Map<K,V> tiene como key un String y como value un Integer, declarado de esta forma:

1
Map<String,Integer> map = new HashMap<String,Integer>();

Ahora podemos iterar el Map de la siguiente forma:

1
2
3
4
for (Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key [" + entry.getKey() + "]");
System.out.println("Value [" + entry.getValue() + "]");
}

Si ve por ahí algún iterador sobre un Map<K,V> al cual le sacan primero la lista de keys y después iteran el Map para sacar el value, no pierda tiempo escribiendo código de más y haciendo que su aplicación ande mas lenta.

Por favor no haga esto!!!

1
2
3
4
5
6
7
Set<string> keys = map.keySet();

for (String key : keys) {
Integer value = map.get(key);
System.out.println("Key [" + key + "]");
System.out.println("Value [" + value + "]");
}

Espero les sirva.

Vala, Gtk, Pino y Twitter

Últimamente en mis ratos de ocio he estado aprendiendo un lenguaje nuevo llamado Vala.

Mi desafío para aprender a hacer algo en vala + gtk fue agregar una funcionalidad al cliente de twitter Pino, que dicho sea de paso esta bastante bueno. La funcionalidad es bastante simple, agregar un nuevo proveedor para acortar URLs bit.ly

Complejidades, bondades y rarezas que me encontré:

  1. Facilidad para enganchar las señales.
  2. Lo parecido a Java del lenguaje, lo que me ayudo mucho, aquí una ayuda para los Java Developers
  3. Aún le falta mucho a los IDEs (valide esta muy verde).
  4. El compilador tiene poca ayuda, sólo dice que fallo pero no en donde ni por qué, rara vez te dice te falta un “;”
  5. Aun son enredado los script de compilación, al más puro estilo de macros m4, automake y vainas por el estilo, aquí hay mucho por mejorar.
  6. La API si bien es cierto esta bien documentada en valadoc aún prefiero la facilidad para encontrar las cosas del javadoc

Les dejo aquí mis mejoras de pino-twitter-2.0.5.tar y a ver si lo van mejorando un poco más, por lo pronto voy a subir los cambios al tracker para que los incluyan en la release.

Para compilar es simple, obviamente deben tener todas las herramientas para compilar:

1
2
3
4
$ ./waf configure --prefix=/home/tu_user/directorio_destino
$ ./waf clean buid install
$ cd /home/tu_user/directorio_destino/bin
$ ./pino &

Espero sus comentarios

Pequeño script de ubiquity

Como todos sabrán, Ubiquity es un complemento para Mozilla Firefox que trata de acercar a la web el lenguaje natural, como por ejemplo, puedes decirle, google wikipedia y ubiquity realizara la búsqueda en Google con la palabra Wikipedia. Además tiene un pequeño recuadro de preview donde mostrara los posibles resultados de búsqueda (ver imagen):

Mas información acerca de Ubiquity en Firefox Chile.

Hoy visitando algunos sitios me encontré con que muchos de ellos publican las URL’s sin que sean un link, es decir, un texto simple al cual no se le puede hacer clic y visitar dicho link.

De ahí partió la necesidad de tener algo a la mano que me permita abrir estos pseudolinks, miré rápidamente ubiquity para ver si tenía un comando para este problema y me fue mal. Entonces me decidí a hacerlo yo mismo :D, les dejo el código para que lo enchulen mejoren a su medida o hagan sus contribuciones.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
* Open URL selected in browser.
*/
CmdUtils.CreateCommand({
names: ["openurl"],
icon: "http://www.mozilla.com/favicon.ico",
description: "Abre una pestaña con la url seleccionada",
help: "open + selected url",
author: {
name: "pcollaog",
email: "pcollaog[at]firefox[dot]cl"
},
license: "GPL",
homepage: "http://blog.pcollaog.cl/",
arguments: [{role: 'object', nountype: noun_arb_text}],

preview: function preview(pblock, args) {
var selectedText = args.object.text;

if (Utils.isEmpty(selectedText)){
pblock.innerHTML = "Debes seleccionar una URL.";
}else{
pblock.innerHTML = "Abrirás la siguiente URL en una nueva"
+ " pestaña:
<strong>" + selectedText + "</strong>";
}

},
execute: function execute(args) {
var selectedText = args.object.text;

// TODO: validar que la URL sea valida
if (Utils.isEmpty(selectedText)) {
return;
}

Utils.openUrlInBrowser(selectedText);
displayMessage("Se abrirá la siguiente URL: "
+ selectedText, this);
}
});

El código se divide en dos grandes partes, la función preview y la función execute, que como sus nombres lo dicen, una muestra una previsualización de la acción y la otra ejecuta la acción.

Analicemos primero la función preview:

Lo primero que hace es obtener desde el argumento la URL seleccionada asignándosela a la variable selectedText y luego valida que ésta variable no este vacía. En ambos casos (vacía o no) se le envía un mensaje al usuario en el cuadro de preview (en el cuadro de dialogo abajo).

Vamos ahora por la función execute:

Hace lo mismo que la función preview al principio, es decir, asigna el valor del argumento a una variable y valida que no este vacía.
Luego viene la parte interesante, toma la URL seleccionada y haciendo uso de la función Utils.openUrlInBrowser() nos permite abrirla en una nueva pestaña. Luego de eso mandamos una notificación al usuario sobre la acción ejecutada, para eso utilizamos la función displayMessage().

Eso seria por ahora, ahora a seguir jugando con Ubiquity. Espero sus comentarios y/o aportes.

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head>
<title>Ubiquity OpenURL Command</title>
<link rel="commands" href="http://site.example.cl/openurl_ubiquity.js" name="OpenURL command"/>
</head>
<body>
<p>This feed contains experimental commands that might later be included as built-in Ubiquity commands.</p>
</body>
</html>

Obviamente debes hacer las adaptaciones necesarias, como por ejemplo, las urls donde quedarán alojados los html/js.