<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Dark[byte]</title>
	<atom:link href="https://darkbyteblog.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://darkbyteblog.wordpress.com</link>
	<description>Bienvenido al blog de:</description>
	<lastBuildDate>Sun, 01 Jan 2012 17:25:14 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<site xmlns="com-wordpress:feed-additions:1">18282440</site><cloud domain='darkbyteblog.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>https://secure.gravatar.com/blavatar/f0979af9782e5ea8cd8997aeb7299d0ae374a1bff054fd04c1c55240c3e50430?s=96&#038;d=https%3A%2F%2Fs0.wp.com%2Fi%2Fbuttonw-com.png</url>
		<title>Dark[byte]</title>
		<link>https://darkbyteblog.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="https://darkbyteblog.wordpress.com/osd.xml" title="Dark[byte]" />
	<atom:link rel='hub' href='https://darkbyteblog.wordpress.com/?pushpress=hub'/>
	<item>
		<title>Java &#8211; Archivos JAR bloqueados por URLClassLoader en Windows</title>
		<link>https://darkbyteblog.wordpress.com/2011/09/10/java-archivos-jar-bloqueados-por-urlclassloader-en-windows/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/09/10/java-archivos-jar-bloqueados-por-urlclassloader-en-windows/#comments</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Sat, 10 Sep 2011 18:10:24 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[cerrar]]></category>
		<category><![CDATA[close()]]></category>
		<category><![CDATA[desbloquear]]></category>
		<category><![CDATA[JAR]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[liberar]]></category>
		<category><![CDATA[recursos]]></category>
		<category><![CDATA[URLClassLoader]]></category>
		<category><![CDATA[windows]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2654</guid>

					<description><![CDATA[Un archivo JAR es un contenedor comprimido de clases y recursos asociados, útil para la distribución de aplicaciones, librerías o módulos.  En arquitecturas de tipo modular es común utilizar diferentes cargadores de clases para poder cargar, re-cargar y descargar módulos en forma dinámica y en tiempo de ejecución sin tener que reiniciar la JVM. Esta [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Un archivo JAR es un contenedor comprimido de clases y recursos asociados, útil para la distribución de aplicaciones, librerías o módulos.  En arquitecturas de tipo modular es común utilizar diferentes cargadores de clases para poder cargar, re-cargar y descargar módulos en forma dinámica y en tiempo de ejecución sin tener que reiniciar la JVM.</p>
<p><span id="more-2654"></span></p>
<p>Esta forma de mantener una aplicación es conocida popularmente en inglés como «hot-deployment», y es normalmente utilizada en aplicaciones de servidor. Todo esto implicaría que, de ser necesario, un modulo puede ser reemplazado por una versión actualizada del mismo sin afectar el funcionamiento general de la aplicación.</p>
<p>El <span style="color:#000000;"><code>java.net.URLClassLoader</code></span> es la implementación de un cargador de clases incluida en la API desde Java 1.2. Permite cargar buscando desde una dirección URL la cual puede ser un archivo en el sistema(file:), un servidor HTTP(http:), o un archivo JAR(jar:). Para muchos casos con usar o extender la funcionalidad de <span style="color:#000000;"><code>URLClassLoader</code></span> ya es suficiente y no requiere un trabajo considerable.</p>
<p>Lamentablemente al utilizar <span style="color:#000000;"><code>URLClassLoader</code></span> en la plataforma Windows existe un pequeño y gran inconveniente reportado como <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5041014" target="_blank">bug</a> (ID: 5041014). Cuando se crea un cargador de clases para cargar el contenido de un JAR este último se mantiene abierto durante la vida de la JVM, esto es seguramente por eficiencia ya que luego no se necesitará reabrir el JAR nuevamente si fuera necesario. El sistema operativo mantiene un JAR abierto a través de un descriptor de archivo, naturalmente en Windows un archivo en este estado no puede ser manipulado por otra aplicación, por lo tanto no se puede eliminar ni renombrar (aunque este no es el caso en sistemas basados en UNIX). Como resultado un archivo JAR utilizado queda «bloqueado» hasta que la JVM finalice, incluso si el cargador de clases asociado y las clases cargadas fueran eliminados por el colector de basura. Esto perjudica a todos aquellos proyectos que pretenden hacer uso del mecanismo modular mencionado antes.</p>
<p>Hasta antes de Java 7 no había ningún método en la API de <span style="color:#000000;"><code>URLClassLoader</code></span> que permitiera liberar los recursos utilizados, sin embargo si había algunas posibles soluciones, aunque algo incomodas, para superar el problema. Al día de hoy hay varias soluciones, a continuación se mencionan las principales.</p>
<h3 style="text-align:left;"><strong>Realizando copias auxiliares</strong></h3>
<p>Un archivo JAR puede ser copiado a una ubicación temporal y entonces se le asigna a un cargador de clases en esa nueva ubicación. Si se necesita actualizar se copia la nueva versión del JAR a otra ubicación temporal y se elimina\reemplaza el cargador de clases anterior por uno nuevo asociado al nuevo JAR. Esto puede terminar siendo más un problema que una solución, el hecho de tener que realizar copias de cada JAR perjudica la eficiencia de la aplicación, y es un desperdicio de espacio y memoria ya que las antiguas versiones del archivo JAR quedaran abiertas y no se pueden eliminar. Inclusive en caso de que este mecanismo se utilice mucho el sistema podría alcanzar el limite máximo de descriptores de archivo abiertos.</p>
<p>El popular contenedor de servlets <strong>Apache Tomcat</strong> parece utilizar esta solución y la implementa en una opción llamada «<em>antiJARLocking</em>«. La documentación sobre esta opción advierte sobre como puede afectar el rendimiento durante la carga, sin embargo suele ser necesario activarla en Windows.</p>
<h3 style="text-align:left;"><strong>Utilizar otro cargador de clases</strong></h3>
<p>Existen otras implementaciones de cargadores de clases que no sufren del mismo problema.  Como desventaja muchas de estas no tienen toda la funcionalidad de <span style="color:#000000;"><code>URLClasLoader</code></span>, y para algunas personas tener que recurrir a componentes terciarios es una opción poco preferible.</p>
<p>Por ejemplo:  <code><a href="http://kickjava.com/src/org/eclipse/jdt/apt/core/internal/JarClassLoader.java.htm" target="_blank">org.eclipse.jdt.apt.core.internal.JarClassLoader</a></code>.</p>
<h3 style="text-align:left;"><strong>Implementar un cargador de clases propio</strong></h3>
<p>Esto puede hacerse extendiendo la clase <span style="color:#000000;"><code>ClassLoader</code></span>. De esta forma tenemos todo el control y podemos encargarnos de cerrar los JAR abiertos. Sin embargo no es una tarea trivial ni tampoco el trabajo que cualquiera quisiera tomarse.</p>
<h3 style="text-align:left;"><strong>Extender URLClassLoader y tratar de cerrar los JAR abiertos</strong></h3>
<p>Esto fue sugerido en el reporte del bug. Mientras no se disponga de un método publico en la API se puede extender <span style="color:#000000;"><code>URLClassLoader</code></span> y añadir un método <span style="color:#000000;"><code>close()</code></span> donde se utiliza un poco de reflexión para poder acceder a la lista de archivos JAR abiertos y así poder cerrarlos. La reflexión es necesaria porque se necesita el acceso a datos que no son de acceso publico, este es el camino a seguir:</p>
<pre>URLClassLoader -&gt; URLClassPath ucp -&gt; ArrayList&lt;Loader&gt; loaders -&gt; JarLoader -&gt; JarFile jar -&gt; jar.close()</pre>
<p>Esto puede considerarse un «hack» valido al menos para la JVM de Sun (ahora Oracle). Aquí el código del método <span style="color:#000000;"><code>close()</code></span>:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
/**
 * Cerrar todos los JAR abiertos
 */
public void close() {
    try {
        Class clazz = java.net.URLClassLoader.class;
        Field ucp = clazz.getDeclaredField(&quot;ucp&quot;);    //acceso a URLClassPath
        ucp.setAccessible(true);
        Object sunMiscURLClassPath = ucp.get(this);
        Field loaders = sunMiscURLClassPath.getClass().getDeclaredField(&quot;loaders&quot;);    //acceso a ArrayList
        loaders.setAccessible(true);
        Object collection = loaders.get(sunMiscURLClassPath);    //colección de JarLoader's
        for (Object sunMiscURLClassPathJarLoader : ((Collection) collection).toArray()) {
            try {
                Field loader = sunMiscURLClassPathJarLoader.getClass().getDeclaredField(&quot;jar&quot;);    //acceso a JarLoader
                loader.setAccessible(true);
                Object jarFile = loader.get(sunMiscURLClassPathJarLoader);    //acceso a JarFile
                ((JarFile) jarFile).close();    //close()
            } catch (Throwable t) {
                // if we got this far, this is probably not a JAR loader so skip it
            }
        }
    } catch (Throwable t) {
        // probably not a SUN VM
    }
    return;
}
...
</pre>
<p>Y en caso de tratar con librerías nativas se puede aplicar otro poco de reflexión en el método <code><span style="color:#000000;">close()</span>. El camino a seguir</code>:</p>
<pre>ClassLoader -&gt; Vector&lt;NativeLibrary&gt; nativeLibraries -&gt;  NativeLibrary -&gt;  finalize()</pre>
<p>El trozo de código:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
clazz = ClassLoader.class;
Field nativeLibraries = clazz.getDeclaredField(&quot;nativeLibraries&quot;);    //acceso a Vector
nativeLibraries.setAccessible(true);
Vector java_lang_ClassLoader_NativeLibrary = (Vector) nativeLibraries.get(this);    //colección de  NativeLibrary's
for (Object lib : java_lang_ClassLoader_NativeLibrary) {
    Method finalize = lib.getClass().getDeclaredMethod(&quot;finalize&quot;, new Class[0]);    //acceso a finalize()
    finalize.setAccessible(true);
    finalize.invoke(lib, new Object[0]);    //finalize()
}
...
</pre>
<p>Sin embargo según algunos reportes no hay garantías de que esto funcione en todo caso.</p>
<p>En este <a href="http://snipplr.com/view/24224/class-loader-which-close-opened-jar-files/" target="_blank">enlace</a> se ve una clase que pertenece a las herramientas de <strong>Hinbernate</strong>. Es también una extensión de <span style="color:#000000;"><code>URLClassLoader</code></span> que realiza la misma tarea mencionada anteriormente pero en una forma más elaborada.</p>
<h3 style="text-align:left;"><strong>Método close() en la API desde JDK 7</strong></h3>
<p>Posiblemente la solución definitiva y tan esperada. Desde el <span style="text-decoration:underline;">build 48 del JDK7</span>  la clase <span style="color:#000000;"><code>URLClassLoader</code></span> posee el método <span style="color:#000000;"><code>close()</code></span>  implementando la interface <span style="color:#000000;"><code>java.lang.Closeable</code></span>. Con este método se cierran todos los recursos previamente abiertos.</p>
<p>La sintaxis de una URL siguiendo el protocolo JAR es:</p>
<pre>  jar:&lt;url&gt;!/[&lt;entry&gt;]</pre>
<p>En el siguiente ejemplo se crea un <span style="color:#000000;"><code>URLClassLoader</code></span> desde la URL <em>jar:file:/c:/users/user/test/TestJAR.jar!/</em>. Se carga la clase <em>SomeClass.class</em> y se invoca su método <span style="color:#000000;"><code>main(String[] args)</code></span> el cual imprime «Hola Mundo» en consola. Luego se cierra el <span style="color:#000000;"><code>URLClassLoader</code></span> con el método <span style="color:#000000;"><code>close()</code></span> y se intenta eliminar el archivo <em>TestJAR.jar</em>. Fue probado en Windows XP y 7.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
import java.net.*;
import java.io.*;

class TestJARClose {

    public static void main(String[] args) {
        try {
            //ubicación del archivo JAR
            String path = &quot;c:\\users\\user\\test\\TestJAR.jar&quot;;

            //crear URL siguiendo el protocolo JAR (también funcionaría con el protocolo FILE)
            String jarName = ((new File(path)).toURI()).toString();
            URL url = new URL(&quot;jar&quot;, &quot;&quot;, jarName + &quot;!/&quot;);
            System.out.println(&quot;* JAR URL: &quot; + url);

            //crear URLClassLoader desde la URL
            URLClassLoader loader = new URLClassLoader(new URL[] {url});
            Class c = loader.loadClass(&quot;SomeClass&quot;);              //cargar clase
            Method main = c.getMethod(&quot;main&quot;, String[].class);    //obtener método main(String[] args)
            main.invoke(null, (Object) new String[0]);            //invocar

            //utilizar close() para liberar el JAR
            loader.close();

            //eliminar el JAR
            if (new File(path).delete()) {
                System.out.println(&quot;* JAR eliminado.&quot;);
            } else {
                System.out.println(&quot;* No se puede eliminar JAR.&quot;);
            }
        } catch (Exception e) {    //en caso de excepción
            e.printStackTrace();
        }
    }
}
</pre>
<p>La salida es:</p>
<div style="overflow:auto;border:1px solid #585858;background-color:black;color:white;">* JAR URL: jar:file:/c:/users/user/test/TestJAR.jar!/<br />
Hola Mundo<br />
* JAR eliminado.</div>
<p>También vale decir que el método <span style="color:#000000;"><code>close()</code></span> no tiene porque ser invocado unicamente para finalizar definitivamente el ciclo de vida de un <span style="color:#000000;"><code>URLClassLoader</code></span>. Este método también se puede invocar inmediatamente luego de haber cargado los recursos necesarios, pero esto teniendo en cuenta que luego no se podrán cargar más recursos desde ese mismo <span style="color:#000000;"><code>URLClassLoader</code></span>, de lo contrario se generaría una <span style="color:#000000;"><code>ClassNotFoundException</code></span>.</p>
<hr />
<p><strong>Más información:</strong><br />
* <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5041014" target="_blank">Bug ID<strong>:</strong> 5041014</a><br />
* <a href="http://blogs.oracle.com/CoreJavaTechTips/entry/closing_a_urlclassloader" target="_blank">Core Java Technologies Tech Tips &#8211; Closing a URLClassLoader</a></p>
<p><strong>Documentación:</strong><br />
* <a href="http://download.oracle.com/javase/7/docs/api/java/net/URLClassLoader.html" target="_blank">Java<img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2122.png" alt="™" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Platform, Standard Edition 6 API Specification &#8211; URLClassLoader</a></p>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/09/10/java-archivos-jar-bloqueados-por-urlclassloader-en-windows/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2654</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>
	</item>
		<item>
		<title>Java &#8211; Proyecto Coin (JSR 334) &#8211; Cambios en el lenguaje para aumentar la productividad</title>
		<link>https://darkbyteblog.wordpress.com/2011/07/31/java-proyecto-coin-jsr-334-cambios-en-el-lenguaje-para-aumentar-la-productividad/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/07/31/java-proyecto-coin-jsr-334-cambios-en-el-lenguaje-para-aumentar-la-productividad/#respond</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Sun, 31 Jul 2011 08:09:05 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[7]]></category>
		<category><![CDATA[diamond]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jdk7]]></category>
		<category><![CDATA[jsr 334]]></category>
		<category><![CDATA[multi-catch]]></category>
		<category><![CDATA[project coin]]></category>
		<category><![CDATA[safevarargs]]></category>
		<category><![CDATA[strings]]></category>
		<category><![CDATA[switch]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2556</guid>

					<description><![CDATA[Uno de los cuatro JSRs (Java Especification Requests) que se incluyen en el nuevo JDK 7 es el JSR 334, conocido también como el Proyecto Coin. Esta propuesta, titulada como «Small Enhancements to the JavaTM Programming Language» (en español: pequeñas mejoras en el lenguaje de programación JavaTM), es sucesora del antiguo JSR 201 (incluido en [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Uno de los cuatro JSRs (Java Especification Requests) que se incluyen en el nuevo JDK 7 es el JSR 334, conocido también como el Proyecto Coin.</p>
<p>Esta propuesta, titulada como «Small Enhancements to the JavaTM Programming Language» (en español: pequeñas mejoras en el lenguaje de programación JavaTM), es sucesora del antiguo JSR 201 (incluido en el JDK 5) el cual fue responsable de notables extensiones en el lenguaje Java al introducir Enumeraciones, Autoboxing, un bucle for mejorado (llamado también: «for each») e importaciones estáticas, todas estas muy bien recordadas y agradecidas hasta el día de hoy.</p>
<p><span id="more-2556"></span></p>
<p>El objetivo fue, sigue y seguirá siendo, realzar la productividad del programador con una nueva sintaxis simplificada (usualmente denominado «azúcar sintáctico») que a grandes rasgos es capas de resolver los problemas del día a día evitando código innecesario o repetitivo mejorando la legibilidad y  así fomentar un desarrollo con menor margen de error. Todo esto gracias a extender el análisis en tiempo de compilación, y al menos por ahora sin romper la retrocompatibilidad.</p>
<p>La frase que resume el objetivo:</p>
<blockquote><p>Making things programmers do everyday easier.</p></blockquote>
<h2 style="text-align:left;">Proyecto Coin (JSR 334)</h2>
<hr />
<p>A continuación los pequeñas mejoras introducidas en el lenguaje.</p>
<h3 style="text-align:left;">Strings en estructuras de control switch</h3>
<p>Ahora se pueden evaluar cadenas de caracteres (Strings) mediante estructuras de control <span style="color:#000000;"><code>switch</code></span>. Un ejemplo:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
String color = &quot;celeste&quot;;

switch (color) {    //evaluación del string
    case &quot;blanco&quot;:
        System.out.println(&quot;#FFFFFF&quot;);
        break;
    case &quot;celeste&quot;:
        System.out.println(&quot;#00FFFF&quot;);
        break;
    case &quot;amarillo&quot;:
        System.out.println(&quot;#FFFF00&quot;);
        break;
    default:
        System.out.println(&quot;Color desconocido&quot;);
}
...
</pre>
<p>Un inconveniente aquí es como ignorar las mayúsculas de ser necesario. Podríamos intentar aplicarlo así:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
case &quot;BLANCO&quot;:
case &quot;blanco&quot;:
    System.out.println(&quot;#FFFFFF&quot;);
    break;
case &quot;CELESTE&quot;:
case &quot;celeste&quot;:
    System.out.println(&quot;#00FFFF&quot;);
    break;
case &quot;AMARILLO&quot;:
case &quot;amarillo&quot;:
    System.out.println(&quot;#FFFF00&quot;);
    break;
...
</pre>
<p>De esta forma las cadenas «celeste» y «CELESTE» serian validas pero no en el caso de una cadena «CeLeStE» o «cElEsTe», y crear un <span style="color:#000000;">case</span> para todas las posibilidades de la cadena seria inadecuado por lo cual esta estructura <span style="color:#000000;"><code>switch</code></span> no es aplicable en todos los casos.</p>
<p>Sin embargo, cuando su uso es posible, puede evitar el constante anidamiento de las típicas sentencias <em>if-then-else</em> para mejorar la legibilidad y potencialmente mejorar el rendimiento en este tipo de operaciones basadas en cadenas de caracteres.</p>
<p>Otros inconvenientes del <span style="color:#000000;"><code>switch</code></span> es que no puede evaluar un caso <span style="color:#000000;"><code>null</code></span>, y con esta nueva capacidad de manejar cadenas ah dejado más compleja la tarea del compilador.</p>
<h3 style="text-align:left;">Literales numéricos de tipo binario</h3>
<p>Junto a las previamente posibles representaciones numéricas: «1» (Decimal), «01» (Octal), y «0x1» (hexadecimal), se añade la posibilidad de utilizar «0b1» para representar literalmente un número binario.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
int binario = 0b00000001;    //binario=00000001 - decimal=1
System.out.println(&quot;El valor decimal de binario es: &quot; + binario);
System.out.println(&quot;El valor decimal de 00000010 es: &quot; + 0b00000010);
System.out.println(&quot;El valor decimal de 00000011 es: &quot; + 0B00000011);    //se puede utilizar &quot;B&quot; como &quot;b&quot;
...
</pre>
<p>En algunos casos resulta más natural la expresión binaria y elimina la necesidad de transformar en hexadecimal.</p>
<h3 style="text-align:left;">Barra baja en literales numéricos</h3>
<p>Las personas tienen que lidiar cotidianamente con números que están divididos por separadores, por ejemplo:</p>
<p>Numero telefónico:  123-123-123<br />
Tarjeta de crédito: 1234-1234-1234-1234<br />
Dinero: $1.000.000</p>
<p>Estos separadores simplifican el trabajo de visualización al ojo humano permitiendo recordar con facilidad.</p>
<p>Desde el JDK 7 se puede utilizar el símbolo de la barra baja «_» en el valor numérico de un dato declarado para mejorar su legibilidad. Solo se permite entre dígitos y no al principio o al final. Esta inclusión no afectará el valor, el compilador simplemente ignorará este símbolo.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
int telNumber = 123_123_123;
long creditCard = 1234_1234_1234_1234L;
int money = 1_000_000;
...
</pre>
<h3 style="text-align:left;">Control de excepciones</h3>
<p>El sistema de control de excepciones se ah visto mejorado por dos pequeñas pero importantes reformas:</p>
<ul>
<li><strong>Captura de múltiples excepciones</strong></li>
</ul>
<p style="padding-left:30px;">Se puede utilizar solo una clausula de <span style="color:#000000;"><code>catch</code></span> para capturar varios tipos de excepciones evitando tener que crear cada <span style="color:#000000;"><code>catch</code></span> para cada excepción como siempre fue usual de hacer.</p>
<p style="padding-left:30px;">Si se considera un caso típico, muchas veces cuando manejamos diferentes excepciones puede suceder que esperamos actuar en consecuencia para todas de la misma manera, por ejemplo:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
try {
    //realiza una operación arriesgada
} catch (A ex1) {
    log.append(ex1);    //manejar la excepción A
} catch (B ex2) {
    log.append(ex2);    //manejar la excepción B
} catch (C ex3) {
    log.append(ex3);    //manejar la excepción C
}
...
</pre>
<p style="padding-left:30px;">Sin duda se ve repetitivo. Quizás a alguien se le podría ocurrir tratar de simplificar el código del ejemplo anterior capturando <span style="color:#000000;"><code>java.lang.Exception</code></span> de la cual extienden las demás excepciones:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
try {
    //realiza una operación arriesgada
} catch (Exception ex) {
    log.append(ex);    //manejar todas las excepciones
}
...
</pre>
<p style="padding-left:30px;">Aunque el código ahora es más claro, puede estar generando más problemas de los que debería de resolver porque esta capturando las excepciones que esperaba y además cualquier otra excepción que pudiera ocurrir. En general no es adecuado capturar más excepciones de las que se espera, a esto se le considera mala practica e incluso un «anti-patron» de diseño.</p>
<p style="padding-left:30px;">Pero desde el JDK7, la nueva sintaxis permite una mejor solución para estos casos:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
try {
    //realiza una operación arriesgada
} catch (A | B | C ex) {
    log.append(ex);    //maneja la excepción A, B y C
}
...
</pre>
<p style="padding-left:30px;">Esto evita la presencia del código redundante mejorando el mantenimiento y legibilidad, sin tener que recurrir a malas practicas como la mencionada anteriormente.</p>
<ul>
<li> <strong>Precisión al regenerar excepciones</strong></li>
</ul>
<p style="padding-left:30px;">Permite regenerar sub-clases de la excepción capturada sin la necesidad de tener que añadir esta en la clausula<span style="color:#000000;"> <code>throws</code></span> de la declaración del método.</p>
<p style="padding-left:30px;">Aunque no es una situación típica, si se necesita capturar todas las excepciones posibles y realizar alguna tarea antes de regenerar la excepción capturada, se puede capturar la clase <span style="color:#000000;"><code>java.lang.Throwable</code></span> de la cual extienden todos los errores y excepciones. Esto significaría tener que añadir también a <span style="color:#000000;"><code>Throwable</code></span> en la clausula <span style="color:#000000;"><code>throws</code></span> de la declaración del método y propagarla hacia arriba en la pila de llamadas. Esto desde el JDK7 ya no es necesario. Este es un ejemplo:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
class FinalRethrow {

    //extiende Exception
    static class SomeException extends Exception {
    }

    //extiende Exception
    static class AnotherException extends Exception {
    }

    //punto de entrada
    public static void main(String[] args) {
        try {
            catchAll();
        } catch (SomeException | AnotherException ex) {
            ex.printStackTrace();
        }
    }

    //no estamos forzados a propagar Throwable, solo las sub-clases en las que estamos interesados
    static void catchAll() throws SomeException, AnotherException {
        try {
            //realizar operaciones arriesgadas
            doSomething();
            doAnotherThing();
        } catch (final Throwable ex) {    //capturamos Throwable
            //algo para hacer aquí...
            throw ex;    //re-genera el sub-tipo de excepción
        }
    }

    //propaga SomeException
    static void doSomething() throws SomeException {
        throw new SomeException();    //genera SomeException
    }

    //propaga AnotherException
    static void doAnotherThing() throws AnotherException {
        throw new AnotherException();    //genera AnotherException
    }
}
...
</pre>
<p style="padding-left:30px;">El resultado de la traza:</p>
<div style="overflow:auto;border:1px solid #585858;background-color:black;color:white;">FinalRethrow$SomeException<br />
at FinalRethrow.doSomething(FinalRethrow.java:45)<br />
at FinalRethrow.catchAll(FinalRethrow.java:34)<br />
at FinalRethrow.main(FinalRethrow.java:24)</div>
<p style="text-align:left;padding-left:30px;">El compilador es lo suficientemente inteligente para saber que excepciones necesitan ser capturadas, y permitirá regenerar la excepción que sea capturada aunque <span style="color:#000000;"><code>Throwable</code></span> no este declarado con la clausula <span style="color:#000000;"><code>throws</code></span> en el método.</p>
<p style="text-align:left;padding-left:30px;">El uso de la clave <span style="color:#000000;"><code>final</code></span> en un bloque <span style="color:#000000;"><code>catch</code></span> no es algo nuevo, se podía utilizar desde antes, y como lo sugiere su semántica significa que una excepción que es final no puede reasignarse. En este contexto de regeneración final de una excepción se sigue utilizando la clave <span style="color:#000000;"><code>final</code></span> con el mismo objetivo, le permite al compilador saber que el tipo de excepción capturado no podrá ser alterado ya que de lo contrario se podría confundir al compilador en la inferencia del sub-tipo de excepción que debe de regenerar. Sin embargo no es obligatorio utilizar la clave final, posiblemente la excepción es implícitamente tratada como <span style="color:#000000;"><code>final</code></span> por el compilador.</p>
<p style="text-align:left;padding-left:30px;">Resumiendo, con esta nueva posibilidad si se tiene un método que maneja de la misma forma varias excepciones que tienen todas una clase base en común y luego de realizar alguna operación (por ejemplo guardar logs) las necesita regenerar propagandolas, puede manejarlas todas capturando simplemente la clase base y propagando solo  las excepciones de las sub-clases.</p>
<p style="text-align:left;padding-left:30px;">Esto puede parecer en su utilidad algo similar al control de múltiples excepciones explicado antes.</p>
<h3 style="text-align:left;">Inferencia de tipos en la creación de instancias genéricas</h3>
<p>Los Generics (tipos genéricos o parametrizados), introducidos en el JDK5 años atrás, aunque sin ser perfectos han tenido un efecto positivo en el lenguaje al mejorar la seguridad de tipos evitando potenciales errores en tiempo de ejecución. Una desventaja es que le agregan cierta complejidad a la sintaxis, y ese es el motivo aquí para tratar de corregir en parte su utilización.</p>
<p>Estos son ejemplos típicos de su uso:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
List list = new ArrayList();
Map&gt; map = new HashMap&gt;();
...
</pre>
<p>Ahora gracias a la inferencia de tipos, podemos escribir lo anterior de la siguiente forma:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
List lista = new ArrayList&lt;&gt;();
Map&gt; tabla = new HashMap&lt;&gt;();
...
</pre>
<p>Se utiliza el operador diamante «&lt;&gt;» en la construcción para indicar que se infieren los mismos tipos de la declaración. Esto elimina código repetitivo y lo hace más agradable a la vista.</p>
<h3 style="text-align:left;"> Gestión automática de recursos</h3>
<p>También conocido por su sigla en ingles ARM (Automatic Resource Management), se trata de un nuevo tipo de  sentencia comprendida por el compilador, se denomina <em>try-with-resources </em>y pretende simplificar el trabajo del la ya conocida <em>try-catch-finally </em>que se utiliza sobre flujo de datos hacia archivos, conexiones a bases de datos, etc. La idea consiste en que no se necesita escribir un bloque tipo <span style="color:#000000;"><code>finally</code></span> para asegurarnos de cerrar los flujos de datos abiertos, ya que esto ocurre implícitamente sin este.</p>
<p>Tomará como ejemplo un pequeño código que escribe la mítica frase «HolaMundo» en un archivo <em>test.txt</em> en el directorio Home del usuario. Este es un sencillo caso de <em>try-catch-finally:</em></p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
String filePath = System.getProperty(&quot;user.home&quot;) + File.separator + &quot;test.txt&quot;;
BufferedWriter bw = null;
try {
    //abrir flujo de datos hacia el destino
    bw = new BufferedWriter(new FileWriter(filePath));
    bw.write(&quot;Hola Mundo&quot;);
} catch (IOException ex) {    //en caso de excepcion
    ex.printStackTrace();
} finally {    //cerrar los recursos abiertos
    if (bw != null) {
        try {
            bw.close();
        } catch (IOException ex) {
            //nada que hacer...
        }
    }
}
...
</pre>
<p>Con el nuevo <em>try-with-resources </em> se podría escribir de esta forma:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
String filePath = System.getProperty(&quot;user.home&quot;) + File.separator + &quot; test.txt&quot;;
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) {    //abrir flujo de datos hacia el destino
    bw.write(&quot;Hola Mundo&quot;);
    bw.newLine();
} catch (IOException ex) {    //en caso de excepción
    ex.printStackTrace();
}
...
</pre>
<p>En este ejemplo el recurso en cuestión era unicamente <span style="color:#000000;"><code>bw</code></span>, un objeto <span style="color:#000000;"><code>BufferedReader</code></span>. Sin embargo se puede declarar más de un recurso en la sentencia <em>try-with-resources </em> y todos estos serán cerrados automáticamente. La única condición es que deben implementar la interface <span style="color:#000000;"><code>java.lang.AutoCloseable</code></span>.</p>
<p>Incluso se puede seguir utilizando el bloque <span style="color:#000000;"><code>finally</code></span> al final de la sentencia <em>try-with-resources</em>, se ejecutará cuando los recursos creados en el <span style="color:#000000;"><code>try</code></span> se hayan cerrado.</p>
<h3 style="text-align:left;"> Métodos con argumentos variables y parámetros formales no disponibles en tiempo de ejecución</h3>
<p>Esto se trata de una mejora en el compilador para el mejor reconocimiento a través de advertencias sobre un problema que involucra dos extensiones incluidas en el lenguaje desde el JDK5: los Generics (tipos genéricos o parametrizados)  y los argumentos variables (varargs). Ambas muy útiles y con buena aceptación pero también con sus respectivos inconvenientes.</p>
<p>Para comenzar, los tipos genéricos como <span style="color:#000000;"><code>ArrayList&lt;Sring&gt;</code></span> o <span style="color:#000000;"><code>HashMap&lt;Integer&gt;</code></span> son tipos no cosificables (en ingles: non-reifiable), es decir no están completamente disponibles en tiempo de ejecución. En tiempo de compilación ocurre un proceso llamado «borrado de tipos» (en ingles: tipe erasure) en el cual el compilador borra la información relacionada con los tipos de parámetros y tipos de argumentos, esto es necesario para mantener la retrocompatibilidad con librerías antiguas a la introducción de los Generics.</p>
<p>Puede resultar posible que a una variable de un tipo genérico se le refiera otra variable que no lo es. Esta situación se denomina «polución del montículo» (heap pollution), y ocurre cuando el programa realiza una operación que pueda generar una advertencia no controlada (unchecked warning). Una advertencia no controlada es generada si en tiempo de compilación o en tiempo de ejecución la corrección de una operación que involucre un tipo generico, en un casting o la llamada a un método, no puede ser verificada. Un simple ejemplo:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
List l = new ArrayList();
List ls = l;                  //warning: [unchecked] unchecked conversion
l.add(0, new Integer(42));    //warning: [unchecked] unchecked call to add(int,E) as a member of the raw type List
String s = ls.get(0);         //ClassCastException
...
</pre>
<p>Esta asignación de un tipo <span style="color:#000000;"><code>List&lt;Number&gt;</code></span> con <span style="color:#000000;"><code>List&lt;String&gt;</code></span>, genera una advertencia, pero por retrocompatibilidad el borrado de tipos convierte los dos tipos a <span style="color:#000000;"><code>List</code></span> y entonces el compilador permite la operación. Esto es una situación de heap pollution.</p>
<p>El compilador también genera una advertencia en el método <span style="color:#000000;"><code>l.add()</code></span>, pero no puede determinar si la variable <span style="color:#000000;"><code>l</code></span> refiere al tipo <span style="color:#000000;"><code>List&lt;String&gt;</code></span> o <span style="color:#000000;"><code>List&lt;Number&gt;</code></span>, luego del borrado de tipos cualquier <span style="color:#000000;"><code>Object</code></span> puede ser añadido a la lista,  por lo que ocurre otra situación de heap pollution. Y como trágico final, el ultimo método <span style="color:#000000;"><code>ls.get()</code></span> es una sentencia valida en tiempo de compilación, pero en tiempo de ejecución genera una inesperada <span style="color:#000000;"><code>ClassCastException</code></span>.</p>
<p>Justamente un caso de excepciones no controladas es cuando se invoca a un método que recibe argumentos variables de tipos genéricos, la información del tipo en el parámetro del método no esta completamente disponible en tiempo de ejecución. La extensión en el lenguaje para argumentos variables esta implementada a través de arreglos, es decir el compilador convierte los argumentos variables en arreglos, y en estos los tipos son guardados internamente para ser usados en tiempo de ejecución. Sin embargo el lenguaje Java no admite crear arreglos de tipos genéricos, y los arreglos no pueden guardar la información necesaria para representar un tipo generico. Existen varios métodos en la API de Java que generan estas advertencias, este es uno de ellos:</p>
<pre>public static &lt;T&gt; boolean Collections.addAll(Collection&lt;? super T&gt; c, T... elements)</pre>
<p>Un ejemplo de su uso:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
List stringList1 = new ArrayList();
List stringList2 = new ArrayList();
List&gt; allStringLists = new ArrayList&gt;();

Collections.addAll(stringList1, &quot;string1&quot;, &quot;string2&quot;, &quot;string3&quot;);
Collections.addAll(stringList1, &quot;string4&quot;, &quot;string4&quot;, &quot;string6&quot;);
//warning: [unchecked] unchecked generic array creation of type java.util.List[] for varargs parameter
Collections.addAll(allStringLists, stringList1, stringList2);
...
</pre>
<p>Por supuesto que los métodos de la API son seguros, y estas advertencias son inútiles para estos casos. Por lo tanto el usuario puede legalmente suprimir las advertencias, en versiones anteriores al JDK7 se puede utilizar la ya conocida anotación <span style="color:#000000;"><code>@SuppressWarnings("unchecked")</code></span>.</p>
<p>Esencialmente estas advertencias indican la posibilidad de heap pollution en un método, y que consecuentemente se pueden obtener inesperadas excepciones <span style="color:#000000;"><code>ClassCastException</code></span>. En los compiladores de Java SE 5 y 6 la llamada a un método conflictivo genera estas advertencias, pero no sucede lo mismo sobre la declaración del método. A todo esto, en el JDK7  se sigue el razonamiento de que la declaración un método conflictivo no es en realidad el causante de una situación de heap pollution, pero su existencia si contribuye a que esto pueda ocurrir, por lo que el compilador genera también una advertencia en la declaración de dichos métodos.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
//JDK7 - warning: [unchecked] Possible heap pollution from parameterized vararg type List
public static void faultyMethod(List... l) {
    Object[] objectArray = l;
    objectArray[0] = Arrays.asList(new Integer(42));    //heap pollution
    String s = l[0].get(0);                             //ClassCastException
}
...
//JDK5-6 - warning: [unchecked] unchecked generic array creation for varargs parameter of type List[]
faultyMethod(Arrays.asList(&quot;Hola&quot;), Arrays.asList(&quot;Mundo&quot;));
...
</pre>
<p>En adición, también se introduce una nueva anotación especifica para suprimir estas advertencias. En un método conflictivo hay tres posibilidades para suprimir las advertencias generadas por el compilador:</p>
<ul>
<li>Utilizar la anotación <span style="color:#000000;"><code>@SafeVarargs</code></span>, en métodos estáticos, finales y no constructores. Esto suprime también las advertencias desde las llamadas al método, por lo que se asume que no se realizan operaciones inseguras con los parámetros. Métodos conflictivos de la API de Java como el visto anteriormente lo utilizan.</li>
<li>Utilizar la anotación <span style="color:#000000;"><code>@SuppressWarnings({"unchecked", "varargs"})</code></span>. Al contrario que la anterior, esta no suprime las advertencias en las llamadas al método.</li>
<li>Utilizar la opción <span style="color:#000000;"><code>-Xlint:varargs</code></span> en el compilador.</li>
</ul>
<h3 style="text-align:center;"><strong>&#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211;</strong></h3>
<p style="text-align:left;">Se llegaron a conocer también algunas mejoras que al final no fueron incluidas para la versión 7, por ejemplo: el operador seguro (null-safe) para no actuar sobre referencias nulas, o la construcción y asignación inmediata en colecciones al igual como se puede hacer con arreglos.</p>
<p style="text-align:left;">Para Java 8 se introducirá el proyecto Coin II, en el cual seguramente estarán las mejoras que no pudieron concretarse en Java 7 junto a otras nuevas.</p>
<hr />
<p><strong>Más información:</strong><br />
<strong>* </strong><a href="http://download.oracle.com/javase/7/docs/technotes/guides/language/enhancements.html#javase7" target="_blank">Oracle &#8211; Enhancements in Java SE 7</a></p>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/07/31/java-proyecto-coin-jsr-334-cambios-en-el-lenguaje-para-aumentar-la-productividad/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2556</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>
	</item>
		<item>
		<title>Java 7 – Versión final</title>
		<link>https://darkbyteblog.wordpress.com/2011/07/28/java-7-%e2%80%93-version-final/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/07/28/java-7-%e2%80%93-version-final/#respond</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Thu, 28 Jul 2011 22:42:10 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Noticias]]></category>
		<category><![CDATA[7]]></category>
		<category><![CDATA[final]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jdk]]></category>
		<category><![CDATA[nuevo]]></category>
		<category><![CDATA[oracle]]></category>
		<category><![CDATA[version]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2538</guid>

					<description><![CDATA[El pasado día 7 del mes 7 de 2011 y después de casi 5 años desde la aparición del JDK 6, Oracle presentaba oficialmente el JDK 7 y dejaba disponible la descarga de la versión RC (Release Candidate). En este importante evento participaron organizaciones como IBM, Eclipse Foundation, etc. y también tuvieron la palabra los [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg"><img data-attachment-id="2541" data-permalink="https://darkbyteblog.wordpress.com/2011/07/28/java-7-%e2%80%93-version-final/javaseven-logo/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg" data-orig-size="230,218" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="javaSeven-logo" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg?w=230" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg?w=230" class="aligncenter size-full wp-image-2541" title="javaSeven-logo" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg 230w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg?w=150&amp;h=142 150w" sizes="(max-width: 230px) 100vw, 230px" /></a></p>
<p>El pasado día 7 del mes 7 de 2011 y después de casi 5 años desde la aparición del JDK 6, <strong>Oracle</strong> presentaba oficialmente el JDK 7 y dejaba disponible la descarga de la versión RC (Release Candidate). En este importante evento participaron organizaciones como <strong>IBM</strong>, <strong>Eclipse Foundation</strong>, etc. y también tuvieron la palabra los representantes de algunos JUG (Java User Groups). Se puede ver la retransmisión Webcast de este evento en el sitio web de <strong>Oracle</strong>, aquí el <a href="http://www.oracle.com/us/corporate/events/java7/index.html" target="_blank">enlace</a>.</p>
<p>En la fecha de hoy, 28/7/11, la versión final GA (General Acceptance) del JDK 7 ah sido publicada y esta lista para descargar. Las versiones disponibles son para <strong>Windows</strong>, <strong>Linux</strong> y <strong>Solaris</strong>. En el caso de <strong>Mac OS X</strong> tardará un poco más.  <span id="more-2538"></span> En general se dice que se han corregido 9,494 bugs, implementado 1,966 mejoras y realizado 9,018 cambios en el código. Estas son las principales características de esta nueva versión:</p>
<ul>
<li>Proyecto Coin (<em>JSR 334)</em>: algunos cambios en el lenguaje Java como poder utilizar estructuras de control switch con strings, gestión automática de recursos, mejora en los literales numéricos, múltiple captura de excepciones, etc.</li>
<li>APIs NIO2 (<em>JSR 203</em>): mejor soporte para la entrada/salida (I/O) asíncrona de datos , buena integración con el sistema de archivos subyacente, etc.</li>
<li>Concurrencia con el Framework Fork\Join (<em>JSR 166</em>): basado en el algoritmo de «dividir y conquistar» y pensado para  favorecer el paralelismo de las tareas aprovechando mejor los nuevos CPUs multi-nucleos.</li>
<li>Cambios en la JVM: se mejora el rendimiento, se incluye el <em>JSR292</em> añadiendo una nueva instrucción para facilitar la implementación de lenguajes de tipado dinamico (Groovy, JRuby, etc) mejorando el rendimiento de estos y se reemplaza el colector de basura Concurrent Mark-Sweep (CMS) por el Garbage-First (GF).</li>
<li>Soporte Unicode 6.0 y mejoras en la internacionalización.</li>
<li>Clase <span style="color:#000000;">java.util.Objects</span>: clase de utilidades similar a <span style="color:#000000;">java.util.Arrays</span> y <span style="color:#000000;">java.util.Collections</span> pero especifica para objetos.</li>
<li>Mejoras en Swing: fácil desarrollo mixto de componentes HeavyWeigth y LightWeigth, soporte para ventanas translucidas y no rectangulares, etc.</li>
<li>Mejoras para la tecnología de aplicaciones ricas de internet (RIA).</li>
<li>Actualización de la arquitectura class-loader. Añadido método close() en URLClassLoader.</li>
<li>Mejoras en Java 2D.</li>
<li>JDBC 4.1</li>
</ul>
<p>Los IDEs <strong>NetBeans</strong> (7.0), <strong>Eclipse</strong>(3.7, 3.8, 4.1, 4.2), e <strong>IntelliJ</strong>(10.5) soportan Java 7.</p>
<hr />
<p><strong>Más información:</strong><br />
* <a href="http://www.oracle.com/technetwork/java/javase/6u25releasenotes-356444.html" target="_blank">Oracle &#8211; JDK 7 Release Notes </a><br />
* <a href="http://www.oracle.com/us/corporate/events/java7/index.html" target="_blank">Oracle &#8211; Java 7 Celebration Webcast</a><br />
* <a href="http://www.jcp.org/en/jsr/detail?id=166" target="_blank">JSR 166: Concurrency Utilities</a><br />
* <a href="http://jcp.org/en/jsr/detail?id=203" target="_blank">JSR 203: More New I/O APIs for the JavaTM Platform («NIO.2»)</a><br />
* <a href="http://jcp.org/en/jsr/detail?id=292" target="_blank">JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform</a><br />
* <a href="http://www.jcp.org/en/jsr/detail?id=334" target="_blank">JSR 334: Small Enhancements to the JavaTM Programming Language</a> <strong></strong></p>
<p><strong>Descargas:</strong> <strong></strong><br />
* <a href="http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html" target="_blank">Oracle &#8211; Java SE Downloads</a></p>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/07/28/java-7-%e2%80%93-version-final/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2538</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/javaseven-logo.jpg" medium="image">
			<media:title type="html">javaSeven-logo</media:title>
		</media:content>
	</item>
		<item>
		<title>Java &#8211; TextPad Demo</title>
		<link>https://darkbyteblog.wordpress.com/2011/07/23/java-textpad-demo/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/07/23/java-textpad-demo/#comments</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Sat, 23 Jul 2011 01:15:57 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[editor]]></category>
		<category><![CDATA[ejemplo]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JTextArea]]></category>
		<category><![CDATA[texto]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2357</guid>

					<description><![CDATA[Estas son las imágenes .PNG 32&#215;32 que se utilizan como recursos en el proyecto:]]></description>
										<content:encoded><![CDATA[<p style="text-align:center;"><img class="aligncenter" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg?w=378&#038;h=210" alt="" width="378" height="210" /><br />
<span id="more-2357"></span></p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
/**
 * TPEditor.java
 *
 * Ejemplo de un editor básico para documentos de texto plano utilizando la biblioteca gráfica Swing.
 * Funciona desde Java SE 5.0 en adelante.
 */

package textpademo;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.undo.UndoManager;

/**
 * Clase principal donde se construye la GUI del editor.
 *
 * @author Dark[byte]
 */
public class TPEditor {

    private JFrame jFrame;            //instancia de JFrame (ventana principal)
    private JMenuBar jMenuBar;        //instancia de JMenuBar (barra de menú)
    private JToolBar jToolBar;        //instancia de JToolBar (barra de herramientas)
    private JTextArea jTextArea;      //instancia de JTextArea (área de edición)
    private JPopupMenu jPopupMenu;    //instancia de JPopupMenu (menú emergente)
    private JPanel statusBar;         //instancia de JPanel (barra de estado)

    private JCheckBoxMenuItem itemLineWrap;         //instancias de algunos items de menú que necesitan ser accesibles
    private JCheckBoxMenuItem itemShowToolBar;
    private JCheckBoxMenuItem itemFixedToolBar;
    private JCheckBoxMenuItem itemShowStatusBar;
    private JMenuItem mbItemUndo;
    private JMenuItem mbItemRedo;
    private JMenuItem mpItemUndo;
    private JMenuItem mpItemRedo;

    private JButton buttonUndo;    //instancias de algunos botones que necesitan ser accesibles
    private JButton buttonRedo;

    private JLabel sbFilePath;    //etiqueta que muestra la ubicación del archivo actual
    private JLabel sbFileSize;    //etiqueta que muestra el tamaño del archivo actual
    private JLabel sbCaretPos;    //etiqueta que muestra la posición del cursor en el área de edición

    private boolean hasChanged = false;    //el estado del documento actual, no modificado por defecto
    private File currentFile = null;       //el archivo actual, ninguno por defecto

    private final EventHandler eventHandler;          //instancia de EventHandler (la clase que maneja eventos)
    private final ActionPerformer actionPerformer;    //instancia de ActionPerformer (la clase que ejecuta acciones)
    private final UndoManager undoManager;            //instancia de UndoManager (administrador de edición)

    /**
     * Punto de entrada del programa.
     *
     * Instanciamos esta clase para construir la GUI y hacerla visible.
     *
     * @param args argumentos de la línea de comandos.
     */
    public static void main(String[] args) {
        //construye la GUI en el EDT (Event Dispatch Thread)
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TPEditor().jFrame.setVisible(true);    //hace visible la GUI creada por la clase TPEditor
            }
        });
    }

    /**
     * Constructor de la clase.
     *
     * Se construye la GUI del editor, y se instancian clases importantes.
     */
    public TPEditor() {
        try {    //LookAndFeel nativo
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception ex) {
            System.err.println(ex);
        }

        //construye un JFrame con título
        jFrame = new JFrame(&quot;TextPad Demo - Sin Título&quot;);
        jFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

        //asigna un manejador de eventos para el cierre del JFrame
        jFrame.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent we) {
                actionPerformer.actionExit();    //invoca el método actionExit()
            }
        });

        eventHandler = new EventHandler();              //construye una instancia de EventHandler
        actionPerformer = new ActionPerformer(this);    //construye una instancia de ActionPerformer
        undoManager = new UndoManager();                //construye una instancia de UndoManager
        undoManager.setLimit(50);                       //le asigna un límite al buffer de ediciones

        buildTextArea();     //construye el área de edición, es importante que esta sea la primera parte en construirse
        buildMenuBar();      //construye la barra de menú
        buildToolBar();      //construye la barra de herramientas
        buildStatusBar();    //construye la barra de estado
        buildPopupMenu();    //construye el menú emergente

        jFrame.setJMenuBar(jMenuBar);                              //designa la barra de menú del JFrame
        Container c = jFrame.getContentPane();                     //obtiene el contendor principal
        c.add(jToolBar, BorderLayout.NORTH);                       //añade la barra de herramientas, orientación NORTE del contendor
        c.add(new JScrollPane(jTextArea), BorderLayout.CENTER);    //añade el area de edición en el CENTRO
        c.add(statusBar, BorderLayout.SOUTH);                      //añade la barra de estado, orientación SUR

        //configura el JFrame con un tamaño inicial proporcionado con respecto a la pantalla
        Dimension pantalla = Toolkit.getDefaultToolkit().getScreenSize();
        jFrame.setSize(pantalla.width / 2, pantalla.height / 2);

        //centra el JFrame en pantalla
        jFrame.setLocationRelativeTo(null);
    }

    /**
     * Construye el área de edición.
     */
    private void buildTextArea() {
        jTextArea = new JTextArea();    //construye un JTextArea

        //se configura por defecto para que se ajusten las líneas al tamaño del área de texto ...
        jTextArea.setLineWrap(true);
        //... y que se respete la integridad de las palaras en el ajuste
        jTextArea.setWrapStyleWord(true);

        //asigna el manejador de eventos para el cursor
        jTextArea.addCaretListener(eventHandler);
        //asigna el manejador de eventos para el ratón
        jTextArea.addMouseListener(eventHandler);
        //asigna el manejador de eventos para registrar los cambios sobre el documento
        jTextArea.getDocument().addUndoableEditListener(eventHandler);

        //remueve las posibles combinaciones de teclas asociadas por defecto con el JTextArea
        jTextArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK), &quot;none&quot;);    //remueve CTRL + X (&quot;Cortar&quot;)
        jTextArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK), &quot;none&quot;);    //remueve CTRL + C (&quot;Copiar&quot;)
        jTextArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK), &quot;none&quot;);    //remueve CTRL + V (&quot;Pegar&quot;)
    }

    /**
     * Construye la barra de menú.
     */
    private void buildMenuBar() {
        jMenuBar = new JMenuBar();    //construye un JMenuBar

        //construye el menú &quot;Archivo&quot;, a continuación se construyen los items para este menú
        JMenu menuFile = new JMenu(&quot;Archivo&quot;);

        //construye el item &quot;Nuevo&quot;
        JMenuItem itemNew = new JMenuItem(&quot;Nuevo&quot;);
        //le asigna una conbinación de teclas
        itemNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK));
        //le asigna un nombre de comando
        itemNew.setActionCommand(&quot;cmd_new&quot;);

        JMenuItem itemOpen = new JMenuItem(&quot;Abrir&quot;);
        itemOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK));
        itemOpen.setActionCommand(&quot;cmd_open&quot;);

        JMenuItem itemSave = new JMenuItem(&quot;Guardar&quot;);
        itemSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
        itemSave.setActionCommand(&quot;cmd_save&quot;);

        JMenuItem itemSaveAs = new JMenuItem(&quot;Guardar como...&quot;);
        itemSaveAs.setActionCommand(&quot;cmd_saveas&quot;);
        itemSaveAs.addActionListener(eventHandler);

        JMenuItem itemPrint = new JMenuItem(&quot;Imprimir&quot;);
        itemPrint.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_MASK));
        itemPrint.setActionCommand(&quot;cmd_print&quot;);

        JMenuItem itemExit = new JMenuItem(&quot;Salir&quot;);
        itemExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK));
        itemExit.setActionCommand(&quot;cmd_exit&quot;);

        menuFile.add(itemNew);    //se añaden los items al menú &quot;Archivo&quot;
        menuFile.add(itemOpen);
        menuFile.add(itemSave);
        menuFile.add(itemSaveAs);
        menuFile.addSeparator();
        menuFile.add(itemPrint);
        menuFile.addSeparator();
        menuFile.add(itemExit);
        //----------------------------------------------

        //construye el menú &quot;Editar&quot;, a continuación se construyen los items para este menú
        JMenu menuEdit = new JMenu(&quot;Editar&quot;);

        mbItemUndo = new JMenuItem(&quot;Deshacer&quot;);
        mbItemUndo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
        mbItemUndo.setEnabled(false);
        mbItemUndo.setActionCommand(&quot;cmd_undo&quot;);

        mbItemRedo = new JMenuItem(&quot;Rehacer&quot;);
        mbItemRedo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
        mbItemRedo.setEnabled(false);
        mbItemRedo.setActionCommand(&quot;cmd_redo&quot;);

        JMenuItem itemCut = new JMenuItem(&quot;Cortar&quot;);
        itemCut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
        itemCut.setActionCommand(&quot;cmd_cut&quot;);

        JMenuItem itemCopy = new JMenuItem(&quot;Copiar&quot;);
        itemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
        itemCopy.setActionCommand(&quot;cmd_copy&quot;);

        JMenuItem itemPaste = new JMenuItem(&quot;Pegar&quot;);
        itemPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK));
        itemPaste.setActionCommand(&quot;cmd_paste&quot;);

        JMenuItem itemSearch = new JMenuItem(&quot;Buscar&quot;);
        itemSearch.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, ActionEvent.CTRL_MASK));
        itemSearch.setActionCommand(&quot;cmd_search&quot;);

        JMenuItem itemSearchNext = new JMenuItem(&quot;Buscar siguiente&quot;);
        itemSearchNext.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0));
        itemSearchNext.setActionCommand(&quot;cmd_searchnext&quot;);

        JMenuItem itemGotoLine = new JMenuItem(&quot;Ir a la línea...&quot;);
        itemGotoLine.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, ActionEvent.CTRL_MASK));
        itemGotoLine.setActionCommand(&quot;cmd_gotoline&quot;);

        JMenuItem itemSelectAll = new JMenuItem(&quot;Seleccionar todo&quot;);
        itemSelectAll.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK));
        itemSelectAll.setActionCommand(&quot;cmd_selectall&quot;);

        menuEdit.add(mbItemUndo);    //se añaden los items al menú &quot;Editar&quot;
        menuEdit.add(mbItemRedo);
        menuEdit.addSeparator();     //añade separadores entre algunos items
        menuEdit.add(itemCut);
        menuEdit.add(itemCopy);
        menuEdit.add(itemPaste);
        menuEdit.addSeparator();
        menuEdit.add(itemSearch);
        menuEdit.add(itemSearchNext);
        menuEdit.add(itemGotoLine);
        menuEdit.addSeparator();
        menuEdit.add(itemSelectAll);
        //----------------------------------------------

        //construye el menú &quot;Opciones&quot;, a continuación se construyen los items para este menú
        JMenu menuTools = new JMenu(&quot;Opciones&quot;);

        itemLineWrap = new JCheckBoxMenuItem(&quot;Ajuste de línea&quot;);
        itemLineWrap.setSelected(true);
        itemLineWrap.setActionCommand(&quot;cmd_linewrap&quot;);

        itemShowToolBar = new JCheckBoxMenuItem(&quot;Ver barra de herramientas&quot;);
        itemShowToolBar.setSelected(true);
        itemShowToolBar.setActionCommand(&quot;cmd_showtoolbar&quot;);

        itemFixedToolBar = new JCheckBoxMenuItem(&quot;Fijar barra de herramientas&quot;);
        itemFixedToolBar.setSelected(true);
        itemFixedToolBar.setActionCommand(&quot;cmd_fixedtoolbar&quot;);

        itemShowStatusBar = new JCheckBoxMenuItem(&quot;Ver barra de estado&quot;);
        itemShowStatusBar.setSelected(true);
        itemShowStatusBar.setActionCommand(&quot;cmd_showstatusbar&quot;);

        JMenuItem itemFont = new JMenuItem(&quot;Fuente de letra&quot;);
        itemFont.setActionCommand(&quot;cmd_font&quot;);

        JMenuItem itemFontColor = new JMenuItem(&quot;Color de letra&quot;);
        itemFontColor.setActionCommand(&quot;cmd_fontcolor&quot;);

        JMenuItem itemBackgroundColor = new JMenuItem(&quot;Color de fondo&quot;);
        itemBackgroundColor.setActionCommand(&quot;cmd_backgroundcolor&quot;);

        menuTools.add(itemLineWrap);    //se añaden los items al menú &quot;Opciones&quot;
        menuTools.add(itemShowToolBar);
        menuTools.add(itemFixedToolBar);
        menuTools.add(itemShowStatusBar);
        menuTools.addSeparator();
        menuTools.add(itemFont);
        menuTools.add(itemFontColor);
        menuTools.add(itemBackgroundColor);

        //construye el menú &quot;Ayuda&quot;, a continuación se construye el único item para este menú
        JMenu menuHelp = new JMenu(&quot;Ayuda&quot;);

        JMenuItem itemAbout = new JMenuItem(&quot;Acerca de&quot;);
        itemAbout.setActionCommand(&quot;cmd_about&quot;);

        menuHelp.add(itemAbout);     //se añade el único item al menú &quot;Ayuda&quot;
        //----------------------------------------------

        jMenuBar.add(menuFile);    //se añaden los menúes construidos a la barra de menú
        jMenuBar.add(Box.createHorizontalStrut(5));    //añade espacios entre cada menú
        jMenuBar.add(menuEdit);
        jMenuBar.add(Box.createHorizontalStrut(5));
        jMenuBar.add(menuTools);
        jMenuBar.add(Box.createHorizontalStrut(5));
        jMenuBar.add(menuHelp);

        /** itera sobre todos los componentes de la barra de menú, se les asigna el mismo
        manejador de eventos a todos excepto a los separadores */
        for (Component c1 : jMenuBar.getComponents()) {
            //si el componente es un menú
            if (c1.getClass().equals(javax.swing.JMenu.class)) {
                //itera sobre los componentes del menú
                for (Component c2 : ((JMenu) c1).getMenuComponents()) {
                    //si el componente no es un separador
                    if (!c2.getClass().equals(javax.swing.JPopupMenu.Separator.class)) {
                        ((JMenuItem) c2).addActionListener(eventHandler);
                    }
                }
            }
        }
    }

    /**
     * Construye la barra de herramientas.
     */
    private void buildToolBar() {
        jToolBar = new JToolBar();       //construye un JToolBar
        jToolBar.setFloatable(false);    //se configura por defecto como barra fija

        //construye el botón &quot;Nuevo&quot;
        JButton buttonNew = new JButton();
        //le asigna una etiqueta flotante
        buttonNew.setToolTipText(&quot;Nuevo&quot;);
        //le asigna una imagen ubicada en los recursos del proyecto
        buttonNew.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_new.png&quot;)));
        //le asigna un nombre de comando
        buttonNew.setActionCommand(&quot;cmd_new&quot;);

        JButton buttonOpen = new JButton();
        buttonOpen.setToolTipText(&quot;Abrir&quot;);
        buttonOpen.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_open.png&quot;)));
        buttonOpen.setActionCommand(&quot;cmd_open&quot;);

        JButton buttonSave = new JButton();
        buttonSave.setToolTipText(&quot;Guardar&quot;);
        buttonSave.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_save.png&quot;)));
        buttonSave.setActionCommand(&quot;cmd_save&quot;);

        JButton buttonSaveAs = new JButton();
        buttonSaveAs.setToolTipText(&quot;Guardar como...&quot;);
        buttonSaveAs.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_saveas.png&quot;)));
        buttonSaveAs.setActionCommand(&quot;cmd_saveas&quot;);

        JButton buttonPrint = new JButton();
        buttonPrint.setToolTipText(&quot;Imprimir&quot;);
        buttonPrint.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_print.png&quot;)));
        buttonPrint.setActionCommand(&quot;cmd_print&quot;);

        buttonUndo = new JButton();
        buttonUndo.setEnabled(false);
        buttonUndo.setToolTipText(&quot;Deshacer&quot;);
        buttonUndo.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_undo.png&quot;)));
        buttonUndo.setActionCommand(&quot;cmd_undo&quot;);

        buttonRedo = new JButton();
        buttonRedo.setEnabled(false);
        buttonRedo.setToolTipText(&quot;Rehacer&quot;);
        buttonRedo.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_redo.png&quot;)));
        buttonRedo.setActionCommand(&quot;cmd_redo&quot;);

        JButton buttonCut = new JButton();
        buttonCut.setToolTipText(&quot;Cortar&quot;);
        buttonCut.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_cut.png&quot;)));
        buttonCut.setActionCommand(&quot;cmd_cut&quot;);

        JButton buttonCopy = new JButton();
        buttonCopy.setToolTipText(&quot;Copiar&quot;);
        buttonCopy.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_copy.png&quot;)));
        buttonCopy.setActionCommand(&quot;cmd_copy&quot;);

        JButton buttonPaste = new JButton();
        buttonPaste.setToolTipText(&quot;Pegar&quot;);
        buttonPaste.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_paste.png&quot;)));
        buttonPaste.setActionCommand(&quot;cmd_paste&quot;);

        jToolBar.add(buttonNew);    //se añaden los botones construidos a la barra de herramientas
        jToolBar.add(buttonOpen);
        jToolBar.add(buttonSave);
        jToolBar.add(buttonSaveAs);
        jToolBar.addSeparator();    //añade separadores entre algunos botones
        jToolBar.add(buttonPrint);
        jToolBar.addSeparator();
        jToolBar.add(buttonUndo);
        jToolBar.add(buttonRedo);
        jToolBar.addSeparator();
        jToolBar.add(buttonCut);
        jToolBar.add(buttonCopy);
        jToolBar.add(buttonPaste);

        /** itera sobre todos los componentes de la barra de herramientas, se les asigna el
        mismo margen y el mismo manejador de eventos unicamente a los botones */
        for (Component c : jToolBar.getComponents()) {
            //si el componente es un botón
            if (c.getClass().equals(javax.swing.JButton.class)) {
                JButton jb = (JButton) c;
                jb.setMargin(new Insets(0, 0, 0, 0));
                jb.addActionListener(eventHandler);
            }
        }
    }

    /**
     * Construye la barra de estado.
     */
    private void buildStatusBar() {
        statusBar = new JPanel();    //construye un JPanel
        //se configura con un BoxLayout
        statusBar.setLayout(new BoxLayout(statusBar, BoxLayout.LINE_AXIS));
        //le añade un borde compuesto
        statusBar.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createLoweredBevelBorder(),
                BorderFactory.createEmptyBorder(5, 5, 5, 5)));

        //construye la etiqueta para mostrar la ubicación del archivo actual
        sbFilePath = new JLabel(&quot;...&quot;);
        //construye la etiqueta para mostrar el tamaño del archivo actual
        sbFileSize = new JLabel(&quot;&quot;);
        //construye la etiqueta para mostrar la posición del cursor en el documento actual
        sbCaretPos = new JLabel(&quot;...&quot;);

        /** se añaden las etiquetas construidas al JPanel, el resultado es un panel
        similar a una barra de estado */
        statusBar.add(sbFilePath);
        statusBar.add(Box.createRigidArea(new Dimension(10, 0)));
        statusBar.add(sbFileSize);
        statusBar.add(Box.createRigidArea(new Dimension(10, 0)));
        statusBar.add(Box.createHorizontalGlue());
        statusBar.add(sbCaretPos);
    }

    /**
     * Construye el menú emergente.
     */
    private void buildPopupMenu() {
        jPopupMenu = new JPopupMenu();    //construye un JPopupMenu

        //construye el item &quot;Deshacer&quot;
        mpItemUndo = new JMenuItem(&quot;Deshacer&quot;);
        //se configura desactivado por defecto
        mpItemUndo.setEnabled(false);
        //le asigna un nombre de comando
        mpItemUndo.setActionCommand(&quot;cmd_undo&quot;);

        mpItemRedo = new JMenuItem(&quot;Rehacer&quot;);
        mpItemRedo.setEnabled(false);
        mpItemRedo.setActionCommand(&quot;cmd_redo&quot;);

        JMenuItem itemCut = new JMenuItem(&quot;Cortar&quot;);
        itemCut.setActionCommand(&quot;cmd_cut&quot;);

        JMenuItem itemCopy = new JMenuItem(&quot;Copiar&quot;);
        itemCopy.setActionCommand(&quot;cmd_copy&quot;);

        JMenuItem itemPaste = new JMenuItem(&quot;Pegar&quot;);
        itemPaste.setActionCommand(&quot;cmd_paste&quot;);

        JMenuItem itemGoto = new JMenuItem(&quot;Ir a...&quot;);
        itemGoto.setActionCommand(&quot;cmd_gotoline&quot;);

        JMenuItem itemSearch = new JMenuItem(&quot;Buscar&quot;);
        itemSearch.setActionCommand(&quot;cmd_search&quot;);

        JMenuItem itemSearchNext = new JMenuItem(&quot;Buscar siguiente&quot;);
        itemSearchNext.setActionCommand(&quot;cmd_searchnext&quot;);

        JMenuItem itemSelectAll = new JMenuItem(&quot;Seleccionar todo&quot;);
        itemSelectAll.setActionCommand(&quot;cmd_selectall&quot;);

        jPopupMenu.add(mpItemUndo);    //se añaden los items al menú emergente
        jPopupMenu.add(mpItemRedo);
        jPopupMenu.addSeparator();     //añade separadores entre algunos items
        jPopupMenu.add(itemCut);
        jPopupMenu.add(itemCopy);
        jPopupMenu.add(itemPaste);
        jPopupMenu.addSeparator();
        jPopupMenu.add(itemGoto);
        jPopupMenu.add(itemSearch);
        jPopupMenu.add(itemSearchNext);
        jPopupMenu.addSeparator();
        jPopupMenu.add(itemSelectAll);

        /** itera sobre todos los componentes del menú emergente, se les asigna el mismo
        manejador de eventos a todos excepto a los separadores */
        for (Component c : jPopupMenu.getComponents()) {
            //si el componente es un item
            if (c.getClass().equals(javax.swing.JMenuItem.class)) {
                ((JMenuItem) c).addActionListener(eventHandler);
            }
        }
    }

    /**
     * Hace visible el menú emergente.
     *
     * @param me evento del ratón
     */
    private void showPopupMenu(MouseEvent me) {
        if (me.isPopupTrigger() == true) {    //si el evento es el desencadenador de menú emergente
            //hace visible el menú emergente en las coordenadas actuales del ratón
            jPopupMenu.show(me.getComponent(), me.getX(), me.getY());
        }
    }

    /**
     * Actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;.
     */
    void updateControls() {
        //averigua si se pueden deshacer los cambios en el documento actual
        boolean canUndo = undoManager.canUndo();
        //averigua si se pueden rehacer los cambios en el documento actual
        boolean canRedo = undoManager.canRedo();

        //activa o desactiva las opciones en la barra de menú
        mbItemUndo.setEnabled(canUndo);
        mbItemRedo.setEnabled(canRedo);

        //activa o desactiva las opciones en la barra de herramientas
        buttonUndo.setEnabled(canUndo);
        buttonRedo.setEnabled(canRedo);

        //activa o desactiva las opciones en el menú emergente
        mpItemUndo.setEnabled(canUndo);
        mpItemRedo.setEnabled(canRedo);
    }

    /**
     * Retorna la instancia de EventHandler, la clase interna que maneja eventos.
     *
     * @return el manejador de eventos.
     */
    EventHandler getEventHandler() {
        return eventHandler;
    }

    /**
     * Retorna la instancia de UndoManager, la cual administra las ediciones sobre
     * el documento en el área de texto.
     *
     * @return el administrador de edición.
     */
    UndoManager getUndoManager() {
        return undoManager;
    }

    /**
     * Retorna el estado del documento actual.
     *
     * @return true si ah sido modificado, false en caso contrario
     */
    boolean documentHasChanged() {
        return hasChanged;
    }

    /**
     * Establece el estado del documento actual.
     *
     * @param hasChanged true si ah sido modificado, false en caso contrario
     */
    void setDocumentChanged(boolean hasChanged) {
        this.hasChanged = hasChanged;
    }

    /**
     * Retorna la instancia de JTextArea, el área de edición.
     *
     * @return retorna el área de edición.
     */
    JTextArea getJTextArea() {
        return jTextArea;
    }

    /**
     * Retorna la instancia de JFrame, la ventana principal del editor.
     *
     * @return la ventana principal del editor.
     */
    JFrame getJFrame() {
        return jFrame;
    }

    /**
     * Retorna la instancia de File, el archivo actual.
     *
     * @return el archivo actual
     */
    File getCurrentFile() {
        return currentFile;
    }

    /**
     * Establece el archivo actual.
     *
     * @param currentFile el archivo actual
     */
    void setCurrentFile(File currentFile) {
        this.currentFile = currentFile;
    }

    /**
     * Retorna la instancia de la etiqueta sbFilePath, donde se muestra la ubicación
     * del archivo actual.
     *
     * @return la instancia de la etiqueta sbFilePath
     */
    JLabel getJLabelFilePath() {
        return sbFilePath;
    }

    /**
     * Retorna la instancia de la etiqueta sbFileSize, donde se muestra el tamaño
     * del archivo actual
     *
     * @return la instancia de la etiqueta sbFileSize
     */
    JLabel getJLabelFileSize() {
        return sbFileSize;
    }

    /**
     * Clase interna que extiende e implementa las clases e interfaces necesarias para
     * atender y manejar los eventos sobre la GUI principal del editor.
     */
    class EventHandler extends MouseAdapter implements ActionListener,
                                                       CaretListener,
                                                       UndoableEditListener {

        /**
         * Atiende y maneja los eventos de acción.
         *
         * @param ae evento de acción
         */
        @Override
        public void actionPerformed(ActionEvent ae) {
            String ac = ae.getActionCommand();    //se obtiene el nombre del comando ejecutado

            if (ac.equals(&quot;cmd_new&quot;) == true) {    //opción seleccionada: &quot;Nuevo&quot;
                actionPerformer.actionNew();
            } else if (ac.equals(&quot;cmd_open&quot;) == true) {    //opción seleccionada: &quot;Abrir&quot;
                actionPerformer.actionOpen();
            } else if (ac.equals(&quot;cmd_save&quot;) == true) {    //opción seleccionada: &quot;Guardar&quot;
                actionPerformer.actionSave();
            } else if (ac.equals(&quot;cmd_saveas&quot;) == true) {    //opción seleccionada: &quot;Guardar como&quot;
                actionPerformer.actionSaveAs();
            } else if (ac.equals(&quot;cmd_print&quot;) == true) {    //opción seleccionada: &quot;Imprimir&quot;
                actionPerformer.actionPrint();
            } else if (ac.equals(&quot;cmd_exit&quot;) == true) {    //opción seleccionada: &quot;Salir&quot;
                actionPerformer.actionExit();
            } else if (ac.equals(&quot;cmd_undo&quot;) == true) {    //opción seleccionada: &quot;Deshacer&quot;
                actionPerformer.actionUndo();
            } else if (ac.equals(&quot;cmd_redo&quot;) == true) {    //opción seleccionada: &quot;Rehacer&quot;
                actionPerformer.actionRedo();
            } else if (ac.equals(&quot;cmd_cut&quot;) == true) {    //opción seleccionada: &quot;Cortar&quot;
                //corta el texto seleccionado en el documento
                jTextArea.cut();
            } else if (ac.equals(&quot;cmd_copy&quot;) == true) {    //opción seleccionada: &quot;Copiar&quot;
                //copia el texto seleccionado en el documento
                jTextArea.copy();
            } else if (ac.equals(&quot;cmd_paste&quot;) == true) {    //opción seleccionada: &quot;Pegar&quot;
                //pega en el documento el texto del portapapeles
                jTextArea.paste();
            } else if (ac.equals(&quot;cmd_gotoline&quot;) == true) {    //opción seleccionada: &quot;Ir a la línea...&quot;
                actionPerformer.actionGoToLine();
            } else if (ac.equals(&quot;cmd_search&quot;) == true) {    //opción seleccionada: &quot;Buscar&quot;
                actionPerformer.actionSearch();
            } else if (ac.equals(&quot;cmd_searchnext&quot;) == true) {    //opción seleccionada: &quot;Buscar siguiente&quot;
                actionPerformer.actionSearchNext();
            } else if (ac.equals(&quot;cmd_selectall&quot;) == true) {    //opción seleccionada: &quot;Seleccionar todo&quot;
                jTextArea.selectAll();
            } else if (ac.equals(&quot;cmd_linewrap&quot;) == true) {    //opción seleccionada: &quot;Ajuste de línea&quot;
                //si esta propiedad esta activada se desactiva, o lo inverso
                jTextArea.setLineWrap(!jTextArea.getLineWrap());
                jTextArea.setWrapStyleWord(!jTextArea.getWrapStyleWord());
            } else if (ac.equals(&quot;cmd_showtoolbar&quot;) == true) {    //opción seleccionada: &quot;Ver barra de herramientas&quot;
                //si la barra de herramientas esta visible se oculta, o lo inverso
                jToolBar.setVisible(!jToolBar.isVisible());
            } else if (ac.equals(&quot;cmd_fixedtoolbar&quot;) == true) {    //opción seleccionada: &quot;Fijar barra de herramientas&quot;
                //si esta propiedad esta activada se desactiva, o lo inverso
                jToolBar.setFloatable(!jToolBar.isFloatable());
            } else if (ac.equals(&quot;cmd_showstatusbar&quot;) == true) {    //opción seleccionada: &quot;Ver barra de estado&quot;
                //si la barra de estado esta visible se oculta, o lo inverso
                statusBar.setVisible(!statusBar.isVisible());
            } else if (ac.equals(&quot;cmd_font&quot;) == true) {    //opción seleccionada: &quot;Fuente de letra&quot;
                actionPerformer.actionSelectFont();
            } else if (ac.equals(&quot;cmd_fontcolor&quot;) == true) {    //opción seleccionada: &quot;Color de letra&quot;
                actionPerformer.actionSelectFontColor();
            } else if (ac.equals(&quot;cmd_backgroundcolor&quot;) == true) {    //opción seleccionada: &quot;Color de fondo&quot;
                actionPerformer.actionSelectBackgroundColor();
            } else if (ac.equals(&quot;cmd_about&quot;) == true) {    //opción seleccionada: &quot;Acerca de&quot;
                //presenta un dialogo modal con alguna informacion
                JOptionPane.showMessageDialog(jFrame,
                                              &quot;TextPad Demo por Dark[byte]&quot;,
                                              &quot;Acerca de&quot;,
                                              JOptionPane.INFORMATION_MESSAGE);
            }
        }

        /**
         * Atiende y maneja los eventos del cursor.
         *
         * @param ce evento del cursor
         */
        @Override
        public void caretUpdate(CaretEvent e) {
            final int caretPos;  //valor de la posición del cursor sin inicializar
            int y = 1;           //valor de la línea inicialmente en 1
            int x = 1;           //valor de la columna inicialmente en 1

            try {
                //obtiene la posición del cursor con respecto al inicio del JTextArea (área de edición)
                caretPos = jTextArea.getCaretPosition();
                //sabiendo lo anterior se obtiene el valor de la línea actual (se cuenta desde 0)
                y = jTextArea.getLineOfOffset(caretPos);

                /** a la posición del cursor se le resta la posición del inicio de la línea para
                determinar el valor de la columna actual */
                x = caretPos - jTextArea.getLineStartOffset(y);

                //al valor de la línea actual se le suma 1 porque estas comienzan contándose desde 0
                y += 1;
            } catch (BadLocationException ex) {    //en caso de que ocurra una excepción
                System.err.println(ex);
            }

            /** muestra la información recolectada en la etiqueta sbCaretPos de la
            barra de estado, también se incluye el número total de lineas */
            sbCaretPos.setText(&quot;Líneas: &quot; + jTextArea.getLineCount() + &quot; - Y: &quot; + y + &quot; - X: &quot; + x);
        }

        /**
         * Atiende y maneja los eventos sobre el documento en el área de edición.
         *
         * @param uee evento de edición
         */
        @Override
        public void undoableEditHappened(UndoableEditEvent uee) {
            /** el cambio realizado en el área de edición se guarda en el buffer
            del administrador de edición */
            undoManager.addEdit(uee.getEdit());
            updateControls();    //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;

            hasChanged = true;
        }

        /**
         * Atiende y maneja los eventos sobre el ratón cuando este es presionado.
         *
         * @param me evento del ratón
         */
        @Override
        public void mousePressed(MouseEvent me) {
            showPopupMenu(me);
        }

        /**
         * Atiende y maneja los eventos sobre el ratón cuando este es liberado.
         *
         * @param me evento del ratón
         */
        @Override
        public void mouseReleased(MouseEvent me) {
            showPopupMenu(me);
        }
    }
}
</pre>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
/**
 * ActionPerformer.java
 */

