| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.jena.atlas.io; |
| import static java.lang.String.format ; |
| |
| import java.io.IOException ; |
| import java.io.OutputStream ; |
| import java.io.PrintStream ; |
| import java.io.Writer ; |
| |
| import org.apache.jena.atlas.lib.Closeable ; |
| |
| /** A writer that records what the current indentation level is, and |
| * uses that to insert a prefix at each line. |
| * It can also insert line numbers at the beginning of lines. */ |
| |
| public class IndentedWriter extends AWriterBase implements AWriter, Closeable |
| { |
| /** Stdout wrapped in an IndentedWriter - no line numbers */ |
| public static final IndentedWriter stdout = new IndentedWriter(System.out) ; |
| /** Stderr wrapped in an IndentedWriter - no line numbers */ |
| public static final IndentedWriter stderr = new IndentedWriter(System.err) ; |
| |
| static { |
| stdout.setFlushOnNewline(true) ; |
| stderr.setFlushOnNewline(true) ; |
| } |
| |
| // Note cases:if (!flatMode) |
| // 1/ incIndent - decIndent with no output should not cause any padding |
| // 2/ newline() then no text, then finish should not cause a line number. |
| |
| protected Writer out = null ; |
| |
| protected static final int INDENT = 2 ; |
| |
| // Configuration. |
| protected int unitIndent = INDENT ; |
| private char padChar = ' ' ; |
| private String padString = null ; |
| private String linePrefix = null ; |
| protected boolean lineNumbers = false ; |
| protected boolean flatMode = false ; |
| private boolean flushOnNewline = false ; |
| |
| // Internal state. |
| protected boolean startingNewLine = true ; |
| private String endOfLineMarker = null ; |
| protected int currentIndent = 0 ; |
| protected int column = 0 ; |
| protected int row = 1 ; |
| |
| /** Construct a UTF8 IndentedWriter around an OutputStream */ |
| public IndentedWriter(OutputStream outStream) { this(outStream, false) ; } |
| |
| /** Construct a UTF8 IndentedWriter around an OutputStream */ |
| public IndentedWriter(OutputStream outStream, boolean withLineNumbers) { |
| this(makeWriter(outStream), withLineNumbers) ; |
| } |
| |
| /** Create an independent copy of the {@code IndentedWriter}. |
| * Changes to the configuration of the copy will not affect the original {@code IndentedWriter}. |
| * This include indentation level. |
| * <br/>Row and column counters are reset. |
| * <br/>Indent is initially. zero. |
| * <br/>They do share the underlying output {@link Writer}. |
| * @param other |
| * @return IndentedWriter |
| */ |
| public IndentedWriter clone(IndentedWriter other) { |
| IndentedWriter dup = new IndentedWriter(other.out); |
| dup.unitIndent = other.unitIndent; |
| dup.padChar = other.padChar; |
| dup.padString = other.padString; |
| dup.linePrefix = other.linePrefix; |
| dup.lineNumbers = other.lineNumbers; |
| dup.flatMode = other.flatMode; |
| dup.flushOnNewline = other.flushOnNewline; |
| return dup; |
| } |
| |
| private static Writer makeWriter(OutputStream out) { |
| // return BufferingWriter.create(out) ; |
| return IO.asBufferedUTF8(out) ; |
| } |
| |
| /** Using Writers directly is discouraged */ |
| protected IndentedWriter(Writer writer) { this(writer, false) ; } |
| |
| /** Using Writers directly is discouraged */ |
| protected IndentedWriter(Writer writer, boolean withLineNumbers) { |
| out = writer ; |
| lineNumbers = withLineNumbers ; |
| startingNewLine = true ; |
| } |
| |
| @Override |
| public void print(String str) { |
| if ( str == null ) |
| str = "null" ; |
| if ( false ) { |
| // Don't check for embedded newlines. |
| write$(str) ; |
| return ; |
| } |
| for ( int i = 0 ; i < str.length() ; i++ ) |
| printOneChar(str.charAt(i)) ; |
| } |
| |
| @Override |
| public void printf(String formatStr, Object... args) { |
| print(format(formatStr, args)) ; |
| } |
| |
| @Override |
| public void print(char ch) { printOneChar(ch) ; } |
| public void print(Object obj) { print(String.valueOf(obj)); } |
| |
| @Override |
| public void println(String str) { print(str) ; newline() ; } |
| public void println(char ch) { print(ch) ; newline() ; } |
| public void println(Object obj) { print(String.valueOf(obj)); newline(); } |
| |
| @Override |
| public void println() { newline() ; } |
| |
| @Override |
| public void print(char[] cbuf) { |
| for ( char aCbuf : cbuf ) { |
| printOneChar(aCbuf) ; |
| } |
| } |
| |
| /** Print a string N times */ |
| public void print(String s, int n) { |
| for ( int i = 0 ; i < n ; i++ ) |
| print(s) ; |
| } |
| |
| /** Print a char N times */ |
| public void print(char ch, int n) { |
| lineStart() ; |
| for ( int i = 0 ; i < n ; i++ ) |
| printOneChar(ch) ; |
| } |
| |
| private char lastChar = '\0' ; |
| |
| // Worker |
| private void printOneChar(char ch) { |
| // Turn \r\n into a single newline call. |
| // Assumes we don't get \r\r\n etc |
| if ( ch == '\n' && lastChar == '\r' ) { |
| lastChar = ch ; |
| return ; |
| } |
| |
| lineStart() ; |
| lastChar = ch ; |
| |
| // newline |
| if ( ch == '\n' || ch == '\r' ) { |
| newline() ; |
| return ; |
| } |
| write$(ch) ; |
| column += 1 ; |
| } |
| |
| private void write$(char ch) |
| { try { out.write(ch) ; } catch (IOException ex) { IO.exception(ex); } } |
| |
| private void write$(String s) |
| { try { out.write(s) ; } catch (IOException ex) { IO.exception(ex); } } |
| |
| public void newline() { |
| lineStart() ; |
| |
| if ( endOfLineMarker != null ) |
| print(endOfLineMarker) ; |
| if ( !flatMode ) |
| write$('\n') ; |
| startingNewLine = true ; |
| row++ ; |
| column = 0 ; |
| // Note that PrintWriters do not autoflush by default |
| // so if layered over a PrintWriter, need to flush that as well. |
| if ( flushOnNewline ) |
| flush() ; |
| } |
| |
| private boolean atStartOfLine() { return column <= currentIndent ; } |
| |
| public void ensureStartOfLine() { |
| if ( !atStartOfLine() ) |
| newline() ; |
| } |
| |
| @Override |
| public void close() { IO.close(out) ; } |
| |
| @Override |
| public void flush() { IO.flush(out); } |
| |
| /** Pad to the indent (if we are before it) */ |
| public void pad() { |
| if ( startingNewLine && currentIndent > 0 ) |
| lineStart() ; |
| padInternal() ; |
| } |
| |
| /** Pad to a given number of columns EXCLUDING the indent. |
| * |
| * @param col Column number (first column is 1). |
| */ |
| public void pad(int col) { pad(col, false) ; } |
| |
| /** Pad to a given number of columns maybe including the indent. |
| * |
| * @param col Column number (first column is 1). |
| * @param absoluteColumn Whether to include the indent |
| */ |
| public void pad(int col, boolean absoluteColumn) { |
| // Make absolute |
| if ( !absoluteColumn ) |
| col = col + currentIndent ; |
| int spaces = col - column ; |
| for ( int i = 0 ; i < spaces ; i++ ) { |
| write$(' ') ; // Always a space. |
| column++ ; |
| } |
| } |
| |
| private void padInternal() { |
| if ( padString == null ) { |
| for ( int i = column ; i < currentIndent ; i++ ) { |
| write$(padChar) ; |
| column++ ; |
| } |
| } else { |
| for ( int i = column ; i < currentIndent ; i += padString.length() ) { |
| write$(padString) ; |
| column += padString.length() ; |
| } |
| } |
| } |
| |
| /** Get row/line (counts from 1) */ |
| public int getRow() { return row ; } |
| /** Get the absolute column. |
| * This is the location where the next character on the line will be printed. |
| * The IndentedWriter may not yet have padded to this place. |
| */ |
| public int getCol() { |
| if ( currentIndent > column ) |
| return currentIndent ; |
| return column ; |
| } |
| |
| /** Get indent from the left hand edge */ |
| public int getAbsoluteIndent() { return currentIndent ; } |
| /** Set indent from the left hand edge */ |
| public void setAbsoluteIndent(int x) { currentIndent = x ; } |
| |
| /** Position past current indent */ |
| public int getCurrentOffset() { |
| int x = getCol() - getAbsoluteIndent() ; |
| if ( x >= 0 ) |
| return x ; |
| // At start of line somehow. |
| return 0 ; |
| } |
| |
| public boolean hasLineNumbers() { |
| return lineNumbers ; |
| } |
| |
| public void setLineNumbers(boolean lineNumbers) { |
| this.lineNumbers = lineNumbers ; |
| } |
| |
| public String getEndOfLineMarker() { return endOfLineMarker ; } |
| |
| /** Set the marker included at end of line - set to null for "none". Usually used for debugging. */ |
| public void setEndOfLineMarker(String marker) { endOfLineMarker = marker ; } |
| |
| /** Flat mode - print without NL, for a more compact representation*/ |
| public boolean inFlatMode() { return flatMode ; } |
| /** Flat mode - print without NL, for a more compact representation*/ |
| public void setFlatMode(boolean flatMode) { this.flatMode = flatMode ; } |
| |
| /** Flush on newline **/ |
| public boolean getFlushOnNewline() { return flushOnNewline; } |
| /** Flush on newline in this code. |
| * This is set for {@link IndentedWriter#stdout} and {@link IndentedWriter#stderr} |
| * but not by default otherwise. The underlying output, if it is a {@link PrintStream} |
| * may also have a flush on newline as well (e.g {@link System#out}). |
| */ |
| public void setFlushOnNewline(boolean flushOnNewline) |
| { this.flushOnNewline = flushOnNewline; } |
| |
| public char getPadChar() { return padChar ; } |
| public void setPadChar(char ch) { this.padChar = ch ; } |
| public String getPadString() { return padString ; } |
| public void setPadString(String str) { this.padString = str ; unitIndent = str.length(); } |
| |
| /** Initial string printed at the start of each line : defaults to no string. */ |
| public String getLinePrefix() { return linePrefix ; } |
| /** Set the initial string printed at the start of each line. */ |
| public void setLinePrefix(String str) { this.linePrefix = str ; } |
| |
| public void incIndent() { incIndent(unitIndent) ; } |
| |
| public void incIndent(int x) { |
| currentIndent += x ; |
| } |
| |
| public void decIndent() { decIndent(unitIndent) ; } |
| |
| public void decIndent(int x) { |
| currentIndent -= x ; |
| } |
| |
| public void setUnitIndent(int x) { unitIndent = x ; } |
| public int getUnitIndent() { return unitIndent ; } |
| public boolean atLineStart() { return startingNewLine ; } |
| |
| // A line is prefix?number?content. |
| private void lineStart() { |
| if ( flatMode ) { |
| if ( startingNewLine && row > 1 ) |
| // Space between each line. |
| write$(' ') ; |
| startingNewLine = false ; |
| return ; |
| } |
| |
| // Need to do its just before we append anything, not after a NL, |
| // so that a final blank does not cause a prefix or line number. |
| if ( startingNewLine ) { |
| if ( linePrefix != null ) |
| write$(linePrefix) ; |
| insertLineNumber() ; |
| } |
| padInternal() ; |
| startingNewLine = false ; |
| } |
| |
| private int widthLineNumber = 3 ; |
| |
| /** Width of the number field */ |
| public int getNumberWidth() { return widthLineNumber ; } |
| |
| /** Set the width of the number field. |
| * There is also a single space after the number not included in this setting. |
| */ |
| public void setNumberWidth(int widthOfNumbers) { widthLineNumber = widthOfNumbers ; } |
| |
| private void insertLineNumber() { |
| if ( !lineNumbers ) |
| return ; |
| String s = Integer.toString(row) ; |
| for ( int i = 0 ; i < widthLineNumber - s.length() ; i++ ) |
| write$(' ') ; |
| write$(s) ; |
| write$(' ') ; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("Indent = %d : [%d, %d]", currentIndent, row, column) ; |
| } |
| } |