package com.simplecity.amp_library.utils;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.simplecity.amp_library.R;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.Formatter;
import java.util.Locale;
import java.util.regex.Pattern;
public class StringUtils {
private static final String TAG = "StringUtils";
private static StringBuilder sFormatBuilder = new StringBuilder();
private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
private static Pattern pattern = Pattern.compile("^(?i)\\s*(?:the |an |a )|(?:, the|, an|, a)\\s*$|[\\[\\]\\(\\)!\\?\\.,']");
private StringUtils() {
}
/**
* Method makeAlbumsLabel.
*
* @param context context
* @param numalbums the number of albums for this artist
* @param numsongs the number of songs for this artist
* @param isUnknown boolean
* @return a label in the vein of "5 albums | 2 songs"
*/
public static String makeAlbumsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) {
// There are two formats for the albums/songs information:
// "N Song(s)" - used for unknown artist/album
// "N Album(s)" - used for known albums
final StringBuilder songs_albums = new StringBuilder();
final Resources r = context.getResources();
if (isUnknown) {
if (numsongs == 1) {
songs_albums.append(context.getString(R.string.onesong));
} else if (numsongs > 0) {
final String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
sFormatBuilder.setLength(0);
sFormatter.format(f, numsongs);
songs_albums.append(sFormatBuilder);
}
} else if (numalbums > 0) {
final String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
sFormatBuilder.setLength(0);
sFormatter.format(f, numalbums);
songs_albums.append(sFormatBuilder);
songs_albums.append(context.getString(R.string.albumsongseparator));
}
return songs_albums.toString();
}
/**
* Method makeTimeString.
* <p>
* Todo: Move to StringUtils or somewhere else
*
* @param context Context
* @param secs long
* @return String
*/
public static String makeTimeString(@NonNull Context context, long secs) {
sFormatBuilder.setLength(0);
return secs < 3600 ? makeShortTimeString(context, secs) : makeLongTimeString(context, secs);
}
private static String makeLongTimeString(@NonNull Context context, long secs) {
return makeTimeString(context.getString(R.string.durationformatlong), secs);
}
private static String makeShortTimeString(@NonNull Context context, long secs) {
return makeTimeString(context.getString(R.string.durationformatshort), secs);
}
private static String makeTimeString(String formatString, long secs) {
return sFormatter.format(formatString,
secs / 3600,
secs / 60,
(secs / 60) % 60,
secs,
secs % 60)
.toString();
}
/**
* Method makeSubfoldersLabel.
*
* @param context context
* @param numSubfolders the number of subFolders for this folder
* @param numSubfiles the number of subFiles for this folder
* @return a label in the vein of "5 folders | 3 files"
*/
public static String makeSubfoldersLabel(Context context, int numSubfolders, int numSubfiles) {
final StringBuilder string = new StringBuilder();
final Resources r = context.getResources();
if (numSubfolders != 0) {
if (numSubfolders == 1) {
string.append(context.getString(R.string.onefolder));
} else {
final String f = r.getQuantityText(R.plurals.Nfolders, numSubfolders)
.toString();
sFormatBuilder.setLength(0);
sFormatter.format(f, numSubfolders);
string.append(sFormatBuilder);
}
}
if (numSubfiles > 0 && numSubfolders > 0) {
string.append(" | ");
}
if (numSubfiles != 0) {
if (numSubfiles == 1) {
string.append(context.getString(R.string.onesong));
} else {
final String f = r.getQuantityText(R.plurals.Nsongs, numSubfiles)
.toString();
sFormatBuilder.setLength(0);
sFormatter.format(f, numSubfiles);
string.append(sFormatBuilder);
}
}
if (numSubfiles == 0 && numSubfolders == 0) {
string.append("-");
}
return string.toString();
}
public static String makeAlbumAndSongsLabel(Context context, int numalbums, int numsongs) {
final StringBuilder stringBuilder = new StringBuilder();
final Resources r = context.getResources();
String f;
if (numalbums > 0) {
f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
sFormatBuilder.setLength(0);
sFormatter.format(f, numalbums);
stringBuilder.append(sFormatBuilder);
}
if (numalbums > 0 && numsongs > 0) {
stringBuilder.append(" | ");
}
if (numsongs == 1) {
stringBuilder.append(context.getString(R.string.onesong));
} else if (numsongs > 0) {
f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
sFormatBuilder.setLength(0);
sFormatter.format(f, numsongs);
stringBuilder.append(sFormatBuilder);
}
return stringBuilder.toString();
}
/**
* Converts a name to a "key" that can be used for grouping, sorting
* and searching.
* The rules that govern this conversion are:
* - remove 'special' characters like ()[]'!?.,
* - remove leading/trailing spaces
* - convert everything to lowercase
* - remove leading "the ", "an " and "a "
* - remove trailing ", the|an|a"
* - remove accents. This step leaves us with CollationKey data,
* which is not human readable
*
* @param name The artist or album name to convert
* @return The "key" for the given name.
*/
public static String keyFor(String name) {
if (!TextUtils.isEmpty(name)) {
name = pattern.matcher(name)
.replaceAll("")
.trim()
.toLowerCase();
} else {
name = "";
}
return name;
}
/**
* @return true if String s1 contains String s2, ignoring case.
*/
public static boolean containsIgnoreCase(String s1, String s2) {
return s1.toLowerCase().contains(s2.toLowerCase());
}
/**
* Find the Jaro Winkler Similarity which indicates the similarity score between two Strings.
* <p>
* Note: This method splits the {@param first} string at whitespaces, and returns the best Jaro-Winkler
* score between {@param second} and the 'split' strings.
*/
public static double getAdjustedJaroWinklerSimilarity(@Nullable String first, @Nullable String second) {
if (TextUtils.isEmpty(first) || TextUtils.isEmpty(second)) {
return 0;
}
String[] split = first.split("\\s");
if (split.length > 1) {
double score = 0;
for (String str : split) {
double curScore = getJaroWinklerSimilarity(str, second);
if (curScore > score) {
score = curScore;
}
}
//Make sure we do a normal (non-adjusted) test as well, in case that comes out as our best match.
return Math.max(getJaroWinklerSimilarity(first, second), score);
} else {
return getJaroWinklerSimilarity(first, second);
}
}
/**
* <p>Find the Jaro Winkler Similarity which indicates the similarity score between two Strings.</p>
* <p>
* <p>The Jaro measure is the weighted sum of percentage of matched characters from each file and transposed characters.
* Winkler increased this measure for matching initial characters.</p>
* <p>
* <p>This implementation is based on the Jaro Winkler similarity algorithm
* from <a href="http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance">http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance</a>.</p>
* <p>
*
* @param first the first String, must not be null
* @param second the second String, must not be null
* @return result similarity
*/
public static double getJaroWinklerSimilarity(@NonNull String first, @NonNull String second) {
final double DEFAULT_SCALING_FACTOR = 0.1;
first = first.toLowerCase();
second = second.toLowerCase();
first = Normalizer.normalize(first, Normalizer.Form.NFD);
second = Normalizer.normalize(second, Normalizer.Form.NFD);
final int[] mtp = matches(first, second);
final double m = mtp[0];
if (m == 0) {
return 0D;
}
final double j = ((m / first.length() + m / second.length() + (m - mtp[1]) / m)) / 3;
final double jw = j < 0.7D ? j : j + Math.min(DEFAULT_SCALING_FACTOR, 1D / mtp[3]) * mtp[2] * (1D - j);
return Math.round(jw * 100.0D) / 100.0D;
}
private static int[] matches(final CharSequence first, final CharSequence second) {
CharSequence max, min;
if (first.length() > second.length()) {
max = first;
min = second;
} else {
max = second;
min = first;
}
final int range = Math.max(max.length() / 2 - 1, 0);
final int[] matchIndexes = new int[min.length()];
Arrays.fill(matchIndexes, -1);
final boolean[] matchFlags = new boolean[max.length()];
int matches = 0;
for (int mi = 0; mi < min.length(); mi++) {
final char c1 = min.charAt(mi);
for (int xi = Math.max(mi - range, 0), xn = Math.min(mi + range + 1, max.length()); xi < xn; xi++) {
if (!matchFlags[xi] && c1 == max.charAt(xi)) {
matchIndexes[mi] = xi;
matchFlags[xi] = true;
matches++;
break;
}
}
}
final char[] ms1 = new char[matches];
final char[] ms2 = new char[matches];
for (int i = 0, si = 0; i < min.length(); i++) {
if (matchIndexes[i] != -1) {
ms1[si] = min.charAt(i);
si++;
}
}
for (int i = 0, si = 0; i < max.length(); i++) {
if (matchFlags[i]) {
ms2[si] = max.charAt(i);
si++;
}
}
int transpositions = 0;
for (int mi = 0; mi < ms1.length; mi++) {
if (ms1[mi] != ms2[mi]) {
transpositions++;
}
}
int prefix = 0;
for (int mi = 0; mi < min.length(); mi++) {
if (first.charAt(mi) == second.charAt(mi)) {
prefix++;
} else {
break;
}
}
return new int[]{matches, transpositions / 2, prefix, max.length()};
}
}