package textpademo;

import java.awt.Color;
import java.awt.Font;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Pattern;
import javax.swing.JColorChooser;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.BadLocationException;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;

/**
 * Clase que ejecuta las operaciones solicitadas.
 * 
 * @author Dark[byte]
 */
public class ActionPerformer {

    private final TPEditor tpEditor;    //instancia de TPEditor (la clase principal)
    private String lastSearch = &quot;&quot;;     //la última búsqueda de texto realizada, por defecto no contiene nada

    /**
     * Constructor de la clase.
     * 
     * @param tpEditor clase principal
     */
    public ActionPerformer(TPEditor tpEditor) {
        this.tpEditor = tpEditor;    //guarda la instancia de la clase TPEditor
    }

    /**
     * Opción seleccionada: &quot;Nuevo&quot;.
     * 
     * Reemplaza el documento actual por uno nuevo vacío.
     */
    public void actionNew() {
        if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
            //le ofrece al usuario guardar los cambios
            int option = JOptionPane.showConfirmDialog(tpEditor.getJFrame(), &quot;¿Desea guardar los cambios?&quot;);

            switch (option) {
                case JOptionPane.YES_OPTION:       //si elige que si
                    actionSave();                  //guarda el archivo
                    break;
                case JOptionPane.CANCEL_OPTION:    //si elige cancelar
                    return;                        //cancela esta operación
                //en otro caso se continúa con la operación y no se guarda el documento actual
            }
        }

