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}