
I have developed the idea a bit further. I think ResourceMessage is a better name (Message could mean too many other things). This is my contribution for you to include in the API if you want to. Regards, Rick package ch.qos.cal10n; import java.io.Serializable; import java.util.Locale; /** * Holds a resource message with the locale lookup deferred until it is needed * or until the locale is known. Instances are immutable so are safe to use as * Exception data or share between threads. Note that the immutability is * actually dependent on the supplied args themselves being immutable. If * ResourceMessage instances are created that contain parameters that are * mutable, the results could be unpredictable. * * @author Rick Beton */ public final class ResourceMessage implements Serializable { private static final long serialVersionUID = 6660897864328889682L; private final Enum<?> e; private final Object[] args; // cache the result of toString private transient String string = null; // cache the result of hashCode private transient int hashcode = 0; /** * Constructs an instance. * * @param e * the key for the corresponding resource. * @param args * any message parameters, as required. */ public ResourceMessage(Enum<?> e, Object... args) { this.e = e; if (args != null) { // take defensive copy this.args = new Object[args.length]; System.arraycopy(args, 0, this.args, 0, args.length); } else { this.args = null; } } /** * Gets the localized message from the resource bundle, using the specified * locale. * * @return the message for the user */ public String getLocalizedMessage(Locale locale) { final IMessageConveyor mc = new MessageConveyor(locale); return mc.getMessage(e, args); } @Override public String toString() { if (string == null) { final StringBuilder b = new StringBuilder("Message("); b.append(e.name()); b.append(", ["); if (args != null) { String comma = ""; for (Object o : args) { b.append(comma).append(o); comma = ", "; } } b.append("])"); // there may be a thread race here but the outcome is never // uncertain string = b.toString(); } return string; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj.getClass() != ResourceMessage.class) { return false; } final ResourceMessage other = (ResourceMessage) obj; if (!this.e.equals(other.e)) { return false; } else if (this.args == null && other.args == null) { return true; } else if (this.args == null || other.args == null) { return false; } else if (this.args.length != other.args.length) { return false; } for (int i = 0; i < args.length; i++) { if (this.args[i] == null && other.args[i] == null) { // ok } else if (this.args[i] == null && other.args[i] != null) { return false; } else if (!this.args[i].equals(other.args[i])) { return false; } } return true; } @Override public int hashCode() { if (hashcode == 0) { int newHashCode = 0; newHashCode = e.hashCode(); if (args != null) { for (Object o : args) { if (o != null) { newHashCode ^= o.hashCode(); } } } // there may be a thread race here but the outcome is never // uncertain hashcode = newHashCode; } return hashcode; } }