Package ch.entwine.weblounge.common.impl.language

Source Code of ch.entwine.weblounge.common.impl.language.LocalizableObject

/*
*  Weblounge: Web Content Management System
*  Copyright (c) 2003 - 2011 The Weblounge Team
*  http://entwinemedia.com/weblounge
*
*  This program 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
*  of the License, or (at your option) any later version.
*
*  This program 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 program; if not, write to the Free Software Foundation
*  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package ch.entwine.weblounge.common.impl.language;

import static ch.entwine.weblounge.common.language.Localizable.LanguageResolution.Default;
import static ch.entwine.weblounge.common.language.Localizable.LanguageResolution.Original;

import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.language.Localizable;
import ch.entwine.weblounge.common.language.LocalizationListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
* This class represents a basic implementation of a {@link Localizable} object
* that brings support for all the language handling and dealing with language
* resolution strategies.
* <p>
* To keep the implementation simple, localized content can only be returned as
* a <code>String</code> using {@link #toString()}, {@link #toString(Language)}
* or {@link #toString(Language, boolean)}. To manage more complex content, it
* might be worth looking at {@link LocalizableContent}.
* <p>
* To be able to properly handle this object it should be noted that every
* localizable content will have an <i>original</i> language, a
* <code>current language</code> and possibly also a <i>default language</i>.
* <ul>
* <li><b>Original language</b>: the original language is automatically set as
* soon as localized content is added and thus represents the language of that
* first content.</li>
* <li><b>Current language</b>: the current language is what the object has been
* set to using the <code>switchTo(Language)</code> method. If that method has
* never been called, then the current language is either the original language
* or the default one if it has been set. In any case, as long as there is
* <i>some</code> content in the object, the current language will always be
* part of what is returned by <code>getSupportedLanguages()</code>, i. e.
* <code>supportsLanguage(currentLanguage)</code> will be true as long as
* <code>getCurrentLanguage()</code> does not return <code>null</code>.</li>
* <li><b>Default language</b>: The default language can be specified on a
* localized object as needed and yields an alternative way of getting content
* that is not available in the requested language.</li>
* </ul>
*
* @see LocalizableContent
*/
public class LocalizableObject implements Localizable {

  /** The logging facility */
  private static final Logger logger = LoggerFactory.getLogger(LocalizableObject.class);

  /** list of added languages */
  protected Set<Language> languages = null;

  /** the language that has been provided first */
  protected Language originalLanguage = null;

  /** the default language */
  protected Language defaultLanguage = null;

  /** the language that is currently set for this object */
  protected Language currentLanguage = null;

  /** the selector for the default language selection */
  protected LanguageResolution behavior = Original;

  /** list of objects interested in language switches */
  protected List<LocalizationListener> localizationListeners = null;

  /**
   * Creates a new localizable object with a default behavior of
   * {@link LanguageBehaviour#Original}.
   */
  public LocalizableObject() {
    this(null);
  }

  /**
   * Creates localizable object with a default language <code>language</code>
   * and the behavior set to {@link LanguageResolution#Default} as long as
   * <code>language</code> is not set to <code>null</code>, in which case the
   * behavior will be set to {@link LanguageResolution#Original}.
   *
   * @param identifier
   *          the default language
   */
  public LocalizableObject(Language defaultLanguage) {
    this.defaultLanguage = defaultLanguage;
    languages = new TreeSet<Language>();
    behavior = (defaultLanguage != null) ? Default : Original;
    localizationListeners = new ArrayList<LocalizationListener>();
  }

  /**
   * Adds the listener to the list of localization listeners. The listeners will
   * be notified as soon as a call to {@link #switchTo(Language)} or
   * {@link #switchTo(Language, boolean)} has been made.
   *
   * @param listener
   *          the listener to add
   */
  public void addLocalizationListener(LocalizationListener listener) {
    synchronized (localizationListeners) {
      localizationListeners.add(listener);
    }
  }

  /**
   * Removes the listener from the list of localization listeners.
   *
   * @param listener
   *          the listener to remove
   */
  public void removeLocalizationListener(LocalizationListener listener) {
    synchronized (localizationListeners) {
      localizationListeners.remove(listener);
    }
  }

  /**
   * Resets all language settings, including original, default, selected and
   * active language.
   */
  protected void reset() {
    originalLanguage = null;
    defaultLanguage = null;
    currentLanguage = null;
    languages.clear();
  }

  /**
   * Enables the given language for this object.
   *
   * @param language
   *          the language to enable
   */
  public void enableLanguage(Language language) {
    if (language == null)
      throw new IllegalArgumentException("Language must not be null");

    if (!languages.contains(language)) {
      languages.add(language);
      if (languages.size() == 1) {
        originalLanguage = language;
        currentLanguage = language;
      }
    }
  }

