cn.wjybxx.dson.text.DsonTextWriter Maven / Gradle / Ivy
/*
* Copyright 2023-2024 wjybxx([email protected])
*
* 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 cn.wjybxx.dson.text;
import cn.wjybxx.base.ObjectUtils;
import cn.wjybxx.dson.*;
import cn.wjybxx.dson.internal.CommonsLang3;
import cn.wjybxx.dson.io.DsonChunk;
import cn.wjybxx.dson.types.*;
import java.io.Writer;
import java.util.Objects;
/**
* 总指导:
* 1. token字符尽量不换行,eg:'{'、'['、'@'
* 2. token字符和内容的空格缩进尽量在行尾
*
* @author wjybxx
* date - 2023/4/21
*/
public class DsonTextWriter extends AbstractDsonWriter {
private final Writer writer;
private final DsonTextWriterSettings settings;
private DsonPrinter printer;
private final StyleOut styleOut = new StyleOut();
public DsonTextWriter(DsonTextWriterSettings settings, Writer writer) {
super(settings);
this.settings = settings;
this.writer = writer;
this.printer = new DsonPrinter(settings, writer);
setContext(new Context().init(null, DsonContextType.TOP_LEVEL, null));
}
public Writer getWriter() {
return writer;
}
@Override
protected Context getContext() {
return (Context) super.getContext();
}
@Override
protected AbstractDsonWriter.Context newContext() {
return new Context();
}
@Override
public void flush() {
printer.flush();
}
@Override
public void close() {
if (printer != null) {
printer.close();
printer = null;
}
styleOut.reset();
super.close();
}
//
// region state
private void writeCurrentName(DsonPrinter printer, DsonType dsonType) {
Context context = getContext();
// header与外层对象无缩进,且是匿名属性 -- 如果打印多个header,将保持连续
if (dsonType == DsonType.HEADER) {
assert context.count == 0;
context.headerCount++;
return;
}
// 处理value之间分隔符-换行之前
if (context.count > 0) {
printer.print(',');
}
// 先处理长度超出,再处理缩进
if (printer.getColumn() >= settings.softLineLength) {
printer.println();
}
boolean newLine = printer.getColumn() == 0;
if (context.style == ObjectStyle.INDENT) {
if (newLine) {
// 新的一行,只需缩进
printer.printIndent();
} else if (context.count == 0 || printer.getColumn() >= printer.getPrettyBodyColum()) {
// 第一个元素,或当前行超过缩进(含逗号),需要换行
printer.println();
printer.printIndent();
} else {
// 当前位置未达缩进位置,不换行
printer.printSpaces(printer.getPrettyBodyColum() - printer.getColumn());
}
} else if (!newLine && context.hasElement()) {
// 非缩进模式下,元素之间打印一个空格
printer.print(' ');
}
if (context.contextType.isLikeObject()) {
printString(printer, context.curName, StringStyle.AUTO_QUOTE);
printer.printFastPath(": ");
}
context.count++;
}
private void printString(DsonPrinter printer, String value, StringStyle style) {
final DsonTextWriterSettings settings = this.settings;
switch (style) {
case AUTO -> {
if (canPrintAsUnquote(value, settings)) {
printer.printFastPath(value);
} else if (canPrintAsText(value, settings)) {
printText(value);
} else {
printEscaped(value);
}
}
case AUTO_QUOTE -> {
if (canPrintAsUnquote(value, settings)) {
printer.printFastPath(value);
} else {
printEscaped(value);
}
}
case QUOTE -> {
printEscaped(value);
}
case UNQUOTE -> {
printer.printFastPath(value);
}
case TEXT -> {
if (settings.enableText) {
printText(value);
} else {
printEscaped(value);
}
}
case SIMPLE_TEXT -> {
printSimpleText(value);
}
case STRING_LINE -> {
printStringLine(value);
}
default -> throw new AssertionError(style);
}
}
private static boolean canPrintAsUnquote(String str, DsonTextWriterSettings settings) {
return DsonTexts.canUnquoteString(str, settings.maxLengthOfUnquoteString)
&& (!settings.unicodeChar || DsonTexts.isASCIIText(str));
}
private static boolean canPrintAsText(String str, DsonTextWriterSettings settings) {
return settings.enableText && (str.length() > settings.textStringLength);
}
/** 打印双引号String */
private void printEscaped(String text) {
boolean unicodeChar = settings.unicodeChar;
int softLineLength = settings.softLineLength;
DsonPrinter printer = this.printer;
printer.print('"');
for (int i = 0, length = text.length(); i < length; i++) {
char c = text.charAt(i);
if (Character.isSurrogate(c)) {
printer.printHpmCodePoint(c, text.charAt(++i));
} else {
printer.printEscaped(c, unicodeChar);
}
if (printer.getColumn() >= softLineLength && (i + 1 < length)) {
printer.println(); // 双引号字符串换行不能缩进
}
}
printer.print('"');
}
/** 纯文本模式打印,要执行换行符 */
private void printText(String text) {
int softLineLength = settings.softLineLength;
DsonPrinter printer = this.printer;
int headIndent;
if (settings.textAlignLeft) {
headIndent = printer.getPrettyBodyColum();
printer.println();
printer.printSpaces(headIndent);
printer.printFastPath("@\"\"\""); // 开始符
printer.println();
printer.printSpaces(headIndent);
printer.printFastPath("@- "); // 首行-避免插入换行符
} else {
headIndent = 0;
printer.println();
printer.printFastPath("@\"\"\""); // 开始符
printer.println();
printer.printFastPath("@- "); // 首行-避免插入换行符
}
for (int i = 0, length = text.length(); i < length; i++) {
char c = text.charAt(i);
// 要执行文本中的换行符
if (c == '\n' || (c == '\r' && i + 1 < length && text.charAt(i + 1) == '\n')) {
printer.println();
printer.printSpaces(headIndent);
printer.printFastPath("@| ");
continue;
}
if (Character.isSurrogate(c)) {
printer.printHpmCodePoint(c, text.charAt(++i));
} else {
printer.print(c);
}
if (printer.getColumn() >= softLineLength && (i + 1 < length)) {
printer.println();
printer.printSpaces(headIndent);
printer.printFastPath("@- ");
}
}
printer.println();
printer.printSpaces(headIndent);
printer.printFastPath("\"\"\""); // 结束符
}
private void printSimpleText(String text) {
DsonPrinter printer = this.printer;
int headIndent;
if (settings.textAlignLeft) {
headIndent = printer.getPrettyBodyColum();
printer.println();
printer.printSpaces(headIndent);
printer.printFastPath("\"\"\""); // 开始符
printer.println();
printer.printSpaces(headIndent);
} else {
headIndent = 0;
printer.println();
printer.printFastPath("\"\"\""); // 开始符
printer.println();
}
for (int i = 0, length = text.length(); i < length; i++) {
char c = text.charAt(i);
// 要执行文本中的换行符
if (c == '\n' || (c == '\r' && i + 1 < length && text.charAt(i + 1) == '\n')) {
printer.println();
printer.printSpaces(headIndent);
continue;
}
if (Character.isSurrogate(c)) {
printer.printHpmCodePoint(c, text.charAt(++i));
} else {
printer.print(c);
}
}
printer.println();
printer.printSpaces(headIndent);
printer.printFastPath("\"\"\""); // 结束符
}
private void printStringLine(String text) {
DsonPrinter printer = this.printer;
printer.printFastPath("@sL ");
printer.print(text);
printer.println(); // 换行表示结束
}
private void printBinary(byte[] buffer, int offset, int length) {
DsonPrinter printer = this.printer;
int softLineLength = this.settings.softLineLength;
// 使用小buffer多次编码代替大的buffer,一方面节省内存,一方面控制行长度
int segment = 8;
char[] cBuffer = new char[segment * 2];
int loop = length / segment;
for (int i = 0; i < loop; i++) {
checkLineLength(printer, softLineLength);
CommonsLang3.encodeHex(buffer, offset + i * segment, segment, cBuffer, 0);
printer.printFastPath(cBuffer, 0, cBuffer.length);
}
int remain = length - loop * segment;
if (remain > 0) {
checkLineLength(printer, softLineLength);
CommonsLang3.encodeHex(buffer, offset + loop * segment, remain, cBuffer, 0);
printer.printFastPath(cBuffer, 0, remain * 2);
}
}
private void checkLineLength(DsonPrinter printer, int softLineLength) {
if (printer.getColumn() >= softLineLength) {
printer.println();
}
}
// endregion
// region 简单值
@Override
protected void doWriteInt32(int value, WireType wireType, INumberStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.INT32);
style.toString(value, styleOut.reset());
if (styleOut.isTyped()) {
printer.printFastPath("@i ");
}
printer.printFastPath(styleOut.getValue());
}
@Override
protected void doWriteInt64(long value, WireType wireType, INumberStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.INT64);
style.toString(value, styleOut.reset());
if (styleOut.isTyped()) {
printer.printFastPath("@L ");
}
printer.printFastPath(styleOut.getValue());
}
@Override
protected void doWriteFloat(float value, INumberStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.FLOAT);
style.toString(value, styleOut.reset());
if (styleOut.isTyped()) {
printer.printFastPath("@f ");
}
printer.printFastPath(styleOut.getValue());
}
@Override
protected void doWriteDouble(double value, INumberStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.DOUBLE);
style.toString(value, styleOut.reset());
if (styleOut.isTyped()) {
printer.printFastPath("@d ");
}
printer.printFastPath(styleOut.getValue());
}
@Override
protected void doWriteBool(boolean value) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.BOOLEAN);
printer.printFastPath(value ? "true" : "false");
}
@Override
protected void doWriteString(String value, StringStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.STRING);
printString(printer, value, style);
}
@Override
protected void doWriteNull() {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.NULL);
printer.printFastPath("null");
}
@Override
protected void doWriteBinary(Binary binary) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.BINARY);
printer.printFastPath("[@bin ");
printer.printFastPath(Integer.toString(binary.getType()));
printer.printFastPath(", ");
printBinary(binary.getData(), 0, binary.getData().length);
printer.print(']');
}
@Override
protected void doWriteBinary(int type, DsonChunk chunk) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.BINARY);
printer.printFastPath("[@bin ");
printer.printFastPath(Integer.toString(type));
printer.printFastPath(", ");
printBinary(chunk.getBuffer(), chunk.getOffset(), chunk.getLength());
printer.print(']');
}
@Override
protected void doWriteExtInt32(ExtInt32 extInt32, WireType wireType, INumberStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.EXT_INT32);
printer.printFastPath("[@ei ");
printer.printFastPath(Integer.toString(extInt32.getType()));
printer.printFastPath(", ");
if (extInt32.hasValue()) {
style.toString(extInt32.getValue(), styleOut.reset());
printer.printFastPath(styleOut.getValue());
} else {
printer.printFastPath("null");
}
printer.print(']');
}
@Override
protected void doWriteExtInt64(ExtInt64 extInt64, WireType wireType, INumberStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.EXT_INT64);
printer.printFastPath("[@eL ");
printer.printFastPath(Integer.toString(extInt64.getType()));
printer.printFastPath(", ");
if (extInt64.hasValue()) {
style.toString(extInt64.getValue(), styleOut.reset());
printer.printFastPath(styleOut.getValue());
} else {
printer.printFastPath("null");
}
printer.print(']');
}
@Override
protected void doWriteExtDouble(ExtDouble extDouble, INumberStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.EXT_DOUBLE);
printer.printFastPath("[@ed ");
printer.printFastPath(Integer.toString(extDouble.getType()));
printer.printFastPath(", ");
if (extDouble.hasValue()) {
style.toString(extDouble.getValue(), styleOut.reset());
printer.printFastPath(styleOut.getValue());
} else {
printer.printFastPath("null");
}
printer.print(']');
}
@Override
protected void doWriteExtString(ExtString extString, StringStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.EXT_STRING);
printer.printFastPath("[@es ");
printer.printFastPath(Integer.toString(extString.getType()));
printer.printFastPath(", ");
if (extString.hasValue()) {
printString(printer, extString.getValue(), style);
} else {
printer.printFastPath("null");
}
printer.print(']');
}
@Override
protected void doWriteRef(ObjectRef objectRef) {
DsonPrinter printer = this.printer;
int softLineLength = this.settings.softLineLength;
writeCurrentName(printer, DsonType.REFERENCE);
if (ObjectUtils.isBlank(objectRef.getNamespace())
&& objectRef.getType() == 0 && objectRef.getPolicy() == 0) {
printer.printFastPath("@ref "); // 只有localId时简写
printString(printer, objectRef.getLocalId(), StringStyle.AUTO_QUOTE);
return;
}
printer.printFastPath("{@ref ");
int count = 0;
if (objectRef.hasNamespace()) {
count++;
printer.printFastPath(ObjectRef.NAMES_NAMESPACE);
printer.printFastPath(": ");
printString(printer, objectRef.getNamespace(), StringStyle.AUTO_QUOTE);
}
if (objectRef.hasLocalId()) {
if (count++ > 0) printer.printFastPath(", ");
checkLineLength(printer, softLineLength);
printer.printFastPath(ObjectRef.NAMES_LOCAL_ID);
printer.printFastPath(": ");
printString(printer, objectRef.getLocalId(), StringStyle.AUTO_QUOTE);
}
if (objectRef.getType() != 0) {
if (count++ > 0) printer.printFastPath(", ");
checkLineLength(printer, softLineLength);
printer.printFastPath(ObjectRef.NAMES_TYPE);
printer.printFastPath(": ");
printer.printFastPath(Integer.toString(objectRef.getType()));
}
if (objectRef.getPolicy() != 0) {
if (count > 0) printer.printFastPath(", ");
checkLineLength(printer, softLineLength);
printer.printFastPath(ObjectRef.NAMES_POLICY);
printer.printFastPath(": ");
printer.printFastPath(Integer.toString(objectRef.getPolicy()));
}
printer.print('}');
}
@Override
protected void doWriteTimestamp(OffsetTimestamp timestamp) {
DsonPrinter printer = this.printer;
int softLineLength = this.settings.softLineLength;
writeCurrentName(printer, DsonType.TIMESTAMP);
if (timestamp.getEnables() == OffsetTimestamp.MASK_DATETIME) {
printer.printFastPath("@dt ");
printer.printFastPath(OffsetTimestamp.formatDateTime(timestamp.getSeconds()));
return;
}
printer.printFastPath("{@dt ");
if (timestamp.hasDate()) {
printer.printFastPath(OffsetTimestamp.NAMES_DATE);
printer.printFastPath(": ");
printer.printFastPath(OffsetTimestamp.formatDate(timestamp.getSeconds()));
}
if (timestamp.hasTime()) {
if (timestamp.hasDate()) printer.printFastPath(", ");
checkLineLength(printer, softLineLength);
printer.printFastPath(OffsetTimestamp.NAMES_TIME);
printer.printFastPath(": ");
printer.printFastPath(OffsetTimestamp.formatTime(timestamp.getSeconds()));
}
if (timestamp.getNanos() > 0) {
printer.printFastPath(", ");
checkLineLength(printer, softLineLength);
if (timestamp.canConvertNanosToMillis()) {
printer.printFastPath(OffsetTimestamp.NAMES_MILLIS);
printer.printFastPath(": ");
printer.printFastPath(Integer.toString(timestamp.convertNanosToMillis()));
} else {
printer.printFastPath(OffsetTimestamp.NAMES_NANOS);
printer.printFastPath(": ");
printer.printFastPath(Integer.toString(timestamp.getNanos()));
}
}
if (timestamp.hasOffset()) {
printer.printFastPath(", ");
checkLineLength(printer, softLineLength);
printer.printFastPath(OffsetTimestamp.NAMES_OFFSET);
printer.printFastPath(": ");
printer.printFastPath(OffsetTimestamp.formatOffset(timestamp.getOffset()));
}
printer.print('}');
}
// endregion
// region 容器
@Override
protected void doWriteStartContainer(DsonContextType contextType, DsonType dsonType, ObjectStyle style) {
DsonPrinter printer = this.printer;
writeCurrentName(printer, dsonType);
Context context = getContext();
if (context.style == ObjectStyle.FLOW) {
style = ObjectStyle.FLOW;
}
Context newContext = newContext(context, contextType, dsonType);
newContext.style = style;
printer.printFastPath(contextType.startSymbol);
if (style == ObjectStyle.INDENT) {
printer.indent(); // 调整缩进
}
setContext(newContext);
this.recursionDepth++;
}
@Override
protected void doWriteEndContainer() {
Context context = getContext();
DsonPrinter printer = this.printer;
if (context.style == ObjectStyle.INDENT) {
printer.retract(); // 恢复缩进
// 打印了内容的情况下才换行结束
if (context.hasElement() && printer.getColumn() > printer.getPrettyBodyColum()) {
printer.println();
printer.printIndent();
}
}
printer.printFastPath(context.contextType.endSymbol);
this.recursionDepth--;
setContext(context.parent);
returnContext(context);
}
// endregion
// region 特殊接口
@Override
public void writeSimpleHeader(String clsName) {
Objects.requireNonNull(clsName, "clsName");
Context context = getContext();
if (context.contextType == DsonContextType.OBJECT && context.state == DsonWriterState.NAME) {
context.setState(DsonWriterState.VALUE);
}
autoStartTopLevel(context);
ensureValueState(context);
DsonPrinter printer = this.printer;
writeCurrentName(printer, DsonType.HEADER);
// header总是使用 @{} 包起来,提高辨识度
printer.print("@{");
printString(printer, clsName, StringStyle.AUTO_QUOTE);
printer.print('}');
setNextState();
}
@Override
protected void doWriteValueBytes(DsonType type, byte[] data) {
throw new UnsupportedOperationException();
}
// endregion
// region context
private Context newContext(Context parent, DsonContextType contextType, DsonType dsonType) {
Context context = (Context) rentContext();
context.init(parent, contextType, dsonType);
return context;
}
protected static class Context extends AbstractDsonWriter.Context {
ObjectStyle style = ObjectStyle.INDENT;
int headerCount = 0;
int count = 0;
public Context() {
}
boolean hasElement() {
return headerCount > 0 || count > 0;
}
public void reset() {
super.reset();
style = ObjectStyle.INDENT;
headerCount = 0;
count = 0;
}
@Override
public Context getParent() {
return (Context) parent;
}
}
// endregion
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy