Etiqueta: Java (página 2 de 2)

Descomprimir fichero zip en Android

En ocasiones, necesitaremos descomprimir un archivo zip en Android para evitar la descarga de muchos archivos pequeños (como imágenes, o pequeños ficheros de texto), agrupándolos en uno solo.

Adjunto una clase heredada desde AsyncTask para mostrar un dialogo modal de espera mientras se ejecuta la descompresión. Esta funciona cargando en un buffer de 2MB para evitar una llamada constante al Garbage Collector mientras se escriben bytes individuales, lo que acelera INCREÍBLEMENTE el tiempo de descompresión.

package com.descompresor;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

class DescomprimeArchivos extends AsyncTask {

	ProgressDialog mProgresoDescompresion;
	String _Ubicacion_ZIP;
	String _Destino_Descompresion;
	boolean _Mantener_ZIP;

	/**
	 * Descomprime un archivo .ZIP
	 * @param ctx Contexto de la Aplicación Android
	 * @param Ubicacion_ZIP Ruta ABSOLUTA de un archivo .zip
	 * @param Destino_Descompresion Ruta ABSOLUTA del destino de la descompresión. Finalizar con /
	 * @param Mantener_ZIP Indica si se debe mantener el archivo ZIP despues de descomprimir
	 */
	public DescomprimeArchivos(Context ctx, String Ubicacion, String Destino, boolean Mantener)
	{
		mProgresoDescompresion = new ProgressDialog(ctx);
		mProgresoDescompresion.setTitle("Descomprimiendo...");
		mProgresoDescompresion.setMessage("Por favor espere...");
		mProgresoDescompresion.setIndeterminate(false);
		mProgresoDescompresion.setProgress(100);
		mProgresoDescompresion.setCancelable(true);
		mProgresoDescompresion.setProgressStyle(ProgressDialog.STYLE_SPINNER);

		_Ubicacion_ZIP = Ubicacion;
		_Destino_Descompresion = Destino;
		_Mantener_ZIP = Mantener;
	}

	@Override
	protected String doInBackground(String... params) {
		int size;
		byte[] buffer = new byte[2048];

		new File(_Destino_Descompresion).mkdirs(); //Crea la ruta de descompresion si no existe

		try {
			try {
				FileInputStream lector_archivo = new FileInputStream(_Ubicacion_ZIP);
				ZipInputStream lector_zip = new ZipInputStream(lector_archivo);
				ZipEntry item_zip = null;

				while ((item_zip = lector_zip.getNextEntry()) != null) {
					Log.v("Descompresor", "Descomprimiendo " + item_zip.getName());

					if (item_zip.isDirectory()) { //Si el elemento es un directorio, crearlo
						Crea_Carpetas(item_zip.getName(), _Destino_Descompresion);
					} else {
						FileOutputStream outStream = new FileOutputStream(
								_Destino_Descompresion + item_zip.getName());
						BufferedOutputStream bufferOut = new BufferedOutputStream(
								outStream, buffer.length);

						while ((size = lector_zip.read(buffer, 0, buffer.length)) != -1) {
							bufferOut.write(buffer, 0, size);
						}

						bufferOut.flush();
						bufferOut.close();
					}

				}
				lector_zip.close();
				lector_archivo.close();

				if(!_Mantener_ZIP)
					new File(_Ubicacion_ZIP).delete();

			} catch (Exception e) {
				Log.e("Descompresor", "Descomprimir", e);
			}
			mProgresoDescompresion.dismiss();
		} catch (Exception e) {
			Log.e("Error", e.getMessage());
		}
		return null;
	}

	@Override
	protected void onPreExecute() {
		super.onPreExecute();
		mProgresoDescompresion.show();
	}

	@Override
	protected void onProgressUpdate(Integer... progress) {
		super.onProgressUpdate(progress);
		mProgresoDescompresion.setProgress(progress[0]);
	}

	private void Crea_Carpetas(String dir, String location) {
		File f = new File(location + dir);

		if (!f.isDirectory()) {
			f.mkdirs();
		}
	}

}

Instanciamos el objeto. Notar que grabaremos en el cache de la aplicación Android y no en la tarjeta SD o la memoria interna.

