001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.math;
018    
019    import java.io.EOFException;
020    import java.io.IOException;
021    import java.io.PrintStream;
022    import java.io.PrintWriter;
023    import java.text.MessageFormat;
024    import java.text.ParseException;
025    import java.util.ConcurrentModificationException;
026    import java.util.Locale;
027    import java.util.MissingResourceException;
028    import java.util.NoSuchElementException;
029    import java.util.ResourceBundle;
030    
031    /**
032    * Base class for commons-math unchecked exceptions.
033    * 
034    * @version $Revision: 786478 $ $Date: 2009-06-19 08:33:36 -0400 (Fri, 19 Jun 2009) $
035    * @since 2.0
036    */
037    public class MathRuntimeException extends RuntimeException {
038        
039        /** Serializable version identifier. */
040        private static final long serialVersionUID = -5128983364075381060L;
041    
042        /**
043         * Pattern used to build the message.
044         */
045        private final String pattern;
046    
047        /**
048         * Arguments used to build the message.
049         */
050        private final Object[] arguments;
051    
052        /**
053         * Translate a string to a given locale.
054         * @param s string to translate
055         * @param locale locale into which to translate the string
056         * @return translated string or original string
057         * for unsupported locales or unknown strings
058         */
059        private static String translate(final String s, final Locale locale) {
060            try {
061                ResourceBundle bundle =
062                        ResourceBundle.getBundle("org.apache.commons.math.MessagesResources", locale);
063                if (bundle.getLocale().getLanguage().equals(locale.getLanguage())) {
064                    // the value of the resource is the translated string
065                    return bundle.getString(s);
066                }
067                
068            } catch (MissingResourceException mre) {
069                // do nothing here
070            }
071    
072            // the locale is not supported or the resource is unknown
073            // don't translate and fall back to using the string as is
074            return s;
075    
076        }
077    
078        /**
079         * Builds a message string by from a pattern and its arguments.
080         * @param locale Locale in which the message should be translated
081         * @param pattern format specifier
082         * @param arguments format arguments
083         * @return a message string
084         */
085        private static String buildMessage(final Locale locale, final String pattern,
086                                           final Object ... arguments) {
087            return (pattern == null) ? "" : new MessageFormat(translate(pattern, locale), locale).format(arguments);        
088        }
089    
090        /**
091         * Constructs a new <code>MathRuntimeException</code> with specified
092         * formatted detail message.
093         * Message formatting is delegated to {@link java.text.MessageFormat}.
094         * @param pattern format specifier
095         * @param arguments format arguments
096         */
097        public MathRuntimeException(final String pattern, final Object ... arguments) {
098          super(buildMessage(Locale.US, pattern, arguments));
099          this.pattern   = pattern;
100          this.arguments = (arguments == null) ? new Object[0] : arguments.clone();
101        }
102    
103        /**
104         * Constructs a new <code>MathRuntimeException</code> with specified
105         * nested <code>Throwable</code> root cause.
106         *
107         * @param rootCause  the exception or error that caused this exception
108         *                   to be thrown.
109         */
110        public MathRuntimeException(final Throwable rootCause) {
111            super(rootCause);
112            this.pattern   = getMessage();
113            this.arguments = new Object[0];
114        }
115        
116        /**
117         * Constructs a new <code>MathRuntimeException</code> with specified
118         * formatted detail message and nested <code>Throwable</code> root cause.
119         * Message formatting is delegated to {@link java.text.MessageFormat}.
120         * @param rootCause the exception or error that caused this exception
121         * to be thrown.
122         * @param pattern format specifier
123         * @param arguments format arguments
124         */
125        public MathRuntimeException(final Throwable rootCause,
126                                    final String pattern, final Object ... arguments) {
127          super(buildMessage(Locale.US, pattern, arguments), rootCause);
128          this.pattern   = pattern;
129          this.arguments = (arguments == null) ? new Object[0] : arguments.clone();
130        }
131    
132        /** Gets the pattern used to build the message of this throwable.
133         *
134         * @return the pattern used to build the message of this throwable
135         */
136        public String getPattern() {
137            return pattern;
138        }
139    
140        /** Gets the arguments used to build the message of this throwable.
141         *
142         * @return the arguments used to build the message of this throwable
143         */
144        public Object[] getArguments() {
145            return arguments.clone();
146        }
147    
148        /** Gets the message in a specified locale.
149         *
150         * @param locale Locale in which the message should be translated
151         * 
152         * @return localized message
153         */
154        public String getMessage(final Locale locale) {
155            return buildMessage(locale, pattern, arguments);
156        }
157    
158        /** {@inheritDoc} */
159        @Override
160        public String getLocalizedMessage() {
161            return getMessage(Locale.getDefault());
162        }
163    
164        /**
165         * Prints the stack trace of this exception to the standard error stream.
166         */
167        @Override
168        public void printStackTrace() {
169            printStackTrace(System.err);
170        }
171        
172        /**
173         * Prints the stack trace of this exception to the specified stream.
174         *
175         * @param out  the <code>PrintStream</code> to use for output
176         */
177        @Override
178        public void printStackTrace(final PrintStream out) {
179            synchronized (out) {
180                PrintWriter pw = new PrintWriter(out, false);
181                printStackTrace(pw);
182                // Flush the PrintWriter before it's GC'ed.
183                pw.flush();
184            }
185        }
186    
187        /**
188         * Constructs a new <code>ArithmeticException</code> with specified formatted detail message.
189         * Message formatting is delegated to {@link java.text.MessageFormat}.
190         * @param pattern format specifier
191         * @param arguments format arguments
192         * @return built exception
193         */
194        public static ArithmeticException createArithmeticException(final String pattern,
195                                                                    final Object ... arguments) {
196            return new ArithmeticException(buildMessage(Locale.US, pattern, arguments)) {
197    
198                /** Serializable version identifier. */
199                private static final long serialVersionUID = 7705628723242533939L;
200    
201                /** {@inheritDoc} */
202                @Override
203                public String getLocalizedMessage() {
204                    return buildMessage(Locale.getDefault(), pattern, arguments);
205                }
206    
207            };
208        }
209    
210        /**
211         * Constructs a new <code>ArrayIndexOutOfBoundsException</code> with specified formatted detail message.
212         * Message formatting is delegated to {@link java.text.MessageFormat}.
213         * @param pattern format specifier
214         * @param arguments format arguments
215         * @return built exception
216         */
217        public static ArrayIndexOutOfBoundsException createArrayIndexOutOfBoundsException(final String pattern,
218                                                                                          final Object ... arguments) {
219            return new ArrayIndexOutOfBoundsException(buildMessage(Locale.US, pattern, arguments)) {
220    
221                /** Serializable version identifier. */
222                private static final long serialVersionUID = -3394748305449283486L;
223    
224                /** {@inheritDoc} */
225                @Override
226                public String getLocalizedMessage() {
227                    return buildMessage(Locale.getDefault(), pattern, arguments);
228                }
229    
230            };
231        }
232    
233        /**
234         * Constructs a new <code>EOFException</code> with specified formatted detail message.
235         * Message formatting is delegated to {@link java.text.MessageFormat}.
236         * @param pattern format specifier
237         * @param arguments format arguments
238         * @return built exception
239         */
240        public static EOFException createEOFException(final String pattern,
241                                                      final Object ... arguments) {
242            return new EOFException(buildMessage(Locale.US, pattern, arguments)) {
243    
244                /** Serializable version identifier. */
245                private static final long serialVersionUID = 279461544586092584L;
246    
247                /** {@inheritDoc} */
248                @Override
249                public String getLocalizedMessage() {
250                    return buildMessage(Locale.getDefault(), pattern, arguments);
251                }
252    
253            };
254        }
255    
256        /**
257         * Constructs a new <code>IOException</code> with specified nested
258         * <code>Throwable</code> root cause.
259         * <p>This factory method allows chaining of other exceptions within an
260         * <code>IOException</code> even for Java 5. The constructor for
261         * <code>IOException</code> with a cause parameter was introduced only
262         * with Java 6.</p>
263         * @param rootCause the exception or error that caused this exception
264         * to be thrown.
265         * @return built exception
266         */
267        public static IOException createIOException(final Throwable rootCause) {
268            IOException ioe = new IOException(rootCause.getLocalizedMessage());
269            ioe.initCause(rootCause);
270            return ioe;
271        }
272    
273        /**
274         * Constructs a new <code>IllegalArgumentException</code> with specified formatted detail message.
275         * Message formatting is delegated to {@link java.text.MessageFormat}.
276         * @param pattern format specifier
277         * @param arguments format arguments
278         * @return built exception
279         */
280        public static IllegalArgumentException createIllegalArgumentException(final String pattern,
281                                                                              final Object ... arguments) {
282            return new IllegalArgumentException(buildMessage(Locale.US, pattern, arguments)) {
283    
284                /** Serializable version identifier. */
285                private static final long serialVersionUID = -6555453980658317913L;
286    
287                /** {@inheritDoc} */
288                @Override
289                public String getLocalizedMessage() {
290                    return buildMessage(Locale.getDefault(), pattern, arguments);
291                }
292    
293            };
294        }
295    
296        /**
297         * Constructs a new <code>IllegalArgumentException</code> with specified nested
298         * <code>Throwable</code> root cause.
299         * @param rootCause the exception or error that caused this exception
300         * to be thrown.
301         * @return built exception
302         */
303        public static IllegalArgumentException createIllegalArgumentException(final Throwable rootCause) {
304            IllegalArgumentException iae = new IllegalArgumentException(rootCause.getLocalizedMessage());
305            iae.initCause(rootCause);
306            return iae;
307        }
308    
309        /**
310         * Constructs a new <code>IllegalStateException</code> with specified formatted detail message.
311         * Message formatting is delegated to {@link java.text.MessageFormat}.
312         * @param pattern format specifier
313         * @param arguments format arguments
314         * @return built exception
315         */
316        public static IllegalStateException createIllegalStateException(final String pattern,
317                                                                        final Object ... arguments) {
318            return new IllegalStateException(buildMessage(Locale.US, pattern, arguments)) {
319    
320                /** Serializable version identifier. */
321                private static final long serialVersionUID = -95247648156277208L;
322    
323                /** {@inheritDoc} */
324                @Override
325                public String getLocalizedMessage() {
326                    return buildMessage(Locale.getDefault(), pattern, arguments);
327                }
328    
329            };
330        }
331    
332        /**
333         * Constructs a new <code>ConcurrentModificationException</code> with specified formatted detail message.
334         * Message formatting is delegated to {@link java.text.MessageFormat}.
335         * @param pattern format specifier
336         * @param arguments format arguments
337         * @return built exception
338         */
339        public static ConcurrentModificationException createConcurrentModificationException(final String pattern,
340                                                                                            final Object ... arguments) {
341            return new ConcurrentModificationException(buildMessage(Locale.US, pattern, arguments)) {
342    
343                /** Serializable version identifier. */
344                private static final long serialVersionUID = 6134247282754009421L;
345    
346                /** {@inheritDoc} */
347                @Override
348                public String getLocalizedMessage() {
349                    return buildMessage(Locale.getDefault(), pattern, arguments);
350                }
351    
352            };
353        }
354    
355        /**
356         * Constructs a new <code>NoSuchElementException</code> with specified formatted detail message.
357         * Message formatting is delegated to {@link java.text.MessageFormat}.
358         * @param pattern format specifier
359         * @param arguments format arguments
360         * @return built exception
361         */
362        public static NoSuchElementException createNoSuchElementException(final String pattern,
363                                                                          final Object ... arguments) {
364            return new NoSuchElementException(buildMessage(Locale.US, pattern, arguments)) {
365    
366                /** Serializable version identifier. */
367                private static final long serialVersionUID = 7304273322489425799L;
368    
369                /** {@inheritDoc} */
370                @Override
371                public String getLocalizedMessage() {
372                    return buildMessage(Locale.getDefault(), pattern, arguments);
373                }
374    
375            };
376        }
377    
378        /**
379         * Constructs a new <code>NullPointerException</code> with specified formatted detail message.
380         * Message formatting is delegated to {@link java.text.MessageFormat}.
381         * @param pattern format specifier
382         * @param arguments format arguments
383         * @return built exception
384         */
385        public static NullPointerException createNullPointerException(final String pattern,
386                                                                      final Object ... arguments) {
387            return new NullPointerException(buildMessage(Locale.US, pattern, arguments)) {
388    
389                /** Serializable version identifier. */
390                private static final long serialVersionUID = -3075660477939965216L;
391    
392                /** {@inheritDoc} */
393                @Override
394                public String getLocalizedMessage() {
395                    return buildMessage(Locale.getDefault(), pattern, arguments);
396                }
397    
398            };
399        }
400    
401       /**
402         * Constructs a new <code>ParseException</code> with specified
403         * formatted detail message.
404         * Message formatting is delegated to {@link java.text.MessageFormat}.
405         * @param offset offset at which error occurred
406         * @param pattern format specifier
407         * @param arguments format arguments
408         * @return built exception
409         */
410        public static ParseException createParseException(final int offset,
411                                                          final String pattern,
412                                                          final Object ... arguments) {
413            return new ParseException(buildMessage(Locale.US, pattern, arguments), offset) {
414    
415                /** Serializable version identifier. */
416                private static final long serialVersionUID = -1103502177342465975L;
417    
418                /** {@inheritDoc} */
419                @Override
420                public String getLocalizedMessage() {
421                    return buildMessage(Locale.getDefault(), pattern, arguments);
422                }
423    
424            };
425        }
426    
427        /** Create an {@link java.lang.RuntimeException} for an internal error.
428         * @param cause underlying cause
429         * @return an {@link java.lang.RuntimeException} for an internal error
430         */
431        public static RuntimeException createInternalError(final Throwable cause) {
432    
433            final String pattern  = "internal error, please fill a bug report at {0}";
434            final String argument = "https://issues.apache.org/jira/browse/MATH";
435    
436            return new RuntimeException(buildMessage(Locale.US, pattern, argument)) {
437    
438                /** Serializable version identifier. */
439                private static final long serialVersionUID = -201865440834027016L;
440    
441                /** {@inheritDoc} */
442                @Override
443                public String getLocalizedMessage() {
444                    return buildMessage(Locale.getDefault(), pattern, argument);
445                }
446    
447            };
448    
449        }
450    
451    }