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}