001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall;
018
019import java.util.*;
020
021/**
022 * Supported wire formats for {@link Locale} values.
023 *
024 * <p>
025 * Used by {@link MarshallingContext.Builder#localeFormat(LocaleFormat)},
026 * {@link Marshalled#localeFormat()},
027 * {@link MarshalledProp#localeFormat()}, and
028 * {@link MarshalledConfig#localeFormat()} to control how {@link Locale}
029 * values are written to and read from the wire.
030 *
031 * <p>
032 * The default is {@link #BCP_47} which preserves the historical wire output produced before this enum existed
033 * ({@code Locale.toLanguageTag()} / {@code Locale.forLanguageTag(String)}).
034 */
035public enum LocaleFormat {
036
037   /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */
038   NOT_SET,
039
040   /**
041    * BCP 47 language tag (the default): <js>"en-US"</js>, <js>"zh-Hant-TW"</js>.
042    *
043    * <p>
044    * Round-trips through {@link Locale#toLanguageTag()} / {@link Locale#forLanguageTag(String)}.
045    */
046   BCP_47,
047
048   /**
049    * Underscore-separated form: <js>"en_US"</js>, <js>"zh_TW"</js>.
050    *
051    * <p>
052    * Round-trips through {@link Locale#toString()} on the format side and a hand-rolled parser
053    * (language + country + variant tokens) on the parse side. Script subtags are not representable.
054    */
055   UNDERSCORE;
056
057   /**
058    * Formats the specified value using this format.
059    *
060    * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>).
061    * @return The formatted string, or <jk>null</jk> if {@code value} is <jk>null</jk>.
062    */
063   public String format(Locale value) {
064      if (value == null)
065         return null;
066      return switch (this) {
067         case UNDERSCORE -> value.toString();
068         case NOT_SET, BCP_47 -> value.toLanguageTag();
069      };
070   }
071
072   /**
073    * Parses the specified wire value using this format.
074    *
075    * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>).
076    * @return The parsed {@link Locale}, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank.
077    */
078   public Locale parse(String value) {
079      if (value == null)
080         return null;
081      var s = value.trim();
082      if (s.isEmpty())
083         return null;
084      return switch (this) {
085         case UNDERSCORE -> parseUnderscore(s);
086         case NOT_SET, BCP_47 -> Locale.forLanguageTag(s);
087      };
088   }
089
090   private static Locale parseUnderscore(String s) {
091      var parts = s.split("_", -1);
092      var b = new Locale.Builder().setLanguage(parts[0]);
093      if (parts.length > 1) b.setRegion(parts[1]);
094      if (parts.length > 2) b.setVariant(parts[2]);
095      return b.build();
096   }
097}