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}