Package com.sos.i18n

Source Code of com.sos.i18n.Msg$BundleBaseName

/********************************************************* begin of preamble
**
** Copyright (C) 2003-2010 Software- und Organisations-Service GmbH.
** All rights reserved.
**
** This file may be used under the terms of either the
**
**   GNU General Public License version 2.0 (GPL)
**
**   as published by the Free Software Foundation
**   http://www.gnu.org/licenses/gpl-2.0.txt and appearing in the file
**   LICENSE.GPL included in the packaging of this file.
**
** or the
** 
**   Agreement for Purchase and Licensing
**
**   as offered by Software- und Organisations-Service GmbH
**   in the respective terms of supply that ship with this file.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
** IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
** THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
********************************************************** end of preamble*/
/**
* I18N Messages and Logging
* Copyright (C) 2006 John J. Mazzitelli
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package com.sos.i18n;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Formatter;
import java.util.Locale;
import java.util.ResourceBundle;

import com.sos.JSHelper.Options.SOSOptionLocale;
import com.sos.i18n.exception.LocalizedException;
import com.sos.i18n.exception.LocalizedRuntimeException;

/**
* Retrieves messages from a message {@link java.util.ResourceBundle} and formats them as appropriate using
* {@link java.text.MessageFormat}. Instances of this object are mostly read-only - the only thing you can change
* is the locale. Changing the locale will attempt to convert the message to that new locale's format.
*
* <p>I18N <code>Msg</code> objects are serializable and will be re-translated once it is deserialized. This means
* that a message could be localized in the English language, serialized and sent over the wire to another JVM with
* a German locale and when the deserialized message is retrieved again, it will be in German. This feature assumes
* a resource bundle with the same base bundle name exists in the JVM that deserialized the <code>Msg</code>. If it
* does not, then the original message will be used (in the previous example, it would mean the English message
* would be retrieved in the JVM, even though its locale is German).</p>
*
* <p>There is a method to the madness to some of the methods' parameter ordering. Anytime you want to specify a
* localized message, you always specify the base bundle name first, followed by the locale, the bundle key and the
* variable list of arguments that go with the keyed message (all in that order). Bundle name and locale are both
* optional. This is consistent with the way localized messages are specified in constructors for
* {@link LocalizedException} and {@link LocalizedRuntimeException}. When you need to specify a <code>
* Throwable</code> with your localized message, it is specified before those parameters. Again, this is consistent
* both in this class and the localized exception classes (see
* {@link #createMsg(com.sos.i18n.Msg.BundleBaseName, Locale, String, Object[])} and
* {@link LocalizedException#LocalizedException(Throwable, com.sos.i18n.Msg.BundleBaseName, Locale, String, Object[])}
* as examples).</p>
*
* @author  <a href="mailto:jmazzitelli@users.sourceforge.net">John Mazzitelli</a>
* @version $Revision: 1.3 $
* @see     LocalizedException
* @see     LocalizedRuntimeException
*/
public class Msg implements Serializable {
  /**
   * Serializable UID
   */
  private static final long      serialVersionUID    = 1L;

  /**
   * Default resource bundle base name. This is not final - you can change this via
   * {@link #setBundleBaseNameDefault(Msg.BundleBaseName)}. Because it is static, this value is not serialized
   * when the object is serialized. This is probably OK; we can assume if we move over to another VM, we probably
   * will want to go back to the original bundle name default.
   */
  private static BundleBaseName    s_bundleBasenameDefault  = new BundleBaseName("messages");

  /**
   * The resource bundle's base name, used in conjunction with the locale to determine which actual resource
   * bundle to find the message in.
   */
  private BundleBaseName        m_bundleBaseName;

  /**
   * The locale used to determine which actual resource bundle to find the message in.
   */
  private Locale            m_locale;

  /**
   * The bundle message that was found last.
   */
  private String            m_lastMessage;

  /**
   * The last resource bundle key that was used to get the last message.
   */
  private String            m_lastKey;

  /**
   * The last set of variable arguments that was used to get the last message. This may be <code>null</code> if
   * this instance never retreived a message or this instance was serialized and one or more of the vararg objects
   * was not serializable.
   */
  private Object[]          m_lastVarargs;

  /**
   * Localized resource bundle used to look up messages
   */
  private transient ResourceBundle  m_bundle;

  /**
   * A flag used to indicate that the last call to {@link #getMsg(String, Object[])} failed to obtain the message
   * successfully from the resource bundle. Its an internal flag that is allowed to be transient, no need to
   * serialize it.
   */
  private transient boolean      m_getFailed;