final DescomprimeArchivos descompresor = 
new DescomprimeArchivos(this, getApplicationContext().getCacheDir().toString() + 
"/archivo.zip", getApplicationContext().getCacheDir().toString() + "/descomprimir/", false);

Para utilizar esta clase, y basándonos en su derivación de AsyncTask podemos invocar al método público y estático excecute. Este método recibe parámetros de longitud variable (es decir, n argumentos), pero para este caso, no necesitamos ninguno. Como inicializamos con el contexto de Android previamente, podemos llamar a este método directamente desde el evento de un control. Para este ejemplo, invocamos el método directamente desde el evento OnClick de un Button.

Button btn_desc = (Button) findViewById(R.id.btn_desc);
		btn_desc.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				descompresor.execute();
			}
		});

La Excepción Aritmética de BigDecimal en Java

Una de las más frecuentes excepciones disparadas por el uso de la clase BigDecimal en Java (java.Math.BigDecimal) es la famosa:

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(Unknown Source)
at com.pruebaBigDecimal.main.main(main.java:13)

Esta excepción suele ocurrir al querer utilizar el método divide con otro BigDecimal como muestra este bloque de código:

BigDecimal A, B;
A = new BigDecimal(4);
B = new BigDecimal(3);

BigDecimal C = A.divide(B); //Excepcion
System.out.println(C.doubleValue());

Esta excepción aritmética (ArithmeticException) ocurre debido a que el método divide es incapaz de manejar una división que de valores periódicos.

En este caso, estamos dividiendo 4/3, que nos entrega un valor de decimales más alto de los que la clase puede manejar de manera nativa. Es por esto que se envía una excepción que nos dice que “no hay un decimal exacto que represente la división”.

Para muestra, si ejecutamos lo siguiente, no habrá problema:

BigDecimal A, B;
A = new BigDecimal(3);
B = new BigDecimal(2);

BigDecimal C = A.divide(B); //Ya no ocurre la Excepción
System.out.println(C.doubleValue());

La consola imprimirá correctamente el valor 1.5 y no lanzara una excepción como en el caso anterior, ya que los decimales obtenidos como resultado de la operación anterior, podían ser controlados por la clase.

Entonces surge la pregunta, ¿y como dividimos dos BigDecimal sin perder la precisión de los decimales?

La siguiente función realiza lo solicitado:

public static BigDecimal divisionControlada(BigDecimal A, BigDecimal B)
	{
		try
		{
			return A.divide(B);
		}
		catch (Exception e)
		{
			return new BigDecimal(A.doubleValue() / B.doubleValue());
		}
	}

Lo que hacemos aquí es bastante simple. Tratamos de obtener el valor primero a través del método divide, y si este falla lanzando la excepción aritmética, procedemos a devolver los valores en tipo de datos doublé (que es primitivo, por lo que puede manejar sin problema los decimales que aguante la maquina virtual de Java), con los que se realiza la operación y después se instancia un nuevo objeto BigDecimal con su constructor que recibe un doublé.

Por cierto, alguno podría pensar que “mejor trabajamos directamente con los doublé“. El siguiente código será explicativo:

BigDecimal A, B;
A = new BigDecimal(4);
B = new BigDecimal(3);

System.out.println("Objeto BigDecimal: " + new BigDecimal(A.doubleValue() / B.doubleValue()));
System.out.println("Trabajando directamente con doubles: " + (A.doubleValue() / B.doubleValue()));

Resultado:

Objeto BigDecimal:                   1.3333333333333332593184650249895639717578887939453125
Trabajando directamente con doubles: 1.3333333333333333

Aquí podríamos empezar a preocuparnos. El objeto BigDecimal tiene muchísimos más decimales provenientes desde la misma división de doubles. Entonces, ¿cual es el truco?

En realidad, no hay truco. Lo que ocurre es que print y println para Java consideran un máximo de 18 caracteres para su representación de salida, y mas allá de eso, aproxima.

BigDecimal A, B;
A = new BigDecimal(4);
B = new BigDecimal(3);

System.out.println("Con Objeto BigDecimal: " + new BigDecimal(A.doubleValue() / B.doubleValue()));
Double resultado = A.doubleValue() / B.doubleValue();
System.out.println("Construyendo objeto con un double: " + new BigDecimal(resultado));
System.out.println("Trabajando directamente con doubles: " + (A.doubleValue() / B.doubleValue()));