        tpEditor.getJFrame().setTitle(&quot;TextPad Demo - Sin Título&quot;);    //nuevo título de la ventana

        //limpia el contenido del area de edición
        tpEditor.getJTextArea().setText(&quot;&quot;);
        //limpia el contenido de las etiquetas en la barra de estado
        tpEditor.getJLabelFilePath().setText(&quot;&quot;);
        tpEditor.getJLabelFileSize().setText(&quot;&quot;);

        tpEditor.getUndoManager().die();    //limpia el buffer del administrador de edición
        tpEditor.updateControls();          //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;

        //el archivo asociado al documento actual se establece como null
        tpEditor.setCurrentFile(null);
        //marca el estado del documento como no modificado
        tpEditor.setDocumentChanged(false);
    }

    /**
     * Opción seleccionada: &quot;Abrir&quot;.
     * 
     * Le permite al usuario elegir un archivo para cargar en el área de edición.
     */
    public void actionOpen() {
        if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
            //le ofrece al usuario guardar los cambios
            int option = JOptionPane.showConfirmDialog(tpEditor.getJFrame(), &quot;¿Desea guardar los cambios?&quot;);

            switch (option) {
                case JOptionPane.YES_OPTION:     //si elige que si
                    actionSave();               //guarda el archivo
                    break;
                case JOptionPane.CANCEL_OPTION:  //si elige cancelar
                    return;                      //cancela esta operación
                //en otro caso se continúa con la operación y no se guarda el documento actual
            }
        }

        JFileChooser fc = getJFileChooser();    //obtiene un JFileChooser

        //presenta un dialogo modal para que el usuario seleccione un archivo
        int state = fc.showOpenDialog(tpEditor.getJFrame());

        if (state == JFileChooser.APPROVE_OPTION) {    //si elige abrir el archivo
            File f = fc.getSelectedFile();    //obtiene el archivo seleccionado

            try {
                //abre un flujo de datos desde el archivo seleccionado
                BufferedReader br = new BufferedReader(new FileReader(f));
                //lee desde el flujo de datos hacia el area de edición
                tpEditor.getJTextArea().read(br, null);
                br.close();    //cierra el flujo

                tpEditor.getJTextArea().getDocument().addUndoableEditListener(tpEditor.getEventHandler());

                tpEditor.getUndoManager().die();    //se limpia el buffer del administrador de edición
                tpEditor.updateControls();          //se actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;

                //nuevo título de la ventana con el nombre del archivo cargado
                tpEditor.getJFrame().setTitle(&quot;TextPad Demo - &quot; + f.getName());

                //muestra la ubicación del archivo actual
                tpEditor.getJLabelFilePath().setText(shortPathName(f.getAbsolutePath()));
                //muestra el tamaño del archivo actual
                tpEditor.getJLabelFileSize().setText(roundFileSize(f.length()));

                //establece el archivo cargado como el archivo actual
                tpEditor.setCurrentFile(f);
                //marca el estado del documento como no modificado
                tpEditor.setDocumentChanged(false);
            } catch (IOException ex) {    //en caso de que ocurra una excepción
                //presenta un dialogo modal con alguna información de la excepción
                JOptionPane.showMessageDialog(tpEditor.getJFrame(),
                                              ex.getMessage(),
                                              ex.toString(),
                                              JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /**
     * Opción seleccionada: &quot;Guardar&quot;.
     * 
     * Guarda el documento actual en el archivo asociado actualmente.
     */
    public void actionSave() {
        if (tpEditor.getCurrentFile() == null) {    //si no hay un archivo asociado al documento actual
            actionSaveAs();    //invoca el método actionSaveAs()
        } else if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
            try {
                //abre un flujo de datos hacia el archivo asociado al documento actual
                BufferedWriter bw = new BufferedWriter(new FileWriter(tpEditor.getCurrentFile()));
                //escribe desde el flujo de datos hacia el archivo
                tpEditor.getJTextArea().write(bw);
                bw.close();    //cierra el flujo

                //marca el estado del documento como no modificado
                tpEditor.setDocumentChanged(false);
            } catch (IOException ex) {    //en caso de que ocurra una excepción
                //presenta un dialogo modal con alguna información de la excepción
                JOptionPane.showMessageDialog(tpEditor.getJFrame(),
                                              ex.getMessage(),
                                              ex.toString(),
                                              JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /**
     * Opción seleccionada: &quot;Guardar como&quot;.
     * 
     * Le permite al usuario elegir la ubicación donde se guardará el documento actual.
     */
    public void actionSaveAs() {
        JFileChooser fc = getJFileChooser();    //obtiene un JFileChooser

        //presenta un dialogo modal para que el usuario seleccione un archivo
        int state = fc.showSaveDialog(tpEditor.getJFrame());
        if (state == JFileChooser.APPROVE_OPTION) {    //si elige guardar en el archivo
            File f = fc.getSelectedFile();    //obtiene el archivo seleccionado

            try {
                //abre un flujo de datos hacia el archivo asociado seleccionado
                BufferedWriter bw = new BufferedWriter(new FileWriter(f));
                //escribe desde el flujo de datos hacia el archivo
                tpEditor.getJTextArea().write(bw);
                bw.close();    //cierra el flujo

                //nuevo título de la ventana con el nombre del archivo guardado
                tpEditor.getJFrame().setTitle(&quot;TextPad Demo - &quot; + f.getName());

                //muestra la ubicación del archivo guardado
                tpEditor.getJLabelFilePath().setText(shortPathName(f.getAbsolutePath()));
                //muestra el tamaño del archivo guardado
                tpEditor.getJLabelFileSize().setText(roundFileSize(f.length()));

                //establece el archivo guardado como el archivo actual
                tpEditor.setCurrentFile(f);
                //marca el estado del documento como no modificado
                tpEditor.setDocumentChanged(false);
            } catch (IOException ex) {    //en caso de que ocurra una excepción
                //presenta un dialogo modal con alguna información de la excepción
                JOptionPane.showMessageDialog(tpEditor.getJFrame(),
                                              ex.getMessage(),
                                              ex.toString(),
                                              JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /**
     * Opción seleccionada: &quot;Imprimir&quot;.
     * 
     * Imprime el documento actual.
     */
    public void actionPrint() {
        boolean result = false;    //resultado de la impresión, por defecto es false

        //si el documento actual no esta vacío
        if (tpEditor.getJTextArea().getText().trim().equals(&quot;&quot;) == false) {
            //invoca nuestra la clase PrintAction para presentar el dialogo de impresión
            result = PrintAction.print(tpEditor.getJTextArea(), tpEditor.getJFrame());
        }
    }

    /**
     * Opción seleccionada: &quot;Salir&quot;.
     * 
     * Finaliza el programa.
     */
    public void actionExit() {
        if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
            //le ofrece al usuario guardar los cambios
            int option = JOptionPane.showConfirmDialog(tpEditor.getJFrame(), &quot;¿Desea guardar los cambios?&quot;);

            switch (option) {
                case JOptionPane.YES_OPTION:     //si elige que si
                    actionSave();                //guarda el archivo
                    break;
                case JOptionPane.CANCEL_OPTION:  //si elige cancelar
                    return;                      //cancela esta operación
                //en otro caso se continúa con la operación y no se guarda el documento actual
            }
        }


        System.exit(0);    //finaliza el programa con el código 0 (sin errores)
    }

    /**
     * Opción seleccionada: &quot;Deshacer&quot;.
     * 
     * Deshace el último cambio realizado en el documento actual.
     */
    public void actionUndo() {
        try {
            //deshace el último cambio realizado sobre el documento en el área de edición
            tpEditor.getUndoManager().undo();
        } catch (CannotUndoException ex) {    //en caso de que ocurra una excepción
            System.err.println(ex);
        }

        //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;
        tpEditor.updateControls();
    }

    /**
     * Opción seleccionada: &quot;Rehacer&quot;.
     * 
     * Rehace el último cambio realizado en el documento actual.
     */
    public void actionRedo() {
        try {
            //rehace el último cambio realizado sobre el documento en el área de edición
            tpEditor.getUndoManager().redo();
        } catch (CannotRedoException ex) {    //en caso de que ocurra una excepción
            System.err.println(ex);
        }

        //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;
        tpEditor.updateControls();
    }

    /**
     * Opción seleccionada: &quot;Buscar&quot;.
     * 
     * Busca un texto especificado por el usuario en el documento actual. El texto queda 
     * guardado para búsquedas siguientes.
     */
    public void actionSearch() {
        //solicita al usuario que introduzca el texto a buscar
        String text = JOptionPane.showInputDialog(
                tpEditor.getJFrame(),
                &quot;Texto:&quot;,
                &quot;TextPad Demo - Buscar&quot;,
                JOptionPane.QUESTION_MESSAGE);

        if (text != null) {    //si se introdujo texto (puede ser una cadena vacía)
            String textAreaContent = tpEditor.getJTextArea().getText();    //obtiene todo el contenido del área de edición
            int pos = textAreaContent.indexOf(text);    //obtiene la posición de la primera ocurrencia del texto

            if (pos &gt; -1) {    //si la posición es mayor a -1 significa que la búsqueda fue positiva
                //selecciona el texto en el área de edición para resaltarlo
                tpEditor.getJTextArea().select(pos, pos + text.length());
            }

            //establece el texto buscado como el texto de la última búsqueda realizada
            lastSearch = text;
        }
    }

    /**
     * Opción seleccionada: &quot;Buscar siguiente&quot;.
     * 
     * Busca el texto de la última búsqueda en el documento actual.
     */
    public void actionSearchNext() {
        if (lastSearch.length() &gt; 0) {    //si la última búsqueda contiene texto
            String textAreaContent = tpEditor.getJTextArea().getText();    //se obtiene todo el contenido del área de edición
            int pos = tpEditor.getJTextArea().getCaretPosition();    //se obtiene la posición del cursor sobre el área de edición
            //buscando a partir desde la posición del cursor, se obtiene la posición de la primera ocurrencia del texto
            pos = textAreaContent.indexOf(lastSearch, pos);

            if (pos &gt; -1) {    //si la posición es mayor a -1 significa que la búsqueda fue positiva
                //selecciona el texto en el área de edición para resaltarlo
                tpEditor.getJTextArea().select(pos, pos + lastSearch.length());
            }
        } else {    //si la última búsqueda no contiene nada
            actionSearch();    //invoca el método actionSearch()
        }
    }

    /**
     * Opción seleccionada: &quot;Ir a la línea...&quot;.
     * 
     * Posiciona el cursor en el inicio de una línea especificada por el usuario.
     */
    public void actionGoToLine() {
        //solicita al usuario que introduzca el número de línea
        String line = JOptionPane.showInputDialog(
                tpEditor.getJFrame(),
                &quot;Número:&quot;,
                &quot;TextPad Demo - Ir a la línea...&quot;,
                JOptionPane.QUESTION_MESSAGE);

        if (line != null &amp;&amp; line.length() &gt; 0) {    //si se introdujo un dato
            try {
                int pos = Integer.parseInt(line);    //el dato introducido se convierte en entero

                //si el número de línea esta dentro de los límites del área de texto
                if (pos &gt;= 0 &amp;&amp; pos &lt;= tpEditor.getJTextArea().getLineCount()) {
                    //posiciona el cursor en el inicio de la línea
                    tpEditor.getJTextArea().setCaretPosition(tpEditor.getJTextArea().getLineStartOffset(pos));
                }
            } catch (NumberFormatException ex) {    //en caso de que ocurran excepciones
                System.err.println(ex);
            } catch (BadLocationException ex) {
                System.err.println(ex);
            }
        }
    }

    /**
     * Opción seleccionada: &quot;Fuente de letra&quot;.
     * 
     * Le permite al usuario elegir la fuente para la letra en el área de edición.
     */
    public void actionSelectFont() {
        //presenta el dialogo de selección de fuentes
        Font font = JFontChooser.showDialog(tpEditor.getJFrame(),
                                            &quot;TextPad Demo - Fuente de letra:&quot;,
                                            tpEditor.getJTextArea().getFont());
        if (font != null) {    //si un fuente fue seleccionado
            //se establece como fuente del area de edición
            tpEditor.getJTextArea().setFont(font);
        }
    }

    /**
     * Opción seleccionada: &quot;Color de letra&quot;.
     * 
     * Le permite al usuario elegir el color para la letra en el área de edición.
     */
    public void actionSelectFontColor() {
        //presenta el dialogo de selección de colores
        Color color = JColorChooser.showDialog(tpEditor.getJFrame(),
                                               &quot;TextPad Demo - Color de letra:&quot;,
                                               tpEditor.getJTextArea().getForeground());
        if (color != null) {    //si un color fue seleccionado
            //se establece como color del fuente y cursor
            tpEditor.getJTextArea().setForeground(color);
            tpEditor.getJTextArea().setCaretColor(color);
        }
    }

    /**
     * Opción seleccionada: &quot;Color de fondo&quot;.
     * 
     * Le permite al usuario elegir el color para el fondo del área de edición.
     */
    public void actionSelectBackgroundColor() {
        //presenta el dialogo de selección de colores
        Color color = JColorChooser.showDialog(tpEditor.getJFrame(),
                                               &quot;TextPad Demo - Color de fondo:&quot;,
                                               tpEditor.getJTextArea().getForeground());
        if (color != null) {    //si un color fue seleccionado
            //se establece como color de fondo
            tpEditor.getJTextArea().setBackground(color);
        }
    }

    /**
     * Retorna la instancia de un JFileChooser, con el cual se muestra un dialogo que permite
     * seleccionar un archivo.
     * 
     * @return un dialogo para seleccionar un archivo.
     */
    private static JFileChooser getJFileChooser() {
        JFileChooser fc = new JFileChooser();                     //construye un JFileChooser
        fc.setDialogTitle(&quot;TextPad Demo - Elige un archivo:&quot;);    //se le establece un título
        fc.setMultiSelectionEnabled(false);                       //desactiva la multi-selección
        fc.setFileFilter(textFileFilter);                         //aplica un filtro de extensiones
        return fc;    //retorna el JFileChooser
    }

    /**
     * Clase anónima interna que extiende la clase javax.swing.filechooser.FileFilter para 
     * establecer un filtro de archivos en el JFileChooser.
     */
    private static FileFilter textFileFilter = new FileFilter() {

        @Override
        public boolean accept(File f) {
            //acepta directorios y archivos de extensión .txt
            return f.isDirectory() || f.getName().toLowerCase().endsWith(&quot;txt&quot;);
        }

        @Override
        public String getDescription() {
            //la descripción del tipo de archivo aceptado
            return &quot;Text Files&quot;;
        }
    };

    /**
     * Retorna la ruta de la ubicación de un archivo en forma reducida.
     * 
     * @param longpath la ruta de un archivo
     * @return la ruta reducida del archivo
     */
    private static String shortPathName(String longPath) {
        //construye un arreglo de cadenas, donde cada una es un nombre de directorio
        String[] tokens = longPath.split(Pattern.quote(File.separator));

        //construye un StringBuilder donde se añadirá el resultado
        StringBuilder shortpath = new StringBuilder();

        //itera sobre el arreglo de cadenas
        for (int i = 0 ; i &lt; tokens.length ; i++) {
            if (i == tokens.length - 1) {              //si la cadena actual es la última, es el nombre del archivo
                shortpath.append(tokens[i]);    //añade al resultado sin separador
                break;                          //termina el bucle
            } else if (tokens[i].length() &gt;= 10) {     //si la cadena actual tiene 10 o más caracteres
                //se toman los primeros 3 caracteres y se añade al resultado con un separador
                shortpath.append(tokens[i].substring(0, 3)).append(&quot;...&quot;).append(File.separator);
            } else {                                   //si la cadena actual tiene menos de 10 caracteres
                //añade al resultado con un separador
                shortpath.append(tokens[i]).append(File.separator);
            }
        }

        return shortpath.toString();    //retorna la cadena resultante
    }

    /**
     * Redondea la longitud de un archivo en KiloBytes si es necesario.
     * 
     * @param length longitud de un archivo
     * @return el tamaño redondeado  
     */
    private static String roundFileSize(long length) {
        //retorna el tamaño del archivo redondeado
        return (length &lt; 1024) ? length + &quot; bytes&quot; : (length / 1024) + &quot; Kbytes&quot;;
    }
}
</pre>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
/**
 * PrintAction.java
 */

package textpademo;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;

/**
 * Clase que implementa la interface java.awt.print.Printable para imprimir el documento
 * presente en el área de edición.
 * 
 * @author Dark[byte]
 */
public class PrintAction implements Printable {

    private final JTextArea jTextArea;    //área de edición donde se encuentra el documento actual
    private JDialog dialog;               //dialogo de estado, muestra el estado de la impresión
    private int[] pageBreaks;             //arreglo de quiebres de página
    private String[] textLines;           //arreglo de líneas de texto
    private int currentPage = -1;         //página actual impresa, por defecto inicilizada en -1
    private boolean result = false;       //resultado de la impresión, por defecto es negativo

    /**
     * Constructor de la clase.
     * 
     * @param jComponent componente cuyo contenido se imprimirá
     */
    public PrintAction(JComponent jComponent) {
        this.jTextArea = (JTextArea) jComponent;    //guarda la instancia del área de edición
    }

    /**
     * Método estático que construye e inicializa la clase PrintAction para imprimir
     * el documento presente en el área de edición.
     * 
     * @param jComponent componente cuyo contenido se imprimirá
     * @param owner la ventana padre
     * @return true si la impresión fue exitosa, false en caso contrario
     */
    public static boolean print(JComponent jComponent, Frame owner) {
        PrintAction pa = new PrintAction(jComponent);    //construye una instancia de PrintAction
        return pa.printDialog(owner);                    //inicia la impresión y retorna un valor booleano
    }

    /**
     * Le permite al usuario configurar algunos aspectos de la impresión antes de comenzar. Durante
     * la impresión muestra una ventana de dialogo con información de la misma.
     * 
     * @param owner la ventana padre
     * @return true si la impresión fue exitosa, false en caso contrario
     */
    public boolean printDialog(Frame owner) {
        //construye un trabajo de impresión
        final PrinterJob pj = PrinterJob.getPrinterJob();
        //construye un conjunto de atributos para la impresión
        final PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
        //establece a la clase PrintAction como responsable de renderizar las páginas del documento
        pj.setPrintable(this);

        boolean option = pj.printDialog(pras);    //presenta un dialogo de impresión

        if (option == true) {    //si el usuario acepta
            //construye el dialogo modal de estado sobre la ventana padre
            dialog = new PrintingMessageBox(owner, pj);

            //crea un nuevo hilo para que se ocupe de la impresión
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        pj.print();                        //inicia la impresión
                        PrintAction.this.result = true;    //resultado positivo
                    } catch (PrinterException ex) {        //en caso de que ocurra una excepción
                        System.err.println(ex);
                    }

                    dialog.setVisible(false);    //oculta el dialogo de estado
                }
            }).start();    //inicia el hilo de impresión

            dialog.setVisible(true);    //hace visible el dialogo de estado
        }

        return PrintAction.this.result;    //retorna el resultado de la impresión
    }

    /**
     * Se renderiza cada página solicitada por el sistema de impresión.
     * 
     * @param g objeto de gráficos
     * @param pf el formato de la página
     * @param pageIndex el índice de la página a imprimir
     * @return PAGE_EXISTS si la página se tiene que imprimir, NO_SUCH_PAGE si la página no es valida
     */
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {
        Graphics2D g2d = (Graphics2D) g;                      //conversión de gráficos simples a gráficos 2D
        g2d.setFont(new Font(&quot;Serif&quot;, Font.PLAIN, 10));       //establece un fuente para todo el texto
        int lineHeight = g2d.getFontMetrics().getHeight();    //obtiene la altura del fuente

        if (pageBreaks == null) {    //si los quiebres de página no fueron calculados
            //construye un arreglo con las líneas de texto presentes en el área de edición
            textLines = jTextArea.getText().split(&quot;\n&quot;);
            //calcula el número de líneas que caben en cada página
            int linesPerPage = (int) (pf.getImageableHeight() / lineHeight);
            //calcula el número de quiebres de página necesarios para imprimir todo el documento
            int numBreaks = (textLines.length - 1) / linesPerPage;
            //construye un arreglo con los quiebres de página 
            pageBreaks = new int[numBreaks];
            for (int i = 0 ; i &lt; numBreaks ; i++) {
                //se calcula la posición para cada quiebre de página
                pageBreaks[i] = (i + 1) * linesPerPage;
            }
        }

        //si el índice de página solicitado es menor o igual que la cantidad de quiebres total
        if (pageIndex &lt;= pageBreaks.length) {
            /** establece una igualdad entre el origen del espacio gráfico (x:0,y:0) y el origen 
            del área imprimible definido por el formato de página */
            g2d.translate(pf.getImageableX(), pf.getImageableY());

            int y = 0;    //coordenada &quot;y&quot;, inicializada en 0 (principio de página)
            //obtiene la primera línea para la página actual
            int startLine = (pageIndex == 0) ? 0 : pageBreaks[pageIndex - 1];
            //obtiene la última línea para la página actual
            int endLine = (pageIndex == pageBreaks.length) ? textLines.length : pageBreaks[pageIndex];

            //itera sobre las líneas que forman parte de la página actual
            for (int line = startLine ; line &lt; endLine ; line++) {
                y += lineHeight;                          //aumenta la coordenada &quot;y&quot; para cada línea
                g2d.drawString(textLines[line], 0, y);    //imprime la linea en las coordenadas actuales
            }

            updateStatus(pageIndex);    //actualiza el estado de impresión

            return PAGE_EXISTS;     //la página solicitada será impresa
        } else {
            return NO_SUCH_PAGE;    //la pagina solicitada no es valida
        }
    }

    /**
     * Actualiza la información en el dialogo de estado de la impresión.
     * 
     * @param pageIndex índice de la página impresa
     */
    private void updateStatus(int pageIndex) {
        if (pageIndex != currentPage) {
            currentPage++;    //incrementa la página actual    

            //acceso seguro al EDT (Event Dispatch Thread) para actualizar la GUI
            javax.swing.SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    //actualiza la información de la etiqueta lbStatusMsg
                    ((PrintingMessageBox) dialog).setStatusMsg(&quot;Imprimiendo página &quot; + (currentPage + 1) + &quot; ...&quot;);
                }
            });
        }
    }

    /**
     * Clase interna que extiende javax.swing.JDialog para presentar una ventana modal de dialogo
     * que muestra el estado de la impresión y un botón para cancelar la operación.
     */
    private class PrintingMessageBox extends JDialog {

        private JLabel lbStatusMsg;    //etiqueta que muestra el estado de impresión

        /**
         * Constructor de esta clase.
         * 
         * Construye una ventana modal de dialogo sobre una ventana padre.
         * 
         * @param owner la ventana padre
         * @param pj trabajo de impresión
         */
        public PrintingMessageBox(Frame owner, final PrinterJob pj) {
            /** invoca el constructor de la superclase para establecer la ventana padre, el título 
            de la ventana, y que será una ventana modal */
            super(owner, &quot;Impresión&quot;, true);
            setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

            //construye y configura la etiqueta que muestra el estado
            lbStatusMsg = new JLabel(&quot;Iniciando ...&quot;);
            lbStatusMsg.setPreferredSize(new Dimension(200, 30));
            lbStatusMsg.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

            //construye el botón de cancelar
            JButton buttonCancel = new JButton(&quot;Cancelar&quot;);
            JPanel jp = new JPanel();
            jp.add(buttonCancel);

            //asigna un manejador de eventos para el botón de cancelar
            buttonCancel.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    pj.cancel();          //cancela el trabajo de impresión
                    setVisible(false);    //oculta esta ventana
                }
            });

            getContentPane().add(lbStatusMsg, BorderLayout.CENTER);    //añade la etiqueta en el CENTRO
            getContentPane().add(jp, BorderLayout.SOUTH);              //añade el botón, orientación SUR

            //asigna un manejador de eventos para cuando la ventana pierde la visibilidad
            this.addComponentListener(new ComponentAdapter() {

                @Override
                public void componentHidden(ComponentEvent e) {
                    Window w = (Window) e.getComponent();    //convierte el componente afectado en una ventana
                    w.dispose();                             //destruye la ventana
                }
            });

            setResizable(false);             //no se permite redimensionar la ventana
            pack();                          //se le da el tamaño preferido
            setLocationRelativeTo(owner);    //la ventana se centra sobre el editor de texto
        }

        /**
         * Establece el texto de estado que se muestra en el dialogo.
         * 
         * @param statusMsg texto de estado
         */
        public void setStatusMsg(String statusMsg) {
            lbStatusMsg.setText(statusMsg);    //establece el texto de la etiqueta lbStatusMsg
        }
    }
}
</pre>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
/**
 * JFontChooser.java
 */

package textpademo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Arrays;
import java.util.Comparator;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.Position;

/**
 * Clase que extiende javax.swing.JComponent para crear un componente Swing donde el usuario
 * pueda seleccionar un fuente.
 * 
 * @author Dark[byte]
 */
public class JFontChooser extends JComponent {

    private final Font initialFont;    //fuente inicial
    private Font font;                 //fuente seleccionado por el usuario

    /**
     * Constructor de esta clase.
     * 
     * @param initialFont el fuente inicial
     */
    public JFontChooser(Font initialFont) {
        this.initialFont = initialFont;    //guarda el fuente inicial
    }

    /**
     * Método estático que construye e inicializa la clase JFontChooser para presentar al 
     * usuario el dialogo de selección de fuente.
     * 
     * @param owner la ventana padre
     * @param title el título de la ventana
     * @param initialFont el fuente inicial
     * 
     * @return el fuente seleccionado
     */
    public static Font showDialog(Frame owner, String title, Font initialFont) {
        JFontChooser fontChooser = new JFontChooser(initialFont);    //construye una instancia de JFontChooser

        //construye el dialogo de selección de fuente sobre la ventana padre
        JDialog dialog = new FontChooserDialog(owner, title, fontChooser);
        dialog.setVisible(true);    //hace visible el dialogo

        return fontChooser.getSelectedFont();    //retorna el fuente seleccionado
    }

