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