  /**
   * Creates a {@link Msg message} object and automatically looks up the given resource bundle message. The caller
   * need only call {@link Msg#toString()} to get the resource bundle message after this method returns.
   *
   * @param  basename the base name of the resource bundle
   * @param  locale   the locale to determine what bundle to use
   * @param  key      the resource bundle key name
   * @param  varargs  arguments to help fill in the resource bundle message
   *
   * @return if the message was logged, a non-<code>null</code> Msg object is returned
   */
  public static Msg createMsg(BundleBaseName basename, Locale locale, String key, Object... varargs) {
    Msg msg = new Msg(basename, locale);
    msg.getMsg(key, varargs);
    return msg;
  }

  /**
   * Creates a {@link Msg message} object and automatically looks up the given resource bundle message. The caller
   * need only call {@link Msg#toString()} to get the resource bundle message after this method returns. A default
   * basename is used along with the given locale.
   *
   * @param  locale  the locale to determine what bundle to use
   * @param  key     the resource bundle key name
   * @param  varargs arguments to help fill in the resource bundle message
   *
   * @return if the message was logged, a non-<code>null</code> Msg object is returned
   */
  public static Msg createMsg(Locale locale, String key, Object... varargs) {
    Msg msg = new Msg(locale);
    msg.getMsg(key, varargs);
    return msg;
  }

  /**
   * Creates a {@link Msg message} object and automatically looks up the given resource bundle message. The caller
   * need only call {@link Msg#toString()} to get the resource bundle message after this method returns.
   *
   * @param  basename the base name of the resource bundle
   * @param  key      the resource bundle key name
   * @param  varargs  arguments to help fill in the resource bundle message
   *
   * @return if the message was logged, a non-<code>null</code> Msg object is returned
   */
  public static Msg createMsg(BundleBaseName basename, String key, Object... varargs) {
    Msg msg = new Msg(basename);
    msg.getMsg(key, varargs);
    return msg;
  }

  /**
   * Creates a {@link Msg message} object and automatically looks up the given resource bundle message. The caller
   * need only call {@link Msg#toString()} to get the resource bundle message after this method returns. A default
   * basename and the default locale is used to determine what resource bundle to use.
   *
   * @param  key     the resource bundle key name
   * @param  varargs arguments to help fill in the resource bundle message
   *
   * @return if the message was logged, a non-<code>null</code> Msg object is returned
   */
  public static Msg createMsg(String key, Object... varargs) {
    Msg msg = new Msg();
    msg.getMsg(key, varargs);
    return msg;
  }

  /**
   * Returns the default bundle base name that all instances of this class will use when no basename is provided.
   *
   * @return the bundle base name default (e.g. "com.abc.messages")
   */
  public static BundleBaseName getBundleBaseNameDefault() {
    return s_bundleBasenameDefault;
  }

  /**
   * Sets the default bundle base name that all instances of this class will use when no basename is provided.
   *
   * @param newDefault the new bundle base name default (e.g. "com.abc.messages")
   */
  public static void setBundleBaseNameDefault(BundleBaseName newDefault) {
    s_bundleBasenameDefault = newDefault;
  }

  /**
   * Initializes the message repository with the appropriate resource bundle.
   *
   * @param basename resource bundle to use, if <code>null</code> uses the
   *                 {@link #getBundleBaseNameDefault() default}
   * @param locale   locale used to determine proper resource bundle to use, if <code>null</code> uses the default
   *                 locale of the JVM
   */
  public Msg(BundleBaseName basename, Locale locale) {
    if (basename == null) {
      basename = s_bundleBasenameDefault;
    }

    if (locale == null) {
      locale = Locale.getDefault();
    }

    m_getFailed = false;
    m_bundleBaseName = basename;
    m_locale = locale;
    m_bundle = null; // will be lazily set the next time we call getMsg() or getLastMessage()
    m_lastMessage = null;
    m_lastKey = null;
    m_lastVarargs = null;

    return;
  }

  /**
   * Initializes the message repository using the default resource bundle and given locale.
   *
   * @param locale locale used to determine proper resource bundle to use
   *
   * @see   Msg#Msg(com.sos.i18n.Msg.BundleBaseName, Locale)
   */
  public Msg(Locale locale) {
    this(s_bundleBasenameDefault, locale);
  }

  /**
   * Initializes the message repository using the default locale and given resource bundle base name.
   *
   * @param basename resource bundle to use
   *
   * @see   Msg#Msg(com.sos.i18n.Msg.BundleBaseName, Locale)
   */
  public Msg(BundleBaseName basename) {
//    this(basename, Locale.getDefault());
    this(basename, SOSOptionLocale.i18nLocale);
  }

