Package hudson

Class Util

java.lang.Object
hudson.Util

public class Util extends Object
Various utility methods that don't have more proper home.
Author:
Kohsuke Kawaguchi
  • Field Details

    • XS_DATETIME_FORMATTER

      @Deprecated public static final org.apache.commons.lang.time.FastDateFormat XS_DATETIME_FORMATTER
      Deprecated.
    • XS_DATETIME_FORMATTER2

      public static final DateTimeFormatter XS_DATETIME_FORMATTER2
    • RFC822_DATETIME_FORMATTER

      @Deprecated public static final org.apache.commons.lang.time.FastDateFormat RFC822_DATETIME_FORMATTER
  • Constructor Details

    • Util

      public Util()
  • Method Details

    • filter

      @NonNull public static <T> List<T> filter(@NonNull Iterable<?> base, @NonNull Class<T> type)
      Creates a filtered sublist.
      Since:
      1.176
    • filter

      @NonNull public static <T> List<T> filter(@NonNull List<?> base, @NonNull Class<T> type)
      Creates a filtered sublist.
    • replaceMacro

      @Nullable public static String replaceMacro(@CheckForNull String s, @NonNull Map<String,String> properties)
      Replaces the occurrence of '$key' by properties.get('key').

      Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)

    • replaceMacro

      @Nullable public static String replaceMacro(@CheckForNull String s, @NonNull VariableResolver<String> resolver)
      Replaces the occurrence of '$key' by resolver.get('key').

      Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)

    • loadFile

      @NonNull @Deprecated public static String loadFile(@NonNull File logfile) throws IOException
      Deprecated.
      call loadFile(java.io.File, java.nio.charset.Charset) instead to specify the charset to use for decoding (preferably StandardCharsets.UTF_8).
      Reads the entire contents of the text file at logfile into a string using the default charset for decoding. If no such file exists, an empty string is returned.
      Parameters:
      logfile - The text file to read in its entirety.
      Returns:
      The entire text content of logfile.
      Throws:
      IOException - If an error occurs while reading the file.
    • loadFile

      @NonNull public static String loadFile(@NonNull File logfile, @NonNull Charset charset) throws IOException
      Reads the entire contents of the text file at logfile into a string using charset for decoding. If no such file exists, an empty string is returned.
      Parameters:
      logfile - The text file to read in its entirety.
      charset - The charset to use for decoding the bytes in logfile.
      Returns:
      The entire text content of logfile.
      Throws:
      IOException - If an error occurs while reading the file.
    • deleteContentsRecursive

      public static void deleteContentsRecursive(@NonNull File file) throws IOException
      Deletes the contents of the given directory (but not the directory itself) recursively. It does not take no for an answer - if necessary, it will have multiple attempts at deleting things.
      Throws:
      IOException - if the operation fails.
    • deleteContentsRecursive

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static void deleteContentsRecursive(@NonNull Path path, @NonNull PathRemover.PathChecker pathChecker) throws IOException
      Deletes the given directory contents (but not the directory itself) recursively using a PathChecker.
      Parameters:
      path - a directory to delete
      pathChecker - a security check to validate a path before deleting
      Throws:
      IOException - if the operation fails
    • deleteFile

      public static void deleteFile(@NonNull File f) throws IOException
      Deletes this file (and does not take no for an answer). If necessary, it will have multiple attempts at deleting things.
      Parameters:
      f - a file to delete
      Throws:
      IOException - if it exists but could not be successfully deleted
    • deleteRecursive

      public static void deleteRecursive(@NonNull File dir) throws IOException
      Deletes the given directory (including its contents) recursively. It does not take no for an answer - if necessary, it will have multiple attempts at deleting things.
      Throws:
      IOException - if the operation fails.
    • deleteRecursive

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static void deleteRecursive(@NonNull Path dir, @NonNull PathRemover.PathChecker pathChecker) throws IOException
      Deletes the given directory and contents recursively using a filter.
      Parameters:
      dir - a directory to delete
      pathChecker - a security check to validate a path before deleting
      Throws:
      IOException - if the operation fails
    • isSymlink

      public static boolean isSymlink(@NonNull File file) throws IOException
      Checks if the given file represents a symlink. Unlike Files.isSymbolicLink(Path), this method also considers NTFS junction points as symbolic links.
      Throws:
      IOException
    • isSymlink

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static boolean isSymlink(@NonNull Path path)
    • isRelativePath

      public static boolean isRelativePath(String path)
      A mostly accurate check of whether a path is a relative path or not. This is designed to take a path against an unknown operating system so may give invalid results.
      Parameters:
      path - the path.
      Returns:
      true if the path looks relative.
      Since:
      1.606
    • isDescendant

      public static boolean isDescendant(File forParent, File potentialChild) throws IOException
      A check if a file path is a descendant of a parent path
      Parameters:
      forParent - the parent the child should be a descendant of
      potentialChild - the path to check
      Returns:
      true if so
      Throws:
      IOException - for invalid paths
      Since:
      2.80
      See Also:
    • createTempDir

      public static File createTempDir() throws IOException
      Creates a new temporary directory.
      Throws:
      IOException
    • displayIOException

      public static void displayIOException(@NonNull IOException e, @NonNull TaskListener listener)
      On Windows, error messages for IOException aren't very helpful. This method generates additional user-friendly error message to the listener
    • getWin32ErrorMessage

      @CheckForNull public static String getWin32ErrorMessage(@NonNull IOException e)
    • getWin32ErrorMessage

      @CheckForNull public static String getWin32ErrorMessage(Throwable e)
      Extracts the Win32 error message from Throwable if possible.
      Returns:
      null if there seems to be no error code or if the platform is not Win32.
    • getWin32ErrorMessage

      @CheckForNull public static String getWin32ErrorMessage(int n)
      Gets a human readable message for the given Win32 error code.
      Returns:
      null if no such message is available.
    • getHostName

      @NonNull public static String getHostName()
      Guesses the current host name.
    • copyStream

      @Deprecated public static void copyStream(@NonNull InputStream in, @NonNull OutputStream out) throws IOException
      Deprecated.
      Use IOUtils.copy(InputStream, OutputStream)
      Throws:
      IOException
    • copyStream

      @Deprecated public static void copyStream(@NonNull Reader in, @NonNull Writer out) throws IOException
      Deprecated.
      Use IOUtils.copy(Reader, Writer)
      Throws:
      IOException
    • copyStreamAndClose

      @Deprecated public static void copyStreamAndClose(@NonNull InputStream in, @NonNull OutputStream out) throws IOException
      Deprecated.
      Use IOUtils.copy(InputStream, OutputStream) in a try-with-resources block
      Throws:
      IOException
    • copyStreamAndClose

      @Deprecated public static void copyStreamAndClose(@NonNull Reader in, @NonNull Writer out) throws IOException
      Deprecated.
      Use IOUtils.copy(Reader, Writer) in a try-with-resources block
      Throws:
      IOException
    • tokenize

      @NonNull public static String[] tokenize(@NonNull String s, @CheckForNull String delimiter)
      Tokenizes the text separated by delimiters.

      In 1.210, this method was changed to handle quotes like Unix shell does. Before that, this method just used StringTokenizer.

      Since:
      1.145
      See Also:
    • tokenize

      @NonNull public static String[] tokenize(@NonNull String s)
    • mapToEnv

      @NonNull public static String[] mapToEnv(@NonNull Map<String,String> m)
      Converts the map format of the environment variables to the K=V format in the array.
    • min

      public static int min(int x, @NonNull int... values)
    • nullify

      @CheckForNull public static String nullify(@CheckForNull String v)
    • removeTrailingSlash

      @NonNull public static String removeTrailingSlash(@NonNull String s)
    • ensureEndsWith

      @Nullable public static String ensureEndsWith(@CheckForNull String subject, @CheckForNull String suffix)
      Ensure string ends with suffix
      Parameters:
      subject - Examined string
      suffix - Desired suffix
      Returns:
      Original subject in case it already ends with suffix, null in case subject was null and subject + suffix otherwise.
      Since:
      1.505
    • getDigestOf

      @NonNull public static String getDigestOf(@NonNull InputStream source) throws IOException
      Computes MD5 digest of the given input stream. This method should only be used for non-security applications where the MD5 weakness is not a problem.
      Parameters:
      source - The stream will be closed by this method at the end of this method.
      Returns:
      32-char wide string
      Throws:
      IOException
      See Also:
      • DigestUtils.md5Hex(InputStream)
    • getDigestOf

      @NonNull public static String getDigestOf(@NonNull String text)
    • getDigestOf

      @NonNull public static String getDigestOf(@NonNull File file) throws IOException
      Computes the MD5 digest of a file.
      Parameters:
      file - a file
      Returns:
      a 32-character string
      Throws:
      IOException - in case reading fails
      Since:
      1.525
    • toAes128Key

      @NonNull public static SecretKey toAes128Key(@NonNull String s)
      Converts a string into 128-bit AES key.
      Since:
      1.308
    • toHexString

      @NonNull public static String toHexString(@NonNull byte[] data, int start, int len)
    • toHexString

      @NonNull public static String toHexString(@NonNull byte[] bytes)
    • fromHexString

      @NonNull public static byte[] fromHexString(@NonNull String data)
    • getTimeSpanString

      @NonNull public static String getTimeSpanString(long duration)
      Returns a human readable text of the time duration, for example "3 minutes 40 seconds". This version should be used for representing a duration of some activity (like build)
      Parameters:
      duration - number of milliseconds.
    • getPastTimeString

      @Deprecated @NonNull public static String getPastTimeString(long duration)
      Deprecated.
      Actually identical to getTimeSpanString(long), does not add ago.
      Get a human readable string representing strings like "xxx days ago", which should be used to point to the occurrence of an event in the past.
    • combine

      @NonNull @Deprecated public static String combine(long n, @NonNull String suffix)
      Deprecated.
      Use individual localization methods instead. See Messages.Util_year(Object) for an example. Deprecated since 2009-06-24, remove method after 2009-12-24.
      Combines number and unit, with a plural suffix if needed.
    • createSubList

      @NonNull public static <T> List<T> createSubList(@NonNull Collection<?> source, @NonNull Class<T> type)
      Create a sub-list by only picking up instances of the specified type.
    • encode

      @NonNull public static String encode(@NonNull String s)
      Escapes non-ASCII characters in URL.

      Note that this methods only escapes non-ASCII but leaves other URL-unsafe characters, such as '#'. rawEncode(String) should generally be used instead, though be careful to pass only a single path component to that method (it will encode /, but this method does not).

    • rawEncode

      @NonNull public static String rawEncode(@NonNull String s)
      Encode a single path component for use in an HTTP URL. Escapes all non-ASCII, general unsafe (space and "#%<>[\]^`{|}~) and HTTP special characters (/;:?) as specified in RFC1738. (so alphanumeric and !@$&*()-_=+',. are not encoded) Note that slash (/) is encoded, so the given string should be a single path component used in constructing a URL. Method name inspired by PHP's rawurlencode.
    • fullEncode

      @NonNull public static String fullEncode(@NonNull String s)
      Encode a single path component for use in an HTTP URL. Escapes all special characters including those outside of the characters specified in RFC1738. All characters outside numbers and letters without diacritic are encoded. Note that slash (/) is encoded, so the given string should be a single path component used in constructing a URL.
      Since:
      2.308
    • singleQuote

      public static String singleQuote(String s)
      Surrounds by a single-quote.
    • escape

      @Nullable public static String escape(@CheckForNull String text)
      Escapes HTML unsafe characters like <, & to the respective character entities.
    • xmlEscape

      @NonNull public static String xmlEscape(@NonNull String text)
    • touch

      public static void touch(@NonNull File file) throws IOException
      Creates an empty file if nonexistent or truncates the existing file. Note: The behavior of this method in the case where the file already exists is unlike the POSIX touch utility which merely updates the file's access and/or modification time.
      Throws:
      IOException
    • copyFile

      @Deprecated @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) @RestrictedSince("2.335") public static void copyFile(@NonNull File src, @NonNull File dst) throws org.apache.tools.ant.BuildException
      Deprecated.
      since 2.335; use Files.copy(Path, Path, CopyOption...) directly
      Copies a single file by using Ant.
      Throws:
      org.apache.tools.ant.BuildException
    • fixNull

      @NonNull public static String fixNull(@CheckForNull String s)
      Convert null to "".
    • fixNull

      @NonNull public static <T> T fixNull(@CheckForNull T s, @NonNull T defaultValue)
      Convert null to a default value.
      Parameters:
      defaultValue - Default value. It may be immutable or not, depending on the implementation.
      Since:
      2.144
    • fixEmpty

      @CheckForNull public static String fixEmpty(@CheckForNull String s)
      Convert empty string to null.
    • fixEmptyAndTrim

      @CheckForNull public static String fixEmptyAndTrim(@CheckForNull String s)
      Convert empty string to null, and trim whitespace.
      Since:
      1.154
    • fixNull

      @NonNull public static <T> List<T> fixNull(@CheckForNull List<T> l)
      Type Parameters:
      T - Type of the list.
      Parameters:
      l - list to check.
      Returns:
      l if l is not null. An empty immutable list if l is null.
    • fixNull

      @NonNull public static <T> Set<T> fixNull(@CheckForNull Set<T> l)
      Type Parameters:
      T - Type of the set.
      Parameters:
      l - set to check.
      Returns:
      l if l is not null. An empty immutable set if l is null.
    • fixNull

      @NonNull public static <T> Collection<T> fixNull(@CheckForNull Collection<T> l)
      Type Parameters:
      T - Type of the collection.
      Parameters:
      l - collection to check.
      Returns:
      l if l is not null. An empty immutable set if l is null.
    • fixNull

      @NonNull public static <T> Iterable<T> fixNull(@CheckForNull Iterable<T> l)
      Type Parameters:
      T - Type of the iterable.
      Parameters:
      l - iterable to check.
      Returns:
      l if l is not null. An empty immutable set if l is null.
    • getFileName

      @NonNull public static String getFileName(@NonNull String filePath)
      Cuts all the leading path portion and get just the file name.
    • join

      @Deprecated @NonNull public static String join(@NonNull Collection<?> strings, @NonNull String separator)
      Deprecated.
      Concatenate multiple strings by inserting a separator.
    • join

      @NonNull public static <T> List<T> join(@NonNull Collection<? extends T>... items)
      Combines all the given collections into a single list.
    • createFileSet

      @NonNull public static org.apache.tools.ant.types.FileSet createFileSet(@NonNull File baseDir, @NonNull String includes, @CheckForNull String excludes)
      Creates Ant FileSet with the base dir and include pattern.

      The difference with this and using AbstractFileSet.setIncludes(String) is that this method doesn't treat whitespace as a pattern separator, which makes it impossible to use space in the file path.

      Parameters:
      includes - String like "foo/bar/*.xml" Multiple patterns can be separated by ',', and whitespace can surround ',' (so that you can write "abc, def" and "abc,def" to mean the same thing.
      excludes - Exclusion pattern. Follows the same format as the 'includes' parameter. Can be null.
      Since:
      1.172
    • createFileSet

      @NonNull public static org.apache.tools.ant.types.FileSet createFileSet(@NonNull File baseDir, @NonNull String includes)
    • createSymlink

      public static void createSymlink(@NonNull File baseDir, @NonNull String targetPath, @NonNull String symlinkPath, @NonNull TaskListener listener) throws InterruptedException
      Creates a symlink to targetPath at baseDir+symlinkPath.

      If there's a prior symlink at baseDir+symlinkPath, it will be overwritten.

      Parameters:
      baseDir - Base directory to resolve the 'symlinkPath' parameter.
      targetPath - The file that the symlink should point to. Usually relative to the directory of the symlink but may instead be an absolute path.
      symlinkPath - Where to create a symlink in (relative to baseDir)
      Throws:
      InterruptedException
    • resolveSymlink

      @Deprecated public static String resolveSymlink(File link, TaskListener listener) throws InterruptedException, IOException
      Deprecated.
      as of 1.456 Use resolveSymlink(File)
      Throws:
      InterruptedException
      IOException
    • resolveSymlinkToFile

      @CheckForNull public static File resolveSymlinkToFile(@NonNull File link) throws InterruptedException, IOException
      Resolves a symlink to the File that points to.
      Returns:
      null if the specified file is not a symlink.
      Throws:
      InterruptedException
      IOException
    • resolveSymlink

      @CheckForNull public static String resolveSymlink(@NonNull File link) throws IOException
      Resolves symlink, if the given file is a symlink. Otherwise return null.

      If the resolution fails, report an error.

      Returns:
      null if the given file is not a symlink. If the symlink is absolute, the returned string is an absolute path. If the symlink is relative, the returned string is that relative representation. The relative path is meant to be resolved from the location of the symlink.
      Throws:
      IOException
    • encodeRFC2396

      @Deprecated public static String encodeRFC2396(String url)
      Deprecated.
      since 2008-05-13. This method is broken (see JENKINS-1666). It should probably be removed but I'm not sure if it is considered part of the public API that needs to be maintained for backwards compatibility. Use encode(String) instead.
      Encodes the URL by RFC 2396. I thought there's another spec that refers to UTF-8 as the encoding, but don't remember it right now.
      Since:
      1.204
    • wrapToErrorSpan

      @NonNull public static String wrapToErrorSpan(@NonNull String s)
      Wraps with the error icon and the CSS class to render error message.
      Since:
      1.173
    • tryParseNumber

      @CheckForNull public static Number tryParseNumber(@CheckForNull String numberStr, @CheckForNull Number defaultNumber)
      Returns the parsed string if parsed successful; otherwise returns the default number. If the string is null, empty or a ParseException is thrown then the defaultNumber is returned.
      Parameters:
      numberStr - string to parse
      defaultNumber - number to return if the string can not be parsed
      Returns:
      returns the parsed string; otherwise the default number
    • isOverridden

      public static boolean isOverridden(@NonNull Class<?> base, @NonNull Class<?> derived, @NonNull String methodName, @NonNull Class<?>... types)
      Checks whether the method defined on the base type with the given arguments is overridden in the given derived type.
      Parameters:
      base - The base type.
      derived - The derived type.
      methodName - The name of the method.
      types - The types of the arguments for the method.
      Returns:
      true when derived provides the specified method other than as inherited from base.
      Throws:
      IllegalArgumentException - When derived does not derive from base, or when base does not contain the specified method.
    • ifOverridden

      public static <T> T ifOverridden(Supplier<T> supplier, @NonNull Class<?> base, @NonNull Class<?> derived, @NonNull String methodName, @NonNull Class<?>... types)
      Calls the given supplier if the method defined on the base type with the given arguments is overridden in the given derived type.
      Parameters:
      supplier - The supplier to call if the method is indeed overridden.
      base - The base type.
      derived - The derived type.
      methodName - The name of the method.
      types - The types of the arguments for the method.
      Returns:
      true when derived provides the specified method other than as inherited from base.
      Throws:
      IllegalArgumentException - When derived does not derive from base, or when base does not contain the specified method.
      AbstractMethodError - If the derived class doesn't override the given method.
      Since:
      2.259
    • changeExtension

      @NonNull public static File changeExtension(@NonNull File dst, @NonNull String ext)
      Returns a file name by changing its extension.
      Parameters:
      ext - For example, ".zip"
    • intern

      @Nullable public static String intern(@CheckForNull String s)
      Null-safe String intern method.
      Returns:
      A canonical representation for the string object. Null for null input strings
    • isAbsoluteUri

      @Deprecated @RestrictedSince("1.651.2 / 2.3") @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static boolean isAbsoluteUri(@NonNull String uri)
      Deprecated.
      Use isSafeToRedirectTo(java.lang.String) instead if your goal is to prevent open redirects
      Return true if the systemId denotes an absolute URI . The same algorithm can be seen in URI, but implementing this by ourselves allow it to be more lenient about escaping of URI.
    • isSafeToRedirectTo

      public static boolean isSafeToRedirectTo(@NonNull String uri)
      Return true iff the parameter does not denote an absolute URI and not a scheme-relative URI.
      Since:
      2.3 / 1.651.2
    • loadProperties

      @NonNull public static Properties loadProperties(@NonNull String properties) throws IOException
      Loads a key/value pair string as Properties
      Throws:
      IOException
      Since:
      1.392
    • closeAndLogFailures

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static void closeAndLogFailures(@CheckForNull Closeable toClose, @NonNull Logger logger, @NonNull String closeableName, @NonNull String closeableOwner)
      Closes the item and logs error to the log in the case of error. Logging will be performed on the WARNING level.
      Parameters:
      toClose - Item to close. Nothing will happen if it is null
      logger - Logger, which receives the error
      closeableName - Name of the closeable item
      closeableOwner - String representation of the closeable holder
      Since:
      2.19, but TODO update once un-restricted
    • permissionsToMode

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static int permissionsToMode(Set<PosixFilePermission> permissions)
    • modeToPermissions

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static Set<PosixFilePermission> modeToPermissions(int mode) throws IOException
      Throws:
      IOException
    • fileToPath

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) @NonNull public static Path fileToPath(@NonNull File file) throws IOException
      Converts a File into a Path and checks runtime exceptions.
      Throws:
      IOException - if f.toPath() throws InvalidPathException.
    • createDirectories

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static Path createDirectories(@NonNull Path dir, FileAttribute<?>... attrs) throws IOException
      Create a directory by creating all nonexistent parent directories first.

      Unlike Files.createDirectory(java.nio.file.Path, java.nio.file.attribute.FileAttribute<?>...), an exception is not thrown if the directory could not be created because it already exists. Unlike Files.createDirectories(java.nio.file.Path, java.nio.file.attribute.FileAttribute<?>...), an exception is not thrown if the directory (or one of its parents) is a symbolic link.

      The attrs parameter contains optional file attributes to set atomically when creating the nonexistent directories. Each file attribute is identified by its FileAttribute.name(). If more than one attribute of the same name is included in the array, then all but the last occurrence is ignored.

      If this method fails, then it may do so after creating some, but not all, of the parent directories.

      Parameters:
      dir - The directory to create.
      attrs - An optional list of file attributes to set atomically when creating the directory.
      Returns:
      The directory.
      Throws:
      UnsupportedOperationException - If the array contains an attribute that cannot be set atomically when creating the directory.
      FileAlreadyExistsException - If dir exists but is not a directory.
      IOException - If an I/O error occurs.
      See Also:
    • daysBetween

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static long daysBetween(@NonNull Date a, @NonNull Date b)
      Compute the number of calendar days elapsed since the given date. As it's only the calendar days difference that matter, "11.00pm" to "2.00am the day after" returns 1, even if there are only 3 hours between. As well as "10am" to "2pm" both on the same day, returns 0.
    • daysElapsedSince

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static long daysElapsedSince(@NonNull Date date)
      Returns:
      positive number of days between the given date and now
      See Also:
    • getNearestAncestorOfTypeOrThrow

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) @NonNull public static <T> T getNearestAncestorOfTypeOrThrow(@NonNull org.kohsuke.stapler.StaplerRequest request, @NonNull Class<T> clazz)
      Find the specific ancestor, or throw an exception. Useful for an ancestor we know is inside the URL to ease readability
    • printRedirect

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static void printRedirect(String contextPath, String redirectUrl, String message, PrintWriter out)
    • getSHA256DigestOf

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static byte[] getSHA256DigestOf(@NonNull byte[] input)
      Returns SHA-256 Digest of input bytes
    • getHexOfSHA256DigestOf

      @Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public static String getHexOfSHA256DigestOf(byte[] input) throws IOException
      Returns Hex string of SHA-256 Digest of passed input
      Throws:
      IOException