/******************************************************************************* * Copyright © 2011, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.mof.utils; import java.io.BufferedWriter; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import org.eclipse.edt.mof.serialization.SerializationException; /** * This class writes indented text. It has two modes of operation. The default * mode is called auto-indent mode. In auto-indent mode, writing a { character * causes the next line to be indented one more tab than the current line. * Writing a } character causes the current line to be indented one less * tab than the previous line. Whitespace at the front of each line is * removed in auto-indent mode. * * <P> If auto-indent mode is turned off, you can control tabbing manually. * Calling pushIndent and popIndent change the amount that a line is indented. * They work even in auto-indent mode. * * <P> In either mode, use the print and println methods to write output. * * @author Matthew Heitz/Raleigh/IBM */ public class TabbedWriter { /** * The underlying output stream. */ private Writer writer; /** * The current level of indentation. */ private int indent; /** * True if printIndented should add tabs before the next bit of output. */ private boolean doIndent; /** * True if { and } trigger changes to the indent level. */ private boolean doAutoIndent; /** * Buffer to hold a line of text in auto-indent mode. */ private String currentLine; /** * The current line number. */ private int lineNumber; /** * An array of one newline character. */ private static final char[] NEWLINE = new char[] { '\n' }; /** * Creates a TabbedWriter which writes its output to a FileWriter * that is wrapped by a BufferedWriter. * * <P> Auto-indenting is on by default, so { and } trigger changes * to the indent level, and whitespace at the front of each line * is removed. * * @param fileName the name of the file to write to. * @see #setAutoIndent(boolean) */ public TabbedWriter() { this.writer = new BufferedWriter( new StringWriter() ); // Assume we should indent the first line. doIndent = true; doAutoIndent = true; lineNumber = 1; } /** * Creates a TabbedWriter which writes its output to the Writer. * It's a good idea to use a BufferedWriter. * * <P> Auto-indenting is on by default, so { and } trigger changes * to the indent level, and whitespace at the front of each line * is removed. * * @param write the output stream. * @see #setAutoIndent(boolean) */ public TabbedWriter( Writer writer ) { this.writer = writer; // Assume we should indent the first line. doIndent = true; doAutoIndent = true; lineNumber = 1; } /** * Closes the stream. */ public void close() { try { if ( doAutoIndent && currentLine != null ) { // This forces currentLine to be written. println(); } writer.close(); } catch (IOException e) { throw new SerializationException(e.getMessage()); } } /** * For each occurrance of } in currentLine, subtracts one from the * indent level. Returns the indent level of the beginning of the * next line, which is determined by the number of {'s. */ private int computeIndent() { int nextIndent = 0; char[] chars = currentLine.toCharArray(); for ( int i = 0; i < chars.length; i++ ) { if ( chars[ i ] == '}' ) { if ( nextIndent > 0 ) { // This } matches a { from the same line, so don't let them // affect the indentation. nextIndent--; } else { indent--; } } else if ( chars[ i ] == '{' ) { nextIndent++; } } return indent + nextIndent; } /** * Flushes the stream. If the last character written was not a newline, * the last partial line of text will not be flushed. This is because the * text is indented accoring to what's in the current line. When you flush * in the middle of a line the TabbedWriter doesn't know how much to indent it. */ public void flush() throws SerializationException { try { writer.flush(); } catch (IOException e) { throw new SerializationException(e.getMessage()); } } /** * Returns true if auto-indent is on. * * @return true if auto-indent is on. */ public boolean getAutoIndent() { return doAutoIndent; } /** * Returns the current line number. The first line of the file * is line number 1. * * @return the current line number. */ public int getLineNumber() { return lineNumber; } /** * Returns the underlying Writer. * * @return the Writer. */ public Writer getWriter() { return writer; } /** * Adds tabs to the stream once per indent level. */ private void indent() { try { for ( int i = 0; i < indent; i++ ) { writer.write( '\t' ); } } catch (IOException e) { throw new SerializationException(e.getMessage()); } } /** * Decreases the indent level. It's safe to call this method * even if the indent level is currently zero. */ public void popIndent() { if ( indent > 0 ) { indent--; } } /** * Prints the given String. * * @param str the String to be printed. */ public void print( String str ) { print( str.toCharArray() ); } /** * Prints each character in an array. * * @param chars the characters to be printed. * @exception IOException if there's a problem. */ public void print( char[] chars ) { if ( doAutoIndent ) { printAutoIndented( chars ); } else { printManuallyIndented( chars ); } } /** * Prints the given char. * * @param c the char to be printed. */ public void print( char c ) { print( new char[] { c } ); } /** * Prints the given int. * * @param i the int to be printed. */ public void print( int i ) { print( Integer.toString( i ).toCharArray() ); } /** * Prints the given boolean. * * @param b the boolean to be printed. */ public void print( boolean b ) { if ( b ) { print( "true" ); } else { print( "false" ); } } /** * Prints a newline character. */ public void println() { print( NEWLINE ); } /** * Prints the given String followed by a newline character. * * @param str the String to be printed. */ public void println( String str ) { print( str ); print( NEWLINE ); } /** * Prints each character in an array followed by a newline character. * * @param chars the characters to be printed. */ public void println( char[] chars ) { print( chars ); print( NEWLINE ); } /** * Prints the given char followed by a newline character. * * @param c the char to be printed. * @exception IOException if there's a problem. */ public void println( char c ) { print( new char[] { c, '\n' } ); } /** * Prints the given int followed by a newline character. * * @param i the int to be printed. */ public void println( int i ) { print( Integer.toString( i ).toCharArray() ); print( NEWLINE ); } /** * Prints the given boolean followed by a newline character. * * @param b the boolean to be printed. */ public void println( boolean b ) { print( b ); print( NEWLINE ); } /** * Prints the characters to the stream. Lines are written one at a time, * and whitespace at the front of a line is removed. * * @param ch the characters to write. */ private void printAutoIndented( char[] ch ) { int currentStart = 0; int end = ch.length; try { for ( int i = 0; i < end; i++ ) { if ( ch[ i ] == '\n' ) { // Time to write this line. String saveText = new String( ch, currentStart, i - currentStart ); if ( currentLine == null ) { currentLine = saveText; } else { currentLine = currentLine.concat( saveText ); } // Remove extra whitespace and add the tabs for this line. currentLine = currentLine.trim(); int nextIndent = computeIndent(); indent(); // Now we can write the text. writer.write( currentLine ); writer.write( '\n' ); currentLine = null; currentStart = i + 1; // Set the indent level for the next line. indent = nextIndent; // Count the line. lineNumber++; } } } catch (IOException e) { throw new SerializationException(e.getMessage()); } // If there are characters after the last newline, we haven't // written them yet. if ( end > currentStart && ch[ end - 1 ] != '\n' ) { String saveText = new String( ch, currentStart, end - currentStart ); if ( currentLine == null ) { currentLine = saveText; } else { currentLine = currentLine.concat( saveText ); } } } /** * Prints the characters to the stream. * * @param ch the characters to write. */ private void printManuallyIndented( char[] ch ) { int currentStart = 0; int end = ch.length; try { for ( int i = 0; i < end; i++ ) { if ( ch[ i ] == '\n' ) { // Indent and write all the characters we've seen so far. if ( doIndent ) { indent(); } writer.write( ch, currentStart, i + 1 - currentStart ); currentStart = i + 1; doIndent = true; // Count the line. lineNumber++; } } // If there are characters after the last newline, we haven't // written them yet. if ( end > currentStart && ch[ end - 1 ] != '\n' ) { if ( doIndent ) indent(); writer.write( ch, currentStart, end - currentStart ); doIndent = false; } } catch (IOException e) { throw new SerializationException(e.getMessage()); } } /** * Increases the indent level. */ public void pushIndent() { indent++; } /** * Turns auto-indenting on or off. If it's on, writing { and } * trigger changes in the indent level, and whitespace at the * front of each line is removed. * * @param flag the new auto-indent setting. * @exception JavaGenException if there's a problem. */ public void setAutoIndent( boolean flag ) { if ( doAutoIndent && !flag && currentLine != null ) { // Part of a line has yet to be written. Write it // out now. doIndent = true; printManuallyIndented( currentLine.toCharArray() ); currentLine = null; } doAutoIndent = flag; } public String getCurrentLine() { return currentLine; } }