All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.util.Locale.scala Maven / Gradle / Ivy

package java.util

import locales.{BCP47, LocaleRegistry}

import scala.collection.{Map => SMap, Set => SSet}
import scala.collection.JavaConverters._
import scala.util.matching.Regex
import locales.BCP47.{GrandfatheredTag, LanguageTag, PrivateUseTag}

import locales.cldr.data.metadata

object Locale {

  // Default locales required by the specs
  lazy val ENGLISH: Locale = LocaleRegistry.en.toLocale
  lazy val FRENCH: Locale = LocaleRegistry.fr.toLocale
  lazy val GERMAN: Locale = LocaleRegistry.de.toLocale
  lazy val ITALIAN: Locale = LocaleRegistry.it.toLocale
  lazy val JAPANESE: Locale = LocaleRegistry.ja.toLocale
  lazy val KOREAN: Locale = LocaleRegistry.ko.toLocale
  lazy val CHINESE: Locale = LocaleRegistry.zh.toLocale
  lazy val SIMPLIFIED_CHINESE: Locale = LocaleRegistry.zh_Hans_CN.toLocale
  lazy val TRADITIONAL_CHINESE: Locale = LocaleRegistry.zh_Hant_TW.toLocale
  lazy val FRANCE: Locale = LocaleRegistry.fr_FR.toLocale
  lazy val GERMANY: Locale = LocaleRegistry.de_DE.toLocale
  lazy val ITALY: Locale = LocaleRegistry.it_IT.toLocale
  lazy val JAPAN: Locale = LocaleRegistry.ja_JP.toLocale
  lazy val KOREA: Locale = LocaleRegistry.ko_KR.toLocale
  lazy val CHINA: Locale = LocaleRegistry.zh_Hans_CN.toLocale
  lazy val PRC: Locale = LocaleRegistry.zh_Hans_CN.toLocale
  lazy val TAIWAN: Locale = LocaleRegistry.zh_Hant_TW.toLocale
  lazy val UK: Locale = LocaleRegistry.en_GB.toLocale
  lazy val US: Locale = LocaleRegistry.en_US.toLocale
  lazy val CANADA: Locale = LocaleRegistry.en_CA.toLocale
  lazy val CANADA_FRENCH: Locale = LocaleRegistry.fr_CA.toLocale
  lazy val ROOT: Locale = LocaleRegistry.root.toLocale

  val PRIVATE_USE_EXTENSION: Char = 'x'
  val UNICODE_LOCALE_EXTENSION: Char = 'u'

  final class Category private (name: String, ordinal: Int)
      extends Enum[Category](name, ordinal)

  object Category {
    val DISPLAY: Category = new Category("DISPLAY", 0)
    val FORMAT: Category = new Category("FORMAT", 1)

    private lazy val categories = Array(DISPLAY, FORMAT)

    def values(): Array[Category] = categories

    def valueOf(name: String): Category =
      categories.find(_.name == name).getOrElse {
        if (name == null)
          throw new NullPointerException("Argument cannot be null")
        else
          throw new IllegalArgumentException(s"No such category: $name")
      }
  }

  private[util] def checkRegex(regex: Regex, s: String): Boolean =
    s match {
      case regex() => true
      case _       => false
    }

  private[util] def checkLanguage(l: String): Boolean =
    checkRegex("[a-zA-Z]{2,8}".r, l)

  private[util] def checkScript(l: String): Boolean =
    checkRegex("[a-zA-Z]{4}".r, l)

  private[util] def checkRegion(l: String): Boolean =
    checkRegex("[a-zA-Z]{2}".r, l) || checkRegex("[0-9]{3}".r, l)

  private[util] def checkAcceptableVariantSegment(l: String): Boolean =
    checkRegex("[0-9a-zA-Z]{1,8}".r, l)

  private[util] def checkVariantSegment(l: String): Boolean = {
    checkRegex("[0-9][0-9a-zA-Z]{3}".r, l) ||
    checkRegex("[0-9a-zA-Z]{5,8}".r, l)
  }

  private[util] def checkVariant(l: String): Boolean = {
    val parts = l.split("-|_")
    parts.forall(checkVariantSegment)
  }

  private[util] def checkExtKey(key: Char): Boolean = key.isLetterOrDigit

  private[util] def checkExtValue(value: String): Boolean =
    value.split("-").forall(checkRegex("[0-9a-zA-Z]{2,8}".r, _))

  private[util] def checkUnicodeKey(l: String): Boolean =
    checkRegex("[a-zA-Z]{2}".r, l)

