Java tutorial
/* * Copyright (c) 2015 Power Group. * Licensed 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.power.commons.lang.util.internal; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import static org.power.commons.lang.BasicConstant.EMPTY_STRING; import static org.power.commons.lang.util.Assert.assertTrue; import static org.power.commons.lang.util.Assert.unreachableCode; /** * ?string builder * * @author Michael Zhou */ public class IndentableStringBuilder extends NormalizableStringBuilder<IndentableStringBuilder> { private final IndentStack indents = new IndentStack(); private final int defaultIndent; private int indentLevel; private int quoteLevel; private boolean lazyAppendNewLine; // ?? private boolean lazyStartHangingIndent; // ???start() private int hangingIndent; public IndentableStringBuilder() { this(-1); } public IndentableStringBuilder(int indent) { this.defaultIndent = indent <= 0 ? 2 : indent; } @Override public void clear() { super.clear(); indents.clear(); indentLevel = 0; quoteLevel = 0; lazyAppendNewLine = false; lazyStartHangingIndent = false; hangingIndent = 0; } /** * CR/LF/CRLF ??LF */ @Override protected void visit(char c) { boolean newLine = endsWithNewLine(); if (c == LF && lazyStartHangingIndent) { appendInternalNewLine(); doStartHanglingIndentIfRequired(); return; } // end quote?? if (!newLine && lazyAppendNewLine) { appendInternalNewLine(); newLine = true; } // begin quotes for (; quoteLevel < indentLevel; quoteLevel++) { String beginQuote = indents.getBeginQuote(quoteLevel); if (StringUtils.isEmpty(beginQuote)) { if (!newLine && indents.independent(quoteLevel)) { appendInternalNewLine(); newLine = true; } } else { if (newLine) { appendIndent(quoteLevel); } else { if (!endsWith(" ")) { appendInternal(" "); // begin quote? } } appendInternal(beginQuote); appendInternalNewLine(); newLine = true; } } lazyAppendNewLine = false; // if (c == LF) { appendInternalNewLine(); } else { if (newLine) { appendIndent(indentLevel); } appendInternal(c); } } /** * */ public IndentableStringBuilder start() { return start(null, null, -1); } /** * */ public IndentableStringBuilder start(int indent) { return start(null, null, indent); } /** * ?? */ public IndentableStringBuilder start(String beginQuote, String endQuote) { return start(beginQuote, endQuote, -1); } /** * ?? */ public IndentableStringBuilder start(String beginQuote, String endQuote, int indent) { doStartHanglingIndentIfRequired(); indents.pushIndent(beginQuote, endQuote, indent); indentLevel++; return this; } /** * ?start() */ public IndentableStringBuilder startHangingIndent() { return startHangingIndent(0); } /** * ?start() */ public IndentableStringBuilder startHangingIndent(int indentOffset) { doStartHanglingIndentIfRequired(); lazyStartHangingIndent = true; if (!lazyAppendNewLine && lineLength() - currentIndent() > 0 && quoteLevel >= indentLevel) { hangingIndent = defaultIndent(lineLength() - currentIndent() + indentOffset); } else { hangingIndent = defaultIndent(indentOffset); } return this; } /** * ???? */ private void doStartHanglingIndentIfRequired() { if (lazyStartHangingIndent) { lazyStartHangingIndent = false; start(EMPTY_STRING, EMPTY_STRING, hangingIndent); } } /** * ???end()???? */ public IndentableStringBuilder end() { flush(); // ?? if (lazyStartHangingIndent) { if (!endsWithNewLine()) { lazyAppendNewLine = true; } lazyStartHangingIndent = false; return this; } // ??end quote if (indentLevel > quoteLevel) { indentLevel--; } else { assertTrue(indentLevel == quoteLevel, "indentLevel != quoteLevel"); if (indentLevel > 0) { indentLevel--; quoteLevel--; String endQuote = indents.getEndQuote(indentLevel); if (StringUtils.isNoneEmpty(endQuote)) { // ?end quote?? if (!endsWithNewLine()) { appendInternalNewLine(); } // end quote appendIndent(indentLevel); appendInternal(endQuote); } lazyAppendNewLine = true; } } indents.popIndent(); return this; } /** * ??? */ public int currentIndent() { return indents.getCurrentIndent(); } /** * indent?indent */ private int defaultIndent(int indent) { return indent <= 0 ? defaultIndent : indent; } private void appendIndent(int indentLevel) { int indent = indents.getIndent(indentLevel - 1); for (int j = 0; j < indent; j++) { appendInternal(' '); } } /** * ? */ private class IndentStack extends ArrayList<Object> { private static final long serialVersionUID = -876139304840511103L; private static final int entrySize = 4; public String getBeginQuote(int indentLevel) { if (indentLevel < 0 || indentLevel >= depth()) { return EMPTY_STRING; } return (String) super.get(indentLevel * entrySize); } public String getEndQuote(int indentLevel) { if (indentLevel < 0 || indentLevel >= depth()) { return EMPTY_STRING; } return (String) super.get(indentLevel * entrySize + 1); } public int getIndent(int indentLevel) { if (indentLevel < 0 || indentLevel >= depth()) { return 0; } return (Integer) super.get(indentLevel * entrySize + 2); } /** * ?level??levelfalse */ public boolean independent(int indentLevel) { if (indentLevel < 0 || indentLevel >= depth() - 1) { return true; } int i1 = (Integer) super.get(indentLevel * entrySize + 3); int i2 = (Integer) super.get((indentLevel + 1) * entrySize + 3); return i1 != i2; } public int getCurrentIndent() { int depth = depth(); if (depth > 0) { return getIndent(depth - 1); } else { return 0; } } public int depth() { return super.size() / entrySize; } public void pushIndent(String beginQuote, String endQuote, int indent) { super.add(StringUtils.defaultString(beginQuote, "{")); super.add(StringUtils.defaultString(endQuote, "}")); super.add(defaultIndent(indent) + getCurrentIndent()); super.add(length()); } public void popIndent() { int length = super.size(); if (length > 0) { for (int i = 0; i < entrySize; i++) { super.remove(--length); } } } } } /** * CR/LF/CRLF?LFstring builder * * @author Michael Zhou */ abstract class NormalizableStringBuilder<B extends NormalizableStringBuilder<B>> implements Appendable { protected final static char CR = '\r'; protected final static char LF = '\n'; private final static char NONE = '\0'; private final StringBuilder out = new StringBuilder(); private final String newLine; private int newLineStartIndex = 0; private char readAheadBuffer = '\0'; public NormalizableStringBuilder() { this(null); } public NormalizableStringBuilder(String newLine) { this.newLine = StringUtils.defaultString(newLine, String.valueOf(LF)); } /** * */ public void clear() { out.setLength(0); newLineStartIndex = 0; readAheadBuffer = '\0'; } /** * ?buffer */ public final int length() { return out.length(); } /** * ?? */ public final int lineLength() { return out.length() - newLineStartIndex; } /** * <code>Appendable</code>? */ public final B append(CharSequence csq) { return append(csq, 0, csq.length()); } /** * <code>Appendable</code>? */ public final B append(CharSequence csq, int start, int end) { for (int i = start; i < end; i++) { append(csq.charAt(i)); } return thisObject(); } /** * <code>Appendable</code>? */ public final B append(char c) { // CR|LF|CRLF ? LF switch (readAheadBuffer) { case NONE: switch (c) { case CR: // \r readAheadBuffer = CR; break; case LF: // \n readAheadBuffer = NONE; visit(LF); break; default: readAheadBuffer = NONE; visit(c); break; } break; case CR: switch (c) { case CR: // \r\r readAheadBuffer = CR; visit(LF); break; case LF: // \r\n readAheadBuffer = NONE; visit(LF); break; default: readAheadBuffer = NONE; visit(LF); visit(c); break; } break; default: unreachableCode(); break; } return thisObject(); } /** * ? CR/LF/CRLF ??LF */ protected abstract void visit(char c); /** * ??buffer */ protected final void appendInternal(String s) { out.append(s); } /** * ??buffer */ protected final void appendInternal(char c) { out.append(c); } /** * ??buffer? * <p> * ????<code>newLineStartIndex</code>? * </p> */ protected final void appendInternalNewLine() { out.append(newLine); newLineStartIndex = out.length(); } /** * buf? */ public final boolean endsWith(String testStr) { if (testStr == null) { return false; } int testStrLength = testStr.length(); int bufferLength = out.length(); if (bufferLength < testStrLength) { return false; } int baseIndex = bufferLength - testStrLength; for (int i = 0; i < testStrLength; i++) { if (out.charAt(baseIndex + i) != testStr.charAt(i)) { return false; } } return true; } /** * out??buffer */ public final boolean endsWithNewLine() { return out.length() == 0 || endsWith(newLine); } private B thisObject() { @SuppressWarnings("unchecked") B buf = (B) this; return buf; } /** * ??? */ public final void flush() { if (readAheadBuffer == CR) { readAheadBuffer = NONE; visit(LF); } } @Override public final String toString() { return out.toString(); } }