content: /*
   * Copyright (C) 2012 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.amnesica.kryptey.inputmethod.latin.common;
  
  import android.text.TextUtils;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Locale;
  
  public final class StringUtils {
    private static final String EMPTY_STRING = "";
  
    private StringUtils() {
      // This utility class is not publicly instantiable.
    }
  
    public static int codePointCount(final CharSequence text) {
      if (TextUtils.isEmpty(text)) {
        return 0;
      }
      return Character.codePointCount(text, 0, text.length());
    }
  
    public static String newSingleCodePointString(final int codePoint) {
      if (Character.charCount(codePoint) == 1) {
        // Optimization: avoid creating a temporary array for characters that are
        // represented by a single char value
        return String.valueOf((char) codePoint);
      }
      // For surrogate pair
      return new String(Character.toChars(codePoint));
    }
  
    public static boolean containsInArray(final String text,
                                          final String[] array) {
      for (final String element : array) {
        if (text.equals(element)) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax.
     * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain
     * a comma character in it.
     */
    private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ",";
  
    public static boolean containsInCommaSplittableText(final String text,
                                                        final String extraValues) {
      if (TextUtils.isEmpty(extraValues)) {
        return false;
      }
      return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
    }
  
    public static String removeFromCommaSplittableTextIfExists(final String text,
                                                               final String extraValues) {
      if (TextUtils.isEmpty(extraValues)) {
        return EMPTY_STRING;
      }
      final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
      if (!containsInArray(text, elements)) {
        return extraValues;
      }
      final ArrayList<String> result = new ArrayList<>(elements.length - 1);
      for (final String element : elements) {
        if (!text.equals(element)) {
          result.add(element);
        }
      }
      return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
    }
  
    public static String capitalizeFirstCodePoint(final String s,
                                                  final Locale locale) {
      if (s.length() <= 1) {
        return s.toUpperCase(getLocaleUsedForToTitleCase(locale));
      }
      // Please refer to the comment below in
      // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
      final int cutoff = s.offsetByCodePoints(0, 1);
      return s.substring(0, cutoff).toUpperCase(getLocaleUsedForToTitleCase(locale))
          + s.substring(cutoff);
    }
  
    public static int[] toCodePointArray(final CharSequence charSequence) {
      return toCodePointArray(charSequence, 0, charSequence.length());
    }
  
    private static final int[] EMPTY_CODEPOINTS = {};
  
    /**
     * Converts a range of a string to an array of code points.
     *
     * @param charSequence the source string.
     * @param startIndex   the start index inside the string in java chars, inclusive.
     * @param endIndex     the end index inside the string in java chars, exclusive.
     * @return a new array of code points. At most endIndex - startIndex, but possibly less.
     */
    public static int[] toCodePointArray(final CharSequence charSequence,
                                         final int startIndex, final int endIndex) {
      final int length = charSequence.length();
      if (length <= 0) {
        return EMPTY_CODEPOINTS;
      }
      final int[] codePoints =
          new int[Character.codePointCount(charSequence, startIndex, endIndex)];
      copyCodePointsAndReturnCodePointCount(codePoints, charSequence, startIndex, endIndex,
          false /* downCase */);
      return codePoints;
    }
  
    /**
     * Copies the codepoints in a CharSequence to an int array.
     * <p>
     * This method assumes there is enough space in the array to store the code points. The size
     * can be measured with Character#codePointCount(CharSequence, int, int) before passing to this
     * method. If the int array is too small, an ArrayIndexOutOfBoundsException will be thrown.
     * Also, this method makes no effort to be thread-safe. Do not modify the CharSequence while
     * this method is running, or the behavior is undefined.
     * This method can optionally downcase code points before copying them, but it pays no attention
     * to locale while doing so.
     *
     * @param destination  the int array.
     * @param charSequence the CharSequence.
     * @param startIndex   the start index inside the string in java chars, inclusive.
     * @param endIndex     the end index inside the string in java chars, exclusive.
     * @param downCase     if this is true, code points will be downcased before being copied.
     * @return the number of copied code points.
     */
    public static int copyCodePointsAndReturnCodePointCount(final int[] destination,
                                                            final CharSequence charSequence, final int startIndex, final int endIndex,
                                                            final boolean downCase) {
      int destIndex = 0;
      for (int index = startIndex; index < endIndex;
           index = Character.offsetByCodePoints(charSequence, index, 1)) {
        final int codePoint = Character.codePointAt(charSequence, index);
        // TODO: stop using this, as it's not aware of the locale and does not always do
        // the right thing.
        destination[destIndex] = downCase ? Character.toLowerCase(codePoint) : codePoint;
        destIndex++;
      }
      return destIndex;
    }
  
    public static int[] toSortedCodePointArray(final String string) {
      final int[] codePoints = toCodePointArray(string);
      Arrays.sort(codePoints);
      return codePoints;
    }
  
    public static boolean isIdenticalAfterUpcase(final String text) {
      final int length = text.length();
      int i = 0;
      while (i < length) {
        final int codePoint = text.codePointAt(i);
        if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
          return false;
        }
        i += Character.charCount(codePoint);
      }
      return true;
    }
  
    public static boolean isIdenticalAfterDowncase(final String text) {
      final int length = text.length();
      int i = 0;
      while (i < length) {
        final int codePoint = text.codePointAt(i);
        if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
          return false;
        }
        i += Character.charCount(codePoint);
      }
      return true;
    }
  
    public static boolean isIdenticalAfterCapitalizeEachWord(final String text) {
      boolean needsCapsNext = true;
      final int len = text.length();
      for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
        final int codePoint = text.codePointAt(i);
        if (Character.isLetter(codePoint)) {
          if ((needsCapsNext && !Character.isUpperCase(codePoint))
              || (!needsCapsNext && !Character.isLowerCase(codePoint))) {
            return false;
          }
        }
        // We need a capital letter next if this is a whitespace.
        needsCapsNext = Character.isWhitespace(codePoint);
      }
      return true;
    }
  
    // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
    // which should be capitalized together in *some* cases.
    public static String capitalizeEachWord(final String text, final Locale locale) {
      final StringBuilder builder = new StringBuilder();
      boolean needsCapsNext = true;
      final int len = text.length();
      for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
        final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1));
        if (needsCapsNext) {
          builder.append(nextChar.toUpperCase(locale));
        } else {
          builder.append(nextChar.toLowerCase(locale));
        }
        // We need a capital letter next if this is a whitespace.
        needsCapsNext = Character.isWhitespace(nextChar.codePointAt(0));
      }
      return builder.toString();
    }
  
    private static final String LANGUAGE_GREEK = "el";
  
    private static Locale getLocaleUsedForToTitleCase(final Locale locale) {
      // In Greek locale {@link String#toUpperCase(Locale)} eliminates accents from its result.
      // In order to get accented upper case letter, {@link Locale#ROOT} should be used.
      if (LANGUAGE_GREEK.equals(locale.getLanguage())) {
        return Locale.ROOT;
      }
      return locale;
    }
  
    public static String toTitleCaseOfKeyLabel(final String label,
                                               final Locale locale) {
      if (label == null) {
        return label;
      }
      return label.toUpperCase(getLocaleUsedForToTitleCase(locale));
    }
  
    public static int toTitleCaseOfKeyCode(final int code, final Locale locale) {
      if (!Constants.isLetterCode(code)) {
        return code;
      }
      final String label = newSingleCodePointString(code);
      final String titleCaseLabel = toTitleCaseOfKeyLabel(label, locale);
      return codePointCount(titleCaseLabel) == 1
          ? titleCaseLabel.codePointAt(0) : Constants.CODE_UNSPECIFIED;
    }
  }
  