  private[util] def checkUnicodeType(l: String): Boolean =
    l.isEmpty || l.split("-").forall(checkRegex("[0-9a-zA-Z]{3,8}".r, _))

  private[util] def checkAttribute(l: String): Boolean =
    checkRegex("[0-9a-zA-Z]{3,8}".r, l)

  class Builder() {
    private[this] var builder = LocaleBuilder()

    // TODO Implement
    //def setLocale(locale: Locale): Builder = ???

    //def setLanguageTag(languageTag: String): Builder = ???

    def setLanguage(language: String): Builder = {
      builder = builder
        .language(language)
        .fold(
            throw new IllformedLocaleException(s"Invalid language $language"))(
            identity)
      this
    }

    def setScript(script: String): Builder = {
      builder = builder
        .script(script)
        .fold(throw new IllformedLocaleException(s"Invalid script $script"))(
            identity)
      this
    }

    def setRegion(region: String): Builder = {
      builder = builder
        .region(region)
        .fold(throw new IllformedLocaleException(s"Invalid region $region"))(
            identity)
      this
    }

    def setVariant(variant: String): Builder = {
      builder = builder
        .variant(variant)
        .fold(throw new IllformedLocaleException(s"Invalid variant $variant"))(
            identity)
      this
    }

    def setExtension(key: Char, value: String): Builder = {
      builder = builder
        .extension(key, value)
        .fold(throw new IllformedLocaleException(
                s"Invalid extension $key: $value"))(identity)
      this
    }

    def setUnicodeLocaleKeyword(key: String, _type: String): Builder = {
      if (key == null) {
        throw new NullPointerException("Null unicode extension key")
      }
      builder = builder
        .unicodeLocaleKeyword(key, _type)
        .fold(throw new IllformedLocaleException(
                s"Invalid unicode keyword $key: ${_type}"))(identity)
      this
    }

    def addUnicodeLocaleAttribute(attribute: String): Builder = {
      if (attribute == null) {
        throw new NullPointerException("Null unicode attribute")
      }
      builder = builder
        .addUnicodeLocaleAttribute(attribute)
        .fold(throw new IllformedLocaleException(
                s"Invalid unicode attribute $attribute"))(identity)
      this
    }

    def removeUnicodeLocaleAttribute(attribute: String): Builder = {
      if (attribute == null) {
        throw new NullPointerException("Null unicode attribute")
      }
      builder = builder
        .removeUnicodeLocaleAttribute(attribute)
        .fold(throw new IllformedLocaleException(
                s"Invalid unicode attribute $attribute"))(identity)
      this
    }

    def clear(): Builder = {
      builder = LocaleBuilder()
      this
    }

    def clearExtensions(): Builder = {
      builder = builder.clearExtensions
      this
    }

    def build(): Locale = builder.build
  }

