001: /*
002: * Adjusts line endings.
003: * Copyright (C) 2001 Stephen Ostermiller
004: * http://ostermiller.org/contact.pl?regarding=Java+Utilities
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * See COPYING.TXT for details.
017: */
018:
019: package com.Ostermiller.util;
020:
021: import java.io.*;
022: import java.text.MessageFormat;
023: import java.util.ResourceBundle;
024: import java.util.Locale;
025:
026: /**
027: * Stream editor to alter the line separators on text to match
028: * that of a given platform.
029: * More information about this class is available from <a target="_top" href=
030: * "http://ostermiller.org/utils/LineEnds.html">ostermiller.org</a>.
031: *
032: * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
033: * @since ostermillerutils 1.00.00
034: */
035: public class LineEnds {
036:
037: /**
038: * Version number of this program
039: *
040: * @since ostermillerutils 1.00.00
041: */
042: public static final String version = "1.2";
043:
044: /**
045: * Locale specific strings displayed to the user.
046: *
047: * @since ostermillerutils 1.00.00
048: */
049: protected static ResourceBundle labels = ResourceBundle.getBundle(
050: "com.Ostermiller.util.LineEnds", Locale.getDefault());
051:
052: private enum LineEndsCmdLnOption {
053: /** --help */
054: HELP(new CmdLnOption(labels.getString("help.option"))
055: .setDescription(labels.getString("help.message"))),
056: /** --version */
057: VERSION(new CmdLnOption(labels.getString("version.option"))
058: .setDescription(labels.getString("version.message"))),
059: /** --about */
060: ABOUT(new CmdLnOption(labels.getString("about.option"))
061: .setDescription(labels.getString("about.message"))),
062: /** --dos --windows */
063: DOS(new CmdLnOption(new String[] {
064: labels.getString("dos.option"),
065: labels.getString("windows.option") },
066: new char[] { 'd' }).setDescription(labels
067: .getString("d.message"))),
068: /** --unix --java */
069: UNIX(new CmdLnOption(new String[] {
070: labels.getString("unix.option"),
071: labels.getString("java.option") }, new char[] { 'n' })
072: .setDescription(labels.getString("n.message"))),
073: /** --mac */
074: MAC(new CmdLnOption(labels.getString("mac.option"), 'r')
075: .setDescription(labels.getString("r.message"))),
076: /** --system */
077: SYSTEM(new CmdLnOption(labels.getString("system.option"), 's')
078: .setDescription(labels.getString("s.message") + " ("
079: + labels.getString("default") + ")")),
080: /** --force */
081: FORCE(new CmdLnOption(labels.getString("force.option"), 'f')
082: .setDescription(labels.getString("f.message"))),
083: /** --quiet */
084: QUIET(new CmdLnOption(labels.getString("quiet.option"), 'q')
085: .setDescription(labels.getString("q.message") + " ("
086: + labels.getString("default") + ")")),
087: /** --reallyquiet */
088: REALLYQUIET(new CmdLnOption(labels
089: .getString("reallyquiet.option"), 'Q')
090: .setDescription(labels.getString("Q.message"))),
091: /** --verbose */
092: VERBOSE(
093: new CmdLnOption(labels.getString("verbose.option"), 'v')
094: .setDescription(labels.getString("v.message"))),
095: /** --reallyverbose */
096: REALLYVERBOSE(new CmdLnOption(labels
097: .getString("reallyverbose.option"), 'V')
098: .setDescription(labels.getString("V.message"))),
099: /** --noforce */
100: NOFORCE(new CmdLnOption(labels.getString("noforce.option"))
101: .setDescription(labels.getString("noforce.message")
102: + " (" + labels.getString("default") + ")"));
103:
104: private CmdLnOption option;
105:
106: private LineEndsCmdLnOption(CmdLnOption option) {
107: option.setUserObject(this );
108: this .option = option;
109: }
110:
111: private CmdLnOption getCmdLineOption() {
112: return option;
113: }
114: }
115:
116: /**
117: * Converts the line ending on files, or standard input.
118: * Run with --help argument for more information.
119: *
120: * @param args Command line arguments.
121: *
122: * @since ostermillerutils 1.00.00
123: */
124: public static void main(String[] args) {
125:
126: CmdLn commandLine = new CmdLn(args).setDescription(labels
127: .getString("lineends")
128: + labels.getString("purpose.message"));
129: for (LineEndsCmdLnOption option : LineEndsCmdLnOption.values()) {
130: commandLine.addOption(option.getCmdLineOption());
131: }
132: int style = STYLE_SYSTEM;
133: boolean force = false;
134: boolean printMessages = false;
135: boolean printExtraMessages = false;
136: boolean printErrors = true;
137: for (CmdLnResult result : commandLine.getResults()) {
138: switch ((LineEndsCmdLnOption) result.getOption()
139: .getUserObject()) {
140: case HELP: {
141: // print out the help message
142: commandLine.printHelp();
143: System.exit(0);
144: }
145: break;
146: case VERSION: {
147: // print out the version message
148: System.out.println(MessageFormat.format(labels
149: .getString("version"),
150: (Object[]) new String[] { version }));
151: System.exit(0);
152: }
153: break;
154: case ABOUT: {
155: System.out
156: .println(labels.getString("lineends")
157: + " -- "
158: + labels.getString("purpose.message")
159: + "\n"
160: + MessageFormat
161: .format(
162: labels
163: .getString("copyright"),
164: (Object[]) new String[] {
165: "2001",
166: "Stephen Ostermiller (http://ostermiller.org/contact.pl?regarding=Java+Utilities)" })
167: + "\n\n" + labels.getString("license"));
168: System.exit(0);
169: }
170: break;
171: case DOS: {
172: style = STYLE_RN;
173: }
174: break;
175: case UNIX: {
176: style = STYLE_N;
177: }
178: break;
179: case MAC: {
180: style = STYLE_R;
181: }
182: break;
183: case SYSTEM: {
184: style = STYLE_SYSTEM;
185: }
186: break;
187: case FORCE: {
188: force = true;
189: }
190: break;
191: case NOFORCE: {
192: force = false;
193: }
194: break;
195: case REALLYVERBOSE: {
196: printExtraMessages = true;
197: printMessages = true;
198: printErrors = true;
199: }
200: break;
201: case VERBOSE: {
202: printExtraMessages = false;
203: printMessages = true;
204: printErrors = true;
205: }
206: break;
207: case QUIET: {
208: printExtraMessages = false;
209: printMessages = false;
210: printErrors = true;
211: }
212: break;
213: case REALLYQUIET: {
214: printExtraMessages = false;
215: printMessages = false;
216: printErrors = false;
217: }
218: break;
219: }
220: }
221:
222: int exitCond = 0;
223: boolean done = false;
224: for (String argument : commandLine.getNonOptionArguments()) {
225: done = true;
226: File source = new File(argument);
227: if (!source.exists()) {
228: if (printErrors) {
229: System.err.println(MessageFormat.format(labels
230: .getString("doesnotexist"),
231: (Object[]) new String[] { argument }));
232: }
233: exitCond = 1;
234: } else if (!source.canRead()) {
235: if (printErrors) {
236: System.err.println(MessageFormat.format(labels
237: .getString("cantread"),
238: (Object[]) new String[] { argument }));
239: }
240: exitCond = 1;
241: } else if (!source.canWrite()) {
242: if (printErrors) {
243: System.err.println(MessageFormat.format(labels
244: .getString("cantwrite"),
245: (Object[]) new String[] { argument }));
246: }
247: exitCond = 1;
248: } else {
249: try {
250: if (convert(source, style, !force)) {
251: if (printMessages) {
252: System.out
253: .println(MessageFormat
254: .format(
255: labels
256: .getString("modified"),
257: (Object[]) new String[] { argument }));
258: }
259: } else {
260: if (printExtraMessages) {
261: System.out
262: .println(MessageFormat
263: .format(
264: labels
265: .getString("alreadycorrect"),
266: (Object[]) new String[] { argument }));
267: }
268: }
269: } catch (IOException x) {
270: if (printErrors) {
271: System.err.println(argument + ": "
272: + x.getMessage());
273: }
274: exitCond = 1;
275: }
276: }
277: }
278: if (!done) {
279: try {
280: convert(System.in, System.out, style, !force);
281: } catch (IOException x) {
282: System.err.println(x.getMessage());
283: exitCond = 1;
284: }
285: }
286: System.exit(exitCond);
287: }
288:
289: /**
290: * The system line ending as determined
291: * by System.getProperty("line.separator")
292: *
293: * @since ostermillerutils 1.00.00
294: */
295: public final static int STYLE_SYSTEM = 0;
296: /**
297: * The Windows and DOS line ending ("\r\n")
298: *
299: * @since ostermillerutils 1.00.00
300: */
301: public final static int STYLE_WINDOWS = 1;
302: /**
303: * The Windows and DOS line ending ("\r\n")
304: *
305: * @since ostermillerutils 1.00.00
306: */
307: public final static int STYLE_DOS = 1;
308: /**
309: * The Windows and DOS line ending ("\r\n")
310: *
311: * @since ostermillerutils 1.00.00
312: */
313: public final static int STYLE_RN = 1;
314: /**
315: * The UNIX and Java line ending ("\n")
316: *
317: * @since ostermillerutils 1.00.00
318: */
319: public final static int STYLE_UNIX = 2;
320: /**
321: * The UNIX and Java line ending ("\n")
322: *
323: * @since ostermillerutils 1.00.00
324: */
325: public final static int STYLE_N = 2;
326: /**
327: * The UNIX and Java line ending ("\n")
328: *
329: * @since ostermillerutils 1.00.00
330: */
331: public final static int STYLE_JAVA = 2;
332: /**
333: * The MacIntosh line ending ("\r")
334: *
335: * @since ostermillerutils 1.00.00
336: */
337: public final static int STYLE_MAC = 3;
338: /**
339: * The MacIntosh line ending ("\r")
340: *
341: * @since ostermillerutils 1.00.00
342: */
343: public final static int STYLE_R = 3;
344:
345: /**
346: * Buffer size when reading from input stream.
347: *
348: * @since ostermillerutils 1.00.00
349: */
350: private final static int BUFFER_SIZE = 1024;
351: private final static int STATE_INIT = 0;
352: private final static int STATE_R = 1;
353:
354: private final static int MASK_N = 0x01;
355: private final static int MASK_R = 0x02;
356: private final static int MASK_RN = 0x04;
357:
358: /**
359: * Change the line endings of the text on the input stream and write
360: * it to the output stream.
361: *
362: * The current system's line separator is used.
363: *
364: * @param in stream that contains the text which needs line number conversion.
365: * @param out stream where converted text is written.
366: * @return true if the output was modified from the input, false if it is exactly the same
367: * @throws BinaryDataException if non-text data is encountered.
368: * @throws IOException if an input or output error occurs.
369: *
370: * @since ostermillerutils 1.00.00
371: */
372: public static boolean convert(InputStream in, OutputStream out)
373: throws IOException {
374: return convert(in, out, STYLE_SYSTEM, true);
375: }
376:
377: /**
378: * Change the line endings of the text on the input stream and write
379: * it to the output stream.
380: *
381: * @param in stream that contains the text which needs line number conversion.
382: * @param out stream where converted text is written.
383: * @param style line separator style.
384: * @return true if the output was modified from the input, false if it is exactly the same
385: * @throws BinaryDataException if non-text data is encountered.
386: * @throws IOException if an input or output error occurs.
387: * @throws IllegalArgumentException if an unknown style is requested.
388: *
389: * @since ostermillerutils 1.00.00
390: */
391: public static boolean convert(InputStream in, OutputStream out,
392: int style) throws IOException {
393: return convert(in, out, style, true);
394: }
395:
396: /**
397: * Change the line endings of the text on the input stream and write
398: * it to the output stream.
399: *
400: * The current system's line separator is used.
401: *
402: * @param in stream that contains the text which needs line number conversion.
403: * @param out stream where converted text is written.
404: * @param binaryException throw an exception and abort the operation if binary data is encountered and binaryExcepion is false.
405: * @return true if the output was modified from the input, false if it is exactly the same
406: * @throws BinaryDataException if non-text data is encountered.
407: * @throws IOException if an input or output error occurs.
408: *
409: * @since ostermillerutils 1.00.00
410: */
411: public static boolean convert(InputStream in, OutputStream out,
412: boolean binaryException) throws IOException {
413: return convert(in, out, STYLE_SYSTEM, binaryException);
414: }
415:
416: /**
417: * Change the line endings of the text on the input stream and write
418: * it to the output stream.
419: *
420: * @param in stream that contains the text which needs line number conversion.
421: * @param out stream where converted text is written.
422: * @param style line separator style.
423: * @param binaryException throw an exception and abort the operation if binary data is encountered and binaryExcepion is false.
424: * @return true if the output was modified from the input, false if it is exactly the same
425: * @throws BinaryDataException if non-text data is encountered.
426: * @throws IOException if an input or output error occurs.
427: * @throws IllegalArgumentException if an unknown style is requested.
428: *
429: * @since ostermillerutils 1.00.00
430: */
431: public static boolean convert(InputStream in, OutputStream out,
432: int style, boolean binaryException) throws IOException {
433: byte[] lineEnding;
434: switch (style) {
435: case STYLE_SYSTEM: {
436: lineEnding = System.getProperty("line.separator")
437: .getBytes();
438: }
439: break;
440: case STYLE_RN: {
441: lineEnding = new byte[] { (byte) '\r', (byte) '\n' };
442: }
443: break;
444: case STYLE_R: {
445: lineEnding = new byte[] { (byte) '\r' };
446: }
447: break;
448: case STYLE_N: {
449: lineEnding = new byte[] { (byte) '\n' };
450: }
451: break;
452: default: {
453: throw new IllegalArgumentException(
454: "Unknown line break style: " + style);
455: }
456: }
457: byte[] buffer = new byte[BUFFER_SIZE];
458: int read;
459: int state = STATE_INIT;
460: int seen = 0x00;
461: while ((read = in.read(buffer)) != -1) {
462: for (int i = 0; i < read; i++) {
463: byte b = buffer[i];
464: if (state == STATE_R) {
465: if (b != '\n') {
466: out.write(lineEnding);
467: seen |= MASK_R;
468: }
469: }
470: if (b == '\r') {
471: state = STATE_R;
472: } else {
473: if (b == '\n') {
474: if (state == STATE_R) {
475: seen |= MASK_RN;
476: } else {
477: seen |= MASK_N;
478: }
479: out.write(lineEnding);
480: } else if (binaryException && b != '\t'
481: && b != '\f' && (b & 0xff) < 32) {
482: throw new BinaryDataException(labels
483: .getString("binaryexcepion"));
484: } else {
485: out.write(b);
486: }
487: state = STATE_INIT;
488: }
489: }
490: }
491: if (state == STATE_R) {
492: out.write(lineEnding);
493: seen |= MASK_R;
494: }
495: if (lineEnding.length == 2 && lineEnding[0] == '\r'
496: && lineEnding[1] == '\n') {
497: return ((seen & ~MASK_RN) != 0);
498: } else if (lineEnding.length == 1 && lineEnding[0] == '\r') {
499: return ((seen & ~MASK_R) != 0);
500: } else if (lineEnding.length == 1 && lineEnding[0] == '\n') {
501: return ((seen & ~MASK_N) != 0);
502: } else {
503: return true;
504: }
505: }
506:
507: /**
508: * Change the line endings on given file.
509: *
510: * The current system's line separator is used.
511: *
512: * @param f File to be converted.
513: * @return true if the file was modified, false if it was already in the correct format
514: * @throws BinaryDataException if non-text data is encountered.
515: * @throws IOException if an input or output error occurs.
516: *
517: * @since ostermillerutils 1.00.00
518: */
519: public static boolean convert(File f) throws IOException {
520: return convert(f, STYLE_SYSTEM, true);
521: }
522:
523: /**
524: * Change the line endings on given file.
525: *
526: * @param f File to be converted.
527: * @param style line separator style.
528: * @return true if the file was modified, false if it was already in the correct format
529: * @throws BinaryDataException if non-text data is encountered.
530: * @throws IOException if an input or output error occurs.
531: * @throws IllegalArgumentException if an unknown style is requested.
532: *
533: * @since ostermillerutils 1.00.00
534: */
535: public static boolean convert(File f, int style) throws IOException {
536: return convert(f, style, true);
537: }
538:
539: /**
540: * Change the line endings on given file.
541: *
542: * The current system's line separator is used.
543: *
544: * @param f File to be converted.
545: * @param binaryException throw an exception and abort the operation if binary data is encountered and binaryExcepion is false.
546: * @return true if the file was modified, false if it was already in the correct format
547: * @throws BinaryDataException if non-text data is encountered.
548: * @throws IOException if an input or output error occurs.
549: *
550: * @since ostermillerutils 1.00.00
551: */
552: public static boolean convert(File f, boolean binaryException)
553: throws IOException {
554: return convert(f, STYLE_SYSTEM, binaryException);
555: }
556:
557: /**
558: * Change the line endings on given file.
559: *
560: * @param f File to be converted.
561: * @param style line separator style.
562: * @param binaryException throw an exception and abort the operation if binary data is encountered and binaryExcepion is false.
563: * @return true if the file was modified, false if it was already in the correct format
564: * @throws BinaryDataException if non-text data is encountered.
565: * @throws IOException if an input or output error occurs.
566: * @throws IllegalArgumentException if an unknown style is requested.
567: *
568: * @since ostermillerutils 1.00.00
569: */
570: public static boolean convert(File f, int style,
571: boolean binaryException) throws IOException {
572: File temp = null;
573: InputStream in = null;
574: OutputStream out = null;
575: boolean modified = false;
576: try {
577: in = new FileInputStream(f);
578: temp = File.createTempFile("LineEnds", null, null);
579: out = new FileOutputStream(temp);
580: modified = convert(in, out, style, binaryException);
581: in.close();
582: in = null;
583: out.flush();
584: out.close();
585: out = null;
586: if (modified) {
587: FileHelper.move(temp, f, true);
588: } else {
589: if (!temp.delete()) {
590: throw new IOException(
591: MessageFormat.format(labels
592: .getString("tempdeleteerror"),
593: (Object[]) new String[] { temp
594: .toString() }));
595: }
596: }
597: } finally {
598: if (in != null) {
599: in.close();
600: in = null;
601: }
602: if (out != null) {
603: out.flush();
604: out.close();
605: out = null;
606: }
607: }
608: return modified;
609: }
610: }
|