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}