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;
17
18import java.io.IOException;
19import java.io.InputStream;
20import java.io.Reader;
21import java.io.StringReader;
22import java.io.StringWriter;
23import java.io.Writer;
24import java.util.ArrayList;
25import java.util.Iterator;
26import java.util.List;
27import java.util.regex.Pattern;
28
29import org.yaml.snakeyaml.DumperOptions.FlowStyle;
30import org.yaml.snakeyaml.composer.Composer;
31import org.yaml.snakeyaml.constructor.BaseConstructor;
32import org.yaml.snakeyaml.constructor.Constructor;
33import org.yaml.snakeyaml.emitter.Emitable;
34import org.yaml.snakeyaml.emitter.Emitter;
35import org.yaml.snakeyaml.error.YAMLException;
36import org.yaml.snakeyaml.events.Event;
37import org.yaml.snakeyaml.introspector.BeanAccess;
38import org.yaml.snakeyaml.nodes.Node;
39import org.yaml.snakeyaml.nodes.Tag;
40import org.yaml.snakeyaml.parser.Parser;
41import org.yaml.snakeyaml.parser.ParserImpl;
42import org.yaml.snakeyaml.reader.StreamReader;
43import org.yaml.snakeyaml.reader.UnicodeReader;
44import org.yaml.snakeyaml.representer.Representer;
45import org.yaml.snakeyaml.resolver.Resolver;
46import org.yaml.snakeyaml.serializer.Serializer;
47
48/**
49 * Public YAML interface. Each Thread must have its own instance.
50 */
51public class Yaml {
52    protected final Resolver resolver;
53    private String name;
54    protected BaseConstructor constructor;
55    protected Representer representer;
56    protected DumperOptions dumperOptions;
57
58    /**
59     * Create Yaml instance. It is safe to create a few instances and use them
60     * in different Threads.
61     */
62    public Yaml() {
63        this(new Constructor(), new Representer(), new DumperOptions(), new Resolver());
64    }
65
66    /**
67     * Create Yaml instance.
68     *
69     * @param dumperOptions
70     *            DumperOptions to configure outgoing objects
71     */
72    public Yaml(DumperOptions dumperOptions) {
73        this(new Constructor(), new Representer(), dumperOptions);
74    }
75
76    /**
77     * Create Yaml instance. It is safe to create a few instances and use them
78     * in different Threads.
79     *
80     * @param representer
81     *            Representer to emit outgoing objects
82     */
83    public Yaml(Representer representer) {
84        this(new Constructor(), representer);
85    }
86
87    /**
88     * Create Yaml instance. It is safe to create a few instances and use them
89     * in different Threads.
90     *
91     * @param constructor
92     *            BaseConstructor to construct incoming documents
93     */
94    public Yaml(BaseConstructor constructor) {
95        this(constructor, new Representer());
96    }
97
98    /**
99     * Create Yaml instance. It is safe to create a few instances and use them
100     * in different Threads.
101     *
102     * @param constructor
103     *            BaseConstructor to construct incoming documents
104     * @param representer
105     *            Representer to emit outgoing objects
106     */
107    public Yaml(BaseConstructor constructor, Representer representer) {
108        this(constructor, representer, new DumperOptions());
109    }
110
111    /**
112     * Create Yaml instance. It is safe to create a few instances and use them
113     * in different Threads.
114     *
115     * @param representer
116     *            Representer to emit outgoing objects
117     * @param dumperOptions
118     *            DumperOptions to configure outgoing objects
119     */
120    public Yaml(Representer representer, DumperOptions dumperOptions) {
121        this(new Constructor(), representer, dumperOptions, new Resolver());
122    }
123
124    /**
125     * Create Yaml instance. It is safe to create a few instances and use them
126     * in different Threads.
127     *
128     * @param constructor
129     *            BaseConstructor to construct incoming documents
130     * @param representer
131     *            Representer to emit outgoing objects
132     * @param dumperOptions
133     *            DumperOptions to configure outgoing objects
134     */
135    public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions) {
136        this(constructor, representer, dumperOptions, new Resolver());
137    }
138
139    /**
140     * Create Yaml instance. It is safe to create a few instances and use them
141     * in different Threads.
142     *
143     * @param constructor
144     *            BaseConstructor to construct incoming documents
145     * @param representer
146     *            Representer to emit outgoing objects
147     * @param dumperOptions
148     *            DumperOptions to configure outgoing objects
149     * @param resolver
150     *            Resolver to detect implicit type
151     */
152    public Yaml(BaseConstructor constructor, Representer representer, DumperOptions dumperOptions,
153            Resolver resolver) {
154        if (!constructor.isExplicitPropertyUtils()) {
155            constructor.setPropertyUtils(representer.getPropertyUtils());
156        } else if (!representer.isExplicitPropertyUtils()) {
157            representer.setPropertyUtils(constructor.getPropertyUtils());
158        }
159        this.constructor = constructor;
160        representer.setDefaultFlowStyle(dumperOptions.getDefaultFlowStyle());
161        representer.setDefaultScalarStyle(dumperOptions.getDefaultScalarStyle());
162        representer.getPropertyUtils().setAllowReadOnlyProperties(
163                dumperOptions.isAllowReadOnlyProperties());
164        representer.setTimeZone(dumperOptions.getTimeZone());
165        this.representer = representer;
166        this.dumperOptions = dumperOptions;
167        this.resolver = resolver;
168        this.name = "Yaml:" + System.identityHashCode(this);
169    }
170
171    /**
172     * Serialize a Java object into a YAML String.
173     *
174     * @param data
175     *            Java object to be Serialized to YAML
176     * @return YAML String
177     */
178    public String dump(Object data) {
179        List<Object> list = new ArrayList<Object>(1);
180        list.add(data);
181        return dumpAll(list.iterator());
182    }
183
184    /**
185     * Produce the corresponding representation tree for a given Object.
186     *
187     * @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing
188     *      Overview</a>
189     * @param data
190     *            instance to build the representation tree for
191     * @return representation tree
192     */
193    public Node represent(Object data) {
194        return representer.represent(data);
195    }
196
197    /**
198     * Serialize a sequence of Java objects into a YAML String.
199     *
200     * @param data
201     *            Iterator with Objects
202     * @return YAML String with all the objects in proper sequence
203     */
204    public String dumpAll(Iterator<? extends Object> data) {
205        StringWriter buffer = new StringWriter();
206        dumpAll(data, buffer, null);
207        return buffer.toString();
208    }
209
210    /**
211     * Serialize a Java object into a YAML stream.
212     *
213     * @param data
214     *            Java object to be serialized to YAML
215     * @param output
216     *            stream to write to
217     */
218    public void dump(Object data, Writer output) {
219        List<Object> list = new ArrayList<Object>(1);
220        list.add(data);
221        dumpAll(list.iterator(), output, null);
222    }
223
224    /**
225     * Serialize a sequence of Java objects into a YAML stream.
226     *
227     * @param data
228     *            Iterator with Objects
229     * @param output
230     *            stream to write to
231     */
232    public void dumpAll(Iterator<? extends Object> data, Writer output) {
233        dumpAll(data, output, null);
234    }
235
236    private void dumpAll(Iterator<? extends Object> data, Writer output, Tag rootTag) {
237        Serializer serializer = new Serializer(new Emitter(output, dumperOptions), resolver,
238                dumperOptions, rootTag);
239        try {
240            serializer.open();
241            while (data.hasNext()) {
242                Node node = representer.represent(data.next());
243                serializer.serialize(node);
244            }
245            serializer.close();
246        } catch (IOException e) {
247            throw new YAMLException(e);
248        }
249    }
250
251    /**
252     * <p>
253     * Serialize a Java object into a YAML string. Override the default root tag
254     * with <code>rootTag</code>.
255     * </p>
256     *
257     * <p>
258     * This method is similar to <code>Yaml.dump(data)</code> except that the
259     * root tag for the whole document is replaced with the given tag. This has
260     * two main uses.
261     * </p>
262     *
263     * <p>
264     * First, if the root tag is replaced with a standard YAML tag, such as
265     * <code>Tag.MAP</code>, then the object will be dumped as a map. The root
266     * tag will appear as <code>!!map</code>, or blank (implicit !!map).
267     * </p>
268     *
269     * <p>
270     * Second, if the root tag is replaced by a different custom tag, then the
271     * document appears to be a different type when loaded. For example, if an
272     * instance of MyClass is dumped with the tag !!YourClass, then it will be
273     * handled as an instance of YourClass when loaded.
274     * </p>
275     *
276     * @param data
277     *            Java object to be serialized to YAML
278     * @param rootTag
279     *            the tag for the whole YAML document. The tag should be Tag.MAP
280     *            for a JavaBean to make the tag disappear (to use implicit tag
281     *            !!map). If <code>null</code> is provided then the standard tag
282     *            with the full class name is used.
283     * @param flowStyle
284     *            flow style for the whole document. See Chapter 10. Collection
285     *            Styles http://yaml.org/spec/1.1/#id930798. If
286     *            <code>null</code> is provided then the flow style from
287     *            DumperOptions is used.
288     *
289     * @return YAML String
290     */
291    public String dumpAs(Object data, Tag rootTag, FlowStyle flowStyle) {
292        FlowStyle oldStyle = representer.getDefaultFlowStyle();
293        if (flowStyle != null) {
294            representer.setDefaultFlowStyle(flowStyle);
295        }
296        List<Object> list = new ArrayList<Object>(1);
297        list.add(data);
298        StringWriter buffer = new StringWriter();
299        dumpAll(list.iterator(), buffer, rootTag);
300        representer.setDefaultFlowStyle(oldStyle);
301        return buffer.toString();
302    }
303
304    /**
305     * <p>
306     * Serialize a Java object into a YAML string. Override the default root tag
307     * with <code>Tag.MAP</code>.
308     * </p>
309     * <p>
310     * This method is similar to <code>Yaml.dump(data)</code> except that the
311     * root tag for the whole document is replaced with <code>Tag.MAP</code> tag
312     * (implicit !!map).
313     * </p>
314     * <p>
315     * Block Mapping is used as the collection style. See 10.2.2. Block Mappings
316     * (http://yaml.org/spec/1.1/#id934537)
317     * </p>
318     *
319     * @param data
320     *            Java object to be serialized to YAML
321     * @return YAML String
322     */
323    public String dumpAsMap(Object data) {
324        return dumpAs(data, Tag.MAP, FlowStyle.BLOCK);
325    }
326
327    /**
328     * Serialize the representation tree into Events.
329     *
330     * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
331     * @param data
332     *            representation tree
333     * @return Event list
334     */
335    public List<Event> serialize(Node data) {
336        SilentEmitter emitter = new SilentEmitter();
337        Serializer serializer = new Serializer(emitter, resolver, dumperOptions, null);
338        try {
339            serializer.open();
340            serializer.serialize(data);
341            serializer.close();
342        } catch (IOException e) {
343            throw new YAMLException(e);
344        }
345        return emitter.getEvents();
346    }
347
348    private static class SilentEmitter implements Emitable {
349        private List<Event> events = new ArrayList<Event>(100);
350
351        public List<Event> getEvents() {
352            return events;
353        }
354
355        public void emit(Event event) throws IOException {
356            events.add(event);
357        }
358    }
359
360    /**
361     * Parse the only YAML document in a String and produce the corresponding
362     * Java object. (Because the encoding in known BOM is not respected.)
363     *
364     * @param yaml
365     *            YAML data to load from (BOM must not be present)
366     * @return parsed object
367     */
368    public Object load(String yaml) {
369        return loadFromReader(new StreamReader(yaml), Object.class);
370    }
371
372    /**
373     * Parse the only YAML document in a stream and produce the corresponding
374     * Java object.
375     *
376     * @param io
377     *            data to load from (BOM is respected and removed)
378     * @return parsed object
379     */
380    public Object load(InputStream io) {
381        return loadFromReader(new StreamReader(new UnicodeReader(io)), Object.class);
382    }
383
384    /**
385     * Parse the only YAML document in a stream and produce the corresponding
386     * Java object.
387     *
388     * @param io
389     *            data to load from (BOM must not be present)
390     * @return parsed object
391     */
392    public Object load(Reader io) {
393        return loadFromReader(new StreamReader(io), Object.class);
394    }
395
396    /**
397     * Parse the only YAML document in a stream and produce the corresponding
398     * Java object.
399     *
400     * @param <T>
401     *            Class is defined by the second argument
402     * @param io
403     *            data to load from (BOM must not be present)
404     * @param type
405     *            Class of the object to be created
406     * @return parsed object
407     */
408    @SuppressWarnings("unchecked")
409    public <T> T loadAs(Reader io, Class<T> type) {
410        return (T) loadFromReader(new StreamReader(io), type);
411    }
412
413    /**
414     * Parse the only YAML document in a String and produce the corresponding
415     * Java object. (Because the encoding in known BOM is not respected.)
416     *
417     * @param <T>
418     *            Class is defined by the second argument
419     * @param yaml
420     *            YAML data to load from (BOM must not be present)
421     * @param type
422     *            Class of the object to be created
423     * @return parsed object
424     */
425    @SuppressWarnings("unchecked")
426    public <T> T loadAs(String yaml, Class<T> type) {
427        return (T) loadFromReader(new StreamReader(yaml), type);
428    }
429
430    /**
431     * Parse the only YAML document in a stream and produce the corresponding
432     * Java object.
433     *
434     * @param <T>
435     *            Class is defined by the second argument
436     * @param input
437     *            data to load from (BOM is respected and removed)
438     * @param type
439     *            Class of the object to be created
440     * @return parsed object
441     */
442    @SuppressWarnings("unchecked")
443    public <T> T loadAs(InputStream input, Class<T> type) {
444        return (T) loadFromReader(new StreamReader(new UnicodeReader(input)), type);
445    }
446
447    private Object loadFromReader(StreamReader sreader, Class<?> type) {
448        Composer composer = new Composer(new ParserImpl(sreader), resolver);
449        constructor.setComposer(composer);
450        return constructor.getSingleData(type);
451    }
452
453    /**
454     * Parse all YAML documents in a String and produce corresponding Java
455     * objects. The documents are parsed only when the iterator is invoked.
456     *
457     * @param yaml
458     *            YAML data to load from (BOM must not be present)
459     * @return an iterator over the parsed Java objects in this String in proper
460     *         sequence
461     */
462    public Iterable<Object> loadAll(Reader yaml) {
463        Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
464        constructor.setComposer(composer);
465        Iterator<Object> result = new Iterator<Object>() {
466            public boolean hasNext() {
467                return constructor.checkData();
468            }
469
470            public Object next() {
471                return constructor.getData();
472            }
473
474            public void remove() {
475                throw new UnsupportedOperationException();
476            }
477        };
478        return new YamlIterable(result);
479    }
480
481    private static class YamlIterable implements Iterable<Object> {
482        private Iterator<Object> iterator;
483
484        public YamlIterable(Iterator<Object> iterator) {
485            this.iterator = iterator;
486        }
487
488        public Iterator<Object> iterator() {
489            return iterator;
490        }
491    }
492
493    /**
494     * Parse all YAML documents in a String and produce corresponding Java
495     * objects. (Because the encoding in known BOM is not respected.) The
496     * documents are parsed only when the iterator is invoked.
497     *
498     * @param yaml
499     *            YAML data to load from (BOM must not be present)
500     * @return an iterator over the parsed Java objects in this String in proper
501     *         sequence
502     */
503    public Iterable<Object> loadAll(String yaml) {
504        return loadAll(new StringReader(yaml));
505    }
506
507    /**
508     * Parse all YAML documents in a stream and produce corresponding Java
509     * objects. The documents are parsed only when the iterator is invoked.
510     *
511     * @param yaml
512     *            YAML data to load from (BOM is respected and ignored)
513     * @return an iterator over the parsed Java objects in this stream in proper
514     *         sequence
515     */
516    public Iterable<Object> loadAll(InputStream yaml) {
517        return loadAll(new UnicodeReader(yaml));
518    }
519
520    /**
521     * Parse the first YAML document in a stream and produce the corresponding
522     * representation tree. (This is the opposite of the represent() method)
523     *
524     * @see <a href="http://yaml.org/spec/1.1/#id859333">Figure 3.1. Processing
525     *      Overview</a>
526     * @param yaml
527     *            YAML document
528     * @return parsed root Node for the specified YAML document
529     */
530    public Node compose(Reader yaml) {
531        Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
532        constructor.setComposer(composer);
533        return composer.getSingleNode();
534    }
535
536    /**
537     * Parse all YAML documents in a stream and produce corresponding
538     * representation trees.
539     *
540     * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
541     * @param yaml
542     *            stream of YAML documents
543     * @return parsed root Nodes for all the specified YAML documents
544     */
545    public Iterable<Node> composeAll(Reader yaml) {
546        final Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver);
547        constructor.setComposer(composer);
548        Iterator<Node> result = new Iterator<Node>() {
549            public boolean hasNext() {
550                return composer.checkNode();
551            }
552
553            public Node next() {
554                return composer.getNode();
555            }
556
557            public void remove() {
558                throw new UnsupportedOperationException();
559            }
560        };
561        return new NodeIterable(result);
562    }
563
564    private static class NodeIterable implements Iterable<Node> {
565        private Iterator<Node> iterator;
566
567        public NodeIterable(Iterator<Node> iterator) {
568            this.iterator = iterator;
569        }
570
571        public Iterator<Node> iterator() {
572            return iterator;
573        }
574    }
575
576    /**
577     * Add an implicit scalar detector. If an implicit scalar value matches the
578     * given regexp, the corresponding tag is assigned to the scalar.
579     *
580     * @param tag
581     *            tag to assign to the node
582     * @param regexp
583     *            regular expression to match against
584     * @param first
585     *            a sequence of possible initial characters or null (which means
586     *            any).
587     */
588    public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
589        resolver.addImplicitResolver(tag, regexp, first);
590    }
591
592    @Override
593    public String toString() {
594        return name;
595    }
596
597    /**
598     * Get a meaningful name. It simplifies debugging in a multi-threaded
599     * environment. If nothing is set explicitly the address of the instance is
600     * returned.
601     *
602     * @return human readable name
603     */
604    public String getName() {
605        return name;
606    }
607
608    /**
609     * Set a meaningful name to be shown in toString()
610     *
611     * @param name
612     *            human readable name
613     */
614    public void setName(String name) {
615        this.name = name;
616    }
617
618    /**
619     * Parse a YAML stream and produce parsing events.
620     *
621     * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a>
622     * @param yaml
623     *            YAML document(s)
624     * @return parsed events
625     */
626    public Iterable<Event> parse(Reader yaml) {
627        final Parser parser = new ParserImpl(new StreamReader(yaml));
628        Iterator<Event> result = new Iterator<Event>() {
629            public boolean hasNext() {
630                return parser.peekEvent() != null;
631            }
632
633            public Event next() {
634                return parser.getEvent();
635            }
636
637            public void remove() {
638                throw new UnsupportedOperationException();
639            }
640        };
641        return new EventIterable(result);
642    }
643
644    private static class EventIterable implements Iterable<Event> {
645        private Iterator<Event> iterator;
646
647        public EventIterable(Iterator<Event> iterator) {
648            this.iterator = iterator;
649        }
650
651        public Iterator<Event> iterator() {
652            return iterator;
653        }
654    }
655
656    public void setBeanAccess(BeanAccess beanAccess) {
657        constructor.getPropertyUtils().setBeanAccess(beanAccess);
658        representer.getPropertyUtils().setBeanAccess(beanAccess);
659    }
660
661}
662