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.
115     *
116     * @param allowMissing true to silently ignore missing item
117     * @return options with the "allow missing" flag set
118     */
119    public ConfigParseOptions setAllowMissing(boolean allowMissing) {
120        if (this.allowMissing == allowMissing)
121            return this;
122        else
123            return new ConfigParseOptions(this.syntax, this.originDescription, allowMissing,
124                    this.includer, this.classLoader);
125    }
126
127    /**
128     * Gets the current "allow missing" flag.
129     * @return whether we allow missing files
130     */
131    public boolean getAllowMissing() {
132        return allowMissing;
133    }
134
135    /**
136     * Set a {@link ConfigIncluder} which customizes how includes are handled.
137     * null means to use the default includer.
138     *
139     * @param includer the includer to use or null for default
140     * @return new version of the parse options with different includer
141     */
142    public ConfigParseOptions setIncluder(ConfigIncluder includer) {
143        if (this.includer == includer)
144            return this;
145        else
146            return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing,
147                    includer, this.classLoader);
148    }
149
150    /**
151     * Prepends a {@link ConfigIncluder} which customizes how
152     * includes are handled.  To prepend your includer, the
153     * library calls {@link ConfigIncluder#withFallback} on your
154     * includer to append the existing includer to it.
155     *
156     * @param includer the includer to prepend (may not be null)
157     * @return new version of the parse options with different includer
158     */
159    public ConfigParseOptions prependIncluder(ConfigIncluder includer) {
160        if (includer == null)
161            throw new NullPointerException("null includer passed to prependIncluder");
162        if (this.includer == includer)
163            return this;
164        else if (this.includer != null)
165            return setIncluder(includer.withFallback(this.includer));
166        else
167            return setIncluder(includer);
168    }
169
170    /**
171     * Appends a {@link ConfigIncluder} which customizes how
172     * includes are handled.  To append, the library calls {@link
173     * ConfigIncluder#withFallback} on the existing includer.
174     *
175     * @param includer the includer to append (may not be null)
176     * @return new version of the parse options with different includer
177     */
178    public ConfigParseOptions appendIncluder(ConfigIncluder includer) {
179        if (includer == null)
180            throw new NullPointerException("null includer passed to appendIncluder");
181        if (this.includer == includer)
182            return this;
183        else if (this.includer != null)
184            return setIncluder(this.includer.withFallback(includer));
185        else
186            return setIncluder(includer);
187    }
188
189    /**
190     * Gets the current includer (will be null for the default includer).
191     * @return current includer or null
192     */
193    public ConfigIncluder getIncluder() {
194        return includer;
195    }
196
197    /**
198     * Set the class loader. If set to null,
199     * <code>Thread.currentThread().getContextClassLoader()</code> will be used.
200     *
201     * @param loader
202     *            a class loader or {@code null} to use thread context class
203     *            loader
204     * @return options with the class loader set
205     */
206    public ConfigParseOptions setClassLoader(ClassLoader loader) {
207        if (this.classLoader == loader)
208            return this;
209        else
210            return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing,
211                    this.includer, loader);
212    }
213
214    /**
215     * Get the class loader; never returns {@code null}, if the class loader was
216     * unset, returns
217     * <code>Thread.currentThread().getContextClassLoader()</code>.
218     *
219     * @return class loader to use
220     */
221    public ClassLoader getClassLoader() {
222        if (this.classLoader == null)
223            return Thread.currentThread().getContextClassLoader();
224        else
225            return this.classLoader;
226    }
227}