/Users/lyon/j4p/src/classUtils/pack/util/PrefixPrintWriter.java
|
1 package classUtils.pack.util;
2
3 import java.io.*;
4 import java.util.*;
5 import java.text.*;
6
7 /**
8 * A PrintWriter that unconditionally prefixes any new line of output
9 * with a given prefix.
10 * <p>
11 * The prefix may be a constant string, or computed on each invocation
12 * if a {@link PrefixProvider PrefixProvider} object is given at construction
13 *
14 * @version 1.1
15 * @author C. Sadun
16 *
17 */
18 public class PrefixPrintWriter extends PrintWriter {
19
20 private static char [] ls=System.getProperty("line.separator").toCharArray();
21 private PrefixProvider pp;
22 private int truncatedNL;
23 private boolean start=true;
24 private boolean autoFlush=true;
25
26 private static final class TimePrefixProvider implements PrefixProvider {
27
28 private TimePrefixProvider() { }
29 public String getPrefix() {
30 return "["+
31 DateFormat.getDateTimeInstance().format(new Date())+
32 "] "; }
33 }
34
35 /**
36 * A prefix provider to show the current directory as a prefix.
37 * <p>
38 * Optionally, a certain directory can be indicated to be substituted
39 * with a "tilde" (~) charachter in the prefix. The substitution occurs
40 * only when the current directory is the given one ora a subdirectory of the
41 * given one (i.e., the current directory path starts or equals the the given
42 * one's path).
43 *
44 */
45 public static final class DirectoryPrefixProvider implements PrefixProvider {
46
47 private String tildeValue;
48
49 /**
50 * Create a prefix provider which shows the absolute path name
51 * of the current directory
52 */
53 public DirectoryPrefixProvider() {
54 this(null);
55 }
56
57 /**
58 * Create a prefix provider which shows the absolute path name
59 * of the current directory, substituting the given directory
60 * with a 'tilde' (~) charachter.
61 * @param tildeValue the directory that must be substituted with a tilde
62 * @exception RuntimeException if the given File is not a directory
63 */
64 public DirectoryPrefixProvider(File tildeValue) {
65 if (tildeValue != null) {
66 if (! tildeValue.isDirectory())
67 throw new RuntimeException("tilde value can only be a directory, or null");
68 this.tildeValue=PathNormalizer.normalize(tildeValue).getAbsolutePath();
69 }
70 }
71
72 /**
73 * Return a string to prefix to the stream lines.
74 * @return a string to prefix to the stream lines.
75 */
76 public String getPrefix() {
77 String dir = PathNormalizer.normalize(new File(".")).getAbsolutePath();
78 if (tildeValue != null) {
79 if (dir.equals(tildeValue)) dir="~"+File.separator;
80 else if (dir.startsWith(tildeValue)) dir="~"+File.separator+dir.substring(tildeValue.length());
81 }
82 return dir;
83 }
84
85 }
86
87 /**
88 * A built-in provider which prefixes the output with time information
89 */
90 public static final TimePrefixProvider TIME_PREFIXPROVIDER = new TimePrefixProvider();
91
92 // A utility provider to allow for static strings
93 private static class ConstantStringPrefixProvider implements PrefixProvider {
94
95 private String s;
96
97 public ConstantStringPrefixProvider(String s) {
98 this.s=s;
99 }
100
101 public String getPrefix() { return s; }
102 }
103
104 /**
105 * Build a PrefixPrintWriter which wraps the given writer,
106 * and use the given {@link PrefixProvider PrefixProvider}
107 * to determine the prefix.
108 *
109 * @param w the writer to wrap on
110 * @param pp the {@link PrefixProvider PrefixProvider} providing the prefix for each line
111 */
112 public PrefixPrintWriter(Writer w, PrefixProvider pp) {
113 super(w, true);
114 this.truncatedNL=0;
115 this.pp=pp;
116 }
117
118 /**
119 * Build a PrefixPrintWriter which wraps the given writer,
120 * and prefixes lines with the given constant string
121 *
122 * @param w the writer to wrap on
123 * @param prefix the prefix for each line
124 */
125 public PrefixPrintWriter(Writer w, String prefix) {
126 this(w, new ConstantStringPrefixProvider(prefix));
127 }
128
129 /**
130 * Build a PrefixPrintWriter which wraps the given output stream,
131 * and use the given {@link PrefixProvider PrefixProvider}
132 * to determine the prefix.
133 *
134 * @param os the output stream to wrap on
135 * @param pp the {@link PrefixProvider PrefixProvider} providing the prefix for each line
136 */
137 public PrefixPrintWriter(OutputStream os, PrefixProvider pp) {
138 this(new OutputStreamWriter(os), pp);
139 }
140
141 /**
142 * Build a PrefixPrintWriter which wraps the given output stream,
143 * and prefixes lines with the given constant string
144 *
145 * @param os the output stream to wrap on
146 * @param prefix the prefix for each line
147 */
148 public PrefixPrintWriter(OutputStream os, String prefix) {
149 this(new OutputStreamWriter(os), prefix);
150 }
151
152 /**
153 * Write a character
154 */
155 public void write(int c) {
156 write(new char[] { (char)c }, 0, 1);
157 }
158
159 /**
160 * Write a substring of a string, of given length from a given offset
161 */
162 public void write(String s, int off, int len) {
163 write(s.toCharArray(), off, len);
164 }
165
166 /**
167 * Write a portion of character array, of given length from a given offset
168 */
169 public void write(char buf[], int off, int len) {
170 synchronized(out) {
171
172 String prefix = pp.getPrefix();
173
174 if (start) {
175 super.write(prefix, 0, prefix.length());
176 start=false;
177 }
178
179 List pos = new ArrayList();
180 int truncated=truncatedNL; // Remember if we start in a truncated-newline situation
181 for(int i=off;i<off+len;i++) {
182 if (isNL(buf, i, off+len)) pos.add(new Integer(i));
183 }
184 int p1 = 0;
185 String s;
186 for(Iterator i=pos.iterator();i.hasNext(); ) {
187 int p2 = ((Integer)i.next()).intValue();
188 super.write(buf, p1, p2-p1);
189 super.write(ls, 0, ls.length);
190 prefix = pp.getPrefix();
191 super.write(prefix, 0, prefix.length());
192 p1=p2+ls.length-truncated;
193 if (truncated!=0) truncated=0; // Just the first time
194 }
195 super.write(buf, p1, off+len-p1);
196 if (autoFlush) super.flush();
197 }
198 }
199
200 /**
201 * Checks if buf matches the line separator this.ls,
202 * setting this.truncatedNL if a partial match exists
203 * but the buffer portion is too short for a complete
204 * match
205 */
206 private boolean isNL(char []buf, int start, int end) {
207 for(int i=truncatedNL;i<ls.length && i<end;i++) {
208 if (buf[start+i-truncatedNL]!=ls[i]) {
209 if (truncatedNL !=0) truncatedNL=0;
210 return false;
211 }
212 }
213 if (end-start+truncatedNL < ls.length) {
214 truncatedNL=end-start;
215 return false;
216 }
217 if (truncatedNL !=0) truncatedNL=0;
218 return true;
219 }
220
221 /**
222 * Terminate the current line by writing the line separator string. The
223 * line separator string is defined by the system property
224 * <code>line.separator</code>, and is not necessarily a single newline
225 * character (<code>'\n'</code>).
226 */
227 public void println() {
228 super.println();
229 start=true;
230 }
231
232
233 public static void main(String []args) throws Exception {
234 PrefixPrintWriter pw = new PrefixPrintWriter(System.out, ">");
235 pw.write(ls[0]);
236 pw.write(ls[1]);
237 pw.print("This is a test... ");
238 pw.println("Hello"+System.getProperty("line.separator")+"World");
239 }
240
241 }