Escribir en varios «Writer» a la vez

Escrito por el .
java programacion blog-stack planeta-codigo
Comentarios

Java

Hace un tiempo tuve necesidad de generar cierto contenido sobre varios writers, la necesidad en concreto era generar un archivo xml en disco y al mismo tiempo el mismo contenido para un correo electrónico. Para no escribir lo mismo en dos Writer diferentes la solución fue crear un writer y este fuese el que escribiese el contenido que se le enviaba sobre varios writers. En la API de Java no hay una clase específica que haga esto pero es muy sencillo hacer una implementación que lo haga, esto va a ser lo que explicaré en el siguiente artículo.

Para hacer que el contenido de un writer se escriba a varios deberemos extender la clase Writer de esta manera su uso será como la de cualquier otro Writer. Lo especial de la implementación del writer es que su misión será realizar la misma operación que se haga sobre él sobre los writers que en este caso se pasan como parámetros en el constructor en forma de varargs.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
package io.github.picodotdev.writer;

import java.io.IOException;
import java.io.Writer;

class MultipleWriter extends Writer {

    private Writer[] writers;

    MultipleWriter(Writer... writers) {
        this.writers = writers;
    }
   
    public Writer append(final char c) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.append(c);
            }
        });
        return this;
    }
   
    public Writer append(final CharSequence csq) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.append(csq);
            }
        });
        return this;
    }
   
    public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.append(csq, start, end);
            }
        });
        return this;
    }
   
    public void close() throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.close();
            }
        });
    }
   
    public void flush() throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.flush();
            }
        });
    }
   
    public void write(final char[] cbuf) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.write(cbuf);
            }
        });
    }
   
    public void write(final char[] cbuf, final int off, final int len) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.write(cbuf, off, len);
            }
        });
    }
   
    public void write(final int c) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.write(c);
            }
        });
    }
   
    public void write(final String str) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.write(str);
            }
        });
    }
   
    public void write(final String str, final int off, final int len) throws IOException {
        doWriters(new Command() {
            public void process(Writer writer) throws IOException {
                writer.write(str, off, len);
            }
        });
    }
   
    private void doWriters(Command command) throws IOException {
        for (Writer w : writers) {
            command.process(w);
        }
    }

    private interface Command {
        public void process(Writer writer) throws IOException;
    }
}

El bucle for sobre cada uno de los Writer está encapsulado en el método doWriters, el objeto Command es que realmente hace la escritura en el writer usando el método write que se llamó sobre la clase MultipleWriter. A falta de las funciones lambda hasta Java 8 se usa el objeto Command y el método doWriters_, por contra se crea por cada método writer invocado se crea una instancia de la clase Command.

Con closures y las novedades de Java 8 en la API no sería necesario que usaramos una clase Command, el código es más sencillo, breve y más legible.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package io.github.picodotdev.writer;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;

class MultipleWriter extends Writer {

    private Writer[] writers;

    MultipleWriter(Writer... writers) {
        this.writers = writers;
    }
   
    public Writer append(final char c) throws IOException {
        doWriters(writer -> {
            writer.append(c);                
        });
        return this;
    }
   
    public Writer append(final CharSequence csq) throws IOException {
        doWriters(writer -> {
            writer.append(csq);
        });
        return this;
    }
   
    public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
        doWriters(writer -> {
            writer.append(csq, start, end);
        });
        return this;
    }
   
    public void close() throws IOException {
        doWriters(writer -> {
            writer.close();
        });
    }
   
    public void flush() throws IOException {
        doWriters(writer -> {
            writer.flush();
        });
    }
   
    public void write(final char[] cbuf) throws IOException {
        doWriters(writer -> {
            writer.write(cbuf);
        });
    }
   
    public void write(final char[] cbuf, final int off, final int len) throws IOException {
        doWriters(writer -> {
            writer.write(cbuf, off, len);
        });
    }
   
    public void write(final int c) throws IOException {
        doWriters(writer -> {
            writer.write(c);
        });
    }
   
    public void write(final String str) throws IOException {
        doWriters(writer -> {
            writer.write(str);
        });
    }
   
    public void write(final String str, final int off, final int len) throws IOException {
        doWriters(writer -> {
            writer.write(str, off, len);
        });
    }
   
    private void doWriters(Command command) throws IOException {
        for (Writer w : writers) {
            command.process(w);
        }
    }

    private interface Command {
        public void process(Writer writer) throws IOException;
    }    
}

Independiente de la implementación con Java 7 o con a Java 8 el uso sería el siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    ...

    public static void main(String[] args) throws Exception {
        Writer w = new MultipleWriter(new OutputStreamWriter(System.out), new OutputStreamWriter(System.out));
        w.write("¡Hola mundo!\n");
        w.flush();
        w.close();
    }

    ...

Con Groovy además de las closures no será necesario que declararemos de forma explícita el lanzamiento de las excepciones sin embargo al usarlo perderíamos la ayuda que ofrece el compilador.