diff --git a/FCL/src/main/java/com/tungsten/fcl/game/LocalizedRemoteModRepository.java b/FCL/src/main/java/com/tungsten/fcl/game/LocalizedRemoteModRepository.java index 4d9e41f5..449b4cde 100644 --- a/FCL/src/main/java/com/tungsten/fcl/game/LocalizedRemoteModRepository.java +++ b/FCL/src/main/java/com/tungsten/fcl/game/LocalizedRemoteModRepository.java @@ -17,23 +17,22 @@ */ package com.tungsten.fcl.game; -import android.app.appsearch.SearchResult; +import static com.tungsten.fclcore.util.Logging.LOG; import com.tungsten.fcl.util.ModTranslations; import com.tungsten.fclcore.mod.LocalModFile; import com.tungsten.fclcore.mod.RemoteMod; import com.tungsten.fclcore.mod.RemoteModRepository; -import com.tungsten.fclcore.util.Lang; import com.tungsten.fclcore.util.Pair; import com.tungsten.fclcore.util.StringUtils; import java.io.IOException; import java.nio.file.Path; import java.util.*; +import java.util.logging.Level; import java.util.stream.Stream; public abstract class LocalizedRemoteModRepository implements RemoteModRepository { - // Yes, I'm not kidding you. The similarity check is based on these two magic number. :) private static final int CONTAIN_CHINESE_WEIGHT = 10; private static final int INITIAL_CAPACITY = 16; @@ -64,45 +63,45 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor if (count >= 3) break; } - SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder); - RemoteMod[] searchResultArray = new RemoteMod[pageSize]; - int chineseIndex = 0, englishIndex = searchResultArray.length - 1; - for (RemoteMod remoteMod : Lang.toIterable(searchResult.getUnsortedResults())) { - if (chineseIndex > englishIndex) { - throw new IOException("There are too many search results!"); - } + int totalPages, chineseIndex = 0, englishIndex = pageSize - 1; + { + SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder); + for (Iterator iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) { + if (chineseIndex > englishIndex) { + LOG.log(Level.WARNING, "Too many search results! Are the backed remote mod repository broken? Or are the API broken?"); + continue; + } - ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); - if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) { - searchResultArray[chineseIndex++] = remoteMod; - } else { - searchResultArray[englishIndex--] = remoteMod; + RemoteMod remoteMod = iterator.next(); + ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); + if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) { + searchResultArray[chineseIndex++] = remoteMod; + } else { + searchResultArray[englishIndex--] = remoteMod; + } } + totalPages = searchResult.getTotalPages(); } - int totalPages = searchResult.getTotalPages(); - searchResult = null; // Release memory - StringUtils.DynamicCommonSubsequence calc = new StringUtils.DynamicCommonSubsequence(16, 16); + StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator(); return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> { ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { return Pair.pair(remoteMod, Integer.MAX_VALUE); } - String chineseRemoteModName = chineseRemoteMod.getName(); if (searchFilter.isEmpty() || chineseRemoteModName.isEmpty()) { return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); } - - int weight = calc.calc(searchFilter, chineseRemoteModName); - for (int i = 0;i < searchFilter.length(); i ++) { + int l = levCalculator.calc(searchFilter, chineseRemoteModName); + for (int i = 0; i < searchFilter.length(); i++) { if (chineseRemoteModName.indexOf(searchFilter.charAt(i)) >= 0) { - return Pair.pair(remoteMod, weight + CONTAIN_CHINESE_WEIGHT); + l -= CONTAIN_CHINESE_WEIGHT; } } - return Pair.pair(remoteMod, weight); - }).sorted(Comparator.>comparingInt(Pair::getValue).reversed()).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages); + return Pair.pair(remoteMod, l); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages); } @Override diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseForgeRemoteModRepository.java b/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseForgeRemoteModRepository.java index 7efcf1f8..2d6bb738 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseForgeRemoteModRepository.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/mod/curse/CurseForgeRemoteModRepository.java @@ -112,7 +112,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository .getJson(new TypeToken>>() { }.getType()); Stream res = response.getData().stream().map(CurseAddon::toMod); - if (sortType != SortType.NAME || searchFilter.length() == 0) { + if (sortType != SortType.NAME || searchFilter.isEmpty()) { return new SearchResult(res, (int)Math.ceil((double)response.pagination.totalCount / pageSize)); } @@ -122,25 +122,11 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository searchFilterWords.put(s, searchFilterWords.getOrDefault(s, 0) + 1); } + StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator(); + return new SearchResult(res.map(remoteMod -> { String lowerCaseResult = remoteMod.getTitle().toLowerCase(); - int[][] lev = new int[lowerCaseSearchFilter.length() + 1][lowerCaseResult.length() + 1]; - for (int i = 0; i < lowerCaseResult.length() + 1; i++) { - lev[0][i] = i; - } - for (int i = 0; i < lowerCaseSearchFilter.length() + 1; i++) { - lev[i][0] = i; - } - for (int i = 1; i < lowerCaseSearchFilter.length() + 1; i++) { - for (int j = 1; j < lowerCaseResult.length() + 1; j++) { - int countByInsert = lev[i][j - 1] + 1; - int countByDel = lev[i - 1][j] + 1; - int countByReplace = lowerCaseSearchFilter.charAt(i - 1) == lowerCaseResult.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; - lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); - } - } - - int diff = lev[lowerCaseSearchFilter.length()][lowerCaseResult.length()]; + int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult); for (String s : StringUtils.tokenize(lowerCaseResult)) { if (searchFilterWords.containsKey(s)) { diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/util/StringUtils.java b/FCLCore/src/main/java/com/tungsten/fclcore/util/StringUtils.java index 47207101..2cb0df5b 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/util/StringUtils.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/util/StringUtils.java @@ -17,8 +17,6 @@ */ package com.tungsten.fclcore.util; -import com.tungsten.fclcore.util.platform.OperatingSystem; - import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; @@ -347,29 +345,59 @@ public final class StringUtils { return true; } - public static class DynamicCommonSubsequence { - private LongestCommonSubsequence calculator; + public static class LevCalculator { + private int[][] lev; - public DynamicCommonSubsequence(int intLengthA, int intLengthB) { - if (intLengthA > intLengthB) { - calculator = new LongestCommonSubsequence(intLengthA, intLengthB); - } else { - calculator = new LongestCommonSubsequence(intLengthB, intLengthA); + public LevCalculator() { + } + + public LevCalculator(int length1, int length2) { + allocate(length1, length2); + } + + private void allocate(int length1, int length2) { + length1 += 1; + length2 += 1; + lev = new int[length1][length2]; + for (int i = 1; i < length1; i++) { + lev[i][0] = i; + } + int[] cache = lev[0]; + for (int i = 0; i < length2; i++) { + cache[i] = i; } } + public int getLength1() { + return lev.length; + } + + public int getLength2() { + return lev[0].length; + } + + private int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + public int calc(CharSequence a, CharSequence b) { - if (a.length() < b.length()) { - CharSequence t = a; - a = b; - b = t; + if (lev == null || a.length() >= lev.length || b.length() >= lev[0].length) { + allocate(a.length(), b.length()); } - if (calculator.maxLengthA < a.length() || calculator.maxLengthB < b.length()) { - calculator = new LongestCommonSubsequence(a.length(), b.length()); + int lengthA = a.length() + 1, lengthB = b.length() + 1; + + for (int i = 1; i < lengthA; i++) { + for (int j = 1; j < lengthB; j++) { + lev[i][j] = min( + lev[i][j - 1] + 1, // insert + lev[i - 1][j] + 1, // del + a.charAt(i - 1) == b.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1 // replace + ); + } } - return calculator.calc(a, b); + return lev[a.length()][b.length()]; } }