  /**
   * Removes the given language from this object.
   *
   * @param language
   *          the language to be removed
   */
  public void disableLanguage(Language language) {
    if (language == null)
      throw new IllegalArgumentException("Language must not be null");

    // Handle the current language
    if (language.equals(currentLanguage))
      currentLanguage = null;

    // Remove the language in question
    languages.remove(language);

    // Fix original language
    if (behavior.equals(Original) && language.equals(originalLanguage)) {
      if (languages.size() > 0) {
        defaultLanguage = languages.iterator().next();
        behavior = Default;
        logger.trace("Switching localizable object {} from original language {} to default language {}", new Object[] {
            this,
            originalLanguage,
            defaultLanguage });
      }
      originalLanguage = null;
    }

    // Fix default language
    else if (behavior.equals(Default) && language.equals(defaultLanguage)) {
      if (languages.size() > 0) {
        defaultLanguage = languages.iterator().next();
        logger.trace("Switching default language of localizable object {} to {}", this, defaultLanguage);
      } else {
        defaultLanguage = null;
      }
    }

  }

  /**
   * Makes <code>language</code> the currently selected language for this
   * object.
   *
   * @param language
   *          the language to switch to
   * @throws IllegalArgumentException
   *           if the language argument is <code>null</code>
   * @throws IllegalStateException
   *           if no fall back language can be determined
   */
  public Language switchTo(Language language) {
    return switchTo(language, false);
  }

  /**
   * Makes <code>language</code> the currently selected language for this
   * object.
   *
   * @param language
   *          the language to switch to
   * @param force
   *          force the language and don't fall back to either original or
   *          default language
   * @throws IllegalArgumentException
   *           if the language argument is <code>null</code>
   * @throws IllegalStateException
   *           if <code>force</code> is <code>false</code> and no fall back
   *           language can be determined
   */
  public Language switchTo(Language language, boolean force) {
    if (language == null)
      throw new IllegalArgumentException("Language must not be null");

    // If this object does not care about language switching, we don't care either
    if (languages.size() == 0)
      return language;
   
    // Remember the current language
    Language original = currentLanguage;

    // If the language is available, then simply select it
    if (supportsLanguage(language)) {
      currentLanguage = language;
    }

    // If the language is forced but not available, then throw an exception
    else if (force) {
      throw new IllegalStateException(this + " is not localized to " + language.getLocale().getDisplayLanguage());
    }

    // The selected language is not available. Use the original language instead
    else if (behavior.equals(Original)) {
      if (getOriginalLanguage() != null)
        currentLanguage = getOriginalLanguage();
      else if (languages.size() > 0)
        throw new IllegalStateException("Language resolution failed for " + this);
    }

    // The selected language is not available. Use the default language instead
    else if (behavior.equals(Default)) {
      if (getDefaultLanguage() != null)
        currentLanguage = getDefaultLanguage();
    }

    // Check the resolution process outcome
    if (currentLanguage == null && languages.size() > 0)
      throw new IllegalStateException("Language resolution failed for " + this);

    // Notify interested parties
    if (original == null || !original.equals(currentLanguage)) {
      fireLanguageChanged(currentLanguage, language);
    }

    return currentLanguage;
  }

  /**
   * Notifies the registered localization listeners about the newly selected
   * language.
   *
   * @param language
   *          the language that has been switched to
   * @param requested
   *          the language that was originally requested
   */
  protected void fireLanguageChanged(Language language, Language requested) {
    synchronized (localizationListeners) {
      for (LocalizationListener l : localizationListeners) {
        l.switchedTo(language, requested);
      }
    }
  }

  /**
   * Returns the language that is currently used to display the object.
   *
   * @return the currently active language
   */
  public Language getLanguage() {
    return currentLanguage;
  }

  /**
   * Returns a fall back language according to the current
   * {@link LanguageResolution}.
   *
   * @return the fall back language
   */
  protected Language resolveLanguage() {
    if (behavior.equals(Original) && originalLanguage != null)
      return originalLanguage;
    else if (behavior.equals(Default) && defaultLanguage != null)
      return defaultLanguage;
    else
      throw new IllegalStateException("Language resolution failed");
  }

