001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyrighted [year] [name of copyright owner]".
013 *
014 *      Copyright 2009 Sun Microsystems, Inc.
015 *      Portions copyright 2011 ForgeRock AS
016 */
017
018package org.forgerock.i18n;
019
020import java.util.Locale;
021import java.util.ResourceBundle;
022
023/**
024 * An opaque handle to a localizable message.
025 */
026public final class LocalizableMessageDescriptor {
027    /**
028     * Subclass for creating messages with no arguments.
029     */
030    public static final class Arg0 extends AbstractLocalizableMessageDescriptor {
031
032        /**
033         * Cached copy of the message created by this descriptor. We can get
034         * away with this for the zero argument message because it is immutable.
035         */
036        private final LocalizableMessage message;
037
038        private final boolean requiresFormat;
039
040        /**
041         * Creates a parameterized instance.
042         *
043         * @param sourceClass
044         *            The class in which this descriptor is defined. This class
045         *            will be used to obtain the {@code ClassLoader} for
046         *            retrieving the {@code ResourceBundle}. The class may also
047         *            be retrieved in order to uniquely identify the source of a
048         *            message, for example using
049         *            {@code getClass().getPackage().getName()}.
050         * @param resourceName
051         *            The name of the resource bundle containing the localizable
052         *            message.
053         * @param key
054         *            The resource bundle property key.
055         * @param ordinal
056         *            The ordinal associated with this descriptor or {@code -1}
057         *            if undefined. A message can be uniquely identified by its
058         *            ordinal and class.
059         */
060        public Arg0(final Class<?> sourceClass, final String resourceName,
061                final String key, final int ordinal) {
062            super(sourceClass, resourceName, key, ordinal);
063            final Object[] args = {};
064            message = new LocalizableMessage(this, args);
065            requiresFormat = containsArgumentLiterals(getFormatString());
066        }
067
068        /**
069         * Creates a localizable message.
070         *
071         * @return The localizable message.
072         */
073        public LocalizableMessage get() {
074            return message;
075        }
076
077        /**
078         * {@inheritDoc}
079         */
080        @Override
081        boolean requiresFormatter() {
082            return requiresFormat;
083        }
084
085        /**
086         * Indicates whether or not formatting should be applied to the given
087         * format string. Note that a format string might have literal
088         * specifiers (%% or %n for example) that require formatting but are not
089         * replaced by arguments.
090         *
091         * @param s
092         *            Candidate for formatting.
093         * @return {@code true} if the format string requires formatting.
094         */
095        private boolean containsArgumentLiterals(final String s) {
096            return s.contains("%%") || s.contains("%n"); // match Formatter
097                                                         // literals
098        }
099    }
100
101    /**
102     * Subclass for creating messages with one argument.
103     *
104     * @param <T1>
105     *            The type of the first message argument.
106     */
107    public static final class Arg1<T1> extends
108            AbstractLocalizableMessageDescriptor {
109
110        /**
111         * Creates a parameterized instance.
112         *
113         * @param sourceClass
114         *            The class in which this descriptor is defined. This class
115         *            will be used to obtain the {@code ClassLoader} for
116         *            retrieving the {@code ResourceBundle}. The class may also
117         *            be retrieved in order to uniquely identify the source of a
118         *            message, for example using
119         *            {@code getClass().getPackage().getName()}.
120         * @param resourceName
121         *            The name of the resource bundle containing the localizable
122         *            message.
123         * @param key
124         *            The resource bundle property key.
125         * @param ordinal
126         *            The ordinal associated with this descriptor or {@code -1}
127         *            if undefined. A message can be uniquely identified by its
128         *            ordinal and class.
129         */
130        public Arg1(final Class<?> sourceClass, final String resourceName,
131                final String key, final int ordinal) {
132            super(sourceClass, resourceName, key, ordinal);
133        }
134
135        /**
136         * Creates a message with arguments that will replace format specifiers
137         * in the associated format string when the message is rendered to
138         * string representation.
139         *
140         * @return The localizable message containing the provided arguments.
141         * @param a1
142         *            A message argument.
143         */
144        public LocalizableMessage get(final T1 a1) {
145            final Object[] args = { a1 };
146            return new LocalizableMessage(this, args);
147        }
148
149        /**
150         * {@inheritDoc}
151         */
152        @Override
153        boolean requiresFormatter() {
154            return true;
155        }
156
157    }
158
159    /**
160     * Subclass for creating messages with two arguments.
161     *
162     * @param <T1>
163     *            The type of the first message argument.
164     * @param <T2>
165     *            The type of the second message argument.
166     */
167    public static final class Arg2<T1, T2> extends
168            AbstractLocalizableMessageDescriptor {
169
170        /**
171         * Creates a parameterized instance.
172         *
173         * @param sourceClass
174         *            The class in which this descriptor is defined. This class
175         *            will be used to obtain the {@code ClassLoader} for
176         *            retrieving the {@code ResourceBundle}. The class may also
177         *            be retrieved in order to uniquely identify the source of a
178         *            message, for example using
179         *            {@code getClass().getPackage().getName()}.
180         * @param resourceName
181         *            The name of the resource bundle containing the localizable
182         *            message.
183         * @param key
184         *            The resource bundle property key.
185         * @param ordinal
186         *            The ordinal associated with this descriptor or {@code -1}
187         *            if undefined. A message can be uniquely identified by its
188         *            ordinal and class.
189         */
190        public Arg2(final Class<?> sourceClass, final String resourceName,
191                final String key, final int ordinal) {
192            super(sourceClass, resourceName, key, ordinal);
193        }
194
195        /**
196         * Creates a message with arguments that will replace format specifiers
197         * in the associated format string when the message is rendered to
198         * string representation.
199         *
200         * @return The localizable message containing the provided arguments.
201         * @param a1
202         *            A message argument.
203         * @param a2
204         *            A message argument.
205         */
206        public LocalizableMessage get(final T1 a1, final T2 a2) {
207            final Object[] args = { a1, a2 };
208            return new LocalizableMessage(this, args);
209        }
210
211        /**
212         * {@inheritDoc}
213         */
214        @Override
215        boolean requiresFormatter() {
216            return true;
217        }
218
219    }
220
221    /**
222     * Subclass for creating messages with three arguments.
223     *
224     * @param <T1>
225     *            The type of the first message argument.
226     * @param <T2>
227     *            The type of the second message argument.
228     * @param <T3>
229     *            The type of the third message argument.
230     */
231    public static final class Arg3<T1, T2, T3> extends
232            AbstractLocalizableMessageDescriptor {
233
234        /**
235         * Creates a parameterized instance.
236         *
237         * @param sourceClass
238         *            The class in which this descriptor is defined. This class
239         *            will be used to obtain the {@code ClassLoader} for
240         *            retrieving the {@code ResourceBundle}. The class may also
241         *            be retrieved in order to uniquely identify the source of a
242         *            message, for example using
243         *            {@code getClass().getPackage().getName()}.
244         * @param resourceName
245         *            The name of the resource bundle containing the localizable
246         *            message.
247         * @param key
248         *            The resource bundle property key.
249         * @param ordinal
250         *            The ordinal associated with this descriptor or {@code -1}
251         *            if undefined. A message can be uniquely identified by its
252         *            ordinal and class.
253         */
254        public Arg3(final Class<?> sourceClass, final String resourceName,
255                final String key, final int ordinal) {
256            super(sourceClass, resourceName, key, ordinal);
257        }
258
259        /**
260         * Creates a message with arguments that will replace format specifiers
261         * in the associated format string when the message is rendered to
262         * string representation.
263         *
264         * @return The localizable message containing the provided arguments.
265         * @param a1
266         *            A message argument.
267         * @param a2
268         *            A message argument.
269         * @param a3
270         *            A message argument.
271         */
272        public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3) {
273            final Object[] args = { a1, a2, a3 };
274            return new LocalizableMessage(this, args);
275        }
276
277        /**
278         * {@inheritDoc}
279         */
280        @Override
281        boolean requiresFormatter() {
282            return true;
283        }
284
285    }
286
287    /**
288     * Subclass for creating messages with four arguments.
289     *
290     * @param <T1>
291     *            The type of the first message argument.
292     * @param <T2>
293     *            The type of the second message argument.
294     * @param <T3>
295     *            The type of the third message argument.
296     * @param <T4>
297     *            The type of the fourth message argument.
298     */
299    public static final class Arg4<T1, T2, T3, T4> extends
300            AbstractLocalizableMessageDescriptor {
301
302        /**
303         * Creates a parameterized instance.
304         *
305         * @param sourceClass
306         *            The class in which this descriptor is defined. This class
307         *            will be used to obtain the {@code ClassLoader} for
308         *            retrieving the {@code ResourceBundle}. The class may also
309         *            be retrieved in order to uniquely identify the source of a
310         *            message, for example using
311         *            {@code getClass().getPackage().getName()}.
312         * @param resourceName
313         *            The name of the resource bundle containing the localizable
314         *            message.
315         * @param key
316         *            The resource bundle property key.
317         * @param ordinal
318         *            The ordinal associated with this descriptor or {@code -1}
319         *            if undefined. A message can be uniquely identified by its
320         *            ordinal and class.
321         */
322        public Arg4(final Class<?> sourceClass, final String resourceName,
323                final String key, final int ordinal) {
324            super(sourceClass, resourceName, key, ordinal);
325        }
326
327        /**
328         * Creates a message with arguments that will replace format specifiers
329         * in the associated format string when the message is rendered to
330         * string representation.
331         *
332         * @return The localizable message containing the provided arguments.
333         * @param a1
334         *            A message argument.
335         * @param a2
336         *            A message argument.
337         * @param a3
338         *            A message argument.
339         * @param a4
340         *            A message argument.
341         */
342        public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3,
343                final T4 a4) {
344            final Object[] args = { a1, a2, a3, a4 };
345            return new LocalizableMessage(this, args);
346        }
347
348        /**
349         * {@inheritDoc}
350         */
351        @Override
352        boolean requiresFormatter() {
353            return true;
354        }
355
356    }
357
358    /**
359     * Subclass for creating messages with five arguments.
360     *
361     * @param <T1>
362     *            The type of the first message argument.
363     * @param <T2>
364     *            The type of the second message argument.
365     * @param <T3>
366     *            The type of the third message argument.
367     * @param <T4>
368     *            The type of the fourth message argument.
369     * @param <T5>
370     *            The type of the fifth message argument.
371     */
372    public static final class Arg5<T1, T2, T3, T4, T5> extends
373            AbstractLocalizableMessageDescriptor {
374
375        /**
376         * Creates a parameterized instance.
377         *
378         * @param sourceClass
379         *            The class in which this descriptor is defined. This class
380         *            will be used to obtain the {@code ClassLoader} for
381         *            retrieving the {@code ResourceBundle}. The class may also
382         *            be retrieved in order to uniquely identify the source of a
383         *            message, for example using
384         *            {@code getClass().getPackage().getName()}.
385         * @param resourceName
386         *            The name of the resource bundle containing the localizable
387         *            message.
388         * @param key
389         *            The resource bundle property key.
390         * @param ordinal
391         *            The ordinal associated with this descriptor or {@code -1}
392         *            if undefined. A message can be uniquely identified by its
393         *            ordinal and class.
394         */
395        public Arg5(final Class<?> sourceClass, final String resourceName,
396                final String key, final int ordinal) {
397            super(sourceClass, resourceName, key, ordinal);
398        }
399
400        /**
401         * Creates a message with arguments that will replace format specifiers
402         * in the associated format string when the message is rendered to
403         * string representation.
404         *
405         * @return The localizable message containing the provided arguments.
406         * @param a1
407         *            A message argument.
408         * @param a2
409         *            A message argument.
410         * @param a3
411         *            A message argument.
412         * @param a4
413         *            A message argument.
414         * @param a5
415         *            A message argument.
416         */
417        public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3,
418                final T4 a4, final T5 a5) {
419            final Object[] args = { a1, a2, a3, a4, a5 };
420            return new LocalizableMessage(this, args);
421        }
422
423        /**
424         * {@inheritDoc}
425         */
426        @Override
427        boolean requiresFormatter() {
428            return true;
429        }
430
431    }
432
433    /**
434     * Subclass for creating messages with six arguments.
435     *
436     * @param <T1>
437     *            The type of the first message argument.
438     * @param <T2>
439     *            The type of the second message argument.
440     * @param <T3>
441     *            The type of the third message argument.
442     * @param <T4>
443     *            The type of the fourth message argument.
444     * @param <T5>
445     *            The type of the fifth message argument.
446     * @param <T6>
447     *            The type of the sixth message argument.
448     */
449    public static final class Arg6<T1, T2, T3, T4, T5, T6> extends
450            AbstractLocalizableMessageDescriptor {
451
452        /**
453         * Creates a parameterized instance.
454         *
455         * @param sourceClass
456         *            The class in which this descriptor is defined. This class
457         *            will be used to obtain the {@code ClassLoader} for
458         *            retrieving the {@code ResourceBundle}. The class may also
459         *            be retrieved in order to uniquely identify the source of a
460         *            message, for example using
461         *            {@code getClass().getPackage().getName()}.
462         * @param resourceName
463         *            The name of the resource bundle containing the localizable
464         *            message.
465         * @param key
466         *            The resource bundle property key.
467         * @param ordinal
468         *            The ordinal associated with this descriptor or {@code -1}
469         *            if undefined. A message can be uniquely identified by its
470         *            ordinal and class.
471         */
472        public Arg6(final Class<?> sourceClass, final String resourceName,
473                final String key, final int ordinal) {
474            super(sourceClass, resourceName, key, ordinal);
475        }
476
477        /**
478         * Creates a message with arguments that will replace format specifiers
479         * in the associated format string when the message is rendered to
480         * string representation.
481         *
482         * @return The localizable message containing the provided arguments.
483         * @param a1
484         *            A message argument.
485         * @param a2
486         *            A message argument.
487         * @param a3
488         *            A message argument.
489         * @param a4
490         *            A message argument.
491         * @param a5
492         *            A message argument.
493         * @param a6
494         *            A message argument.
495         */
496        public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3,
497                final T4 a4, final T5 a5, final T6 a6) {
498            final Object[] args = { a1, a2, a3, a4, a5, a6 };
499            return new LocalizableMessage(this, args);
500        }
501
502        /**
503         * {@inheritDoc}
504         */
505        @Override
506        boolean requiresFormatter() {
507            return true;
508        }
509
510    }
511
512    /**
513     * Subclass for creating messages with seven arguments.
514     *
515     * @param <T1>
516     *            The type of the first message argument.
517     * @param <T2>
518     *            The type of the second message argument.
519     * @param <T3>
520     *            The type of the third message argument.
521     * @param <T4>
522     *            The type of the fourth message argument.
523     * @param <T5>
524     *            The type of the fifth message argument.
525     * @param <T6>
526     *            The type of the sixth message argument.
527     * @param <T7>
528     *            The type of the seventh message argument.
529     */
530    public static final class Arg7<T1, T2, T3, T4, T5, T6, T7> extends
531            AbstractLocalizableMessageDescriptor {
532
533        /**
534         * Creates a parameterized instance.
535         *
536         * @param sourceClass
537         *            The class in which this descriptor is defined. This class
538         *            will be used to obtain the {@code ClassLoader} for
539         *            retrieving the {@code ResourceBundle}. The class may also
540         *            be retrieved in order to uniquely identify the source of a
541         *            message, for example using
542         *            {@code getClass().getPackage().getName()}.
543         * @param resourceName
544         *            The name of the resource bundle containing the localizable
545         *            message.
546         * @param key
547         *            The resource bundle property key.
548         * @param ordinal
549         *            The ordinal associated with this descriptor or {@code -1}
550         *            if undefined. A message can be uniquely identified by its
551         *            ordinal and class.
552         */
553        public Arg7(final Class<?> sourceClass, final String resourceName,
554                final String key, final int ordinal) {
555            super(sourceClass, resourceName, key, ordinal);
556        }
557
558        /**
559         * Creates a message with arguments that will replace format specifiers
560         * in the associated format string when the message is rendered to
561         * string representation.
562         *
563         * @return The localizable message containing the provided arguments.
564         * @param a1
565         *            A message argument.
566         * @param a2
567         *            A message argument.
568         * @param a3
569         *            A message argument.
570         * @param a4
571         *            A message argument.
572         * @param a5
573         *            A message argument.
574         * @param a6
575         *            A message argument.
576         * @param a7
577         *            A message argument.
578         */
579        public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3,
580                final T4 a4, final T5 a5, final T6 a6, final T7 a7) {
581            final Object[] args = { a1, a2, a3, a4, a5, a6, a7 };
582            return new LocalizableMessage(this, args);
583        }
584
585        /**
586         * {@inheritDoc}
587         */
588        @Override
589        boolean requiresFormatter() {
590            return true;
591        }
592
593    }
594
595    /**
596     * Subclass for creating messages with eight arguments.
597     *
598     * @param <T1>
599     *            The type of the first message argument.
600     * @param <T2>
601     *            The type of the second message argument.
602     * @param <T3>
603     *            The type of the third message argument.
604     * @param <T4>
605     *            The type of the fourth message argument.
606     * @param <T5>
607     *            The type of the fifth message argument.
608     * @param <T6>
609     *            The type of the sixth message argument.
610     * @param <T7>
611     *            The type of the seventh message argument.
612     * @param <T8>
613     *            The type of the eighth message argument.
614     */
615    public static final class Arg8<T1, T2, T3, T4, T5, T6, T7, T8> extends
616            AbstractLocalizableMessageDescriptor {
617
618        /**
619         * Creates a parameterized instance.
620         *
621         * @param sourceClass
622         *            The class in which this descriptor is defined. This class
623         *            will be used to obtain the {@code ClassLoader} for
624         *            retrieving the {@code ResourceBundle}. The class may also
625         *            be retrieved in order to uniquely identify the source of a
626         *            message, for example using
627         *            {@code getClass().getPackage().getName()}.
628         * @param resourceName
629         *            The name of the resource bundle containing the localizable
630         *            message.
631         * @param key
632         *            The resource bundle property key.
633         * @param ordinal
634         *            The ordinal associated with this descriptor or {@code -1}
635         *            if undefined. A message can be uniquely identified by its
636         *            ordinal and class.
637         */
638        public Arg8(final Class<?> sourceClass, final String resourceName,
639                final String key, final int ordinal) {
640            super(sourceClass, resourceName, key, ordinal);
641        }
642
643        /**
644         * Creates a message with arguments that will replace format specifiers
645         * in the associated format string when the message is rendered to
646         * string representation.
647         *
648         * @return The localizable message containing the provided arguments.
649         * @param a1
650         *            A message argument.
651         * @param a2
652         *            A message argument.
653         * @param a3
654         *            A message argument.
655         * @param a4
656         *            A message argument.
657         * @param a5
658         *            A message argument.
659         * @param a6
660         *            A message argument.
661         * @param a7
662         *            A message argument.
663         * @param a8
664         *            A message argument.
665         */
666        public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3,
667                final T4 a4, final T5 a5, final T6 a6, final T7 a7, final T8 a8) {
668            final Object[] args = { a1, a2, a3, a4, a5, a6, a7, a8 };
669            return new LocalizableMessage(this, args);
670        }
671
672        /**
673         * {@inheritDoc}
674         */
675        @Override
676        boolean requiresFormatter() {
677            return true;
678        }
679
680    }
681
682    /**
683     * Subclass for creating messages with nine arguments.
684     *
685     * @param <T1>
686     *            The type of the first message argument.
687     * @param <T2>
688     *            The type of the second message argument.
689     * @param <T3>
690     *            The type of the third message argument.
691     * @param <T4>
692     *            The type of the fourth message argument.
693     * @param <T5>
694     *            The type of the fifth message argument.
695     * @param <T6>
696     *            The type of the sixth message argument.
697     * @param <T7>
698     *            The type of the seventh message argument.
699     * @param <T8>
700     *            The type of the eighth message argument.
701     * @param <T9>
702     *            The type of the ninth message argument.
703     */
704    public static final class Arg9<T1, T2, T3, T4, T5, T6, T7, T8, T9> extends
705            AbstractLocalizableMessageDescriptor {
706
707        /**
708         * Creates a parameterized instance.
709         *
710         * @param sourceClass
711         *            The class in which this descriptor is defined. This class
712         *            will be used to obtain the {@code ClassLoader} for
713         *            retrieving the {@code ResourceBundle}. The class may also
714         *            be retrieved in order to uniquely identify the source of a
715         *            message, for example using
716         *            {@code getClass().getPackage().getName()}.
717         * @param resourceName
718         *            The name of the resource bundle containing the localizable
719         *            message.
720         * @param key
721         *            The resource bundle property key.
722         * @param ordinal
723         *            The ordinal associated with this descriptor or {@code -1}
724         *            if undefined. A message can be uniquely identified by its
725         *            ordinal and class.
726         */
727        public Arg9(final Class<?> sourceClass, final String resourceName,
728                final String key, final int ordinal) {
729            super(sourceClass, resourceName, key, ordinal);
730        }
731
732        /**
733         * Creates a message with arguments that will replace format specifiers
734         * in the associated format string when the message is rendered to
735         * string representation.
736         *
737         * @return The localizable message containing the provided arguments.
738         * @param a1
739         *            A message argument.
740         * @param a2
741         *            A message argument.
742         * @param a3
743         *            A message argument.
744         * @param a4
745         *            A message argument.
746         * @param a5
747         *            A message argument.
748         * @param a6
749         *            A message argument.
750         * @param a7
751         *            A message argument.
752         * @param a8
753         *            A message argument.
754         * @param a9
755         *            A message argument.
756         */
757        public LocalizableMessage get(final T1 a1, final T2 a2, final T3 a3,
758                final T4 a4, final T5 a5, final T6 a6, final T7 a7,
759                final T8 a8, final T9 a9) {
760            final Object[] args = { a1, a2, a3, a4, a5, a6, a7, a8, a9 };
761            return new LocalizableMessage(this, args);
762        }
763
764        /**
765         * {@inheritDoc}
766         */
767        @Override
768        boolean requiresFormatter() {
769            return true;
770        }
771
772    }
773
774    /**
775     * Subclass for creating messages with an any number of arguments. In
776     * general this class should be used when a message needs to be defined with
777     * more arguments that can be handled with the current number of subclasses
778     */
779    public static final class ArgN extends AbstractLocalizableMessageDescriptor {
780
781        /**
782         * Creates a parameterized instance.
783         *
784         * @param sourceClass
785         *            The class in which this descriptor is defined. This class
786         *            will be used to obtain the {@code ClassLoader} for
787         *            retrieving the {@code ResourceBundle}. The class may also
788         *            be retrieved in order to uniquely identify the source of a
789         *            message, for example using
790         *            {@code getClass().getPackage().getName()}.
791         * @param resourceName
792         *            The name of the resource bundle containing the localizable
793         *            message.
794         * @param key
795         *            The resource bundle property key.
796         * @param ordinal
797         *            The ordinal associated with this descriptor or {@code -1}
798         *            if undefined. A message can be uniquely identified by its
799         *            ordinal and class.
800         */
801        public ArgN(final Class<?> sourceClass, final String resourceName,
802                final String key, final int ordinal) {
803            super(sourceClass, resourceName, key, ordinal);
804        }
805
806        /**
807         * Creates a message with arguments that will replace format specifiers
808         * in the associated format string when the message is rendered to
809         * string representation.
810         *
811         * @return The localizable message containing the provided arguments.
812         * @param args
813         *            The message arguments.
814         */
815        public LocalizableMessage get(final Object... args) {
816            return new LocalizableMessage(this, args);
817        }
818
819        /**
820         * {@inheritDoc}
821         */
822        @Override
823        boolean requiresFormatter() {
824            return true;
825        }
826
827    }
828
829    /**
830     * Base class for all message descriptors.
831     */
832    abstract static class AbstractLocalizableMessageDescriptor {
833        /**
834         * Container for caching the last locale specific format string.
835         */
836        private static final class CachedFormatString {
837            private final Locale locale;
838
839            private final String formatString;
840
841            private CachedFormatString(final Locale locale,
842                    final String formatString) {
843                this.locale = locale;
844                this.formatString = formatString;
845            }
846        }
847
848        // Used for accessing format string from the resource bundle.
849        private final String key;
850
851        /*
852         * The class in which this descriptor is defined. This class will be
853         * used to obtain the ClassLoader for retrieving the ResourceBundle. The
854         * class may also be retrieved in order to uniquely identify the source
855         * of a message, for example using getClass().getPackage().getName().
856         */
857        private final Class<?> sourceClass;
858
859        /*
860         * The name of the resource bundle containing the localizable message.
861         */
862        private final String resourceName;
863
864        /*
865         * The ordinal associated with this descriptor or -1 if undefined. A
866         * message can be uniquely identified by its ordinal and class.
867         */
868        private final int ordinal;
869
870        // It's ok if there are race conditions.
871        private CachedFormatString cachedFormatString = null;
872
873        /**
874         * Creates a parameterized message descriptor.
875         *
876         * @param sourceClass
877         *            The class in which this descriptor is defined. This class
878         *            will be used to obtain the {@code ClassLoader} for
879         *            retrieving the {@code ResourceBundle}. The class may also
880         *            be retrieved in order to uniquely identify the source of a
881         *            message, for example using
882         *            {@code getClass().getPackage().getName()}.
883         * @param resourceName
884         *            The name of the resource bundle containing the localizable
885         *            message.
886         * @param key
887         *            The resource bundle property key.
888         * @param ordinal
889         *            The ordinal associated with this descriptor or {@code -1}
890         *            if undefined. A message can be uniquely identified by its
891         *            ordinal and class.
892         */
893        private AbstractLocalizableMessageDescriptor(
894                final Class<?> sourceClass, final String resourceName,
895                final String key, final int ordinal) {
896            this.sourceClass = sourceClass;
897            this.resourceName = resourceName;
898            this.key = key;
899            this.ordinal = ordinal;
900        }
901
902        /**
903         * Returns the format string which should be used when creating the
904         * string representation of this message using the default locale.
905         *
906         * @return The format string.
907         */
908        final String getFormatString() {
909            return getFormatString(Locale.getDefault());
910        }
911
912        /**
913         * Returns the format string which should be used when creating the
914         * string representation of this message using the specified locale.
915         *
916         * @param locale
917         *            The locale.
918         * @return The format string.
919         * @throws NullPointerException
920         *             If {@code locale} was {@code null}.
921         */
922        String getFormatString(final Locale locale) {
923            if (locale == null) {
924                throw new NullPointerException("locale was null");
925            }
926
927            // Fast path.
928            final CachedFormatString cfs = cachedFormatString;
929            if (cfs != null && cfs.locale == locale) {
930                return cfs.formatString;
931            }
932
933            // There's a potential race condition here but it's benign - we'll
934            // just do a bit more work than needed.
935            final ResourceBundle bundle = getBundle(locale);
936            final String formatString = bundle.getString(key);
937            cachedFormatString = new CachedFormatString(locale, formatString);
938
939            return formatString;
940        }
941
942        /**
943         * Returns the ordinal associated with this message, or {@code -1} if
944         * undefined. A message can be uniquely identified by its resource name
945         * and ordinal.
946         * <p>
947         * This may be useful when an application wishes to identify the source
948         * of a message. For example, a logging implementation could log the
949         * resource name in addition to the ordinal in order to unambiguously
950         * identify a message in a locale independent way.
951         *
952         * @return The ordinal associated with this descriptor, or {@code -1} if
953         *         undefined.
954         */
955        public final int ordinal() {
956            return ordinal;
957        }
958
959        /**
960         * Indicates whether or not this descriptor format string should be
961         * processed by {@code Formatter} during string rendering.
962         *
963         * @return {@code true} if a {@code Formatter} should be used, otherwise
964         *         {@code false}.
965         */
966        abstract boolean requiresFormatter();
967
968        /**
969         * Returns the name of the resource in which this message is defined. A
970         * message can be uniquely identified by its resource name and ordinal.
971         * <p>
972         * This may be useful when an application wishes to identify the source
973         * of a message. For example, a logging implementation could log the
974         * resource name in addition to the ordinal in order to unambiguously
975         * identify a message in a locale independent way.
976         * <p>
977         * The resource name may be used for obtaining named loggers, e.g. using
978         * SLF4J's {@code org.slf4j.LoggerFactory#getLogger(String name)}.
979         *
980         * @return The name of the resource in which this message is defined, or
981         *         {@code null} if this message is a raw message and its source
982         *         is undefined.
983         */
984        public final String resourceName() {
985            return resourceName;
986        }
987
988        private ResourceBundle getBundle(final Locale locale) {
989            return ResourceBundle.getBundle(resourceName,
990                    locale == null ? Locale.getDefault() : locale,
991                    sourceClass.getClassLoader());
992        }
993    }
994
995    /**
996     * A descriptor for creating a raw message from a {@code String}. In general
997     * this descriptor should NOT be used internally.
998     */
999    static final class Raw extends AbstractLocalizableMessageDescriptor {
1000
1001        private final String formatString;
1002
1003        private final boolean requiresFormatter;
1004
1005        /**
1006         * Creates a parameterized instance.
1007         *
1008         * @param formatString
1009         *            The format string.
1010         */
1011        Raw(final CharSequence formatString) {
1012            super(Void.class, null, null, -1);
1013            this.formatString = formatString.toString();
1014            this.requiresFormatter = this.formatString.matches(".*%.*");
1015        }
1016
1017        /**
1018         * Creates a message with arguments that will replace format specifiers
1019         * in the associated format string when the message is rendered to
1020         * string representation.
1021         *
1022         * @return The localizable message containing the provided arguments.
1023         * @param args
1024         *            The message arguments.
1025         */
1026        LocalizableMessage get(final Object... args) {
1027            return new LocalizableMessage(this, args);
1028        }
1029
1030        /**
1031         * Overridden in order to bypass the resource bundle plumbing and return
1032         * the format string directly.
1033         */
1034        @Override
1035        String getFormatString(final Locale locale) {
1036            return this.formatString;
1037        }
1038
1039        /**
1040         * {@inheritDoc}
1041         */
1042        @Override
1043        boolean requiresFormatter() {
1044            return this.requiresFormatter;
1045        }
1046
1047    }
1048
1049    /**
1050     * Cached zero arg raw message descriptor.
1051     */
1052    static final LocalizableMessageDescriptor.Raw RAW0 = new LocalizableMessageDescriptor.Raw(
1053            "%s");
1054
1055    // Prevent instantiation.
1056    private LocalizableMessageDescriptor() {
1057        // Do nothing.
1058    }
1059
1060}