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}