001/**
002 *   Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
003 */
004package com.typesafe.config;
005
006
007/**
008 * A set of options related to parsing.
009 *
010 * <p>
011 * This object is immutable, so the "setters" return a new object.
012 *
013 * <p>
014 * Here is an example of creating a custom {@code ConfigParseOptions}:
015 *
016 * <pre>
017 *     ConfigParseOptions options = ConfigParseOptions.defaults()
018 *         .setSyntax(ConfigSyntax.JSON)
019 *         .setAllowMissing(false)
020 * </pre>
021 *
022 */
023public final class ConfigParseOptions {
024    final ConfigSyntax syntax;
025    final String originDescription;
026    final boolean allowMissing;
027    final ConfigIncluder includer;
028    final ClassLoader classLoader;
029
030    private ConfigParseOptions(ConfigSyntax syntax, String originDescription, boolean allowMissing,
031            ConfigIncluder includer, ClassLoader classLoader) {
032        this.syntax = syntax;
033        this.originDescription = originDescription;
034        this.allowMissing = allowMissing;
035        this.includer = includer;
036        this.classLoader = classLoader;
037    }
038
039    /**
040     * Gets an instance of <code>ConfigParseOptions</code> with all fields
041     * set to the default values. Start with this instance and make any
042     * changes you need.
043     * @return the default parse options
044     */
045    public static ConfigParseOptions defaults() {
046        return new ConfigParseOptions(null, null, true, null, null);
047    }
048
049    /**
050     * Set the file format. If set to null, try to guess from any available
051     * filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}.
052     *
053     * @param syntax
054     *            a syntax or {@code null} for best guess
055     * @return options with the syntax set
056     */
057    public ConfigParseOptions setSyntax(ConfigSyntax syntax) {
058        if (this.syntax == syntax)
059            return this;
060        else
061            return new ConfigParseOptions(syntax, this.originDescription, this.allowMissing,
062                    this.includer, this.classLoader);
063    }
064
065    /**
066     * Gets the current syntax option, which may be null for "any".
067     * @return the current syntax or null
068     */
069    public ConfigSyntax getSyntax() {
070        return syntax;
071    }
072
073    /**
074     * Set a description for the thing being parsed. In most cases this will be
075     * set up for you to something like the filename, but if you provide just an
076     * input stream you might want to improve on it. Set to null to allow the
077     * library to come up with something automatically. This description is the
078     * basis for the {@link ConfigOrigin} of the parsed values.
079     *
080     * @param originDescription description to put in the {@link ConfigOrigin}
081     * @return options with the origin description set
082     */
083    public ConfigParseOptions setOriginDescription(String originDescription) {
084        // findbugs complains about == here but is wrong, do not "fix"
085        if (this.originDescription == originDescription)
086            return this;
087        else if (this.originDescription != null && originDescription != null
088                && this.originDescription.equals(originDescription))
089            return this;
090        else
091            return new ConfigParseOptions(this.syntax, originDescription, this.allowMissing,
092                    this.includer, this.classLoader);
093    }
094
095    /**
096     * Gets the current origin description, which may be null for "automatic".
097     * @return the current origin description or null
098     */
099    public String getOriginDescription() {
100        return originDescription;
101    }
102
103    /** this is package-private, not public API */
104    ConfigParseOptions withFallbackOriginDescription(String originDescription) {
105        if (this.originDescription == null)
106            return setOriginDescription(originDescription);
107        else
108            return this;
109    }
110
111    /**
112     * Set to false to throw an exception if the item being parsed (for example
113     * a file) is missing. Set to true to just return an empty document in that
114     * case. Note that this setting applies on only to fetching the root document,
115     * it has no effect on any nested includes.
116     *
117     * @param allowMissing true to silently ignore missing item
118     * @return options with the "allow missing" flag set
119     */
120    public ConfigParseOptions setAllowMissing(boolean allowMissing) {
121        if (this.allowMissing == allowMissing)
122            return this;
123        else
124            return new ConfigParseOptions(this.syntax, this.originDescription, allowMissing,
125                    this.includer, this.classLoader);
126    }
127
128    /**
129     * Gets the current "allow missing" flag.
130     * @return whether we allow missing files
131     */
132    public boolean getAllowMissing() {
133        return allowMissing;
134    }
135
136    /**
137     * Set a {@link ConfigIncluder} which customizes how includes are handled.
138     * null means to use the default includer.
139     *
140     * @param includer the includer to use or null for default
141     * @return new version of the parse options with different includer
142     */
143    public ConfigParseOptions setIncluder(ConfigIncluder includer) {
144        if (this.includer == includer)
145            return this;
146        else
147            return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing,
148                    includer, this.classLoader);
149    }
150
151    /**
152     * Prepends a {@link ConfigIncluder} which customizes how
153     * includes are handled.  To prepend your includer, the
154     * library calls {@link ConfigIncluder#withFallback} on your
155     * includer to append the existing includer to it.
156     *
157     * @param includer the includer to prepend (may not be null)
158     * @return new version of the parse options with different includer
159     */
160    public ConfigParseOptions prependIncluder(ConfigIncluder includer) {
161        if (includer == null)
162            throw new NullPointerException("null includer passed to prependIncluder");
163        if (this.includer == includer)
164            return this;
165        else if (this.includer != null)
166            return setIncluder(includer.withFallback(this.includer));
167        else
168            return setIncluder(includer);
169    }
170
171    /**
172     * Appends a {@link ConfigIncluder} which customizes how
173     * includes are handled.  To append, the library calls {@link
174     * ConfigIncluder#withFallback} on the existing includer.
175     *
176     * @param includer the includer to append (may not be null)
177     * @return new version of the parse options with different includer
178     */
179    public ConfigParseOptions appendIncluder(ConfigIncluder includer) {
180        if (includer == null)
181            throw new NullPointerException("null includer passed to appendIncluder");
182        if (this.includer == includer)
183            return this;
184        else if (this.includer != null)
185            return setIncluder(this.includer.withFallback(includer));
186        else
187            return setIncluder(includer);
188    }
189
190    /**
191     * Gets the current includer (will be null for the default includer).
192     * @return current includer or null
193     */
194    public ConfigIncluder getIncluder() {
195        return includer;
196    }
197
198    /**
199     * Set the class loader. If set to null,
200     * <code>Thread.currentThread().getContextClassLoader()</code> will be used.
201     *
202     * @param loader
203     *            a class loader or {@code null} to use thread context class
204     *            loader
205     * @return options with the class loader set
206     */
207    public ConfigParseOptions setClassLoader(ClassLoader loader) {
208        if (this.classLoader == loader)
209            return this;
210        else
211            return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing,
212                    this.includer, loader);
213    }
214
215    /**
216     * Get the class loader; never returns {@code null}, if the class loader was
217     * unset, returns
218     * <code>Thread.currentThread().getContextClassLoader()</code>.
219     *
220     * @return class loader to use
221     */
222    public ClassLoader getClassLoader() {
223        if (this.classLoader == null)
224            return Thread.currentThread().getContextClassLoader();
225        else
226            return this.classLoader;
227    }
228}