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