    001/**
    002 *   Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
    003 */
    004package com.typesafe.config;
    005
    006import java.io.IOException;
    007import java.io.Serializable;
    008import java.lang.reflect.Field;
    009
    010import com.typesafe.config.impl.ConfigImplUtil;
    011
    012/**
    013 * All exceptions thrown by the library are subclasses of
    014 * <code>ConfigException</code>.
    015 */
    016public abstract class ConfigException extends RuntimeException implements Serializable {
    017    private static final long serialVersionUID = 1L;
    018
    019    final private transient ConfigOrigin origin;
    020
    021    protected ConfigException(ConfigOrigin origin, String message,
    022            Throwable cause) {
    023        super(origin.description() + ": " + message, cause);
    024        this.origin = origin;
    025    }
    026
    027    protected ConfigException(ConfigOrigin origin, String message) {
    028        this(origin.description() + ": " + message, null);
    029    }
    030
    031    protected ConfigException(String message, Throwable cause) {
    032        super(message, cause);
    033        this.origin = null;
    034    }
    035
    036    protected ConfigException(String message) {
    037        this(message, null);
    038    }
    039
    040    /**
    041     * Returns an "origin" (such as a filename and line number) for the
    042     * exception, or null if none is available. If there's no sensible origin
    043     * for a given exception, or the kind of exception doesn't meaningfully
    044     * relate to a particular origin file, this returns null. Never assume this
    045     * will return non-null, it can always return null.
    046     *
    047     * @return origin of the problem, or null if unknown/inapplicable
    048     */
    049    public ConfigOrigin origin() {
    050        return origin;
    051    }
    052
    053    // we customize serialization because ConfigOrigin isn't
    054    // serializable and we don't want it to be (don't want to
    055    // support it)
    056    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    057        out.defaultWriteObject();
    058        ConfigImplUtil.writeOrigin(out, origin);
    059    }
    060
    061    // For deserialization - uses reflection to set the final origin field on the object
    062    private static <T> void setOriginField(T hasOriginField, Class<T> clazz,
    063            ConfigOrigin origin) throws IOException {
    064        // circumvent "final"
    065        Field f;
    066        try {
    067            f = clazz.getDeclaredField("origin");
    068        } catch (NoSuchFieldException e) {
    069            throw new IOException(clazz.getSimpleName() + " has no origin field?", e);
    070        } catch (SecurityException e) {
    071            throw new IOException("unable to fill out origin field in " +
    072                    clazz.getSimpleName(), e);
    073        }
    074        f.setAccessible(true);
    075        try {
    076            f.set(hasOriginField, origin);
    077        } catch (IllegalArgumentException e) {
    078            throw new IOException("unable to set origin field", e);
    079        } catch (IllegalAccessException e) {
    080            throw new IOException("unable to set origin field", e);
    081        }
    082    }
    083
    084    private void readObject(java.io.ObjectInputStream in) throws IOException,
    085            ClassNotFoundException {
    086        in.defaultReadObject();
    087        ConfigOrigin origin = ConfigImplUtil.readOrigin(in);
    088        setOriginField(this, ConfigException.class, origin);
    089    }
    090
    091    /**
    092     * Exception indicating that the type of a value does not match the type you
    093     * requested.
    094     *
    095     */
    096    public static class WrongType extends ConfigException {
    097        private static final long serialVersionUID = 1L;
    098
    099        public WrongType(ConfigOrigin origin, String path, String expected, String actual,
    100                Throwable cause) {
    101            super(origin, path + " has type " + actual + " rather than " + expected, cause);
    102        }
    103
    104        public WrongType(ConfigOrigin origin, String path, String expected, String actual) {
    105            this(origin, path, expected, actual, null);
    106        }
    107
    108        public WrongType(ConfigOrigin origin, String message, Throwable cause) {
    109            super(origin, message, cause);
    110        }
    111
    112        public WrongType(ConfigOrigin origin, String message) {
    113            super(origin, message, null);
    114        }
    115    }
    116
    117    /**
    118     * Exception indicates that the setting was never set to anything, not even
    119     * null.
    120     */
    121    public static class Missing extends ConfigException {
    122        private static final long serialVersionUID = 1L;
    123
    124        public Missing(String path, Throwable cause) {
    125            super("No configuration setting found for key '" + path + "'",
    126                    cause);
    127        }
    128
    129        public Missing(ConfigOrigin origin, String path) {
    130            this(origin, "No configuration setting found for key '" + path + "'", null);
    131        }
    132
    133        public Missing(String path) {
    134            this(path, null);
    135        }
    136
    137        protected Missing(ConfigOrigin origin, String message, Throwable cause) {
    138            super(origin, message, cause);
    139        }
    140
    141    }
    142
    143    /**
    144     * Exception indicates that the setting was treated as missing because it
    145     * was set to null.
    146     */
    147    public static class Null extends Missing {
    148        private static final long serialVersionUID = 1L;
    149
    150        private static String makeMessage(String path, String expected) {
    151            if (expected != null) {
    152                return "Configuration key '" + path
    153                        + "' is set to null but expected " + expected;
    154            } else {
    155                return "Configuration key '" + path + "' is null";
    156            }
    157        }
    158
    159        public Null(ConfigOrigin origin, String path, String expected,
    160                Throwable cause) {
    161            super(origin, makeMessage(path, expected), cause);
    162        }
    163
    164        public Null(ConfigOrigin origin, String path, String expected) {
    165            this(origin, path, expected, null);
    166        }
    167    }
    168
    169    /**
    170     * Exception indicating that a value was messed up, for example you may have
    171     * asked for a duration and the value can't be sensibly parsed as a
    172     * duration.
    173     *
    174     */
    175    public static class BadValue extends ConfigException {
    176        private static final long serialVersionUID = 1L;
    177
    178        public BadValue(ConfigOrigin origin, String path, String message,
    179                Throwable cause) {
    180            super(origin, "Invalid value at '" + path + "': " + message, cause);
    181        }
    182
    183        public BadValue(ConfigOrigin origin, String path, String message) {
    184            this(origin, path, message, null);
    185        }
    186
    187        public BadValue(String path, String message, Throwable cause) {
    188            super("Invalid value at '" + path + "': " + message, cause);
    189        }
    190
    191        public BadValue(String path, String message) {
    192            this(path, message, null);
    193        }
    194    }
    195
    196    /**
    197     * Exception indicating that a path expression was invalid. Try putting
    198     * double quotes around path elements that contain "special" characters.
    199     *
    200     */
    201    public static class BadPath extends ConfigException {
    202        private static final long serialVersionUID = 1L;
    203
    204        public BadPath(ConfigOrigin origin, String path, String message,
    205                Throwable cause) {
    206            super(origin,
    207                    path != null ? ("Invalid path '" + path + "': " + message)
    208                            : message, cause);
    209        }
    210
    211        public BadPath(ConfigOrigin origin, String path, String message) {
    212            this(origin, path, message, null);
    213        }
    214
    215        public BadPath(String path, String message, Throwable cause) {
    216            super(path != null ? ("Invalid path '" + path + "': " + message)
    217                    : message, cause);
    218        }
    219
    220        public BadPath(String path, String message) {
    221            this(path, message, null);
    222        }
    223
    224        public BadPath(ConfigOrigin origin, String message) {
    225            this(origin, null, message);
    226        }
    227    }
    228
    229    /**
    230     * Exception indicating that there's a bug in something (possibly the
    231     * library itself) or the runtime environment is broken. This exception
    232     * should never be handled; instead, something should be fixed to keep the
    233     * exception from occurring. This exception can be thrown by any method in
    234     * the library.
    235     */
    236    public static class BugOrBroken extends ConfigException {
    237        private static final long serialVersionUID = 1L;
    238
    239        public BugOrBroken(String message, Throwable cause) {
    240            super(message, cause);
    241        }
    242
    243        public BugOrBroken(String message) {
    244            this(message, null);
    245        }
    246    }
    247
    248    /**
    249     * Exception indicating that there was an IO error.
    250     *
    251     */
    252    public static class IO extends ConfigException {
    253        private static final long serialVersionUID = 1L;
    254
    255        public IO(ConfigOrigin origin, String message, Throwable cause) {
    256            super(origin, message, cause);
    257        }
    258
    259        public IO(ConfigOrigin origin, String message) {
    260            this(origin, message, null);
    261        }
    262    }
    263
    264    /**
    265     * Exception indicating that there was a parse error.
    266     *
    267     */
    268    public static class Parse extends ConfigException {
    269        private static final long serialVersionUID = 1L;
    270
    271        public Parse(ConfigOrigin origin, String message, Throwable cause) {
    272            super(origin, message, cause);
    273        }
    274
    275        public Parse(ConfigOrigin origin, String message) {
    276            this(origin, message, null);
    277        }
    278    }
    279
    280    /**
    281     * Exception indicating that a substitution did not resolve to anything.
    282     * Thrown by {@link Config#resolve}.
    283     */
    284    public static class UnresolvedSubstitution extends Parse {
    285        private static final long serialVersionUID = 1L;
    286
    287        private final String detail;
    288
    289        public UnresolvedSubstitution(ConfigOrigin origin, String detail, Throwable cause) {
    290            super(origin, "Could not resolve substitution to a value: " + detail, cause);
    291            this.detail = detail;
    292        }
    293
    294        public UnresolvedSubstitution(ConfigOrigin origin, String detail) {
    295            this(origin, detail, null);
    296        }
    297
    298        private UnresolvedSubstitution(UnresolvedSubstitution wrapped, ConfigOrigin origin, String message) {
    299            super(origin, message, wrapped);
    300            this.detail = wrapped.detail;
    301        }
    302
    303        public UnresolvedSubstitution addExtraDetail(String extra) {
    304            return new UnresolvedSubstitution(this, origin(), String.format(extra, detail));
    305        }
    306    }
    307
    308    /**
    309     * Exception indicating that you tried to use a function that requires
    310     * substitutions to be resolved, but substitutions have not been resolved
    311     * (that is, {@link Config#resolve} was not called). This is always a bug in
    312     * either application code or the library; it's wrong to write a handler for
    313     * this exception because you should be able to fix the code to avoid it by
    314     * adding calls to {@link Config#resolve}.
    315     */
    316    public static class NotResolved extends BugOrBroken {
    317        private static final long serialVersionUID = 1L;
    318
    319        public NotResolved(String message, Throwable cause) {
    320            super(message, cause);
    321        }
    322
    323        public NotResolved(String message) {
    324            this(message, null);
    325        }
    326    }
    327
    328    /**
    329     * Information about a problem that occurred in {@link Config#checkValid}. A
    330     * {@link ConfigException.ValidationFailed} exception thrown from
    331     * <code>checkValid()</code> includes a list of problems encountered.
    332     */
    333    public static class ValidationProblem implements Serializable {
    334
    335        final private String path;
    336        final private transient ConfigOrigin origin;
    337        final private String problem;
    338
    339        public ValidationProblem(String path, ConfigOrigin origin, String problem) {
    340            this.path = path;
    341            this.origin = origin;
    342            this.problem = problem;
    343        }
    344
    345        /**
    346         * Returns the config setting causing the problem.
    347         * @return the path of the problem setting
    348         */
    349        public String path() {
    350            return path;
    351        }
    352
    353        /**
    354         * Returns where the problem occurred (origin may include info on the
    355         * file, line number, etc.).
    356         * @return the origin of the problem setting
    357         */
    358        public ConfigOrigin origin() {
    359            return origin;
    360        }
    361
    362        /**
    363         * Returns a description of the problem.
    364         * @return description of the problem
    365         */
    366        public String problem() {
    367            return problem;
    368        }
    369
    370        // We customize serialization because ConfigOrigin isn't
    371        // serializable and we don't want it to be
    372        private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    373            out.defaultWriteObject();
    374            ConfigImplUtil.writeOrigin(out, origin);
    375        }
    376
    377        private void readObject(java.io.ObjectInputStream in) throws IOException,
    378                ClassNotFoundException {
    379            in.defaultReadObject();
    380            ConfigOrigin origin = ConfigImplUtil.readOrigin(in);
    381            setOriginField(this, ValidationProblem.class, origin);
    382        }
    383
    384        @Override
    385        public String toString() {
    386            return "ValidationProblem(" + path + "," + origin + "," + problem + ")";
    387        }
    388    }
    389
    390    /**
    391     * Exception indicating that {@link Config#checkValid} found validity
    392     * problems. The problems are available via the {@link #problems()} method.
    393     * The <code>getMessage()</code> of this exception is a potentially very
    394     * long string listing all the problems found.
    395     */
    396    public static class ValidationFailed extends ConfigException {
    397        private static final long serialVersionUID = 1L;
    398
    399        final private Iterable<ValidationProblem> problems;
    400
    401        public ValidationFailed(Iterable<ValidationProblem> problems) {
    402            super(makeMessage(problems), null);
    403            this.problems = problems;
    404        }
    405
    406        public Iterable<ValidationProblem> problems() {
    407            return problems;
    408        }
    409
    410        private static String makeMessage(Iterable<ValidationProblem> problems) {
    411            StringBuilder sb = new StringBuilder();
    412            for (ValidationProblem p : problems) {
    413                sb.append(p.origin().description());
    414                sb.append(": ");
    415                sb.append(p.path());
    416                sb.append(": ");
    417                sb.append(p.problem());
    418                sb.append(", ");
    419            }
    420            if (sb.length() == 0)
    421                throw new ConfigException.BugOrBroken(
    422                        "ValidationFailed must have a non-empty list of problems");
    423            sb.setLength(sb.length() - 2); // chop comma and space
    424
    425            return sb.toString();
    426        }
    427    }
    428
    429    /**
    430     * Some problem with a JavaBean we are trying to initialize.
    431     * @since 1.3.0
    432     */
    433    public static class BadBean extends BugOrBroken {
    434        private static final long serialVersionUID = 1L;
    435
    436        public BadBean(String message, Throwable cause) {
    437            super(message, cause);
    438        }
    439
    440        public BadBean(String message) {
    441            this(message, null);
    442        }
    443    }
    444
    445    /**
    446     * Exception that doesn't fall into any other category.
    447     */
    448    public static class Generic extends ConfigException {
    449        private static final long serialVersionUID = 1L;
    450
    451        public Generic(String message, Throwable cause) {
    452            super(message, cause);
    453        }
    454
    455        public Generic(String message) {
    456            this(message, null);
    457        }
    458    }
    459
    460}