Home | History | Annotate | Download | only in emitter
      1 /**
      2  * Copyright (c) 2008, http://www.snakeyaml.org
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package org.yaml.snakeyaml.emitter;
     17 
     18 import java.io.IOException;
     19 import java.io.Writer;
     20 import java.util.HashMap;
     21 import java.util.Iterator;
     22 import java.util.LinkedHashMap;
     23 import java.util.Map;
     24 import java.util.Queue;
     25 import java.util.Set;
     26 import java.util.TreeSet;
     27 import java.util.concurrent.ArrayBlockingQueue;
     28 import java.util.regex.Pattern;
     29 
     30 import org.yaml.snakeyaml.DumperOptions;
     31 import org.yaml.snakeyaml.DumperOptions.Version;
     32 import org.yaml.snakeyaml.error.YAMLException;
     33 import org.yaml.snakeyaml.events.AliasEvent;
     34 import org.yaml.snakeyaml.events.CollectionEndEvent;
     35 import org.yaml.snakeyaml.events.CollectionStartEvent;
     36 import org.yaml.snakeyaml.events.DocumentEndEvent;
     37 import org.yaml.snakeyaml.events.DocumentStartEvent;
     38 import org.yaml.snakeyaml.events.Event;
     39 import org.yaml.snakeyaml.events.MappingEndEvent;
     40 import org.yaml.snakeyaml.events.MappingStartEvent;
     41 import org.yaml.snakeyaml.events.NodeEvent;
     42 import org.yaml.snakeyaml.events.ScalarEvent;
     43 import org.yaml.snakeyaml.events.SequenceEndEvent;
     44 import org.yaml.snakeyaml.events.SequenceStartEvent;
     45 import org.yaml.snakeyaml.events.StreamEndEvent;
     46 import org.yaml.snakeyaml.events.StreamStartEvent;
     47 import org.yaml.snakeyaml.nodes.Tag;
     48 import org.yaml.snakeyaml.reader.StreamReader;
     49 import org.yaml.snakeyaml.scanner.Constant;
     50 import org.yaml.snakeyaml.util.ArrayStack;
     51 
     52 /**
     53  * <pre>
     54  * Emitter expects events obeying the following grammar:
     55  * stream ::= STREAM-START document* STREAM-END
     56  * document ::= DOCUMENT-START node DOCUMENT-END
     57  * node ::= SCALAR | sequence | mapping
     58  * sequence ::= SEQUENCE-START node* SEQUENCE-END
     59  * mapping ::= MAPPING-START (node node)* MAPPING-END
     60  * </pre>
     61  */
     62 public final class Emitter implements Emitable {
     63     private static final Map<Character, String> ESCAPE_REPLACEMENTS = new HashMap<Character, String>();
     64     public static final int MIN_INDENT = 1;
     65     public static final int MAX_INDENT = 10;
     66 
     67     private static final char[] SPACE = { ' ' };
     68 
     69     static {
     70         ESCAPE_REPLACEMENTS.put('\0', "0");
     71         ESCAPE_REPLACEMENTS.put('\u0007', "a");
     72         ESCAPE_REPLACEMENTS.put('\u0008', "b");
     73         ESCAPE_REPLACEMENTS.put('\u0009', "t");
     74         ESCAPE_REPLACEMENTS.put('\n', "n");
     75         ESCAPE_REPLACEMENTS.put('\u000B', "v");
     76         ESCAPE_REPLACEMENTS.put('\u000C', "f");
     77         ESCAPE_REPLACEMENTS.put('\r', "r");
     78         ESCAPE_REPLACEMENTS.put('\u001B', "e");
     79         ESCAPE_REPLACEMENTS.put('"', "\"");
     80         ESCAPE_REPLACEMENTS.put('\\', "\\");
     81         ESCAPE_REPLACEMENTS.put('\u0085', "N");
     82         ESCAPE_REPLACEMENTS.put('\u00A0', "_");
     83         ESCAPE_REPLACEMENTS.put('\u2028', "L");
     84         ESCAPE_REPLACEMENTS.put('\u2029', "P");
     85     }
     86 
     87     private final static Map<String, String> DEFAULT_TAG_PREFIXES = new LinkedHashMap<String, String>();
     88     static {
     89         DEFAULT_TAG_PREFIXES.put("!", "!");
     90         DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!");
     91     }
     92     // The stream should have the methods `write` and possibly `flush`.
     93     private final Writer stream;
     94 
     95     // Encoding is defined by Writer (cannot be overriden by STREAM-START.)
     96     // private Charset encoding;
     97 
     98     // Emitter is a state machine with a stack of states to handle nested
     99     // structures.
    100     private final ArrayStack<EmitterState> states;
    101     private EmitterState state;
    102 
    103     // Current event and the event queue.
    104     private final Queue<Event> events;
    105     private Event event;
    106 
    107     // The current indentation level and the stack of previous indents.
    108     private final ArrayStack<Integer> indents;
    109     private Integer indent;
    110 
    111     // Flow level.
    112     private int flowLevel;
    113 
    114     // Contexts.
    115     private boolean rootContext;
    116     private boolean mappingContext;
    117     private boolean simpleKeyContext;
    118 
    119     //
    120     // Characteristics of the last emitted character:
    121     // - current position.
    122     // - is it a whitespace?
    123     // - is it an indention character
    124     // (indentation space, '-', '?', or ':')?
    125     // private int line; this variable is not used
    126     private int column;
    127     private boolean whitespace;
    128     private boolean indention;
    129     private boolean openEnded;
    130 
    131     // Formatting details.
    132     private Boolean canonical;
    133     // pretty print flow by adding extra line breaks
    134     private Boolean prettyFlow;
    135 
    136     private boolean allowUnicode;
    137     private int bestIndent;
    138     private int indicatorIndent;
    139     private int bestWidth;
    140     private char[] bestLineBreak;
    141     private boolean splitLines;
    142 
    143     // Tag prefixes.
    144     private Map<String, String> tagPrefixes;
    145 
    146     // Prepared anchor and tag.
    147     private String preparedAnchor;
    148     private String preparedTag;
    149 
    150     // Scalar analysis and style.
    151     private ScalarAnalysis analysis;
    152     private Character style;
    153 
    154     public Emitter(Writer stream, DumperOptions opts) {
    155         // The stream should have the methods `write` and possibly `flush`.
    156         this.stream = stream;
    157         // Emitter is a state machine with a stack of states to handle nested
    158         // structures.
    159         this.states = new ArrayStack<EmitterState>(100);
    160         this.state = new ExpectStreamStart();
    161         // Current event and the event queue.
    162         this.events = new ArrayBlockingQueue<Event>(100);
    163         this.event = null;
    164         // The current indentation level and the stack of previous indents.
    165         this.indents = new ArrayStack<Integer>(10);
    166         this.indent = null;
    167         // Flow level.
    168         this.flowLevel = 0;
    169         // Contexts.
    170         mappingContext = false;
    171         simpleKeyContext = false;
    172 
    173         //
    174         // Characteristics of the last emitted character:
    175         // - current position.
    176         // - is it a whitespace?
    177         // - is it an indention character
    178         // (indentation space, '-', '?', or ':')?
    179         column = 0;
    180         whitespace = true;
    181         indention = true;
    182 
    183         // Whether the document requires an explicit document indicator
    184         openEnded = false;
    185 
    186         // Formatting details.
    187         this.canonical = opts.isCanonical();
    188         this.prettyFlow = opts.isPrettyFlow();
    189         this.allowUnicode = opts.isAllowUnicode();
    190         this.bestIndent = 2;
    191         if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) {
    192             this.bestIndent = opts.getIndent();
    193         }
    194         this.indicatorIndent = opts.getIndicatorIndent();
    195         this.bestWidth = 80;
    196         if (opts.getWidth() > this.bestIndent * 2) {
    197             this.bestWidth = opts.getWidth();
    198         }
    199         this.bestLineBreak = opts.getLineBreak().getString().toCharArray();
    200         this.splitLines = opts.getSplitLines();
    201 
    202         // Tag prefixes.
    203         this.tagPrefixes = new LinkedHashMap<String, String>();
    204 
    205         // Prepared anchor and tag.
    206         this.preparedAnchor = null;
    207         this.preparedTag = null;
    208 
    209         // Scalar analysis and style.
    210         this.analysis = null;
    211         this.style = null;
    212     }
    213 
    214     public void emit(Event event) throws IOException {
    215         this.events.add(event);
    216         while (!needMoreEvents()) {
    217             this.event = this.events.poll();
    218             this.state.expect();
    219             this.event = null;
    220         }
    221     }
    222 
    223     // In some cases, we wait for a few next events before emitting.
    224 
    225     private boolean needMoreEvents() {
    226         if (events.isEmpty()) {
    227             return true;
    228         }
    229         Event event = events.peek();
    230         if (event instanceof DocumentStartEvent) {
    231             return needEvents(1);
    232         } else if (event instanceof SequenceStartEvent) {
    233             return needEvents(2);
    234         } else if (event instanceof MappingStartEvent) {
    235             return needEvents(3);
    236         } else {
    237             return false;
    238         }
    239     }
    240 
    241     private boolean needEvents(int count) {
    242         int level = 0;
    243         Iterator<Event> iter = events.iterator();
    244         iter.next();
    245         while (iter.hasNext()) {
    246             Event event = iter.next();
    247             if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) {
    248                 level++;
    249             } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) {
    250                 level--;
    251             } else if (event instanceof StreamEndEvent) {
    252                 level = -1;
    253             }
    254             if (level < 0) {
    255                 return false;
    256             }
    257         }
    258         return events.size() < count + 1;
    259     }
    260 
    261     private void increaseIndent(boolean flow, boolean indentless) {
    262         indents.push(indent);
    263         if (indent == null) {
    264             if (flow) {
    265                 indent = bestIndent;
    266             } else {
    267                 indent = 0;
    268             }
    269         } else if (!indentless) {
    270             this.indent += bestIndent;
    271         }
    272     }
    273 
    274     // States
    275 
    276     // Stream handlers.
    277 
    278     private class ExpectStreamStart implements EmitterState {
    279         public void expect() throws IOException {
    280             if (event instanceof StreamStartEvent) {
    281                 writeStreamStart();
    282                 state = new ExpectFirstDocumentStart();
    283             } else {
    284                 throw new EmitterException("expected StreamStartEvent, but got " + event);
    285             }
    286         }
    287     }
    288 
    289     private class ExpectNothing implements EmitterState {
    290         public void expect() throws IOException {
    291             throw new EmitterException("expecting nothing, but got " + event);
    292         }
    293     }
    294 
    295     // Document handlers.
    296 
    297     private class ExpectFirstDocumentStart implements EmitterState {
    298         public void expect() throws IOException {
    299             new ExpectDocumentStart(true).expect();
    300         }
    301     }
    302 
    303     private class ExpectDocumentStart implements EmitterState {
    304         private boolean first;
    305 
    306         public ExpectDocumentStart(boolean first) {
    307             this.first = first;
    308         }
    309 
    310         public void expect() throws IOException {
    311             if (event instanceof DocumentStartEvent) {
    312                 DocumentStartEvent ev = (DocumentStartEvent) event;
    313                 if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) {
    314                     writeIndicator("...", true, false, false);
    315                     writeIndent();
    316                 }
    317                 if (ev.getVersion() != null) {
    318                     String versionText = prepareVersion(ev.getVersion());
    319                     writeVersionDirective(versionText);
    320                 }
    321                 tagPrefixes = new LinkedHashMap<String, String>(DEFAULT_TAG_PREFIXES);
    322                 if (ev.getTags() != null) {
    323                     Set<String> handles = new TreeSet<String>(ev.getTags().keySet());
    324                     for (String handle : handles) {
    325                         String prefix = ev.getTags().get(handle);
    326                         tagPrefixes.put(prefix, handle);
    327                         String handleText = prepareTagHandle(handle);
    328                         String prefixText = prepareTagPrefix(prefix);
    329                         writeTagDirective(handleText, prefixText);
    330                     }
    331                 }
    332                 boolean implicit = first && !ev.getExplicit() && !canonical
    333                         && ev.getVersion() == null
    334                         && (ev.getTags() == null || ev.getTags().isEmpty())
    335                         && !checkEmptyDocument();
    336                 if (!implicit) {
    337                     writeIndent();
    338                     writeIndicator("---", true, false, false);
    339                     if (canonical) {
    340                         writeIndent();
    341                     }
    342                 }
    343                 state = new ExpectDocumentRoot();
    344             } else if (event instanceof StreamEndEvent) {
    345                 // TODO fix 313 PyYAML changeset
    346                 // if (openEnded) {
    347                 // writeIndicator("...", true, false, false);
    348                 // writeIndent();
    349                 // }
    350                 writeStreamEnd();
    351                 state = new ExpectNothing();
    352             } else {
    353                 throw new EmitterException("expected DocumentStartEvent, but got " + event);
    354             }
    355         }
    356     }
    357 
    358     private class ExpectDocumentEnd implements EmitterState {
    359         public void expect() throws IOException {
    360             if (event instanceof DocumentEndEvent) {
    361                 writeIndent();
    362                 if (((DocumentEndEvent) event).getExplicit()) {
    363                     writeIndicator("...", true, false, false);
    364                     writeIndent();
    365                 }
    366                 flushStream();
    367                 state = new ExpectDocumentStart(false);
    368             } else {
    369                 throw new EmitterException("expected DocumentEndEvent, but got " + event);
    370             }
    371         }
    372     }
    373 
    374     private class ExpectDocumentRoot implements EmitterState {
    375         public void expect() throws IOException {
    376             states.push(new ExpectDocumentEnd());
    377             expectNode(true, false, false);
    378         }
    379     }
    380 
    381     // Node handlers.
    382 
    383     private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException {
    384         rootContext = root;
    385         mappingContext = mapping;
    386         simpleKeyContext = simpleKey;
    387         if (event instanceof AliasEvent) {
    388             expectAlias();
    389         } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) {
    390             processAnchor("&");
    391             processTag();
    392             if (event instanceof ScalarEvent) {
    393                 expectScalar();
    394             } else if (event instanceof SequenceStartEvent) {
    395                 if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).getFlowStyle()
    396                         || checkEmptySequence()) {
    397                     expectFlowSequence();
    398                 } else {
    399                     expectBlockSequence();
    400                 }
    401             } else {// MappingStartEvent
    402                 if (flowLevel != 0 || canonical || ((MappingStartEvent) event).getFlowStyle()
    403                         || checkEmptyMapping()) {
    404                     expectFlowMapping();
    405                 } else {
    406                     expectBlockMapping();
    407                 }
    408             }
    409         } else {
    410             throw new EmitterException("expected NodeEvent, but got " + event);
    411         }
    412     }
    413 
    414     private void expectAlias() throws IOException {
    415         if (((NodeEvent) event).getAnchor() == null) {
    416             throw new EmitterException("anchor is not specified for alias");
    417         }
    418         processAnchor("*");
    419         state = states.pop();
    420     }
    421 
    422     private void expectScalar() throws IOException {
    423         increaseIndent(true, false);
    424         processScalar();
    425         indent = indents.pop();
    426         state = states.pop();
    427     }
    428 
    429     // Flow sequence handlers.
    430 
    431     private void expectFlowSequence() throws IOException {
    432         writeIndicator("[", true, true, false);
    433         flowLevel++;
    434         increaseIndent(true, false);
    435         if (prettyFlow) {
    436             writeIndent();
    437         }
    438         state = new ExpectFirstFlowSequenceItem();
    439     }
    440 
    441     private class ExpectFirstFlowSequenceItem implements EmitterState {
    442         public void expect() throws IOException {
    443             if (event instanceof SequenceEndEvent) {
    444                 indent = indents.pop();
    445                 flowLevel--;
    446                 writeIndicator("]", false, false, false);
    447                 state = states.pop();
    448             } else {
    449                 if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
    450                     writeIndent();
    451                 }
    452                 states.push(new ExpectFlowSequenceItem());
    453                 expectNode(false, false, false);
    454             }
    455         }
    456     }
    457 
    458     private class ExpectFlowSequenceItem implements EmitterState {
    459         public void expect() throws IOException {
    460             if (event instanceof SequenceEndEvent) {
    461                 indent = indents.pop();
    462                 flowLevel--;
    463                 if (canonical) {
    464                     writeIndicator(",", false, false, false);
    465                     writeIndent();
    466                 }
    467                 writeIndicator("]", false, false, false);
    468                 if (prettyFlow) {
    469                     writeIndent();
    470                 }
    471                 state = states.pop();
    472             } else {
    473                 writeIndicator(",", false, false, false);
    474                 if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
    475                     writeIndent();
    476                 }
    477                 states.push(new ExpectFlowSequenceItem());
    478                 expectNode(false, false, false);
    479             }
    480         }
    481     }
    482 
    483     // Flow mapping handlers.
    484 
    485     private void expectFlowMapping() throws IOException {
    486         writeIndicator("{", true, true, false);
    487         flowLevel++;
    488         increaseIndent(true, false);
    489         if (prettyFlow) {
    490             writeIndent();
    491         }
    492         state = new ExpectFirstFlowMappingKey();
    493     }
    494 
    495     private class ExpectFirstFlowMappingKey implements EmitterState {
    496         public void expect() throws IOException {
    497             if (event instanceof MappingEndEvent) {
    498                 indent = indents.pop();
    499                 flowLevel--;
    500                 writeIndicator("}", false, false, false);
    501                 state = states.pop();
    502             } else {
    503                 if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
    504                     writeIndent();
    505                 }
    506                 if (!canonical && checkSimpleKey()) {
    507                     states.push(new ExpectFlowMappingSimpleValue());
    508                     expectNode(false, true, true);
    509                 } else {
    510                     writeIndicator("?", true, false, false);
    511                     states.push(new ExpectFlowMappingValue());
    512                     expectNode(false, true, false);
    513                 }
    514             }
    515         }
    516     }
    517 
    518     private class ExpectFlowMappingKey implements EmitterState {
    519         public void expect() throws IOException {
    520             if (event instanceof MappingEndEvent) {
    521                 indent = indents.pop();
    522                 flowLevel--;
    523                 if (canonical) {
    524                     writeIndicator(",", false, false, false);
    525                     writeIndent();
    526                 }
    527                 if (prettyFlow) {
    528                     writeIndent();
    529                 }
    530                 writeIndicator("}", false, false, false);
    531                 state = states.pop();
    532             } else {
    533                 writeIndicator(",", false, false, false);
    534                 if (canonical || (column > bestWidth && splitLines) || prettyFlow) {
    535                     writeIndent();
    536                 }
    537                 if (!canonical && checkSimpleKey()) {
    538                     states.push(new ExpectFlowMappingSimpleValue());
    539                     expectNode(false, true, true);
    540                 } else {
    541                     writeIndicator("?", true, false, false);
    542                     states.push(new ExpectFlowMappingValue());
    543                     expectNode(false, true, false);
    544                 }
    545             }
    546         }
    547     }
    548 
    549     private class ExpectFlowMappingSimpleValue implements EmitterState {
    550         public void expect() throws IOException {
    551             writeIndicator(":", false, false, false);
    552             states.push(new ExpectFlowMappingKey());
    553             expectNode(false, true, false);
    554         }
    555     }
    556 
    557     private class ExpectFlowMappingValue implements EmitterState {
    558         public void expect() throws IOException {
    559             if (canonical || (column > bestWidth) || prettyFlow) {
    560                 writeIndent();
    561             }
    562             writeIndicator(":", true, false, false);
    563             states.push(new ExpectFlowMappingKey());
    564             expectNode(false, true, false);
    565         }
    566     }
    567 
    568     // Block sequence handlers.
    569 
    570     private void expectBlockSequence() throws IOException {
    571         boolean indentless = mappingContext && !indention;
    572         increaseIndent(false, indentless);
    573         state = new ExpectFirstBlockSequenceItem();
    574     }
    575 
    576     private class ExpectFirstBlockSequenceItem implements EmitterState {
    577         public void expect() throws IOException {
    578             new ExpectBlockSequenceItem(true).expect();
    579         }
    580     }
    581 
    582     private class ExpectBlockSequenceItem implements EmitterState {
    583         private boolean first;
    584 
    585         public ExpectBlockSequenceItem(boolean first) {
    586             this.first = first;
    587         }
    588 
    589         public void expect() throws IOException {
    590             if (!this.first && event instanceof SequenceEndEvent) {
    591                 indent = indents.pop();
    592                 state = states.pop();
    593             } else {
    594                 writeIndent();
    595                 writeWhitespace(indicatorIndent);
    596                 writeIndicator("-", true, false, true);
    597                 states.push(new ExpectBlockSequenceItem(false));
    598                 expectNode(false, false, false);
    599             }
    600         }
    601     }
    602 
    603     // Block mapping handlers.
    604     private void expectBlockMapping() throws IOException {
    605         increaseIndent(false, false);
    606         state = new ExpectFirstBlockMappingKey();
    607     }
    608 
    609     private class ExpectFirstBlockMappingKey implements EmitterState {
    610         public void expect() throws IOException {
    611             new ExpectBlockMappingKey(true).expect();
    612         }
    613     }
    614 
    615     private class ExpectBlockMappingKey implements EmitterState {
    616         private boolean first;
    617 
    618         public ExpectBlockMappingKey(boolean first) {
    619             this.first = first;
    620         }
    621 
    622         public void expect() throws IOException {
    623             if (!this.first && event instanceof MappingEndEvent) {
    624                 indent = indents.pop();
    625                 state = states.pop();
    626             } else {
    627                 writeIndent();
    628                 if (checkSimpleKey()) {
    629                     states.push(new ExpectBlockMappingSimpleValue());
    630                     expectNode(false, true, true);
    631                 } else {
    632                     writeIndicator("?", true, false, true);
    633                     states.push(new ExpectBlockMappingValue());
    634                     expectNode(false, true, false);
    635                 }
    636             }
    637         }
    638     }
    639 
    640     private class ExpectBlockMappingSimpleValue implements EmitterState {
    641         public void expect() throws IOException {
    642             writeIndicator(":", false, false, false);
    643             states.push(new ExpectBlockMappingKey(false));
    644             expectNode(false, true, false);
    645         }
    646     }
    647 
    648     private class ExpectBlockMappingValue implements EmitterState {
    649         public void expect() throws IOException {
    650             writeIndent();
    651             writeIndicator(":", true, false, true);
    652             states.push(new ExpectBlockMappingKey(false));
    653             expectNode(false, true, false);
    654         }
    655     }
    656 
    657     // Checkers.
    658 
    659     private boolean checkEmptySequence() {
    660         return event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent;
    661     }
    662 
    663     private boolean checkEmptyMapping() {
    664         return event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent;
    665     }
    666 
    667     private boolean checkEmptyDocument() {
    668         if (!(event instanceof DocumentStartEvent) || events.isEmpty()) {
    669             return false;
    670         }
    671         Event event = events.peek();
    672         if (event instanceof ScalarEvent) {
    673             ScalarEvent e = (ScalarEvent) event;
    674             return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e
    675                     .getValue().length() == 0;
    676         }
    677         return false;
    678     }
    679 
    680     private boolean checkSimpleKey() {
    681         int length = 0;
    682         if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) {
    683             if (preparedAnchor == null) {
    684                 preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor());
    685             }
    686             length += preparedAnchor.length();
    687         }
    688         String tag = null;
    689         if (event instanceof ScalarEvent) {
    690             tag = ((ScalarEvent) event).getTag();
    691         } else if (event instanceof CollectionStartEvent) {
    692             tag = ((CollectionStartEvent) event).getTag();
    693         }
    694         if (tag != null) {
    695             if (preparedTag == null) {
    696                 preparedTag = prepareTag(tag);
    697             }
    698             length += preparedTag.length();
    699         }
    700         if (event instanceof ScalarEvent) {
    701             if (analysis == null) {
    702                 analysis = analyzeScalar(((ScalarEvent) event).getValue());
    703             }
    704             length += analysis.scalar.length();
    705         }
    706         return length < 128 && (event instanceof AliasEvent
    707                 || (event instanceof ScalarEvent && !analysis.empty && !analysis.multiline)
    708                 || checkEmptySequence() || checkEmptyMapping());
    709     }
    710 
    711     // Anchor, Tag, and Scalar processors.
    712 
    713     private void processAnchor(String indicator) throws IOException {
    714         NodeEvent ev = (NodeEvent) event;
    715         if (ev.getAnchor() == null) {
    716             preparedAnchor = null;
    717             return;
    718         }
    719         if (preparedAnchor == null) {
    720             preparedAnchor = prepareAnchor(ev.getAnchor());
    721         }
    722         writeIndicator(indicator + preparedAnchor, true, false, false);
    723         preparedAnchor = null;
    724     }
    725 
    726     private void processTag() throws IOException {
    727         String tag = null;
    728         if (event instanceof ScalarEvent) {
    729             ScalarEvent ev = (ScalarEvent) event;
    730             tag = ev.getTag();
    731             if (style == null) {
    732                 style = chooseScalarStyle();
    733             }
    734             if ((!canonical || tag == null) && ((style == null && ev.getImplicit()
    735                     .canOmitTagInPlainScalar()) || (style != null && ev.getImplicit()
    736                     .canOmitTagInNonPlainScalar()))) {
    737                 preparedTag = null;
    738                 return;
    739             }
    740             if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) {
    741                 tag = "!";
    742                 preparedTag = null;
    743             }
    744         } else {
    745             CollectionStartEvent ev = (CollectionStartEvent) event;
    746             tag = ev.getTag();
    747             if ((!canonical || tag == null) && ev.getImplicit()) {
    748                 preparedTag = null;
    749                 return;
    750             }
    751         }
    752         if (tag == null) {
    753             throw new EmitterException("tag is not specified");
    754         }
    755         if (preparedTag == null) {
    756             preparedTag = prepareTag(tag);
    757         }
    758         writeIndicator(preparedTag, true, false, false);
    759         preparedTag = null;
    760     }
    761 
    762     private Character chooseScalarStyle() {
    763         ScalarEvent ev = (ScalarEvent) event;
    764         if (analysis == null) {
    765             analysis = analyzeScalar(ev.getValue());
    766         }
    767         if (ev.getStyle() != null && ev.getStyle() == '"' || this.canonical) {
    768             return '"';
    769         }
    770         if (ev.getStyle() == null && ev.getImplicit().canOmitTagInPlainScalar()) {
    771             if (!(simpleKeyContext && (analysis.empty || analysis.multiline))
    772                     && ((flowLevel != 0 && analysis.allowFlowPlain) || (flowLevel == 0 && analysis.allowBlockPlain))) {
    773                 return null;
    774             }
    775         }
    776         if (ev.getStyle() != null && (ev.getStyle() == '|' || ev.getStyle() == '>')) {
    777             if (flowLevel == 0 && !simpleKeyContext && analysis.allowBlock) {
    778                 return ev.getStyle();
    779             }
    780         }
    781         if (ev.getStyle() == null || ev.getStyle() == '\'') {
    782             if (analysis.allowSingleQuoted && !(simpleKeyContext && analysis.multiline)) {
    783                 return '\'';
    784             }
    785         }
    786         return '"';
    787     }
    788 
    789     private void processScalar() throws IOException {
    790         ScalarEvent ev = (ScalarEvent) event;
    791         if (analysis == null) {
    792             analysis = analyzeScalar(ev.getValue());
    793         }
    794         if (style == null) {
    795             style = chooseScalarStyle();
    796         }
    797         boolean split = !simpleKeyContext && splitLines;
    798         if (style == null) {
    799             writePlain(analysis.scalar, split);
    800         } else {
    801             switch (style) {
    802             case '"':
    803                 writeDoubleQuoted(analysis.scalar, split);
    804                 break;
    805             case '\'':
    806                 writeSingleQuoted(analysis.scalar, split);
    807                 break;
    808             case '>':
    809                 writeFolded(analysis.scalar, split);
    810                 break;
    811             case '|':
    812                 writeLiteral(analysis.scalar);
    813                 break;
    814             default:
    815                 throw new YAMLException("Unexpected style: " + style);
    816             }
    817         }
    818         analysis = null;
    819         style = null;
    820     }
    821 
    822     // Analyzers.
    823 
    824     private String prepareVersion(Version version) {
    825         if (version.major() != 1) {
    826             throw new EmitterException("unsupported YAML version: " + version);
    827         }
    828         return version.getRepresentation();
    829     }
    830 
    831     private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$");
    832 
    833     private String prepareTagHandle(String handle) {
    834         if (handle.length() == 0) {
    835             throw new EmitterException("tag handle must not be empty");
    836         } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') {
    837             throw new EmitterException("tag handle must start and end with '!': " + handle);
    838         } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) {
    839             throw new EmitterException("invalid character in the tag handle: " + handle);
    840         }
    841         return handle;
    842     }
    843 
    844     private String prepareTagPrefix(String prefix) {
    845         if (prefix.length() == 0) {
    846             throw new EmitterException("tag prefix must not be empty");
    847         }
    848         StringBuilder chunks = new StringBuilder();
    849         int start = 0;
    850         int end = 0;
    851         if (prefix.charAt(0) == '!') {
    852             end = 1;
    853         }
    854         while (end < prefix.length()) {
    855             end++;
    856         }
    857         if (start < end) {
    858             chunks.append(prefix.substring(start, end));
    859         }
    860         return chunks.toString();
    861     }
    862 
    863     private String prepareTag(String tag) {
    864         if (tag.length() == 0) {
    865             throw new EmitterException("tag must not be empty");
    866         }
    867         if ("!".equals(tag)) {
    868             return tag;
    869         }
    870         String handle = null;
    871         String suffix = tag;
    872         // shall the tag prefixes be sorted as in PyYAML?
    873         for (String prefix : tagPrefixes.keySet()) {
    874             if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) {
    875                 handle = prefix;
    876             }
    877         }
    878         if (handle != null) {
    879             suffix = tag.substring(handle.length());
    880             handle = tagPrefixes.get(handle);
    881         }
    882 
    883         int end = suffix.length();
    884         String suffixText = end > 0 ? suffix.substring(0, end) : "";
    885 
    886         if (handle != null) {
    887             return handle + suffixText;
    888         }
    889         return "!<" + suffixText + ">";
    890     }
    891 
    892     private final static Pattern ANCHOR_FORMAT = Pattern.compile("^[-_\\w]*$");
    893 
    894     static String prepareAnchor(String anchor) {
    895         if (anchor.length() == 0) {
    896             throw new EmitterException("anchor must not be empty");
    897         }
    898         if (!ANCHOR_FORMAT.matcher(anchor).matches()) {
    899             throw new EmitterException("invalid character in the anchor: " + anchor);
    900         }
    901         return anchor;
    902     }
    903 
    904     private ScalarAnalysis analyzeScalar(String scalar) {
    905         // Empty scalar is a special case.
    906         if (scalar.length() == 0) {
    907             return new ScalarAnalysis(scalar, true, false, false, true, true, false);
    908         }
    909         // Indicators and special characters.
    910         boolean blockIndicators = false;
    911         boolean flowIndicators = false;
    912         boolean lineBreaks = false;
    913         boolean specialCharacters = false;
    914 
    915         // Important whitespace combinations.
    916         boolean leadingSpace = false;
    917         boolean leadingBreak = false;
    918         boolean trailingSpace = false;
    919         boolean trailingBreak = false;
    920         boolean breakSpace = false;
    921         boolean spaceBreak = false;
    922 
    923         // Check document indicators.
    924         if (scalar.startsWith("---") || scalar.startsWith("...")) {
    925             blockIndicators = true;
    926             flowIndicators = true;
    927         }
    928         // First character or preceded by a whitespace.
    929         boolean preceededByWhitespace = true;
    930         boolean followedByWhitespace = scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.charAt(1));
    931         // The previous character is a space.
    932         boolean previousSpace = false;
    933 
    934         // The previous character is a break.
    935         boolean previousBreak = false;
    936 
    937         int index = 0;
    938 
    939         while (index < scalar.length()) {
    940             char ch = scalar.charAt(index);
    941             // Check for indicators.
    942             if (index == 0) {
    943                 // Leading indicators are special characters.
    944                 if ("#,[]{}&*!|>\'\"%@`".indexOf(ch) != -1) {
    945                     flowIndicators = true;
    946                     blockIndicators = true;
    947                 }
    948                 if (ch == '?' || ch == ':') {
    949                     flowIndicators = true;
    950                     if (followedByWhitespace) {
    951                         blockIndicators = true;
    952                     }
    953                 }
    954                 if (ch == '-' && followedByWhitespace) {
    955                     flowIndicators = true;
    956                     blockIndicators = true;
    957                 }
    958             } else {
    959                 // Some indicators cannot appear within a scalar as well.
    960                 if (",?[]{}".indexOf(ch) != -1) {
    961                     flowIndicators = true;
    962                 }
    963                 if (ch == ':') {
    964                     flowIndicators = true;
    965                     if (followedByWhitespace) {
    966                         blockIndicators = true;
    967                     }
    968                 }
    969                 if (ch == '#' && preceededByWhitespace) {
    970                     flowIndicators = true;
    971                     blockIndicators = true;
    972                 }
    973             }
    974             // Check for line breaks, special, and unicode characters.
    975             boolean isLineBreak = Constant.LINEBR.has(ch);
    976             if (isLineBreak) {
    977                 lineBreaks = true;
    978             }
    979             if (!(ch == '\n' || ('\u0020' <= ch && ch <= '\u007E'))) {
    980                 if ((ch == '\u0085' || ('\u00A0' <= ch && ch <= '\uD7FF') || ('\uE000' <= ch && ch <= '\uFFFD'))
    981                         && (ch != '\uFEFF')) {
    982                     // unicode is used
    983                     if (!this.allowUnicode) {
    984                         specialCharacters = true;
    985                     }
    986                 } else {
    987                     specialCharacters = true;
    988                 }
    989             }
    990             // Detect important whitespace combinations.
    991             if (ch == ' ') {
    992                 if (index == 0) {
    993                     leadingSpace = true;
    994                 }
    995                 if (index == scalar.length() - 1) {
    996                     trailingSpace = true;
    997                 }
    998                 if (previousBreak) {
    999                     breakSpace = true;
   1000                 }
   1001                 previousSpace = true;
   1002                 previousBreak = false;
   1003             } else if (isLineBreak) {
   1004                 if (index == 0) {
   1005                     leadingBreak = true;
   1006                 }
   1007                 if (index == scalar.length() - 1) {
   1008                     trailingBreak = true;
   1009                 }
   1010                 if (previousSpace) {
   1011                     spaceBreak = true;
   1012                 }
   1013                 previousSpace = false;
   1014                 previousBreak = true;
   1015             } else {
   1016                 previousSpace = false;
   1017                 previousBreak = false;
   1018             }
   1019 
   1020             // Prepare for the next character.
   1021             index++;
   1022             preceededByWhitespace = Constant.NULL_BL_T.has(ch) || isLineBreak;
   1023             followedByWhitespace = index + 1 >= scalar.length()
   1024                     || (Constant.NULL_BL_T.has(scalar.charAt(index + 1))) || isLineBreak;
   1025         }
   1026         // Let's decide what styles are allowed.
   1027         boolean allowFlowPlain = true;
   1028         boolean allowBlockPlain = true;
   1029         boolean allowSingleQuoted = true;
   1030         boolean allowBlock = true;
   1031         // Leading and trailing whitespaces are bad for plain scalars.
   1032         if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) {
   1033             allowFlowPlain = allowBlockPlain = false;
   1034         }
   1035         // We do not permit trailing spaces for block scalars.
   1036         if (trailingSpace) {
   1037             allowBlock = false;
   1038         }
   1039         // Spaces at the beginning of a new line are only acceptable for block
   1040         // scalars.
   1041         if (breakSpace) {
   1042             allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
   1043         }
   1044         // Spaces followed by breaks, as well as special character are only
   1045         // allowed for double quoted scalars.
   1046         if (spaceBreak || specialCharacters) {
   1047             allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false;
   1048         }
   1049         // Although the plain scalar writer supports breaks, we never emit
   1050         // multiline plain scalars in the flow context.
   1051         if (lineBreaks) {
   1052             allowFlowPlain = false;
   1053         }
   1054         // Flow indicators are forbidden for flow plain scalars.
   1055         if (flowIndicators) {
   1056             allowFlowPlain = false;
   1057         }
   1058         // Block indicators are forbidden for block plain scalars.
   1059         if (blockIndicators) {
   1060             allowBlockPlain = false;
   1061         }
   1062 
   1063         return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain,
   1064                 allowSingleQuoted, allowBlock);
   1065     }
   1066 
   1067     // Writers.
   1068 
   1069     void flushStream() throws IOException {
   1070         stream.flush();
   1071     }
   1072 
   1073     void writeStreamStart() {
   1074         // BOM is written by Writer.
   1075     }
   1076 
   1077     void writeStreamEnd() throws IOException {
   1078         flushStream();
   1079     }
   1080 
   1081     void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace,
   1082             boolean indentation) throws IOException {
   1083         if (!this.whitespace && needWhitespace) {
   1084             this.column++;
   1085             stream.write(SPACE);
   1086         }
   1087         this.whitespace = whitespace;
   1088         this.indention = this.indention && indentation;
   1089         this.column += indicator.length();
   1090         openEnded = false;
   1091         stream.write(indicator);
   1092     }
   1093 
   1094     void writeIndent() throws IOException {
   1095         int indent;
   1096         if (this.indent != null) {
   1097             indent = this.indent;
   1098         } else {
   1099             indent = 0;
   1100         }
   1101 
   1102         if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) {
   1103             writeLineBreak(null);
   1104         }
   1105 
   1106         writeWhitespace(indent - this.column);
   1107     }
   1108 
   1109     private void writeWhitespace(int length) throws IOException {
   1110         if (length <= 0) {
   1111             return;
   1112         }
   1113         this.whitespace = true;
   1114         char[] data = new char[length];
   1115         for (int i = 0; i < data.length; i++) {
   1116             data[i] = ' ';
   1117         }
   1118         this.column += length;
   1119         stream.write(data);
   1120     }
   1121 
   1122     private void writeLineBreak(String data) throws IOException {
   1123         this.whitespace = true;
   1124         this.indention = true;
   1125         this.column = 0;
   1126         if (data == null) {
   1127             stream.write(this.bestLineBreak);
   1128         } else {
   1129             stream.write(data);
   1130         }
   1131     }
   1132 
   1133     void writeVersionDirective(String versionText) throws IOException {
   1134         stream.write("%YAML ");
   1135         stream.write(versionText);
   1136         writeLineBreak(null);
   1137     }
   1138 
   1139     void writeTagDirective(String handleText, String prefixText) throws IOException {
   1140         // XXX: not sure 4 invocations better then StringBuilders created by str
   1141         // + str
   1142         stream.write("%TAG ");
   1143         stream.write(handleText);
   1144         stream.write(SPACE);
   1145         stream.write(prefixText);
   1146         writeLineBreak(null);
   1147     }
   1148 
   1149     // Scalar streams.
   1150     private void writeSingleQuoted(String text, boolean split) throws IOException {
   1151         writeIndicator("'", true, false, false);
   1152         boolean spaces = false;
   1153         boolean breaks = false;
   1154         int start = 0, end = 0;
   1155         char ch;
   1156         while (end <= text.length()) {
   1157             ch = 0;
   1158             if (end < text.length()) {
   1159                 ch = text.charAt(end);
   1160             }
   1161             if (spaces) {
   1162                 if (ch == 0 || ch != ' ') {
   1163                     if (start + 1 == end && this.column > this.bestWidth && split && start != 0
   1164                             && end != text.length()) {
   1165                         writeIndent();
   1166                     } else {
   1167                         int len = end - start;
   1168                         this.column += len;
   1169                         stream.write(text, start, len);
   1170                     }
   1171                     start = end;
   1172                 }
   1173             } else if (breaks) {
   1174                 if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
   1175                     if (text.charAt(start) == '\n') {
   1176                         writeLineBreak(null);
   1177                     }
   1178                     String data = text.substring(start, end);
   1179                     for (char br : data.toCharArray()) {
   1180                         if (br == '\n') {
   1181                             writeLineBreak(null);
   1182                         } else {
   1183                             writeLineBreak(String.valueOf(br));
   1184                         }
   1185                     }
   1186                     writeIndent();
   1187                     start = end;
   1188                 }
   1189             } else {
   1190                 if (Constant.LINEBR.has(ch, "\0 \'")) {
   1191                     if (start < end) {
   1192                         int len = end - start;
   1193                         this.column += len;
   1194                         stream.write(text, start, len);
   1195                         start = end;
   1196                     }
   1197                 }
   1198             }
   1199             if (ch == '\'') {
   1200                 this.column += 2;
   1201                 stream.write("''");
   1202                 start = end + 1;
   1203             }
   1204             if (ch != 0) {
   1205                 spaces = ch == ' ';
   1206                 breaks = Constant.LINEBR.has(ch);
   1207             }
   1208             end++;
   1209         }
   1210         writeIndicator("'", false, false, false);
   1211     }
   1212 
   1213     private void writeDoubleQuoted(String text, boolean split) throws IOException {
   1214         writeIndicator("\"", true, false, false);
   1215         int start = 0;
   1216         int end = 0;
   1217         while (end <= text.length()) {
   1218             Character ch = null;
   1219             if (end < text.length()) {
   1220                 ch = text.charAt(end);
   1221             }
   1222             if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1
   1223                     || !('\u0020' <= ch && ch <= '\u007E')) {
   1224                 if (start < end) {
   1225                     int len = end - start;
   1226                     this.column += len;
   1227                     stream.write(text, start, len);
   1228                     start = end;
   1229                 }
   1230                 if (ch != null) {
   1231                     String data;
   1232                     if (ESCAPE_REPLACEMENTS.containsKey(ch)) {
   1233                         data = "\\" + ESCAPE_REPLACEMENTS.get(ch);
   1234                     } else if (!this.allowUnicode || !StreamReader.isPrintable(ch)) {
   1235                         // if !allowUnicode or the character is not printable,
   1236                         // we must encode it
   1237                         if (ch <= '\u00FF') {
   1238                             String s = "0" + Integer.toString(ch, 16);
   1239                             data = "\\x" + s.substring(s.length() - 2);
   1240                         } else if (ch >= '\uD800' && ch <= '\uDBFF') {
   1241                             if (end + 1 < text.length()) {
   1242                                 Character ch2 = text.charAt(++end);
   1243                                 String s = "000" + Long.toHexString(Character.toCodePoint(ch, ch2));
   1244                                 data = "\\U" + s.substring(s.length() - 8);
   1245                             } else {
   1246                                 String s = "000" + Integer.toString(ch, 16);
   1247                                 data = "\\u" + s.substring(s.length() - 4);
   1248                             }
   1249                         } else {
   1250                             String s = "000" + Integer.toString(ch, 16);
   1251                             data = "\\u" + s.substring(s.length() - 4);
   1252                         }
   1253                     } else {
   1254                         data = String.valueOf(ch);
   1255                     }
   1256                     this.column += data.length();
   1257                     stream.write(data);
   1258                     start = end + 1;
   1259                 }
   1260             }
   1261             if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end)
   1262                     && (this.column + (end - start)) > this.bestWidth && split) {
   1263                 String data;
   1264                 if (start >= end) {
   1265                     data = "\\";
   1266                 } else {
   1267                     data = text.substring(start, end) + "\\";
   1268                 }
   1269                 if (start < end) {
   1270                     start = end;
   1271                 }
   1272                 this.column += data.length();
   1273                 stream.write(data);
   1274                 writeIndent();
   1275                 this.whitespace = false;
   1276                 this.indention = false;
   1277                 if (text.charAt(start) == ' ') {
   1278                     data = "\\";
   1279                     this.column += data.length();
   1280                     stream.write(data);
   1281                 }
   1282             }
   1283             end += 1;
   1284         }
   1285         writeIndicator("\"", false, false, false);
   1286     }
   1287 
   1288     private String determineBlockHints(String text) {
   1289         StringBuilder hints = new StringBuilder();
   1290         if (Constant.LINEBR.has(text.charAt(0), " ")) {
   1291             hints.append(bestIndent);
   1292         }
   1293         char ch1 = text.charAt(text.length() - 1);
   1294         if (Constant.LINEBR.hasNo(ch1)) {
   1295             hints.append("-");
   1296         } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) {
   1297             hints.append("+");
   1298         }
   1299         return hints.toString();
   1300     }
   1301 
   1302     void writeFolded(String text, boolean split) throws IOException {
   1303         String hints = determineBlockHints(text);
   1304         writeIndicator(">" + hints, true, false, false);
   1305         if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) {
   1306             openEnded = true;
   1307         }
   1308         writeLineBreak(null);
   1309         boolean leadingSpace = true;
   1310         boolean spaces = false;
   1311         boolean breaks = true;
   1312         int start = 0, end = 0;
   1313         while (end <= text.length()) {
   1314             char ch = 0;
   1315             if (end < text.length()) {
   1316                 ch = text.charAt(end);
   1317             }
   1318             if (breaks) {
   1319                 if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
   1320                     if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') {
   1321                         writeLineBreak(null);
   1322                     }
   1323                     leadingSpace = ch == ' ';
   1324                     String data = text.substring(start, end);
   1325                     for (char br : data.toCharArray()) {
   1326                         if (br == '\n') {
   1327                             writeLineBreak(null);
   1328                         } else {
   1329                             writeLineBreak(String.valueOf(br));
   1330                         }
   1331                     }
   1332                     if (ch != 0) {
   1333                         writeIndent();
   1334                     }
   1335                     start = end;
   1336                 }
   1337             } else if (spaces) {
   1338                 if (ch != ' ') {
   1339                     if (start + 1 == end && this.column > this.bestWidth && split) {
   1340                         writeIndent();
   1341                     } else {
   1342                         int len = end - start;
   1343                         this.column += len;
   1344                         stream.write(text, start, len);
   1345                     }
   1346                     start = end;
   1347                 }
   1348             } else {
   1349                 if (Constant.LINEBR.has(ch, "\0 ")) {
   1350                     int len = end - start;
   1351                     this.column += len;
   1352                     stream.write(text, start, len);
   1353                     if (ch == 0) {
   1354                         writeLineBreak(null);
   1355                     }
   1356                     start = end;
   1357                 }
   1358             }
   1359             if (ch != 0) {
   1360                 breaks = Constant.LINEBR.has(ch);
   1361                 spaces = ch == ' ';
   1362             }
   1363             end++;
   1364         }
   1365     }
   1366 
   1367     void writeLiteral(String text) throws IOException {
   1368         String hints = determineBlockHints(text);
   1369         writeIndicator("|" + hints, true, false, false);
   1370         if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') {
   1371             openEnded = true;
   1372         }
   1373         writeLineBreak(null);
   1374         boolean breaks = true;
   1375         int start = 0, end = 0;
   1376         while (end <= text.length()) {
   1377             char ch = 0;
   1378             if (end < text.length()) {
   1379                 ch = text.charAt(end);
   1380             }
   1381             if (breaks) {
   1382                 if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
   1383                     String data = text.substring(start, end);
   1384                     for (char br : data.toCharArray()) {
   1385                         if (br == '\n') {
   1386                             writeLineBreak(null);
   1387                         } else {
   1388                             writeLineBreak(String.valueOf(br));
   1389                         }
   1390                     }
   1391                     if (ch != 0) {
   1392                         writeIndent();
   1393                     }
   1394                     start = end;
   1395                 }
   1396             } else {
   1397                 if (ch == 0 || Constant.LINEBR.has(ch)) {
   1398                     stream.write(text, start, end - start);
   1399                     if (ch == 0) {
   1400                         writeLineBreak(null);
   1401                     }
   1402                     start = end;
   1403                 }
   1404             }
   1405             if (ch != 0) {
   1406                 breaks = Constant.LINEBR.has(ch);
   1407             }
   1408             end++;
   1409         }
   1410     }
   1411 
   1412     void writePlain(String text, boolean split) throws IOException {
   1413         if (rootContext) {
   1414             openEnded = true;
   1415         }
   1416         if (text.length() == 0) {
   1417             return;
   1418         }
   1419         if (!this.whitespace) {
   1420             this.column++;
   1421             stream.write(SPACE);
   1422         }
   1423         this.whitespace = false;
   1424         this.indention = false;
   1425         boolean spaces = false;
   1426         boolean breaks = false;
   1427         int start = 0, end = 0;
   1428         while (end <= text.length()) {
   1429             char ch = 0;
   1430             if (end < text.length()) {
   1431                 ch = text.charAt(end);
   1432             }
   1433             if (spaces) {
   1434                 if (ch != ' ') {
   1435                     if (start + 1 == end && this.column > this.bestWidth && split) {
   1436                         writeIndent();
   1437                         this.whitespace = false;
   1438                         this.indention = false;
   1439                     } else {
   1440                         int len = end - start;
   1441                         this.column += len;
   1442                         stream.write(text, start, len);
   1443                     }
   1444                     start = end;
   1445                 }
   1446             } else if (breaks) {
   1447                 if (Constant.LINEBR.hasNo(ch)) {
   1448                     if (text.charAt(start) == '\n') {
   1449                         writeLineBreak(null);
   1450                     }
   1451                     String data = text.substring(start, end);
   1452                     for (char br : data.toCharArray()) {
   1453                         if (br == '\n') {
   1454                             writeLineBreak(null);
   1455                         } else {
   1456                             writeLineBreak(String.valueOf(br));
   1457                         }
   1458                     }
   1459                     writeIndent();
   1460                     this.whitespace = false;
   1461                     this.indention = false;
   1462                     start = end;
   1463                 }
   1464             } else {
   1465                 if (ch == 0 || Constant.LINEBR.has(ch)) {
   1466                     int len = end - start;
   1467                     this.column += len;
   1468                     stream.write(text, start, len);
   1469                     start = end;
   1470                 }
   1471             }
   1472             if (ch != 0) {
   1473                 spaces = ch == ' ';
   1474                 breaks = Constant.LINEBR.has(ch);
   1475             }
   1476             end++;
   1477         }
   1478     }
   1479 }
   1480