Download 18 - Proyectos

Document related concepts
no text concepts found
Transcript
18: Proyectos
Este capítulo incluye un conjunto de proyectos que se construyen en el
material presentado en este libro o de otra manera no ajustada en capítulos
anteriores.
La mayor parte de estos proyectos son significativamente más complicados
que los ejemplos en el resto de libro, y a menudo demuestran usos y técnicas
nuevas de bibliotecas de clase.
Procesar texto
Si vienes de un C o C++ de fondo, podrías ser escéptico en primero del poder
de Java en lo que se refiere a manejar texto. Ciertamente, un inconveniente es
que la velocidad de ejecución es más lenta y eso podría entorpecer una cierta
cantidad de tus esfuerzos. Sin embargo, las herramientas (en detalle la clase
String) son muy poderosas, como los ejemplos en esta sección muestran (y
las mejoras de desempeño han sido prometidas para Java).
Como verás, estos ejemplos fueron creados para solucionar problemas que se
levantaron en la creación de este libro. Sin embargo, no están restringidos
para eso y las soluciones que le ofrecen fácilmente pueden ser adaptadas a
otras situaciones. Además, demuestran el poder de Java en un área que
previamente no ha sido enfatizado en este libro.
Extrayendo listados de código
Sin duda has notado que cada listado completo (no el fragmento de código) de
código en este libro comienza y finaliza con marcas especiales de etiqueta de
comentario '//:' y '///:~'. Esta meta información es incluida a fin de que el
código pueda ser automáticamente extraído del libro en archivos de código
fuente del compilable. En mi libro previo, tuve un sistema que me permitió
automáticamente incorporarse archivos probados de código en el libro. En este
libro, sin embargo, descubrí que fue a menudo más fácil pegar el código en el
libro una vez que estaba inicialmente probado y, ya que es difícil de poner lo
correcto la primera vez, para realizar ediciones para el código dentro del lib ro.
¿Pero cómo extraerlo y probar el código? Este programa es la respuesta, y
podría venir bien cuando te dispones a solucionar un problema de
procesamiento del texto. También demuestra muchas de las características de
clase String.
Primero guardo el libro entero en formato del texto de ASCII en un archivo
separado. El programa CodePackager tiene dos modos (el cual puedes ver
descrito en usageString ): Si usas la bandera -p, espera ver un archivo de
entrada conteniendo el texto de ASCII del libro. Pasará a través de este
archivo y usará las marcas de la etiqueta del comentario para extraer el
código, y usa el nombre de archivo en la primera línea para determinar el
nombre del archivo. Además, busca la declaración del paquete en el caso de
que necesita colocar el archivo en un directorio especial (escogido por la ruta
indicada por la declaración package).
Pero eso no es todo. También espera el cambio en los capítulos siguiéndole la
pista a los nombres del paquete. Ya que todos los paquetes para cada capítulo
empiezan con c02, c03, c04, etc. para indicar el capítulo donde pertenecen
(excepto por esos a partir de com, los cuales son ignorados con el objeto de
seguirle la pista a los capítulos), con tal de que el primer listado en cada
capítulo contenga una declaración package con el número de capítulo, el
programa CodePackager puede seguir la pista de cuando el capítulo cambió y
metió todos los subsiguientes archivos en e l subdirectorio nuevo chapter.
Como cada archivo es extraído, es colocado dentro de un objeto
SourceCodeFile que es luego colocado dentro de una colección. (Este proceso
estará descrito más a fondo después.) Estos objetos SourceCodeFile
simplemente podrían guardarse en archivos, pero eso nos trae para el segundo
uso para este proyecto. Si invocas a CodePackager sin la bandera -p espera
un archivo “empacado” como la entrada, que luego se extraerá en archivos
separados. Así la bandera -p quiere decir que los archivos extraídos serán
encontrados “empacados” en este único archivo.
¿Por qué la molestia con el archivo empacado? Porque las plataformas
diferentes de la computadora tienen formas diferentes de almacenar
información del texto en archivos. Un gran asunto es el caracter fin de línea o
caracteres, pero los otros asuntos también pueden existir. Sin embargo, Java
tiene un tipo de especial del flujo IO – el DataOutputStream – que promete
eso, a pesar de qué máquina los datos vienen, el almacenamiento de esa
información estará en una forma que puede ser correctamente recuperada por
cualquier otra máquina usando a un DataInputStream. Es decir, Java maneja
todos los detalles específicos en la plataforma, lo cual es una gran parte de la
promesa de Java. Así la bandera -p almacena todo en un único archivo en un
formato universal. Haces un download de este archivo y el programa Java de la
Web, y cuando corres CodePackager en este archivo sin la bandera -p los
archivos todos serán extraídos a lugares correctos en tu sistema. (Puedes
especificar un subdirectorio alterno; De otra manera los subdirectorios
solamente serán creados en el directorio actual.) Para asegurar que ningún
resto específico de sistema de formatos, los objetos File son usados en todas
partes como una ruta o un archivo es descrito. Además, hay una comprobación
de cordura: Un archivo vacío es colocado en cada subdirectorio; El nombre de
ese archivo indica cuántos archivos deberías encontrar en ese subdirectorio.
Aquí está el código, lo cual será relatado detalladamente al final del listado:
//: CodePackager.java
// "Packs" and "unpacks" the code in "Thinking
// in Java" for cross-platform distribution.
/* Commented so CodePackager sees it and starts
a new chapter directory, but so you don't
have to worry about the directory where this
program lives:
package c17;
*/
import java.util.*;
import java.io.*;
class Pr {
static void error(String e) {
System.err.println("ERROR: " + e);
System.exit(1);
}
}
class IO {
static BufferedReader disOpen(File f) {
BufferedReader in = null;
try {
in = new BufferedReader(
new FileReader(f));
} catch(IOException e) {
Pr.error("could not open " + f);
}
return in;
}
static BufferedReader disOpen(String fname) {
return disOpen(new File(fname));
}
static DataOutputStream dosOpen(File f) {
DataOutputStream in = null ;
try {
in = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(f)));
} catch(IOException e) {
Pr.error("could not open " + f);
}
return in;
}
static DataOutputStream dosOpen(String fname) {
return dosOpen(new File(fname));
}
static PrintWriter psOpen(File f) {
PrintWriter in = null ;
try {
in = new PrintWriter(
new BufferedWriter(
new FileWriter(f)));
} catch(IOException e) {
Pr.error("could not open " + f);
}
return in;
}
static PrintWriter psOpen(String fname) {
return psOpen(new File(fname));
}
static void close(Writer os) {
try {
os.close();
} catch(IOException e) {
Pr.error("closing " + os);
}
}
static void close(DataOutputStream os) {
try {
os.close();
} catch(IOException e) {
Pr.error("closing " + os);
}
}
static void close(Reader os) {
try {
os.close();
} catch(IOException e) {
Pr.error("closing " + os);
}
}
}
class SourceCodeFile {
public static final String
startMarker = "//:", // Start of source file
endMarker = "} ///:~" , // End of source
endMarker2 = "}; ///:~", // C++ file end
beginContinue = "} ///:Continued",
endContinue = "///:Continuing",
packMarker = "###", // Packed file header tag
eol = // Line separator on current system
System.getProperty( "line.separator"),
filesep = // System's file path separator
System.getProperty( "file.separator");
public static String copyright = "";
static {
try {
BufferedReader cr =
new BufferedReader(
new FileReader( "Copyright.txt" ));
String crin;
while((crin = cr.readLine()) != null)
copyright += crin + "\n";
cr.close();
} catch(Exception e) {
copyright = "";
}
}
private String filename, dirname,
contents = new String();
private static String chapter = "c02";
// The file name separator from the old system:
public static String oldsep;
public String toString() {
return dirname + filesep + filename;
}
// Constructor for parsing from document fi le:
public SourceCodeFile(String firstLine,
BufferedReader in) {
dirname = chapter;
// Skip past marker:
filename = firstLine.substring(
startMarker.length()).trim();
// Find space that terminates file name:
if(filename.indexOf(' ') != -1)
filename = filename.substring(
0, filename.indexOf(' '));
System.out.println("found: " + filename);
contents = firstLine + eol;
if(copyright.length() != 0)
contents += copyright + eol;
String s;
boolean foundEndMarker = false;
try {
while((s = in.readLine()) != null) {
if(s.startsWith(startMarker))
Pr.error("No end of file marker for " +
filename);
// For this program, no spaces before
// the "package" keyword are allowed
// in the input source code:
else if(s.startsWith("package")) {
// Extract package name:
String pdir = s.substring(
s.indexOf(' ')).trim();
pdir = pdir.substring(
0, pdir.indexOf(';')).trim();
// Capture the chapter from the package
// ignoring the 'com' subdirectories:
if(!pdir.startsWith( "com" )) {
int firstDot = pdir.indexOf('.');
if(firstDot != -1)
chapter =
pdir.substring(0,firstDot);
else
chapter = pdir;
}
// Convert package name to path name:
pdir = pdir.replace(
'.', filesep.charAt(0));
System.out.println("package " + pdir);
dirname = pdir;
}
contents += s + eol;
// Move past continuations:
if(s.startsWith(beginContinue))
while((s = in.readLine()) != null)
if(s.startsWith(endContinue)) {
contents += s + eol;
break;
}
// Watch for end of code listing:
if(s.startsWith(endMarker) ||
s.startsWith(endMarker2)) {
foundEndMarker = true;
break;
}
}
if(!foundEndMarker)
Pr.error(
"End marker not found before EOF");
System.out.println( "Chapter: " + chapter);
} catch(IOException e) {
Pr.error("Error reading line" );
}
}
// For recovering from a packed file:
public SourceCodeFile(BufferedReader pFile) {
try {
String s = pFile.readLine();
if(s == null) return;
if(!s.startsWith(packMarker))
Pr.error("Can't find " + packMarker
+ " in " + s);
s = s.substring(
packMarker.length()).trim();
dirname = s.substring(0, s.indexOf('#'));
filename = s.substring(s.indexOf('#') + 1);
dirname = dirname.replace(
oldsep.charAt(0), filesep.charAt(0));
filename = filename.replace(
oldsep.charAt(0), filesep.charAt(0));
System.out.println( "listing: " + dirname
+ filesep + filename);
while((s = pFile.readLine()) != null) {
// Watch for end of code listing:
if(s.startsWith(endMarker) ||
s.startsWith(endMarker2)) {
contents += s;
break;
}
contents += s + eol;
}
} catch(IOException e) {
System.err.println( "Error reading line" );
}
}
public boolean hasFile() {
return filename != null;
}
public String directory() { return dirname; }
public String filename() { return filename; }
public String contents() { return contents; }
// To write to a packed file:
public void writePacked(DataOutputStream out) {
try {
out.writeBytes(
packMarker + dirname + "#"
+ filename + eol);
out.writeBytes(contents);
} catch(IOException e) {
Pr.error("writing " + dirname +
filesep + filename);
}
}
// To generate the actual file:
public void writeFile(String rootpath) {
File path = new File(rootpath, dirname);
path.mkdirs();
PrintWriter p =
IO.psOpen(new File(path, filename));
p.print(contents);
IO.close(p);
}
}
class DirMap {
private Hashtable t = new Hashtable();
private String rootpath;
DirMap() {
rootpath = System.getProperty("user.dir");
}
DirMap(String alternateDir) {
rootpath = alternateDir;
}
public void add(SourceCodeFile f){
String path = f.directory();
if(!t.containsKey(path))
t.put(path, new Vector());
((Vector)t.get(path)).addElement(f);
}
public void writePackedFile(String fname) {
DataOutputStream packed = IO.dosOpen(fname);
try {
packed.writeBytes("###Old Separator:" +
SourceCodeFile.filesep + "###\n");
} catch(IOException e) {
Pr.error("Writing separator to " + fname);
}
Enumeration e = t.keys();
while(e.hasMoreElements()) {
String dir = (String)e.nextElement();
System.out.println(
"Writing directory " + dir);
Vector v = (Vector)t.get(dir);
for(int i = 0; i < v.size(); i++) {
SourceCodeFile f =
(SourceCodeFile)v.elementAt(i);
f.writePacked(packed);
}
}
IO.close(packed);
}
// Write all the files in their directories:
public void write() {
Enumeration e = t.keys();
while(e.hasMoreElements()) {
String dir = (String)e.nextElement();
Vector v = (Vector)t.get(dir);
for(int i = 0; i < v.size(); i++) {
SourceCodeFile f =
(SourceCodeFile)v.elementAt(i);
f.writeFile(rootpath);
}
// Add file indicating file quantity
// written to this directory as a check:
IO.close(IO.dosOpen(
new File(new File(rootpath, dir),
Integer.toString(v.size())+".files" )));
}
}
}
public class CodePackager {
private static final String usageString =
"usage: java CodePackager packedFileName" +
"\nExtracts source code files from packed \n" +
"version of Tjava. doc sources into " +
"directories off current directory\n" +
"java CodePackager packedFileName newDir\n" +
"Extracts into directories off newDir\n" +
"java CodePackager -p source.txt packedFile" +
"\nCreates packed version of source files" +
"\nfrom text version of Tjava.doc";
private static void usage() {
System.err.println(usageString);
System.exit(1);
}
public static void main(String[] args) {
if(args.length == 0) usage();
if(args[0].equals("-p")) {
if(args.length != 3)
usage();
createPackedFile(args);
}
else {
if(args.length > 2)
usage();
extractPackedFile(args);
}
}
private static String currentLine;
private static BufferedReader in;
private static DirMap dm;
private static void
createPackedFile(String[] args) {
dm = new DirMap();
in = IO.disOpen(args[1]);
try {
while((currentLine = in.readLine())
!= null) {
if(currentLine.startsWith(
SourceCodeFile.startMar ker)) {
dm.add(new SourceCodeFile(
currentLine, in));
}
else if(currentLine.startsWith(
SourceCodeFile.endMarker))
Pr.error("file has no start marker" );
// Else ignore the input line
}
} catch(IOException e) {
Pr.error("Error reading " + args[1]);
}
IO.close(in);
dm.writePackedFile(args[2]);
}
private static void
extractPackedFile(String[] args) {
if(args.length == 2) // Alternate directory
dm = new DirMap(args[1]);
else // Current directory
dm = new DirMap();
in = IO.disOpen(args[0]);
String s = null;
try {
s = in.readLine();
} catch(IOException e) {
Pr.error("Cannot read from " + in);
}
// Capture the separator used in the system
// that packed the file:
if(s.indexOf("###Old Separator:") != -1 ) {
String oldsep = s.substring(
"###Old Separator:".length());
oldsep = oldsep.substring(
0, oldsep. indexOf('#'));
SourceCodeFile.oldsep = oldsep;
}
SourceCodeFile sf = new SourceCodeFile(in);
while(sf.hasFile()) {
dm.add(sf);
sf = new SourceCodeFile(in);
}
dm.write();
}
} ///:~
Primero notarás la declaración package que se dejó comentado. Ya que éste
es el primer programa en el capítulo, la declaración package hay que decirle a
CodePackager que el capítulo ha cambiado, pero meterla en un paquete sería
un problema. Cuando creas un paquete, amarras el programa resultante a una
estructura particular del directorio, lo cual es bueno para muchos de los
ejemplos en este libro. Aquí, sin embargo, el programa CodePackager debe
ser compilado y ejecutado desde un directorio arbitrario, así es que la
declaración package se deja comentada. Todavía se parecerá a una
declaración común package para CodePackager, sin embargo, ya que el
programa no es lo suficientemente sofisticado para detectar comentarios
multilínea. (No tiene necesidad para tal sofisticación, un hecho que viene bien
aquí.)
Las primeras dos clases son clases del soporte/utilidad diseñadas para hacer el
resto de programa más consistente para escribir y más fácil para leer. La
primera parte, Pr, es similar a la biblioteca ANSI C perror, ya que imprime un
mensaje de error (pero sale del programa). La segunda categoría narra de
forma resumida la creación de archivos, un proceso que fue demostrado en el
Capítulo 12 como una que rápidamente se vuelve poco conciso y molesto. En
el Capítulo 12, la solución propuesta creó clases nuevas, pero aquí las
llamadas estáticas de método son usadas. Dentro de esos métodos las
excepciones apropiadas son percibidas y distribuidas. Estos métodos le hacen
al resto de código mucho más claro para leer.
La primera clase que ayuda a solucionar el problema es SourceCodeFile , que
represente toda la información (incluyendo el contenido, nombre de archivo, y
directorio) para un archivo de código fuente en el libro. También contiene un
conjunto de constantes String representando los marcadores que empiezan y
finalizan un archivo, un marcador usado dentro el archivo empacado, el
separador de fin de línea del sistema actual y el separador de la ruta del
archivo (note el uso de System.getProperty() para obtener la versión local),
y un aviso de derecho de autor, que es extraído del siguiente archivo
Copyright.txt.
//////////////////////////////////////////////////
// Copyright (c) Bruce Eckel, 1998
// Source code file from the book "Thinking in Java"
// All rights reserved EXCEPT as allowed by the
// following statements: You may freely use this file
// for your own work (personal or commercial),
// including modifications and distribution in
// executable form only. Permission is granted to use
// this file in classroom situations, including its
// use in presentation materials, as long as the book
// "Thinking in Java" is cited as the source.
// Except in classroom situations, you may not copy
// and distribute this code; instead, the sole
// distribution point is http://www.BruceEckel.com
// (and official mirror s ites) where it is
// freely available. You may not remove this
// copyright and notice. You may not distribute
// modified versions of the source code in this
// package. You may not use this file in printed
// media without the express permission of the
// author. Bruce Eckel makes no representation about
// the suitability of this software for any purpose.
// It is provided "as is" without express or implied
// warranty of any kind, including any implied
// warranty of merchantability, fitness for a
// particular purpose or non-infringement. The entire
// risk as to the quality and performance of the
// software is with you. Bruce Eckel and the
// publisher shall not be liable for any damages
// suffered by you or any third party as a result of
// using or distributing software. In no event will
// Bruce Eckel or the publisher be liable for any
// lost revenue, profit, or data, or for direct,
// indirect, special, consequential, incidental, or
// punitive damages, however caused and regardless of
// the theory of liability, arising out of the use of
// or inability to use software, even if Bruce Eckel
// and the publisher have been advised of the
// possibility of such damages. Should the software
// prove defective, you assume the cost of all
// necessary servicing, repair, or correction. If you
// think you've found an error, please email all
// modified files with clearly commented changes to:
// [email protected]. (please use the same
// address for non-code errors found in the book).
//////////////////////////////////////////////////
Al extraer archivos de un archivo empacado, el separador del archivo del
sistema que empacó el archivo es también notable, así es que puede ser
reemplazado con el correcto para el sistema local.
El nombre del subdirecto rio para el capítulo actual es guardado en el campo
chapter, lo cual es inicializado a c02. (Notarás que el listado en el Capítulo 2
no contiene una declaración package.) La única vez que el campo de capítulo
cambia es cuando una declaración package es descubierta en el archivo
corriente.
Construyendo un archivo empacado
El primer constructor está acostumbrado a extraer un archivo de la versión del
texto de ASCII de este libro. El código llamado (que aparece más abajo en el
listado) lee cada línea hasta que encuentra uno que corresponde al comienzo
de un listado. En ese momento, crea un objeto nuevo SourceCodeFile ,
pasándolos a la primera línea (que ya ha sido leídos por el código llamado) y el
objeto BufferedReader del cual extrae el resto de listado de código fuente.
En este punto, comienzas a ver el uso pesado de los métodos String . Para
extraer el nombre de archivo, la versión sobrecargada de substring() es
llamada que tomas el comienzo del desplazamiento y va al final del String.
Este índice de comienzo se produce encontrando al length () del startMarker.
trim() remueve espacios en blanco de ambos finales del String. La primera
línea también puede tener palabras después del nombre del archivo; Estos son
detectados usando indexOf(), lo cual retorna -1 si no pudiera encontrar el
carácter que buscas y el valor donde la primera instancia de ese carácter es
encontrada si lo hace. Note que allí está también una versión sobrecargada de
indexOf() que toma a un String en lugar de un carácter.
Una vez que el nombre de archivo es analizado gramaticalmente y
almacenado, la primera línea es colocada dentro del contenido String (que se
usa para contener el texto entero del listado de código fuente). En este punto,
el resto de las líneas son leídas y concatenadas en el contenido String. Es no
completamente tan simple, ya que en ciertas situaciones requieren un manejo
especial. Un caso es la detección de errores: Si lo ejecutas en un startMarker,
quiere decir que ningún marcador de fin estaba posado al final del listado que
está actualmente siendo coleccionado. Ésta es una condición de error que
aborta el programa.
La segunda causa especial es la palabra clave package. Aunque Java es un
lenguaje de forma independiente, este programa pide que la palabra clave
package esté en el comienzo de la línea. Cuando la palabra clave package se
ve, el nombre del paquete es extraído buscando el espacio al comienzo y el
punto y coma al final. (Note que esto también pudo haber sido realizado en
una operación única usando al substring() sobrecargado que toma ambos los
índices del comienzo e índices finales.) Luego los puntos en el nombre del
paquete son reemplazados por el separador del archivo, aunque una
suposición está hecha aquí es que el separador del archivo es sólo un carácter
largo. Esto es probablemente cierto en todos los sistemas, pero es un lugar
para ver si hay problemas.
El comportamiento predeterminado es concatenar cada línea para contenido,
junto con el string fin de línea, hasta que el endMarker es descubierto, el
cual señala que el constructor debería terminar. Si el fin del archivo es
encontrado antes de que el endMarker se vea, ese es un error.
Extrayendo desde un archivo empaquetado
El segundo constructor se usa para recuperar los archivos de código fuente
desde un archivo empaquetado. Aquí, el método llamado no tiene que
preocuparse por saltar por encima del texto intermedio. El archivo contiene
todos los archivos de código fuente, colocado extremo a extremo. Todo lo que
necesitas dar para este constructor es el BufferedReader donde la
información viene, y el constructor la toma de allí. Hay algo de meta
información, sin embargo, al principio de cada listado, y esto es denotado por
el packMarker. Si el packMarker no está allí, quiere decir que la persona que
llama está equivocadamente tratando de usar a este constructor donde no es
apropiado.
Una vez que el packMarker es encontrado, es quitado y el nombre del
directorio (terminado por un '#') y el nombre de archivo (que vaya al fin de la
línea) son extraídos. En ambos casos, el carácter viejo del separador es
reemplazado por uno que está reciente para esta máquina usando el método
String replace (). El separador viejo está posado al principio del archivo
empaquetado, y verás cómo es extraído este después en el listado.
El resto del constructor es muy simple. Lee y concatena cada línea para los
contents hasta que el endMarker sea encontrado.
Acceder y escribir los listados
El siguiente conjunto de métodos son asesores simples: directory(),
filename() (nota el método puedes tener lo mismo ortografía y la
capitalización como el campo) y contents (), y hasFile() para indicar si este
objeto contiene un archivo o no. (La necesidad para esto se verá más
adelante.)
Los tres métodos finales están preocupados con escribir a este lista do de
código en un archivo, ya sea un archivo empaquetado por writePacked() o un
archivo fuente Java por writeFile (). Todo lo que writePacked() necesita es el
DataOutputStream, lo cual fue abierto en otra parte, y representa el archivo
que está siendo escrito. Pone la información del encabezado en la primera
línea y luego llama a writeBytes() a escribir a contents en un formato
“universal”.
Al escribir el archivo fuente Java, el archivo debe ser creado. Esto se hace por
IO.psOpen(), dándole un objeto File que contiene no sólo el nombre de
archivo pero también la ruta. Pero la pregunta ahora es: ¿Existe esta ruta? El
usuario tiene la opción de colocar todos los directorios de código de la fuente
dentro de un subdirectorio completamente diferente, lo cual aun no podría
existir. Así es que antes de que cada archivo sea escrito, File.mkdirs() es
llamado con la ruta en la que quieres escribir el archivo. Esto hará la ruta
entera al mismo tiempo.
Conteniendo la colección entera de listados
Conviene organizar los listados como subdirectorios mientras la colección
entera se construye en la memoria. Una razón es otra comprobación de
cordura: Como cada subdirectorio de listados es creado, un archivo adicional
será añadido cuyo nombre contiene el número de archivos en ese directorio.
La clase DirMap produce este efecto y demuestra el concepto de un
“multimapa.” Esto es implementado usando a un Hashtable cuyas claves son
los subdirectorios siendo creados y cuyos valores son objetos Vector
conteniendo los objetos SourceCodeFile en ese directorio particular. Así, en
lugar de asociar una clave para un valor único, el “multimapa” asocia una
clave para un conjunto de valores por el Vector asociado. Aunque esto suena
complejo, es notablemente franco implementar. Verás que la mayor parte del
tamaño de la clase DirMap es debido a las porciones que escriben a archivos,
no para la implementación “multimapa”.
Hay dos formas que puedes hacer un DirMap: El constructor predeterminado
asume que quieres que los directorios se ramifiquen completamente de lo
actual, y el segundo constructor te deja especificar una ruta absoluta alterna
para el directorio que empieza.
El método add() es donde un montón de acción densa ocurre. Primero, el
directory () es extraído del SourceCodeFile que quieres agregar, y luego el
Hashtable es examinado para ver si ya contiene esa clave. De lo contrario, un
Vector nuevo es añadido al Hashtable y asociado con esa clave. En este
punto, el Vector está allí, de una u otra manera, y es extraído así es que el
SourceCodeFile puede agregarse. Porque los Vectors pueden estar
fácilmente combinados con Hashtables como éste, el poder de ambos es
amplificado.
Escribir un archivo empaquetado involucra a abrir el archivo para escribir
(como un DataOutputStream así los datos son universalmente recuperables)
y escribiendo la información del encabezado acerca del separador viejo en la
primera línea. Después, un Enumeration de las claves Hashtable es
producido y dado un paso hasta el final para seleccionar cada directorio e irle a
traer al Vector asociado con ese directorio así cada SourceCodeFile en que a
Vector le pueden ser escritos para el archivo empaquetado.
Escribiendo los archivos de origen Java a sus directorios en write() son casi
idénticos para writePackedFile () desde que ambos métodos simplemente
llamen el método apropiado en SourceCodeFile. Aquí, sin embargo, la ruta
raíz es pasada en SourceCodeFile.writeFile() y cuando todos los archivos
han sido escritos el archivo adicional con el nombre conteniendo el número de
archivos es también escrito.
El programa principal
Las clases descritas anteriormente son usadas dentro de CodePackager.
Primero ves el uso string que queda impreso cada vez que el usuario final
invoca el programa incorrectamente, junto con el método usage() que lo llama
y sale del programa. Todo lo que main() hace es determinar si quieres crear
un extracto o archivo empaquetado de uno, luego asegura que los argumentos
son correctos y llama el método apropiado.
Cuando un archivo empaquetado es creado, - se asume - se hace en el
directorio actual, así es que el DirMap es creado usando al constructor
predeterminado. Después de que el archivo es abierto cada línea es leída y
examinada para condiciones particulares:
1. Si la línea comienza con el marcador que empieza para un listado de código fuente,
un objeto nuevo SourceCodeFile es creado. El constructor lee en el resto de
listado de programa. El manipulador que resulte es directamente añadida al
DirMap.
2. Si la línea comienza con el marcador de fin para un listado de código fuente, algo ha
salido mal, ya que los marcadores de fin deberían ser encontrados sólo por el
constructor SourceCodeFile.
Al extraer un archivo empaquetado, la extracción puede estar en el directorio
actual o en un directorio alterno, así es que el objeto DirMap es creado
consecuentemente. El archivo es abierto y la primera línea es leída. El
separador viejo de información de la ruta del archivo es extraído de esta línea.
Luego la entrada se usa para crear el primer objeto SourceCodeFile , el cual
es añadido al DirMap. Los objetos nuevos SourceCodeFile son creados y
añadidos con tal de que contengan un archivo. (Lo último creado simplemente
retornará cuando se queda sin entrada y luego hasFile() retornará false.)
Comprobando estilo de capitalización
Aunque el ejemplo previo puede venir bien como un guía para algún proyecto
tuyo que involucra proceso de texto, este proyecto será directamente útil
porque realiza una comprobación de estilo para asegurarse de que tu
capitalización se conforma al estilo Java efectiva. Abre cada archivo .java en el
directorio actual y extrae todos los nombres de clase e identificadores, luego te
muestra si cualquiera de ellos no encuentra el estilo Java.
Para que el programa maneje correctamente, primero debes fortalecer un
depositario de nombre de clase para contener todos los nombres de clase en la
biblioteca estándar Java. Haces esto moviéndose hacia todos los subdirectorios
de código fuente para la biblioteca estándar Java y corriendo a ClassScanner
en cada subdirectorio. Provee como los argumentos el nombre del archivo del
depositario (usando la misma ruta y nombre cada vez) y la opción de línea de
comando -a señala que los nombres de clase deberían ser añadidos al
depositario.
Para usar el programa para comprobar tu código, lo corres y le das la ruta y
nombre del depositario a usar. Comprobará todas las clases e identificadores
en el directorio actual y te dirá cuáles no siguen el estilo típico de capitalización
Java.
Deberías ser consciente de que el programa no sea perfecto; Allí pocas veces
señalará cuando se piensa que hay un problema pero al mirar el código verás
que nada necesita variarse. Esto es un poco molesto, pero es todavía mucho
más fácil tratando de encontrar todos estos casos mirando tu código.
La explicación inmediatamente sigue el listado:
//: ClassScanner.java
// Scans all files in directory for classes
// and identifiers, to check capitalization.
// Assumes properly compiling code listings.
// Doesn't do everything right, but is a very
// useful aid.
import java.io.*;
import java.util.*;
class MultiStringMap extends Hashtable {
public void add(String key, String value) {
if(!containsKey(key))
put(key, new Vector());
((Vector)get(key)).addElement(value);
}
public Vector getVector(String key) {
if(!containsKey(key)) {
System.err.println(
"ERROR: can't find key: " + key);
System.exit(1);
}
return (Vector)get(key);
}
public void printValues(PrintStream p) {
Enumeration k = keys();
while(k.hasMoreE lements()) {
String oneKey = (String)k.nextElement();
Vector val = getVector(oneKey);
for(int i = 0; i < val.size(); i++)
p.println((String)val.elementAt(i));
}
}
}
public class ClassScanner {
private File path;
private String[] fileList;
private Properties classes = new Properties();
private MultiStringMap
classMap = new MultiStringMap(),
identMap = new MultiStringMap();
private StreamTokenizer in;
public ClassScanner() {
path = new File( ".");
fileList = path.list( new JavaFilter());
for(int i = 0; i < fileList.length; i++) {
System.out.println(fileList[i]);
scanListing(fileList[i]);
}
}
void scanListing(String fname) {
try {
in = new StreamTokenizer(
new BufferedReader(
new FileReader(fname)));
// Doesn't seem to work:
// in.slashStarComments(true);
// in.slashSlashComments(true);
in.ordinaryChar('/');
in.ordinaryChar('.');
in.wordChars('_', '_');
in.eolIsSignificant(true );
while(in.nextToken() !=
StreamTokenizer.TT_EOF) {
if(in.ttype == '/')
eatComments();
else if(in.ttype ==
StreamTokenizer.TT_WORD) {
if(in.sval.equals("class" ) ||
in.sval.equals("interface")) {
// Get class name:
while (in.nextToken() !=
StreamTokenizer.TT_EOF
&& in.ttype !=
StreamTokenizer.TT_WORD)
;
classes.put(in.sval, in.sval);
classMap.add(fname, in.sval);
}
if(in.sval.equals("import") ||
in.sval.equals("package"))
discardLine();
else // It's an identifier or keyword
identMap.add(fname, in.sval);
}
}
} catch(IOException e) {
e.printStackTrace();
}
}
void discardLine() {
try {
while(in.nextToken() !=
StreamTokenizer.TT_EOF
&& in.tt ype !=
StreamTokenizer.TT_EOL)
; // Throw away tokens to end of line
} catch(IOException e) {
e.printStackTrace();
}
}
// StreamTokenizer's comment removal seemed
// to be broken. This extracts them:
void eatComments() {
try {
if(in.nextToken() !=
StreamTokenizer.TT_EOF) {
if(in.ttype == '/')
discardLine();
else if(in.ttype != '*')
in.pushBack();
else
while(true ) {
if(in.nextToken() ==
StreamTokenizer.TT_EOF)
break;
if(in.ttype == '*')
if(in.nextToken() !=
StreamTokenizer.TT_EOF
&& in.ttype == '/')
break;
}
}
} catch(IOException e) {
e.printStackTrace();
}
}
public String[] classNames() {
String[] result = new String[classes.size()];
Enumeration e = classes.keys();
int i = 0;
while(e.hasMoreElements())
result[i++] = (String)e.nextElement();
return result;
}
public void checkClassNames() {
Enumeration files = classMap.keys();
while(files.hasMoreElements()) {
String file = (String)files.nextElement();
Vector cls = classMap.getVector(file);
for(int i = 0; i < cls.size(); i++) {
String className =
(String)cls.elementAt(i);
if(Character.isLowerCase(
className.charAt(0)))
System.out.println(
"class capitalization error, file: "
+ file + ", class: "
+ className);
}
}
}
public void checkIdentNames() {
Enumeration files = identMap.keys();
Vector reportSet = new Vector();
while(files.hasMoreElements()) {
String file = (String)files.nextElement();
Vector ids = identMap.getVector(file);
for(int i = 0; i < ids.size(); i++) {
String id =
(String)ids.elementAt(i);
if(!classes.contains(id)) {
// Ignore identifiers of length 3 or
// longer that are all uppercase
// (probably static final values):
if(id.length() >= 3 &&
id.equals(
id.toUpperCase()))
continue ;
// Check to see if first char is upper:
if(Character.isUpperCase(id.charAt(0))){
if(reportSet.indexOf(file + id)
== -1){ // Not reported yet
reportSet.addElement(file + id);
System.out.println(
"Ident capitalization error in:"
+ file + ", ident: " + id);
}
}
}
}
}
}
static final String usage =
"Usage: \n" +
"ClassScanner classnames -a\n" +
"\tAdds all the class names in this \n" +
"\tdirectory to the repository file \n" +
"\tcalled 'classnames'\n" +
"ClassScanner classnames\n" +
"\tChecks all the java files in this \n" +
"\tdirectory for capitalization errors, \n" +
"\tusing the repository file 'classnames'";
private static void usage() {
System.err.println(usage);
System.exit(1);
}
public static void main(String[] args) {
if(args.length < 1 || args.length > 2)
usage();
ClassScanner c = new ClassScanner();
File old = new File(args[0]);
if(old.exists()) {
try {
// Try to open an existing
// properties file:
InputStream oldlist =
new BufferedInputStream(
new FileInputStream(old));
c.classes.load(oldlist);
oldlist.close();
} catch(IOException e) {
System.err.println("Could not open "
+ old + " for reading");
System.exit(1);
}
}
if(args.length == 1) {
c.checkClassNames();
}
c.checkIdentNames();
}
// Write the class names to a repository:
if(args.length == 2) {
if(!args[1].equals( "-a"))
usage();
try {
BufferedOutputStream out =
new BufferedOutputStream(
new FileOutputStream(args[0]));
c.classes.save(out,
"Classes found by ClassScanner.java");
out.close();
} catch(IOException e) {
System.err.println(
"Could not write " + args[0]);
System.exit(1);
}
}
}
class JavaFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
// Strip path information:
String f = new File(name).getName();
return f.trim().endsWith(".java");
}
} ///:~
La clase MultiStringMap es una herramienta que te permite asociar un grupo
de string encima de cada entrada crucial. Como en el ejemplo previo, usa a un
Hashtable (esta vez con herencia) con la clave como el sencillo string que se
asoció encima del valor Vector. El método add() simplemente inspecciona para
ver si hay ya una clave en el Hashtable , y en caso de que no coloca uno allí.
El método getVector() produce a un Vector para una clave particular, y
printValues(), lo cual es muy apropiado para corrección de errores, imprime
todo el valor Vector por Vector.
Para conservar la vigencia básica, los nombres de clase de las bibliotecas
estándar Java son todo puestos en un objeto Properties (de la biblioteca
estándar Java). Recuerde que un objeto Properties es un Hashtable que
contiene sólo objetos String para ambos la clave y entradas de valor. Sin
embargo, puede ser salvado a disco y recuperado de disco en una llamada de
método, así es que es ideal para el depositario de nombres. Realmente,
necesitamos sólo una lista de nombres, y un Hashtable no puede aceptar nulo
para ya sea su clave o su entrada de valor. Así es que el mismo objeto servirá
para ambos la clave y el valor.
Para las clases e identificadores que son descubiertos para los archivos en un
directorio particular, dos MultiStringMaps son usados: ClassMap e
identMap . También, cuando el programa empieza le carga el depositario
estándar de nombre de clase en el objeto Properties llamadas classes, y
cuando un nombre nuevo de clase es encontrado en el directorio local que es
también añadido a las clases así como también para classMap. Así, classMap
puede estar acostumbrado a dar un paso hasta el final todas las clases en el
directorio local, y las clases pueden usarse para ver si la muestra actual es un
nombre de clase (que indica que una definición de un objeto o método
empieza, así es que agarra las siguientes muestras – hasta un punto y coma –
y las introduce en identMap).
El constructor predeterminado para ClassScanner crea una lista de nombres
de archivo (usando la implementación JavaFilter de FilenameFilter, como se
ha descrito en el Capítulo 12). Luego llama a scanListing() para cada nombre
de archivo.
Dentro de scanListing() el archivo del código fuente es abierto y revuelto en
un
StreamTokenizer.
En
la
documentación,
pasando
true
a
slashStarComments () y slashSlashComments() - se supone - devasta esos
comentarios fuera, pero esto parece ser un poco defectuoso (realmente no
trabaja en Java 1.0). En lugar de eso, esas líneas son comentadas y los
comentarios son extraídos por otro método. Para hacer esto, el debe ser
capturado como un carácter común en vez de dejar a StreamTokenizer
absorberlo como parte de un comentario, y el método ordinaryChar() dice al
StreamTokenizer que haga esto. Esto es también cierto por puntos ('.'), Ya
que queremos tener las llamadas de método jaladas aparte en identificadores
individuales. Sin embargo, la línea subrayada, el cual es ordinariamente
tratado por StreamTokenizer como un carácter individual, debería quedar
como parte de identificadores desde que aparezca en tales valores finales
estáticos como TT_EOF etcétera., Usado en este mismo programa. El método
wordChars () toma un rango de personajes que quieres añadirle a esos que se
quedan dentro de una muestra que está siendo analizada gramaticalmente
como una palabra. Finalmente, al analizar gramaticalmente por comentarios de
un línea o descartar una línea necesitamos conocer cuándo ocurre un fin de
línea, así llamando a eolIsSignificant(true) el eol aparecerá en vez de
siendo absorbido por el StreamTokenizer.
El resto de scanListing() lee y reacciona a los valores simbólicos hasta que el
final del archivo, significado cuando nextToken() devuelva el valor estático
final StreamTokenizer.TT_EOF.
Si la muestra es uno que es potencialmente un comentario, así
eatComments() son llamados para ocuparse de ella. La única otra situació n
que estamos interesados aquí dentro es que si es una palabra, del cual hay
algunas causas especiales.
Si la palabra es clase o interfaz entonces la siguiente muestra representa una
clase o un nombre de la interfaz, y es introducida en classs y classMap. Si la
palabra es import o package, entonces no queremos el resto de la línea.
Cualquier otra cosa debe ser un identificador o una palabra clave (en el cual
estamos interesados) (los cuales no son, pero son toda letra minúscula de
cualquier manera así es que no echará a perder cosas para echar esos). Estos
son añadidos a identMap.
El método discardLine () es una herramienta simple que busca el fin de una
línea. Nota que siempre que obtienes una muestra nueva, debes revisar en
busca del fin del archivo.
El método eatComments() es llamado cada vez que un slash adelantado es
encontrado en el bucle principal de análisis gramatical. Sin embargo, eso
necesariamente no quiere decir que un comentario haya sido encontrado, así
es que la siguiente muestra debe ser extra ída para ver si es otro slash
adelantado (en cuyo caso la línea es descartada) o un asterisco. ¡Pero si son
ningún de esos, quiere decir que la muestra que justamente has arrancado es
necesaria retornarlo
en el
bucle
general
de
análisis
sintáctico!
Afortunadamente, el método pushBack () te permite retroceder la muestra
actual encima del flujo de entrada a fin de que cuando el bucle principal de
análisis gramatical llama a nextToken() traerá el mismo que justamente
retrocediste.
Por conveniencia, el método classNames() produce un arreglo de todos los
nombres en la colección de clases. Este método no es usado en el programa
pero es de ayuda para depurar.
Los siguientes dos métodos son los únicos en los cuales la comprobación real
tiene lugar. En checkClassNames(), los nombres de clase son extraídos del
classMap (el cual, recuerda que, contiene sólo los nombres en este directorio,
organizado por el nombre de archivo así es que el nombre de archivo puede
ser impreso junto con el nombre errante de clase). Esto está consumado
atrayendo a cada Vector asociado y dando un paso a través de eso, viendo si
el primer carácter es letra minúscula. Si es así, el mensaje apropiado de error
es impreso.
En checkIdentNames(), un acercamiento similar es tomado: Cada nombre
de identificador es extraído de identMap. Si el nombre no está en la lista de
clases, - se asume - es un identificador o palabra clave. Una causa especial es
comprobada: Si la longitud de identificador es 3 o más y todos los caracteres
son mayúsculas, este identificador es ignorado porque es probablemente un
valor final estático como TT_EOF . Por supuesto, éste no es un algoritmo
perfecto, pero da por supuesto que eventualmente notarás cualquiera de todos
los identificadores mayúsculas que están fuera de lugar.
En lugar de reportar cada identificador que comienza con un carácter
mayúscula, este método le sigue la pista a los cuales ya han sido reportados
en un Vector llamado reportSet(). Esto trata al Vector como un conjunto que
te dice ya sea que un artículo está ya en el conjunto. El artículo se produce
concatenando el nombre de archivo e identificador. Si el elemento no está en
el conjunto, se agrega y entonces el informe se hace.
El resto de listado está implícito de main(), lo cual se ocupa manejando los
argumentos de la línea de comando e imaginándose que fuera de si estás
fortaleciendo a un depositario de nombres de clase de la biblioteca estándar
Java o comprobando la validez de código que has escrito. En ambos casos hace
un objeto ClassScanner.
Si estás construyendo a un depositario o usando uno, debes tratar de abrir al
depositario existente. Haciendo un objeto File y haciendo pruebas para la
existencia, puedes decidirte ya sea abrir el archivo y load() las clases de la
lista de Properties dentro de ClassScanner. (Las clases del depositario se
añaden a, en vez de sobrescribir, las clases encontradas por el constructor
ClassScanner.) Si provees sólo un argumento de línea de comando quiere
decir que quieres realizar una comprobación de los nombres de clase y los
nombres de identificador, pero si provees dos argumentos (el segundo siendo a) construyes un depositario de nombre de clase. En este caso, un archivo de
salida es abierto y el método Properties.save() se usa para escribir la lista en
un archivo, junto co n uno string que provee información del archivo del
encabezado.
Una herramienta de búsqueda de
método
El capítulo 10 introdujo a la Java 1.1 concepto de reflexión y se usó esa
característica para buscar métodos para una clase particular – ya sea la lista
entera de métodos o un subconjunto de esos cuyos nombres corresponden a
una palabra clave que provee. La magia de esto es que automáticamente os
puede demostrar a todos ustedes los métodos para una clase sin obligarte a
caminar arriba de la jerarquía de a
l herencia examinando las clases base en
cada nivel. Así, provee una herramienta ahorradora de tiempo valiosa para
programar: Porque los nombres de la mayoría de nombres de método Java son
hechos amablemente verboso y descriptivo, puedes ir en busca de los nombres
de método que contienen una palabra particular de interés. Cuando encuentras
lo que piensas que estás buscando, compruebas la documentación en línea.
Sin embargo, por el Capítulo 10 no habías visto Swing, a fin de que la
herramienta fuera desarrollada como una aplicación de línea de comando. Aquí
está la versión GUI más útil, lo cual dinámicamente actualiza la salida cuando
escribes y también te permite cortar y pegar desde la salida:
//: DisplayMethods.java
// Display the methods of any class inside
// a window. Dynamically narrows your search.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.lang.reflect.*;
import java.io.*;
public class DisplayMethods extends Applet {
Class cl;
Method[] m;
Constructor[] ctor;
String[] n = new String[0];
TextField
name = new TextField(40),
searchFor = new TextField(30);
Checkbox strip =
new Checkbox("Strip Qualifiers" );
TextArea results = new TextArea(40, 65);
public void init() {
strip.setState(true);
name.addTextListener( new NameL());
searchFor.addTextListener( new SearchForL());
strip.addItemListener(new StripL());
Panel
top = new Panel(),
lower = new Panel(),
p = new Panel();
top.add(new Label("Qualified class name:" ));
top.add(name);
lower.add(
new Label("String to search for:"));
lower.add(searchFor);
lower.add(strip);
p.setLayout(new BorderLayout());
p.add(top, BorderLayout.NORTH);
p.add(lower, BorderLayout.SOUTH);
setLayout(new BorderLayout());
add(p, BorderLayout.NORTH);
add(results, BorderLayout.CENTER);
}
class NameL implements TextListener {
public void textValueChanged(TextEvent e) {
String nm = name.getText().trim();
if(nm.length() == 0) {
results.setText("No match");
n = new String[0];
return;
}
try {
cl = Class.forName(nm);
} catch (ClassNotFoundException ex) {
results.setText("No match");
return;
}
m = cl.getMethods();
ctor = cl.getConstructors();
// Convert to an array of Strings:
n = new String[m.length + ctor.length];
for(int i = 0; i < m.length; i++)
n[i] = m[i].toString();
for(int i = 0; i < ctor.length; i++)
n[i + m.length] = ctor[i].toString();
reDisplay();
}
}
void reDisplay() {
// Create the result set:
String[] rs = new String[n.length];
String find = searchFor.getText();
int j = 0;
// Select from the list if find exists:
for (int i = 0; i < n.length; i++) {
if(find == null)
rs[j++] = n[i];
else if(n[i].indexOf(find) != -1)
rs[j++] = n[i];
}
results.setText( "");
if(strip.getState() == true)
for (int i = 0; i < j; i++)
results.append(
StripQualifiers.strip(rs[i]) + "\n");
else // Leave qualifiers on
for (int i = 0; i < j; i++)
results.append(rs[i] + "\n");
}
class StripL implements ItemListener {
public void itemStateChanged(ItemEve nt e) {
reDisplay();
}
}
class SearchForL implements TextListener {
public void textValueChanged(TextEvent e) {
reDisplay();
}
}
public static void main(String[] args) {
DisplayMethods applet = new DisplayMethods();
Frame aFrame = new Frame("Display Methods");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(500,750);
applet.init();
applet.start();
aFrame.setVisible(true);
}
}
class StripQualifiers {
private StreamTokenizer st;
public StripQualifiers(String qualified) {
st = new StreamTokenizer(
new StringReader(qualified));
st.ordinaryChar(' ');
}
public String getNext() {
String s = null;
try {
if(st.nextToken() !=
StreamTokenizer.TT_EOF) {
switch(st.ttype) {
case StreamTokenizer.TT_EOL:
s = null ;
break;
case StreamTokenizer.TT_NUMBER:
s = Double.toString(st.nval);
break;
case StreamTokenizer.TT_WORD:
s = new String(st.sval);
break;
default: // single character in ttype
s = String.valueOf((char)st.ttype);
}
}
} catch(IOException e) {
System.out.println(e);
}
return s;
}
public static String strip(String qualified) {
StripQualifiers sq =
new StripQualifiers(qualified);
String s = "", si;
while((si = sq.getNext()) != null) {
int lastDot = si.lastIndexOf('.');
if(lastDot != -1)
si = si.substring(lastDot + 1);
s += si;
}
return s;
}
} ///:~
Algunas cosas que has visto antes. Al igual que con muchos de los programas
GUI en este libro, esto es creado para realizar ambos como una aplicación y
como un applet. También, la clase StripQualifiers es exactamente igual como
estaba en el Capítulo 10.
El GUI contiene un nombre TextField en el cual puedes introducir el nombre
de clase con creces calificado que quieres buscar, y otro, searchFor, en el cual
puedes entrar en el texto optativo para ir en busca dentro de la lista de
métodos. El Checkbox te permite decir si quieres usar los nombres
completamente calificados en la salida o si quieres la capacitación quitada.
Finalmente, los resultados son desplegados en un TextArea.
Notarás que no hay botones u otros componentes por los cuales señala que
quieres que la búsqueda empiece. Eso es porque ambos de los TextFields y el
Checkbox son monitoreados por sus objetos del oyente. Cada vez que haces
un cambio, la lista está inmediatamente actualizada. Si cambias el texto dentro
del campo name, el texto nuevo es capturado en la clase NameL . Si el texto
no está vacío, es usado dentro de Class.forName() para tratar de buscar la
clase. Como escribe, claro está, el nombre será incompleto y Class.forName()
fallará, lo cual quiere decir que lanza una excepción. Esto es atrapado y la
TextArea está lista para "No match". Pero tan pronto como introduces un
nombre correcto (la capitalización tiene importancia), Class.forName() tiene
éxito y getMethods() y getConstructors() devolverán arreglos de objetos
Method y Constructor, respectivamente. Cada uno de lo s objetos en estos
arreglos son convertidos en un String por toString () (éste produce la firma
de método completo o del constructor) y ambas listas están combinados en n,
un sencillo arreglo String. El arreglo n forma parte de la clase
DisplayMethods y es usada en actualizar el despliegue cada vez que
reDisplay() es llamado.
Si cambias al Checkbox o componentes searchFor, sus oyentes simplemente
llaman reDisplay(). reDisplay() crea un arreglo temporal String llamados rs
(para el "conjunto de resultado"). El conjunto de resultado es cualquier
copiado directamente de n si no esta la palabra find , o condicionalmente
copiado de los Strings en n que contiene la palabra find. Finalmente, el strip
Checkbox es interrogado para ver si el usuario quiere que los nombre s fueran
devastados (el defecto es yes). Si es así, StripQualifiers .strip() hace el
trabajo; Si no, la lista es simplemente desplegada.
En init(), podrías pensar que abunda el trabajo ocupado involucrado en
establecer el diseño. De hecho, cabe diseñar los componentes con menos
trabajo, pero la ventaja de usar a BorderLayouts así es que le permite al
usuario ajustar el tamaño de la ventana y hacer – en particular – al TextArea
más grande, que quiere decir que puedes ajustar el tamaño para permitirte ver
nombres más largos sin desplazar.
Podrías encontrarte con que conservarás esta herramienta corriendo mientras
programas, ya que provee una de las mejores “primeras líneas de ataque”
cuando estás tratando de resolver qué método llamar.
La teoría de complejidad
Este programa fue modificado de código originalmente creado por Larry
O'Brien, y se basa en los programas “Boids” creado por Craig Reynolds en
1986 para demostrar un aspecto de teoría de complejidad llamada
“emergencia.”
La meta aquí es producir una reproducción razonablemente parecida a la
realidad de viajar en tropel o reuniendo en manada el comportamiento en
animales estableciendo un conjunto pequeño de reglas simples para cada
animal. Cada animal puede mirar la escena entera y todos los demás animales
en la escena, pero reacciona sólo a un conjunto cercanos “flockmates.” El
animal se mueve según tres comportamientos simples de la dirección:
1. Separación: Evita atestar flockmates locales.
2. Alineación: Sigue el encabezamiento común de flockmates locales.
3. Cohesión: Se Mueve hacia el centro del grupo de flockmates locales.
Los modelos más elaborados pueden incluir obstáculos y la habilidad para los
animales a predecir colisiones y evitarles a ellos, así es que los animales
pueden fluir alrededor de objeto s fijos en el ambiente. Además, los animales
también podrían recibir una meta, lo cual puede causar que el rebaño siga un
camino deseado. Para la simplicidad, la evitación de obstáculo y el buscar
metas no son incluidos en el modelo presentado aquí.
La emergencia quiere decir que, a pesar de la naturaleza limitada de
computadoras y la simplicidad de las reglas de la dirección, el resultado parece
realista. Es decir, el comportamiento notablemente parecido a la realidad
“emerge” de este modelo simple.
El pro grama se replantea como una aplicación/applet combinada:
//: FieldOBeasts.java
// Demonstration of complexity theory; simulates
// herding behavior in animals. Adapted from
// a program by Larry O'Brien [email protected]
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;
class Beast {
int
x, y,
// Screen position
currentSpeed;
// Pixels per second
float currentDirection; // Radians
Color color;
// Fill color
FieldOBeasts field; // Where the Beast roams
static final int GSIZE = 10; // Graphic size
public Beast(FieldOBeasts f, int x, int y,
float cD, int cS, Color c) {
field = f;
this.x = x;
this.y = y;
currentDirection = cD;
currentSpeed = cS;
color = c;
}
public void step() {
// You move based on those within your sight:
Vector seen = field.beastListInSector(this);
// If you're not out in front
if(seen.size() > 0) {
// Gather data on those you see
int totalSpeed = 0;
float totalBearing = 0.0f;
float distanceToNearest = 100000.0f;
Beast nearestBeast =
(Beast)seen.elementAt(0);
Enumeration e = seen.elements();
while(e.hasMoreElements()) {
Beast aBeast = (Beast) e.nextElement();
totalSpeed += aBeast.currentSpeed;
float bearing =
aBeast.bearingFromPointAlongAxis(
x, y, currentDirection);
totalBearing += bearing;
float distanceToBeast =
aBeast.distanceFromPoint(x, y);
if(distanceToBeast < distanceToNearest) {
nearestBeast = aBeast;
distanceToNearest = distanceToBeast;
}
}
// Rule 1: Match average speed of those
// in the list:
currentSpeed = totalSpeed / seen.size();
// Rule 2: Move towards the perceived
// center of gravity of the herd:
currentDirection =
totalBearing / seen.size();
// Rule 3: Maintain a minimum distance
// from those around you:
if(distanceToNearest <=
field.minimumDistance) {
currentDirection =
nearestBeast.currentDirection;
currentSpeed = nearestBeast.currentSpeed;
if(currentSpeed > field.maxSpeed) {
currentSpeed = field.maxSpeed;
}
}
}
else { // You are in front, so slow down
currentSpeed =
(int)(currentSpeed * field.decayRate);
}
// Make the beast move:
x += (int)(Math.cos(currentDirection)
* currentSpeed);
y += (int)(Math.sin(currentDirection)
* currentSpeed);
x %= field.xExtent;
y %= field.yExtent;
if(x < 0)
x += field.xExtent;
if(y < 0)
y += field.yExtent;
}
public float bearingFromPointAlongAxis (
int originX, int originY, float axis) {
// Returns bearing angle of the current Beast
// in the world coordiante system
try {
double bearingInRadians =
Math.atan(
(this.y - originY) /
(this.x - originX));
// Inverse tan has two solutions, so you
// have to correct for other quarters:
if(x < originX) {
if(y < originY) {
bearingInRadians += - (float)Math.PI;
}
else {
bearingInRadians =
(float)Math.PI - bearingInRadians;
}
}
// Just subtract the axis (in radians):
return (float) (axis - bearingInRadians);
} catch(ArithmeticException aE) {
// Divide by 0 error possible on this
if(x > originX) {
return 0;
}
else
return (float) Math.PI;
}
}
public float distanceFromPoint(int x1, int y1){
return (float) Math.sqrt(
Math.pow(x1 - x, 2) +
Math.pow(y1 - y, 2));
}
public Point position() {
return new Point(x, y);
}
// Beasts know how to draw themselves:
public void draw(Graphics g) {
g.setColor(color);
int directionInDegrees = ( int)(
(currentDirection * 360) / (2 * Math.PI));
int startAngle = directionInDegrees FieldOBeasts.halfFieldOfView;
int endAngle = 90;
g.fillArc(x, y, GSIZE, GSIZE,
startAngle, endAngle);
}
}
public class FieldOBeasts extends Applet
implements Runnable {
private Vector beasts;
static float
fieldOfView =
(float) (Math. PI / 4), // In radians
// Deceleration % per second:
decayRate = 1.0f,
minimumDistance = 10f; // In pixels
static int
halfFieldOfView = (int)(
(fieldOfView * 360) / (2 * Math.PI)),
xExtent = 0,
yExtent = 0,
numBeasts = 50,
maxSpeed = 20; // Pixels/second
boolean uniqueColors = true;
Thread thisThread;
int delay = 25;
public void init() {
if (xExtent == 0 && yExtent == 0) {
xExtent = Integer.parseInt(
getParameter("xExtent" ));
yExtent = Integer.parseInt(
getParameter("yExtent" ));
}
beasts =
makeBeastVector(numBeasts, uniqueColors);
// Now start the beasts a-rovin':
thisThread = new Thread(this);
thisThread.start();
}
public void run() {
while(true) {
for(int i = 0; i < beasts.size(); i++){
Beast b = (Beast) beasts.elementAt(i);
b.step();
}
try {
thisThread.sleep(delay);
} catch(InterruptedException ex){}
repaint(); // Otherwise it won't update
}
}
Vector makeBeastVector(
int quantity, boolean uniqueColors) {
Vector newBeasts = new Vector();
Random generator = new Random();
// Used only if uniqueColors is on:
double cubeRootOfBeastNumber =
Math.pow((double)nu mBeasts, 1.0 / 3.0);
float colorCubeStepSize =
(float) (1.0 / cubeRootOfBeastNumber);
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
for(int i = 0; i < quantity; i++) {
int x =
(int) (generator.nextFloat() * xExtent);
if(x > xExtent - Beast.GSIZE)
x -= Beast.GSIZE;
int y =
(int) (generator.nextFloat() * yExtent);
if(y > yExtent - Beast.GSIZE)
y -= Beast.GSIZE;
float direction = ( float )(
generator.nextFloat() * 2 * Math.PI);
int speed = (int)(
generator.nextFloat() * (float)maxSpeed);
if(uniqueColors) {
r += colorCubeStepSize;
if(r > 1.0) {
r -= 1.0f;
g += colorCubeStepSize;
if( g > 1.0) {
g -= 1.0f;
b += colorCubeStepSize;
if(b > 1.0)
b -= 1.0f;
}
}
}
newBeasts.addElement(
new Beast(this, x, y, direction, speed,
new Color(r,g,b)));
}
return newBeasts;
}
public Vector beastListInSector(Beast viewer) {
Vector output = new Vector();
Enumeration e = beasts.elements();
Beast aBeast = (Beast)beasts.elementAt(0);
int counter = 0;
while(e.hasMoreElements()) {
aBeast = (Beast) e.nextElement();
if(aBeast != viewer) {
Point p = aBeast.position();
Point v = viewer.position();
float bearing =
aBeast.bearingFromPointAlongAxis(
v.x, v.y, viewer.currentDirection);
if(Math.abs(bearing) < fieldOfView / 2)
output.addElement(aBeast);
}
}
return output;
}
public void paint(Graphics g) {
Enumeration e = beasts.elements();
while(e.hasMoreElements()) {
((Beast)e.nextElement()).draw(g);
}
}
public static void main(String[] args)
{
FieldOBeasts field = new FieldOBeasts();
field.xExtent = 640;
field.yExtent = 480;
Frame frame = new Frame("Field 'O Beasts" );
// Optionally use a command-line argument
// for the sleep time:
if(args.length >= 1)
field.delay = Integer.parseInt(args[0]);
frame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.add(field, BorderLayout.CENTER);
frame.setSize(640,480);
field.init();
field.start();
frame.setVisible(true );
}
} ///:~
Aunque ésta no es una reproducción perfecta del comportamiento en “Boids”
de Craig Reynold el ejemplo, exhibe sus propias características fascinantes, el
cual puedes modificar ajustando los números. Puedes encontrar más acerca
del modelado de comportamiento de viajar en tropel y puedes ver una versión
espectacular
3-D
de
Boids
en
la
página
de
Craig
Reynold
http://www.hmt.com/cwr/boids.html.
Para correr éste programa como un applet, pones la siguiente etiqueta de
applet en un archivo de HTML:
<applet
code=FieldOBeasts
width=640
height=480>
<param name=xExtent value = "640">
<param name=yExtent value = "480">
</applet>
Resumen
Este capítulo demuestra que una cierta cantidad de las cosas más sofisticadas
se pueden hacer con Java. También hace el punto que mientras Java
ciertamente debe tener sus límites, esos límites son primordialmente relegados
para el desempeño. (Cuando a los programas de procesos de texto les fue
escrito, por ejemplo, las versiones C++ fueron mucho más rápidas – éste
podría deberse en parte a una implementación ineficiente de la biblioteca IO, lo
cual debería cambiar con el tiempo.) Los límites de Java no parecen estar en el
área de expresividad. No sólo lo hace parece posible expresar justamente
acerca de todo lo que puedes imaginar, pero Java parece orientada a facilitar
esa expresión de escribir y leer. Por eso no entras corriendo a la pared de
complejidad que a menudo cursa con lenguajes que son más trivial por usar
que Java (al menos parecen de ese modo, al principio). Y con la librería
JFC/Swing 1.2 de Java, emparejan la expresividad y la facilidad de uso del
AWT mejora dramáticamente.
Ejercicios
1. (Avanzado) Reescribe FieldOBeasts.java a fin de que su estado puede ser
persistente. Implementa botones para permitirte salvar y recordar archivos
diferentes del estado y continuar corriéndolos donde salió completamente. Usa
CADState.java desde el Capítulo 12 como un ejemplo de cómo hacer esto.
2. (Proyecto de período) Tomando a FieldOBeasts.java como un punto de
partida, construye un sistema de simulación de tráfico del automóvil.
3. (Proyecto de período) Usando a ClassScanner.java como un punto de partida,
construye una herramienta que señala métodos y campos que están definidos pero
nunca usados.
4 . (Proyecto de período) Usando a JDBC, construye un programa de la gerencia de
contacto usando una base de datos de archivo plano conteniendo nombres,
direcciones, números de teléfono, direcciones de correo electrónico, etc. Deberías
poder fácilmente añadirle los nombres nuevos a la base de datos. Al introducir el
nombre para ser buscado, usa terminación automática de nombre como se muestra
en VLookup.java en el Capítulo 16.