  private case class LocaleBuilder(strict: Boolean = true,
      language: Option[String] = None, region: Option[String] = None,
      variant: Option[String] = None, script: Option[String] = None,
      extensions: SMap[Char, String] = SMap.empty,
      unicodeExtensions: SMap[String, String] = SMap.empty,
      unicodeAttributes: SSet[String] = SSet.empty) {

    def language(language: String): Option[LocaleBuilder] =
      if (language == null || language.isEmpty) {
        Some(copy(language = None))
      } else if (!strict || checkLanguage(language)) {
        Some(copy(language = Some(language.toLowerCase)))
      } else {
        None
      }

    def script(script: String): Option[LocaleBuilder] =
      if (script == null || script.isEmpty) {
        Some(copy(script = None))
      } else if (!strict || checkScript(script)) {
        // Script must be canonicalized
        Some(copy(
            script = Some(script.charAt(0).toUpper + script.substring(1))))
      } else {
        None
      }

    def region(region: String): Option[LocaleBuilder] =
      if (region == null || region.isEmpty) {
        Some(copy(region = None))
      } else if (!strict || checkRegion(region)) {
        Some(copy(region = Some(region.toUpperCase)))
      } else {
        None
      }

    def addVariant(v: String): Option[LocaleBuilder] =
      Some(copy(variant = this.variant.map(_ + "_" + v).orElse(Some(v))))

    def variant(variant: String): Option[LocaleBuilder] =
      if (variant == null || variant.isEmpty) {
        Some(copy(variant = None))
      } else if (!strict || checkVariant(variant)) {
        Some(copy(variant = Some(variant.replace("-", "_"))))
      } else {
        None
      }

    def extension(key: Char, value: String): Option[LocaleBuilder] =
      if (extensions.contains(key) || (value == null || value.isEmpty)) {
        // remove
        Some(copy(extensions = extensions - key))
      } else if (key == UNICODE_LOCALE_EXTENSION) {
        // replace all unicode extensions
        Some(copy(extensions = extensions + (key -> value.toLowerCase),
            unicodeExtensions = SMap.empty))
      } else if (!strict || checkExtKey(key) && checkExtValue(value)) {
        Some(copy(extensions = extensions + (key -> value.toLowerCase)))
      } else {
        None
      }

    def unicodeLocaleKeyword(key: String,
        _type: String): Option[LocaleBuilder] = {
      if (!strict || checkUnicodeKey(key) && checkUnicodeType(_type)) {
        Some(copy(unicodeExtensions = unicodeExtensions + (key -> _type)))
      } else {
        None
      }
    }

    def addUnicodeLocaleAttribute(attribute: String): Option[LocaleBuilder] = {
      if (!strict || checkAttribute(attribute)) {
        Some(copy(unicodeAttributes = unicodeAttributes + attribute))
      } else {
        None
      }
    }

    def removeUnicodeLocaleAttribute(
        attribute: String): Option[LocaleBuilder] = {
      if (!strict || checkAttribute(attribute)) {
        Some(copy(unicodeAttributes = unicodeAttributes.filterNot(
            _.equalsIgnoreCase(attribute))))
      } else {
        None
      }
    }

    def clearExtensions: LocaleBuilder =
      copy(extensions = SMap.empty)

    def build: Locale = {
      new Locale(language.getOrElse(""), region.getOrElse(""),
          variant.getOrElse(""), script, extensions, unicodeExtensions,
          unicodeAttributes)
    }
  }

  def getDefault(): Locale = LocaleRegistry.default

  def getDefault(category: Category): Locale = LocaleRegistry.default(category)

  def setDefault(newLocale: Locale): Unit = LocaleRegistry.setDefault(newLocale)

  def setDefault(category: Category, newLocale: Locale): Unit =
    LocaleRegistry.setDefault(category, newLocale)

  def getAvailableLocales(): Array[Locale] =
    LocaleRegistry.availableLocales.toArray

  def getISOCountries(): Array[String] = metadata.isoCountries

  def getISOLanguages(): Array[String] = metadata.isoLanguages

  private def parseLanguageTag(tag: String): Option[Locale] = {
    // grandfathered mapping
    val grandfathered = SMap("art-lojban" -> "jbo", "i-ami" -> "ami",
        "i-bnn" -> "bnn", "i-hak" -> "hak", "i-klingon" -> "tlh",
        "i-lux" -> "lb", "i-hak" -> "hak", "i-navajo" -> "nv",
        "i-pwn" -> "pwn", "i-tao" -> "tao", "i-tay" -> "tay", "i-tsu" -> "tsu",
        "no-bok" -> "nb", "no-nyn" -> "nn", "sgn-BE-FR" -> "sfb",
        "sgn-BE-NL" -> "vgt", "sgn-CH-DE" -> "sgg", "zh-guoyu" -> "cmn",
        "zh-hakka" -> "hak", "zh-min-nan" -> "nan", "zh-xiang" -> "hsn",
        "cel-gaulish" -> "xtg", "en-GB-oed" -> "en-GB-x-oed",
        "i-default" -> "en-x-i-default", "i-enochian" -> "und-x-i-enochian",
        "i-mingo" -> "see-x-i-mingo", "zh-min" -> "nan-x-zh-min")

    def sanitizePrivateExtension(b: LocaleBuilder,
        p: Option[String]): LocaleBuilder = {
      val lvariantRegex = "lvariant-(.*)".r
      val longVariantRegex = "(.*)-lvariant-(.*)".r
      p.collect {
        case longVariantRegex(x, y) =>
          for {
            b1 <- b.addVariant(y.replace("-", "_"))
            b2 <- b1.extension('x', x)
          } yield b2

        case lvariantRegex(x) => b.addVariant(x.replace("-", "_"))
        case x                => b.extension('x', x)
      }.flatten.getOrElse(b)
    }

    BCP47.parseTag(tag) match {
      case Some(LanguageTag(l, e, s, r, v, x, p)) =>
        // By the javadocs the builder is lenient
        val builder = LocaleBuilder(strict = false)

        // "und" defaults to the empty string
        val la = if (l == "und") "" else l

        // Spilt the extensions
        val extRegex = "([0-9A-WY-Za-wy-z])-([A-Za-z0-9]{2,8})+".r
        val exts = x.collect {
          case extRegex(c, xv) => c.charAt(0) -> xv
        }
        val b = for {
          b1 <- builder.language(
              e.flatMap(_.split("-").headOption).getOrElse(la))
          b2 <- b1.script(s.getOrElse(""))
          b3 <- b2.region(r.getOrElse(""))
          b4 <- b3.variant(v.mkString("_"))
          b5 <- exts.foldLeft(Option(b4)) {
            case (bu, (c, xv)) => bu.flatMap(_.extension(c, xv))
          }
        } yield sanitizePrivateExtension(b5, p)
        b.map(_.build)

      case Some(GrandfatheredTag(g)) =>
        val default = new Locale(g)
        grandfathered.get(g).fold(Option(default))(parseLanguageTag)

      case Some(PrivateUseTag(p)) =>
        val default =
          LocaleBuilder(strict = false).extension('x', p).fold(ROOT)(_.build)
        grandfathered.get(p).fold(Option(default))(parseLanguageTag)

      case None =>
        // Last ditch attempt to parse, Javadocs don't define this case well
        val split = tag.split("-").toList
        split match {
          case l :: Nil if checkLanguage(l)    => Some(new Locale(l))
          case l :: c :: _ if checkLanguage(l) => Some(new Locale(l, c))
          case _                               => None
        }
    }
  }

