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