Resultado:

Objeto BigDecimal:                    1.3333333333333332593184650249895639717578887939453125
Construyendo objeto con un double:    1.3333333333333332593184650249895639717578887939453125
Trabajando directamente con doubles:  1.3333333333333333

Por lo tanto, si obtenemos el valor de la división por doubles en otra variable del mismo tipo, podemos estar seguros de que los valores decimales están ahí para cualquier operación, pero al mostrarlos, debemos ser bastante inteligentes, ya que por ejemplo, el dinero no se aproxima a no ser que la lógica de negocio diga explícitamente lo contrario.

Operador Ternario “?” en C/C++

Es común encontrar esta sentencia en C/C++ en códigos fuente algo extensos:

x = 1;
x = (x == 1) ? 0:1;

Esto se llama “Operación Ternaria”, y es un método bastante popular de abreviar del clásico if...else...if que podemos encontrar en prácticamente todos los lenguajes de programación de alto nivel y que se ha implementado en otros lenguajes de programación como JavaC#.

Se trata de tener 3 expresiones que se escriben de esta forma:

E1 ? E2:E3;

E1: La condición que íria como argumento de un bloque if.
E2: En caso que E1 sea true.
E3: En caso que E1 sea false.

Si lo convertimos en nuestros bloques comunes queda algo como:

if (E1) {E2;} else {E3;}

Como ven, es una forma bastante abstracta de escribir lo mismo y que sirve principalmente para condicionar de manera rápida una expresión, por ejemplo:

for (i = 0; i != n; i++)
printf ("%6d%c", a[i], (i%10 == 9 || i == n-1) ? '\n' : ' ');

o mas útil aún:

printf ("Tienes %d item%s.\n", n, n == 1 ? "" : "s");

Espero que eso resuelva algunas dudas, el uso de este método de abstracción es idéntico en Java.

Validar XML a través de un XSD en Java

Para un proyecto en Grails (con Java), se me solicito crear alguna forma de validar un documento XML generado por nuestra aplicación, con un XSD entregado por el cliente. Esta validación, pese a no ser decisiva para el proyecto, si se consideraba una ventaja comparativa frente a la competencia, por lo que hice lo que siempre hago cuando no sé por dónde partir: ir a google.

Encontré muchas respuestas pero como suele ocurrir cuando se busca algo demasiado especifico, ninguna fue realmente satisfactoria, así que creé una clase que hiciera todo lo necesario.

El archivo .jar lo pueden descargar desde aquí.

El código fuente lo pueden descargar desde aquí.

Imports necesarios

import validadorxml.*;

Nombre de la clase

ValidadorXML

Constructores

Constructor estándar

public ValidadorXML();

Constructor con Variables a definir

  1. JAXP_Lenguaje Lenguaje de XSD
  2. AXP_Fuente Fuente del XSD Matriz
  3. WC3_SCHEMA Fuente del XSD Matriz de WC3
public ValidadorXML(String JAXP_Lenguaje, String JAXP_Fuente, String WC3_SCHEMA)

Métodos

Valida un XML contra un archivo XSD

  1. ruta_XSD Ruta donde se encuentra el archivo XSD (Schema)
  2. Contenido_XML Contenido (En UTF-8) del XML
  3. TextoOArchivo true si es texto UTF-8, false si es una Ruta
public void validarXMLconXSD(String ruta_XSD, String XML_INPUT, boolean TextoOArchivo)

Obtiene un StringBuilder con todos los errores encontrados de la última validación

public ArrayList obtener_errores()

Retorna si el XML es válido respecto al XSD de acuerdo a la última validación

public boolean es_valido()

Funcionamiento

//Se declara el objeto que validará XMLs
ValidadorXML validador = new ValidadorXML();
//xml un texto pasado en una variable
validador.validarXMLconXSD("schema.xsd", xml, true);
//Se envía la dirección de un archivo xml
validador.validarXMLconXSD("C:/schema.xsd", "C:/documento.xml", false);
//Obtenemos los errores encontrados alguna de las validaciones precedentes
ArrayList lista_errores = validador.obtener_errores();