    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    private void readObject(java.io.ObjectInputStream in) throws IOException,
    062            ClassNotFoundException {
    063        in.defaultReadObject();
    064        ConfigOrigin origin = ConfigImplUtil.readOrigin(in);
    065        // circumvent "final"
    066        Field f;
    067        try {
    068            f = ConfigException.class.getDeclaredField("origin");
    069        } catch (NoSuchFieldException e) {
    070            throw new IOException("ConfigException has no origin field?", e);
    071        } catch (SecurityException e) {
    072            throw new IOException("unable to fill out origin field in ConfigException", e);
    073        }
    074        f.setAccessible(true);
    075        try {
    076            f.set(this, 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    /**
    085     * Exception indicating that the type of a value does not match the type you
    086     * requested.
    087     *
    088     */
    089    public static class WrongType extends ConfigException {
    090        private static final long serialVersionUID = 1L;
    091
    092        public WrongType(ConfigOrigin origin, String path, String expected, String actual,
    093                Throwable cause) {
    094            super(origin, path + " has type " + actual + " rather than " + expected, cause);
    095        }
    096
    097        public WrongType(ConfigOrigin origin, String path, String expected, String actual) {
    098            this(origin, path, expected, actual, null);
    099        }
    100
    101        public WrongType(ConfigOrigin origin, String message, Throwable cause) {
    102            super(origin, message, cause);
    103        }
    104
    105        public WrongType(ConfigOrigin origin, String message) {
    106            super(origin, message, null);
    107        }
    108    }
    109
    110    /**
    111     * Exception indicates that the setting was never set to anything, not even
    112     * null.
    113     */
    114    public static class Missing extends ConfigException {
    115        private static final long serialVersionUID = 1L;
    116
    117        public Missing(String path, Throwable cause) {
    118            super("No configuration setting found for key '" + path + "'",
    119                    cause);
    120        }
    121
    122        public Missing(String path) {
    123            this(path, null);
    124        }
    125
    126        protected Missing(ConfigOrigin origin, String message, Throwable cause) {
    127            super(origin, message, cause);
    128        }
    129
    130        protected Missing(ConfigOrigin origin, String message) {
    131            this(origin, message, null);
    132        }
    133    }
    134
    135    /**
    136     * Exception indicates that the setting was treated as missing because it
    137     * was set to null.
    138     */
    139    public static class Null extends Missing {
    140        private static final long serialVersionUID = 1L;
    141
    142        private static String makeMessage(String path, String expected) {
    143            if (expected != null) {
    144                return "Configuration key '" + path
    145                        + "' is set to null but expected " + expected;
    146            } else {
    147                return "Configuration key '" + path + "' is null";
    148            }
    149        }
    150
    151        public Null(ConfigOrigin origin, String path, String expected,
    152                Throwable cause) {
    153            super(origin, makeMessage(path, expected), cause);
    154        }
    155
    156        public Null(ConfigOrigin origin, String path, String expected) {
    157            this(origin, path, expected, null);
    158        }
    159    }
    160
    161    /**
    162     * Exception indicating that a value was messed up, for example you may have
    163     * asked for a duration and the value can't be sensibly parsed as a
    164     * duration.
    165     *
    166     */
    167    public static class BadValue extends ConfigException {
    168        private static final long serialVersionUID = 1L;
    169
    170        public BadValue(ConfigOrigin origin, String path, String message,
    171                Throwable cause) {
    172            super(origin, "Invalid value at '" + path + "': " + message, cause);
    173        }
    174
    175        public BadValue(ConfigOrigin origin, String path, String message) {
    176            this(origin, path, message, null);
    177        }
    178
    179        public BadValue(String path, String message, Throwable cause) {
    180            super("Invalid value at '" + path + "': " + message, cause);
    181        }
    182
    183        public BadValue(String path, String message) {
    184            this(path, message, null);
    185        }
    186    }
    187
    188    /**
    189     * Exception indicating that a path expression was invalid. Try putting
    190     * double quotes around path elements that contain "special" characters.
    191     *
    192     */
    193    public static class BadPath extends ConfigException {
    194        private static final long serialVersionUID = 1L;
    195
    196        public BadPath(ConfigOrigin origin, String path, String message,
    197                Throwable cause) {
    198            super(origin,
    199                    path != null ? ("Invalid path '" + path + "': " + message)
    200                            : message, cause);
    201        }
    202
    203        public BadPath(ConfigOrigin origin, String path, String message) {
    204            this(origin, path, message, null);
    205        }
    206
    207        public BadPath(String path, String message, Throwable cause) {
    208            super(path != null ? ("Invalid path '" + path + "': " + message)
    209                    : message, cause);
    210        }
    211
    212        public BadPath(String path, String message) {
    213            this(path, message, null);
    214        }
    215
    216        public BadPath(ConfigOrigin origin, String message) {
    217            this(origin, null, message);
    218        }
    219    }
    220
    221    /**
    222     * Exception indicating that there's a bug in something (possibly the
    223     * library itself) or the runtime environment is broken. This exception
    224     * should never be handled; instead, something should be fixed to keep the
    225     * exception from occurring. This exception can be thrown by any method in
    226     * the library.
    227     */
    228    public static class BugOrBroken extends ConfigException {
    229        private static final long serialVersionUID = 1L;
    230
    231        public BugOrBroken(String message, Throwable cause) {
    232            super(message, cause);
    233        }
    234
    235        public BugOrBroken(String message) {
    236            this(message, null);
    237        }
    238    }
    239
    240    /**
    241     * Exception indicating that there was an IO error.
    242     *
    243     */
    244    public static class IO extends ConfigException {
    245        private static final long serialVersionUID = 1L;
    246
    247        public IO(ConfigOrigin origin, String message, Throwable cause) {
    248            super(origin, message, cause);
    249        }
    250
    251        public IO(ConfigOrigin origin, String message) {
    252            this(origin, message, null);
    253        }
    254    }
    255
    256    /**
    257     * Exception indicating that there was a parse error.
    258     *
    259     */
    260    public static class Parse extends ConfigException {
    261        private static final long serialVersionUID = 1L;
    262
    263        public Parse(ConfigOrigin origin, String message, Throwable cause) {
    264            super(origin, message, cause);
    265        }
    266
    267        public Parse(ConfigOrigin origin, String message) {
    268            this(origin, message, null);
    269        }
    270    }
    271
    272    /**
    273     * Exception indicating that a substitution did not resolve to anything.
    274     * Thrown by {@link Config#resolve}.
    275     */
    276    public static class UnresolvedSubstitution extends Parse {
    277        private static final long serialVersionUID = 1L;
    278
    279        public UnresolvedSubstitution(ConfigOrigin origin, String detail, Throwable cause) {
    280            super(origin, "Could not resolve substitution to a value: " + detail, cause);
    281        }
    282
    283        public UnresolvedSubstitution(ConfigOrigin origin, String detail) {
    284            this(origin, detail, null);
    285        }
    286    }
    287
    288    /**
    289     * Exception indicating that you tried to use a function that requires
    290     * substitutions to be resolved, but substitutions have not been resolved
    291     * (that is, {@link Config#resolve} was not called). This is always a bug in
    292     * either application code or the library; it's wrong to write a handler for
    293     * this exception because you should be able to fix the code to avoid it by
    294     * adding calls to {@link Config#resolve}.
    295     */
    296    public static class NotResolved extends BugOrBroken {
    297        private static final long serialVersionUID = 1L;
    298
    299        public NotResolved(String message, Throwable cause) {
    300            super(message, cause);
    301        }
    302
    303        public NotResolved(String message) {
    304            this(message, null);
    305        }
    306    }
    307
    308    /**
    309     * Information about a problem that occurred in {@link Config#checkValid}. A
    310     * {@link ConfigException.ValidationFailed} exception thrown from
    311     * <code>checkValid()</code> includes a list of problems encountered.
    312     */
    313    public static class ValidationProblem {
    314
    315        final private String path;
    316        final private ConfigOrigin origin;
    317        final private String problem;
    318
    319        public ValidationProblem(String path, ConfigOrigin origin, String problem) {
    320            this.path = path;
    321            this.origin = origin;
    322            this.problem = problem;
    323        }
    324
    325        /**
    326         * Returns the config setting causing the problem.
    327         * @return the path of the problem setting
    328         */
    329        public String path() {
    330            return path;
    331        }
    332
    333        /**
    334         * Returns where the problem occurred (origin may include info on the
    335         * file, line number, etc.).
    336         * @return the origin of the problem setting
    337         */
    338        public ConfigOrigin origin() {
    339            return origin;
    340        }
    341
    342        /**
    343         * Returns a description of the problem.
    344         * @return description of the problem
    345         */
    346        public String problem() {
    347            return problem;
    348        }
    349
    350        @Override
    351        public String toString() {
    352            return "ValidationProblem(" + path + "," + origin + "," + problem + ")";
    353        }
    354    }
    355
    356    /**
    357     * Exception indicating that {@link Config#checkValid} found validity
    358     * problems. The problems are available via the {@link #problems()} method.
    359     * The <code>getMessage()</code> of this exception is a potentially very
    360     * long string listing all the problems found.
    361     */
    362    public static class ValidationFailed extends ConfigException {
    363        private static final long serialVersionUID = 1L;
    364
    365        final private Iterable<ValidationProblem> problems;
    366
    367        public ValidationFailed(Iterable<ValidationProblem> problems) {
    368            super(makeMessage(problems), null);
    369            this.problems = problems;
    370        }
    371
    372        public Iterable<ValidationProblem> problems() {
    373            return problems;
    374        }
    375
    376        private static String makeMessage(Iterable<ValidationProblem> problems) {
    377            StringBuilder sb = new StringBuilder();
    378            for (ValidationProblem p : problems) {
    379                sb.append(p.origin().description());
    380                sb.append(": ");
    381                sb.append(p.path());
    382                sb.append(": ");
    383                sb.append(p.problem());
    384                sb.append(", ");
    385            }
    386            if (sb.length() == 0)
    387                throw new ConfigException.BugOrBroken(
    388                        "ValidationFailed must have a non-empty list of problems");
    389            sb.setLength(sb.length() - 2); // chop comma and space
    390
    391            return sb.toString();
    392        }
    393    }
    394
    395    /**
    396     * Some problem with a JavaBean we are trying to initialize.
    397     * @since 1.3.0
    398     */
    399    public static class BadBean extends BugOrBroken {
    400        private static final long serialVersionUID = 1L;
    401
    402        public BadBean(String message, Throwable cause) {
    403            super(message, cause);
    404        }
    405
    406        public BadBean(String message) {
    407            this(message, null);
    408        }
    409    }
    410
    411    /**
    412     * Exception that doesn't fall into any other category.
    413     */
    414    public static class Generic extends ConfigException {
    415        private static final long serialVersionUID = 1L;
    416
    417        public Generic(String message, Throwable cause) {
    418            super(message, cause);
    419        }
    420
    421        public Generic(String message) {
    422            this(message, null);
    423        }
    424    }
    425
    426}