  /**
   * Initializes the message repository using the default, localized resource bundle.
   */
  public Msg() {
    this(s_bundleBasenameDefault, Locale.getDefault());
  }

  /**
   * Sets a new locale. This allows this class to change the message it returns after this instance has already
   * been constructed.
   *
   * @return the current locale used by this class to determine which bundle to find the message in
   */
  public Locale getLocale() {
    return m_locale;
  }

  /**
   * Sets a new locale. This allows this class to change the message it returns after this instance has already
   * been constructed. The side effect of calling this method is that the next time {@link #getLastMessage()} is
   * called, it may return a different string if the locale was set to a different locale than what it was before.
   *
   * @param locale the new locale to set, if <code>null</code>, the default locale will be set
   */
  public void setLocale(Locale locale) {
    if (locale == null) {
      locale = Locale.getDefault();
    }

    if (!locale.equals(getLocale())) {
      m_locale = locale;
      m_bundle = null; // the locale changed so the bundle we used before is no longer valid
    }

    return;
  }

  /**
   * Returns the base name of the resource bundles to be used when looking for messages.
   *
   * @return the resource bundles' base name
   */
  public BundleBaseName getBundleBaseName() {
    return m_bundleBaseName;
  }

  /**
   * Returns the last message that this instance read from a resource bundle.
   *
   * <p>This object can change the messages it finds by simply {@link #setLocale(Locale) setting the locale}. When
   * you change the local, the message is retrieved again when this method is called and is stored as the last
   * message.
   *
   * @return the last retrieved resource bundle message
   */
  public String getLastMessage() {
    // If bundle was not yet set, we've either never retreived a message or
    // we've been serialized to another VM or someone set a new locale.
    // In either of these cases, we need to get the message again.
    if (m_bundle == null) {
      // Note that if the last varargs is null, that means the deserialization of the varargs
      // failed. In this case, we don't want to get the message since we've now lost some of the
      // message data.  We will rely on the last message that hopefully contains all the data, albeit
      // in a different locale (but at least the data isn't lost).
      if (m_lastVarargs != null) {
        String lastMessageBackup = m_lastMessage;

        getMsg(m_lastKey, m_lastVarargs);

        if (m_getFailed) {
          m_lastMessage = lastMessageBackup;
        }
      }
    }

    return m_lastMessage;
  }

  /**
   * Returns the message string identified with the given key. The additional arguments replace any placeholders
   * found in the message. This sets the {@link #getLastMessage()} when it returns.
   *
   * @param  key     identifies the message to be retrieved
   * @param  varargs arguments to replace placeholders in message
   *
   * @return localized and formatted message
   *
   * @see    java.text.MessageFormat
   */
  public String getMsg(String key, Object... varargs) {
    String retMessage = null;

    // See if we can find the bundle that has our new locale's messages.  If we can't,
    // this usually means this object was serialized and sent to another VM that doesn't have the resource bundles.
    // Any exception in here will fall back to using the key and varargs as the last message unless the resource
    // message was found (but failed to be formatted) - in that case, the resource message will be used.
    // In either case, the varargs will be returned in the standard Java List toString format.
    try {
      if (m_bundle == null || m_locale.equals(SOSOptionLocale.i18nLocale) == false) {
        m_bundle = getResourceBundle();
      }

      retMessage = m_bundle.getString(key);

      if (varargs.length > 0) {
        MessageFormat mf = new MessageFormat(retMessage, m_locale);
        retMessage = mf.format(varargs);
      }

      // remember these in case we are asked to get the message again in a different locale or
      // we need to reconsitute the message after being serialized.
      m_lastKey = key;
      m_lastVarargs = varargs;

      // everything is OK
      m_getFailed = false;
    }
    catch (Exception e) {
      m_getFailed = true;

      if (retMessage == null) {
        retMessage = key;
      }

      Formatter formatter = new Formatter();
      formatter.format("!!! missing resource message key=[%s] args=%s", retMessage, Arrays.asList(varargs));

      retMessage = formatter.toString();
    }

    m_lastMessage = retMessage;

    return retMessage;
  }

  /**
   * Same as {@link #getLastMessage()}.
   *
   * @see java.lang.Object#toString()
   */
  public String toString() {
    return getLastMessage();
  }

