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        public UnresolvedSubstitution(ConfigOrigin origin, String detail, Throwable cause) {
288            super(origin, "Could not resolve substitution to a value: " + detail, cause);
289        }
290
291        public UnresolvedSubstitution(ConfigOrigin origin, String detail) {
292            this(origin, detail, null);
293        }
294    }
295
296    /**
297     * Exception indicating that you tried to use a function that requires
298     * substitutions to be resolved, but substitutions have not been resolved
299     * (that is, {@link Config#resolve} was not called). This is always a bug in
300     * either application code or the library; it's wrong to write a handler for
301     * this exception because you should be able to fix the code to avoid it by
302     * adding calls to {@link Config#resolve}.
303     */
304    public static class NotResolved extends BugOrBroken {
305        private static final long serialVersionUID = 1L;
306
307        public NotResolved(String message, Throwable cause) {
308            super(message, cause);
309        }
310
311        public NotResolved(String message) {
312            this(message, null);
313        }
314    }
315
316    /**
317     * Information about a problem that occurred in {@link Config#checkValid}. A
318     * {@link ConfigException.ValidationFailed} exception thrown from
319     * <code>checkValid()</code> includes a list of problems encountered.
320     */
321    public static class ValidationProblem implements Serializable {
322
323        final private String path;
324        final private transient ConfigOrigin origin;
325        final private String problem;
326
327        public ValidationProblem(String path, ConfigOrigin origin, String problem) {
328            this.path = path;
329            this.origin = origin;
330            this.problem = problem;
331        }
332
333        /**
334         * Returns the config setting causing the problem.
335         * @return the path of the problem setting
336         */
337        public String path() {
338            return path;
339        }
340
341        /**
342         * Returns where the problem occurred (origin may include info on the
343         * file, line number, etc.).
344         * @return the origin of the problem setting
345         */
346        public ConfigOrigin origin() {
347            return origin;
348        }
349
350        /**
351         * Returns a description of the problem.
352         * @return description of the problem
353         */
354        public String problem() {
355            return problem;
356        }
357
358        // We customize serialization because ConfigOrigin isn't
359        // serializable and we don't want it to be
360        private void writeObject(java.io.ObjectOutputStream out) throws IOException {
361            out.defaultWriteObject();
362            ConfigImplUtil.writeOrigin(out, origin);
363        }
364
365        private void readObject(java.io.ObjectInputStream in) throws IOException,
366                ClassNotFoundException {
367            in.defaultReadObject();
368            ConfigOrigin origin = ConfigImplUtil.readOrigin(in);
369            setOriginField(this, ValidationProblem.class, origin);
370        }
371
372        @Override
373        public String toString() {
374            return "ValidationProblem(" + path + "," + origin + "," + problem + ")";
375        }
376    }
377
378    /**
379     * Exception indicating that {@link Config#checkValid} found validity
380     * problems. The problems are available via the {@link #problems()} method.
381     * The <code>getMessage()</code> of this exception is a potentially very
382     * long string listing all the problems found.
383     */
384    public static class ValidationFailed extends ConfigException {
385        private static final long serialVersionUID = 1L;
386
387        final private Iterable<ValidationProblem> problems;
388
389        public ValidationFailed(Iterable<ValidationProblem> problems) {
390            super(makeMessage(problems), null);
391            this.problems = problems;
392        }
393
394        public Iterable<ValidationProblem> problems() {
395            return problems;
396        }
397
398        private static String makeMessage(Iterable<ValidationProblem> problems) {
399            StringBuilder sb = new StringBuilder();
400            for (ValidationProblem p : problems) {
401                sb.append(p.origin().description());
402                sb.append(": ");
403                sb.append(p.path());
404                sb.append(": ");
405                sb.append(p.problem());
406                sb.append(", ");
407            }
408            if (sb.length() == 0)
409                throw new ConfigException.BugOrBroken(
410                        "ValidationFailed must have a non-empty list of problems");
411            sb.setLength(sb.length() - 2); // chop comma and space
412
413            return sb.toString();
414        }
415    }
416
417    /**
418     * Some problem with a JavaBean we are trying to initialize.
419     * @since 1.3.0
420     */
421    public static class BadBean extends BugOrBroken {
422        private static final long serialVersionUID = 1L;
423
424        public BadBean(String message, Throwable cause) {
425            super(message, cause);
426        }
427
428        public BadBean(String message) {
429            this(message, null);
430        }
431    }
432
433    /**
434     * Exception that doesn't fall into any other category.
435     */
436    public static class Generic extends ConfigException {
437        private static final long serialVersionUID = 1L;
438
439        public Generic(String message, Throwable cause) {
440            super(message, cause);
441        }
442
443        public Generic(String message) {
444            this(message, null);
445        }
446    }
447
448}