    /**
     * Retorna el fuente inicial.
     * 
     * @return el fuente inicial
     */
    public Font getInitialFont() {
        return initialFont;
    }

    /**
     * Retorna el fuente seleccionado.
     * 
     * @return el fuente seleccionado
     */
    public Font getSelectedFont() {
        return font;
    }

    /**
     * Establece el fuente seleccionado.
     * 
     * @param font el fuente seleccionado
     */
    public void setSelectedFont(Font font) {
        this.font = font;
    }
}

class FontChooserDialog extends JDialog {

    private JTextField textFieldNames;     //campo de texto para el nombre del fuente
    private JTextField textFieldStyles;    //campo de texto para el estilo del fuente
    private JTextField textFieldSizes;     //campo de texto para el tamaño del fuente
    private JList listFontNames;     //lista para nombres de fuente
    private JList listFontStyles;    //lista para estilos de fuente
    private JList listFontSizes;     //lista para tamaños de fuente
    private JLabel textExample;    //etiqueta que muestra un ejemplo del fuente seleccionado
    //arreglo con nombres de fuente disponibles
    private static final String[] FONT_NAMES = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
    //arreglo con estilos de fuente
    private static final String[] FONT_STYLES = {
        &quot;Normal&quot;, &quot;Bold&quot;, &quot;Italic&quot;, &quot;Bold Italic&quot;
    };
    //arreglo con tamaños de fuente
    private static final String[] FONT_SIZES = {
        &quot;8&quot;, &quot;9&quot;, &quot;10&quot;, &quot;11&quot;, &quot;12&quot;, &quot;13&quot;, &quot;14&quot;, &quot;16&quot;, &quot;18&quot;, &quot;20&quot;, &quot;24&quot;, &quot;28&quot;, &quot;32&quot;, &quot;48&quot;, &quot;72&quot;
    };

