diff --git a/patches/server/0842-Fix-saving-configs-with-more-long-comments.patch b/patches/server/0842-Fix-saving-configs-with-more-long-comments.patch new file mode 100644 index 0000000000..7d52b01085 --- /dev/null +++ b/patches/server/0842-Fix-saving-configs-with-more-long-comments.patch @@ -0,0 +1,1702 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 1 Jan 2022 21:24:50 -0800 +Subject: [PATCH] Fix saving configs with more long comments + +This is a bug with snakeyaml +PR: https://bitbucket.org/snakeyaml/snakeyaml/pull-requests/3 +Issue: https://bitbucket.org/snakeyaml/snakeyaml/issues/518/comments-could-cause-queue-full + +Added the entire Emitter class from snakeyaml because +dev-imports doesn't work with non-mojang-added dependencies + +Replacement for upstream: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/a7505b3cd0498baca152777767f0e4ddebbe4d1a + +diff --git a/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ca32c00775f3d98abea39bbfeb0268bb9efbc12b +--- /dev/null ++++ b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java +@@ -0,0 +1,1682 @@ ++/** ++ * Copyright (c) 2008, SnakeYAML ++ * ++ * 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.yaml.snakeyaml.emitter; ++ ++import org.yaml.snakeyaml.DumperOptions; ++import org.yaml.snakeyaml.DumperOptions.ScalarStyle; ++import org.yaml.snakeyaml.DumperOptions.Version; ++import org.yaml.snakeyaml.comments.CommentEventsCollector; ++import org.yaml.snakeyaml.comments.CommentLine; ++import org.yaml.snakeyaml.comments.CommentType; ++import org.yaml.snakeyaml.error.YAMLException; ++import org.yaml.snakeyaml.events.AliasEvent; ++import org.yaml.snakeyaml.events.CollectionEndEvent; ++import org.yaml.snakeyaml.events.CollectionStartEvent; ++import org.yaml.snakeyaml.events.CommentEvent; ++import org.yaml.snakeyaml.events.DocumentEndEvent; ++import org.yaml.snakeyaml.events.DocumentStartEvent; ++import org.yaml.snakeyaml.events.Event; ++import org.yaml.snakeyaml.events.Event.ID; ++import org.yaml.snakeyaml.events.MappingEndEvent; ++import org.yaml.snakeyaml.events.MappingStartEvent; ++import org.yaml.snakeyaml.events.NodeEvent; ++import org.yaml.snakeyaml.events.ScalarEvent; ++import org.yaml.snakeyaml.events.SequenceEndEvent; ++import org.yaml.snakeyaml.events.SequenceStartEvent; ++import org.yaml.snakeyaml.events.StreamEndEvent; ++import org.yaml.snakeyaml.events.StreamStartEvent; ++import org.yaml.snakeyaml.nodes.Tag; ++import org.yaml.snakeyaml.reader.StreamReader; ++import org.yaml.snakeyaml.scanner.Constant; ++import org.yaml.snakeyaml.util.ArrayStack; ++ ++import java.io.IOException; ++import java.io.Writer; ++import java.util.ArrayDeque; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.LinkedHashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Queue; ++import java.util.Set; ++import java.util.TreeSet; ++import java.util.concurrent.ArrayBlockingQueue; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++/** ++ *
++ * Emitter expects events obeying the following grammar:
++ * stream ::= STREAM-START document* STREAM-END
++ * document ::= DOCUMENT-START node DOCUMENT-END
++ * node ::= SCALAR | sequence | mapping
++ * sequence ::= SEQUENCE-START node* SEQUENCE-END
++ * mapping ::= MAPPING-START (node node)* MAPPING-END
++ * 
++ */ ++public final class Emitter implements Emitable { ++ public static final int MIN_INDENT = 1; ++ public static final int MAX_INDENT = 10; ++ private static final char[] SPACE = {' '}; ++ ++ private static final Pattern SPACES_PATTERN = Pattern.compile("\\s"); ++ private static final Set INVALID_ANCHOR = new HashSet(); ++ static { ++ INVALID_ANCHOR.add('['); ++ INVALID_ANCHOR.add(']'); ++ INVALID_ANCHOR.add('{'); ++ INVALID_ANCHOR.add('}'); ++ INVALID_ANCHOR.add(','); ++ INVALID_ANCHOR.add('*'); ++ INVALID_ANCHOR.add('&'); ++ } ++ ++ private static final Map ESCAPE_REPLACEMENTS = new HashMap(); ++ static { ++ ESCAPE_REPLACEMENTS.put('\0', "0"); ++ ESCAPE_REPLACEMENTS.put('\u0007', "a"); ++ ESCAPE_REPLACEMENTS.put('\u0008', "b"); ++ ESCAPE_REPLACEMENTS.put('\u0009', "t"); ++ ESCAPE_REPLACEMENTS.put('\n', "n"); ++ ESCAPE_REPLACEMENTS.put('\u000B', "v"); ++ ESCAPE_REPLACEMENTS.put('\u000C', "f"); ++ ESCAPE_REPLACEMENTS.put('\r', "r"); ++ ESCAPE_REPLACEMENTS.put('\u001B', "e"); ++ ESCAPE_REPLACEMENTS.put('"', "\""); ++ ESCAPE_REPLACEMENTS.put('\\', "\\"); ++ ESCAPE_REPLACEMENTS.put('\u0085', "N"); ++ ESCAPE_REPLACEMENTS.put('\u00A0', "_"); ++ ESCAPE_REPLACEMENTS.put('\u2028', "L"); ++ ESCAPE_REPLACEMENTS.put('\u2029', "P"); ++ } ++ ++ private final static Map DEFAULT_TAG_PREFIXES = new LinkedHashMap(); ++ static { ++ DEFAULT_TAG_PREFIXES.put("!", "!"); ++ DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!"); ++ } ++ // The stream should have the methods `write` and possibly `flush`. ++ private final Writer stream; ++ ++ // Encoding is defined by Writer (cannot be overridden by STREAM-START.) ++ // private Charset encoding; ++ ++ // Emitter is a state machine with a stack of states to handle nested ++ // structures. ++ private final ArrayStack states; ++ private EmitterState state; ++ ++ // Current event and the event queue. ++ private final Queue events; ++ private Event event; ++ ++ // The current indentation level and the stack of previous indents. ++ private final ArrayStack indents; ++ private Integer indent; ++ ++ // Flow level. ++ private int flowLevel; ++ ++ // Contexts. ++ private boolean rootContext; ++ private boolean mappingContext; ++ private boolean simpleKeyContext; ++ ++ // ++ // Characteristics of the last emitted character: ++ // - current position. ++ // - is it a whitespace? ++ // - is it an indention character ++ // (indentation space, '-', '?', or ':')? ++ // private int line; this variable is not used ++ private int column; ++ private boolean whitespace; ++ private boolean indention; ++ private boolean openEnded; ++ ++ // Formatting details. ++ private final Boolean canonical; ++ // pretty print flow by adding extra line breaks ++ private final Boolean prettyFlow; ++ ++ private final boolean allowUnicode; ++ private int bestIndent; ++ private final int indicatorIndent; ++ private final boolean indentWithIndicator; ++ private int bestWidth; ++ private final char[] bestLineBreak; ++ private final boolean splitLines; ++ private final int maxSimpleKeyLength; ++ private final boolean emitComments; ++ ++ // Tag prefixes. ++ private Map tagPrefixes; ++ ++ // Prepared anchor and tag. ++ private String preparedAnchor; ++ private String preparedTag; ++ ++ // Scalar analysis and style. ++ private ScalarAnalysis analysis; ++ private DumperOptions.ScalarStyle style; ++ ++ // Comment processing ++ private final CommentEventsCollector blockCommentsCollector; ++ private final CommentEventsCollector inlineCommentsCollector; ++ ++ ++ public Emitter(Writer stream, DumperOptions opts) { ++ // The stream should have the methods `write` and possibly `flush`. ++ this.stream = stream; ++ // Emitter is a state machine with a stack of states to handle nested ++ // structures. ++ this.states = new ArrayStack(100); ++ this.state = new ExpectStreamStart(); ++ // Current event and the event queue. ++ this.events = new ArrayDeque<>(100); // Paper - allow more than 100 events (or comments) ++ // this.events = new ArrayBlockingQueue<>(100); ++ this.event = null; ++ // The current indentation level and the stack of previous indents. ++ this.indents = new ArrayStack(10); ++ this.indent = null; ++ // Flow level. ++ this.flowLevel = 0; ++ // Contexts. ++ mappingContext = false; ++ simpleKeyContext = false; ++ ++ // ++ // Characteristics of the last emitted character: ++ // - current position. ++ // - is it a whitespace? ++ // - is it an indention character ++ // (indentation space, '-', '?', or ':')? ++ column = 0; ++ whitespace = true; ++ indention = true; ++ ++ // Whether the document requires an explicit document indicator ++ openEnded = false; ++ ++ // Formatting details. ++ this.canonical = opts.isCanonical(); ++ this.prettyFlow = opts.isPrettyFlow(); ++ this.allowUnicode = opts.isAllowUnicode(); ++ this.bestIndent = 2; ++ if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) { ++ this.bestIndent = opts.getIndent(); ++ } ++ this.indicatorIndent = opts.getIndicatorIndent(); ++ this.indentWithIndicator = opts.getIndentWithIndicator(); ++ this.bestWidth = 80; ++ if (opts.getWidth() > this.bestIndent * 2) { ++ this.bestWidth = opts.getWidth(); ++ } ++ this.bestLineBreak = opts.getLineBreak().getString().toCharArray(); ++ this.splitLines = opts.getSplitLines(); ++ this.maxSimpleKeyLength = opts.getMaxSimpleKeyLength(); ++ this.emitComments = opts.isProcessComments(); ++ ++ // Tag prefixes. ++ this.tagPrefixes = new LinkedHashMap(); ++ ++ // Prepared anchor and tag. ++ this.preparedAnchor = null; ++ this.preparedTag = null; ++ ++ // Scalar analysis and style. ++ this.analysis = null; ++ this.style = null; ++ ++ // Comment processing ++ this.blockCommentsCollector = new CommentEventsCollector(events, ++ CommentType.BLANK_LINE, CommentType.BLOCK); ++ this.inlineCommentsCollector = new CommentEventsCollector(events, ++ CommentType.IN_LINE); ++ } ++ ++ public void emit(Event event) throws IOException { ++ this.events.add(event); ++ while (!needMoreEvents()) { ++ this.event = this.events.poll(); ++ this.state.expect(); ++ this.event = null; ++ } ++ } ++ ++ // In some cases, we wait for a few next events before emitting. ++ ++ private boolean needMoreEvents() { ++ if (events.isEmpty()) { ++ return true; ++ } ++ ++ Iterator iter = events.iterator(); ++ Event event = iter.next(); // FIXME why without check ??? ++ while(event instanceof CommentEvent) { ++ if (!iter.hasNext()) { ++ return true; ++ } ++ event = iter.next(); ++ } ++ ++ if (event instanceof DocumentStartEvent) { ++ return needEvents(iter, 1); ++ } else if (event instanceof SequenceStartEvent) { ++ return needEvents(iter, 2); ++ } else if (event instanceof MappingStartEvent) { ++ return needEvents(iter, 3); ++ } else if (event instanceof StreamStartEvent) { ++ return needEvents(iter, 2); ++ } else if (event instanceof StreamEndEvent) { ++ return false; ++ } else if (emitComments) { ++ return needEvents(iter, 1); ++ } ++ return false; ++ } ++ ++ private boolean needEvents(Iterator iter, int count) { ++ int level = 0; ++ int actualCount = 0; ++ while (iter.hasNext()) { ++ Event event = iter.next(); ++ if (event instanceof CommentEvent) { ++ continue; ++ } ++ actualCount++; ++ if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) { ++ level++; ++ } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) { ++ level--; ++ } else if (event instanceof StreamEndEvent) { ++ level = -1; ++ } ++ if (level < 0) { ++ return false; ++ } ++ } ++ return actualCount < count; ++ } ++ ++ private void increaseIndent(boolean flow, boolean indentless) { ++ indents.push(indent); ++ if (indent == null) { ++ if (flow) { ++ indent = bestIndent; ++ } else { ++ indent = 0; ++ } ++ } else if (!indentless) { ++ this.indent += bestIndent; ++ } ++ } ++ ++ // States ++ ++ // Stream handlers. ++ ++ private class ExpectStreamStart implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof StreamStartEvent) { ++ writeStreamStart(); ++ state = new ExpectFirstDocumentStart(); ++ } else { ++ throw new EmitterException("expected StreamStartEvent, but got " + event); ++ } ++ } ++ } ++ ++ private class ExpectNothing implements EmitterState { ++ public void expect() throws IOException { ++ throw new EmitterException("expecting nothing, but got " + event); ++ } ++ } ++ ++ // Document handlers. ++ ++ private class ExpectFirstDocumentStart implements EmitterState { ++ public void expect() throws IOException { ++ new ExpectDocumentStart(true).expect(); ++ } ++ } ++ ++ private class ExpectDocumentStart implements EmitterState { ++ private final boolean first; ++ ++ public ExpectDocumentStart(boolean first) { ++ this.first = first; ++ } ++ ++ public void expect() throws IOException { ++ if (event instanceof DocumentStartEvent) { ++ DocumentStartEvent ev = (DocumentStartEvent) event; ++ if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) { ++ writeIndicator("...", true, false, false); ++ writeIndent(); ++ } ++ if (ev.getVersion() != null) { ++ String versionText = prepareVersion(ev.getVersion()); ++ writeVersionDirective(versionText); ++ } ++ tagPrefixes = new LinkedHashMap(DEFAULT_TAG_PREFIXES); ++ if (ev.getTags() != null) { ++ Set handles = new TreeSet(ev.getTags().keySet()); ++ for (String handle : handles) { ++ String prefix = ev.getTags().get(handle); ++ tagPrefixes.put(prefix, handle); ++ String handleText = prepareTagHandle(handle); ++ String prefixText = prepareTagPrefix(prefix); ++ writeTagDirective(handleText, prefixText); ++ } ++ } ++ boolean implicit = first && !ev.getExplicit() && !canonical ++ && ev.getVersion() == null ++ && (ev.getTags() == null || ev.getTags().isEmpty()) ++ && !checkEmptyDocument(); ++ if (!implicit) { ++ writeIndent(); ++ writeIndicator("---", true, false, false); ++ if (canonical) { ++ writeIndent(); ++ } ++ } ++ state = new ExpectDocumentRoot(); ++ } else if (event instanceof StreamEndEvent) { ++ writeStreamEnd(); ++ state = new ExpectNothing(); ++ } else if (event instanceof CommentEvent) { ++ blockCommentsCollector.collectEvents(event); ++ writeBlockComment(); ++ // state = state; remains unchanged ++ } else { ++ throw new EmitterException("expected DocumentStartEvent, but got " + event); ++ } ++ } ++ } ++ ++ private class ExpectDocumentEnd implements EmitterState { ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (event instanceof DocumentEndEvent) { ++ writeIndent(); ++ if (((DocumentEndEvent) event).getExplicit()) { ++ writeIndicator("...", true, false, false); ++ writeIndent(); ++ } ++ flushStream(); ++ state = new ExpectDocumentStart(false); ++ } else { ++ throw new EmitterException("expected DocumentEndEvent, but got " + event); ++ } ++ } ++ } ++ ++ private class ExpectDocumentRoot implements EmitterState { ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ if (!blockCommentsCollector.isEmpty()) { ++ writeBlockComment(); ++ if (event instanceof DocumentEndEvent) { ++ new ExpectDocumentEnd().expect(); ++ return; ++ } ++ } ++ states.push(new ExpectDocumentEnd()); ++ expectNode(true, false, false); ++ } ++ } ++ ++ // Node handlers. ++ ++ private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException { ++ rootContext = root; ++ mappingContext = mapping; ++ simpleKeyContext = simpleKey; ++ if (event instanceof AliasEvent) { ++ expectAlias(); ++ } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) { ++ processAnchor("&"); ++ processTag(); ++ if (event instanceof ScalarEvent) { ++ expectScalar(); ++ } else if (event instanceof SequenceStartEvent) { ++ if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).isFlow() ++ || checkEmptySequence()) { ++ expectFlowSequence(); ++ } else { ++ expectBlockSequence(); ++ } ++ } else {// MappingStartEvent ++ if (flowLevel != 0 || canonical || ((MappingStartEvent) event).isFlow() ++ || checkEmptyMapping()) { ++ expectFlowMapping(); ++ } else { ++ expectBlockMapping(); ++ } ++ } ++ } else { ++ throw new EmitterException("expected NodeEvent, but got " + event); ++ } ++ } ++ ++ private void expectAlias() throws IOException { ++ if (!(event instanceof AliasEvent)) { ++ throw new EmitterException("Alias must be provided"); ++ } ++ processAnchor("*"); ++ state = states.pop(); ++ } ++ ++ private void expectScalar() throws IOException { ++ increaseIndent(true, false); ++ processScalar(); ++ indent = indents.pop(); ++ state = states.pop(); ++ } ++ ++ // Flow sequence handlers. ++ ++ private void expectFlowSequence() throws IOException { ++ writeIndicator("[", true, true, false); ++ flowLevel++; ++ increaseIndent(true, false); ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ state = new ExpectFirstFlowSequenceItem(); ++ } ++ ++ private class ExpectFirstFlowSequenceItem implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof SequenceEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ writeIndicator("]", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ state = states.pop(); ++ } else if (event instanceof CommentEvent) { ++ blockCommentsCollector.collectEvents(event); ++ writeBlockComment(); ++ } else { ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ states.push(new ExpectFlowSequenceItem()); ++ expectNode(false, false, false); ++ event = inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ } ++ ++ private class ExpectFlowSequenceItem implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof SequenceEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ if (canonical) { ++ writeIndicator(",", false, false, false); ++ writeIndent(); ++ } else if (prettyFlow) { ++ writeIndent(); ++ } ++ writeIndicator("]", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ state = states.pop(); ++ } else if (event instanceof CommentEvent) { ++ event = blockCommentsCollector.collectEvents(event); ++ } else { ++ writeIndicator(",", false, false, false); ++ writeBlockComment(); ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ states.push(new ExpectFlowSequenceItem()); ++ expectNode(false, false, false); ++ event = inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ } ++ ++ // Flow mapping handlers. ++ ++ private void expectFlowMapping() throws IOException { ++ writeIndicator("{", true, true, false); ++ flowLevel++; ++ increaseIndent(true, false); ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ state = new ExpectFirstFlowMappingKey(); ++ } ++ ++ private class ExpectFirstFlowMappingKey implements EmitterState { ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (event instanceof MappingEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ writeIndicator("}", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ state = states.pop(); ++ } else { ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ if (!canonical && checkSimpleKey()) { ++ states.push(new ExpectFlowMappingSimpleValue()); ++ expectNode(false, true, true); ++ } else { ++ writeIndicator("?", true, false, false); ++ states.push(new ExpectFlowMappingValue()); ++ expectNode(false, true, false); ++ } ++ } ++ } ++ } ++ ++ private class ExpectFlowMappingKey implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof MappingEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ if (canonical) { ++ writeIndicator(",", false, false, false); ++ writeIndent(); ++ } ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ writeIndicator("}", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ state = states.pop(); ++ } else { ++ writeIndicator(",", false, false, false); ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ if (!canonical && checkSimpleKey()) { ++ states.push(new ExpectFlowMappingSimpleValue()); ++ expectNode(false, true, true); ++ } else { ++ writeIndicator("?", true, false, false); ++ states.push(new ExpectFlowMappingValue()); ++ expectNode(false, true, false); ++ } ++ } ++ } ++ } ++ ++ private class ExpectFlowMappingSimpleValue implements EmitterState { ++ public void expect() throws IOException { ++ writeIndicator(":", false, false, false); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ writeInlineComments(); ++ states.push(new ExpectFlowMappingKey()); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ ++ private class ExpectFlowMappingValue implements EmitterState { ++ public void expect() throws IOException { ++ if (canonical || (column > bestWidth) || prettyFlow) { ++ writeIndent(); ++ } ++ writeIndicator(":", true, false, false); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ writeInlineComments(); ++ states.push(new ExpectFlowMappingKey()); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ ++ // Block sequence handlers. ++ ++ private void expectBlockSequence() throws IOException { ++ boolean indentless = mappingContext && !indention; ++ increaseIndent(false, indentless); ++ state = new ExpectFirstBlockSequenceItem(); ++ } ++ ++ private class ExpectFirstBlockSequenceItem implements EmitterState { ++ public void expect() throws IOException { ++ new ExpectBlockSequenceItem(true).expect(); ++ } ++ } ++ ++ private class ExpectBlockSequenceItem implements EmitterState { ++ private final boolean first; ++ ++ public ExpectBlockSequenceItem(boolean first) { ++ this.first = first; ++ } ++ ++ public void expect() throws IOException { ++ if (!this.first && event instanceof SequenceEndEvent) { ++ indent = indents.pop(); ++ state = states.pop(); ++ } else if( event instanceof CommentEvent) { ++ blockCommentsCollector.collectEvents(event); ++ } else { ++ writeIndent(); ++ if (!indentWithIndicator || this.first) { ++ writeWhitespace(indicatorIndent); ++ } ++ writeIndicator("-", true, false, true); ++ if (indentWithIndicator && this.first) { ++ indent += indicatorIndent; ++ } ++ if (!blockCommentsCollector.isEmpty()) { ++ increaseIndent(false, false); ++ writeBlockComment(); ++ if(event instanceof ScalarEvent) { ++ analysis = analyzeScalar(((ScalarEvent)event).getValue()); ++ if (!analysis.isEmpty()) { ++ writeIndent(); ++ } ++ } ++ indent = indents.pop(); ++ } ++ states.push(new ExpectBlockSequenceItem(false)); ++ expectNode(false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ } ++ } ++ } ++ ++ // Block mapping handlers. ++ private void expectBlockMapping() throws IOException { ++ increaseIndent(false, false); ++ state = new ExpectFirstBlockMappingKey(); ++ } ++ ++ private class ExpectFirstBlockMappingKey implements EmitterState { ++ public void expect() throws IOException { ++ new ExpectBlockMappingKey(true).expect(); ++ } ++ } ++ ++ private class ExpectBlockMappingKey implements EmitterState { ++ private final boolean first; ++ ++ public ExpectBlockMappingKey(boolean first) { ++ this.first = first; ++ } ++ ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (!this.first && event instanceof MappingEndEvent) { ++ indent = indents.pop(); ++ state = states.pop(); ++ } else { ++ writeIndent(); ++ if (checkSimpleKey()) { ++ states.push(new ExpectBlockMappingSimpleValue()); ++ expectNode(false, true, true); ++ } else { ++ writeIndicator("?", true, false, true); ++ states.push(new ExpectBlockMappingValue()); ++ expectNode(false, true, false); ++ } ++ } ++ } ++ } ++ ++ private boolean isFoldedOrLiteral(Event event) { ++ if(!event.is(ID.Scalar)) { ++ return false; ++ } ++ ScalarEvent scalarEvent = (ScalarEvent) event; ++ ScalarStyle style = scalarEvent.getScalarStyle(); ++ return style == ScalarStyle.FOLDED || style == ScalarStyle.LITERAL; ++ } ++ ++ private class ExpectBlockMappingSimpleValue implements EmitterState { ++ public void expect() throws IOException { ++ writeIndicator(":", false, false, false); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ if(!isFoldedOrLiteral(event)) { ++ if(writeInlineComments()) { ++ increaseIndent(true, false); ++ writeIndent(); ++ indent = indents.pop(); ++ } ++ } ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ if(!blockCommentsCollector.isEmpty()) { ++ increaseIndent(true, false); ++ writeBlockComment(); ++ writeIndent(); ++ indent = indents.pop(); ++ } ++ states.push(new ExpectBlockMappingKey(false)); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ } ++ } ++ ++ private class ExpectBlockMappingValue implements EmitterState { ++ public void expect() throws IOException { ++ writeIndent(); ++ writeIndicator(":", true, false, true); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ writeInlineComments(); ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ states.push(new ExpectBlockMappingKey(false)); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ ++ // Checkers. ++ ++ private boolean checkEmptySequence() { ++ return event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent; ++ } ++ ++ private boolean checkEmptyMapping() { ++ return event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent; ++ } ++ ++ private boolean checkEmptyDocument() { ++ if (!(event instanceof DocumentStartEvent) || events.isEmpty()) { ++ return false; ++ } ++ Event event = events.peek(); ++ if (event instanceof ScalarEvent) { ++ ScalarEvent e = (ScalarEvent) event; ++ return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e ++ .getValue().length() == 0; ++ } ++ return false; ++ } ++ ++ private boolean checkSimpleKey() { ++ int length = 0; ++ if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) { ++ if (preparedAnchor == null) { ++ preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor()); ++ } ++ length += preparedAnchor.length(); ++ } ++ String tag = null; ++ if (event instanceof ScalarEvent) { ++ tag = ((ScalarEvent) event).getTag(); ++ } else if (event instanceof CollectionStartEvent) { ++ tag = ((CollectionStartEvent) event).getTag(); ++ } ++ if (tag != null) { ++ if (preparedTag == null) { ++ preparedTag = prepareTag(tag); ++ } ++ length += preparedTag.length(); ++ } ++ if (event instanceof ScalarEvent) { ++ if (analysis == null) { ++ analysis = analyzeScalar(((ScalarEvent) event).getValue()); ++ } ++ length += analysis.getScalar().length(); ++ } ++ return length < maxSimpleKeyLength && (event instanceof AliasEvent ++ || (event instanceof ScalarEvent && !analysis.isEmpty() && !analysis.isMultiline()) ++ || checkEmptySequence() || checkEmptyMapping()); ++ } ++ ++ // Anchor, Tag, and Scalar processors. ++ ++ private void processAnchor(String indicator) throws IOException { ++ NodeEvent ev = (NodeEvent) event; ++ if (ev.getAnchor() == null) { ++ preparedAnchor = null; ++ return; ++ } ++ if (preparedAnchor == null) { ++ preparedAnchor = prepareAnchor(ev.getAnchor()); ++ } ++ writeIndicator(indicator + preparedAnchor, true, false, false); ++ preparedAnchor = null; ++ } ++ ++ private void processTag() throws IOException { ++ String tag = null; ++ if (event instanceof ScalarEvent) { ++ ScalarEvent ev = (ScalarEvent) event; ++ tag = ev.getTag(); ++ if (style == null) { ++ style = chooseScalarStyle(); ++ } ++ if ((!canonical || tag == null) && ((style == null && ev.getImplicit() ++ .canOmitTagInPlainScalar()) || (style != null && ev.getImplicit() ++ .canOmitTagInNonPlainScalar()))) { ++ preparedTag = null; ++ return; ++ } ++ if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) { ++ tag = "!"; ++ preparedTag = null; ++ } ++ } else { ++ CollectionStartEvent ev = (CollectionStartEvent) event; ++ tag = ev.getTag(); ++ if ((!canonical || tag == null) && ev.getImplicit()) { ++ preparedTag = null; ++ return; ++ } ++ } ++ if (tag == null) { ++ throw new EmitterException("tag is not specified"); ++ } ++ if (preparedTag == null) { ++ preparedTag = prepareTag(tag); ++ } ++ writeIndicator(preparedTag, true, false, false); ++ preparedTag = null; ++ } ++ ++ private DumperOptions.ScalarStyle chooseScalarStyle() { ++ ScalarEvent ev = (ScalarEvent) event; ++ if (analysis == null) { ++ analysis = analyzeScalar(ev.getValue()); ++ } ++ if (!ev.isPlain() && ev.getScalarStyle() == DumperOptions.ScalarStyle.DOUBLE_QUOTED || this.canonical) { ++ return DumperOptions.ScalarStyle.DOUBLE_QUOTED; ++ } ++ if (ev.isPlain() && ev.getImplicit().canOmitTagInPlainScalar()) { ++ if (!(simpleKeyContext && (analysis.isEmpty() || analysis.isMultiline())) ++ && ((flowLevel != 0 && analysis.isAllowFlowPlain()) || (flowLevel == 0 && analysis.isAllowBlockPlain()))) { ++ return null; ++ } ++ } ++ if (!ev.isPlain() && (ev.getScalarStyle() == DumperOptions.ScalarStyle.LITERAL || ev.getScalarStyle() == DumperOptions.ScalarStyle.FOLDED)) { ++ if (flowLevel == 0 && !simpleKeyContext && analysis.isAllowBlock()) { ++ return ev.getScalarStyle(); ++ } ++ } ++ if (ev.isPlain() || ev.getScalarStyle() == DumperOptions.ScalarStyle.SINGLE_QUOTED) { ++ if (analysis.isAllowSingleQuoted() && !(simpleKeyContext && analysis.isMultiline())) { ++ return DumperOptions.ScalarStyle.SINGLE_QUOTED; ++ } ++ } ++ return DumperOptions.ScalarStyle.DOUBLE_QUOTED; ++ } ++ ++ private void processScalar() throws IOException { ++ ScalarEvent ev = (ScalarEvent) event; ++ if (analysis == null) { ++ analysis = analyzeScalar(ev.getValue()); ++ } ++ if (style == null) { ++ style = chooseScalarStyle(); ++ } ++ boolean split = !simpleKeyContext && splitLines; ++ if (style == null) { ++ writePlain(analysis.getScalar(), split); ++ } else { ++ switch (style) { ++ case DOUBLE_QUOTED: ++ writeDoubleQuoted(analysis.getScalar(), split); ++ break; ++ case SINGLE_QUOTED: ++ writeSingleQuoted(analysis.getScalar(), split); ++ break; ++ case FOLDED: ++ writeFolded(analysis.getScalar(), split); ++ break; ++ case LITERAL: ++ writeLiteral(analysis.getScalar()); ++ break; ++ default: ++ throw new YAMLException("Unexpected style: " + style); ++ } ++ } ++ analysis = null; ++ style = null; ++ } ++ ++ // Analyzers. ++ ++ private String prepareVersion(Version version) { ++ if (version.major() != 1) { ++ throw new EmitterException("unsupported YAML version: " + version); ++ } ++ return version.getRepresentation(); ++ } ++ ++ private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$"); ++ ++ private String prepareTagHandle(String handle) { ++ if (handle.length() == 0) { ++ throw new EmitterException("tag handle must not be empty"); ++ } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') { ++ throw new EmitterException("tag handle must start and end with '!': " + handle); ++ } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) { ++ throw new EmitterException("invalid character in the tag handle: " + handle); ++ } ++ return handle; ++ } ++ ++ private String prepareTagPrefix(String prefix) { ++ if (prefix.length() == 0) { ++ throw new EmitterException("tag prefix must not be empty"); ++ } ++ StringBuilder chunks = new StringBuilder(); ++ int start = 0; ++ int end = 0; ++ if (prefix.charAt(0) == '!') { ++ end = 1; ++ } ++ while (end < prefix.length()) { ++ end++; ++ } ++ if (start < end) { ++ chunks.append(prefix, start, end); ++ } ++ return chunks.toString(); ++ } ++ ++ private String prepareTag(String tag) { ++ if (tag.length() == 0) { ++ throw new EmitterException("tag must not be empty"); ++ } ++ if ("!".equals(tag)) { ++ return tag; ++ } ++ String handle = null; ++ String suffix = tag; ++ // shall the tag prefixes be sorted as in PyYAML? ++ for (String prefix : tagPrefixes.keySet()) { ++ if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) { ++ handle = prefix; ++ } ++ } ++ if (handle != null) { ++ suffix = tag.substring(handle.length()); ++ handle = tagPrefixes.get(handle); ++ } ++ ++ int end = suffix.length(); ++ String suffixText = end > 0 ? suffix.substring(0, end) : ""; ++ ++ if (handle != null) { ++ return handle + suffixText; ++ } ++ return "!<" + suffixText + ">"; ++ } ++ ++ static String prepareAnchor(String anchor) { ++ if (anchor.length() == 0) { ++ throw new EmitterException("anchor must not be empty"); ++ } ++ for (Character invalid : INVALID_ANCHOR) { ++ if (anchor.indexOf(invalid) > -1) { ++ throw new EmitterException("Invalid character '" + invalid + "' in the anchor: " + anchor); ++ } ++ } ++ Matcher matcher = SPACES_PATTERN.matcher(anchor); ++ if (matcher.find()) { ++ throw new EmitterException("Anchor may not contain spaces: " + anchor); ++ } ++ return anchor; ++ } ++ ++ private ScalarAnalysis analyzeScalar(String scalar) { ++ // Empty scalar is a special case. ++ if (scalar.length() == 0) { ++ return new ScalarAnalysis(scalar, true, false, false, true, true, false); ++ } ++ // Indicators and special characters. ++ boolean blockIndicators = false; ++ boolean flowIndicators = false; ++ boolean lineBreaks = false; ++ boolean specialCharacters = false; ++ ++ // Important whitespace combinations. ++ boolean leadingSpace = false; ++ boolean leadingBreak = false; ++ boolean trailingSpace = false; ++ boolean trailingBreak = false; ++ boolean breakSpace = false; ++ boolean spaceBreak = false; ++ ++ // Check document indicators. ++ if (scalar.startsWith("---") || scalar.startsWith("...")) { ++ blockIndicators = true; ++ flowIndicators = true; ++ } ++ // First character or preceded by a whitespace. ++ boolean preceededByWhitespace = true; ++ boolean followedByWhitespace = scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.codePointAt(1)); ++ // The previous character is a space. ++ boolean previousSpace = false; ++ ++ // The previous character is a break. ++ boolean previousBreak = false; ++ ++ int index = 0; ++ ++ while (index < scalar.length()) { ++ int c = scalar.codePointAt(index); ++ // Check for indicators. ++ if (index == 0) { ++ // Leading indicators are special characters. ++ if ("#,[]{}&*!|>'\"%@`".indexOf(c) != -1) { ++ flowIndicators = true; ++ blockIndicators = true; ++ } ++ if (c == '?' || c == ':') { ++ flowIndicators = true; ++ if (followedByWhitespace) { ++ blockIndicators = true; ++ } ++ } ++ if (c == '-' && followedByWhitespace) { ++ flowIndicators = true; ++ blockIndicators = true; ++ } ++ } else { ++ // Some indicators cannot appear within a scalar as well. ++ if (",?[]{}".indexOf(c) != -1) { ++ flowIndicators = true; ++ } ++ if (c == ':') { ++ flowIndicators = true; ++ if (followedByWhitespace) { ++ blockIndicators = true; ++ } ++ } ++ if (c == '#' && preceededByWhitespace) { ++ flowIndicators = true; ++ blockIndicators = true; ++ } ++ } ++ // Check for line breaks, special, and unicode characters. ++ boolean isLineBreak = Constant.LINEBR.has(c); ++ if (isLineBreak) { ++ lineBreaks = true; ++ } ++ if (!(c == '\n' || (0x20 <= c && c <= 0x7E))) { ++ if (c == 0x85 || (c >= 0xA0 && c <= 0xD7FF) ++ || (c >= 0xE000 && c <= 0xFFFD) ++ || (c >= 0x10000 && c <= 0x10FFFF)) { ++ // unicode is used ++ if (!this.allowUnicode) { ++ specialCharacters = true; ++ } ++ } else { ++ specialCharacters = true; ++ } ++ } ++ // Detect important whitespace combinations. ++ if (c == ' ') { ++ if (index == 0) { ++ leadingSpace = true; ++ } ++ if (index == scalar.length() - 1) { ++ trailingSpace = true; ++ } ++ if (previousBreak) { ++ breakSpace = true; ++ } ++ previousSpace = true; ++ previousBreak = false; ++ } else if (isLineBreak) { ++ if (index == 0) { ++ leadingBreak = true; ++ } ++ if (index == scalar.length() - 1) { ++ trailingBreak = true; ++ } ++ if (previousSpace) { ++ spaceBreak = true; ++ } ++ previousSpace = false; ++ previousBreak = true; ++ } else { ++ previousSpace = false; ++ previousBreak = false; ++ } ++ ++ // Prepare for the next character. ++ index += Character.charCount(c); ++ preceededByWhitespace = Constant.NULL_BL_T.has(c) || isLineBreak; ++ followedByWhitespace = true; ++ if (index + 1 < scalar.length()) { ++ int nextIndex = index + Character.charCount(scalar.codePointAt(index)); ++ if (nextIndex < scalar.length()) { ++ followedByWhitespace = (Constant.NULL_BL_T.has(scalar.codePointAt(nextIndex))) || isLineBreak; ++ } ++ } ++ } ++ // Let's decide what styles are allowed. ++ boolean allowFlowPlain = true; ++ boolean allowBlockPlain = true; ++ boolean allowSingleQuoted = true; ++ boolean allowBlock = true; ++ // Leading and trailing whitespaces are bad for plain scalars. ++ if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) { ++ allowFlowPlain = allowBlockPlain = false; ++ } ++ // We do not permit trailing spaces for block scalars. ++ if (trailingSpace) { ++ allowBlock = false; ++ } ++ // Spaces at the beginning of a new line are only acceptable for block ++ // scalars. ++ if (breakSpace) { ++ allowFlowPlain = allowBlockPlain = allowSingleQuoted = false; ++ } ++ // Spaces followed by breaks, as well as special character are only ++ // allowed for double quoted scalars. ++ if (spaceBreak || specialCharacters) { ++ allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false; ++ } ++ // Although the plain scalar writer supports breaks, we never emit ++ // multiline plain scalars in the flow context. ++ if (lineBreaks) { ++ allowFlowPlain = false; ++ } ++ // Flow indicators are forbidden for flow plain scalars. ++ if (flowIndicators) { ++ allowFlowPlain = false; ++ } ++ // Block indicators are forbidden for block plain scalars. ++ if (blockIndicators) { ++ allowBlockPlain = false; ++ } ++ ++ return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain, ++ allowSingleQuoted, allowBlock); ++ } ++ ++ // Writers. ++ ++ void flushStream() throws IOException { ++ stream.flush(); ++ } ++ ++ void writeStreamStart() { ++ // BOM is written by Writer. ++ } ++ ++ void writeStreamEnd() throws IOException { ++ flushStream(); ++ } ++ ++ void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace, ++ boolean indentation) throws IOException { ++ if (!this.whitespace && needWhitespace) { ++ this.column++; ++ stream.write(SPACE); ++ } ++ this.whitespace = whitespace; ++ this.indention = this.indention && indentation; ++ this.column += indicator.length(); ++ openEnded = false; ++ stream.write(indicator); ++ } ++ ++ void writeIndent() throws IOException { ++ int indent; ++ if (this.indent != null) { ++ indent = this.indent; ++ } else { ++ indent = 0; ++ } ++ ++ if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) { ++ writeLineBreak(null); ++ } ++ ++ writeWhitespace(indent - this.column); ++ } ++ ++ private void writeWhitespace(int length) throws IOException { ++ if (length <= 0) { ++ return; ++ } ++ this.whitespace = true; ++ char[] data = new char[length]; ++ for (int i = 0; i < data.length; i++) { ++ data[i] = ' '; ++ } ++ this.column += length; ++ stream.write(data); ++ } ++ ++ private void writeLineBreak(String data) throws IOException { ++ this.whitespace = true; ++ this.indention = true; ++ this.column = 0; ++ if (data == null) { ++ stream.write(this.bestLineBreak); ++ } else { ++ stream.write(data); ++ } ++ } ++ ++ void writeVersionDirective(String versionText) throws IOException { ++ stream.write("%YAML "); ++ stream.write(versionText); ++ writeLineBreak(null); ++ } ++ ++ void writeTagDirective(String handleText, String prefixText) throws IOException { ++ // XXX: not sure 4 invocations better then StringBuilders created by str ++ // + str ++ stream.write("%TAG "); ++ stream.write(handleText); ++ stream.write(SPACE); ++ stream.write(prefixText); ++ writeLineBreak(null); ++ } ++ ++ // Scalar streams. ++ private void writeSingleQuoted(String text, boolean split) throws IOException { ++ writeIndicator("'", true, false, false); ++ boolean spaces = false; ++ boolean breaks = false; ++ int start = 0, end = 0; ++ char ch; ++ while (end <= text.length()) { ++ ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (spaces) { ++ if (ch == 0 || ch != ' ') { ++ if (start + 1 == end && this.column > this.bestWidth && split && start != 0 ++ && end != text.length()) { ++ writeIndent(); ++ } else { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ } ++ start = end; ++ } ++ } else if (breaks) { ++ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { ++ if (text.charAt(start) == '\n') { ++ writeLineBreak(null); ++ } ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ writeIndent(); ++ start = end; ++ } ++ } else { ++ if (Constant.LINEBR.has(ch, "\0 '")) { ++ if (start < end) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ start = end; ++ } ++ } ++ } ++ if (ch == '\'') { ++ this.column += 2; ++ stream.write("''"); ++ start = end + 1; ++ } ++ if (ch != 0) { ++ spaces = ch == ' '; ++ breaks = Constant.LINEBR.has(ch); ++ } ++ end++; ++ } ++ writeIndicator("'", false, false, false); ++ } ++ ++ private void writeDoubleQuoted(String text, boolean split) throws IOException { ++ writeIndicator("\"", true, false, false); ++ int start = 0; ++ int end = 0; ++ while (end <= text.length()) { ++ Character ch = null; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1 ++ || !('\u0020' <= ch && ch <= '\u007E')) { ++ if (start < end) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ start = end; ++ } ++ if (ch != null) { ++ String data; ++ if (ESCAPE_REPLACEMENTS.containsKey(ch)) { ++ data = "\\" + ESCAPE_REPLACEMENTS.get(ch); ++ } else if (!this.allowUnicode || !StreamReader.isPrintable(ch)) { ++ // if !allowUnicode or the character is not printable, ++ // we must encode it ++ if (ch <= '\u00FF') { ++ String s = "0" + Integer.toString(ch, 16); ++ data = "\\x" + s.substring(s.length() - 2); ++ } else if (ch >= '\uD800' && ch <= '\uDBFF') { ++ if (end + 1 < text.length()) { ++ Character ch2 = text.charAt(++end); ++ String s = "000" + Long.toHexString(Character.toCodePoint(ch, ch2)); ++ data = "\\U" + s.substring(s.length() - 8); ++ } else { ++ String s = "000" + Integer.toString(ch, 16); ++ data = "\\u" + s.substring(s.length() - 4); ++ } ++ } else { ++ String s = "000" + Integer.toString(ch, 16); ++ data = "\\u" + s.substring(s.length() - 4); ++ } ++ } else { ++ data = String.valueOf(ch); ++ } ++ this.column += data.length(); ++ stream.write(data); ++ start = end + 1; ++ } ++ } ++ if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end) ++ && (this.column + (end - start)) > this.bestWidth && split) { ++ String data; ++ if (start >= end) { ++ data = "\\"; ++ } else { ++ data = text.substring(start, end) + "\\"; ++ } ++ if (start < end) { ++ start = end; ++ } ++ this.column += data.length(); ++ stream.write(data); ++ writeIndent(); ++ this.whitespace = false; ++ this.indention = false; ++ if (text.charAt(start) == ' ') { ++ data = "\\"; ++ this.column += data.length(); ++ stream.write(data); ++ } ++ } ++ end += 1; ++ } ++ writeIndicator("\"", false, false, false); ++ } ++ ++ private boolean writeCommentLines(List commentLines) throws IOException { ++ boolean wroteComment = false; ++ if(emitComments) { ++ int indentColumns = 0; ++ boolean firstComment = true; ++ for (CommentLine commentLine : commentLines) { ++ if (commentLine.getCommentType() != CommentType.BLANK_LINE) { ++ if (firstComment) { ++ firstComment = false; ++ writeIndicator("#", commentLine.getCommentType() == CommentType.IN_LINE, false, false); ++ indentColumns = this.column > 0 ? this.column - 1 : 0; ++ } else { ++ writeWhitespace(indentColumns); ++ writeIndicator("#", false, false, false); ++ } ++ stream.write(commentLine.getValue()); ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(null); ++ writeIndent(); ++ } ++ wroteComment = true; ++ } ++ } ++ return wroteComment; ++ } ++ ++ private void writeBlockComment() throws IOException { ++ if(!blockCommentsCollector.isEmpty()) { ++ writeIndent(); ++ writeCommentLines(blockCommentsCollector.consume()); ++ } ++ } ++ ++ private boolean writeInlineComments() throws IOException { ++ return writeCommentLines(inlineCommentsCollector.consume()); ++ } ++ ++ private String determineBlockHints(String text) { ++ StringBuilder hints = new StringBuilder(); ++ if (Constant.LINEBR.has(text.charAt(0), " ")) { ++ hints.append(bestIndent); ++ } ++ char ch1 = text.charAt(text.length() - 1); ++ if (Constant.LINEBR.hasNo(ch1)) { ++ hints.append("-"); ++ } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) { ++ hints.append("+"); ++ } ++ return hints.toString(); ++ } ++ ++ void writeFolded(String text, boolean split) throws IOException { ++ String hints = determineBlockHints(text); ++ writeIndicator(">" + hints, true, false, false); ++ if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) { ++ openEnded = true; ++ } ++ if(!writeInlineComments()) { ++ writeLineBreak(null); ++ } ++ boolean leadingSpace = true; ++ boolean spaces = false; ++ boolean breaks = true; ++ int start = 0, end = 0; ++ while (end <= text.length()) { ++ char ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (breaks) { ++ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { ++ if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') { ++ writeLineBreak(null); ++ } ++ leadingSpace = ch == ' '; ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ if (ch != 0) { ++ writeIndent(); ++ } ++ start = end; ++ } ++ } else if (spaces) { ++ if (ch != ' ') { ++ if (start + 1 == end && this.column > this.bestWidth && split) { ++ writeIndent(); ++ } else { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ } ++ start = end; ++ } ++ } else { ++ if (Constant.LINEBR.has(ch, "\0 ")) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ if (ch == 0) { ++ writeLineBreak(null); ++ } ++ start = end; ++ } ++ } ++ if (ch != 0) { ++ breaks = Constant.LINEBR.has(ch); ++ spaces = ch == ' '; ++ } ++ end++; ++ } ++ } ++ ++ void writeLiteral(String text) throws IOException { ++ String hints = determineBlockHints(text); ++ writeIndicator("|" + hints, true, false, false); ++ if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') { ++ openEnded = true; ++ } ++ if(!writeInlineComments()) { ++ writeLineBreak(null); ++ } ++ boolean breaks = true; ++ int start = 0, end = 0; ++ while (end <= text.length()) { ++ char ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (breaks) { ++ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ if (ch != 0) { ++ writeIndent(); ++ } ++ start = end; ++ } ++ } else { ++ if (ch == 0 || Constant.LINEBR.has(ch)) { ++ stream.write(text, start, end - start); ++ if (ch == 0) { ++ writeLineBreak(null); ++ } ++ start = end; ++ } ++ } ++ if (ch != 0) { ++ breaks = Constant.LINEBR.has(ch); ++ } ++ end++; ++ } ++ } ++ ++ void writePlain(String text, boolean split) throws IOException { ++ if (rootContext) { ++ openEnded = true; ++ } ++ if (text.length() == 0) { ++ return; ++ } ++ if (!this.whitespace) { ++ this.column++; ++ stream.write(SPACE); ++ } ++ this.whitespace = false; ++ this.indention = false; ++ boolean spaces = false; ++ boolean breaks = false; ++ int start = 0, end = 0; ++ while (end <= text.length()) { ++ char ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (spaces) { ++ if (ch != ' ') { ++ if (start + 1 == end && this.column > this.bestWidth && split) { ++ writeIndent(); ++ this.whitespace = false; ++ this.indention = false; ++ } else { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ } ++ start = end; ++ } ++ } else if (breaks) { ++ if (Constant.LINEBR.hasNo(ch)) { ++ if (text.charAt(start) == '\n') { ++ writeLineBreak(null); ++ } ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ writeIndent(); ++ this.whitespace = false; ++ this.indention = false; ++ start = end; ++ } ++ } else { ++ if (Constant.LINEBR.has(ch, "\0 ")) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ start = end; ++ } ++ } ++ if (ch != 0) { ++ spaces = ch == ' '; ++ breaks = Constant.LINEBR.has(ch); ++ } ++ end++; ++ } ++ } ++}