  /**
   * Gets the resource bundle to use, based on the current values of {@link #getBundleBaseName() the base name}
   * and the {@link #getLocale() locale}.
   *
   * @return the resource bundle to be used by this object when looking up messages
   */
  protected ResourceBundle getResourceBundle() {
    setLocale(SOSOptionLocale.i18nLocale);
    Locale locale = getLocale();
    BundleBaseName basename = getBundleBaseName();
    ResourceBundle bundle = ResourceBundle.getBundle(basename.getBundleBaseName(), locale);

    return bundle;
  }

  /**
   * Given a serializable object, this will return the object's serialized byte array representation.
   *
   * @param  object the object to serialize
   *
   * @return the serialized bytes
   *
   * @throws Exception if failed to serialize the object
   */
  protected static byte[] serialize(Serializable object) throws Exception {
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    ObjectOutputStream oos;

    oos = new ObjectOutputStream(byteStream);
    oos.writeObject(object);
    oos.close();

    return byteStream.toByteArray();
  }

  /**
   * Deserializes the given serialization data and returns the object.
   *
   * @param  serializedData the serialized data as a byte array
   *
   * @return the deserialized object
   *
   * @throws Exception if failed to deserialize the object
   */
  protected static Object deserialize(byte[] serializedData) throws Exception {
    ByteArrayInputStream byteStream = new ByteArrayInputStream(serializedData);
    ObjectInputStream ois;
    Object retObject;

    ois = new ObjectInputStream(byteStream);
    retObject = ois.readObject();
    ois.close();

    return retObject;
  }

  /**
   * <code>ResourceBundle</code> is not serializable so this serializes the base bundle name and the locale with
   * the hopes that this will be enough to look up the message again when this instance is deserialized. This
   * assumes the new place where this object was deserialized has the resource bundle available. If it does not,
   * the original message will be reused.
   *
   * @param  out where to write the serialized stream
   *
   * @throws IOException
   */
  private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    out.writeObject(m_bundleBaseName);
    out.writeObject(m_locale);
    out.writeObject(m_lastMessage);
    out.writeObject(m_lastKey);

    byte[] varargs;
    int varargsLength;

    try {
      // Nothing we can do about it if one or more args aren't serializable.
      // We'll just have to rely on the last message when we get to the other side.
      // We do our own serialization here because the writeObject docs says that if a write
      // fails, the whole output stream is corrupted and in an indeterminate state.  Since
      // it is completely valid that some vararg objects may not be serializable, we have
      // to take into account that we may not be able to serialize the varargs.
      varargs = serialize(m_lastVarargs);
      varargsLength = varargs.length;
    }
    catch (Exception e) {
      varargs = null;
      varargsLength = -1;
    }

    out.writeInt(varargsLength);
    if (varargsLength != -1) {
      out.write(varargs);
    }

    return;
  }

  /**
   * <code>ResourceBundle</code> is not serializable so this deserializes the base bundle name and the locale with
   * the hopes that this will be enough to look up the message. This assumes the new place where this object is
   * being deserialized has the resource bundle available. If it does not, the original message will be reused.
   *
   * @param  in where to read the serialized stream
   *
   * @throws IOException
   * @throws ClassNotFoundException
   */
  private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
    // set our transient fields
    m_bundle = null;
    m_getFailed = false;

    // now read in our serialized object
    m_bundleBaseName = (BundleBaseName) in.readObject();
    m_locale = (Locale) in.readObject();
    m_lastMessage = (String) in.readObject();
    m_lastKey = (String) in.readObject();
    m_lastVarargs = null;

    int varargsLength = in.readInt();

    if (varargsLength != -1) {
      byte[] varargs = new byte[varargsLength];

      try {
        in.readFully(varargs);
        m_lastVarargs = (Object[]) deserialize(varargs);
      }
      catch (Exception e) {
        m_lastVarargs = null;
      }
    }

    return;
  }

  /**
   * The purpose of this class is to offer a strongly typed object (more strongly than String) so we can pass
   * bundle base names to our vararg methods and not have this be confused with a key or arg parameter.
   */
  public static final class BundleBaseName implements Serializable {
    private static final long  serialVersionUID  = 1L;
    private final String    m_name;

    /**
     * Creates a new {@link BundleBaseName} object.
     *
     * @param bundleBaseName the bundle base name string
     */
    public BundleBaseName(String bundleBaseName) {
      m_name = bundleBaseName;
    }

    /**
     * Returns the bundle base name.
     *
     * @return the bundle base name.
     */
    public String getBundleBaseName() {
      return m_name;
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
      return getBundleBaseName();
    }
  }
}
TOP

Related Classes of com.sos.i18n.Msg$BundleBaseName

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.