    /**
     * Constructor de la clase.
     * 
     * Construye una ventana modal de dialogo sobre una ventana padre. El usuario 
     * puede seleccionar un fuente.
     * 
     * @param owner la ventana padre
     * @param title
     * @param jFontChooser 
     */
    public FontChooserDialog(Frame owner, String title, final JFontChooser jFontChooser) {
        /** invoca el constructor de la superclase para establecer la ventana padre, el título 
        de la ventana, y que será una ventana modal */
        super(owner, title, true);

        final EventHandler eventHandler = new EventHandler();    //construye una instancia de EventHandler
        JScrollPane jScrollPane;

        JPanel cp = (JPanel) getContentPane();                        //obtiene el panel de contenido principal
        cp.setLayout(new GridBagLayout());                            //establece un GridBagLayout
        cp.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));    //establece un borde de espacio

        //construye un conjunto de limitaciones para los componentes del GridBagLayout
        GridBagConstraints gbc = new GridBagConstraints();

        JLabel label1 = new JLabel(&quot;Name:&quot;);    //construye la etiqueta &quot;Name:&quot;
        gbc.insets = new Insets(0, 5, 0, 5);
        gbc.anchor = GridBagConstraints.FIRST_LINE_START;
        gbc.weightx = 0.5;
        gbc.gridx = 1;
        gbc.gridy = 1;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(label1, gbc);    //añade la etiqueta, coordenadas X:1 - Y:1

        JLabel label2 = new JLabel(&quot;Style:&quot;);    //construye la etiqueta &quot;Style:&quot;
        gbc.gridx = 2;
        gbc.gridy = 1;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(label2, gbc);    //añade la etiqueta, coordenadas X:2 - Y:1

        JLabel label3 = new JLabel(&quot;Size:&quot;);    //construye la etiqueta &quot;Size:&quot;
        gbc.gridx = 3;
        gbc.gridy = 1;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(label3, gbc);    //añade la etiqueta, coordenadas X:3 - Y:1

        textFieldNames = new JTextField(&quot;&quot;);    //construye el campo de texto para el nombre del fuente
        int fixedWidth = textFieldNames.getPreferredSize().width;
        Dimension fixedSize = new Dimension(fixedWidth, 20);
        textFieldNames.setMinimumSize(fixedSize);
        textFieldNames.setMaximumSize(fixedSize);
        textFieldNames.setPreferredSize(fixedSize);
        textFieldNames.addKeyListener(eventHandler);    //asigna el manejador de eventos para el teclado
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.gridx = 1;
        gbc.gridy = 2;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(textFieldNames, gbc);    //añade el campo de texto, coordenadas X:1 - Y:2

        textFieldStyles = new JTextField(&quot;&quot;);    //construye el campo de texto para el estilo del fuente
        fixedWidth = textFieldStyles.getPreferredSize().width;
        fixedSize = new Dimension(fixedWidth, 20);
        textFieldStyles.setMinimumSize(fixedSize);
        textFieldStyles.setMaximumSize(fixedSize);
        textFieldStyles.setPreferredSize(fixedSize);
        textFieldStyles.addKeyListener(eventHandler);    //asigna el manejador de eventos para el teclado
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.gridx = 2;
        gbc.gridy = 2;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(textFieldStyles, gbc);    //añade el campo de texto, coordenadas X:2 - Y:2

        textFieldSizes = new JTextField(&quot;&quot;);    //construye el campo de texto para el tamaño del fuente
        fixedWidth = textFieldSizes.getPreferredSize().width;
        fixedSize = new Dimension(fixedWidth, 20);
        textFieldSizes.setMinimumSize(fixedSize);
        textFieldSizes.setMaximumSize(fixedSize);
        textFieldSizes.setPreferredSize(fixedSize);
        textFieldSizes.addKeyListener(eventHandler);    //asigna el manejador de eventos para el teclado
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.gridx = 3;
        gbc.gridy = 2;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(textFieldSizes, gbc);    //añade el campo de texto, coordenadas X:3 - Y:2

        listFontNames = new JList(FONT_NAMES);    //construye la lista para nombres de fuente
        jScrollPane = new JScrollPane(listFontNames);
        String fontName = jFontChooser.getInitialFont().getName();
        listFontNames.setSelectedValue(fontName, true);    //selecciona el nombre de fuente inicial
        textFieldNames.setText(fontName);
        listFontNames.addListSelectionListener(eventHandler);
        gbc.gridx = 1;
        gbc.gridy = 3;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(jScrollPane, gbc);    //añade la lista, coordenadas X:1 - Y:3

        listFontStyles = new JList(FONT_STYLES);    //construye la lista para estilos de fuente
        jScrollPane = new JScrollPane(listFontStyles);
        int fontSyle = jFontChooser.getInitialFont().getStyle();
        listFontStyles.setSelectedIndex(fontSyle);    //selecciona el estilo de fuente inicial
        textFieldStyles.setText(listFontStyles.getSelectedValue().toString());
        listFontStyles.addListSelectionListener(eventHandler);
        gbc.gridx = 2;
        gbc.gridy = 3;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(jScrollPane, gbc);    //añade la lista, coordenadas X:2 - Y:3

        listFontSizes = new JList(FONT_SIZES);    //construye la lista para tamaños de fuente
        jScrollPane = new JScrollPane(listFontSizes);
        String fontSize = String.valueOf(jFontChooser.getInitialFont().getSize());
        listFontSizes.setSelectedValue(fontSize, true);    //selecciona el tamaño de fuente inicial
        textFieldSizes.setText(fontSize);
        listFontSizes.addListSelectionListener(eventHandler);
        gbc.gridx = 3;
        gbc.gridy = 3;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        cp.add(jScrollPane, gbc);    //añade la lista, coordenadas X:3 - Y:3

        textExample = new JLabel(&quot;AaBaCcDdEeFfGgHhJj&quot;);        //construye la etiqueta para mostrar un ejemplo del fuente seleccionado 
        textExample.setHorizontalAlignment(JLabel.CENTER);
        textExample.setFont(jFontChooser.getInitialFont());    //establece el fuente inicial
        textExample.setOpaque(true);
        textExample.setBackground(Color.WHITE);
        textExample.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(&quot;Texto de ejemplo&quot;),
                                                                 BorderFactory.createEmptyBorder(10, 10, 10, 10)));
        int fixedHeight = textExample.getPreferredSize().height;
        fixedSize = new Dimension(100, fixedHeight);
        textExample.setMinimumSize(fixedSize);
        textExample.setMaximumSize(fixedSize);
        textExample.setPreferredSize(fixedSize);
        gbc.gridx = 1;
        gbc.gridy = 4;
        gbc.gridwidth = 3;
        gbc.gridheight = 1;
        cp.add(textExample, gbc);    //añade la etiqueta, coordenadas X:1 - Y:4

        JPanel jp = new JPanel();                        //construye un panel para los botones
        JButton buttonOk = new JButton(&quot;Ok&quot;);            //construye el botón de aceptar
        JButton buttonCancel = new JButton(&quot;Cancel&quot;);    //construye el botón de cancelar
        jp.add(buttonOk);                                //añade los botones al panel
        jp.add(buttonCancel);
        gbc.anchor = GridBagConstraints.CENTER;
        gbc.gridx = 1;
        gbc.gridy = 5;
        cp.add(jp, gbc);    //añade el panel, coordenadas X:1 - Y:5

        //asigna un manejador de eventos para el botón de cancelar
        buttonOk.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                jFontChooser.setSelectedFont(getSelectedFont());
                //fontChooser.font = getSelectedFont();
                setVisible(false);
            }
        });

        //asigna un manejador de eventos para el botón de cancelar
        buttonCancel.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                setVisible(false);    //oculta esta ventana
            }
        });

        //asigna un manejador de eventos para el cierre del JDialog
        this.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                Window w = e.getWindow();    //convierte el componente afectado en una ventana
                w.setVisible(false);         //oculta esta ventana
            }
        });

        //asigna un manejador de eventos para cuando la ventana pierde la visibilidad
        this.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentHidden(ComponentEvent e) {
                Window w = (Window) e.getComponent();    //convierte el componente afectado en una ventana
                w.dispose();                             //destruye la ventana
            }
        });

        setResizable(false);              //no se permite redimensionar la ventana
        pack();                           //se le da el tamaño preferido
        setLocationRelativeTo(owner);     //la ventana se centra sobre el editor de texto
    }

    /**
     * Retorna el fuente seleccionado por el usuario. Es el producto resultante de la elección de 
     * un tipo, un estilo y un tamaño
     * 
     * @return el fuente seleccionado.
     */
    public Font getSelectedFont() {
        try {
            //retorna el fuente seleccionado en el dialogo
            return new Font(String.valueOf(listFontNames.getSelectedValue()), listFontStyles.getSelectedIndex(),
                            Integer.parseInt(String.valueOf(listFontSizes.getSelectedValue())));
        } catch (NumberFormatException nfe) {    //en caso de que ocurra una excepción
            System.err.println(nfe);
        }
        return null;    //retorna null
    }

    /**
     * Clase interna que extiende e implementa las clases e interfaces necesarias para 
     * atender y manejar los eventos sobre la ventana de selección de fuente.
     */
    class EventHandler extends KeyAdapter implements Comparator&lt;String&gt;,
                                                     ListSelectionListener {

        /**
         * Atiende y maneja los eventos de teclado cuando se libera una tecla.
         * 
         * @param ke evento de tecla
         */
        @Override
        public void keyReleased(KeyEvent ke) {
            //obtiene el origen del evento, y lo convierte en un campo de texto
            final JTextField eventTField = (JTextField) ke.getSource();
            final String text = eventTField.getText();    //obtiene el contenido del campo de texto


            //se averigua en que campo de texto se ah ejecutado el evento
            if (eventTField == textFieldNames) {    //si el campo de texto es textFieldNames
                //obtiene el índice del proximo elemento en la lista que coincida con el contenido del campo de texto
                int index = listFontNames.getNextMatch(text, 0, Position.Bias.Forward);

                if (index &gt; -1) {    //si el índice es mayor que -1
                    /** realiza una búsqueda binaria sobre el arreglo de nombres de fuente, se utiliza
                    esta clase como comparador implementando la interface Comparator */
                    if (Arrays.binarySearch(FONT_NAMES, text, this) &gt; -1) //si el resultado es mayor que -1
                    {
                        listFontNames.setSelectedIndex(index);    //selecciona el índice
                    }

                    listFontNames.ensureIndexIsVisible(index);    //hace el índice visible en la lista
                }
            } else if (eventTField == textFieldStyles) {    //si el campo de texto es textFieldStyles
                //obtiene el índice del proximo elemento en la lista que coincida con el contenido del campo de texto
                int index = listFontStyles.getNextMatch(text, 0, Position.Bias.Forward);

                if (index &gt; -1) {    //si el índice es mayor que -1
                    //itera sobre los elementos en el arreglo de estilos de fuente
                    for (int i = 0 ; i &lt; FONT_STYLES.length ; i++) {
                        //si el contenido del campo de texto es igual al elemento actual
                        if (text.equalsIgnoreCase(FONT_STYLES[i]) == true) {
                            listFontStyles.setSelectedIndex(index);    //selecciona el índice
                        }
                    }

                    listFontStyles.ensureIndexIsVisible(index);    //hace el índice visible en la lista
                }
            } else if (eventTField == textFieldSizes) {    //si el campo de texto es textFieldSizes
                //obtiene el índice del proximo elemento en la lista que coincida con el contenido del campo de texto
                int index = listFontSizes.getNextMatch(text, 0, Position.Bias.Forward);

                if (index &gt; -1) {    //si el índice es mayor que -1
                    //itera sobre los elementos en el arreglo de tamaños de fuente
                    for (int i = 0 ; i &lt; FONT_SIZES.length ; i++) {
                        //si el contenido del campo de texto es igual al elemento actual
                        if (text.equalsIgnoreCase(FONT_SIZES[i]) == true) {
                            listFontSizes.setSelectedIndex(index);    //selecciona el índice
                        }
                    }

                    listFontSizes.ensureIndexIsVisible(index);    //hace el índice visible en la lista
                }
            }

            //establece el fuente seleccionado en la etiqueta de muestra
            textExample.setFont(getSelectedFont());
        }

        /**
         * Comparación lexicográfica de dos cadenas de texto ignorando mayúsculas.
         * 
         * @param string1 una cadena de texto
         * @param string2 otra cadena de texto
         * @return true si son dos cadenas iguales, false en caso contrario 
         */
        @Override
        public int compare(String string1, String string2) {
            //compara dos cadenas de texto ignorando mayúsculas
            return string1.compareToIgnoreCase(string2);
        }

        /**
         * Atiende y maneja los eventos de selección en las listas de la ventana.
         * 
         * @param lse evento de selección en una lista
         */
        @Override
        public void valueChanged(ListSelectionEvent lse) {
            //averigua en que lista se ah ejecutado el evento
            if (lse.getSource() == listFontNames) {    //si la lista es listFontNames
                //establece el contenido del campo de texto textFieldNames
                textFieldNames.setText(String.valueOf(listFontNames.getSelectedValue()));
            } else if (lse.getSource() == listFontStyles) {    //si la lista es listFontStyles
                //establece el contenido del campo de texto textFieldStyles
                textFieldStyles.setText(String.valueOf(listFontStyles.getSelectedValue()));
            } else if (lse.getSource() == listFontSizes) {    //si la lista es listFontSizes
                //establece el contenido del campo de texto textFieldSizes
                textFieldSizes.setText(String.valueOf(listFontSizes.getSelectedValue()));
            }

            //establece el fuente seleccionado en la etiqueta de muestra
            textExample.setFont(getSelectedFont());
        }
    }
}
</pre>
<p>Estas son las imágenes .<em>PNG</em> 32&#215;32 que se utilizan como recursos en el proyecto:<br />
<img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_new.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_open.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_save.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_saveas.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_print.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_undo.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_redo.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_cut.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_copy.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_paste.png?w=32&#038;h=32" alt="" width="32" height="32" /></p>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/07/23/java-textpad-demo/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2357</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_new.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_open.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_save.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_saveas.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_print.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_undo.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_redo.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_cut.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_copy.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_paste.png" medium="image" />
	</item>
		<item>
		<title>Java &#8211; Crear un editor de texto</title>
		<link>https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/#comments</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Tue, 05 Jul 2011 02:26:54 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[editor]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JTextArea]]></category>
		<category><![CDATA[texto]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=1814</guid>

					<description><![CDATA[En este tutorial crearemos un editor básico multi-plataforma para documentos de texto plano. Se llamará TextPad Demo y se utilizará la biblioteca gráfica Swing por ser la biblioteca predeterminada en Java, además de flexible y portable. En el desarrollo puede sernos de utilidad un IDE para facilitarnos algunas tareas, pero en la creación de la [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>En este tutorial crearemos un editor básico multi-plataforma para documentos de texto plano. Se llamará <em>TextPad Demo</em> y se utilizará la biblioteca gráfica Swing por ser la biblioteca predeterminada en Java, además de flexible y portable. En el desarrollo puede sernos de utilidad un IDE para facilitarnos algunas tareas, pero en la creación de la interface gráfica de usuario (GUI) no se utilizará el editor gráfico, todo el código se escribirá directamente a mano ya sea en el IDE o en el editor de preferencia.</p>
<p><span id="more-1814"></span></p>
<p>Requisitos:</p>
<ul>
<li>Tener instalado el JDK (Java Development Kit): son las herramientas de desarrollo junto al entorno de ejecución. A la fecha actual el último JDK es el JSE 6.0 &#8211; Update 26.</li>
<li>Conocimientos básicos de Java.</li>
<li>En caso de no utilizar un IDE, se necesita una cierta comprensión de como trabajar con las herramientas de desarrollo del JDK desde la línea de comandos para construir el proyecto. Eso no se explica en este tutorial.</li>
</ul>
<h2 style="text-align:left;">Introducción</h2>
<hr />
<p>Se le denomina texto plano al texto que contiene únicamente caracteres y no contiene información que defina formatos como lo son la <em>Negrita</em>, <em> Cursiva</em>, <em>Subrayado</em>, etc. o el estilo de letra como lo es <em>Arial</em>, <em>Times</em>, <em>Courier</em>, etc. En prácticamente todos los sistemas operativos la extensión de archivo <em>.TXT </em>es por convención la preferida para archivos de texto plano, sin embargo se pueden utilizar otras<em>.</em> Algunas extensiones populares que también son de texto plano son <em>.INF</em>, <em>.DAT</em>, etc.</p>
<p>Aunque el editor trabajará internamente solo con texto plano, el área de edición del editor podrá configurarse visualmente con un fuente y color a gusto del usuario.</p>
<p>A continuación se lista la mayoría de las clases e interfaces que necesitaremos para crear este editor de texto.</p>
<p>Las clases que definen los contenedores y componentes principales de Swing que conformaran la interface gráfica:</p>
<ul>
<li><span style="color:#000000;"><code>javax.swing.JFrame</code></span> &#8211; Este es la ventana principal del editor.</li>
<li><span style="color:#000000;"><code>javax.swing.JMenuBar</code></span>, <span style="color:#000000;"><code>JPopupMenu</code></span>, <span style="color:#000000;"><code>JMenu</code></span>, <span style="color:#000000;"><code>JItem</code></span>, <span style="color:#000000;"><code>JCheckBoxMenuItem</code></span> &#8211; Con estos crearemos los menúes de opciones.</li>
<li><span style="color:#000000;"><code>javax.swing.JToolBar</code></span>, <span style="color:#000000;"><code>JButton</code></span> &#8211; Con estos crearemos la barra de herramientas.</li>
<li><span style="color:#000000;"><code>javax.swing.JTextArea</code></span> &#8211; Este es el área de edición para texto plano donde se visualiza el documento.</li>
<li><span style="color:#000000;"><code>javax.swing.JPanel</code></span>, <span style="color:#000000;"><code>JLabel</code></span> &#8211; Con estos crearemos la barra de estado.</li>
<li><span style="color:#000000;"><code>javax.swing.JFileChooser</code></span> &#8211; Este es un cuadro de dialogo que permite elegir archivos del sistema.</li>
</ul>
<p>Las clases e interfaces que le otorgaran funcionalidad al editor:</p>
<ul>
<li><span style="color:#000000;"><code>java.awt.event.WindowAdapter</code></span>, <span style="color:#000000;"><code>WindowEvent</code></span> &#8211; Control de eventos de la ventana principal.</li>
<li><span style="color:#000000;"><code>java.awt.event.ActionListener</code></span>, <span style="color:#000000;"><code>ActionEvent</code></span> &#8211; Control de eventos sobre menúes y botones.</li>
<li><span style="color:#000000;"><code>java.awt.event.MouseAdapter</code></span>, <span style="color:#000000;"><code>MouseEvent</code></span> &#8211; Control de eventos del ratón.</li>
<li><span style="color:#000000;"><code>java.awt.print.Printable</code></span>, <span style="color:#000000;"><code>PrinterJob</code></span>, <span style="color:#000000;"><code>PageFormat</code></span> &#8211; Impresión del documento.</li>
<li><span style="color:#000000;"><code>javax.swing.event.CaretListener</code></span>, <span style="color:#000000;"><code>CaretEvent</code></span> &#8211; Control de eventos sobre el cursor.</li>
<li><span style="color:#000000;"><code>javax.swing.undo.UndoManager</code></span> &#8211; Administrador de edición.</li>
<li><span style="color:#000000;"><code>javax.swing.event.UndoableEditListener</code></span>, <span style="color:#000000;"><code>UndoableEditEvent</code></span> &#8211; Control de cambios en el documento.</li>
<li><span style="color:#000000;"><code>java.io.File</code></span>, <span style="color:#000000;"><code>FileReader</code></span>, <span style="color:#000000;"><code>FileWriter</code></span>, <span style="color:#000000;"><code>BufferedReader</code></span>, <span style="color:#000000;"><code>BufferedWriter</code></span> &#8211; Manejo de archivos.</li>
</ul>
<p><strong>Nota:</strong> es buena idea leer la documentación de cada clase.</p>
<p>En resumen, las posibilidades y la funcionalidad de este editor:</p>
<ul>
<li>Cargar un documento en el área de edición.</li>
<li>Guardar el documento en un archivo.</li>
<li>Imprimir el documento.</li>
<li>Deshacer/Rehacer cambios en el documento.</li>
<li>Acciones típicas sobre texto: Cortar, Copiar, Pegar, Seleccionar todo, Buscar, llevar el cursor a una línea especifica.</li>
<li>Opciones visuales: ajuste de línea, ver y fijar barra de herramientas, ver barra de estado, fuente de letra, color de letra, color de fondo.</li>
</ul>
<p>Algunas imágenes del aspecto del editor en diferentes entornos:</p>
<p style="text-align:center;"><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg"><img data-attachment-id="2378" data-permalink="https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/java-tpeditor0/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg" data-orig-size="591,332" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;JosEdu&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1311184441&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="java-tpeditor0" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg?w=300" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg?w=465" class="aligncenter size-full wp-image-2378" title="java-tpeditor0" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg?w=372&amp;h=209 372w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg?w=150&amp;h=84 150w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg?w=300&amp;h=169 300w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg 591w" sizes="(max-width: 372px) 100vw, 372px" /></a><strong>Windows XP</strong></p>
<p style="text-align:center;"><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg"><img data-attachment-id="2261" data-permalink="https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/java-tpeditor1/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg" data-orig-size="590,328" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;JosEdu&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1309125587&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="java-tpeditor1" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg?w=300" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg?w=465" class="aligncenter size-full wp-image-2261" title="java-tpeditor1" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg?w=372&amp;h=207 372w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg?w=150&amp;h=83 150w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg?w=300&amp;h=167 300w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg 590w" sizes="(max-width: 372px) 100vw, 372px" /></a><strong>Windows Seven</strong></p>
<p style="text-align:center;"><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg"><img loading="lazy" data-attachment-id="2262" data-permalink="https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/java-tpeditor2/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg" data-orig-size="601,322" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;JosEdu&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1309191048&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="java-tpeditor2" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg?w=300" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg?w=465" class="aligncenter size-full wp-image-2262" title="java-tpeditor2" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg?w=372&amp;h=199 372w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg?w=150&amp;h=80 150w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg?w=300&amp;h=161 300w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg 601w" sizes="(max-width: 372px) 100vw, 372px" /></a><strong>Debian Squeeze 6 &#8211; GNOME 2</strong></p>
<p style="text-align:center;"><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor2.jpg"><br />
</a><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg"><img loading="lazy" data-attachment-id="2263" data-permalink="https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/java-tpeditor3/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg" data-orig-size="613,325" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;JosEdu&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1309200692&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="java-tpeditor3" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg?w=300" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg?w=465" class="aligncenter size-full wp-image-2263" title="java-tpeditor3" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg?w=372&amp;h=197 372w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg?w=150&amp;h=80 150w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg?w=300&amp;h=159 300w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg 613w" sizes="(max-width: 372px) 100vw, 372px" /></a><strong>Slackware 13 &#8211; KDE 4</strong></p>
<p><strong>Nota:</strong> faltaría una captura en Mac.</p>
<h2 style="text-align:left;">El proyecto TextPad</h2>
<hr />
<p>El proyecto TextPad contendrá los siguientes paquetes:</p>
<h3 style="text-align:left;">Paquete: textpademo</h3>
<p>Aquí se encuentran las clases del proyecto. Las cuatro principales definen un archivo de código fuente cada una:</p>
<ul>
<li>ActionPerformer.java</li>
<li>JFontChooser.java</li>
<li>PrintAction.java</li>
<li>TPEditor.java</li>
</ul>
<p><strong>Nota:</strong> dentro de algunos archivos de código fuente también hay otras clases adicionales, por ejemplo <span style="color:#000000;">EventHandler</span> es una clase interna en <span style="color:#000000;">TPEditor</span>, estas clases luego de compiladas quedan almacenadas en archivos <em> .CLASS</em> separados.</p>
<h3 style="text-align:left;">Paquete: res</h3>
<p>Aquí se encuentran los recursos del proyecto. Estos son diez archivos de imagen de tipo <em>.PNG</em> (dimensión: 32 x 32) para la interface gráfica:</p>
<ul>
<li>tp_copy.png</li>
<li>tp_cut.png</li>
<li>tp_new.png</li>
<li>tp_open.png</li>
<li>tp_paste.png</li>
<li>tp_print.png</li>
<li>tp_redo.png</li>
<li>tp_save.png</li>
<li>tp_saveas.png</li>
<li>tp_undo.png</li>
</ul>
<h3 style="text-align:center;">&#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211;</h3>
<p>Para aquellos que utilizan algún IDE, tener una estructura de proyecto bien organizada no supone ningún problema ni perdida de tiempo ya que cualquier IDE crea automáticamente una estructura de directorios adecuada y el proyecto comienza a tomar forma a medida que se le van agregando paquetes y clases. Además el IDE puede realizar la compilación del proyecto y el empaquetado en tarros <em>.JAR</em> con unos pocos clicks.</p>
<p>En <strong>NetBeans</strong> el proyecto se ve de la siguiente forma desde la pestaña «<span style="text-decoration:underline;">Projects</span>«:</p>
<p><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor52.jpg"><img class="aligncenter" title="java-tpeditor5" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor52.jpg?w=222&#038;h=195" alt="" width="222" height="195" /></a><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor5.jpg"><br />
</a></p>
<p>En <strong>Eclipse</strong> el proyecto se ve de la siguiente forma desde la pestaña «<span style="text-decoration:underline;">Package Explorer</span>«:</p>
<p><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor6.jpg"><br />
</a><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor62.jpg"><img class="aligncenter" title="java-tpeditor6" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor62.jpg?w=237&#038;h=226" alt="" width="237" height="226" /></a></p>
<p>En <strong>IntelliJ</strong> el proyecto se ve de la siguiente forma desde la pestaña «<span style="text-decoration:underline;">Project</span>«:</p>
<p><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg"><img loading="lazy" data-attachment-id="2344" data-permalink="https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/java-tpeditor7/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg" data-orig-size="242,279" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="java-tpeditor7" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg?w=242" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg?w=242" class="aligncenter size-full wp-image-2344" title="java-tpeditor7" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg 242w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg?w=130&amp;h=150 130w" sizes="(max-width: 242px) 100vw, 242px" /></a></p>
<p>En el caso de aquellos que no están utilizando un IDE, pueden recurrir a utilizar alguna herramienta de gestión de proyectos como <strong>Ant</strong> con el fin de automatizar y facilitar el proceso de compilado, empaquetado, y etc., simplemente escribiendo scripts XML.</p>
<p>La estructura principal de los directorios del proyecto es:</p>
<pre>...\TextPad\src<strong>\textpademo\</strong>
...\TextPad\src<strong>\res\ </strong></pre>
<p>Donde los directorios<em> textpademo\</em> y <em>res\</em> definen los paquetes del proyecto.</p>
<p>En Windows la ubicación completa del proyecto podría ser:</p>
<pre>c:\Users\User\Java\Proyectos\TextPad\src<strong>\textpademo\</strong>
c:\Users\User\Java\Proyectos\TextPad\src<strong>\res\</strong></pre>
<p>En Linux la ubicación completa del proyecto podría ser:</p>
<pre>/home/user/Java/Proyectos/TextPad/src<strong>/textpademo/</strong>
/home/user/Java/Proyectos/TextPad/src<strong>/res/</strong></pre>
<p>El siguiente esquema es una propuesta ideal para la estructura del proyecto:</p>
<pre>[TextPad/]
├── [src/]
│   ├── [res/]
│   │   ├── tp_copy.png
│   │   ├── tp_cut.png
│   │   ├── tp_new.png
│   │   ├── tp_open.png
│   │   ├── tp_paste.png
│   │   ├── tp_print.png
│   │   ├── tp_redo.png
│   │   ├── tp_save.png
│   │   ├── tp_saveas.png
│   │   └── tp_undo.png
│   └── [textpademo/]
│       ├── ActionPerformer.java
│       ├── JFontChooser.java
│       ├── PrintAction.java
│       └── TPEditor.java
├── [build/]
│       └── [classes/]
├── [doc/]
├── [test/]
└── build.xml</pre>
<h2 style="text-align:left;">Creando el editor</h2>
<hr />
<p>Ahora escribiremos el código del programa, clase por clase y método por método. Podemos observar aquí un sencillo diagrama de flujo que describe la ejecución a través de las clases principales del proyecto:</p>
<p><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor4.jpg"><img class="aligncenter" title="java-tpeditor4" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor4.jpg?w=372&#038;h=310" alt="" width="372" height="310" /></a></p>
<p>La propia clase <span style="color:#000000;"><code>TPEditor</code></span> podría encargarse de manejar los eventos, sin embargo decidí manejarlos a todos en una clase especial y desde esta llamar a otra para ejecutar las operaciones requeridas. Más allá de que aquí todo, o casi todo, es un objeto, el diseño presentado no es especialmente OO (orientado a objetos), sino más bien simple y concreto.</p>
<p><strong>Nota:</strong> El código fuente completo se encuentra al final.</p>
<h3 style="text-align:center;">Archivo TPEditor.java</h3>
<p>El archivo de código fuente <em>TPEditor.java</em> contiene la clase publica <span style="color:#000000;"><code>TPEditor</code></span> que se encarga de construir la interface gráfica de usuario (GUI), en esta clase se encuentra el método <span style="color:#000000;"><code>void main(String[] args)</code></span> que define el punto de entrada del programa.</p>
<p>Dentro de la clase <span style="color:#000000;"><code>TPEditor</code></span> se encuentra la clase interna <span style="color:#000000;"><code>EventHandler</code></span>, a la cual se le deja la tarea de manejar todos los eventos que ocurran sobre los componentes de la GUI construida.</p>
<p>Comencemos con el esqueleto de este archivo, se definen las clases, métodos (sin contenido), y variables:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
package textpademo;    //se define el paquete donde debe estar este archivo

import java.awt.BorderLayout;    //importamos todo lo que se utilizará
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.undo.UndoManager;

public class TPEditor {    //clase publica TPEditor

    private JFrame jFrame;            //instancia de JFrame (ventana principal)
    private JMenuBar jMenuBar;        //instancia de JMenuBar (barra de menú)
    private JToolBar jToolBar;        //instancia de JToolBar (barra de herramientas)
    private JTextArea jTextArea;      //instancia de JTextArea (area de edición)
    private JPopupMenu jPopupMenu;    //instancia de JPopupMenu (menú emergente)
    private JPanel statusBar;         //instancia de JPanel (barra de estado)

    private JCheckBoxMenuItem itemLineWrap;         //instancias de algunos items de menú que necesitan ser accesibles
    private JCheckBoxMenuItem itemShowToolBar;
    private JCheckBoxMenuItem itemFixedToolBar;
    private JCheckBoxMenuItem itemShowStatusBar;
    private JMenuItem mbItemUndo;
    private JMenuItem mbItemRedo;
    private JMenuItem mpItemUndo;
    private JMenuItem mpItemRedo;

    private JButton buttonUndo;    //instancias de algunos botones que necesitan ser accesibles
    private JButton buttonRedo;

    private JLabel sbFilePath;    //etiqueta que muestra la ubicación del archivo actual
    private JLabel sbFileSize;    //etiqueta que muestra el tamaño del archivo actual
    private JLabel sbCaretPos;    //etiqueta que muestra la posición del cursor en el área de edición

    private boolean hasChanged = false;    //el estado del documento actual, no modificado por defecto
    private File currentFile = null;       //el archivo actual, ninguno por defecto

    private final EventHandler eventHandler;          //instancia de EventHandler (la clase que maneja eventos)
    private final ActionPerformer actionPerformer;    //instancia de ActionPerformer (la clase que ejecuta acciones)
    private final UndoManager undoManager;            //instancia de UndoManager (administrador de edición)

    public static void main(String[] args) {    //punto de entrada del programa
    }

    public TPEditor() {    //constructor de la clase TPEditor
    }

    private void buildMenuBar() {    //construye la barra de menú
    }

    private void buildToolBar() {    //construye la barra de herramientas
    }

    private void buildTextArea() {    //construye el área de edición
    }

    private void buildStatusBar() {    //construye la barra de estado
    }

    private void buildPopupMenu() {    //construye el menú emergente
    }

    private void showPopupMenu(MouseEvent e) {    //despliega el menú emergente
    }

    void updateControls() {    //actualiza algunos componentes de la GUI
    }

    EventHandler getEventHandler() {    //retorna la instancia de EventHandler (la clase que maneja eventos)
    }

    UndoManager getUndoManager() {    //retorna la instancia de UndoManager (administrador de edición)
    }

    boolean documentHasChanged() {    //retorna el estado de la variable hasChanged
    }

    void setDocumentChanged(boolean hasChanged) {    //establece el estado de la variable hasChanged
    }

    JTextArea getJTextArea() {    //retorna la instancia de JTextArea (área de edición)
    }

    JFrame getJFrame() {    //retorna la instancia de JFrame (ventana principal)
    }

    File getCurrentFile() {    //retorna el valor de la variable currentFile
    }

    void setCurrentFile(File currentFile) {    //establece el valor de la variable currentFile
    }

    JLabel getJLabelFilePath() {    //retorna la instancia de la etiqueta sbFilePath
    }

    JLabel getJLabelFileSize() {    //retorna la instancia de la etiqueta sbFileSize
    }

    /* la clase EventHandler extiende e implementa las clases e interfaces necesarias para
    atender y manejar los eventos sobre la GUI principal del editor */
    class EventHandler extends MouseAdapter implements ActionListener,
                                                       CaretListener,
                                                       UndoableEditListener {

        @Override
        public void actionPerformed(ActionEvent ae) {    //implemento de la interface ActionListener
        }

        @Override
        public void caretUpdate(CaretEvent ce) {    //implemento de la interface CaretListener
        }

        @Override
        public void undoableEditHappened(UndoableEditEvent uee) {    //implemento de la interface UndoableEditListener
        }

        @Override
        public void mousePressed(MouseEvent me) {    //herencia de la clase MouseAdapter
        }

        @Override
        public void mouseReleased(MouseEvent me) {    //herencia de la clase MouseAdapter
        }
    }
}
</pre>
<p>A partir de aquí iremos completando el cuerpo de los métodos.</p>
<p>Comenzamos con el método <span style="color:#000000;"><code>void main(String[] args)</code></span>, este es el punto de entrada del programa, aquí es donde todo lo relacionado con nuestro código comienza. Construimos una instancia de la clase <span style="color:#000000;"><code>TPEditor</code></span>, la cual se encargará de construir la GUI, el detalle es que nos aseguramos que esto suceda desde el principio en el EDT (Event Dispatch Thread). Es de buena practica comenzar las aplicaciones Swing de esta forma, aunque es más importante cuando se trata de programas más complejos que este.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public static void main(String[] args) {
    //construye la GUI en el EDT (Event Dispatch Thread)
    javax.swing.SwingUtilities.invokeLater(new Runnable() {

        @Override
        public void run() {
            new TPEditor().jFrame.setVisible(true);    //hace visible la GUI creada por la clase TPEditor
        }
    });
}
...
</pre>
<p>Cuando el método <span style="color:#000000;"><code>void main(String[] args)</code></span> construye la clase <span style="color:#000000;"><code>TPEditor</code></span>, el correspondiente constructor de esta clase realiza su principal tarea de crear la GUI con todos los componente necesarios antes de que se haga visible para el usuario, y también se preparan y configuran otras clases que luego tendrán un papel fundamental.</p>
<p>Lo primero es asegurar un LookAndFeel adecuado para el sistema subyacente, de esta forma nuestro editor encajara con el aspecto de las demás aplicaciones del sistema. Luego creamos un <span style="color:#000000;"><code>JFrame</code></span> que será la ventana principal del editor y se le asigna una clase anónima que se encargara de manejar el evento de cierre, todos los restantes eventos serán manejados por <span style="color:#000000;"><code>EventHandler</code></span>.</p>
<p>Aquí se inicializan clases importantes, se construye una instancia de la clase <span style="color:#000000;"><code>EventHandler</code></span> que manejara los eventos, una instancia de la clase <span style="color:#000000;"><code>ActionPerformer</code></span> que ejecutará las operaciones necesarias y una instancia de la clase <span style="color:#000000;"><code>UndoManager</code></span> que administrará las ediciones realizadas en el documento permitiendo deshacer o rehacer cambios.</p>
<p>Se ejecuta una sucesión de métodos encargados de crear las diferentes partes del editor: el área de edición, una barra de menú, una barra de herramientas, y una barra de estado. En esta sucesión el método que tiene relevancia en el orden es <span style="color:#000000;"><code>void buildTextArea()</code></span> el cual debe de ejecutarse primero, el motivo se explica más adelante.</p>
<p>Todas las partes del editor se colocan dentro del contenedor principal del <span style="color:#000000;"><code>JFrame</code></span>, en su correspondiente orientación.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public TPEditor() {
    try {    //LookAndFeel nativo
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (Exception ex) {
        System.err.println(ex);
    }

    //construye un JFrame con título
    jFrame = new JFrame(&quot;TextPad Demo - Sin Título&quot;);
    jFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

    //asigna un manejador de eventos para el cierre del JFrame
    jFrame.addWindowListener(new WindowAdapter() {

        @Override
        public void windowClosing(WindowEvent we) {
            actionPerformer.actionExit();    //invoca el método actionExit()
        }
    });

    eventHandler = new EventHandler();              //construye una instancia de EventHandler
    actionPerformer = new ActionPerformer(this);    //construye una instancia de ActionPerformer
    undoManager = new UndoManager();                //construye una instancia de UndoManager
    undoManager.setLimit(50);                       //le asigna un límite al buffer de ediciones

    buildTextArea();     //construye el área de edición, es importante que esta sea la primera parte en construirse
    buildMenuBar();      //construye la barra de menú
    buildToolBar();      //construye la barra de herramientas
    buildStatusBar();    //construye la barra de estado
    buildPopupMenu();    //construye el menú emergente

    jFrame.setJMenuBar(jMenuBar);                              //designa la barra de menú del JFrame
    Container c = jFrame.getContentPane();                     //obtiene el contenedor principal
    c.add(jToolBar, BorderLayout.NORTH);                       //añade la barra de herramientas, orientación NORTE del contendor
    c.add(new JScrollPane(jTextArea), BorderLayout.CENTER);    //añade el área de edición en el CENTRO
    c.add(statusBar, BorderLayout.SOUTH);                      //añade la barra de estado, orientación SUR

    //configura el JFrame con un tamaño inicial proporcionado con respecto a la pantalla
    Dimension pantalla = Toolkit.getDefaultToolkit().getScreenSize();
    jFrame.setSize(pantalla.width / 2, pantalla.height / 2);

    //centra el JFrame en pantalla
    jFrame.setLocationRelativeTo(null);
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void buildTextArea()</code></span> es el encargado de construir el área de edición donde el usuario trabaja con sus documentos. El componente ideal para documentos de texto plano es el <span style="color:#000000;"><code>JTextArea</code></span>, a este se le asigna el manejador de eventos necesario para conocer la posición del cursor, detectar eventos del ratón, detectar cambios en el documento, y etc.</p>
<p>El motivo por el cual esta parte del editor es la primera en construirse se debe a que en el sistema operativo Windows (y en otros también) se le asigna por defecto al <span style="color:#000000;"><code>JTextArea</code></span> combinaciones de teclas asociadas a las operaciones típicas: Cortar (CTRL + X), Copiar (CTRL + C) y Pegar (CTRL + V). Como la intención en este editor es crear una barra de menú en la cual los items Cortar, Copiar y Pegar tengan asignados esas mismas combinaciones de teclas hay que asegurarse de tener el control sobre estas operaciones, para eso se remueven las combinaciones CTRL + X, CTRL + C y CTRL + V de la configuración predeterminada cuando recién se crea el <span style="color:#000000;"><code>JTextArea</code></span> y antes de que se construya la barra de menú. Si no realizáramos esto, posiblemente al presionar por ejemplo CTRL + X no estaríamos invocando nuestro propio método. Si bien lo anterior no supone un problema critico para este programa, en proyectos más complejos podría ser algo a tener en cuenta.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private void buildTextArea() {
    jTextArea = new JTextArea();    //construye un JTextArea

    //se configura por defecto para que se ajusten las líneas al tamaño del área de texto ...
    jTextArea.setLineWrap(true);
    //... y que se respete la integridad de las palabras en el ajuste
    jTextArea.setWrapStyleWord(true);

    //asigna el manejador de eventos para el cursor
    jTextArea.addCaretListener(eventHandler);
    //asigna el manejador de eventos para el ratón
    jTextArea.addMouseListener(eventHandler);
    //asigna el manejador de eventos para registrar los cambios sobre el documento
    jTextArea.getDocument().addUndoableEditListener(eventHandler);

    //remueve las posibles combinaciones de teclas asociadas por defecto con el JTextArea
    jTextArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK), &quot;none&quot;);    //remueve CTRL + X (&quot;Cortar&quot;)
    jTextArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK), &quot;none&quot;);    //remueve CTRL + C (&quot;Copiar&quot;)
    jTextArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK), &quot;none&quot;);    //remueve CTRL + V (&quot;Pegar&quot;)
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void buildMenuBar()</code></span> es el encargado de construir la barra de menú la cual tiene cuatro menúes propiamente dichos. Cada menú tiene una cierta cantidad de elementos (items), a la mayoría de estos se les atribuye una combinación de teclas para facilitar su acceso con el teclado, por ejemplo al item «Abrir» se accede con CTRL + O. A todos los items se les asigna un nombre de comando, esta es una forma de identificarlos para facilitar la tarea del manejador de eventos. También, a todos los items se les asigna el manejador de eventos, el cual siempre es el mismo, y para evitar escribir el mismo código correspondiente a cada item creado se utiliza al final un bucle que itera sobre todos los componentes de la barra de menú y le asigna el manejador de eventos siempre que no sea un simple separador.</p>
<p><strong>Nota:</strong> en la barra de menú no todo componente es un item, también existen separadores. Si se le asigna un manejador de eventos a un separador se genera una excepción.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private void buildMenuBar() {
    jMenuBar = new JMenuBar();    //construye un JMenuBar

    //construye el menú &quot;Archivo&quot;, a continuación se construyen los items para este menú
    JMenu menuFile = new JMenu(&quot;Archivo&quot;);

    //construye el item &quot;Nuevo&quot;
    JMenuItem itemNew = new JMenuItem(&quot;Nuevo&quot;);
    //le asigna una combinación de teclas
    itemNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK));
    //le asigna un nombre de comando
    itemNew.setActionCommand(&quot;cmd_new&quot;);

    JMenuItem itemOpen = new JMenuItem(&quot;Abrir&quot;);
    itemOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK));
    itemOpen.setActionCommand(&quot;cmd_open&quot;);

    JMenuItem itemSave = new JMenuItem(&quot;Guardar&quot;);
    itemSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
    itemSave.setActionCommand(&quot;cmd_save&quot;);

    JMenuItem itemSaveAs = new JMenuItem(&quot;Guardar como...&quot;);
    itemSaveAs.setActionCommand(&quot;cmd_saveas&quot;);
    itemSaveAs.addActionListener(eventHandler);

    JMenuItem itemPrint = new JMenuItem(&quot;Imprimir&quot;);
    itemPrint.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_MASK));
    itemPrint.setActionCommand(&quot;cmd_print&quot;);

    JMenuItem itemExit = new JMenuItem(&quot;Salir&quot;);
    itemExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK));
    itemExit.setActionCommand(&quot;cmd_exit&quot;);

    menuFile.add(itemNew);    //se añaden los items al menú &quot;Archivo&quot;
    menuFile.add(itemOpen);
    menuFile.add(itemSave);
    menuFile.add(itemSaveAs);
    menuFile.addSeparator();
    menuFile.add(itemPrint);
    menuFile.addSeparator();
    menuFile.add(itemExit);
    //----------------------------------------------

    //construye el menú &quot;Editar&quot;, a continuación se construyen los items para este menú
    JMenu menuEdit = new JMenu(&quot;Editar&quot;);

    mbItemUndo = new JMenuItem(&quot;Deshacer&quot;);
    mbItemUndo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
    mbItemUndo.setEnabled(false);
    mbItemUndo.setActionCommand(&quot;cmd_undo&quot;);

    mbItemRedo = new JMenuItem(&quot;Rehacer&quot;);
    mbItemRedo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
    mbItemRedo.setEnabled(false);
    mbItemRedo.setActionCommand(&quot;cmd_redo&quot;);

    JMenuItem itemCut = new JMenuItem(&quot;Cortar&quot;);
    itemCut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
    itemCut.setActionCommand(&quot;cmd_cut&quot;);

    JMenuItem itemCopy = new JMenuItem(&quot;Copiar&quot;);
    itemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
    itemCopy.setActionCommand(&quot;cmd_copy&quot;);

    JMenuItem itemPaste = new JMenuItem(&quot;Pegar&quot;);
    itemPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK));
    itemPaste.setActionCommand(&quot;cmd_paste&quot;);

    JMenuItem itemSearch = new JMenuItem(&quot;Buscar&quot;);
    itemSearch.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, ActionEvent.CTRL_MASK));
    itemSearch.setActionCommand(&quot;cmd_search&quot;);

    JMenuItem itemSearchNext = new JMenuItem(&quot;Buscar siguiente&quot;);
    itemSearchNext.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0));
    itemSearchNext.setActionCommand(&quot;cmd_searchnext&quot;);

    JMenuItem itemGotoLine = new JMenuItem(&quot;Ir a la línea...&quot;);
    itemGotoLine.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, ActionEvent.CTRL_MASK));
    itemGotoLine.setActionCommand(&quot;cmd_gotoline&quot;);

    JMenuItem itemSelectAll = new JMenuItem(&quot;Seleccionar todo&quot;);
    itemSelectAll.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK));
    itemSelectAll.setActionCommand(&quot;cmd_selectall&quot;);

    menuEdit.add(mbItemUndo);    //se añaden los items al menú &quot;Editar&quot;
    menuEdit.add(mbItemRedo);
    menuEdit.addSeparator();     //añade separadores entre algunos items
    menuEdit.add(itemCut);
    menuEdit.add(itemCopy);
    menuEdit.add(itemPaste);
    menuEdit.addSeparator();
    menuEdit.add(itemSearch);
    menuEdit.add(itemSearchNext);
    menuEdit.add(itemGotoLine);
    menuEdit.addSeparator();
    menuEdit.add(itemSelectAll);
    //----------------------------------------------

    //construye el menú &quot;Opciones&quot;, a continuación se construyen los items para este menú
    JMenu menuTools = new JMenu(&quot;Opciones&quot;);

    itemLineWrap = new JCheckBoxMenuItem(&quot;Ajuste de línea&quot;);
    itemLineWrap.setSelected(true);
    itemLineWrap.setActionCommand(&quot;cmd_linewrap&quot;);

    itemShowToolBar = new JCheckBoxMenuItem(&quot;Ver barra de herramientas&quot;);
    itemShowToolBar.setSelected(true);
    itemShowToolBar.setActionCommand(&quot;cmd_showtoolbar&quot;);

    itemFixedToolBar = new JCheckBoxMenuItem(&quot;Fijar barra de herramientas&quot;);
    itemFixedToolBar.setSelected(true);
    itemFixedToolBar.setActionCommand(&quot;cmd_fixedtoolbar&quot;);

    itemShowStatusBar = new JCheckBoxMenuItem(&quot;Ver barra de estado&quot;);
    itemShowStatusBar.setSelected(true);
    itemShowStatusBar.setActionCommand(&quot;cmd_showstatusbar&quot;);

    JMenuItem itemFont = new JMenuItem(&quot;Fuente de letra&quot;);
    itemFont.setActionCommand(&quot;cmd_font&quot;);

    JMenuItem itemFontColor = new JMenuItem(&quot;Color de letra&quot;);
    itemFontColor.setActionCommand(&quot;cmd_fontcolor&quot;);

    JMenuItem itemBackgroundColor = new JMenuItem(&quot;Color de fondo&quot;);
    itemBackgroundColor.setActionCommand(&quot;cmd_backgroundcolor&quot;);

    menuTools.add(itemLineWrap);    //se añaden los items al menú &quot;Opciones&quot;
    menuTools.add(itemShowToolBar);
    menuTools.add(itemFixedToolBar);
    menuTools.add(itemShowStatusBar);
    menuTools.addSeparator();
    menuTools.add(itemFont);
    menuTools.add(itemFontColor);
    menuTools.add(itemBackgroundColor);

    //construye el menú &quot;Ayuda&quot;, a continuación se construye el único item para este menú
    JMenu menuHelp = new JMenu(&quot;Ayuda&quot;);

    JMenuItem itemAbout = new JMenuItem(&quot;Acerca de&quot;);
    itemAbout.setActionCommand(&quot;cmd_about&quot;);

    menuHelp.add(itemAbout);     //se añade el único item al menú &quot;Ayuda&quot;
    //----------------------------------------------

    jMenuBar.add(menuFile);    //se añaden los menúes construidos a la barra de menú
    jMenuBar.add(Box.createHorizontalStrut(5));    //añade espacios entre cada menú
    jMenuBar.add(menuEdit);
    jMenuBar.add(Box.createHorizontalStrut(5));
    jMenuBar.add(menuTools);
    jMenuBar.add(Box.createHorizontalStrut(5));
    jMenuBar.add(menuHelp);

    /* itera sobre todos los componentes de la barra de menú, se les asigna el mismo
    manejador de eventos a todos excepto a los separadores */
    for (Component c1 : jMenuBar.getComponents()) {
        //si el componente es un menú
        if (c1.getClass().equals(javax.swing.JMenu.class)) {
            //itera sobre los componentes del menú
            for (Component c2 : ((JMenu) c1).getMenuComponents()) {
                //si el componente no es un separador
                if (!c2.getClass().equals(javax.swing.JPopupMenu.Separator.class)) {
                    ((JMenuItem) c2).addActionListener(eventHandler);
                }
            }
        }
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void buildToolBar()</code></span> es el encargado de construir la barra de herramientas. Esta formada por diez botones de imagen que representan las operaciones mas notables que puede realizar el editor: Nuevo, Abrir, Guardar, Guardar como, Imprimir, Deshacer, Rehacer, Cortar, Copiar y Pegar. A todos estos botones se les asigna un nombre de comando; esta es una forma de identificarlos para facilitar la tarea del manejador de eventos, un icono de imagen; archivo .PNG de 32&#215;32 pixeles ubicado en los recursos del proyecto, una etiqueta flotante; texto de descripción visible cuando el ratón esta encima del botón. También, a todos los botones se les asigna el manejador de eventos, el cual siempre es el mismo, y para evitar escribir el mismo código correspondiente a cada botón creado se utiliza al final un bucle que itera sobre todos los componentes de la barra de herramientas y le asigna el manejador de eventos siempre que se trate de un botón.</p>
<p><strong>Nota:</strong> en la barra de herramientas no todo componente es un botón, también existen separadores. Si se le asigna un manejador de eventos a un separador se genera una excepción.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private void buildToolBar() {
    jToolBar = new JToolBar();       //construye un JToolBar
    jToolBar.setFloatable(false);    //se configura por defecto como barra fija

    //construye el botón &quot;Nuevo&quot;
    JButton buttonNew = new JButton();
    //le asigna una etiqueta flotante
    buttonNew.setToolTipText(&quot;Nuevo&quot;);
    //le asigna una imagen ubicada en los recursos del proyecto
    buttonNew.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_new.png&quot;)));
    //le asigna un nombre de comando
    buttonNew.setActionCommand(&quot;cmd_new&quot;);

    JButton buttonOpen = new JButton();
    buttonOpen.setToolTipText(&quot;Abrir&quot;);
    buttonOpen.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_open.png&quot;)));
    buttonOpen.setActionCommand(&quot;cmd_open&quot;);

    JButton buttonSave = new JButton();
    buttonSave.setToolTipText(&quot;Guardar&quot;);
    buttonSave.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_save.png&quot;)));
    buttonSave.setActionCommand(&quot;cmd_save&quot;);

    JButton buttonSaveAs = new JButton();
    buttonSaveAs.setToolTipText(&quot;Guardar como...&quot;);
    buttonSaveAs.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_saveas.png&quot;)));
    buttonSaveAs.setActionCommand(&quot;cmd_saveas&quot;);

    JButton buttonPrint = new JButton();
    buttonPrint.setToolTipText(&quot;Imprimir&quot;);
    buttonPrint.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_print.png&quot;)));
    buttonPrint.setActionCommand(&quot;cmd_print&quot;);

    buttonUndo = new JButton();
    buttonUndo.setEnabled(false);
    buttonUndo.setToolTipText(&quot;Deshacer&quot;);
    buttonUndo.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_undo.png&quot;)));
    buttonUndo.setActionCommand(&quot;cmd_undo&quot;);

    buttonRedo = new JButton();
    buttonRedo.setEnabled(false);
    buttonRedo.setToolTipText(&quot;Rehacer&quot;);
    buttonRedo.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_redo.png&quot;)));
    buttonRedo.setActionCommand(&quot;cmd_redo&quot;);

    JButton buttonCut = new JButton();
    buttonCut.setToolTipText(&quot;Cortar&quot;);
    buttonCut.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_cut.png&quot;)));
    buttonCut.setActionCommand(&quot;cmd_cut&quot;);

    JButton buttonCopy = new JButton();
    buttonCopy.setToolTipText(&quot;Copiar&quot;);
    buttonCopy.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_copy.png&quot;)));
    buttonCopy.setActionCommand(&quot;cmd_copy&quot;);

    JButton buttonPaste = new JButton();
    buttonPaste.setToolTipText(&quot;Pegar&quot;);
    buttonPaste.setIcon(new ImageIcon(getClass().getResource(&quot;/res/tp_paste.png&quot;)));
    buttonPaste.setActionCommand(&quot;cmd_paste&quot;);

    jToolBar.add(buttonNew);    //se añaden los botones construidos a la barra de herramientas
    jToolBar.add(buttonOpen);
    jToolBar.add(buttonSave);
    jToolBar.add(buttonSaveAs);
    jToolBar.addSeparator();    //añade separadores entre algunos botones
    jToolBar.add(buttonPrint);
    jToolBar.addSeparator();
    jToolBar.add(buttonUndo);
    jToolBar.add(buttonRedo);
    jToolBar.addSeparator();
    jToolBar.add(buttonCut);
    jToolBar.add(buttonCopy);
    jToolBar.add(buttonPaste);

    /* itera sobre todos los componentes de la barra de herramientas, se les asigna el
    mismo margen y el mismo manejador de eventos unicamente a los botones */
    for (Component c : jToolBar.getComponents()) {
        //si el componente es un botón
        if (c.getClass().equals(javax.swing.JButton.class)) {
            JButton jb = (JButton) c;
            jb.setMargin(new Insets(0, 0, 0, 0));
            jb.addActionListener(eventHandler);
        }
    }
}
...
</pre>
<p>El método <span style="color:#000000;">void buildStatusBar()</span> es el encargado de construir la barra de estado. En esta se muestra alguna información sobre el archivo\documento actual. Como no existe una barra de estado en el set estándar de componentes Swing, y para no tener que utilizar una librería externa escrita por terceros, se crea un panel al cual se le añaden las etiquetas necesarias para mostrar la información y con esto ya es suficiente para imitar una barra de estado decente.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private void buildStatusBar() {
    statusBar = new JPanel();    //construye un JPanel
    //se configura con un BoxLayout
    statusBar.setLayout(new BoxLayout(statusBar, BoxLayout.LINE_AXIS));
    //le añade un borde compuesto
    statusBar.setBorder(BorderFactory.createCompoundBorder(
                        BorderFactory.createLoweredBevelBorder(),
                        BorderFactory.createEmptyBorder(5, 5, 5, 5)));

    //construye la etiqueta para mostrar la ubicación del archivo actual
    sbFilePath = new JLabel(&quot;...&quot;);
    //construye la etiqueta para mostrar el tamaño del archivo actual
    sbFileSize = new JLabel(&quot;&quot;);
    //construye la etiqueta para mostrar la posición del cursor en el documento actual
    sbCaretPos = new JLabel(&quot;...&quot;);

    /* se añaden las etiquetas construidas al JPanel, el resultado es un panel
    similar a una barra de estado */
    statusBar.add(sbFilePath);
    statusBar.add(Box.createRigidArea(new Dimension(10, 0)));
    statusBar.add(sbFileSize);
    statusBar.add(Box.createRigidArea(new Dimension(10, 0)));
    statusBar.add(Box.createHorizontalGlue());
    statusBar.add(sbCaretPos);
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void buildPopupMenu()</code></span> es el encargado de construir el menú emergente que solo se hace visible con el click izquierdo del ratón sobre el área de edición. A todos los items se les asigna un nombre de comando, esta es una forma de identificarlos para facilitar la tarea del manejador de eventos. También, a todos los items se les asigna el manejador de eventos, el cual siempre es el mismo, y para evitar escribir el mismo código correspondiente a cada item creado se utiliza al final un bucle que itera sobre todos los componentes del menú emergente y le asigna el manejador de eventos siempre que no sea un simple separador.</p>
<p><strong>Nota:</strong> en el menú emergente no todo componente es un item, también existen separadores. Si se le asigna un manejador de eventos a un separador se genera una excepción.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private void buildPopupMenu() {
    jPopupMenu = new JPopupMenu();    //se construye un JPopupMenu

    //construye el item &quot;Deshacer&quot;
    mpItemUndo = new JMenuItem(&quot;Deshacer&quot;);
    //se configura desactivado por defecto
    mpItemUndo.setEnabled(false);
    //le asigna un nombre de comando
    mpItemUndo.setActionCommand(&quot;cmd_undo&quot;);

    mpItemRedo = new JMenuItem(&quot;Rehacer&quot;);
    mpItemRedo.setEnabled(false);
    mpItemRedo.setActionCommand(&quot;cmd_redo&quot;);

    JMenuItem itemCut = new JMenuItem(&quot;Cortar&quot;);
    itemCut.setActionCommand(&quot;cmd_cut&quot;);

    JMenuItem itemCopy = new JMenuItem(&quot;Copiar&quot;);
    itemCopy.setActionCommand(&quot;cmd_copy&quot;);

    JMenuItem itemPaste = new JMenuItem(&quot;Pegar&quot;);
    itemPaste.setActionCommand(&quot;cmd_paste&quot;);

    JMenuItem itemGoto = new JMenuItem(&quot;Ir a...&quot;);
    itemGoto.setActionCommand(&quot;cmd_gotoline&quot;);

    JMenuItem itemSearch = new JMenuItem(&quot;Buscar&quot;);
    itemSearch.setActionCommand(&quot;cmd_search&quot;);

    JMenuItem itemSearchNext = new JMenuItem(&quot;Buscar siguiente&quot;);
    itemSearchNext.setActionCommand(&quot;cmd_searchnext&quot;);

    JMenuItem itemSelectAll = new JMenuItem(&quot;Seleccionar todo&quot;);
    itemSelectAll.setActionCommand(&quot;cmd_selectall&quot;);

    jPopupMenu.add(mpItemUndo);    //se añaden los items al menú emergente
    jPopupMenu.add(mpItemRedo);
    jPopupMenu.addSeparator();     //añade separadores entre algunos items
    jPopupMenu.add(itemCut);
    jPopupMenu.add(itemCopy);
    jPopupMenu.add(itemPaste);
    jPopupMenu.addSeparator();
    jPopupMenu.add(itemGoto);
    jPopupMenu.add(itemSearch);
    jPopupMenu.add(itemSearchNext);
    jPopupMenu.addSeparator();
    jPopupMenu.add(itemSelectAll);

    /* itera sobre todos los componentes del menú emergente, se les asigna el mismo
    manejador de eventos a todos excepto a los separadores */
    for (Component c : jPopupMenu.getComponents()) {
        //si el componente es un item
        if (c.getClass().equals(javax.swing.JMenuItem.class)) {
            ((JMenuItem) c).addActionListener(eventHandler);
        }
    }
}
...
</pre>
<p>Como el menú emergente solo se hace visible haciendo click sobre el área de edición, se necesita de un método que lo haga visible al detectar el correspondiente evento del ratón. Usualmente en la mayoría de los sistemas se tomaría un click izquierdo como el responsable de mostrar un menú de opciones, sin embargo esto no tiene porque ser siempre así. El manejador de eventos invocará al método <span style="color:#000000;"><code>void showPopupMenu()</code></span> en la situación de un click del ratón, y este método averiguara si es el evento indicado para hacer visible el menú.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private void showPopupMenu(MouseEvent me) {
    if (me.isPopupTrigger() == true) {    //si el evento es el desencadenador del menú emergente
        //hace visible el menú emergente en las coordenadas actuales del ratón
        jPopupMenu.show(me.getComponent(), me.getX(), me.getY());
    }
}
...
</pre>
<p>Las opciones «Deshacer» y «Rehacer» están presentes en la barra de menú, la barra de herramientas y el menú emergente. Al inicio del programa y cuando se carga un nuevo documento estas opciones están desactivadas, y según el estado del documento actual estas opciones pueden activarse o desactivarse. El criterio para esto es manejado por el método <span style="color:#000000;"><code>void updateControls()</code></span> el cual es invocado por el manejador de eventos cuando se detectan cambios en el área de edición, este método activa o desactiva estas opciones según lo que permita el administrador de edición basado en los límites del buffer.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
void updateControls() {
    //averigua si se pueden deshacer los cambios en el documento actual
    boolean canUndo = undoManager.canUndo();
    //averigua si se pueden rehacer los cambios en el documento actual
    boolean canRedo = undoManager.canRedo();

    //activa o desactiva las opciones en la barra de menú
    mbItemUndo.setEnabled(canUndo);
    mbItemRedo.setEnabled(canRedo);

    //activa o desactiva las opciones en la barra de herramientas
    buttonUndo.setEnabled(canUndo);
    buttonRedo.setEnabled(canRedo);

    //activa o desactiva las opciones en el menú emergente
    mpItemUndo.setEnabled(canUndo);
    mpItemRedo.setEnabled(canRedo);
}
...
</pre>
<p>Algunos datos (instancias y variables) necesitan ser accedidos externamente por otras clases en el paquete <span style="color:#000000;"><code>textpademo</code></span>. Para ello están los correspondientes getters y setters.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
EventHandler getEventHandler() {    //retorna la instancia de EventHandler (la clase interna que maneja eventos)
    return eventHandler;
}

UndoManager getUndoManager() {    //retorna la instancia de UndoManager (administrador de edición)
    return undoManager;
}

boolean documentHasChanged() {    //retorna el estado del documento actual
    return hasChanged;
}

void setDocumentChanged(boolean hasChanged) {    //establece el estado del documento actual
    this.hasChanged = hasChanged;
}

JTextArea getJTextArea() {    //retorna la instancia de JTextArea (área de edición)
    return jTextArea;
}

JFrame getJFrame() {    //retorna la instancia de JFrame (ventana principal del editor)
    return jFrame;
}

File getCurrentFile() {    //retorna la instancia de File (el archivo actual)
    return currentFile;
}

void setCurrentFile(File currentFile) {    //establece el archivo actual
    this.currentFile = currentFile;
}

JLabel getJLabelFilePath() {    //retorna la instancia de la etiqueta sbFilePath
    return sbFilePath;
}

JLabel getJLabelFileSize() {    //retorna la instancia de la etiqueta sbFileSize
    return sbFileSize;
}
...
</pre>
<p>Hasta aquí hemos visto los métodos de la clase <span style="color:#000000;"><code>TPEditor</code></span>, ahora continuamos con los métodos de la clase interna <span style="color:#000000;"><code>EventHander</code></span>. En esta se sobrescriben e implementan una variedad de métodos necesarios para poder detectar los eventos en la GUI del editor y poder actuar en consecuencia llamando a la clase <span style="color:#000000;"><code>ActionPerformer</code></span>, no obstante algunas operaciones serán ejecutadas aquí mismo.</p>
<p>Al implementar la interface <span style="color:#000000;"><code>java.awt.event.ActionListener</code></span> se implementa el método <span style="color:#000000;"><code>void actionPerformed()</code></span>, donde se averigua que botón o item de menú ah seleccionado el usuario y se realiza la operación correspondiente.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
@Override
public void actionPerformed(ActionEvent ae) {
    String ac = ae.getActionCommand();    //se obtiene el nombre del comando ejecutado

    if (ac.equals(&quot;cmd_new&quot;) == true) {    //opción seleccionada: &quot;Nuevo&quot;
        actionPerformer.actionNew();
    } else if (ac.equals(&quot;cmd_open&quot;) == true) {    //opción seleccionada: &quot;Abrir&quot;
        actionPerformer.actionOpen();
    } else if (ac.equals(&quot;cmd_save&quot;) == true) {    //opción seleccionada: &quot;Guardar&quot;
        actionPerformer.actionSave();
    } else if (ac.equals(&quot;cmd_saveas&quot;) == true) {    //opción seleccionada: &quot;Guardar como&quot;
        actionPerformer.actionSaveAs();
    } else if (ac.equals(&quot;cmd_print&quot;) == true) {    //opción seleccionada: &quot;Imprimir&quot;
        actionPerformer.actionPrint();
    } else if (ac.equals(&quot;cmd_exit&quot;) == true) {    //opción seleccionada: &quot;Salir&quot;
        actionPerformer.actionExit();
    } else if (ac.equals(&quot;cmd_undo&quot;) == true) {    //opción seleccionada: &quot;Deshacer&quot;
        actionPerformer.actionUndo();
    } else if (ac.equals(&quot;cmd_redo&quot;) == true) {    //opción seleccionada: &quot;Rehacer&quot;
        actionPerformer.actionRedo();
    } else if (ac.equals(&quot;cmd_cut&quot;) == true) {    //opción seleccionada: &quot;Cortar&quot;
        //corta el texto seleccionado en el documento
        jTextArea.cut();
    } else if (ac.equals(&quot;cmd_copy&quot;) == true) {    //opción seleccionada: &quot;Copiar&quot;
        //copia el texto seleccionado en el documento
        jTextArea.copy();
    } else if (ac.equals(&quot;cmd_paste&quot;) == true) {    //opción seleccionada: &quot;Pegar&quot;
        //pega en el documento el texto del portapapeles
        jTextArea.paste();
    } else if (ac.equals(&quot;cmd_gotoline&quot;) == true) {    //opción seleccionada: &quot;Ir a la línea...&quot;
        actionPerformer.actionGoToLine();
    } else if (ac.equals(&quot;cmd_search&quot;) == true) {    //opción seleccionada: &quot;Buscar&quot;
        actionPerformer.actionSearch();
    } else if (ac.equals(&quot;cmd_searchnext&quot;) == true) {    //opción seleccionada: &quot;Buscar siguiente&quot;
        actionPerformer.actionSearchNext();
    } else if (ac.equals(&quot;cmd_selectall&quot;) == true) {    //opción seleccionada: &quot;Seleccionar todo&quot;
        jTextArea.selectAll();
    } else if (ac.equals(&quot;cmd_linewrap&quot;) == true) {    //opción seleccionada: &quot;Ajuste de línea&quot;
        //si esta propiedad esta activada se desactiva, o lo inverso
        jTextArea.setLineWrap(!jTextArea.getLineWrap());
        jTextArea.setWrapStyleWord(!jTextArea.getWrapStyleWord());
    } else if (ac.equals(&quot;cmd_showtoolbar&quot;) == true) {    //opción seleccionada: &quot;Ver barra de herramientas&quot;
        //si la barra de herramientas esta visible se oculta, o lo inverso
        jToolBar.setVisible(!jToolBar.isVisible());
    } else if (ac.equals(&quot;cmd_fixedtoolbar&quot;) == true) {    //opción seleccionada: &quot;Fijar barra de herramientas&quot;
        //si esta propiedad esta activada se desactiva, o lo inverso
        jToolBar.setFloatable(!jToolBar.isFloatable());
    } else if (ac.equals(&quot;cmd_showstatusbar&quot;) == true) {    //opción seleccionada: &quot;Ver barra de estado&quot;
        //si la barra de estado esta visible se oculta, o lo inverso
        statusBar.setVisible(!statusBar.isVisible());
    } else if (ac.equals(&quot;cmd_font&quot;) == true) {    //opción seleccionada: &quot;Fuente de letra&quot;
        actionPerformer.actionSelectFont();
    } else if (ac.equals(&quot;cmd_fontcolor&quot;) == true) {    //opción seleccionada: &quot;Color de letra&quot;
        actionPerformer.actionSelectFontColor();
    } else if (ac.equals(&quot;cmd_backgroundcolor&quot;) == true) {    //opción seleccionada: &quot;Color de fondo&quot;
        actionPerformer.actionSelectBackgroundColor();
    } else if (ac.equals(&quot;cmd_about&quot;) == true) {    //opción seleccionada: &quot;Acerca de&quot;
        //presenta un dialogo modal con alguna informacion
        JOptionPane.showMessageDialog(jFrame,
                                      &quot;TextPad Demo por Dark[byte]&quot;,
                                      &quot;Acerca de&quot;,
                                      JOptionPane.INFORMATION_MESSAGE);
    }
}
...
</pre>
<p>Al implementar la interface <span style="color:#000000;"><code>javax.swing.event.CaretListener</code></span> se implementa el método <span style="color:#000000;"><code>void caretUpdate()</code></span>, el cual es invocado cuando el cursor se mueve en el área de edición. Aquí mismo se realizan unos simples cálculos para mostrar en la barra de estado la información sobre la posición actual del cursor.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
@Override
public void caretUpdate(CaretEvent e) {
    final int caretPos;  //valor de la posición del cursor sin inicializar
    int y = 1;           //valor de la línea inicialmente en 1
    int x = 1;           //valor de la columna inicialmente en 1

    try {
        //obtiene la posición del cursor con respecto al inicio del JTextArea (área de edición)
        caretPos = jTextArea.getCaretPosition();
        //sabiendo lo anterior se obtiene el valor de la línea actual (se cuenta desde 0)
        y = jTextArea.getLineOfOffset(caretPos);

        /** a la posición del cursor se le resta la posición del inicio de la línea para
           determinar el valor de la columna actual */
        x = caretPos - jTextArea.getLineStartOffset(y);

        //al valor de la línea actual se le suma 1 porque estas comienzan contándose desde 0
        y += 1;
    } catch (BadLocationException ex) {    //en caso de que ocurra una excepción
        System.err.println(ex);
    }

    /** muestra la información recolectada en la etiqueta sbCaretPos de la
       barra de estado, también se incluye el número total de lineas */
    sbCaretPos.setText(&quot;Líneas: &quot; + jTextArea.getLineCount() + &quot; - Y: &quot; + y + &quot; - X: &quot; + x);
}
...
</pre>
<p>Al implementar la interface <span style="color:#000000;"><code>javax.swing.event.UndoableEditListener</code></span> se implementa el método <span style="color:#000000;"><code>void undoableEditHappened()</code></span>, el cual es invocado cuando el usuario realiza algún cambio en el área de edición, es decir sobre el documento. El administrador de edición guarda estos cambios en un buffer y permite al usuario deshacer o rehacer. Sin embargo el buffer fue limitado a 50 registros, por lo cual mas allá de ese valor no se guardaran los cambios.</p>
<p>Este evento es también tomado como referencia para cambiar el estado del documento actual representado por la variable <span style="color:#000000;"><code>hasChanged</code></span>, esta al valer <span style="color:#000000;"><code>true</code></span> significa que el documento actual no ah sido guardado, por lo que en caso de que el usuario intente cerrar abruptamente el editor se le ofrecerá guardar los cambios.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
@Override
public void undoableEditHappened(UndoableEditEvent uee) {
    /* el cambio realizado en el área de edición se guarda en el buffer
       del administrador de edición */
    undoManager.addEdit(uee.getEdit());
    updateControls();     //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;

    hasChanged = true;    //marca el documento como modificado
}
...
</pre>
<p>Al extender la clase <span style="color:#000000;"><code>java.awt.event.MouseAdapter</code></span> se sobrescriben los métodos <span style="color:#000000;"><code>void mousePressed(MouseEvent me)</code></span> y <span style="color:#000000;"><code>void mouseReleased(MouseEvent me)</code></span>, los cuales permiten detectar el click del ratón sobre el área de edición. Ambos invocan al método <span style="color:#000000;"><code>void showPopupMenu()</code></span>, definido en la clase <span style="color:#000000;"><code>TPEditor</code></span>, con el fin de hacer visible el menú emergente.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
@Override
public void mousePressed(MouseEvent me) {
    showPopupMenu(me);
}

@Override
public void mouseReleased(MouseEvent me) {
    showPopupMenu(me);
}
...
</pre>
<h3 style="text-align:left;">Archivo ActionPerformer.java</h3>
<p>El archivo de código fuente <em>ActionPerformer.java</em> contiene la clase publica <span style="color:#000000;"><code>ActionPerformer</code></span> que se encarga de ejecutar la mayoría de las acciones solicitadas por el manejador de eventos, algunas acciones muy simples son ejecutadas en el propio manejador de eventos sin necesitar llegar hasta aquí y otras acciones muy complejas requieren de clases adicionales (por ejemplo <span style="color:#000000;"><code>PrintAction</code></span>) que se explicarán más adelante.</p>
<p>La clase <span style="color:#000000;"><code>ActionPerformer</code></span> también contiene una clase anónima interna que define un filtro de extensiones de archivo.</p>
<p>Comencemos con el esqueleto de este archivo, se definen las clases, métodos (sin contenido), y variables:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
package textpademo;    //se define el paquete donde debe estar este archivo

import java.awt.Color;    //importamos todo lo que se utilizará
import java.awt.Font;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Pattern;
import javax.swing.JColorChooser;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.BadLocationException;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;

public class ActionPerformer {    //clase publica ActionPerformer

    private final TPEditor tpEditor;    //instancia de TPEditor (la clase principal)
    private String lastSearch = &quot;&quot;;     //la última búsqueda de texto realizada, por defecto no contiene nada

    public ActionPerformer(TPEditor tpEditor) {    //constructor de la clase ActionPerformer
    }

    public void actionNew() {    //opción seleccionada: &quot;Nuevo&quot;
    }

    public void actionOpen() {    //opción seleccionada: &quot;Abrir&quot;
    }

    public void actionSave() {    //opción seleccionada: &quot;Guardar&quot;
    }

    public void actionSaveAs() {    //opción seleccionada: &quot;Guardar como&quot;
    }

    public void actionPrint() {    //opción seleccionada: &quot;Imprimir&quot;
    }

    public void actionExit() {    //opción seleccionada: &quot;Salir&quot;
    }

    public void actionUndo() {    //opción seleccionada: &quot;Deshacer&quot;
    }

    public void actionRedo() {    //opción seleccionada: &quot;Rehacer&quot;
    }

    public void actionSearch() {    //opción seleccionada: &quot;Buscar&quot;
    }

    public void actionSearchNext() {    //opción seleccionada: &quot;Buscar siguiente&quot;
    }

    public void actionGoToLine() {    //opción seleccionada: &quot;Ir a la línea...&quot;
    }

    public void actionSelectFont() {   //opción seleccionada: &quot;Fuente de letra&quot;
    }

    public void actionSelectFontColor() {    //opción seleccionada: &quot;Color de letra&quot;
    }

    public void actionSelectBackgroundColor() {    //opción seleccionada: &quot;Color de fondo&quot;
    }

    private static JFileChooser getJFileChooser() {    //retorna un JFileChooser
    }

    //clase anónima interna que define un filtro de extensiones
    private static FileFilter textFileFilter = new FileFilter() {
        public boolean accept(File f) {
        }

        public String getDescription() {
        }
    };

    private static String shortPathName(String longpath) {    //comprime una ruta de archivo muy larga
    }

    private static String roundFileSize(long length) {    //retorna el tamaño de un archivo redondeado
    }
}
</pre>
<p>Al instanciar esta clase el constructor recibe como argumento una instancia de la clase principal <span style="color:#000000;"><code>TPEditor</code></span> y la guarda para poder acceder a datos y métodos de la misma.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public ActionPerformer(TPEditor tpEditor) {
    this.tpEditor = tpEditor;    //guarda la instancia de la clase TPEditor
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionNew()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Nuevo</span>«. El documento actual en el área de edición es reemplazado por uno nuevo vacío, lo cual no significa que se crea un archivo de este nuevo documento en el disco duro. Si el documento actual esta marcado como modificado, es decir la variable <span style="color:#000000;"><code>hasChanged</code></span> vale <span style="color:#000000;"><code>true</code></span>, entonces se le ofrece al usuario guardar los cambios físicamente antes de crear el nuevo documento. Básicamente lo que se hace aquí es reusar el área de edición para poder escribir otro documento.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionNew() {
    if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
        //se le ofrece al usuario guardar los cambios
        int option = JOptionPane.showConfirmDialog(tpEditor.getJFrame(), &quot;¿Desea guardar los cambios?&quot;);

        switch (option) {
            case JOptionPane.YES_OPTION:       //si elige que si
                actionSaveFile();              //se guarda el archivo
                break;
            case JOptionPane.CANCEL_OPTION:    //si elige cancelar
                return;                        //se cancela esta operación
            //en otro caso se continúa con la operación y no se guarda el documento actual
        }
    }

    tpEditor.getJFrame().setTitle(&quot;TextPad Demo - Sin Título&quot;);    //nuevo título de la ventana

    //limpia el contenido del area de edición
    tpEditor.getJTextArea().setText(&quot;&quot;);
    //limpia el contenido de las etiquetas en la barra de estado
    tpEditor.getJLabelFilePath().setText(&quot;&quot;);
    tpEditor.getJLabelFileSize().setText(&quot;&quot;);

    tpEditor.getUndoManager().die();    //limpia el buffer del administrador de edición
    tpEditor.updateControls();          //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;

    //el archivo asociado al documento actual se establece como null
    tpEditor.setCurrentFile(null);
    //marca el estado del documento como no modificado
    tpEditor.setDocumentChanged(false);
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionOpen()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Abrir</span>«. Se le presenta al usuario una ventana de dialogo donde elegir un archivo de texto presente en el sistema, se carga su contenido en el área de edición mostrando así un nuevo documento. Si el documento actual esta marcado como modificado, es decir la variable <span style="color:#000000;"><code>hasChanged</code></span> vale <span style="color:#000000;"><code>true</code></span>, entonces se le ofrece al usuario guardar los cambios antes de cargar el archivo. En esta operación podría ocurrir una excepción si el usuario no tuviera los permisos necesarios para abrir el archivo elegido.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionOpen() {
    if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
        //le ofrece al usuario guardar los cambios
        int option = JOptionPane.showConfirmDialog(tpEditor.getJFrame(), &quot;¿Desea guardar los cambios?&quot;);

        switch (option) {
            case JOptionPane.YES_OPTION:     //si elige que si
                actionSaveFile();            //se guarda el archivo
                break;
            case JOptionPane.CANCEL_OPTION:  //si elige cancelar
                return;                      //se cancela esta operación
            //en otro caso se continúa con la operación y no se guarda el documento actual
        }
    }

    JFileChooser fc = getJFileChooser();    //obtiene un JFileChooser

    //se presenta un dialogo modal para que el usuario seleccione un archivo
    int state = fc.showOpenDialog(tpEditor.getJFrame());

    if (state == JFileChooser.APPROVE_OPTION) {    //si elige abrir el archivo
        File f = fc.getSelectedFile();    //obtiene el archivo seleccionado

        try {
            //abre un flujo de datos desde el archivo seleccionado
            BufferedReader br = new BufferedReader(new FileReader(f));
            tpEditor.getJTextArea().read(br, null);    //lee desde el flujo de datos hacia el área de edición
            br.close();    //cierra el flujo de datos

            //asigna el manejador de eventos para registrar los cambios en el nuevo documento actual
            tpEditor.getJTextArea().getDocument().addUndoableEditListener(tpEditor.getEventHandler());

            tpEditor.getUndoManager().die();    //limpia el buffer del administrador de edición
            tpEditor.updateControls();          //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;

            //nuevo título de la ventana con el nombre del archivo abierto
            tpEditor.getJFrame().setTitle(&quot;TextPad Demo - &quot; + f.getName());

            //muestra la ubicación del archivo abierto
            tpEditor.getJLabelFilePath().setText(shortPathName(f.getAbsolutePath()));
            //muestra el tamaño del archivo abierto
            tpEditor.getJLabelFileSize().setText(roundFileSize(f.length()));

            //establece el archivo abierto como el archivo actual
            tpEditor.setCurrentFile(f);
            //marca el estado del documento como no modificado
            tpEditor.setDocumentChanged(false);
        } catch (IOException ex) {    //en caso de que ocurra una excepción
            //presenta un dialogo modal con alguna información de la excepción
            JOptionPane.showMessageDialog(tpEditor.getJFrame(),
                                          ex.getMessage(),
                                          ex.toString(),
                                          JOptionPane.ERROR_MESSAGE);
        }
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionSave()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Guardar</span>«. Se guarda el contenido del área de edición en el archivo actual. Al igual que en cualquier programa, el usuario utiliza esta opción normalmente cuando el documento ya fue guardado anteriormente, es decir ya existe un archivo asociado al documento actual, pero si este no fuera el caso entonces este método desvía la operación hacia el método <span style="color:#000000;"><code>void actionSaveAs()</code></span> que se explica luego.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionSave() {
    if (tpEditor.getCurrentFile() == null) {    //si no hay un archivo asociado al documento actual
        actionSaveAs();    //invoca el método actionSaveAs()
    } else if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
        try {
            //abre un flujo de datos hacia el archivo asociado al documento actual
            BufferedWriter bw = new BufferedWriter(new FileWriter(tpEditor.getCurrentFile()));
            //escribe desde el flujo de datos hacia el archivo
            tpEditor.getJTextArea().write(bw);
            bw.close();    //cierra el flujo

            //marca el estado del documento como no modificado
            tpEditor.setDocumentChanged(false);
        } catch (IOException ex) {    //en caso de que ocurra una excepción
            //presenta un dialogo modal con alguna información de la excepción
            JOptionPane.showMessageDialog(tpEditor.getJFrame(),
                                          ex.getMessage(),
                                          ex.toString(),
                                          JOptionPane.ERROR_MESSAGE);
        }
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionSaveAs()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Guardar como</span>«. Se le presenta al usuario una ventana de dialogo donde elegir en que ubicación prefiere guardar el documento actual. Al igual que en cualquier programa, el usuario utilizará esta opción normalmente cuando el documento no fue guardado anteriormente, es decir cuando todavía no existe un archivo asociado al documento actual.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionSaveAs() {
    JFileChooser fc = getJFileChooser();    //se obtiene un JFileChooser

    //presenta un dialogo modal para que el usuario seleccione un archivo
    int state = fc.showSaveDialog(tpEditor.getJFrame());
    if (state == JFileChooser.APPROVE_OPTION) {    //si elige guardar en el archivo
        File f = fc.getSelectedFile();    //se obtiene el archivo seleccionado

        try {
            //abre un flujo de datos hacia el archivo asociado seleccionado
            BufferedWriter bw = new BufferedWriter(new FileWriter(f));
            //escribe desde el flujo de datos hacia el archivo
            tpEditor.getJTextArea().write(bw);
            bw.close();    //cierra el flujo

            //nuevo título de la ventana con el nombre del archivo guardado
            tpEditor.getJFrame().setTitle(&quot;TextPad Demo - &quot; + f.getName());

            //muestra la ubicación del archivo guardado
            tpEditor.getJLabelFilePath().setText(shortPathName(f.getAbsolutePath()));
            //muestra el tamaño del archivo guardado
            tpEditor.getJLabelFileSize().setText(roundFileSize(f.length()));

            //establece el archivo guardado como el archivo actual
            tpEditor.setCurrentFile(f);
            //marca el estado del documento como no modificado
            tpEditor.setDocumentChanged(false);
        } catch (IOException ex) {    //en caso de que ocurra una excepción
            //presenta un dialogo modal con alguna información de la excepción
            JOptionPane.showMessageDialog(tpEditor.getJFrame(),
                                          ex.getMessage(),
                                          ex.toString(),
                                          JOptionPane.ERROR_MESSAGE);
        }
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionPrint()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Imprimir</span>«. Si el documento actual no esta vacío, se invoca al método estático <span style="color:#000000;"><code>boolean print()</code></span> de la clase <span style="color:#000000;"><code>PrintAction</code></span> para presentar al usuario una ventana de dialogo donde configurar la impresión. Aquí participa nuestra clase <span style="color:#000000;"><code>PrintAction</code></span> que implementa la interface <span style="color:#000000;"><code>Printable</code></span>.</p>
<p>La comprobación para saber si el documento no esta vacío se podría implementar también en otras operaciones del editor. La impresión retorna un valor booleano en caso de éxito o lo contrario, pero en este caso se esta ignorando ya que no se hace nada útil con el mismo.</p>
<p><strong>Nota</strong>: Desde <strong>J2SE 6</strong> la alternativa mas sencilla y prudente es utilizar los nuevos métodos sobrecargados <span style="color:#000000;"><code>boolean print(...)</code></span> definidos en la clase <span style="color:#000000;"><code>JTextComponent</code></span> y disponibles también en sus subclases como por ejemplo en el <span style="color:#000000;"><code>JTextArea</code></span>.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionPrint() {
    boolean result = false;    //resultado de la impresión, por defecto es false

    //si el documento actual no esta vacío
    if (tpEditor.getJTextArea().getText().trim().equals(&quot;&quot;) == false) {
        //invoca nuestra la clase PrintAction para presentar el dialogo de impresión
        result = PrintAction.print(tpEditor.getJTextArea(), tpEditor.getJFrame());
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionExit()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Salir</span>» o intenta cerrar la ventana. Si el documento actual esta marcado como modificado, es decir la variable <span style="color:#000000;"><code>hasChanged</code></span> vale <span style="color:#000000;"><code>true</code></span>, entonces se le ofrece al usuario guardar los cambios antes de finalizar el programa.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionExit() {
    if (tpEditor.documentHasChanged() == true) {    //si el documento esta marcado como modificado
        //se le ofrece al usuario guardar los cambios
        int option = JOptionPane.showConfirmDialog(tpEditor.getJFrame(), &quot;¿Desea guardar los cambios?&quot;);

        switch (option) {
            case JOptionPane.YES_OPTION:     //si elige que si
                actionSave();                //se guarda el archivo
                break;
            case JOptionPane.CANCEL_OPTION:  //si elige cancelar
                return;                      //se cancela esta operación
            //en otro caso se continúa con la operación y no se guarda el documento actual
        }
    }

    System.exit(0);    //se finaliza el programa con el código 0 (sin error)
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionUndo()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Deshacer</span>«. A través del administrador de edición se deshace el último cambio realizado en el documento actual.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionUndo() {
    try {
        //deshace el último cambio realizado sobre el documento en el área de edición
        tpEditor.getUndoManager().undo();
    } catch (CannotUndoException ex) {    //en caso de que ocurra una excepción
        System.err.println(ex);
    }

    //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;
    tpEditor.updateControls();
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionRedo()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Rehacer</span>«. A través del administrador de edición se rehace el último cambio realizado en el documento actual.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionRedo() {
    try {
        //rehace el último cambio realizado sobre el documento en el área de edición
        tpEditor.getUndoManager().redo();
    } catch (CannotRedoException ex) {    //en caso de que ocurra una excepción
        System.err.println(ex);
    }

    //actualiza el estado de las opciones &quot;Deshacer&quot; y &quot;Rehacer&quot;
    tpEditor.updateControls();
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionSearch()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Buscar</span>«. Se le presenta al usuario una ventana de dialogo donde introducir el texto a buscar en el área de edición, si se encuentra se selecciona para resaltarlo en el área de edición, de lo contrario no sucede nada. Luego de una primera búsqueda se pueden realizar búsquedas subsecuentes con la opción «<span style="text-decoration:underline;">Buscar Siguiente</span>» que se explica luego.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionSearch() {
    //solicita al usuario que introduzca el texto a buscar
    String text = JOptionPane.showInputDialog(
            tpEditor.getJFrame(),
            &quot;Texto:&quot;,
            &quot;TextPad Demo - Buscar&quot;,
            JOptionPane.QUESTION_MESSAGE);

    if (text != null) {    //si se introdujo texto (puede ser una cadena vacía)
        String textAreaContent = tpEditor.getJTextArea().getText();    //obtiene todo el contenido del área de edición
        int pos = textAreaContent.indexOf(text);    //obtiene la posición de la primera ocurrencia del texto

        if (pos &gt; -1) {    //si la posición es mayor a -1 significa que la búsqueda fue positiva
            //selecciona el texto en el área de edición para resaltarlo
            tpEditor.getJTextArea().select(pos, pos + text.length());
        }

        //establece el texto buscado como el texto de la última búsqueda realizada
        lastSearch = text;
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionSearchNext()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Buscar siguiente</span>«. Realiza una búsqueda del texto guardado en la última búsqueda, es decir el contenido de <span style="color:#000000;"><code>lastSearch</code></span>, si se encuentra se selecciona para resaltarlo en el área de edición, de lo contrario no sucede nada. Este método busca a partir de la posición actual del cursor en el área de edición. Si la última busqueda no contiene nada entonces este método desvía la operación hacia el método <span style="color:#000000;"><code>void actionSearch()</code></span> que fue explicado antes.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionSearchNext() {
    if (lastSearch.isEmpty() == false) {    //si la última búsqueda contiene texto
        String textAreaContent = tpEditor.getJTextArea().getText();    //se obtiene todo el contenido del área de edición
        int pos = tpEditor.getJTextArea().getCaretPosition();    //se obtiene la posición del cursor sobre el área de edición
        //buscando a partir desde la posición del cursor, se obtiene la posición de la primera ocurrencia del texto
        pos = textAreaContent.indexOf(lastSearch, pos);

        if (pos &gt; -1) {    //si la posición es mayor a -1 significa que la búsqueda fue positiva
            //selecciona el texto en el área de edición para resaltarlo
            tpEditor.getJTextArea().select(pos, pos + lastSearch.length());
        }
    } else {    //si la última búsqueda no contiene nada
        actionSearch();    //invoca el método actionSearch()
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionGoToLine()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Ir a la línea&#8230;</span>«. Se le presenta al usuario una ventana de dialogo donde introducir el número de línea, y si este un dato coherente, es decir se encuentra dentro de los limites del área de edición, se posiciona el cursor en el inicio de la línea solicitada.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionGoToLine() {
    //solicita al usuario que introduzca el número de línea
    String line = JOptionPane.showInputDialog(
            tpEditor.getJFrame(),
            &quot;Número:&quot;,
            &quot;TextPad Demo - Ir a la línea...&quot;,
            JOptionPane.QUESTION_MESSAGE);

    if (line != null &amp;&amp; line.length() &gt; 0) {    //si se introdujo un dato
        try {
            int pos = Integer.parseInt(line);    //el dato introducido se convierte en entero

            //si el número de línea esta dentro de los límites del área de texto
            if (pos &gt;= 0 &amp;&amp; pos &lt;= tpEditor.getJTextArea().getLineCount()) {
                //posiciona el cursor en el inicio de la línea
                tpEditor.getJTextArea().setCaretPosition(tpEditor.getJTextArea().getLineStartOffset(pos));
            }
        } catch (NumberFormatException ex) {    //en caso de que ocurran excepciones
            System.err.println(ex);
        } catch (BadLocationException ex) {
            System.err.println(ex);
        }
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionSelectFont()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Fuente de letra</span>«. Se le presenta al usuario un dialogo donde elegir el fuente que prefiere para la letra en el área de edición. Aquí participa nuestra clase <span style="color:#000000;"><code>JFontChooser</code></span> que extiende de <span style="color:#000000;"><code>JDialog</code></span>.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionSelectFont() {
    //presenta el dialogo de selección de fuentes
    Font font = JFontChooser.showDialog(tpEditor.getJFrame(),
                                        &quot;TextPad Demo - Fuente de letra:&quot;,
                                        null);
    if (font != null) {    //si un fuente fue seleccionado
        //se establece como fuente del área de edición
        tpEditor.getJTextArea().setFont(font);
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionSelectFontColor()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Color de letra</span>«. Se le presenta al usuario un dialogo donde elegir el color que prefiere para la letra en el área de edición.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionSelectFontColor() {
    //presenta el dialogo de selección de colores
    Color color = JColorChooser.showDialog(tpEditor.getJFrame(),
                                           &quot;TextPad Demo - Color de letra:&quot;,
                                           tpEditor.getJTextArea().getForeground());
    if (color != null) {    //si un color fue seleccionado
        //se establece como color del fuente y cursor
        tpEditor.getJTextArea().setForeground(color);
        tpEditor.getJTextArea().setCaretColor(color);
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void actionSelectBackgroundColor()</code></span> es ejecutado cuando el usuario selecciona la opción «<span style="text-decoration:underline;">Color de fondo</span>«. Se le presenta al usuario un dialogo donde elegir el color que prefiere para el fondo del área de edición.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void actionSelectBackgroundColor() {
    //presenta el dialogo de selección de colores
    Color color = JColorChooser.showDialog(tpEditor.getJFrame(),
                                           &quot;TextPad Demo - Color de fondo:&quot;,
                                           tpEditor.getJTextArea().getForeground());
    if (color != null) {    //si un color fue seleccionado
        //se establece como color de fondo
        tpEditor.getJTextArea().setBackground(color);
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>JFileChooser getJFileChooser()</code></span> es utilizado por otros métodos para obtener un <span style="color:#000000;"><code>JFileChooser</code></span> construido y configurado listo para usar donde el usuario puede elegir un archivo del sistema. Se utiliza un filtro de extensiones para mostrar en principio solo los archivos de texto (idealmente los «<em>.txt</em>«), aunque el usuario puede cambiar el tipo de archivo y elegir cualquiera.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private static JFileChooser getJFileChooser() {
    JFileChooser fc = new JFileChooser();                     //construye un JFileChooser
    fc.setDialogTitle(&quot;TextPad Demo - Elige un archivo:&quot;);    //se le establece un título
    fc.setMultiSelectionEnabled(false);                       //desactiva la multi-selección
    fc.setFileFilter(textFileFilter);                         //aplica un filtro de extensiones
    return fc;    //retorna el JFileChooser
}
...
</pre>
<p>Aquí tenemos una clase anónima interna donde se sobrescriben los métodos necesarios de <span style="color:#000000;"><code>javax.swing.filechooser.FileFilter</code></span> para aplicar un filtro de extensiones en el <span style="color:#000000;"><code>JFileChooser</code></span>.</p>
<p><strong>Nota</strong>: Desde <strong>J2SE 6</strong>, gracias a nuevas clases incorporadas en la API, esto podría realizarse sencillamente con: <span style="color:#000000;"><code>new FileNameExtensionFilter("Text Files", "txt")</code></span>.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private static FileFilter textFileFilter = new FileFilter() {
    public boolean accept(File f) {
        //acepta directorios y archivos de extensión .txt
        return f.isDirectory() || f.getName().toLowerCase().endsWith(&quot;txt&quot;);
    }

    public String getDescription() {
        //la descripción del tipo de archivo aceptado
        return &quot;Text Files&quot;;
    }
};
...
</pre>
<p>El método <span style="color:#000000;"><code>String shortPathName(String longpath)</code></span> es utilizado por otros métodos para obtener la ruta completa de la ubicación de un archivo en forma comprimida. Es un procedimiento sencillo donde cada nombre de directorio que tenga 10 o mas caracteres será reducido a sus 3 primeros caracteres añadiéndosele unos «&#8230;» que indican la reducción. El propósito es mostrar la ruta completa (en realidad comprimida) en la barra de estado.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private static String shortPathName(String longPath) {
    //construye un arreglo de cadenas, donde cada una es un nombre de directorio
    String[] tokens = longPath.split(Pattern.quote(File.separator));

    //construye un StringBuilder donde se añadira el resultado
    StringBuilder shortpath = new StringBuilder();

    //itera sobre el arreglo de cadenas
    for (int i = 0 ; i &lt; tokens.length ; i++) {
        if (i == tokens.length - 1) {             //si la cadena actual es la última, es el nombre del archivo
            shortpath.append(tokens[i]);    //se añade al resultado sin separador
            break;                          //se termina el bucle
        } else if (tokens[i].length() &gt;= 10) {    //si la cadena actual tiene 10 o más caracteres
            //se toman los primeros 3 caracteres y se añade al resultado con un separador
            shortpath.append(tokens[i].substring(0, 3)).append(&quot;...&quot;).append(File.separator);
        } else {                                  //si la cadena actual tiene menos de 10 caracteres
            //se añade al resultado con un separador
            shortpath.append(tokens[i]).append(File.separator);
        }
    }

    return shortpath.toString();    //retorna la cadena resultante
}
...
</pre>
<p>El método <span style="color:#000000;"><code>String roundFileSize(long length)</code></span> es utilizado por otros métodos para obtener el tamaño del archivo actual redondeado en KiloBytes cuando este es mayor a 1024 bytes.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private static String roundFileSize(long length) {
    //retorna el tamaño del archivo redondeado
    return (length &lt; 1024) ? length + &quot; bytes&quot; : (length / 1024) + &quot; Kbytes&quot;;
}
...
</pre>
<h3 style="text-align:left;">Archivo PrintAction.java</h3>
<p>El archivo de código fuente <em>PrintAction.java</em> contiene la clase publica <span style="color:#000000;"><code>PrintAction</code></span> que se encarga de imprimir el documento presente en el área de edición. Se implementa la interface <span style="color:#000000;"><code>java.awt.print.Printable</code></span> con la cual se sigue un modelo de impresión secuencial de las paginas renderizadas del documento, comenzando con la pagina 0.</p>
<p>Dentro de <span style="color:#000000;"><code>PrintAction</code></span> tenemos la clase interna <span style="color:#000000;"><code>PrintingMessageBox</code></span> que extiende <span style="color:#000000;"><code>java.swing.JDialog</code></span>, se utiliza para presentarle al usuario una ventana de dialogo modal que muestra la información de la pagina que se esta imprimiendo y una opción para permitir abortar la operación.</p>
<p>Comencemos con el esqueleto de este archivo, se definen las clases, métodos (sin contenido), y variables:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
package textpademo;    //se define el paquete donde debe estar este archivo

import java.awt.BorderLayout;    //importamos todo lo que se utilizará
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;

public class PrintAction implements Printable {    //clase publica que implementa la interface Printable

    private final JTextArea jTextArea;    //área de edición donde se encuentra el documento actual
    private JDialog dialog;               //dialogo de estado, muestra el estado de la impresión
    private int[] pageBreaks;             //arreglo de quiebres de página
    private String[] textLines;           //arreglo de líneas de texto
    private int currentPage = -1;         //página actual impresa, por defecto inicilizada en -1
    private boolean result = false;       //resultado de la impresión, por defecto es negativo

    public PrintAction(JComponent jComponent) {    //constructor de la clase PrintAction
    }

    //método estático conveniente para inicializar la clase PrintAction
    public static boolean print(JComponent jComponent, Frame owner) {
    }

    //presenta el dialogo de impresión e inicia la impresión del documento
    public boolean printDialog(Frame owner) {
    }

    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {    //implemento de la interface Printable
    }

    private void updateStatus(int pageIndex) {    //actualiza el estado de la impresión en el dialogo de estado
    }

    //clase que extiende JDialog, construye un dialogo modal
    private class PrintingMessageBox extends JDialog {

        private JLabel lbStatusMsg;    //etiqueta que muestra el estado de impresión

        public PrintingMessageBox(Frame owner, final PrinterJob pj) {    //constructor de la clase PrintingMessageBox
        }

        public void setStatusMsg(String statusMsg){    //establece el texto de la etiqueta lbStatusMsg
        }
    }
}
</pre>
<p>Al instanciar la clase <span style="color:#000000;"><code>PrintAction</code></span> su constructor recibe como argumento una instancia del componente <span style="color:#000000;"><code>JTextArea</code></span> (área de edición) y la guarda.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public PrintAction(JComponent jComponent) {
    this.jTextArea = (JTextArea) jComponent;    //guarda la instancia del área de edición
}
...
</pre>
<p>El método estático <span style="color:#000000;"><code>boolean print(JComponent jComponent, Frame owner)</code></span> es invocado directamente desde la clase <span style="color:#000000;"><code>ActionPerformer</code></span>. Construye e inicia la clase <span style="color:#000000;"><code>PrintAction</code></span> para realizar la impresión del documento presente en el área de edición. Retorna <span style="color:#000000;"><code>true</code></span> en caso de éxito o <span style="color:#000000;"><code>false</code></span> en caso contrario.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public static boolean print(JComponent jComponent, Frame owner) {
    PrintAction pa = new PrintAction(jComponent);   //construye una instancia de PrintAction
    return pa.printDialog(owner);                   //inicia la impresión y retorna un valor booleano
}
...
</pre>
<p>El método <span style="color:#000000;"><code>boolean printDialog(Frame owner)</code></span> realiza la impresión del documento. Se le presenta al usuario una ventana de dialogo donde configurar las propiedades de impresión, esta luego es iniciada en un hilo externo al EDT (Event Dispatch Thread) y durante su transcurso se presenta otro dialogo mostrando el estado para informar al usuario.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public boolean printDialog(Frame owner) {
    //construye un trabajo de impresión
    final PrinterJob pj = PrinterJob.getPrinterJob();
    //construye un conjunto de atributos para la impresión
    final PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
    //establece a la clase PrintAction como responsable de renderizar las páginas del documento
    pj.setPrintable(this);

    boolean option = pj.printDialog(pras);    //presenta un dialogo de impresión

    if (option == true) {    //si el usuario acepta
        //construye el dialogo modal de estado sobre la ventana padre
        dialog = new PrintingMessageBox(owner, pj);

        //crea un nuevo hilo para que se ocupe de la impresión
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    pj.print();                        //inicia la impresión
                    PrintAction.this.result = true;    //resultado positivo
                } catch (PrinterException ex) {        //en caso de que ocurra una excepción
                    System.err.println(ex);
                }

                dialog.setVisible(false);    //oculta el dialogo de estado
            }
        }).start();    //inicia el hilo de impresión

        dialog.setVisible(true);     //hace visible el dialogo de estado
    }

    return PrintAction.this.result;    //retorna el resultado de la impresión
}
...
</pre>
<p>El método <span style="color:#000000;"><code>int print(Graphics g, PageFormat pf, int pageIndex)</code></span> implementado de la interface <span style="color:#000000;"><code>Printable</code></span> es invocado secuencialmente por el sistema de impresión para solicitar cada pagina a imprimir. La primera ves que este método es invocado se calculan los quiebres de pagina (<span style="color:#000000;"><code>pageBreaks</code></span>) necesarios para imprimir todo el documento, cada uno de estos corresponde a cada pagina que será solicitada en la cual cabe una determinada cantidad de líneas.</p>
<p>Esta implementación trata el contenido del área de edición como lo que es, simple texto plano, por lo tanto se ignora el fuente o los colores.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public int print(Graphics g, PageFormat pf, int pageIndex) {
    Graphics2D g2d = (Graphics2D) g;                      //conversión de gráficos simples a gráficos 2D
    g2d.setFont(new Font(&quot;Serif&quot;, Font.PLAIN, 10));       //establece un fuente para todo el texto
    int lineHeight = g2d.getFontMetrics().getHeight();    //obtiene la altura del fuente

    if (pageBreaks == null) {    //si los quiebres de página no fueron calculados
        //construye un arreglo con las líneas de texto presentes en el área de edición
        textLines = jTextArea.getText().split(&quot;\n&quot;);
        //calcula el número de líneas que caben en cada página
        int linesPerPage = (int) (pf.getImageableHeight() / lineHeight);
        //calcula el número de quiebres de página necesarios para imprimir todo el documento
        int numBreaks = (textLines.length - 1) / linesPerPage;
        //construye un arreglo con los quiebres de página 
        pageBreaks = new int[numBreaks];
        for (int i = 0 ; i &lt; numBreaks ; i++) {
            //se calcula la posición para cada quiebre de página
            pageBreaks[i] = (i + 1) * linesPerPage;
        }
    }

    //si el índice de página solicitado es menor o igual que la cantidad de quiebres total
    if (pageIndex &lt;= pageBreaks.length) {
        /* establece una igualdad entre el origen del espacio gráfico (x:0,y:0) y el origen 
        del área imprimible definido por el formato de página */
        g2d.translate(pf.getImageableX(), pf.getImageableY());

        int y = 0;    //coordenada &quot;y&quot;, inicializada en 0 (principio de página)
        //obtiene la primera línea para la página actual
        int startLine = (pageIndex == 0) ? 0 : pageBreaks[pageIndex - 1];
        //obtiene la última línea para la página actual
        int endLine = (pageIndex == pageBreaks.length) ? textLines.length : pageBreaks[pageIndex];

        //itera sobre las líneas que forman parte de la página actual
        for (int line = startLine ; line &lt; endLine ; line++) {
            y += lineHeight;                          //aumenta la coordenada &quot;y&quot; para cada línea
            g2d.drawString(textLines[line], 0, y);    //imprime la linea en las coordenadas actuales
        }

        updateStatus(pageIndex);    //actualiza el estado de impresión

        return PAGE_EXISTS;     //la página solicitada será impresa
    } else {
        return NO_SUCH_PAGE;    //la pagina solicitada no es valida
    }
}
...
</pre>
<p>El método <span style="color:#000000;"><code>void updateStatus(int pageIndex)</code></span> es invocado por <span style="color:#000000;"><code>int print(Graphics g, PageFormat pf, int pageIndex)</code></span> en el transcurso de la impresión para actualizar el estado de la impresión en el dialogo de estado, donde se muestra la página que se esta imprimiendo actualmente.</p>
<p>Es normal que el método <span style="color:#000000;"><code>int print(Graphics g, PageFormat pf, int pageIndex)</code></span> sea invocado por el sistema de impresión más de una ves para una misma página, lo que significa que la actualización del estado se podría ejecutar innecesariamente. Por ese motivo antes de actualizar se comprueba que la página actual sea diferente al índice de la página impresa, de lo contrario no hay nada que actualizar.</p>
<p>Conforme a la metodología de Swing, el acceso desde un hilo externo a la GUI del dialogo de estado el cual se encuentra en el EDT (Event Dispatch Thread), se realiza de forma segura a través del método <span style="color:#000000;"><code>void invokeLater(Runnable doRun)</code></span>.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
private void updateStatus(int pageIndex) {
    if (pageIndex != currentPage) {
        currentPage++;    //incrementa la página actual    

        //acceso seguro al EDT (Event Dispatch Thread) para actualizar la GUI
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                //actualiza la información de la etiqueta lbStatusMsg
                ((PrintingMessageBox) dialog).setStatusMsg(&quot;Imprimiendo página &quot; + (currentPage + 1) + &quot; ...&quot;);
            }
        });
    }
}
...
</pre>
<p>Hasta aquí hemos visto los métodos de la clase <span style="color:#000000;"><code>PrintAction</code></span>, ahora continuamos con los métodos de la clase interna <span style="color:#000000;"><code>PrintingMessageBox</code></span>. Esta extiende <span style="color:#000000;"><code>javax.swing.JDialog</code></span> para presentarle al usuario una sencilla ventana modal de dialogo donde se muestra el estado actual de la impresión y también un botón que permite cancelar la impresión en curso.</p>
<p>En el constructor se realiza todo lo necesario para crear esta ventana.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public PrintingMessageBox(Frame owner, final PrinterJob pj) {
    /** invoca el constructor de la superclase para establecer la ventana padre, el título
    de la ventana, y que será una ventana modal */
    super(owner, &quot;Impresión&quot;, true);
    setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

    //construye y configura la etiqueta que muestra el estado
    lbStatusMsg = new JLabel(&quot;Iniciando ...&quot;);
    lbStatusMsg.setPreferredSize(new Dimension(200, 30));
    lbStatusMsg.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    //construye el botón de cancelar
    JButton buttonCancel = new JButton(&quot;Cancelar&quot;);
    JPanel jp = new JPanel();
    jp.add(buttonCancel);

    //asigna un manejador de eventos para el botón de cancelar
    buttonCancel.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            pj.cancel();          //cancela el trabajo de impresión
            setVisible(false);    //oculta esta ventana
        }
    });

    getContentPane().add(lbStatusMsg, BorderLayout.CENTER);    //añade la etiqueta en el CENTRO
    getContentPane().add(jp, BorderLayout.SOUTH);              //añade el botón, orientación SUR

    //asigna un manejador de eventos para cuando la ventana pierde la visibilidad
    this.addComponentListener(new ComponentAdapter() {

        @Override
        public void componentHidden(ComponentEvent e) {
            Window w = (Window) e.getComponent();    //convierte el componente afectado en una ventana
            w.dispose();                             //destruye la ventana
        }
    });

    setResizable(false);             //no se permite redimensionar la ventana
    pack();                          //se le da el tamaño preferido
    setLocationRelativeTo(owner);    //centra la ventana sobre el editor de texto
}
...
</pre>
<p>Además del constructor, el otro método presente en esta clase interna es <span style="color:#000000;"><code>void setStatusMsg(String statusMsg)</code></span>. Este es invocado desde la clase exterior <span style="color:#000000;"><code>PrintAction</code></span> para actualizar el estado de impresión.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public void setStatusMsg(String statusMsg) {
    lbStatusMsg.setText(statusMsg);    //establece el texto de la etiqueta lbStatusMsg
}
...
</pre>
<h3 style="text-align:left;">Archivo JFontChooser.java</h3>
<p>Como la API estándar de Java no ofrece algún componente ya hecho para que el usuario pueda elegir un fuente, similar a como <span style="color:#000000;"><code>javax.swing.JColorChooser</code></span> permite elegir un color, tenemos aquí una sencilla implementación de <span style="color:#000000;"><code>JComponent</code></span> y <span style="color:#000000;"><code>JDialog</code></span> para crear dicho componente.</p>
<p>El archivo de código fuente <em>JFontChooser.java</em> contiene la clase publica <span style="color:#000000;"><code>JFontChooser</code></span> que se encarga de presentarle al usuario una ventana de dialogo donde elegir el fuente para el área de texto. Se extiende la clase <span style="color:#000000;"><code>javax.swing.JComponent</code></span> de la cual deben de extender todos los componentes de Swing que no sean contenedores.</p>
<p>La clase <span style="color:#000000;"><code>FontChooserDialog</code></span> que extiende <span style="color:#000000;"><code>javax.swing.JDialog</code></span> es invocada por la clase <span style="color:#000000;"><code>JFontChooser</code></span> para construir una ventana de dialogo modal, la mayoría de los eventos en esta ventana son manejados por la clase interna <span style="color:#000000;"><code>EventHandler</code></span> (similar a como realizamos anteriormente con la ventana principal del editor).</p>
<p>El aspecto de este componente:</p>
<p style="text-align:center;"><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg"><img loading="lazy" data-attachment-id="2379" data-permalink="https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/java-tpeditor8/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg" data-orig-size="299,312" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;JosEdu&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1311182973&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="java-tpeditor8" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg?w=288" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg?w=299" class="size-full wp-image-2379" title="java-tpeditor8" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg?w=239&amp;h=249 239w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg?w=144&amp;h=150 144w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg?w=288&amp;h=300 288w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg 299w" sizes="(max-width: 239px) 100vw, 239px" /></a></p>
<p>Comencemos con el esqueleto de este archivo, se definen las clases, métodos (sin contenido), y variables:</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
package textpademo;    //se define el paquete donde debe estar este archivo

import java.awt.Color;    //importamos todo lo que se utilizará
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Arrays;
import java.util.Comparator;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.Position;

public class JFontChooser extends JComponent {    //clase publica que extiende JComponent

    private final Font initialFont;    //fuente inicial
    private Font font;                 //fuente seleccionado por el usuario

    public JFontChooser(Font initialFont) {    //constructor de la clase JFontChooser
    }

    //método estático conveniente para inicializar la clase JFontChooser
    public static Font showDialog(Frame owner, String title, Font initialFont) {
    }

    public Font getInitialFont() {    //retorna el fuente inicial
    }

    public Font getSelectedFont() {    //retorna el fuente seleccionado
    }

    public void setSelectedFont(Font font) {    //establece el fuente seleccionado
    }
}

class FontChooserDialog extends JDialog {    //clase que extiende JDialog

    private JTextField textFieldNames;     //campo de texto para el nombre del fuente
    private JTextField textFieldStyles;    //campo de texto para el estilo del fuente
    private JTextField textFieldSizes;     //campo de texto para el tamaño del fuente
    private JList listFontNames;     //lista para nombres de fuente
    private JList listFontStyles;    //lista para estilos de fuente
    private JList listFontSizes;     //lista para tamaños de fuente
    private JLabel textExample;    //etiqueta que muestra un ejemplo del fuente seleccionado

    //arreglo con nombres de fuente disponibles
    private static final String[] FONT_NAMES = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
    //arreglo con estilos de fuente
    private static final String[] FONT_STYLES = {
        &quot;Normal&quot;, &quot;Bold&quot;, &quot;Italic&quot;, &quot;Bold Italic&quot;
    };
    //arreglo con tamaños de fuente
    private static final String[] FONT_SIZES = {
        &quot;8&quot;, &quot;9&quot;, &quot;10&quot;, &quot;11&quot;, &quot;12&quot;, &quot;13&quot;, &quot;14&quot;, &quot;16&quot;, &quot;18&quot;, &quot;20&quot;, &quot;24&quot;, &quot;28&quot;, &quot;32&quot;, &quot;48&quot;, &quot;72&quot;
    };

    //constructor de la clase FontChooserDialog, construye un dialogo modal
    public FontChooserDialog(Frame owner, String title, final JFontChooser jFontChooser) {
    }

    //clase interna que extiende KeyAdapter e implementa ListSelectionListener y Comparator
    class EventHandler extends KeyAdapter implements Comparator,
                                                     ListSelectionListener {
        @Override
        public void keyReleased(KeyEvent ke) {    //herencia de la clase KeyAdapter
        }

        @Override
        public int compare(String string1, String string2) {    //implemento de la interface Comparator
        }

        @Override
        public void valueChanged(ListSelectionEvent lse) {    //implemento de la interface ListSelectionListener
        }
    }

    public Font getSelectedFont() {    //retorna el fuente seleccionado
    }
}
</pre>
<p>Al instanciar la clase <span style="color:#000000;"><code>JFontChooser</code></span> su constructor recibe como argumento el fuente inicial y lo guarda. Este será el primer fuente seleccionado al hacerse visible el dialogo.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public JFontChooser(Font initialFont) {
    this.initialFont = initialFont;    //guarda el fuente inicial
}
...
</pre>
<p>El método estático <span style="color:#000000;"><code>Font showDialog(Frame owner, String title, Font initialFont)</code></span> es invocado directamente desde la clase <span style="color:#000000;"><code>ActionPerformer</code></span>. Construye e inicia la clase <span style="color:#000000;"><code>JFontChooser</code></span> para presentar al usuario el dialogo de selección de fuente. Retorna el fuente seleccionado.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public static Font showDialog(Frame owner, String title, Font initialFont) {
    JFontChooser fontChooser = new JFontChooser(initialFont);    //construye una instancia de JFontChooser

    //construye el dialogo de selección de fuente sobre la ventana padre
    JDialog dialog = new FontChooserDialog(owner, title, fontChooser);
    dialog.setVisible(true);    //hace visible el dialogo

    return fontChooser.getSelectedFont();    //retorna el fuente seleccionado
}
...
</pre>
<p>Los datos de la clase <span style="color:#000000;"><code>JFontChooser</code></span> son accesibles desde el exterior. Aquí están los correspondientes getters y setters.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public Font getInitialFont() {    //retorna el fuente inicial
    return initialFont;
}

public Font getSelectedFont() {    //retorna el fuente seleccionado
    return font;
}

public void setSelectedFont(Font font) {    //establece el fuente seleccionado
    this.font = font;
}
...
</pre>
<p>Hasta aquí hemos visto los métodos de la clase <span style="color:#000000;"><code>JFontChooser</code></span>, ahora continuamos con los métodos de la clase <span style="color:#000000;"><code>FontChooserDialog</code></span>. Esta extiende <span style="color:#000000;"><code>JDialog</code></span> para presentarle al usuario una ventana modal de dialogo donde elegir un fuente.</p>
<p>En el constructor se realiza todo lo necesario para crear esta ventana. La mayoría de los eventos se dejan a cargo de la clase interna <span style="color:#000000;"><code>EventHandler</code></span>.</p>
<p><strong>Nota:</strong> no confundir esta clase interna <span style="color:#000000;"><code>EventHandler</code></span> con la que está dentro de la clase <span style="color:#000000;"><code>TPEditor</code></span>. Las eh nombrado igual pero pertenecen a clases diferentes.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public FontChooserDialog(Frame owner, String title, final JFontChooser jFontChooser) {
    /** invoca el constructor de la superclase para establecer la ventana padre, el título
    de la ventana, y que será una ventana modal */
    super(owner, title, true);

    final EventHandler eventHandler = new EventHandler();    //construye una instancia de EventHandler
    JScrollPane jScrollPane;

    JPanel cp = (JPanel) getContentPane();                        //obtiene el panel de contenido principal
    cp.setLayout(new GridBagLayout());                            //establece un GridBagLayout
    cp.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));    //establece un borde de espacio

    //construye un conjunto de limitaciones para los componentes del GridBagLayout
    GridBagConstraints gbc = new GridBagConstraints();

    JLabel label1 = new JLabel(&quot;Name:&quot;);    //construye la etiqueta &quot;Name:&quot;
    gbc.insets = new Insets(0, 5, 0, 5);
    gbc.anchor = GridBagConstraints.FIRST_LINE_START;
    gbc.weightx = 0.5;
    gbc.gridx = 1;
    gbc.gridy = 1;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(label1, gbc);    //añade la etiqueta, coordenadas X:1 - Y:1

    JLabel label2 = new JLabel(&quot;Style:&quot;);    //construye la etiqueta &quot;Style:&quot;
    gbc.gridx = 2;
    gbc.gridy = 1;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(label2, gbc);    //añade la etiqueta, coordenadas X:2 - Y:1

    JLabel label3 = new JLabel(&quot;Size:&quot;);    //construye la etiqueta &quot;Size:&quot;
    gbc.gridx = 3;
    gbc.gridy = 1;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(label3, gbc);    //añade la etiqueta, coordenadas X:3 - Y:1

    textFieldNames = new JTextField(&quot;&quot;);    //construye el campo de texto para el nombre del fuente
    int fixedWidth = textFieldNames.getPreferredSize().width;
    Dimension fixedSize = new Dimension(fixedWidth, 20);
    textFieldNames.setMinimumSize(fixedSize);
    textFieldNames.setMaximumSize(fixedSize);
    textFieldNames.setPreferredSize(fixedSize);
    textFieldNames.addKeyListener(eventHandler);    //asigna el manejador de eventos para el teclado
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.gridx = 1;
    gbc.gridy = 2;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(textFieldNames, gbc);    //añade el campo de texto, coordenadas X:1 - Y:2

    textFieldStyles = new JTextField(&quot;&quot;);    //construye el campo de texto para el estilo del fuente
    fixedWidth = textFieldStyles.getPreferredSize().width;
    fixedSize = new Dimension(fixedWidth, 20);
    textFieldStyles.setMinimumSize(fixedSize);
    textFieldStyles.setMaximumSize(fixedSize);
    textFieldStyles.setPreferredSize(fixedSize);
    textFieldStyles.addKeyListener(eventHandler);    //asigna el manejador de eventos para el teclado
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.gridx = 2;
    gbc.gridy = 2;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(textFieldStyles, gbc);    //añade el campo de texto, coordenadas X:2 - Y:2

    textFieldSizes = new JTextField(&quot;&quot;);    //construye el campo de texto para el tamaño del fuente
    fixedWidth = textFieldSizes.getPreferredSize().width;
    fixedSize = new Dimension(fixedWidth, 20);
    textFieldSizes.setMinimumSize(fixedSize);
    textFieldSizes.setMaximumSize(fixedSize);
    textFieldSizes.setPreferredSize(fixedSize);
    textFieldSizes.addKeyListener(eventHandler);    //asigna el manejador de eventos para el teclado
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.gridx = 3;
    gbc.gridy = 2;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(textFieldSizes, gbc);    //añade el campo de texto, coordenadas X:3 - Y:2

    listFontNames = new JList(FONT_NAMES);    //construye la lista para nombres de fuente
    jScrollPane = new JScrollPane(listFontNames);
    String fontName = jFontChooser.getInitialFont().getName();
    listFontNames.setSelectedValue(fontName, true);    //selecciona el nombre de fuente inicial
    textFieldNames.setText(fontName);
    listFontNames.addListSelectionListener(eventHandler);
    gbc.gridx = 1;
    gbc.gridy = 3;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(jScrollPane, gbc);    //añade la lista, coordenadas X:1 - Y:3

    listFontStyles = new JList(FONT_STYLES);    //construye la lista para estilos de fuente
    jScrollPane = new JScrollPane(listFontStyles);
    int fontSyle = jFontChooser.getInitialFont().getStyle();
    listFontStyles.setSelectedIndex(fontSyle);    //selecciona el estilo de fuente inicial
    textFieldStyles.setText(listFontStyles.getSelectedValue().toString());
    listFontStyles.addListSelectionListener(eventHandler);
    gbc.gridx = 2;
    gbc.gridy = 3;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(jScrollPane, gbc);    //añade la lista, coordenadas X:2 - Y:3

    listFontSizes = new JList(FONT_SIZES);    //construye la lista para tamaños de fuente
    jScrollPane = new JScrollPane(listFontSizes);
    String fontSize = String.valueOf(jFontChooser.getInitialFont().getSize());
    listFontSizes.setSelectedValue(fontSize, true);    //selecciona el tamaño de fuente inicial
    textFieldSizes.setText(fontSize);
    listFontSizes.addListSelectionListener(eventHandler);
    gbc.gridx = 3;
    gbc.gridy = 3;
    gbc.gridwidth = 1;
    gbc.gridheight = 1;
    cp.add(jScrollPane, gbc);    //añade la lista, coordenadas X:3 - Y:3

    textExample = new JLabel(&quot;AaBaCcDdEeFfGgHhJj&quot;);        //contruye la etiqueta para mostrar un ejemplo del fuente seleccionado
    textExample.setHorizontalAlignment(JLabel.CENTER);
    textExample.setFont(jFontChooser.getInitialFont());    //establece el fuente inicial
    textExample.setOpaque(true);
    textExample.setBackground(Color.WHITE);
    textExample.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(&quot;Texto de ejemplo&quot;),
                                                             BorderFactory.createEmptyBorder(10, 10, 10, 10)));
    int fixedHeight = textExample.getPreferredSize().height;
    fixedSize = new Dimension(100, fixedHeight);
    textExample.setMinimumSize(fixedSize);
    textExample.setMaximumSize(fixedSize);
    textExample.setPreferredSize(fixedSize);
    gbc.gridx = 1;
    gbc.gridy = 4;
    gbc.gridwidth = 3;
    gbc.gridheight = 1;
    cp.add(textExample, gbc);    //añade la etiqueta, coordenadas X:1 - Y:4

    JPanel jp = new JPanel();                        //construye un panel para los botones
    JButton buttonOk = new JButton(&quot;Ok&quot;);            //construye el botón de aceptar
    JButton buttonCancel = new JButton(&quot;Cancel&quot;);    //construye el botón de cancelar
    jp.add(buttonOk);                                //añade los botones al panel
    jp.add(buttonCancel);
    gbc.anchor = GridBagConstraints.CENTER;
    gbc.gridx = 1;
    gbc.gridy = 5;
    cp.add(jp, gbc);    //añade el panel, coordenadas X:1 - Y:5

    //asigna un manejador de eventos para el botón de cancelar
    buttonOk.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            jFontChooser.setSelectedFont(getSelectedFont());
            //fontChooser.font = getSelectedFont();
            setVisible(false);
        }
    });

    //asigna un manejador de eventos para el botón de cancelar
    buttonCancel.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            setVisible(false);    //oculta esta ventana
        }
    });

    //asigna un manejador de eventos para el cierre del JDialog
    this.addWindowListener(new WindowAdapter() {

        @Override
        public void windowClosing(WindowEvent e) {
            Window w = e.getWindow();    //convierte el componente afectado en una ventana
            w.setVisible(false);         //oculta esta ventana
        }
    });

    //asigna un manejador de eventos para cuando la ventana pierde la visibilidad
    this.addComponentListener(new ComponentAdapter() {

        @Override
        public void componentHidden(ComponentEvent e) {
            Window w = (Window) e.getComponent();    //convierte el componente afectado en una ventana
            w.dispose();                             //destruye la ventana
        }
    });

    setResizable(false);              //no se permite redimensionar la ventana
    pack();                           //se le da el tamaño preferido
    setLocationRelativeTo(owner);     //la ventana se centra sobre el editor de texto
}
...
</pre>
<p>El método <span style="color:#000000;"><code>Font getSelectedFont()</code></span> retorna un fuente como resultado de la combinación de un tipo, estilo y tamaño de fuente seleccionados en las listas del dialogo.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
public Font getSelectedFont() {
    try {
        //retorna el fuente seleccionado en el dialogo
        return new Font(String.valueOf(listFontNames.getSelectedValue()), listFontStyles.getSelectedIndex(),
                        Integer.parseInt(String.valueOf(listFontSizes.getSelectedValue())));
    } catch (NumberFormatException nfe) {    //en caso de que ocurra una excepción
        System.err.println(nfe);
    }

    return null;    //retorna null
}
...
</pre>
<p>Hasta aquí hemos visto los métodos de la clase <span style="color:#000000;"><code>FontChooserDialog</code></span>, ahora continuamos con los métodos de la clase interna <span style="color:#000000;"><code>EventHandler</code></span>. En esta se sobrescriben e implementan métodos necesarios para detectar algunos eventos en la GUI del dialogo y poder actuar en consecuencia.</p>
<p>Al extender la clase <span style="color:#000000;"><code>java.awt.event.KeyAdapter</code></span> se sobrescribe el método <span style="color:#000000;"><code>void keyReleased(KeyEvent ke)</code></span>, donde se detecta cuando el usuario libera una tecla sobre los campos de texto. Esto sirve para poder buscar en la lista coincidencias con lo que el usuario escribe, y entonces así ir desplazando la barra vertical manteniendo visible el texto en cuestión.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
@Override
public void keyReleased(KeyEvent ke) {
    //obtiene el origen del evento, y lo convierte en un campo de texto
    final JTextField eventTField = (JTextField) ke.getSource();
    final String text = eventTField.getText();    //obtiene el contenido del campo de texto

    //averigua en que campo de texto se ah ejecutado el evento
    if (eventTField == textFieldNames) {    //si el campo de texto es textFieldNames
        //obtiene el índice del proximo elemento en la lista que coincida con el contenido del campo de texto
        int index = listFontNames.getNextMatch(text, 0, Position.Bias.Forward);

        if (index &gt; -1) {    //si el índice es mayor que -1
            /* realiza una búsqueda binaria sobre el arreglo de nombres de fuente, se utiliza
            esta clase como comparador implementando la interface Comparator */
            if (Arrays.binarySearch(FONT_NAMES, text, this) &gt; -1) {    //si el resultado es mayor que -1
                listFontNames.setSelectedIndex(index);    //selecciona el índice
            }

            listFontNames.ensureIndexIsVisible(index);    //hace el índice visible en la lista
        }
    } else if (eventTField == textFieldStyles) {        //si el campo de texto es textFieldStyles
        //obtiene el índice del proximo elemento en la lista que coincida con el contenido del campo de texto
        int index = listFontStyles.getNextMatch(text, 0, Position.Bias.Forward);

        if (index &gt; -1) {    //si el índice es mayor que -1
            //itera sobre los elementos en el arreglo de estilos de fuente
            for (int i = 0 ; i &lt; FONT_STYLES.length ; i++){
                //si el contenido del campo de texto es igual al elemento actual
                if (text.equalsIgnoreCase(FONT_STYLES[i]) == true) {
                    listFontStyles.setSelectedIndex(index);    //selecciona el índice
                }
            }

            listFontStyles.ensureIndexIsVisible(index);    //hace el índice visible en la lista
        }
    } else if (eventTField == textFieldSizes) {    //si el campo de texto es textFieldSizes
        //obtiene el índice del proximo elemento en la lista que coincida con el contenido del campo de texto
        int index = listFontSizes.getNextMatch(text, 0, Position.Bias.Forward);

        if (index &gt; -1) {    //si el índice es mayor que -1
            //itera sobre los elementos en el arreglo de tamaños de fuente
            for (int i = 0 ; i &lt; FONT_SIZES.length ; i++) {
                //si el contenido del campo de texto es igual al elemento actual
                if (text.equalsIgnoreCase(FONT_SIZES[i]) == true) {
                    listFontSizes.setSelectedIndex(index);    //selecciona el índice
                }
            }

            listFontSizes.ensureIndexIsVisible(index);    //hace el índice visible en la lista
        }
    }

    //establece el fuente seleccionado en la etiqueta de muestra
    textExample.setFont(getSelectedFont());
}
...
</pre>
<p>Al implementar la interface <span style="color:#000000;"><code>java.util.Comparator</code></span> se implementa el método <span style="color:#000000;"><code>int compare(Object string1, Object string2)</code></span>, con el cual se realiza una comparación lexicográfica insensible de mayúsculas sobre dos cadenas de texto. Con esta implementación hemos convertido la clase <span style="color:#000000;"><code>EventHandler</code></span> en un comparador que es utilizado para la búsqueda binaria realizada en el método <span style="color:#000000;"><code>void keyReleased(KeyEvent ke)</code></span> visto anteriormente.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
@Override
public int compare(String string1, String string2) {
    //compara dos cadenas de texto ignorando mayúsculas
    return string1.compareToIgnoreCase(string2);
}
...
</pre>
<p>Al implementar la interface <span style="color:#000000;"><code>javax.swing.event.ListSelectionListener</code></span> se implementa el método <span style="color:#000000;"><code>void valueChanged(ListSelectionEvent lse)</code></span>, en el cual se detecta cuando cambia el elemento seleccionado en alguna de las listas del dialogo. Cuando esto sucede se actualizan el contenido del campo de texto relacionado con el elemento seleccionado y la etiqueta de muestra con el fuente seleccionado.</p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
@Override
public void valueChanged(ListSelectionEvent lse) {
    //averigua en que lista se ah ejecutado el evento
    if (lse.getSource() == listFontNames) {    //si la lista es listFontNames
        //establece el contenido del campo de texto textFieldNames
        textFieldNames.setText(String.valueOf(listFontNames.getSelectedValue()));
    } else if (lse.getSource() == listFontStyles) {    //si la lista es listFontStyles
        //establece el contenido del campo de texto textFieldStyles
        textFieldStyles.setText(String.valueOf(listFontStyles.getSelectedValue()));
    } else if (lse.getSource() == listFontSizes) {    //si la lista es listFontSizes
        //establece el contenido del campo de texto textFieldSizes
        textFieldSizes.setText(String.valueOf(listFontSizes.getSelectedValue()));
    }

    //establece el fuente seleccionado en la etiqueta de muestra
    textExample.setFont(getSelectedFont());
}
...
</pre>
<p>Aquí finaliza el código del proyecto.</p>
<p>Estas son las imágenes .<em>PNG</em> 32&#215;32 que se utilizan como recursos en el proyecto:<br />
<img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_new.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_open.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_save.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_saveas.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_print.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_undo.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_redo.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_cut.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_copy.png?w=32&#038;h=32" alt="" width="32" height="32" /><img src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_paste.png?w=32&#038;h=32" alt="" width="32" height="32" /></p>
<h2 style="text-align:left;">Mejorar el editor</h2>
<hr />
<p>Algunas cosas que se podrían tener en cuenta para mejorar o incluir en el editor:</p>
<ul>
<li>El editor actualmente puede manejar un solo documento porque el área de edición es directamente un <span style="color:#000000;"><code>JTextArea</code></span>. Normalmente las buenas aplicaciones de escritorio son de tipo MDI (multiple-document interface), las cuales pueden manejar la edición de varios documentos simultáneamente. El área de edición se puede representar con un panel contenedor <span style="color:#000000;"><code>JDesktopPane</code></span> y según lo requerido crear en su interior ventanas internas <span style="color:#000000;"><code>JInternalFrame</code></span> cada una con su propio <span style="color:#000000;"><code>JTextArea</code></span>. De esta forma se podría tener un editor MDI.</li>
<li>El <span style="color:#000000;"><code>JTextArea</code></span> es un componente especifico para texto plano. Si se pretende un editor que soporte formatos compuestos como .<em>HTML</em> o .<em>RFT</em> se puede utilizar un componente de texto mas avanzado como <span style="color:#000000;"><code>JEditorPane</code></span> o un <span style="color:#000000;"><code>JTextPane</code></span> (que extiende el anterior). Con la ayuda de librerías adicionales se pueden manipular también otros formatos, por ejemplo .<em>PDF</em> con <strong>iTEXT</strong>.</li>
<li>En caso de tratar con formatos compuestos la impresión del documento debería de respetar diferentes fuentes, colores, imágenes o lo que fuese. Por ejemplo se puede calcular cuantas páginas se necesitan para representar todo el contenido de un componente <span style="color:#000000;"><code>JTextPane</code></span>, y utilizar el método <span style="color:#000000;"><code>void paint(Graphics g)</code></span> (que ofrecen los componentes Swing) para ir dibujando el contenido en un objeto de gráficos para cada página que será impresa. Desde <strong>J2SE 6</strong> existen convenientemente un grupo de métodos sobrecargados <span style="color:#000000;"><code>boolean print(…)</code></span> disponibles en los componentes de texto que extienden de <span style="color:#000000;"><code>JTextComponent</code></span>, estos hacen mas sencillo el trabajo.</li>
<li>Utilizar hilos obreros adicionales para cargar o guardar archivos, principalmente si se piensa trabajar con archivos grandes. Desde<strong> J2SE 6</strong> es una buena opción utilizar la clase <span style="color:#000000;"><code>javax.swing.SwingWorker</code></span> para crear estos hilos.</li>
<li>El administrador de edición (Deshacer\Rehacer) debería de guardar modificaciones continuas en un solo registro y no carácter por carácter, por ejemplo si el usuario mantiene presionada la tecla «A» cada nueva letra <em>A</em> se registra individualmente, esto es poco eficiente y deja sin espacio el buffer fácilmente.</li>
<li>El sistema de búsqueda de texto podría ofrecer más opciones al usuario como ignorar mayúsculas, buscar solo palabras completas, buscar hacia atrás en el texto, utilizar expresiones regulares, etc.</li>
<li>Junto al sistema de búsqueda se puede incluir la opción de reemplazar texto.</li>
<li>Permitir al usuario elegir la codificación de caracteres.</li>
<li>Colocar una columna en un lado del área de edición mostrando el número de cada línea.</li>
<li>Internacionalización. Para que la GUI del programa este adecuada al idioma local del sistema.</li>
<li>Permitir seleccionar otros LookAndFeel. Los que Swing ofrece, o utilizar alguna librería externa para dar una gran variedad.</li>
</ul>
<hr />
<p><strong>Código fuente:</strong><br />
* <a title="Java – TextPad Demo" href="https://darkbyteblog.wordpress.com/2011/07/23/java-textpad-demo/" target="_blank">TextPad Demo</a></p>
<p><strong>Documentación:</strong><br />
* <a href="http://download.oracle.com/javase/6/docs/api/index.html" target="_blank">Java<img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2122.png" alt="™" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Platform, Standard Edition 6 API Specification</a></p>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/07/05/java-crear-un-editor-de-texto/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1814</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor01.jpg" medium="image">
			<media:title type="html">java-tpeditor0</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor11.jpg" medium="image">
			<media:title type="html">java-tpeditor1</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor21.jpg" medium="image">
			<media:title type="html">java-tpeditor2</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor31.jpg" medium="image">
			<media:title type="html">java-tpeditor3</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor52.jpg" medium="image">
			<media:title type="html">java-tpeditor5</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor62.jpg" medium="image">
			<media:title type="html">java-tpeditor6</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor7.jpg" medium="image">
			<media:title type="html">java-tpeditor7</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/java-tpeditor4.jpg" medium="image">
			<media:title type="html">java-tpeditor4</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/java-tpeditor8.jpg" medium="image">
			<media:title type="html">java-tpeditor8</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_new.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_open.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_save.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_saveas.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_print.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_undo.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_redo.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_cut.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_copy.png" medium="image" />

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/07/tp_paste.png" medium="image" />
	</item>
		<item>
		<title>Java &#8211; ROT13</title>
		<link>https://darkbyteblog.wordpress.com/2011/06/26/java-rot13/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/06/26/java-rot13/#respond</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Sun, 26 Jun 2011 22:23:14 +0000</pubDate>
				<category><![CDATA[Informática]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rot13]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2245</guid>

					<description><![CDATA[Hola Mundo Ubyn Zhaqb Hola Mundo]]></description>
										<content:encoded><![CDATA[<div style="overflow:auto;border:1px solid #585858;background-color:black;color:white;">Hola Mundo<br />
Ubyn Zhaqb<br />
Hola Mundo</div>
<p><span id="more-2245"></span></p>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
/**
 * Rot13.java
 *
 * Ejemplo de la implementación del cifrado ROT13 en Java.
 *
 * Compilar:
 *     javac Rot13.java
 */

/**
 *
 * @author Dark[byte]
 */
class Rot13 {

    static String cadena = &quot;Hola Mundo&quot;;    //cadena de caracteres

    /**
     * Punto de entrada del programa.
     *
     * @param args argumentos de la línea de comandos.
     */
    public static void main(String[] args) {
        System.out.println(cadena);     //imprime la cadena en pantalla

        String str1 = rot13(cadena);    //cifra la cadena y se guarda en str1
        System.out.println(str1);       //imprime str1 en pantalla
 
        String str2 = rot13(str1);      //descifra str1 y se guarda en str2
        System.out.println(str2);       //imprime str2 en pantalla 
    }

    /**
     * Cifra o Descifra una cadena de caracteres en ROT13.
     * 
     * @param cadena la cadena de caracteres.
     * 
     * @return una cadena de caracteres cifrada o descifrada.
     */
    static String rot13(String cadena) {
        char c;
        StringBuilder temp = new StringBuilder();        //crea un StringBuilder para construir la cadena resultante en la variable temp

        for (int i = 0 ; i &lt; cadena.length() ; i++) {    //comienza el bucle para analizar los caracteres de la cadena
            c = cadena.charAt(i);                        //obtiene el carácter de la posición actual de la cadena y se guarda en la variable c

            if (c &gt;= 'A' &amp;&amp; c &lt; 'N') {            //compara c con A y N
                c += 13;                          //si es igual o mayor que A y menor que N, se realiza un desplazamiento sumándole 13
            } else if (c &gt;= 'N' &amp;&amp; c &lt;= 'Z') {    //compara c con N y Z
                c -= 13;                          //si es igual o mayor que N e igual o menor que Z, se realiza un desplazamiento restándole 13
            } else if (c &gt;= 'a' &amp;&amp; c &lt; 'n') {     //compara c con a y n
                c += 13;                          //si es igual o mayor que a y menor que n, se realiza un desplazamiento sumándole 13
            } else if (c &gt;= 'n' &amp;&amp; c &lt;= 'z') {    //compara c con n y z
                c -= 13;                          //si es igual o mayor que n e igual o menor que z, se realiza un desplazamiento restándole 13
            }

            temp.append(c);    //añade c en temp
        }

        return temp.toString();    //retornamos la variable temp, la cual contiene la cadena cifrada o descifrada
    }
}
</pre>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/06/26/java-rot13/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2245</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>
	</item>
		<item>
		<title>C &#8211; ROT13</title>
		<link>https://darkbyteblog.wordpress.com/2011/06/26/c-rot13/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/06/26/c-rot13/#respond</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Sun, 26 Jun 2011 22:22:49 +0000</pubDate>
				<category><![CDATA[C]]></category>
		<category><![CDATA[Informática]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[c]]></category>
		<category><![CDATA[rot13]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2240</guid>

					<description><![CDATA[Hola Mundo Ubyn Zhaqb Hola Mundo]]></description>
										<content:encoded><![CDATA[<div style="overflow:auto;border:1px solid #585858;background-color:black;color:white;">Hola Mundo<br />
Ubyn Zhaqb<br />
Hola Mundo</div>
<p><span id="more-2240"></span></p>
<pre class="brush: objc; title: ; wrap-lines: false; notranslate">
/**
 * \brief Ejemplo de la implementación del cifrado ROT13 en C.
 *
 * Compilar:
 *     gcc rot13.c -o rot13
 *
 * \file rot13.c
 *
 * \author Dark[byte]
 */

#include &lt;stdio.h&gt;
void rot13(char*);        //declaración de la función rot13()

char cadena[] = { 'H', 'o', 'l', 'a', ' ', 'M', 'u', 'n', 'd', 'o', '\n', '\r', '&#092;&#048;' };        //cadena de caracteres terminada en &#092;&#048;

/**
 * Punto de entrada del programa.
 * 
 * \return 0 
 */
int main()
{
        printf(cadena);        //imprime la cadena en pantalla

        rot13(cadena);         //cifra la cadena

        printf(cadena);        //imprime la cadena en pantalla

        rot13(cadena);         //descifra la cadena

        printf(cadena);        //imprime la cadena en pantalla

        return 0;
}

/**
 * Cifra o Descifra una cadena de caracteres en ROT13.
 *
 * \param cadena puntero hacia la cadena de caracteres.
 */
void rot13(char *cadena)
{
        int c;
        int i = 0;                         //valor de la posición actual en la cadena
        while (cadena[i] != '&#092;&#048;') {        //comienza el bucle para analizar los caracteres de la cadena[] hasta llegar a &#092;&#048;
                c = cadena[i];              //introduce el carácter de la posición actual de la cadena en la variable c

                if (c &gt;= 'A' &amp;&amp; c &lt; 'N') {                //compara c con A y N
                        cadena[i] = c + 13;               //si es igual o mayor que A y menor que N, se realiza un desplazamiento sumándole 13
                } else if (c &gt;= 'N' &amp;&amp; c &lt;= 'Z') {        //compara c con N y Z
                        cadena[i] = c - 13;               //si es igual o mayor que N e igual o menor que Z, se realiza un desplazamiento restándole 13
                } else if (c &gt;= 'a' &amp;&amp; c &lt; 'n') {         //compara c con a y n
                        cadena[i] = c + 13;               //si es igual o mayor que a y menor que n, se realiza un desplazamiento sumándole 13
                } else if (c &gt;= 'n' &amp;&amp; c &lt;= 'z') {        //compara c con n y z
                        cadena[i] = c - 13;               //si es igual o mayor que n e igual o menor que z, se realiza un desplazamiento restándole 13
                }

                i++;        //incrementa la variable i en uno para poder analizar el siguiente carácter
        }
}
</pre>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/06/26/c-rot13/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2240</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>
	</item>
		<item>
		<title>ASM x86 &#8211; Linux &#8211; ROT13</title>
		<link>https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-linux-rot13/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-linux-rot13/#respond</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Sun, 26 Jun 2011 06:34:04 +0000</pubDate>
				<category><![CDATA[ASM x86]]></category>
		<category><![CDATA[Informática]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[asm]]></category>
		<category><![CDATA[ensamblador]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[x86]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2165</guid>

					<description><![CDATA[Hola Mundo Ubyn Zhaqb Hola Mundo]]></description>
										<content:encoded><![CDATA[<div style="overflow:auto;border:1px solid #585858;background-color:black;color:white;">Hola Mundo<br />
Ubyn Zhaqb<br />
Hola Mundo</div>
<p><span id="more-2165"></span></p>
<pre class="brush: delphi; title: ; wrap-lines: false; notranslate">
;##########################################################################################################
;# rot13Lnx.asm
;# Sintaxis NASM
;#
;# Ejemplo de la implementación del cifrado ROT13 en ensamblador x86 para Linux.
;#
;# Ensamblar:
;#     nasm -f elf rot13Lnx.asm
;# Linkar:
;#     gcc -o rot13Lnx rot13Lnx.o
;##########################################################################################################

[CPU 386]        ;un par de directivas realmente no necesarias
[BITS 32]
[GLOBAL main]    ;define el punto de entrada del programa

[SECTION .data]
cadena  DB      &quot;Hola Mundo&quot;, 13, 10        ;cadena de texto
cadLen  EQU     $ - cadena                  ;longitud de la cadena de texto

[SECTION .text]
        main:
                CALL    printStr          ;imprime la cadena en pantalla

                PUSH    cadLen
                PUSH    cadena
                CALL    rot13             ;cifra la cadena

                CALL    printStr          ;imprime la cadena en pantalla

                PUSH    cadLen
                PUSH    cadena
                CALL    rot13             ;descifra la cadena

                CALL    printStr          ;imprime la cadena en pantalla

                MOV	eax, 1
                MOV	ebx, 0
                INT	80h              ;termina el proceso con el código de error 0 (sin error)

        ;
        ; Imprime una cadena de texto en pantalla.
        ;
        ; Utiliza: eax
        ;          edx
        ;          ecx
        ;          ebx
        ;
        printStr:
                MOV     ecx, cadena
                MOV     edx, cadLen
                MOV     ebx, 1
                MOV     eax, 4
                INT     80h
                RET

        ;
        ; Cifra o Descifra una cadena de texto en ROT13.
        ;
        ; Parámetros: cadena:DWORD = [esp + 4] &gt; Cadena de texto.
        ;             cadLen:DWORD = [esp + 8] &gt; Longitud de la cadena.
        ;
        ; Utiliza: eax (al)
        ;          ecx
        ;          edx
        ;
        rot13:
                MOV     edx, [esp + 4]	    ;copia el parámetro cadena:DWORD en EDX
                MOV     ecx, [esp + 8]	    ;copia el parámetro cadLen:DWORD en ECX

                .loop:                    	    ;comienza el bucle para analizar los bytes de cadena:DWORD
                        MOV     al, BYTE [edx]	    ;copia el byte de la posición actual de cadena:DWORD en AL para analizarlo

                        CMP     al, 'A'             ;compara AL con: [ASCII:A = Dec:65 = Hex:41]
                        JB      .next               ;si es menor, se ignora el byte y se salta a .next para analizar el siguiente byte

                        CMP     al, 'N'             ;compara AL con: [ASCII:N = Dec:78 = Hex:4E]
                        JB      .add13              ;si es menor, se salta a .add13 para realizar el desplazamiento sumándole 13

                        CMP     al, 'Z'             ;compara AL con: [ASCII:Z = Dec:90 = Hex:5A]
                        JBE     .sub13              ;si es menor o igual, se salta a .sub13 para realizar el desplazamiento restándole 13

                        CMP     al, 'a'             ;compara AL con: [ASCII:a = Dec:97 = Hex:61]
                        JB      .next               ;si es menor, se ignora el byte y se salta a .next para analizar el siguiente byte

                        CMP     al, 'n'             ;compara AL con: [ASCII:n = Dec:110 = Hex:6E]
                        JB      .add13              ;si es menor, se salta a .add13 para realizar el desplazamiento sumándole 13

                        CMP     al, 'z'             ;compara AL con: [ASCII:z = Dec:122 = Hex:7A]
                        JA      .next	            ;si es mayor, se ignora el byte y se salta a .next para analizar el siguiente byte
                                                    ;si es menor o igual, continua en .sub13 para realizar el desplazamiento restándole 13

                .sub13:                         ;
                        SUB     al, 13          ;resta 13 al byte en AL
                        MOV     [edx], al       ;copia AL a la posición actual de cadena:DWORD
                        JMP     .next	        ;salta a .next para analizar el siguiente byte

                .add13:                         ;
                        ADD     al, 13          ;suma 13 al byte en AL
                        MOV     [edx], al       ;copia AL a la posición actual de cadena:DWORD
                                                ;continua en .next para analizar el siguiente byte

                .next:                      ;
                        ADD     edx, 1      ;incrementa cadena:DWORD en uno para poder analizar al siguiente byte
                        LOOP    .loop       ;disminuye cadLen:DWORD en uno, y continua el bucle solo si es mayor que 0.

                .end:
                RET     4     ;retorna limpiando las variables locales de la pila                
</pre>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-linux-rot13/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2165</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>
	</item>
		<item>
		<title>ASM x86 &#8211; Windows &#8211; ROT13</title>
		<link>https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-windows-rot13/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-windows-rot13/#respond</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Sun, 26 Jun 2011 06:33:15 +0000</pubDate>
				<category><![CDATA[ASM x86]]></category>
		<category><![CDATA[Informática]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[asm]]></category>
		<category><![CDATA[ensamblador]]></category>
		<category><![CDATA[rot13]]></category>
		<category><![CDATA[windows]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2158</guid>

					<description><![CDATA[Hola Mundo Ubyn Zhaqb Hola Mundo]]></description>
										<content:encoded><![CDATA[<div style="overflow:auto;border:1px solid #585858;background-color:black;color:white;">Hola Mundo<br />
Ubyn Zhaqb<br />
Hola Mundo</div>
<p><span id="more-2158"></span></p>
<pre class="brush: delphi; title: ; wrap-lines: false; notranslate">
;##########################################################################################################
;# rot13Win.asm
;# Sintaxis NASM
;#
;# Ejemplo de la implementación del cifrado ROT13 en ensamblador x86 para Windows.
;#
;# Ensamblar:
;#     nasm -f win32 rot13Win.asm
;# Linkar:
;#     golink /entry main /console /mix rot13Win.obj kernel32.dll
;##########################################################################################################

[CPU 386]        ;un par de directivas realmente no necesarias
[BITS 32]
[GLOBAL main]    ;define el punto de entrada del programa

[EXTERN _GetStdHandle@4]       ;declaración de la API de Windows
[EXTERN _WriteFile@20]         ;
[EXTERN _ExitProcess@4]        ;

[SECTION .data]
cadena  DB      &quot;Hola Mundo&quot;, 13, 10        ;cadena de texto
cadLen  EQU     $ - cadena                  ;longitud de la cadena de texto

[SECTION .text]
        main:
                CALL    printStr          ;imprime la cadena en pantalla

                PUSH    cadLen
                PUSH    cadena
                CALL    rot13             ;cifra la cadena

                CALL    printStr          ;imprime la cadena en pantalla

                PUSH    cadLen
                PUSH    cadena
                CALL    rot13             ;se descifra la cadena

                CALL    printStr          ;imprime la cadena en pantalla

                PUSH    0
                CALL    _ExitProcess@4    ;termina el proceso con el código de error 0 (sin error)

        ;
        ; Imprime una cadena de texto en pantalla.
        ;
        ; Locales: lpNumberOfBytesWritten:DWORD = [ebp - 4] &gt; Dirección de memoria donde se guarda el
        ;                                                    resultado de bytes escritos.
        ;
        ; Utiliza: eax
        ;          edx
        ;          ebp
        ;
        printStr:
                PUSH   ebp         ;prólogo de la pila
                MOV    ebp, esp
                SUB    esp, 4

                PUSH    -11
                CALL    _GetStdHandle@4

                PUSH    0
                LEA     edx, [ebp - 4]
                PUSH    edx
                PUSH    cadLen
                PUSH    cadena
                PUSH    eax
                CALL    _WriteFile@20

                MOV     esp, ebp   ;epílogo
                POP     ebp
                RET

        ;
        ; Cifra o Descifra una cadena de texto en ROT13.
        ;
        ; Parámetros: cadena:DWORD = [esp + 4] &gt; Cadena de texto.
        ;              cadLen:DWORD = [esp + 8] &gt; Longitud de la cadena.
        ;
        ; Utiliza: eax (al)
        ;          ecx
        ;          edx
        ;
        rot13:
                MOV     edx, [esp + 4]	    ;copia el parámetro cadena:DWORD en EDX
                MOV     ecx, [esp + 8]	    ;copia el parámetro cadLen:DWORD en ECX

                .loop:                    	    ;comienza el bucle para analizar los bytes de cadena:DWORD
                        MOV     al, BYTE [edx]	    ;copia el byte de la posición actual de cadena:DWORD en AL para analizarlo

                        CMP     al, 'A'             ;compara AL con: [ASCII:A = Dec:65 = Hex:41]
                        JB      .next               ;si es menor, se ignora el byte y se salta a .next para analizar el siguiente byte

                        CMP     al, 'N'             ;compara AL con: [ASCII:N = Dec:78 = Hex:4E]
                        JB      .add13              ;si es menor, se salta a .add13 para realizar el desplazamiento sumándole 13

                        CMP     al, 'Z'             ;compara AL con: [ASCII:Z = Dec:90 = Hex:5A]
                        JBE     .sub13              ;si es menor o igual, se salta a .sub13 para realizar el desplazamiento restándole 13

                        CMP     al, 'a'             ;compara AL con: [ASCII:a = Dec:97 = Hex:61]
                        JB      .next               ;si es menor, se ignora el byte y se salta a .next para analizar el siguiente byte

                        CMP     al, 'n'             ;compara AL con: [ASCII:n = Dec:110 = Hex:6E]
                        JB      .add13              ;si es menor, se salta a .add13 para realizar el desplazamiento sumándole 13

                        CMP     al, 'z'             ;compara AL con: [ASCII:z = Dec:122 = Hex:7A]
                        JA      .next	            ;si es mayor, se ignora el byte y se salta a .next para analizar el siguiente byte
                                                    ;si es menor o igual, continua en .sub13 para realizar el desplazamiento restándole 13

                .sub13:                         ;
                        SUB     al, 13          ;resta 13 al byte en AL
                        MOV     [edx], al       ;copia AL a la posición actual de cadena:DWORD
                        JMP     .next	        ;salta a .next para analizar el siguiente byte

                .add13:                         ;
                        ADD     al, 13          ;le suma 13 al byte en AL
                        MOV     [edx], al       ;copia AL a la posición actual de cadena:DWORD
                                                ;continua  en .next para analizar el siguiente byte

                .next:                      ;
                        ADD     edx, 1      ;incrementa cadena:DWORD en uno para poder analizar al siguiente byte
                        LOOP    .loop       ;disminuye cadLen:DWORD en uno, y continua el bucle solo si es mayor que 0.

                .end:
                RET     4     ;retorna limpiando las variables locales de la pila
</pre>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-windows-rot13/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2158</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>
	</item>
		<item>
		<title>Criptografía &#8211; ROT13</title>
		<link>https://darkbyteblog.wordpress.com/2011/06/15/criptografia-rot13/</link>
					<comments>https://darkbyteblog.wordpress.com/2011/06/15/criptografia-rot13/#respond</comments>
		
		<dc:creator><![CDATA[Dark[byte]]]></dc:creator>
		<pubDate>Wed, 15 Jun 2011 04:22:27 +0000</pubDate>
				<category><![CDATA[ASM x86]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Informática]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[asm]]></category>
		<category><![CDATA[c]]></category>
		<category><![CDATA[cifrado]]></category>
		<category><![CDATA[clasica]]></category>
		<category><![CDATA[criptografia]]></category>
		<category><![CDATA[ensamblador]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rot13]]></category>
		<category><![CDATA[x86]]></category>
		<guid isPermaLink="false">http://darkbyteblog.wordpress.com/?p=2027</guid>

					<description><![CDATA[ROT13 es un método criptográfico clásico basado en el cifrado por sustitución, de su nombre se entiende «rotar 13 posiciones». Este consiste en el desplazamiento de las letras del alfabeto latino, es decir se altera el orden, lo cual se logra sustituyendo sucesivamente cada letra por la letra que se encuentra 13 posiciones más adelante [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>ROT13 es un método criptográfico clásico basado en el cifrado por sustitución, de su nombre se entiende «rotar 13 posiciones». Este consiste en el desplazamiento de las letras del alfabeto latino, es decir se altera el orden, lo cual se logra sustituyendo sucesivamente cada letra por la letra que se encuentra 13 posiciones más adelante en el alfabeto.</p>
<p><span id="more-2027"></span></p>
<p>Los números, símbolos, espacios y otros caracteres son ignorados permaneciendo en su posición original.</p>
<p>El siguiente es un esquema del método de cifrado ROT13.</p>
<p><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg"><img loading="lazy" data-attachment-id="2028" data-permalink="https://darkbyteblog.wordpress.com/2011/06/15/criptografia-rot13/cripto-rot131/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg" data-orig-size="447,158" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="cripto-rot131" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg?w=300" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg?w=447" class="aligncenter size-full wp-image-2028" title="cripto-rot131" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg 447w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg?w=150&amp;h=53 150w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg?w=300&amp;h=106 300w" sizes="(max-width: 447px) 100vw, 447px" /></a></p>
<p>El esquema muestra que la letra A se convierte en N, B se convierte en O, C se convierte en P y así sucesivamente hasta llegar a M que se convierte en Z. Pero la secuencia se invierte a partir de N donde este se convierte en A, O se convierte en B, P se convierte en C y así sucesivamente hasta llegar a Z que se convierte en M.</p>
<h2 style="text-align:left;">Historia</h2>
<hr />
<p>ROT13 es en realidad un caso particular de un grupo simple de algoritmos de cifrado llamado cifrados Cesar, los cuales están basados en el cifrado por sustitución y pertenecen a la criptografía clásica.</p>
<p>Este método de cifrado data de al menos 600-500 años A.C (procedencia hebrea), y fue redescubierto en la década de los 80 para ser utilizado por un grupo de noticias que intentaba ofuscar chistes que podrían ser ofensivos para algunos lectores o evitar que el chiste fuera leído muy pronto.</p>
<p>El método resulto útil en cuanto a la compatibilidad, porque al trabajar solo sobre letras se garantizaba que no hubiera problemas relacionados con caracteres extraños.  El<strong> ROT13</strong> (valor de rotación 13),  a diferencia de <strong>ROT-N</strong> donde el valor de rotación <em>N</em> no esta definido (un caso es ROT-3 que seria el cifrado Cesar original),  permite utilizar el mismo algoritmo para cifrar y descifrar al aplicarse sobre el alfabeto latino que tiene 26 letras, ya que <strong>26 = 13 * 2</strong>, y este esta incluido en el juego de caracteres ASCII que puede cubrir al menos los idiomas occidentales. Por lo tanto el valor de rotación 13 resulta conveniente para este alfabeto de 26 letras.</p>
<p>Los servidores UNIX poseen el comando <em><strong>tr</strong></em>, que puede utilizarse para cifrar o descifrar ROT13 y también ROT-N. La sintaxis de <em> <strong>tr</strong></em>:</p>
<pre>tr '[A-Z][a-z]' '[N-ZA-M][n-za-m]' &lt; archivo</pre>
<h2 style="text-align:left;">En matemáticas</h2>
<hr />
<p>Dado el alfabeto latino (que no tiene Ñ) con 26 letras, le asignamos un numero a cada una de modo que A= 1, B = 2, C = 3, &#8230; etc, y obtenemos el siguiente conjunto finito de orden ascendente:</p>
<p style="text-align:center;"><strong>N = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 16, 17 , 18, 19 , 20, 21, 22, 23, 24, 25, 26 }</strong>.</p>
<p>Para realizar un cifrado ROT13 sobre el conjunto <strong>N</strong>, se utiliza la siguiente función:</p>
<p style="text-align:center;"><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg"><img loading="lazy" data-attachment-id="2030" data-permalink="https://darkbyteblog.wordpress.com/2011/06/15/criptografia-rot13/cripto-rot132/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg" data-orig-size="400,116" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="cripto-rot132" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg?w=300" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg?w=400" class="aligncenter size-full wp-image-2030" title="cripto-rot132" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg?w=320&amp;h=93 320w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg?w=150&amp;h=44 150w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg?w=300&amp;h=87 300w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg 400w" sizes="(max-width: 320px) 100vw, 320px" /></a></p>
<p>Por ejemplo para cifrar el texto <strong>HOLA</strong>, sabemos que <strong>H = 8</strong>, <strong>0 = 15, L = 12</strong>, <strong>A = 1</strong>. Aplicamos la función para cada letra:</p>
<p><strong><em>f</em>(8) = 8 + 13 = 21 ≡ U</strong></p>
<p><strong><em>f</em>(15) = 15 &#8211; 13 = 2 ≡ B</strong></p>
<p><strong><em>f</em>(12) = 12 + 13 = 25 ≡ Y</strong></p>
<p><strong><em>f</em>(1) = 1 + 13 = 14 ≡ N</strong></p>
<p>El resultado cifrado es <strong>UBYN</strong>.</p>
<p>Para descifrar lo único que tenemos que hacer es aplicar nuevamente la misma función sobre el texto cifrado,  sabemos que <strong>U= 21</strong>,<strong> B = 2</strong>,<strong> Y = 25</strong>,<strong> N = 14</strong>. Aplicamos la función para cada letra:</p>
<p><strong><em>f</em>(21) = 21 &#8211; 13 = 8 ≡ H</strong></p>
<p><strong><em>f</em>(2) = 2 + 13 = 15 ≡ O</strong></p>
<p><strong><em>f</em>(25) = 25 &#8211; 13 = 12 ≡ L</strong></p>
<p><strong><em>f</em>(14) = 14 &#8211; 13 = 1 ≡ A</strong></p>
<p>El resultado es el texto original<strong> HOLA.</strong></p>
<p>Visto gráficamente:</p>
<p style="text-align:center;"><a href="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg"><img loading="lazy" data-attachment-id="2032" data-permalink="https://darkbyteblog.wordpress.com/2011/06/15/criptografia-rot13/cripto-rot133/" data-orig-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg" data-orig-size="617,604" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="cripto-rot133" data-image-description="" data-image-caption="" data-medium-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg?w=300" data-large-file="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg?w=465" class="aligncenter size-full wp-image-2032" title="cripto-rot133" src="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg?w=465" alt=""   srcset="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg?w=279&amp;h=273 279w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg?w=558&amp;h=546 558w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg?w=150&amp;h=147 150w, https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg?w=300&amp;h=294 300w" sizes="(max-width: 279px) 100vw, 279px" /></a></p>
<p>Esta función resulta ser involutiva porque es su propia inversa, en criptografía se le denomina cifrado reciproco.</p>
<h2 style="text-align:left;">Utilidad</h2>
<hr />
<p>ROT13 no es un cifrado de utilidad para tratar información de comercio electrónico o de bancos, no ofrece seguridad real (al menos en nuestros tiempos). Es un cifrado débil que al utilizar un desplazamiento constante no tiene clave, para el descifrado un atacante no necesita mas que simplemente saber que se esta usando ROT13, y aunque no lo supiese el algoritmo podría romperse fácilmente con un análisis de frecuencia u otro método.</p>
<p>Hoy en día ROT13 sigue siendo utilizado con los mismos o similares fines que en los 80, como lo hace por ejemplo el grupo de noticias USENET en sus artículos. También suele utilizarse para ocultar el resultado de adivinanzas, alguna información en foros, realizar juegos de letras, y etc.</p>
<h2 style="text-align:left;">Implementación</h2>
<hr />
<p>A continuación se exhiben y explican algunos ejemplos de la implementación del cifrado ROT13 en algunos lenguajes de programación. La idea es tomar una cadena de caracteres para cifrarla y descifrarla mientras se van imprimiendo los resultados en pantalla.</p>
<p><strong>Notas:</strong><br />
<strong>*</strong> Los ejemplos pretenden ser legibles, no óptimos.<br />
<strong>*</strong> Los códigos fuentes completos de los ejemplos están al final.</p>
<h3 style="text-align:left;">Ensamblador (ASM) x86</h3>
<p>La sintaxis es para NASM.</p>
<pre class="brush: delphi; title: ; wrap-lines: false; notranslate">
...
cadena  DB      &quot;Hola Mundo&quot;, 13, 10        ;cadena de caracteres
cadLen  EQU     $ - cadena                  ;longitud de la cadena
...
rot13:
	MOV     edx, [esp + 4]	    ;copia el parámetro cadena:DWORD en EDX
	MOV     ecx, [esp + 8]	    ;copia el parámetro cadLen:DWORD en ECX

        .loop:                    	    ;comienza el bucle para analizar los bytes de cadena:DWORD
                MOV     al, BYTE [edx]	    ;copia el byte de la posición actual de cadena:DWORD en AL para analizarlo

                CMP     al, 'A'             ;compara AL con: [ASCII:A = Dec:65 = Hex:41]
                JB      .next               ;si es menor, se ignora el byte y se salta a .next para analizar el siguiente byte

                CMP     al, 'N'             ;compara AL con: [ASCII:N = Dec:78 = Hex:4E]
                JB      .add13              ;si es menor, se salta a .add13 para realizar el desplazamiento sumándole 13

                CMP     al, 'Z'             ;compara AL con: [ASCII:Z = Dec:90 = Hex:5A]
                JBE     .sub13              ;si es menor o igual, se salta a .sub13 para realizar el desplazamiento restándole 13

                CMP     al, 'a'             ;compara AL con: [ASCII:a = Dec:97 = Hex:61]
                JB      .next               ;si es menor, se ignora el byte y se salta a .next para analizar el siguiente byte

                CMP     al, 'n'             ;compara AL con: [ASCII:n = Dec:110 = Hex:6E]
                JB      .add13              ;si es menor, se salta a .add13 para realizar el desplazamiento sumándole 13

                CMP     al, 'z'             ;compara AL con: [ASCII:z = Dec:122 = Hex:7A]
                JA      .next	            ;si es mayor, se ignora el byte y se salta a .next para analizar el siguiente byte
                                            ;si es menor o igual, continua en .sub13 para realizar el desplazamiento restándole 13

        .sub13:                         ;
                SUB     al, 13          ;resta 13 al byte en AL
                MOV     [edx], al       ;copia AL a la posición actual de cadena:DWORD
                JMP     .next	        ;salta a .next para analizar el siguiente byte

        .add13:                         ;
                ADD     al, 13          ;suma 13 al byte en AL
                MOV     [edx], al       ;copia AL a la posición actual de cadena:DWORD
                                        ;continua en .next para analizar el siguiente byte

        .next:                      ;
                ADD     edx, 1      ;incrementa cadena:DWORD en uno para poder analizar al siguiente byte
                LOOP    .loop       ;disminuye cadLen:DWORD en uno, y continua el bucle solo si es mayor que 0.

        .end:
        RET     4     ;retorna limpiando las variables locales de la pila
...
</pre>
<h3 style="text-align:left;">C</h3>
<pre class="brush: objc; title: ; wrap-lines: false; notranslate">
...
char cadena[] = { 'H', 'o', 'l', 'a', ' ', 'M', 'u', 'n', 'd', 'o', '\r', '\n', '&#092;&#048;' };        //cadena de caracteres terminada en &#092;&#048;
...
void rot13(char *cadena)
{
        int c;
        int i = 0;                         //valor de la posición actual en la cadena
        while (cadena[i] != '&#092;&#048;') {        //comienza el bucle para analizar los caracteres de la cadena[] hasta llegar a &#092;&#048;
                c = cadena[i];              //introduce el carácter de la posición actual de la cadena en la variable c

                if (c &gt;= 'A' &amp;&amp; c &lt; 'N') {                //compara c con A y N
                        cadena[i] = c + 13;               //si es igual o mayor que A y menor que N, se realiza un desplazamiento sumándole 13
                } else if (c &gt;= 'N' &amp;&amp; c &lt;= 'Z') {        //compara c con N y Z
                        cadena[i] = c - 13;               //si es igual o mayor que N e igual o menor que Z, se realiza un desplazamiento restándole 13
                } else if (c &gt;= 'a' &amp;&amp; c &lt; 'n') {         //compara c con a y n
                        cadena[i] = c + 13;               //si es igual o mayor que a y menor que n, se realiza un desplazamiento sumándole 13
                } else if (c &gt;= 'n' &amp;&amp; c &lt;= 'z') {        //compara c con n y z
                        cadena[i] = c - 13;               //si es igual o mayor que n e igual o menor que z, se realiza un desplazamiento restándole 13
                }

	        i++;        //incrementa la variable i en uno para poder analizar el siguiente carácter
        }
}
...
</pre>
<h3 style="text-align:left;">Java</h3>
<pre class="brush: java; title: ; wrap-lines: false; notranslate">
...
static String cadena = &quot;Hola Mundo&quot;;    //cadena de caracteres
...
static String rot13(String cadena) {
    char c;
    StringBuilder temp = new StringBuilder();        //crea un StringBuilder para construir la cadena resultante en la variable temp

    for (int i = 0 ; i &lt; cadena.length() ; i++) {    //comienza el bucle para analizar los caracteres de la cadena
        c = cadena.charAt(i);                        //se obtiene el carácter de la posición actual de la cadena y se guarda en la variable c

        if (c &gt;= 'A' &amp;&amp; c &lt; 'N') {            //compara c con A y N
            c += 13;                          //si es igual o mayor que A y menor que N, se realiza un desplazamiento sumándole 13
        } else if (c &gt;= 'N' &amp;&amp; c &lt;= 'Z') {    //compara c con N y Z
            c -= 13;                          //si es igual o mayor que N e igual o menor que Z, se realiza un desplazamiento restándole 13
        } else if (c &gt;= 'a' &amp;&amp; c &lt; 'n') {     //compara c con a y n
            c += 13;                          //si es igual o mayor que a y menor que n, se realiza un desplazamiento sumándole 13
        } else if (c &gt;= 'n' &amp;&amp; c &lt;= 'z') {    //compara c con n y z
            c -= 13;                          //si es igual o mayor que n e igual o menor que z, se realiza un desplazamiento restándole 13
        }

        temp.append(c);    //se añade c en temp
    }

    return temp.toString();    //retornamos la variable temp, la cual contiene la cadena cifrada o descifrada
}
...
</pre>
<hr />
<p><strong>Código fuente:</strong><br />
* <a href="https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-windows-rot13/" target="_blank">ASM x86 &#8211; Windows &#8211; ROT13</a><br />
* <a href="https://darkbyteblog.wordpress.com/2011/06/26/asm-x86-linux-rot13/" target="_blank">ASM x86 &#8211; Linux -ROT13</a><br />
* <a href="https://darkbyteblog.wordpress.com/2011/06/26/c-rot13/" target="_blank">C &#8211; ROT13</a><br />
* <a href="https://darkbyteblog.wordpress.com/2011/06/26/java-rot13/" target="_blank">Java &#8211; ROT13</a></p>
<p><strong>Más información:</strong><br />
* <a href="http://es.wikipedia.org/wiki/ROT13" target="_blank">Wikipedia &#8211; ROT13</a></p>
<hr />
]]></content:encoded>
					
					<wfw:commentRss>https://darkbyteblog.wordpress.com/2011/06/15/criptografia-rot13/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2027</post-id>
		<media:content url="https://1.gravatar.com/avatar/444f446ec4d1419a452db2c0eaa820f848d307cc1e17086dc363377a36bebc8c?s=96&#38;d=https%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">darkbyt3</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot131.jpg" medium="image">
			<media:title type="html">cripto-rot131</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot132.jpg" medium="image">
			<media:title type="html">cripto-rot132</media:title>
		</media:content>

		<media:content url="https://darkbyteblog.wordpress.com/wp-content/uploads/2011/06/cripto-rot133.jpg" medium="image">
			<media:title type="html">cripto-rot133</media:title>
		</media:content>
	</item>
	</channel>
</rss>