  def forLanguageTag(languageTag: String): Locale =
    LocaleRegistry
      .localeForLanguageTag(languageTag)
      .orElse(parseLanguageTag(languageTag))
      .getOrElse(ROOT)

  //class LanguageRange

  //class FilteringMode

  //def filter(priorityList: List[LanguageRange], locales: Collection[Locale], mode: FilteringMode): List[Locale] = ???

  //def filter(priorityList: List[LanguageRange], locales: Collection[Locale]): List[Locale] = ???

  //def filter(priorityList: List[LanguageRange], tags: Collection[String], mode: FilteringMode): List[Locale] = ???

  //def filter(priorityList: List[LanguageRange], tags: Collection[String]): List[Locale] = ???

  //def lookupTag(priorityList: List[LanguageRange], tags: Collection[String]): String = ???
}

class Locale private[util] (private[this] val language: String,
    private[this] val country: String, private[this] val variant: String,
    private[this] val script: Option[String],
    private[this] val _extensions: SMap[Char, String],
    private[this] val unicodeExtensions: SMap[String, String],
    private[this] val unicodeAttributes: SSet[String],
    private[this] val supportSpecialCases: Boolean = true) {

  // Required by the javadocs
  if (language == null || country == null || variant == null)
    throw new NullPointerException("Null argument to constructor not allowed")

  // Handle 2 special cases jp_JP_JP and th_TH_TH
  private[this] val extensions = {
    if ((language, country, variant) == ("ja", "JP", "JP") &&
        supportSpecialCases) {
      _extensions + (Locale.UNICODE_LOCALE_EXTENSION -> "ca-japanese")
    } else if ((language, country, variant) == ("th", "TH", "TH") &&
        supportSpecialCases) {
      _extensions + (Locale.UNICODE_LOCALE_EXTENSION -> "nu-thai")
    } else {
      _extensions
    }
  }

  private def updateSpecialLanguages(l: String) = l match {
    case "iw" => "he"
    case "ji" => "yi"
    case "in" => "id"
    case _    => l
  }

  private def translateSpecialLanguages(l: String) = l match {
    case "he" => "iw"
    case "yi" => "ji"
    case "id" => "in"
    case _    => l
  }

  // public constructors
  def this(language: String, country: String, variant: String) =
    this(language, country, variant, None, SMap.empty, SMap.empty, SSet.empty)

  // Additional constructors
  def this(language: String, country: String) = this(language, country, "")

  def this(language: String) = this(language, "", "")

  def getLanguage(): String = translateSpecialLanguages(language.toLowerCase)

  def getScript(): String = script.getOrElse("")

  def getCountry(): String = country.toUpperCase

  def getVariant(): String = variant

  def hasExtensions(): Boolean =
    extensions.nonEmpty || unicodeExtensions.nonEmpty

  def stripExtensions(): Locale =
    new Locale(language, country, variant, script, SMap.empty, SMap.empty,
        SSet.empty, false)

  def getExtension(key: Char): String = {
    if (key == Locale.UNICODE_LOCALE_EXTENSION && unicodeExtensions.nonEmpty) {
      unicodeExtensions.collect {
        case (k, v) if v.isEmpty => k
        case (k, v)              => s"$k-$v"
      }.mkString("-")
    } else extensions.get(key).orNull
  }

  def getExtensionKeys(): Set[Char] = {
    if (unicodeExtensions.nonEmpty) {
      extensions.keySet + Locale.UNICODE_LOCALE_EXTENSION
    } else extensions.keySet
  }.asJava

  def getUnicodeLocaleAttributes(): Set[String] = unicodeAttributes.asJava

  def getUnicodeLocaleType(key: String): String =
    unicodeExtensions.get(key).orNull

  def getUnicodeLocaleKeys(): Set[String] = unicodeExtensions.keySet.asJava

  override def toString(): String = {
    // Rather complex toString following the specifications
    val hasVariant = getVariant().nonEmpty
    val hasCountry = getCountry().nonEmpty
    val hasLanguage = getLanguage().nonEmpty
    val hasScript = getScript().nonEmpty

    val countryPart =
      if (hasCountry) s"_${getCountry()}"
      else if (hasVariant || hasScript) "_"
      else ""

    val variantPart =
      if (hasVariant) s"_${getVariant()}"
      else if (hasScript) "_"
      else ""

    val scriptPart =
      if (hasScript && hasExtensions()) s"#${getScript()}_"
      else if (hasScript) s"#${getScript()}"
      else if (hasExtensions()) "_#"
      else ""

    val extensionsPart = extensions.map { case (x, v) => s"$x-$v" }
      .mkString("")
    if (hasLanguage || hasCountry) {
      s"${getLanguage()}$countryPart$variantPart$scriptPart$extensionsPart"
    } else {
      ""
    }
  }

  def toLanguageTag(): String = {
    val language = {
      if (getLanguage().nonEmpty && Locale.checkLanguage(getLanguage))
        updateSpecialLanguages(getLanguage())
      else
        "und"
    }
    val country =
      if (Locale.checkRegion(getCountry())) s"-${getCountry()}"
      else ""
    val variantSegments = getVariant().split("-|_")
    val allSegmentsWellFormed =
      variantSegments.forall(Locale.checkVariantSegment)
    val allAcceptableSegments =
      variantSegments.forall(Locale.checkAcceptableVariantSegment)
    val variant = {
      if (allSegmentsWellFormed) {
        variantSegments.mkString("-", "-", "")
      } else if (allAcceptableSegments) {
        val (wellFormed, acceptable) =
          variantSegments.partition(Locale.checkVariantSegment)
        val pre =
          if (wellFormed.nonEmpty) wellFormed.take(1).mkString("-", "-", "-")
          else "-"
        pre + "x-lvariant" +
        (acceptable ++ wellFormed.drop(1)).mkString("-", "-", "")
      } else {
        ""
      }
    }
    val ext = {
      if (extensions.nonEmpty)
        extensions.map { case (x, v) => s"$x-$v" }.mkString("-", "-", "")
      else
        ""
    }
    val script = this.script.map(_ => s"-${getScript()}").getOrElse("")

    if (getLanguage == "no" && getCountry == "NO" && getVariant == "NY") "nn-NO"
    else s"$language$script$country$ext$variant"
  }

  // TODO Implement
  //def getISO3Language(): String = ???

  //def getISO3Country(): String = ???

  //final def getDisplayLanguage(): String = ???

  //final def getDisplayLanguage(inLocale: Locale): String = ???

  //final def getDisplayScript(): String = ???

  //final def getDisplayScript(inLocale: Locale): String = ???

  //final def getDisplayCountry(): String = ???

  //final def getDisplayCountry(inLocale: Locale): String = ???

  //final def getDisplayVariant(): String = ???

  //final def getDisplayVariant(inLocale: Locale): String = ???

  //final def getDisplayName(): String = ???

  //final def getDisplayName(inLocale: Locale): String = ???

  override def clone(): AnyRef = this // Locale is immutable

  private def isEqual(l: Locale): Boolean = {
    language == l.getLanguage && country == l.getCountry &&
    variant == l.getVariant && script.forall(_ == l.getScript) &&
    extensions.forall { case (k, v) => l.getExtension(k) == v } &&
    unicodeExtensions.forall { case (k, v) => l.getUnicodeLocaleType(k) == v } &&
    unicodeAttributes == l.getUnicodeLocaleAttributes.asScala
  }

  override def equals(x: Any): Boolean = x match {
    case l: Locale => isEqual(l)
    case _         => false
  }

  override def hashCode(): Int = toLanguageTag.hashCode
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy