    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}