// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.contextualsearch; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; /** * Blacklist used to suppress selections. */ public class ContextualSearchBlacklist { /** * Reasons that may cause a selection to be blacklisted. */ public enum BlacklistReason { NONE, NUMBER, DETERMINER, PREPOSITION, NAVIGATION, MISC } // Number pattern. private static final String DIGITS_PATTERN = "^\\d+$"; private static final Pattern mDigitsPattern = Pattern.compile(DIGITS_PATTERN); // Blacklist. private static final Map<String, BlacklistReason> BLACKLIST; static { Map<String, BlacklistReason> codes = new HashMap<>(); codes.put("a", BlacklistReason.DETERMINER); codes.put("el", BlacklistReason.DETERMINER); codes.put("la", BlacklistReason.DETERMINER); codes.put("that", BlacklistReason.DETERMINER); codes.put("the", BlacklistReason.DETERMINER); codes.put("this", BlacklistReason.DETERMINER); codes.put("un", BlacklistReason.DETERMINER); codes.put("your", BlacklistReason.DETERMINER); codes.put("by", BlacklistReason.PREPOSITION); codes.put("con", BlacklistReason.PREPOSITION); codes.put("del", BlacklistReason.PREPOSITION); codes.put("en", BlacklistReason.PREPOSITION); codes.put("for", BlacklistReason.PREPOSITION); codes.put("from", BlacklistReason.PREPOSITION); codes.put("in", BlacklistReason.PREPOSITION); codes.put("of", BlacklistReason.PREPOSITION); codes.put("on", BlacklistReason.PREPOSITION); codes.put("para", BlacklistReason.PREPOSITION); codes.put("por", BlacklistReason.PREPOSITION); codes.put("to", BlacklistReason.PREPOSITION); codes.put("with", BlacklistReason.PREPOSITION); codes.put("account", BlacklistReason.NAVIGATION); codes.put("com", BlacklistReason.NAVIGATION); codes.put("continuar", BlacklistReason.NAVIGATION); codes.put("continue", BlacklistReason.NAVIGATION); codes.put("download", BlacklistReason.NAVIGATION); codes.put("descargar", BlacklistReason.NAVIGATION); codes.put("facebook", BlacklistReason.NAVIGATION); codes.put("google", BlacklistReason.NAVIGATION); codes.put("here1234567891011", BlacklistReason.NAVIGATION); codes.put("https", BlacklistReason.NAVIGATION); codes.put("lanjutkan", BlacklistReason.NAVIGATION); codes.put("menu", BlacklistReason.NAVIGATION); codes.put("more", BlacklistReason.NAVIGATION); codes.put("next", BlacklistReason.NAVIGATION); codes.put("play", BlacklistReason.NAVIGATION); codes.put("prevnext", BlacklistReason.NAVIGATION); codes.put("search", BlacklistReason.NAVIGATION); codes.put("video", BlacklistReason.NAVIGATION); codes.put("videos", BlacklistReason.NAVIGATION); codes.put("whatsapp", BlacklistReason.NAVIGATION); codes.put("www", BlacklistReason.NAVIGATION); codes.put("youtube", BlacklistReason.NAVIGATION); codes.put("دانلود", BlacklistReason.NAVIGATION); codes.put("and", BlacklistReason.MISC); codes.put("android", BlacklistReason.MISC); codes.put("are", BlacklistReason.MISC); codes.put("available", BlacklistReason.MISC); codes.put("de", BlacklistReason.MISC); codes.put("do", BlacklistReason.MISC); codes.put("e", BlacklistReason.MISC); codes.put("have", BlacklistReason.MISC); codes.put("is", BlacklistReason.MISC); codes.put("m", BlacklistReason.MISC); codes.put("mobile", BlacklistReason.MISC); codes.put("no", BlacklistReason.MISC); codes.put("offline", BlacklistReason.MISC); codes.put("online", BlacklistReason.MISC); codes.put("or", BlacklistReason.MISC); codes.put("page", BlacklistReason.MISC); codes.put("que", BlacklistReason.MISC); codes.put("se", BlacklistReason.MISC); codes.put("videollamadas", BlacklistReason.MISC); codes.put("waiting", BlacklistReason.MISC); codes.put("was", BlacklistReason.MISC); codes.put("x", BlacklistReason.MISC); codes.put("y", BlacklistReason.MISC); codes.put("you", BlacklistReason.MISC); BLACKLIST = Collections.unmodifiableMap(codes); } // Metrics codes. private static final int NONE_SEEN = 0; private static final int NONE_NOT_SEEN = 1; private static final int NUMBER_SEEN = 2; private static final int NUMBER_NOT_SEEN = 3; private static final int DETERMINER_SEEN = 4; private static final int DETERMINER_NOT_SEEN = 5; private static final int PREPOSITION_SEEN = 6; private static final int PREPOSITION_NOT_SEEN = 7; private static final int NAVIGATION_SEEN = 8; private static final int NAVIGATION_NOT_SEEN = 9; private static final int MISC_SEEN = 10; private static final int MISC_NOT_SEEN = 11; public static final int BLACKLIST_BOUNDARY = 12; // Blacklist Metrics Code map. // TODO(pedrosimonetti): Design better solution for getting metrics codes and use it elsewhere. private static final Map<BlacklistSeenKey, Integer> BLACKLIST_METRICS_CODE; static { Map<BlacklistSeenKey, Integer> codes = new HashMap<>(); codes.put(new BlacklistSeenKey(BlacklistReason.NONE, true), NONE_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.NONE, false), NONE_NOT_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.NUMBER, true), NUMBER_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.NUMBER, false), NUMBER_NOT_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.DETERMINER, true), DETERMINER_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.DETERMINER, false), DETERMINER_NOT_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.PREPOSITION, true), PREPOSITION_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.PREPOSITION, false), PREPOSITION_NOT_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.NAVIGATION, true), NAVIGATION_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.NAVIGATION, false), NAVIGATION_NOT_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.MISC, true), MISC_SEEN); codes.put(new BlacklistSeenKey(BlacklistReason.MISC, false), MISC_NOT_SEEN); BLACKLIST_METRICS_CODE = Collections.unmodifiableMap(codes); } // Key used in the Blacklist Metrics Code map. static class BlacklistSeenKey { final BlacklistReason mReason; final boolean mWasSeen; final int mHashCode; BlacklistSeenKey(BlacklistReason reason, boolean wasSeen) { mReason = reason; mWasSeen = wasSeen; mHashCode = 31 * reason.hashCode() + (wasSeen ? 1231 : 1237); } @Override public boolean equals(Object obj) { if (!(obj instanceof BlacklistSeenKey)) { return false; } if (obj == this) { return true; } BlacklistSeenKey other = (BlacklistSeenKey) obj; return mReason.equals(other.mReason) && mWasSeen == other.mWasSeen; } @Override public int hashCode() { return mHashCode; } } /** * Tests the selection against the blacklist heuristics, returning a reason to suppress the * selection, or BlacklistReason.NONE, if no reason was found to suppress it. * @param selection The given selection. * @return The reason to suppress or not the selection. */ public static BlacklistReason findReasonToSuppressSelection(String selection) { selection = selection.toLowerCase(Locale.getDefault()); if (isNumber(selection)) { return BlacklistReason.NUMBER; } BlacklistReason blacklistReason = BLACKLIST.get(selection); if (blacklistReason != null) { return blacklistReason; } return BlacklistReason.NONE; } /** * @param reason The reason for blacklisting. * @param wasSeen Whether the results were seen. * @return The code used to log the blacklist metrics. */ public static Integer getBlacklistMetricsCode(BlacklistReason reason, boolean wasSeen) { return BLACKLIST_METRICS_CODE.get(new BlacklistSeenKey(reason, wasSeen)); } /** * @param selection A given selection. * @return Whether the given |selection| represents a number. */ private static boolean isNumber(String selection) { return mDigitsPattern.matcher(selection).find(); } }