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