  /**
   * Sets the behavior of this localizable object in the case that the object
   * description is requested in a language that has not been provided.<br>
   * There are two known behaviors:
   * <ul>
   * <li>{@link LanguageResolution#Original}: If the requested language is not
   * in the list of supported languages, then the language is chosen that was
   * used to first create this object.</li>
   * <li>{@link LanguageResolution#Default}: If the requested language is not in
   * the list of supported languages, then the language is chosen that was set
   * using {@link #setDefaultLanguage(Language)}.
   * </ul>
   * The default for this setting is {@link LanguageResolution#Original}.
   *
   * @param behavior
   *          the language behavior
   * @throws IllegalStateException
   *           if a default language has not been specified but
   *           {@link LanguageResolution#Default} has been chosen for language
   *           resolution
   */
  public void setLanguageResolution(LanguageResolution behavior)
      throws IllegalStateException {
    if (Default.equals(behavior) && defaultLanguage == null)
      throw new IllegalStateException("Must specify default language first");
    if (Original.equals(behavior) && originalLanguage == null && languages.size() > 0)
      throw new IllegalStateException("Must specify original language first");
    this.behavior = behavior;
  }

  /**
   * Returns the behavior of this multilingual object regarding it's default
   * language.
   *
   * @return the default language behavior
   * @see #setLanguageResolution(int)
   */
  public LanguageResolution getLanguageResolution() {
    return behavior;
  }

  /**
   * Explicitly sets the default language.
   * <p>
   * If the language has not yet been enabled, this method will enable it.
   *
   * @param language
   *          the default language
   * @throws IllegalArgumentException
   *           if the argument <code>language</code> was null
   */
  public void setDefaultLanguage(Language language) {
    if (language == null && behavior.equals(Default))
      throw new IllegalArgumentException("Default language may not be null while language resolution is set to default");

    defaultLanguage = language;
  }

  /**
   * Returns the default language.
   *
   * @return the default language for this object
   */
  public Language getDefaultLanguage() {
    return defaultLanguage;
  }

  /**
   * Returns the original language of this object or <code>null</code>, if no
   * original language exists.
   *
   * @return the original language
   */
  public Language getOriginalLanguage() {
    return originalLanguage;
  }

  /**
   * Sets the original language for this object. The original language is
   * considered to be the language that was first used to describe the object
   * (the native language).
   * <p>
   * If the language has not yet been enabled, this method will enable it.
   *
   * @param language
   *          the original language for this object
   * @throws IllegalArgumentException
   *           if the argument <code>language</code> was null
   */
  public void setOriginalLanguage(Language language) {
    if (language == null)
      throw new IllegalArgumentException("Language must not be null!");

    originalLanguage = language;
  }

  /**
   * Returns <code>true</code> if the given language is supported, i. e. if the
   * language has been added using <code>addLanguage()</code>.
   *
   * @param language
   *          the language to be supported
   * @return <code>true</code> if the language is supported
   */
  public boolean supportsLanguage(Language language) {
    if (language == null)
      throw new IllegalArgumentException("Language must not be null");
    return languages.contains(language);
  }

  /**
   * Returns the languages currently supported by this object.
   *
   * @return a supported language iteration.
   */
  public Set<Language> languages() {
    return languages;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.language.Localizable#compareTo(ch.entwine.weblounge.common.language.Localizable)
   */
  public int compareTo(Localizable o, Language l) {
    if (o == null)
      throw new IllegalArgumentException("Localizable must not be null");
    if (l == null)
      throw new IllegalArgumentException("Language must not be null");

    if (o instanceof LocalizableObject) {
      return toString(l).compareTo(((LocalizableObject) o).toString(l));
    }
    return toString(l).compareTo(o.toString());
  }

  /**
   * Returns the string representation in the current language.
   * <p>
   * This implementation forwards the request to
   * {@link #toString(Language, boolean)}.
   *
   * @return the component title.
   */
  public String toString() {
    return toString(resolveLanguage(), false);
  }

  /**
   * Returns the string representation in the requested language or
   * <code>null</code> if the title doesn't exist in that language.
   * <p>
   * This implementation forwards the request to
   * {@link #toString(Language, boolean)}.
   *
   * @param language
   *          the requested language
   * @return the object title
   */
  public String toString(Language language) {
    return toString(language, false);
  }

  /**
   * Returns the string representation in the specified language. If no content
   * can be found in that language, then it will be looked up in the default
   * language (unless <code>force</code> is set to <code>true</code>). <br>
   * If this doesn't produce a result as well, <code>null</code> is returned.
   *
   * @param language
   *          the language
   * @param force
   *          <code>true</code> to force the language
   * @return the object's string representation in the given language
   */
  public String toString(Language language, boolean force) {
    return super.toString();
  }

}
TOP

Related Classes of ch.entwine.weblounge.common.impl.language.LocalizableObject

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.