From 0b4d2fd24dac7a229317c1282ab2d30bfd92ff59 Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Fri, 7 Apr 2023 09:53:46 +0200 Subject: [PATCH] feat: move YAML parsing to compile time Fixes #77 --- pom.xml | 22 +- src/main/java/ua_parser/CachingParser.java | 12 - src/main/java/ua_parser/DeviceParser.java | 29 +- src/main/java/ua_parser/OSParser.java | 34 +- src/main/java/ua_parser/Parser.java | 82 +- src/main/java/ua_parser/Regexes.java | 1210 +++++++++++++++++ src/main/java/ua_parser/UserAgentParser.java | 33 +- .../java/ua_parser/CachingParserTest.java | 14 - src/test/java/ua_parser/ParserTest.java | 31 +- src/test/java/ua_parser/RegexesBuilder.java | 185 +++ .../java/ua_parser/RegexesBuilderTest.java | 170 +++ 11 files changed, 1588 insertions(+), 234 deletions(-) create mode 100644 src/main/java/ua_parser/Regexes.java create mode 100644 src/test/java/ua_parser/RegexesBuilder.java create mode 100644 src/test/java/ua_parser/RegexesBuilderTest.java diff --git a/pom.xml b/pom.xml index 30688a7..ab88102 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github.ua-parser uap-java jar - 1.5.5-SNAPSHOT + 2.0.0-SNAPSHOT User Agent Parser for Java @@ -56,17 +56,14 @@ - - + + ua_parser ${basedir}/uap-core regexes.yaml - - - - + ua_parser ${basedir}/uap-core/test_resources @@ -152,16 +149,17 @@ - - org.yaml - snakeyaml - 1.33 - org.apache.commons commons-collections4 4.4 + + org.yaml + snakeyaml + 2.0 + test + junit junit diff --git a/src/main/java/ua_parser/CachingParser.java b/src/main/java/ua_parser/CachingParser.java index 7147d4a..a2a1267 100644 --- a/src/main/java/ua_parser/CachingParser.java +++ b/src/main/java/ua_parser/CachingParser.java @@ -1,6 +1,5 @@ package ua_parser; -import java.io.InputStream; import java.util.Map; import org.apache.commons.collections4.map.LRUMap; @@ -38,23 +37,12 @@ public CachingParser() { super(); } - public CachingParser(InputStream regexYaml) { - super(regexYaml); - } - public CachingParser(int cacheSize) { super(); assert cacheSize > 0: INVALID_CACHE_SIZE_ERROR_MESSAGE; this.cacheSize = cacheSize; } - public CachingParser(InputStream regexYaml, int cacheSize) { - super(regexYaml); - assert cacheSize > 0: INVALID_CACHE_SIZE_ERROR_MESSAGE; - this.cacheSize = cacheSize; - } - - // ------------------------------------------ @Override diff --git a/src/main/java/ua_parser/DeviceParser.java b/src/main/java/ua_parser/DeviceParser.java index 72ed6ff..9b3067b 100644 --- a/src/main/java/ua_parser/DeviceParser.java +++ b/src/main/java/ua_parser/DeviceParser.java @@ -18,8 +18,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,8 +30,8 @@ public class DeviceParser { private final List patterns; - public DeviceParser(List patterns) { - this.patterns = patterns; + public DeviceParser() { + this.patterns = Regexes.getDevicePatterns(); } public Device parse(String agentString) { @@ -50,29 +48,6 @@ public Device parse(String agentString) { return Device.OTHER; } - /** - * Constructs a thread-safe DeviceParser. - * @param configList configure a device parser from a list of regexp hashmaps - * @return a device parser - */ - public static DeviceParser fromList(List> configList) { - List configPatterns = new ArrayList<>(); - for (Map configMap : configList) { - configPatterns.add(DeviceParser.patternFromMap(configMap)); - } - return new DeviceParser(new CopyOnWriteArrayList<>(configPatterns)); - } - - protected static DevicePattern patternFromMap(Map configMap) { - String regex = configMap.get("regex"); - if (regex == null) { - throw new IllegalArgumentException("Device is missing regex"); - } - Pattern pattern = "i".equals(configMap.get("regex_flag")) // no ohter flags used (by now) - ? Pattern.compile(regex, Pattern.CASE_INSENSITIVE) : Pattern.compile(regex); - return new DevicePattern(pattern, configMap.get("device_replacement")); - } - protected static class DevicePattern { private static final Pattern SUBSTITUTIONS_PATTERN = Pattern.compile("\\$\\d"); private final Pattern pattern; diff --git a/src/main/java/ua_parser/OSParser.java b/src/main/java/ua_parser/OSParser.java index 59c0ea5..b706e93 100644 --- a/src/main/java/ua_parser/OSParser.java +++ b/src/main/java/ua_parser/OSParser.java @@ -16,10 +16,7 @@ package ua_parser; -import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,22 +29,8 @@ public class OSParser { private final List patterns; - public OSParser(List patterns) { - this.patterns = patterns; - } - - /** - * Constructs a thread-safe OSParser. - * @param configList configure an operating system parser from a list of regexp hashmaps - * @return operating system parser - */ - public static OSParser fromList(List> configList) { - List configPatterns = new ArrayList<>(); - - for (Map configMap : configList) { - configPatterns.add(OSParser.patternFromMap(configMap)); - } - return new OSParser(new CopyOnWriteArrayList<>(configPatterns)); + public OSParser() { + this.patterns = Regexes.getOSPatterns(); } public OS parse(String agentString) { @@ -64,19 +47,6 @@ public OS parse(String agentString) { return OS.OTHER; } - protected static OSPattern patternFromMap(Map configMap) { - String regex = configMap.get("regex"); - if (regex == null) { - throw new IllegalArgumentException("OS is missing regex"); - } - - return(new OSPattern(Pattern.compile(regex), - configMap.get("os_replacement"), - configMap.get("os_v1_replacement"), - configMap.get("os_v2_replacement"), - configMap.get("os_v3_replacement"))); - } - protected static class OSPattern { private final Pattern pattern; private final String osReplacement, v1Replacement, v2Replacement, v3Replacement; diff --git a/src/main/java/ua_parser/Parser.java b/src/main/java/ua_parser/Parser.java index e410805..99fe479 100644 --- a/src/main/java/ua_parser/Parser.java +++ b/src/main/java/ua_parser/Parser.java @@ -16,15 +16,6 @@ package ua_parser; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.SafeConstructor; - /** * Java implementation of UA Parser * @@ -32,51 +23,15 @@ */ public class Parser { - private static final String REGEX_YAML_PATH = "/ua_parser/regexes.yaml"; - - public static final int CODE_POINT_LIMIT = 3455764; private UserAgentParser uaParser; private OSParser osParser; private DeviceParser deviceParser; /** - * Creates a parser using the regular expression yaml file bundled in the jar. - * @throws RuntimeException if there's a problem reading the file from the classpath + * Creates a parser */ public Parser() { - this(getDefaultLoaderOptions()); - } - - /** - * Creates a parser using the regular expression yaml file bundled in the jar. - * - * @param loaderOptions configuration for loading parser safe limits. - * @throws RuntimeException if there's a problem reading the file from the classpath. - */ - public Parser(LoaderOptions loaderOptions) { - try (InputStream is = Parser.class.getResourceAsStream(REGEX_YAML_PATH)) { - initialize(is, loaderOptions); - } catch (IOException e) { - throw new RuntimeException("failed to initialize parser from regexes.yaml bundled in jar", e); - } - } - - /** - * Creates a parser using the supplied regular expression yaml file. - * It is the responsibility of the caller to close the InputStream after construction. - * @param regexYaml the yaml file containing the regular expressions - */ - public Parser(InputStream regexYaml) { - this(regexYaml, getDefaultLoaderOptions()); - } - /** - * Creates a parser using the supplied regular expression yaml file. - * It is the responsibility of the caller to close the InputStream after construction. - * @param regexYaml the yaml file containing the regular expressions - * @param loaderOptions configuration for loading parser safe limits. - */ - public Parser(InputStream regexYaml, LoaderOptions loaderOptions) { - initialize(regexYaml, loaderOptions); + initialize(); } public Client parse(String agentString) { @@ -98,34 +53,9 @@ public OS parseOS(String agentString) { return osParser.parse(agentString); } - public static LoaderOptions getDefaultLoaderOptions(){ - LoaderOptions options = new LoaderOptions(); - options.setCodePointLimit(CODE_POINT_LIMIT); - return options; - } - - private void initialize(InputStream regexYaml, LoaderOptions loaderOptions) { - Yaml yaml = new Yaml(new SafeConstructor(loaderOptions)); - - @SuppressWarnings("unchecked") - Map>> regexConfig = (Map>>) yaml.load(regexYaml); - - List> uaParserConfigs = regexConfig.get("user_agent_parsers"); - if (uaParserConfigs == null) { - throw new IllegalArgumentException("user_agent_parsers is missing from yaml"); - } - uaParser = UserAgentParser.fromList(uaParserConfigs); - - List> osParserConfigs = regexConfig.get("os_parsers"); - if (osParserConfigs == null) { - throw new IllegalArgumentException("os_parsers is missing from yaml"); - } - osParser = OSParser.fromList(osParserConfigs); - - List> deviceParserConfigs = regexConfig.get("device_parsers"); - if (deviceParserConfigs == null) { - throw new IllegalArgumentException("device_parsers is missing from yaml"); - } - deviceParser = DeviceParser.fromList(deviceParserConfigs); + private void initialize() { + uaParser = new UserAgentParser(); + osParser = new OSParser(); + deviceParser = new DeviceParser(); } } diff --git a/src/main/java/ua_parser/Regexes.java b/src/main/java/ua_parser/Regexes.java new file mode 100644 index 0000000..f8231ac --- /dev/null +++ b/src/main/java/ua_parser/Regexes.java @@ -0,0 +1,1210 @@ +/** + * Copyright 2023 Twitter, Inc + * + * 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 ua_parser; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.regex.Pattern; + +import ua_parser.DeviceParser.DevicePattern; +import ua_parser.OSParser.OSPattern; +import ua_parser.UserAgentParser.UAPattern; + +import java.util.ArrayList; + +/** + * This class is generated at build time, based on the content of "uap-core/regexes.yaml" + */ +class Regexes { + + public static List getUserAgentPatterns() { + List configPatterns = new ArrayList<>(); + configPatterns.add(new UAPattern(Pattern.compile("(GeoEvent Server) (\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(ArcGIS Pro)(?: (\\d+)\\.(\\d+)\\.([^ ]+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("ArcGIS Client Using WinInet"), "ArcMap", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OperationsDashboard)-(?:Windows)-(\\d+)\\.(\\d+)\\.(\\d+)"), "Operations Dashboard for ArcGIS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(arcgisearth)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "ArcGIS Earth", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("com.esri.(earth).phone/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "ArcGIS Earth", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(arcgis-explorer)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Explorer for ArcGIS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("arcgis-(collector|aurora)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Collector for ArcGIS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(arcgis-workforce)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Workforce for ArcGIS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Collector|Explorer|Workforce)-(?:Android|iOS)-(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "$1 for ArcGIS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Explorer|Collector)/(\\d+) CFNetwork"), "$1 for ArcGIS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("ArcGISRuntime-(Android|iOS|NET|Qt)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "ArcGIS Runtime SDK for $1", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("ArcGIS\\.?(iOS|Android|NET|Qt)(?:-|\\.)(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "ArcGIS Runtime SDK for $1", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("ArcGIS\\.Runtime\\.(Qt)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "ArcGIS Runtime SDK for $1", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Luminary)[Stage]+/(\\d+) CFNetwork"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(ESPN)[%20| ]+Radio/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Antenna)/(\\d+) CFNetwork"), "AntennaPod", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(TopPodcasts)Pro/(\\d+) CFNetwork"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MusicDownloader)Lite/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(.{0,200})-iPad\\/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(.{0,200})-iPhone/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(.{0,200})/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Luminary)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(espn\\.go)"), "ESPN", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(espnradio\\.com)"), "ESPN", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("ESPN APP$"), "ESPN", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(audioboom\\.com)"), "AudioBoom", null, null)); + configPatterns.add(new UAPattern(Pattern.compile(" (Rivo) RHYTHM"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(CFNetwork)(?:/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)"), "CFNetwork", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Pingdom\\.com_bot_version_)(\\d+)\\.(\\d+)"), "PingdomBot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PingdomTMS)/(\\d+)\\.(\\d+)\\.(\\d+)"), "PingdomBot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile(" (PTST)/(\\d+)(?:\\.(\\d+)|)$"), "WebPageTest.org bot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("X11; (Datanyze); Linux"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(NewRelicPinger)/(\\d+)\\.(\\d+)"), "NewRelicPingerBot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Tableau)/(\\d+)\\.(\\d+)"), "Tableau", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("AppleWebKit/\\d{1,10}\\.\\d{1,10}.{0,200} Safari.{0,200} (CreativeCloud)/(\\d+)\\.(\\d+).(\\d+)"), "Adobe CreativeCloud", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Salesforce)(?:.)\\/(\\d+)\\.(\\d?)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(\\(StatusCake\\))"), "StatusCakeBot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(facebookexternalhit)/(\\d+)\\.(\\d+)"), "FacebookBot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Google.{0,50}/\\+/web/snippet"), "GooglePlusBot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("via ggpht\\.com GoogleImageProxy"), "GmailImageProxy", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("YahooMailProxy; https://help\\.yahoo\\.com/kb/yahoo-mail-proxy-SLN28749\\.html"), "YahooMailProxy", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Twitterbot)/(\\d+)\\.(\\d+)"), "Twitterbot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("/((?:Ant-|)Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \\-](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\b(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|OgScrper|Pandora|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MSIE) (\\d+)\\.(\\d+)([a-z]\\d|[a-z]|);.{0,200} MSIECrawler"), "MSIECrawler", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(DAVdroid)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Google-HTTP-Java-Client|Apache-HttpClient|PostmanRuntime|Go-http-client|scalaj-http|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP|okhttp|aihttp|reqwest|axios|unirest-(?:java|python|ruby|nodejs|php|net))(?:[ /](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Pinterest(?:bot|))/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)[;\\s(]+\\+https://www.pinterest.com/bot.html"), "Pinterestbot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\\/\\$BotVersion|123metaspider-Bot|1470\\.net crawler|50\\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\\b\\w{0,30}favicon\\w{0,30}\\b|\\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\\(S\\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PHPCrawl|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\\.almaden\\.ibm\\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\\.ze\\.bz|ZooShot|ZyBorg|ArcGIS Hub Indexer)(?:[ /]v?(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\b(Boto3?|JetS3t|aws-(?:cli|sdk-(?:cpp|go|java|nodejs|ruby2?|dotnet-(?:\\d{1,2}|core)))|s3fs)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(FME)\\/(\\d+\\.\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(QGIS)\\/(\\d)\\.?0?(\\d{1,2})\\.?0?(\\d{1,2})"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(JOSM)/(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Tygron Platform) \\((\\d+)\\.(\\d+)\\.(\\d+(?:\\.\\d+| RC \\d+\\.\\d+))"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\[(FBAN/MessengerForiOS|FB_IAB/MESSENGER);FBAV/(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)|)"), "Facebook Messenger", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\[FB.{0,300};(FBAV)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), "Facebook", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\[FB.{0,300};"), "Facebook", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^.{0,200}?(?:\\/[A-Za-z0-9\\.]{0,50}|) {0,2}([A-Za-z0-9 \\-_\\!\\[\\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50}))[/ ](\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^.{0,200}?((?:[A-Za-z][A-Za-z0-9 -]{0,50}|)[^C][^Uu][Bb]ot)\\b(?:(?:[ /]| v)(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^.{0,200}?((?:[A-z0-9]{1,50}|[A-z\\-]{1,50} ?|)(?: the |)(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]{0,50})(?:(?:[ /]| v)(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(HbbTV)/(\\d+)\\.(\\d+)\\.(\\d+) \\("), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Chimera|SeaMonkey|Camino|Waterfox)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(SailfishBrowser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Sailfish Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\[(Pinterest)/[^\\]]{1,50}\\]"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Pinterest)(?: for Android(?: Tablet|)|)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mozilla.{1,200}Mobile.{1,100}(Instagram).(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mozilla.{1,200}Mobile.{1,100}(Flipboard).(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mozilla.{1,200}Mobile.{1,100}(Flipboard-Briefing).(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mozilla.{1,200}Mobile.{1,100}(Onefootball)\\/Android.(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Snapchat)\\/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Twitter for (?:iPhone|iPad)|TwitterAndroid)(?:\\/(\\d+)\\.(\\d+)|)"), "Twitter", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mozilla.{1,200}Mobile.{1,100}(Phantom\\/ios|android).(\\d+)\\.(\\d+)\\.(\\d+)"), "Phantom", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mozilla.{1,100}Mobile.{1,100}AspiegelBot"), "Spider", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("AspiegelBot"), "Spider", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)/(\\d+)\\.(\\d+) Basilisk/(\\d+)"), "Basilisk", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PaleMoon)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Pale Moon", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Fennec)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*)"), "Firefox Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Fennec)/(\\d+)\\.(\\d+)(pre)"), "Firefox Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Fennec)/(\\d+)\\.(\\d+)"), "Firefox Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(?:Mobile|Tablet);.{0,200}(Firefox)/(\\d+)\\.(\\d+)"), "Firefox Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre|))"), "Firefox ($1)", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)/(\\d+)\\.(\\d+)(a\\d+[a-z]*)"), "Firefox Alpha", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)/(\\d+)\\.(\\d+)(b\\d+[a-z]*)"), "Firefox Beta", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)-(?:\\d+\\.\\d+|)/(\\d+)\\.(\\d+)(a\\d+[a-z]*)"), "Firefox Alpha", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)-(?:\\d+\\.\\d+|)/(\\d+)\\.(\\d+)(b\\d+[a-z]*)"), "Firefox Beta", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|)"), "Firefox ($1)", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox).{0,200}Tablet browser (\\d+)\\.(\\d+)\\.(\\d+)"), "MicroB", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MozillaDeveloperPreview)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(FxiOS)/(\\d+)\\.(\\d+)(\\.(\\d+)|)(\\.(\\d+)|)"), "Firefox iOS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Flock)/(\\d+)\\.(\\d+)(b\\d+?)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(RockMelt)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Navigator)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Netscape", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Navigator)/(\\d+)\\.(\\d+)([ab]\\d+)"), "Netscape", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Netscape6)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+|)"), "Netscape", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MyIBrow)/(\\d+)\\.(\\d+)"), "My Internet Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(UC? ?Browser|UCWEB|U3)[ /]?(\\d+)\\.(\\d+)\\.(\\d+)"), "UC Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Opera Tablet).{0,200}Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Opera Mini)(?:/att|)/?(\\d+|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Opera)/.{1,100}Opera Mobi.{1,100}Version/(\\d+)\\.(\\d+)"), "Opera Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Opera)/(\\d+)\\.(\\d+).{1,100}Opera Mobi"), "Opera Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Opera Mobi.{1,100}(Opera)(?:/|\\s+)(\\d+)\\.(\\d+)"), "Opera Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Opera Mobi"), "Opera Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Opera)/9.80.{0,200}Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(?:Mobile Safari).{1,300}(OPR)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Opera Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(?:Chrome).{1,300}(OPR)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Opera", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Coast)/(\\d+).(\\d+).(\\d+)"), "Opera Coast", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OPiOS)/(\\d+).(\\d+).(\\d+)"), "Opera Mini", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Chrome/.{1,200}( MMS)/(\\d+).(\\d+).(\\d+)"), "Opera Neon", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(hpw|web)OS/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "webOS Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(luakit)"), "LuaKit", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Snowshoe)/(\\d+)\\.(\\d+).(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Gecko/\\d+ (Lightning)/(\\d+)\\.(\\d+)\\.?((?:[ab]?\\d+[a-z]*)|(?:\\d*))"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre|)) \\(Swiftfox\\)"), "Swiftfox", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|) \\(Swiftfox\\)"), "Swiftfox", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(rekonq)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|) Safari"), "Rekonq", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("rekonq"), "Rekonq", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(conkeror|Conkeror)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Conkeror", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(konqueror)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Konqueror", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(WeTab)-Browser"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Comodo_Dragon)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Comodo Dragon", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Symphony) (\\d+).(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("PLAYSTATION 3.{1,200}WebKit"), "NetFront NX", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("PLAYSTATION 3"), "NetFront", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PlayStation Portable)"), "NetFront", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PlayStation Vita)"), "NetFront NX", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("AppleWebKit.{1,200} (NX)/(\\d+)\\.(\\d+)\\.(\\d+)"), "NetFront NX", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Nintendo 3DS)"), "NetFront NX", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+)|)"), "Amazon Silk", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Puffin)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Windows Phone .{0,200}(Edge)/(\\d+)\\.(\\d+)"), "Edge Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(EdgiOS|EdgA)/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Edge Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(SamsungBrowser)/(\\d+)\\.(\\d+)"), "Samsung Internet", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(SznProhlizec)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Seznam prohlížeč", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(coc_coc_browser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Coc Coc", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(baidubrowser)[/\\s](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), "Baidu Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(FlyFlow)/(\\d+)\\.(\\d+)"), "Baidu Explorer", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MxBrowser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Maxthon", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Crosswalk)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Line)/(\\d+)\\.(\\d+)\\.(\\d+)"), "LINE", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MiuiBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)"), "MiuiBrowser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Mint Browser)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Mint Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(TopBuzz)/(\\d+).(\\d+).(\\d+)"), "TopBuzz", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mozilla.{1,200}Android.{1,200}(GSA)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Google", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MQQBrowser/Mini)(?:(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)"), "QQ Browser Mini", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MQQBrowser)(?:/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)"), "QQ Browser Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(QQBrowser)(?:/(\\d+)(?:\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)|)"), "QQ Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Mobile.{0,200}(DuckDuckGo)/(\\d+)"), "DuckDuckGo Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Tenta/)(\\d+)\\.(\\d+)\\.(\\d+)"), "Tenta Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Version/.{1,300}(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), "Chrome Mobile WebView", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("; wv\\).{1,300}(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), "Chrome Mobile WebView", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(CrMo)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), "Chrome Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(CriOS)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), "Chrome Mobile iOS", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) Mobile(?:[ /]|$)"), "Chrome Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile(" Mobile .{1,300}(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), "Chrome Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(chromeframe)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Chrome Frame", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(SLP Browser)/(\\d+)\\.(\\d+)"), "Tizen Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(SE 2\\.X) MetaSr (\\d+)\\.(\\d+)"), "Sogou Explorer", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Rackspace Monitoring)/(\\d+)\\.(\\d+)"), "RackspaceBot", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PRTG Network Monitor)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PyAMF)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(YaBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Yandex Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+).{0,100} MRCHROME"), "Mail.ru Chromium Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(AOL) (\\d+)\\.(\\d+); AOLBuild (\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PodCruncher|Downcast)[ /]?(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile(" (BoxNotes)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Whale)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) Mobile(?:[ /]|$)"), "Whale", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Whale)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Whale", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(1Password)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Ghost)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("PAN (GlobalProtect)/(\\d+)\\.(\\d+)\\.(\\d+) .{1,100} \\(X11; Linux x86_64\\)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(surveyon)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Surveyon", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Slack_SSB)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Slack Desktop Client", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(HipChat)/?(\\d+|)"), "HipChat Desktop Client", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\b(MobileIron|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave|MacOutlook|Electron|OktaMobile)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Microsoft Office Outlook 12\\.\\d+\\.\\d+|MSOffice 12"), "Outlook", "2007", null)); + configPatterns.add(new UAPattern(Pattern.compile("Microsoft Outlook 14\\.\\d+\\.\\d+|MSOffice 14"), "Outlook", "2010", null)); + configPatterns.add(new UAPattern(Pattern.compile("Microsoft Outlook 15\\.\\d+\\.\\d+"), "Outlook", "2013", null)); + configPatterns.add(new UAPattern(Pattern.compile("Microsoft Outlook (?:Mail )?16\\.\\d+\\.\\d+|MSOffice 16"), "Outlook", "2016", null)); + configPatterns.add(new UAPattern(Pattern.compile("Microsoft Office (Word) 2014"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Outlook-Express\\/7\\.0"), "Windows Live Mail", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Airmail) (\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Thunderbird)/(\\d+)\\.(\\d+)(?:\\.(\\d+(?:pre|))|)"), "Thunderbird", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Postbox)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Postbox", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Barca(?:Pro)?)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Barca", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Lotus-Notes)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Lotus Notes", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("Superhuman"), "Superhuman", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Vivaldi)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Edge?)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), "Edge", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(brave)/(\\d+)\\.(\\d+)\\.(\\d+) Chrome"), "Brave", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)[\\d.]{0,100} Iron[^/]"), "Iron", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\b(Dolphin)(?: |HDCN/|/INT\\-)(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(HeadlessChrome)(?:/(\\d+)\\.(\\d+)\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Evolution)/(\\d+)\\.(\\d+)\\.(\\d+\\.\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(RCM CardDAV plugin)/(\\d+)\\.(\\d+)\\.(\\d+(?:-dev|))"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(bingbot|Bolt|AdobeAIR|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|iTunes|MacAppStore|NetNewsWire|Space Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris|Abrowser|Planetweb|ICE Browser|mDolphin|qutebrowser|Otter|QupZilla|MailBar|kmail2|YahooMobileMail|ExchangeWebServices|ExchangeServicesClient|Dragon|Outlook-iOS-Android)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Chromium|Chrome)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(IEMobile)[ /](\\d+)\\.(\\d+)"), "IE Mobile", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(BacaBerita App)\\/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(bPod|Pocket Casts|Player FM)$"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(AlexaMediaPlayer|VLC)/(\\d+)\\.(\\d+)\\.([^.\\s]+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(AntennaPod|WMPlayer|Zune|Podkicker|Radio|ExoPlayerDemo|Overcast|PocketTunes|NSPlayer|okhttp|DoggCatcher|QuickNews|QuickTime|Peapod|Podcasts|GoldenPod|VLC|Spotify|Miro|MediaGo|Juice|iPodder|gPodder|Banshee)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Peapod|Liferea)/([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(bPod|Player FM) BMID/(\\S+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Podcast ?Addict)/v(\\d+) "), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Podcast ?Addict) "), "PodcastAddict", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Replay) AV"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(VOX) Music Player"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(CITA) RSS Aggregator/(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Pocket Casts)$"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Player FM)$"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(LG Player|Doppler|FancyMusic|MediaMonkey|Clementine) (\\d+)\\.(\\d+)\\.?([^.\\s]+|)\\.?([^.\\s]+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(philpodder)/(\\d+)\\.(\\d+)\\.?([^.\\s]+|)\\.?([^.\\s]+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Player FM|Pocket Casts|DoggCatcher|Spotify|MediaMonkey|MediaGo|BashPodder)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(QuickTime)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Kinoma)(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Fancy) Cloud Music (\\d+)\\.(\\d+)"), "FancyMusic", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("EspnDownloadManager"), "ESPN", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(ESPN) Radio (\\d+)\\.(\\d+)(?:\\.(\\d+)|) ?(?:rv:(\\d+)|) "), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(podracer|jPodder) v ?(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(ZDM)/(\\d+)\\.(\\d+)[; ]?"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Zune|BeyondPod) (\\d+)(?:\\.(\\d+)|)[\\);]"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(WMPlayer)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Lavf)"), "WMPlayer", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(RSSRadio)[ /]?(\\d+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(RSS_Radio) (\\d+)\\.(\\d+)"), "RSSRadio", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Podkicker) \\S+/(\\d+)\\.(\\d+)\\.(\\d+)"), "Podkicker", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(HTC) Streaming Player \\S+ / \\S+ / \\S+ / (\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Stitcher)/iOS"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Stitcher)/Android"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(VLC) .{0,200}version (\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile(" (VLC) for"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(vlc)/(\\d+)\\.(\\d+)\\.(\\d+)"), "VLC", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(foobar)\\S{1,10}/(\\d+)\\.(\\d+|)\\.?([\\da-z]+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Clementine)\\S{1,10} (\\d+)\\.(\\d+|)\\.?(\\d+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(amarok)/(\\d+)\\.(\\d+|)\\.?(\\d+|)"), "Amarok", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Custom)-Feed Reader"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iCab|Lunascape|Opera|Android|Jasmine|Polaris|Microsoft SkyDriveSync|The Bat!) (\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Kindle)/(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Android) Donut"), null, "1", "2")); + configPatterns.add(new UAPattern(Pattern.compile("(Android) Eclair"), null, "2", "1")); + configPatterns.add(new UAPattern(Pattern.compile("(Android) Froyo"), null, "2", "2")); + configPatterns.add(new UAPattern(Pattern.compile("(Android) Gingerbread"), null, "2", "3")); + configPatterns.add(new UAPattern(Pattern.compile("(Android) Honeycomb"), null, "3", null)); + configPatterns.add(new UAPattern(Pattern.compile("(MSIE) (\\d+)\\.(\\d+).{0,100}XBLWP7"), "IE Large Screen", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Nextcloud)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(mirall)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(ownCloud-android)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Owncloud", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OC)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) \\(Skype for Business\\)"), "Skype", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OpenVAS)(?:-VT)?(?:[ \\/](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)"), "OpenVAS Scanner", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(AnyConnect)\\/(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("compatible; monitis"), "Monitis", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Obigo)InternetBrowser"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Obigo)\\-Browser"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Obigo|OBIGO)[^\\d]*(\\d+)(?:.(\\d+)|)"), "Obigo", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(MAXTHON|Maxthon) (\\d+)\\.(\\d+)"), "Maxthon", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Maxthon|MyIE2|Uzbl|Shiira)"), null, "0", null)); + configPatterns.add(new UAPattern(Pattern.compile("(BrowseX) \\((\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(NCSA_Mosaic)/(\\d+)\\.(\\d+)"), "NCSA Mosaic", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(POLARIS)/(\\d+)\\.(\\d+)"), "Polaris", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Embider)/(\\d+)\\.(\\d+)"), "Polaris", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(BonEcho)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+|)"), "Bon Echo", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(TopBuzz) com.alex.NewsMaster/(\\d+).(\\d+).(\\d+)"), "TopBuzz", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(TopBuzz) com.mobilesrepublic.newsrepublic/(\\d+).(\\d+).(\\d+)"), "TopBuzz", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(TopBuzz) com.topbuzz.videoen/(\\d+).(\\d+).(\\d+)"), "TopBuzz", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iPod|iPhone|iPad).{1,200}GSA/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|) Mobile"), "Google", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iPod|iPhone|iPad).{1,200}Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|).{1,200}[ +]Safari"), "Mobile Safari", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iPod|iPod touch|iPhone|iPad);.{0,30}CPU.{0,30}OS[ +](\\d+)_(\\d+)(?:_(\\d+)|).{0,30} AppleNews\\/\\d+\\.\\d+(?:\\.\\d+|)"), "Mobile Safari UI/WKWebView", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iPod|iPhone|iPad).{1,200}Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Mobile Safari UI/WKWebView", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iPod|iPod touch|iPhone|iPad).{0,200} Safari"), "Mobile Safari", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(iPod|iPod touch|iPhone|iPad)"), "Mobile Safari UI/WKWebView", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Watch)(\\d+),(\\d+)"), "Apple $1 App", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Outlook-iOS)/\\d+\\.\\d+\\.prod\\.iphone \\((\\d+)\\.(\\d+)\\.(\\d+)\\)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(AvantGo) (\\d+).(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OneBrowser)/(\\d+).(\\d+)"), "ONE Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Avant)"), null, "1", null)); + configPatterns.add(new UAPattern(Pattern.compile("(QtCarBrowser)"), null, "1", null)); + configPatterns.add(new UAPattern(Pattern.compile("^(iBrowser/Mini)(\\d+).(\\d+)"), "iBrowser Mini", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(iBrowser|iRAPP)/(\\d+).(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Nokia)"), "Nokia Services (WAP) Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)\\.(\\d+)"), "Nokia Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)"), "Nokia Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(NokiaBrowser)/(\\d+)\\.(\\d+)"), "Nokia Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(BrowserNG)/(\\d+)\\.(\\d+).(\\d+)"), "Nokia Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Series60)/5\\.0"), "Nokia Browser", "7", "0")); + configPatterns.add(new UAPattern(Pattern.compile("(Series60)/(\\d+)\\.(\\d+)"), "Nokia OSS Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(S40OviBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)"), "Ovi Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Nokia)[EN]?(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PlayBook).{1,200}RIM Tablet OS (\\d+)\\.(\\d+)\\.(\\d+)"), "BlackBerry WebKit", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Black[bB]erry|BB10).{1,200}Version/(\\d+)\\.(\\d+)\\.(\\d+)"), "BlackBerry WebKit", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Black[bB]erry)\\s?(\\d+)"), "BlackBerry", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OmniWeb)/v(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Blazer)/(\\d+)\\.(\\d+)"), "Palm Blazer", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Pre)/(\\d+)\\.(\\d+)"), "Palm Pre", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(ELinks)/(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(ELinks) \\((\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Links) \\((\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(QtWeb) Internet Browser/(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(PhantomJS)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(AppleWebKit)/(\\d+)(?:\\.(\\d+)|)\\+ .{0,200} Safari"), "WebKit Nightly", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Version)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|).{0,100}Safari/"), "Safari", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Safari)/\\d+"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OLPC)/Update(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(OLPC)/Update()\\.(\\d+)"), null, "0", null)); + configPatterns.add(new UAPattern(Pattern.compile("(SEMC\\-Browser)/(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Teleca)"), "Teleca Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Phantom)/V(\\d+)\\.(\\d+)"), "Phantom Browser", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Trident)/(7|8)\\.(0)"), "IE", "11", null)); + configPatterns.add(new UAPattern(Pattern.compile("(Trident)/(6)\\.(0)"), "IE", "10", null)); + configPatterns.add(new UAPattern(Pattern.compile("(Trident)/(5)\\.(0)"), "IE", "9", null)); + configPatterns.add(new UAPattern(Pattern.compile("(Trident)/(4)\\.(0)"), "IE", "8", null)); + configPatterns.add(new UAPattern(Pattern.compile("(Espial)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(AppleWebKit)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Apple Mail", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Firefox)/(\\d+)\\.(\\d+)(pre|[ab]\\d+[a-z]*|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("([MS]?IE) (\\d+)\\.(\\d+)"), "IE", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(python-requests)/(\\d+)\\.(\\d+)"), "Python Requests", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\b(Windows-Update-Agent|WindowsPowerShell|Microsoft-CryptoAPI|SophosUpdateManager|SophosAgent|Debian APT-HTTP|Ubuntu APT-HTTP|libcurl-agent|libwww-perl|urlgrabber|curl|PycURL|Wget|wget2|aria2|Axel|OpenBSD ftp|lftp|jupdate|insomnia|fetch libfetch|akka-http|got|CloudCockpitBackend|ReactorNetty|axios|Jersey|Vert.x-WebClient|Apache-CXF|Go-CF-client|go-resty|AHC|HTTPie)(?:[ /](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(cf)\\/(\\d+)\\.(\\d+)\\.(\\S+)"), "CloudFoundry", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(sap-leonardo-iot-sdk-nodejs) \\/ (\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(SAP NetWeaver Application Server) \\(1.0;(\\d{1})(\\d{2})\\)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(\\w+-HTTPClient)\\/(\\d+)\\.(\\d+)-(\\S+)"), "HTTPClient", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(go-cli)\\s(\\d+)\\.(\\d+).(\\S+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Java-EurekaClient|Java-EurekaClient-Replication|HTTPClient|lua-resty-http)\\/v?(\\d+)\\.(\\d+)\\.?(\\d*)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(ping-service|sap xsuaa|Node-oauth|Site24x7|SAP CPI|JAEGER_SECURITY)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Python/3\\.\\d{1,3} aiohttp)/(\\d+)\\.(\\d+)\\.(\\d+)"), "Python aiohttp", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Java)[/ ]?\\d+\\.(\\d+)\\.(\\d+)[_-]*([a-zA-Z0-9]+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(minio-go)/v(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(ureq)[/ ](\\d+)\\.(\\d+).(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(http\\.rb)/(\\d+)\\.(\\d+).(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(GuzzleHttp)/(\\d+)\\.(\\d+).(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(grab)\\b"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Cyberduck)/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.\\d+|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(S3 Browser) (\\d+)[.-](\\d+)[.-](\\d+)(?:\\s*https?://s3browser\\.com|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(S3Gof3r)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\b(ibm-cos-sdk-(?:core|java|js|python))/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(rusoto)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(rclone)/v(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Roku)/DVP-(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Kurio)\\/(\\d+)\\.(\\d+)\\.(\\d+)"), "Kurio App", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(Box(?: Sync)?)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("^(ViaFree|Viafree)-(?:tvOS-)?[A-Z]{2}/(\\d+)\\.(\\d+)\\.(\\d+)"), "ViaFree", null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Transmit)/(\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("(Download Master)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("\\b(HTTrack) (\\d+)\\.(\\d+)(?:[\\.\\-](\\d+)|)"), null, null, null)); + configPatterns.add(new UAPattern(Pattern.compile("SerenityOS"), "SerenityOS Browser", null, null)); + return new CopyOnWriteArrayList<>(configPatterns); + } + + public static List getOSPatterns() { + List configPatterns = new ArrayList<>(); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/\\d+\\.\\d+\\.\\d+ \\( ;(LG)E ;NetCast 4.0"), null, "2013", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/\\d+\\.\\d+\\.\\d+ \\( ;(LG)E ;NetCast 3.0"), null, "2012", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/1.1.1 \\(;;;;;\\) Maple_2011"), "Samsung", "2011", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/\\d+\\.\\d+\\.\\d+ \\(;(Samsung);SmartTV([0-9]{4});.{0,200}FXPDEUC"), null, null, "UE40F7000", null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/\\d+\\.\\d+\\.\\d+ \\(;(Samsung);SmartTV([0-9]{4});.{0,200}MST12DEUC"), null, null, "UE32F4500", null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/1\\.1\\.1 \\(; (Philips);.{0,200}NETTV/4"), null, "2013", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/1\\.1\\.1 \\(; (Philips);.{0,200}NETTV/3"), null, "2012", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/1\\.1\\.1 \\(; (Philips);.{0,200}NETTV/2"), null, "2011", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/\\d+\\.\\d+\\.\\d+.{0,100}(firetv)-firefox-plugin (\\d+).(\\d+).(\\d+)"), "FireHbbTV", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("HbbTV/\\d+\\.\\d+\\.\\d+ \\(.{0,30}; ?([a-zA-Z]+) ?;.{0,30}(201[1-9]).{0,30}\\)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("AspiegelBot"), "Other", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows Phone) (?:OS[ /])?(\\d+)\\.(\\d+)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone)[ +]+(\\d+)[_\\.](\\d+)(?:[_\\.](\\d+)|).{0,100}Outlook-iOS-Android"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("ArcGIS\\.?(iOS|Android)-\\d+\\.\\d+(?:\\.\\d+|)(?:[^\\/]{1,50}|)\\/(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("ArcGISRuntime-(?:Android|iOS)\\/\\d+\\.\\d+(?:\\.\\d+|) \\((Android|iOS) (\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|);"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android)[ \\-/](\\d+)(?:\\.(\\d+)|)(?:[.\\-]([a-z0-9]+)|)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android) Donut"), null, "1", "2", null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android) Eclair"), null, "2", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android) Froyo"), null, "2", "2", null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android) Gingerbread"), null, "2", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android) Honeycomb"), null, "3", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android) (\\d+);"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Android): (\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|);"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("^UCWEB.{0,200}; (Adr) (\\d+)\\.(\\d+)(?:[.\\-]([a-z0-9]{1,100})|);"), "Android", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("^UCWEB.{0,200}; (iPad|iPh|iPd) OS (\\d+)_(\\d+)(?:_(\\d+)|);"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("^UCWEB.{0,200}; (wds) (\\d+)\\.(\\d+)(?:\\.(\\d+)|);"), "Windows Phone", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("^(JUC).{0,200}; ?U; ?(?:Android|)(\\d+)\\.(\\d+)(?:[\\.\\-]([a-z0-9]{1,100})|)"), "Android", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(android)\\s(?:mobile\\/)(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)"), "Android", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Silk-Accelerated=[a-z]{4,5})"), "Android", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(x86_64|aarch64)\\ (\\d+)\\.(\\d+)\\.(\\d+).{0,100}Chrome.{0,100}(?:CitrixChromeApp)$"), "Chrome OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(XBLWP7)"), "Windows Phone", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows ?Mobile)"), "Windows Mobile", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows 10)"), "Windows", "10", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows (?:NT 5\\.2|NT 5\\.1))"), "Windows", "XP", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Win(?:dows NT |32NT\\/)6\\.1)"), "Windows", "7", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Win(?:dows NT |32NT\\/)6\\.0)"), "Windows", "Vista", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Win 9x 4\\.90)"), "Windows", "ME", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows NT 6\\.2; ARM;)"), "Windows", "RT", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Win(?:dows NT |32NT\\/)6\\.2)"), "Windows", "8", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows NT 6\\.3; ARM;)"), "Windows", "RT 8", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("(Win(?:dows NT |32NT\\/)6\\.3)"), "Windows", "8", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("(Win(?:dows NT |32NT\\/)6\\.4)"), "Windows", "10", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows NT 10\\.0)"), "Windows", "10", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows NT 5\\.0)"), "Windows", "2000", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(WinNT4.0)"), "Windows", "NT 4.0", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows ?CE)"), "Windows", "CE", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Win(?:dows)? ?(95|98|3.1|NT|ME|2000|XP|Vista|7|CE)"), "Windows", "$1", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Win16"), "Windows", "3.1", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Win32"), "Windows", "95", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("^Box.{0,200}Windows/([\\d.]+);"), "Windows", "$1", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Tizen)[/ ](\\d+)\\.(\\d+)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("((?:Mac[ +]?|; )OS[ +]X)[\\s+/](?:(\\d+)[_.](\\d+)(?:[_.](\\d+)|)|Mach-O)"), "Mac OS X", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Mac OS X\\s.{1,50}\\s(\\d+).(\\d+).(\\d+)"), "Mac OS X", "$1", "$2", "$3")); + configPatterns.add(new OSPattern(Pattern.compile(" (Dar)(win)/(9).(\\d+).{0,100}\\((?:i386|x86_64|Power Macintosh)\\)"), "Mac OS X", "10", "5", null)); + configPatterns.add(new OSPattern(Pattern.compile(" (Dar)(win)/(10).(\\d+).{0,100}\\((?:i386|x86_64)\\)"), "Mac OS X", "10", "6", null)); + configPatterns.add(new OSPattern(Pattern.compile(" (Dar)(win)/(11).(\\d+).{0,100}\\((?:i386|x86_64)\\)"), "Mac OS X", "10", "7", null)); + configPatterns.add(new OSPattern(Pattern.compile(" (Dar)(win)/(12).(\\d+).{0,100}\\((?:i386|x86_64)\\)"), "Mac OS X", "10", "8", null)); + configPatterns.add(new OSPattern(Pattern.compile(" (Dar)(win)/(13).(\\d+).{0,100}\\((?:i386|x86_64)\\)"), "Mac OS X", "10", "9", null)); + configPatterns.add(new OSPattern(Pattern.compile("Mac_PowerPC"), "Mac OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(?:PPC|Intel) (Mac OS X)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("^Box.{0,200};(Darwin)/(10)\\.(1\\d)(?:\\.(\\d+)|)"), "Mac OS X", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Apple\\s?TV)(?:/(\\d+)\\.(\\d+)|)"), "ATV OS X", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(\\d+)[_\\.](\\d+)(?:[_\\.](\\d+)|)"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(iPhone|iPad|iPod); Opera"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(iPhone|iPad|iPod).{0,100}Mac OS X.{0,100}Version/(\\d+)\\.(\\d+)"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/(5)48\\.0\\.3.{0,100} Darwin/11\\.0\\.0"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/(5)48\\.(0)\\.4.{0,100} Darwin/(1)1\\.0\\.0"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/(5)48\\.(1)\\.4"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/(4)85\\.1(3)\\.9"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/(6)09\\.(1)\\.4"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/(6)(0)9"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/6(7)2\\.(1)\\.13"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/6(7)2\\.(1)\\.(1)4"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CF)(Network)/6(7)(2)\\.1\\.15"), "iOS", "7", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/6(7)2\\.(0)\\.(?:2|8)"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CFNetwork)/709\\.1"), "iOS", "8", "0.b5", null)); + configPatterns.add(new OSPattern(Pattern.compile("(CF)(Network)/711\\.(\\d)"), "iOS", "8", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CF)(Network)/(720)\\.(\\d)"), "Mac OS X", "10", "10", null)); + configPatterns.add(new OSPattern(Pattern.compile("(CF)(Network)/(760)\\.(\\d)"), "Mac OS X", "10", "11", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/7.{0,100} Darwin/15\\.4\\.\\d+"), "iOS", "9", "3", "1")); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/7.{0,100} Darwin/15\\.5\\.\\d+"), "iOS", "9", "3", "2")); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/7.{0,100} Darwin/15\\.6\\.\\d+"), "iOS", "9", "3", "5")); + configPatterns.add(new OSPattern(Pattern.compile("(CF)(Network)/758\\.(\\d)"), "iOS", "9", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/808\\.3 Darwin/16\\.3\\.\\d+"), "iOS", "10", "2", "1")); + configPatterns.add(new OSPattern(Pattern.compile("(CF)(Network)/808\\.(\\d)"), "iOS", "10", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/17\\.\\d+.{0,100}\\(x86_64\\)"), "Mac OS X", "10", "13", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/16\\.\\d+.{0,100}\\(x86_64\\)"), "Mac OS X", "10", "12", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/15\\.\\d+.{0,100}\\(x86_64\\)"), "Mac OS X", "10", "11", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/(9)\\.\\d+"), "iOS", "1", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/(10)\\.\\d+"), "iOS", "4", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/(11)\\.\\d+"), "iOS", "5", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/(13)\\.\\d+"), "iOS", "6", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/6.{0,100} Darwin/(14)\\.\\d+"), "iOS", "7", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/7.{0,100} Darwin/(14)\\.\\d+"), "iOS", "8", "0", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/7.{0,100} Darwin/(15)\\.\\d+"), "iOS", "9", "0", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/16\\.5\\.\\d+"), "iOS", "10", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/16\\.6\\.\\d+"), "iOS", "10", "3", "2")); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/16\\.7\\.\\d+"), "iOS", "10", "3", "3")); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/(16)\\.\\d+"), "iOS", "10", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/17\\.0\\.\\d+"), "iOS", "11", "0", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/17\\.2\\.\\d+"), "iOS", "11", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/17\\.3\\.\\d+"), "iOS", "11", "2", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/17\\.4\\.\\d+"), "iOS", "11", "2", "6")); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/17\\.5\\.\\d+"), "iOS", "11", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/17\\.6\\.\\d+"), "iOS", "11", "4", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/17\\.7\\.\\d+"), "iOS", "11", "4", "1")); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/8.{0,100} Darwin/(17)\\.\\d+"), "iOS", "11", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/18\\.0\\.\\d+"), "iOS", "12", "0", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/18\\.2\\.\\d+"), "iOS", "12", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/18\\.5\\.\\d+"), "iOS", "12", "2", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/18\\.6\\.\\d+"), "iOS", "12", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/18\\.7\\.\\d+"), "iOS", "12", "4", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/9.{0,100} Darwin/(18)\\.\\d+"), "iOS", "12", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/11.{0,100} Darwin/19\\.2\\.\\d+"), "iOS", "13", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/11.{0,100} Darwin/19\\.3\\.\\d+"), "iOS", "13", "3", "1")); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/11.{0,100} Darwin/19\\.4\\.\\d+"), "iOS", "13", "4", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/11.{0,100} Darwin/19\\.5\\.\\d+"), "iOS", "13", "5", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/11.{0,100} Darwin/19\\.6\\.\\d+"), "iOS", "13", "6", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/1[01].{0,100} Darwin/19\\.\\d+"), "iOS", "13", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/12.{0,100} Darwin/20\\.1\\.\\d+"), "iOS", "14", "2", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/12.{0,100} Darwin/20\\.2\\.\\d+"), "iOS", "14", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/12.{0,100} Darwin/20\\.3\\.\\d+"), "iOS", "14", "4", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/12.{0,100} Darwin/20\\.4\\.\\d+"), "iOS", "14", "5", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/12.{0,100} Darwin/20\\.5\\.\\d+"), "iOS", "14", "6", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/12.{0,100} Darwin/20\\.6\\.\\d+"), "iOS", "14", "8", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/(20)\\.\\d+"), "iOS", "14", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/13.{0,100} Darwin/21\\.0\\.\\d+"), "iOS", "15", "0", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/13.{0,100} Darwin/21\\.1\\.\\d+"), "iOS", "15", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/13.{0,100} Darwin/21\\.2\\.\\d+"), "iOS", "15", "2", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/13.{0,100} Darwin/21\\.3\\.\\d+"), "iOS", "15", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/13.{0,100} Darwin/21\\.4\\.\\d+"), "iOS", "15", "4", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/13.{0,100} Darwin/21\\.5\\.\\d+"), "iOS", "15", "5", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/13.{0,100} Darwin/21\\.6\\.\\d+"), "iOS", "15", "6", null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/(21)\\.\\d+"), "iOS", "15", null, null)); + configPatterns.add(new OSPattern(Pattern.compile("CFNetwork/.{0,100} Darwin/"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\b(iOS[ /]|iOS; |iPhone(?:/| v|[ _]OS[/,]|; | OS : |\\d,\\d/|\\d,\\d; )|iPad/)(\\d{1,2})[_\\.](\\d{1,2})(?:[_\\.](\\d+)|)"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((iOS);"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(watchOS)[/ ](\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "WatchOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Outlook-(iOS)/\\d+\\.\\d+\\.prod\\.iphone"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(iPod|iPhone|iPad)"), "iOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(tvOS)[/ ](\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "tvOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CrOS) [a-z0-9_]+ (\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "Chrome OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("([Dd]ebian)"), "Debian", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Linux Mint)(?:/(\\d+)|)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Mandriva)(?: Linux|)/(?:[\\d.-]+m[a-z]{2}(\\d+).(\\d)|)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Symbian[Oo][Ss])[/ ](\\d+)\\.(\\d+)"), "Symbian OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Symbian/3).{1,200}NokiaBrowser/7\\.3"), "Symbian^3 Anna", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Symbian/3).{1,200}NokiaBrowser/7\\.4"), "Symbian^3 Belle", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Symbian/3)"), "Symbian^3", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\b(Series 60|SymbOS|S60Version|S60V\\d|S60\\b)"), "Symbian OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(MeeGo)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Symbian [Oo][Ss]"), "Symbian OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Series40;"), "Nokia Series 40", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("Series30Plus;"), "Nokia Series 30 Plus", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(BB10);.{1,200}Version/(\\d+)\\.(\\d+)\\.(\\d+)"), "BlackBerry OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Black[Bb]erry)[0-9a-z]+/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "BlackBerry OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Black[Bb]erry).{1,200}Version/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "BlackBerry OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(RIM Tablet OS) (\\d+)\\.(\\d+)\\.(\\d+)"), "BlackBerry Tablet OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Play[Bb]ook)"), "BlackBerry Tablet OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Black[Bb]erry)"), "BlackBerry OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(K[Aa][Ii]OS)\\/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "KaiOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Gecko/18.0 Firefox/\\d+\\.\\d+"), "Firefox OS", "1", "0", "1")); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Gecko/18.1 Firefox/\\d+\\.\\d+"), "Firefox OS", "1", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Gecko/26.0 Firefox/\\d+\\.\\d+"), "Firefox OS", "1", "2", null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Gecko/28.0 Firefox/\\d+\\.\\d+"), "Firefox OS", "1", "3", null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Gecko/30.0 Firefox/\\d+\\.\\d+"), "Firefox OS", "1", "4", null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Gecko/32.0 Firefox/\\d+\\.\\d+"), "Firefox OS", "2", "0", null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Gecko/34.0 Firefox/\\d+\\.\\d+"), "Firefox OS", "2", "1", null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((?:Mobile|Tablet);.{1,200}Firefox/\\d+\\.\\d+"), "Firefox OS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(BREW)[ /](\\d+)\\.(\\d+)\\.(\\d+)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(BREW);"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Brew MP|BMP)[ /](\\d+)\\.(\\d+)\\.(\\d+)"), "Brew MP", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("BMP;"), "Brew MP", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(GoogleTV)(?: (\\d+)\\.(\\d+)(?:\\.(\\d+)|)|/[\\da-z]+)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(WebTV)/(\\d+).(\\d+)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(CrKey)(?:[/](\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)"), "Chromecast", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(hpw|web)OS/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)"), "webOS", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(VRE);"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Fedora|Red Hat|PCLinuxOS|Puppy|Ubuntu|Kindle|Bada|Sailfish|Lubuntu|BackTrack|Slackware|(?:Free|Open|Net|\\b)BSD)[/ ](\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Linux)[ /](\\d+)\\.(\\d+)(?:\\.(\\d+)|).{0,100}gentoo"), "Gentoo", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((Bada);"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Windows|Android|WeTab|Maemo|Web0S)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Ubuntu|Kubuntu|Arch Linux|CentOS|Slackware|Gentoo|openSUSE|SUSE|Red Hat|Fedora|PCLinuxOS|Mageia|SerenityOS|(?:Free|Open|Net|\\b)BSD)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("(Linux)(?:[ /](\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)"), null, null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("SunOS"), "Solaris", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\(linux-gnu\\)"), "Linux", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\(x86_64-redhat-linux-gnu\\)"), "Red Hat", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("\\((freebsd)(\\d+)\\.(\\d+)\\)"), "FreeBSD", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("linux"), "Linux", null, null, null)); + configPatterns.add(new OSPattern(Pattern.compile("^(Roku)/DVP-(\\d+)\\.(\\d+)"), null, null, null, null)); + return new CopyOnWriteArrayList<>(configPatterns); + } + + public static List getDevicePatterns() { + List configPatterns = new ArrayList<>(); + configPatterns.add(new DevicePattern(Pattern.compile("^.{0,100}?(?:(?:iPhone|Windows CE|Windows Phone|Android).{0,300}(?:(?:Bot|Yeti)-Mobile|YRSpider|BingPreview|bots?/\\d|(?:bot|spider)\\.html)|AdsBot-Google-Mobile.{0,200}iPhone)", Pattern.CASE_INSENSITIVE), "Spider")); + configPatterns.add(new DevicePattern(Pattern.compile("^.{0,100}?(?:DoCoMo|\\bMOT\\b|\\bLG\\b|Nokia|Samsung|SonyEricsson).{0,200}(?:(?:Bot|Yeti)-Mobile|bots?/\\d|(?:bot|crawler)\\.html|(?:jump|google|Wukong)bot|ichiro/mobile|/spider|YahooSeeker)", Pattern.CASE_INSENSITIVE), "Spider")); + configPatterns.add(new DevicePattern(Pattern.compile(" PTST/\\d+(?:\\.\\d+|)$"), "Spider")); + configPatterns.add(new DevicePattern(Pattern.compile("X11; Datanyze; Linux"), "Spider")); + configPatterns.add(new DevicePattern(Pattern.compile("Mozilla.{1,100}Mobile.{1,100}AspiegelBot"), "Spider")); + configPatterns.add(new DevicePattern(Pattern.compile("Mozilla.{0,200}AspiegelBot"), "Spider")); + configPatterns.add(new DevicePattern(Pattern.compile("\\bSmartWatch {0,2}\\( {0,2}([^;]{1,200}) {0,2}; {0,2}([^;]{1,200}) {0,2};"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("Android Application[^\\-]{1,300} - (Sony) ?(Ericsson|) (.{1,200}) \\w{1,20} - "), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("Android Application[^\\-]{1,300} - (?:HTC|HUAWEI|LGE|LENOVO|MEDION|TCT) (HTC|HUAWEI|LG|LENOVO|MEDION|ALCATEL)[ _\\-](.{1,200}) \\w{1,20} - ", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("Android Application[^\\-]{1,300} - ([^ ]+) (.{1,200}) \\w{1,20} - "), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([BLRQ]C\\d{4}[A-Z]{1,100}?)(?: Build|\\) AppleWebKit)"), "3Q $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:3Q_)([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "3Q $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Android [34].{0,200}; {0,2}(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700(?: Lite| 3G|)|A701|B1-A71|A1-\\d{3}|B1-\\d{3}|V360|V370|W500|W500P|W501|W501P|W510|W511|W700|Slider SL101|DA22[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Acer Iconia Tab ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Z1[1235]0|E320[^/]{0,10}|S500|S510|Liquid[^;/]{0,30}|Iconia A\\d+)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Acer |ACER )([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Advent |)(Vega(?:Bean|Comb|)).{0,200}?(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Ainol |)((?:NOVO|[Nn]ovo)[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}AIRIS[ _\\-]?([^/;\\)]+) {0,2}(?:;|\\)|Build)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(OnePAD[^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Airpad[ \\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Airpad $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(one ?touch) (EVO7|T10|T20)(?: Build|\\) AppleWebKit)"), "Alcatel One Touch $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:alcatel[ _]|)(?:(?:one[ _]?touch[ _])|ot[ \\-])([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Alcatel One Touch $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TCL)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Vodafone Smart II|Optimus_Madrid)(?: Build|\\) AppleWebKit)"), "Alcatel $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}BASE_Lutea_3(?: Build|\\) AppleWebKit)"), "Alcatel One Touch 998")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}BASE_Varia(?: Build|\\) AppleWebKit)"), "Alcatel One Touch 918D")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:FINE|Fine)\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ALLVIEW[ _]?|Allview[ _]?)((?:Speed|SPEED).{0,200}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ALLVIEW[ _]?|Allview[ _]?|)(AX1_Shine|AX2_Frenzy)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ALLVIEW[ _]?|Allview[ _]?)([^;/]*?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A13-MID)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Allwinner)[ _\\-]?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A651|A701B?|A702|A703|A705|A706|A707|A711|A712|A713|A717|A722|A785|A801|A802|A803|A901|A902|A1002|A1003|A1006|A1007|A9701|A9703|Q710|Q80)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:AMOI|Amoi)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Amoi $1")); + configPatterns.add(new DevicePattern(Pattern.compile("^(?:AMOI|Amoi)[ _]([^;/]{1,100}?) Linux"), "Amoi $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MW(?:0[789]|10)[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(G7|M1013|M1015G|M11[CG]?|M-?12[B]?|M15|M19[G]?|M30[ACQ]?|M31[GQ]|M32|M33[GQ]|M36|M37|M38|M701T|M710|M712B|M713|M715G|M716G|M71(?:G|GS|T|)|M72[T]?|M73[T]?|M75[GT]?|M77G|M79T|M7L|M7LN|M81|M810|M81T|M82|M92|M92KS|M92S|M717G|M721|M722G|M723|M725G|M739|M785|M791|M92SK|M93D)(?: Build|\\) AppleWebKit)"), "Aoson $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Aoson ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Aoson $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}[Aa]panda[ _\\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Apanda $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:ARCHOS|Archos) ?(GAMEPAD.{0,200}?)(?: Build|\\) AppleWebKit)"), "Archos $1")); + configPatterns.add(new DevicePattern(Pattern.compile("ARCHOS; GOGI; ([^;]{1,200});"), "Archos $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:ARCHOS|Archos)[ _]?(.{0,200}?)(?: Build|[;/\\(\\)\\-]|$)"), "Archos $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(AN(?:7|8|9|10|13)[A-Z0-9]{1,4})(?: Build|\\) AppleWebKit)"), "Archos $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A28|A32|A43|A70(?:BHT|CHT|HB|S|X)|A101(?:B|C|IT)|A7EB|A7EB-WK|101G9|80G9)(?: Build|\\) AppleWebKit)"), "Archos $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(PAD-FMD[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(BioniQ) ?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(AN\\d[^;/]{1,100}|ARCHM\\d+)(?: Build|\\) AppleWebKit)"), "Arnova $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:ARNOVA|Arnova) ?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Arnova $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:ASSISTANT |)(AP)-?([1789]\\d{2}[A-Z]{0,2}|80104)(?: Build|\\) AppleWebKit)"), "Assistant $1-$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ME17\\d[^;/]*|ME3\\d{2}[^;/]{1,100}|K00[A-Z]|Nexus 10|Nexus 7(?: 2013|)|PadFone[^;/]*|Transformer[^;/]*|TF\\d{3}[^;/]*|eeepc)(?: Build|\\) AppleWebKit)"), "Asus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}ASUS[ _]{0,10}([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Asus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Garmin-Asus ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Garmin-Asus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Garminfone)(?: Build|\\) AppleWebKit)"), "Garmin $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (@TAB-[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-(?:07|[^0]\\d)[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Axioo[ _\\-]([^;/]{1,100}?)|(picopad)[ _\\-]([^;/]{1,100}?))(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Axioo $1$2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(V(?:100|700|800)[^;/]*)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IBAK\\-[^;/]*)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(HY5001|HY6501|X12|X21|I5)(?: Build|\\) AppleWebKit)"), "Bedove $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(JC-[^;/]*)(?: Build|\\) AppleWebKit)"), "Benss $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(BB) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(BlackBird)[ _](I8.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(BlackBird)[ _](.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([0-9]+BP[EM][^;/]*|Endeavour[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Blaupunkt $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:BLU|Blu)[ _\\-])([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:BMOBILE )?(Blu|BLU|DASH [^;/]{1,100}|VIVO 4\\.3|TANK 4\\.5)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TOUCH\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(AX5\\d+)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([Bb]q) ([^;/]{1,100}?);?(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Maxwell [^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:B-Tab|B-TAB) ?\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Broncho) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}CAPTIVA ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Captiva $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(C771|CAL21|IS11CA)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Cat|CAT) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Cat $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Cat)(Nova.{0,200}?)(?: Build|\\) AppleWebKit)"), "Cat $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(INM8002KP|ADM8000KP_[AB])(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:[Cc]elkon[ _\\*]|CELKON[ _\\*])([^;/\\)]+) ?(?:Build|;|\\))"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("Build/(?:[Cc]elkon)+_?([^;/_\\)]+)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(CT)-?(\\d+)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A19|A19Q|A105|A107[^;/\\)]*) ?(?:Build|;|\\))"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TPC[0-9]{4,5})(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Cloudfone)[ _](Excite)([^ ][^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Excite|ICE)[ _](\\d+[^;/]{0,100}?)(?: Build|\\) AppleWebKit)"), "Cloudfone $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Cloudfone|CloudPad)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:Aquila|Clanga|Rapax)[^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:CFW-|Kyros )?(MID[0-9]{4}(?:[ABC]|SR|TV)?)(\\(3G\\)-4G| GB 8K| 3G| 8K| GB)? {0,2}(?:Build|[;\\)])"), "CobyKyros $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([^;/]{0,50})Coolpad[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(CUBE[ _])?([KU][0-9]+ ?GT.{0,200}?|A5300)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}CUBOT ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(BOBBY)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Dslide [^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(XCD)[ _]?(28|35)(?: Build|\\) AppleWebKit)"), "Dell $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(001DL)(?: Build|\\) AppleWebKit)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Dell|DELL) (Streak)(?: Build|\\) AppleWebKit)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(101DL|GS01|Streak Pro[^;/]{0,100})(?: Build|\\) AppleWebKit)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([Ss]treak ?7)(?: Build|\\) AppleWebKit)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Mini-3iX)(?: Build|\\) AppleWebKit)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Dell|DELL)[ _](Aero|Venue|Thunder|Mini.{0,200}?|Streak[ _]Pro)(?: Build|\\) AppleWebKit)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Dell[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TA[CD]-\\d+[^;/]{0,100})(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(iP[789]\\d{2}(?:-3G)?|IP10\\d{2}(?:-8GB)?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(AirTab)[ _\\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(F\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(HT-03A)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(HT\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(L\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(N\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(P\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SC\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SH\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SO\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T\\-0[12][^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(DOOV)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Enot|ENOT)[ -]?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}[^;/]{1,100} Build/(?:CROSS|Cross)+[ _\\-]([^\\)]+)"), "CROSS $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(CROSS|Cross)[ _\\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Explay[_ ](.{1,200}?)(?:[\\)]| Build)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IQ.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Fly|FLY)[ _](IQ[^;]{1,100}?|F[34]\\d+[^;]{0,100}?);?(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(M532|Q572|FJL21)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(G1)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Geeksphone) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(G[^F]?FIVE) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Gionee)[ _\\-]([^;/]{1,100}?)(?:/[^;/]{1,100}|)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(GN\\d+[A-Z]?|INFINITY_PASSION|Ctrl_V1)(?: Build|\\) AppleWebKit)"), "Gionee $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(E3) Build/JOP40D"), "Gionee $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\sGIONEE[-\\s_](\\w*)", Pattern.CASE_INSENSITIVE), "Gionee $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:FONE|QUANTUM|INSIGNIA) \\d+[^;/]{0,100}|PLAYTAB)(?: Build|\\) AppleWebKit)"), "GoClever $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}GOCLEVER ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "GoClever $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Glass \\d+)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([g|G]oogle)? (Pixel[ a-zA-z0-9]{1,100});(?: Build|.{0,50}\\) AppleWebKit)"), "$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([g|G]oogle)? (Pixel.{0,200}?)(?: Build|\\) AppleWebKit)"), "$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(GSmart)[ -]([^/]{1,50})(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(imx5[13]_[^/]{1,50})(?: Build|\\) AppleWebKit)"), "Freescale $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Haier[ _\\-]([^/]{1,50})(?: Build|\\) AppleWebKit)"), "Haier $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(PAD1016)(?: Build|\\) AppleWebKit)"), "Haipad $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(M701|M7|M8|M9)(?: Build|\\) AppleWebKit)"), "Haipad $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SN\\d+T[^;\\)/]*)(?: Build|[;\\)])"), "Hannspree $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Build/HCL ME Tablet ([^;\\)]{1,3})[\\);]"), "HCLme $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([^;\\/]+) Build/HCL"), "HCLme $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MID-?\\d{4}C[EM])(?: Build|\\) AppleWebKit)"), "Hena $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(EG\\d{2,}|HS-[^;/]{1,100}|MIRA[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Hisense $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(andromax[^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Hisense $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:AMAZE[ _](S\\d+)|(S\\d+)[ _]AMAZE)(?: Build|\\) AppleWebKit)"), "AMAZE $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(PlayBook)(?: Build|\\) AppleWebKit)"), "HP $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}HP ([^/]{1,50})(?: Build|\\) AppleWebKit)"), "HP $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([^/]{1,30}_tenderloin)(?: Build|\\) AppleWebKit)"), "HP TouchPad")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(HUAWEI |Huawei-|)([UY][^;/]{1,100}) Build/(?:Huawei|HUAWEI)([UY][^\\);]+)\\)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([^;/]{1,100}) Build[/ ]Huawei(MT1-U06|[A-Z]{1,50}\\d+[^\\);]{1,50})\\)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(S7|M860) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:HUAWEI|Huawei)[ \\-]?)(MediaPad) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:HUAWEI[ _]?|Huawei[ _]|)Ascend[ _])([^;/]{1,100}) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:HUAWEI|Huawei)[ _\\-]?)((?:G700-|MT-)[^;/]{1,100}) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:HUAWEI|Huawei)[ _\\-]?)([^;/]{1,100}) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MediaPad[^;]{1,200}|SpringBoard) Build/Huawei"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([^;]{1,200}) Build/(?:Huawei|HUAWEI)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([Uu])([89]\\d{3}) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Ideos |IDEOS )(S7) Build"), "Huawei Ideos$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Ideos |IDEOS )([^;/]{1,50}\\s{0,5}|\\s{0,5})Build"), "Huawei Ideos$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus 6P|ATH-.{1,200}?) Build[/ ]"), "Huawei $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:[A-Z]{3})\\-L[A-Za0-9]{2})[\\)]"), "Huawei $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([^;]{1,200}) Build/(HONOR|Honor)"), "Huawei Honor $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}HTC[ _]([^;]{1,200}); Windows Phone"), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: {0,2}Build|\\))"), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: {0,2}Build|\\))"), "HTC $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: {0,2}Build|\\))"), "HTC $1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: {0,2}Build|\\))"), "HTC $1 $2 $3 $4")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/;]+)(?: {0,2}Build|[;\\)]| - )"), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/;\\)]+)|)(?: {0,2}Build|[;\\)]| - )"), "HTC $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/;\\)]+)|)|)(?: {0,2}Build|[;\\)]| - )"), "HTC $1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ /;]+)|)|)|)(?: {0,2}Build|[;\\)]| - )"), "HTC $1 $2 $3 $4")); + configPatterns.add(new DevicePattern(Pattern.compile("HTC Streaming Player [^\\/]{0,30}/[^\\/]{0,10}/ htc_([^/]{1,10}) /"), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:[;,] {0,2}|^)(?:htccn_chs-|)HTC[ _-]?([^;]{1,200}?)(?: {0,2}Build|clay|Android|-?Mozilla| Opera| Profile| UNTRUSTED|[;/\\(\\)]|$)", Pattern.CASE_INSENSITIVE), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A6277|ADR6200|ADR6300|ADR6350|ADR6400[A-Z]*|ADR6425[A-Z]*|APX515CKT|ARIA|Desire[^_ ]*|Dream|EndeavorU|Eris|Evo|Flyer|HD2|Hero|HERO200|Hero CDMA|HTL21|Incredible|Inspire[A-Z0-9]*|Legend|Liberty|Nexus ?(?:One|HD2)|One|One S C2|One[ _]?(?:S|V|X\\+?)\\w*|PC36100|PG06100|PG86100|S31HT|Sensation|Wildfire)(?: Build|[/;\\(\\)])", Pattern.CASE_INSENSITIVE), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ADR6200|ADR6400L|ADR6425LVW|Amaze|DesireS?|EndeavorU|Eris|EVO|Evo\\d[A-Z]+|HD2|IncredibleS?|Inspire[A-Z0-9]*|Inspire[A-Z0-9]*|Sensation[A-Z0-9]*|Wildfire)[ _-](.{1,200}?)(?:[/;\\)]|Build|MIUI|1\\.0)", Pattern.CASE_INSENSITIVE), "HTC $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}HYUNDAI (T\\d[^/]{0,10})(?: Build|\\) AppleWebKit)"), "Hyundai $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}HYUNDAI ([^;/]{1,10}?)(?: Build|\\) AppleWebKit)"), "Hyundai $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(X700|Hold X|MB-6900)(?: Build|\\) AppleWebKit)"), "Hyundai $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:iBall[ _\\-]|)(Andi)[ _]?(\\d[^;/]*)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IBall)(?:[ _]([^;/]{1,100}?)|)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(NT-\\d+[^ ;/]{0,50}|Net[Tt]AB [^;/]{1,50}|Mercury [A-Z]{1,50}|iconBIT)(?: S/N:[^;/]{1,50}|)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IMO)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}i-?mobile[ _]([^/]{1,50})(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "i-mobile $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(i-(?:style|note)[^/]{0,10})(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "i-mobile $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ImPAD) ?(\\d+(?:.){0,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Infinix)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Informer)[ \\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TAB) ?([78][12]4)(?: Build|\\) AppleWebKit)"), "Intenso $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Intex[ _]|)(AQUA|Aqua)([ _\\.\\-])([^;/]{1,100}?) {0,2}(?:Build|;)"), "$1$2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:INTEX|Intex)(?:[_ ]([^\\ _;/]+))(?:[_ ]([^\\ _;/]+)|) {0,2}(?:Build|;)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([iI]Buddy)[ _]?(Connect)(?:_|\\?_| |)([^;/]{0,50}) {0,2}(?:Build|;)"), "$1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(I-Buddy)[ _]([^;/]{1,100}?) {0,2}(?:Build|;)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(iOCEAN) ([^/]{1,50})(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TP\\d+(?:\\.\\d+|)\\-\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "ionik $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(M702pro)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}itel ([^;/]*)(?: Build|\\) AppleWebKit)"), "Itel $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(DE88Plus|MD70)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}IVIO[_\\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TPC-\\d+|JAY-TECH)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(JY-[^;/]{1,100}|G[234]S?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(JXD)[ _\\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Karbonn[ _]?([^;/]{1,100}) {0,2}(?:Build|;)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([^;]{1,200}) Build/Karbonn"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A11|A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2|Titanium S\\d) +Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS01|IS03|IS05|IS\\d{2}SH)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS04)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS06|IS\\d{2}PT)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS11S)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS11CA)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS11LG)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS11N)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS11PT)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS12F)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS12M)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IS12S)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW11F)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW11HT)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW11K)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW11M)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW11SC)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW12HT)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW13HT)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ISW?[0-9]{2}[A-Z]{0,2})(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(INFOBAR [^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(JOYPAD|Joypad)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Vox|VOX|Arc|K080)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(Kobo Touch)\\b"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(K-Touch)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:EV|KM)-S\\d+[A-Z]?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Zio|Hydro|Torque|Event|EVENT|Echo|Milano|Rise|URBANO PROGRESSO|WX04K|WX06K|WX10K|KYL21|101K|C5[12]\\d{2})(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:LAVA[ _]|)IRIS[ _\\-]?([^/;\\)]+) {0,2}(?:;|\\)|Build)", Pattern.CASE_INSENSITIVE), "Iris $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}LAVA[ _]([^;/]{1,100}) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:(Aspire A1)|(?:LEMON|Lemon)[ _]([^;/]{1,100}))_?(?: Build|\\) AppleWebKit)"), "Lemon $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TAB-1012)(?: Build|\\) AppleWebKit)"), "Lenco $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; Lenco ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Lenco $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A1_07|A2107A-H|S2005A-H|S1-37AH0) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Idea[Tp]ab)[ _]([^;/]{1,100});? Build"), "Lenovo $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Idea(?:Tab|pad)) ?([^;/]{1,100}) Build"), "Lenovo $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ThinkPad) ?(Tablet) Build/"), "Lenovo $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:LNV-|)(?:=?[Ll]enovo[ _\\-]?|LENOVO[ _])(.{1,200}?)(?:Build|[;/\\)])"), "Lenovo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("[;,] (?:Vodafone |)(SmartTab) ?(II) ?(\\d+) Build/"), "Lenovo $1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Ideapad |)K1 Build/"), "Lenovo Ideapad K1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(3GC101|3GW10[01]|A390) Build/"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(?:Lenovo|LENOVO)+[ _\\-]?([^,;:/ ]+)"), "Lenovo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MFC\\d+)[A-Z]{2}([^;,/]*),?(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]{1,30}|Nexus 4|Nexus 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) {0,2}(?:Build|;)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("[;:] {0,2}(L-\\d+[A-Z]|LGL\\d+[A-Z]?)(?:/V\\d+|) {0,2}(?:Build|[;\\)])"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(LG-)([A-Z]{1,2}\\d{2,}[^,;/\\)\\(]*?)(?:Build| V\\d+|[,;/\\)\\(]|$)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(LG[ \\-]|LG)([^;/]{1,100})[;/]? Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("^(LG)-([^;/]{1,100})/ Mozilla/.{0,200}; Android"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("(Web0S); Linux/(SmartTV)"), "LG $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:SMB|smb)[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Malata|MALATA) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MS[45][0-9]{3}|MID0[568][NS]?|MID[1-9]|MID[78]0[1-9]|MID970[1-9]|MID100[1-9])(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(M1052|M806|M9000|M9100|M9701|MID100|MID120|MID125|MID130|MID135|MID140|MID701|MID710|MID713|MID727|MID728|MID731|MID732|MID733|MID735|MID736|MID737|MID760|MID800|MID810|MID820|MID830|MID833|MID835|MID860|MID900|MID930|MID933|MID960|MID980)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(GenxDroid7|MSD7.{0,200}?|AX\\d.{0,200}?|Tab 701|Tab 722)(?: Build|\\) AppleWebKit)"), "Maxx $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(M-PP[^;/]{1,30}|PhonePad ?\\d{2,}[^;/]{1,30}?)(?: Build|\\) AppleWebKit)"), "Mediacom $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(M-MP[^;/]{1,30}|SmartPad ?\\d{2,}[^;/]{1,30}?)(?: Build|\\) AppleWebKit)"), "Mediacom $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:MD_|)LIFETAB[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Medion Lifetab $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}MEDION ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Medion $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(M030|M031|M035|M040|M065|m9)(?: Build|\\) AppleWebKit)"), "Meizu $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:meizu_|MEIZU )(.{1,200}?) {0,2}(?:Build|[;\\)])"), "Meizu $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Micromax[ _](A111|A240)|(A111|A240)) Build", Pattern.CASE_INSENSITIVE), "Micromax $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Micromax[ _](A\\d{2,3}[^;/]*) Build", Pattern.CASE_INSENSITIVE), "Micromax $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A\\d{2}|A[12]\\d{2}|A90S|A110Q) Build", Pattern.CASE_INSENSITIVE), "Micromax $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Micromax[ _](P\\d{3}[^;/]*) Build", Pattern.CASE_INSENSITIVE), "Micromax $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(P\\d{3}|P\\d{3}\\(Funbook\\)) Build", Pattern.CASE_INSENSITIVE), "Micromax $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MITO)[ _\\-]?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Cynus)[ _](F5|T\\d|.{1,200}?) {0,2}(?:Build|[;/\\)])", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MODECOM |)(FreeTab) ?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1$2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MODECOM )([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MZ\\d{3}\\+?|MZ\\d{3} 4G|Xoom|XOOM[^;/]*) Build"), "Motorola $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Milestone )(XT[^;/]*) Build"), "Motorola $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Motoroi ?x|Droid X|DROIDX) Build", Pattern.CASE_INSENSITIVE), "Motorola $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Droid[^;/]*|DROID[^;/]*|Milestone[^;/]*|Photon|Triumph|Devour|Titanium) Build"), "Motorola $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A555|A85[34][^;/]*|A95[356]|ME[58]\\d{2}\\+?|ME600|ME632|ME722|MB\\d{3}\\+?|MT680|MT710|MT870|MT887|MT917|WX435|WX453|WX44[25]|XT\\d{3,4}[A-Z\\+]*|CL[iI]Q|CL[iI]Q XT) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Motorola MOT-|Motorola[ _\\-]|MOT\\-?)([^;/]{1,100}) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Moto[_ ]?|MOT\\-)([^;/]{1,100}) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:MP[DQ]C|MPG\\d{1,4}|MP\\d{3,4}|MID(?:(?:10[234]|114|43|7[247]|8[24]|7)C|8[01]1))[^;/]*)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:MSI[ _]|)(Primo\\d+|Enjoy[ _\\-][^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Multilaser[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(My)[_]?(Pad)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(My)\\|?(Phone)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A\\d+)[ _](Duo|)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(myTab[^;/]*)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(NABI2?-)([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(N-\\d+[CDE])(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(NEC-)(.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(LT-NA7)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(NXM\\d+[A-Za-z0-9_]{0,50}|Next\\d[A-Za-z0-9_ \\-]{0,50}|NEXT\\d[A-Za-z0-9_ \\-]{0,50}|Nextbook [A-Za-z0-9_ ]{0,50}|DATAM803HC|M805)(?: Build|[\\);])"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Nokia)([ _\\-]{0,5})([^;/]{0,50}) Build", Pattern.CASE_INSENSITIVE), "$1$2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TA\\-\\d{4})(?: Build|\\) AppleWebKit)"), "Nokia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Nook ?|Barnes & Noble Nook |BN )([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(NOOK |)(BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; Build/(Nook)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(OP110|OliPad[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Olivetti $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}OMEGA[ _\\-](MID[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Omega $1")); + configPatterns.add(new DevicePattern(Pattern.compile("^(MID7500|MID\\d+) Mozilla/5\\.0 \\(iPad;"), "Omega $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:CIUS|cius)[^;/]*)(?: Build|\\) AppleWebKit)"), "Openpeak $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Find ?(?:5|7a)|R8[012]\\d{1,2}|T703\\d?|U70\\d{1,2}T?|X90\\d{1,2}|[AFR]\\d{1,2}[a-z]{1,2})(?: Build|\\) AppleWebKit)"), "Oppo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}OPPO ?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Oppo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(CPH\\d{1,4}|RMX\\d{1,4}|P[A-Z]{3}\\d{2})(?: Build|\\) AppleWebKit)"), "Oppo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A1601)(?: Build|\\) AppleWebKit)"), "Oppo F1s")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Odys\\-|ODYS\\-|ODYS )([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Odys $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SELECT) ?(7)(?: Build|\\) AppleWebKit)"), "Odys $1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(PEDI)_(PLUS)_(W)(?: Build|\\) AppleWebKit)"), "Odys $1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(AEON|BRAVIO|FUSION|FUSION2IN1|Genio|EOS10|IEOS[^;/]*|IRON|Loox|LOOX|LOOX Plus|Motion|NOON|NOON_PRO|NEXT|OPOS|PEDI[^;/]*|PRIME[^;/]*|STUDYTAB|TABLO|Tablet-PC-4|UNO_X8|XELIO[^;/]*|Xelio ?\\d+ ?[Pp]ro|XENO10|XPRESS PRO)(?: Build|\\) AppleWebKit)"), "Odys $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (ONE [a-zA-Z]\\d+)(?: Build|\\) AppleWebKit)"), "OnePlus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (ONEPLUS [a-zA-Z]\\d+)(?: Build|\\) AppleWebKit)"), "OnePlus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(HD1903|GM1917|IN2025|LE2115|LE2127|HD1907|BE2012|BE2025|BE2026|BE2028|BE2029|DE2117|DE2118|EB2101|GM1900|GM1910|GM1915|HD1905|HD1925|IN2015|IN2017|IN2019|KB2005|KB2007|LE2117|LE2125|BE2015|GM1903|HD1900|HD1901|HD1910|HD1913|IN2010|IN2013|IN2020|LE2111|LE2120|LE2121|LE2123|BE2011|IN2023|KB2003|LE2113|NE2215|DN2101)(?: Build|\\) AppleWebKit)"), "OnePlus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (OnePlus[ a-zA-z0-9]{0,50});((?: Build|.{0,50}\\) AppleWebKit))"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (OnePlus[ a-zA-z0-9]{0,50})((?: Build|\\) AppleWebKit))"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TP-\\d+)(?: Build|\\) AppleWebKit)"), "Orion $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(G100W?)(?: Build|\\) AppleWebKit)"), "PackardBell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Panasonic)[_ ]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(FZ-A1B|JT-B1)(?: Build|\\) AppleWebKit)"), "Panasonic $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(dL1|DL1)(?: Build|\\) AppleWebKit)"), "Panasonic $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SKY[ _]|)(IM\\-[AT]\\d{3}[^;/]{1,100}).{0,30} Build/"), "Pantech $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:ADR8995|ADR910L|ADR930L|ADR930VW|PTL21|P8000)(?: 4G|)) Build/"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Pantech([^;/]{1,30}).{0,200}? Build/"), "Pantech $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(papyre)[ _\\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Touchlet )?(X10\\.[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Pearl $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; PHICOMM (i800)(?: Build|\\) AppleWebKit)"), "Phicomm $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; PHICOMM ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Phicomm $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(FWS\\d{3}[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Phicomm $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(D633|D822|D833|T539|T939|V726|W335|W336|W337|W3568|W536|W5510|W626|W632|W6350|W6360|W6500|W732|W736|W737|W7376|W820|W832|W8355|W8500|W8510|W930)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Philips|PHILIPS)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Philips $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Android 4\\..{0,200}; {0,2}(M[12356789]|U[12368]|S[123])\\ ?(pro)?(?: Build|\\) AppleWebKit)"), "Pipo $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MOMO[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Polaroid[ _]|)((?:MIDC\\d{3,}|PMID\\d{2,}|PTAB\\d{3,})[^;/]{0,30}?)(\\/[^;/]{0,30}|)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Polaroid )(Tablet)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(POMP)[ _\\-](.{1,200}?) {0,2}(?:Build|[;/\\)])"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TB07STA|TB10STA|TB07FTA|TB10FTA)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Positivo |)((?:YPY|Ypy)[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MOB-[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}POV[ _\\-]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "POV $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:TAB-PLAYTAB|TAB-PROTAB|PROTAB|PlayTabPro|Mobii[ _\\-]|TAB-P)[^;/]*)(?: Build|\\) AppleWebKit)"), "POV $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Prestigio |)((?:PAP|PMP)\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Prestigio $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(PLT[0-9]{4}.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A2|A5|A8|A900)_?(Classic|)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Q[Mm]obile)_([^_]+)_([^_]+?)(?: Build|\\) AppleWebKit)"), "Qmobile $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Q\\-?[Mm]obile)[_ ](A[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Qmobile $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Q\\-Smart)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Q\\-?[Mm]obile)[ _\\-](S[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TA1013)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (RCT\\w+)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; RCA (\\w+)(?: Build|\\) AppleWebKit)"), "RCA $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(RK\\d+),?(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile(" Build/(RK\\d+)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SAMSUNG |Samsung |)((?:Galaxy (?:Note II|S\\d)|GT-I9082|GT-I9205|GT-N7\\d{3}|SM-N9005)[^;/]{0,100})\\/?[^;/]{0,50} Build/"), "Samsung $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Google |)(Nexus [Ss](?: 4G|)) Build/"), "Samsung $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SAMSUNG |Samsung )([^\\/]{0,50})\\/[^ ]{0,50} Build/"), "Samsung $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Galaxy(?: Ace| Nexus| S ?II+|Nexus S| with MCR 1.2| Mini Plus 4G|)) Build/"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SAMSUNG[ _\\-]|)(?:SAMSUNG[ _\\-])([^;/]{1,100}) Build"), "Samsung $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SAMSUNG-|)(GT\\-[BINPS]\\d{4}[^\\/]{0,50})(\\/[^ ]{0,50}) Build"), "Samsung $1$2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:; {0,2}|^)((?:GT\\-[BIiNPS]\\d{4}|I9\\d{2}0[A-Za-z\\+]?\\b)[^;/\\)]*?)(?:Build|Linux|MIUI|[;/\\)])"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (SAMSUNG-)([A-Za-z0-9\\-]{0,50}).{0,200} Build/"), "Samsung $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|) Build"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:SC)\\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|)\\)"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile(" ((?:SCH)\\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|) Build"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Behold ?(?:2|II)|YP\\-G[^;/]{1,100}|EK-GC100|SCL21|I9300) Build"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\\-[A-Za-z0-9]{5,6})[\\)]"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SH\\-?\\d\\d[^;/]{1,100}|SBM\\d[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SHARP[ -])([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SPX[_\\-]\\d[^;/]*)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SX7\\-PEARL\\.GmbH)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SP[T]?\\-\\d{2}[^;/]*)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SK\\-.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:SKYTEX|SX)-([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(IMAGINE [^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SmartQ) ?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(WF7C|WF10C|SBT[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SBM(?:003SH|005SH|006SH|007SH|102SH)) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(003P|101P|101P11C|102P) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(00\\dZ) Build/"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; HTC(X06HT) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(001HT|X06HT) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(201M) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ST\\d{4}.{0,200})Build/ST"), "Trekstor $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ST\\d{4}.{0,200}?)(?: Build|\\) AppleWebKit)"), "Trekstor $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Sony ?Ericsson ?)([^;/]{1,100}) Build"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:SK|ST|E|X|LT|MK|MT|WT)\\d{2}[a-z0-9]*(?:-o|)|R800i|U20i) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Xperia (?:A8|Arc|Acro|Active|Live with Walkman|Mini|Neo|Play|Pro|Ray|X\\d+)[^;/]{0,50}) Build", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; Sony (Tablet[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Sony $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; Sony ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Sony $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Sony)([A-Za-z0-9\\-]+)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Xperia [^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(C(?:1[0-9]|2[0-9]|53|55|6[0-9])[0-9]{2}|D[25]\\d{3}|D6[56]\\d{2})(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SGP\\d{3}|SGPT\\d{2})(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(NW-Z1000Series)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("PLAYSTATION 3"), "PlayStation 3")); + configPatterns.add(new DevicePattern(Pattern.compile("(PlayStation (?:Portable|Vita|\\d+))"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:CSL_Spice|Spice|SPICE|CSL)[ _\\-]?|)([Mm][Ii])([ _\\-]|)(\\d{3}[^;/]*)(?: Build|\\) AppleWebKit)"), "$1$2$3$4")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Sprint )(.{1,200}?) {0,2}(?:Build|[;/])"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(Sprint)[: ]([^;,/ ]+)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TAGI[ ]?)(MID) ?([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Oyster500|Opal 800)(?: Build|\\) AppleWebKit)"), "Tecmobile $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TECNO[ _])([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Android for (Telechips|Techvision) ([^ ]+) ", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-Hub2)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(PAD) ?(100[12])(?: Build|\\) AppleWebKit)"), "Terra $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T[BM]-\\d{3}[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(tolino [^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}Build/.{0,200} (TOLINO_BROWSER)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:CJ[ -])?(ThL|THL)[ -]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T100|T200|T5|W100|W200|W8s)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-Mobile[ _]G2[ _]Touch) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-Mobile[ _]G2) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-Mobile myTouch Q) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-Mobile myTouch) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-Mobile_Espresso) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(T-Mobile G1) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(T-Mobile ?|)(myTouch)[ _]?([34]G)[ _]?([^\\/]*) (?:Mozilla|Build)"), "$1$2 $3 $4")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(T-Mobile)_([^_]+)_(.{0,200}) Build"), "$1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(T-Mobile)[_ ]?(.{0,200}?)Build"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile(" (ATP[0-9]{4})(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile(" ?(TOOKY)[ _\\-]([^;/]{1,100}) ?(?:Build|;)", Pattern.CASE_INSENSITIVE), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(TOSHIBA_AC_AND_AZ|TOSHIBA_FOLIO_AND_A|FOLIO_AND_A)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([Ff]olio ?100)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(AT[0-9]{2,3}(?:\\-A|LE\\-A|PE\\-A|SE|a|)|AT7-A|AT1S0|Hikari-iFrame/WDPF-[^;/]{1,100}|THRiVE|Thrive)(?: Build|\\) AppleWebKit)"), "Toshiba $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TM-MID\\d+[^;/]{1,50}|TOUCHMATE|MID-750)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TM-SM\\d+[^;/]{1,50}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A10 [Bb]asic2?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(TREQ[ _\\-])([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(X-?5|X-?3)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(A502\\+?|A936|A603|X1|X2)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; thor Build/"), "Thor")); + configPatterns.add(new DevicePattern(Pattern.compile("; Thor (E)? Build/"), "Thor $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; Apollo Lite Build/"), "Apollo Lite")); + configPatterns.add(new DevicePattern(Pattern.compile("(TOUCH(?:TAB|PAD).{1,200}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Versus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(VERTU) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Videocon)[ _\\-]([^;/]{1,100}?) {0,2}(?:Build|;)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile(" (VT\\d{2}[A-Za-z]*)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((?:ViewPad|ViewPhone|VSD)[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ViewSonic-)([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(GTablet.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([Vv]ivo)[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("(Vodafone) (.{0,200}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Walton[ _\\-]|)(Primo[ _\\-][^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Walton $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:WIKO[ \\-]|)(CINK\\+?|BARRY|BLOOM|DARKFULL|DARKMOON|DARKNIGHT|DARKSIDE|FIZZ|HIGHWAY|IGGY|OZZY|RAINBOW|STAIRWAY|SUBLIM|WAX|CINK [^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Wiko $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}WellcoM-([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Wellcom $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:(WeTab)-Browser|; (wetab) Build)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(AT-AS[^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Wolfgang $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Woxter|Wxt) ([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "Woxter $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Xenta |Luna |)(TAB[234][0-9]{2}|TAB0[78]-\\d{3}|TAB0?9-\\d{3}|TAB1[03]-\\d{3}|SMP\\d{2}-\\d{3})(?: Build|\\) AppleWebKit)"), "Yarvik $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([A-Z]{2,4})(M\\d{3,}[A-Z]{2})([^;\\)\\/]*)(?: Build|[;\\)])"), "Yifang $1$2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/]*) (Build|MIUI)/"), "XiaoMi $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/\\)]*)"), "XiaoMi $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(MIX) (Build|MIUI)/"), "XiaoMi $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}((MIX) ([^;/]*)) (Build|MIUI)/"), "XiaoMi $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}XOLO[ _]([^;/]{0,30}tab.{0,30})(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Xolo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}XOLO[ _]([^;/]{1,100}?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Xolo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(q\\d0{2,3}[a-z]?)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "Xolo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(PAD ?[79]\\d+[^;/]{0,50}|TelePAD\\d+[^;/])(?: Build|\\) AppleWebKit)"), "Xoro $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:(?:ZOPO|Zopo)[ _]([^;/]{1,100}?)|(ZP ?(?:\\d{2}[^;/]{1,100}|C2))|(C[2379]))(?: Build|\\) AppleWebKit)"), "$1$2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ZiiLABS) (Zii[^;/]*)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Zii)_([^;/]*)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(ARIZONA|(?:ATLAS|Atlas) W|D930|Grand (?:[SX][^;]{0,200}?|Era|Memo[^;]{0,200}?)|JOE|(?:Kis|KIS)\\b[^;]{0,200}?|Libra|Light [^;]{0,200}?|N8[056][01]|N850L|N8000|N9[15]\\d{2}|N9810|NX501|Optik|(?:Vip )Racer[^;]{0,200}?|RacerII|RACERII|San Francisco[^;]{0,200}?|V9[AC]|V55|V881|Z[679][0-9]{2}[A-z]?)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}([A-Z]\\d+)_USA_[^;]{0,200}(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(SmartTab\\d+)[^;]{0,50}(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Blade|BLADE|ZTE-BLADE)([^;/]*)(?: Build|\\) AppleWebKit)"), "ZTE Blade$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:Skate|SKATE|ZTE-SKATE)([^;/]*)(?: Build|\\) AppleWebKit)"), "ZTE Skate$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(Orange |Optimus )(Monte Carlo|San Francisco)(?: Build|\\) AppleWebKit)"), "$1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(?:ZXY-ZTE_|ZTE\\-U |ZTE[\\- _]|ZTE-C[_ ])([^;/]{1,100}?)(?: Build|\\) AppleWebKit)"), "ZTE $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (BASE) (lutea|Lutea 2|Tab[^;]{0,200}?)(?: Build|\\) AppleWebKit)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; (Avea inTouch 2|soft stone|tmn smart a7|Movistar[ _]Link)(?: Build|\\) AppleWebKit)", Pattern.CASE_INSENSITIVE), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(vp9plus)\\)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(Cloud[ _]Z5|z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900)(?: Build|\\) AppleWebKit)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFOT|Kindle Fire) Build\\b"), "Kindle Fire")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFOTE|Amazon Kindle Fire2) Build\\b"), "Kindle Fire 2")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFTT) Build\\b"), "Kindle Fire HD")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFJWI) Build\\b"), "Kindle Fire HD 8.9\" WiFi")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFJWA) Build\\b"), "Kindle Fire HD 8.9\" 4G")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFSOWI) Build\\b"), "Kindle Fire HD 7\" WiFi")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFTHWI) Build\\b"), "Kindle Fire HDX 7\" WiFi")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFTHWA) Build\\b"), "Kindle Fire HDX 7\" 4G")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFAPWI) Build\\b"), "Kindle Fire HDX 8.9\" WiFi")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(KFAPWA) Build\\b"), "Kindle Fire HDX 8.9\" 4G")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?Amazon ([^;/]{1,100}) Build\\b"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(Kindle) Build\\b"), "Kindle")); + configPatterns.add(new DevicePattern(Pattern.compile("; ?(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+)|) Build\\b"), "Kindle Fire")); + configPatterns.add(new DevicePattern(Pattern.compile(" (Kindle)/(\\d+\\.\\d+)"), "Kindle")); + configPatterns.add(new DevicePattern(Pattern.compile(" (Silk|Kindle)/(\\d+)\\."), "Kindle")); + configPatterns.add(new DevicePattern(Pattern.compile("(sprd)\\-([^/]{1,50})/"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(H\\d{2}00\\+?) Build"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(iphone|iPhone5) Build/"), "Xianghe $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(e\\d{4}[a-z]?_?v\\d+|v89_[^;/]{1,100})[^;/]{1,30} Build/"), "Xianghe $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\bUSCC[_\\-]?([^ ;/\\)]+)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:ALCATEL)[^;]{0,200}; {0,2}([^;,\\)]+)"), "Alcatel $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:ASUS|Asus)[^;]{0,200}; {0,2}([^;,\\)]+)"), "Asus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:DELL|Dell)[^;]{0,200}; {0,2}([^;,\\)]+)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:HTC|Htc|HTC_blocked[^;]{0,200})[^;]{0,200}; {0,2}(?:HTC|)([^;,\\)]+)"), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:HUAWEI)[^;]{0,200}; {0,2}(?:HUAWEI |)([^;,\\)]+)"), "Huawei $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:LG|Lg)[^;]{0,200}; {0,2}(?:LG[ \\-]|)([^;,\\)]+)"), "LG $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:rv:11; |)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)(\\d{3,10}[^;\\)]*)"), "Lumia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(RM-\\d{3,})"), "Nokia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)]|WPDesktop;) ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)([^;\\)]+)"), "Nokia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:Microsoft(?: Corporation|))[^;]{0,200}; {0,2}([^;,\\)]+)"), "Microsoft $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:SAMSUNG)[^;]{0,200}; {0,2}(?:SAMSUNG |)([^;,\\.\\)]+)"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:TOSHIBA|FujitsuToshibaMobileCommun)[^;]{0,200}; {0,2}([^;,\\)]+)"), "Toshiba $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)([^;]{1,200}); {0,2}([^;,\\)]+)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:^|; )SAMSUNG\\-([A-Za-z0-9\\-]{1,50}).{0,200} Bada/"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\(Mobile; ALCATEL ?(One|ONE) ?(Touch|TOUCH) ?([^;/]{1,100}?)(?:/[^;]{1,200}|); rv:[^\\)]{1,200}\\) Gecko/[^\\/]{1,200} Firefox/"), "Alcatel $1 $2 $3")); + configPatterns.add(new DevicePattern(Pattern.compile("\\(Mobile; (?:ZTE([^;]{1,200})|(OpenC)); rv:[^\\)]{1,200}\\) Gecko/[^\\/]{1,200} Firefox/"), "ZTE $1$2")); + configPatterns.add(new DevicePattern(Pattern.compile("\\(Mobile; ALCATEL([A-Za-z0-9\\-]+); rv:[^\\)]{1,200}\\) Gecko/[^\\/]{1,200} Firefox/[^\\/]{1,200} KaiOS/"), "Alcatel $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\(Mobile; LYF\\/([A-Za-z0-9\\-]{1,100})\\/.{0,100};.{0,100}rv:[^\\)]{1,100}\\) Gecko/[^\\/]{1,100} Firefox/[^\\/]{1,100} KAIOS/"), "LYF $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\(Mobile; Nokia_([A-Za-z0-9\\-]{1,100})_.{1,100}; rv:[^\\)]{1,100}\\) Gecko/[^\\/]{1,100} Firefox/[^\\/]{1,100} KAIOS/"), "Nokia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Nokia(N[0-9]+)([A-Za-z_\\-][A-Za-z0-9_\\-]*)"), "Nokia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:NOKIA|Nokia)(?:\\-| {0,2})(?:([A-Za-z0-9]+)\\-[0-9a-f]{32}|([A-Za-z0-9\\-]+)(?:UCBrowser)|([A-Za-z0-9\\-]+))"), "Nokia $1$2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("Lumia ([A-Za-z0-9\\-]+)"), "Lumia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\(Symbian; U; S60 V5; [A-z]{2}\\-[A-z]{2}; (SonyEricsson|Samsung|Nokia|LG)([^;/]{1,100}?)\\)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("\\(Symbian(?:/3|); U; ([^;]{1,200});"), "Nokia $1")); + configPatterns.add(new DevicePattern(Pattern.compile("BB10; ([A-Za-z0-9\\- ]+)\\)"), "BlackBerry $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Play[Bb]ook.{1,200}RIM Tablet OS"), "BlackBerry Playbook")); + configPatterns.add(new DevicePattern(Pattern.compile("Black[Bb]erry ([0-9]+);"), "BlackBerry $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Black[Bb]erry([0-9]+)"), "BlackBerry $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Black[Bb]erry;"), "BlackBerry")); + configPatterns.add(new DevicePattern(Pattern.compile("(Pre|Pixi)/\\d+\\.\\d+"), "Palm $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Palm([0-9]+)"), "Palm $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Treo([A-Za-z0-9]+)"), "Palm Treo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("webOS.{0,200}(P160U(?:NA|))/(\\d+).(\\d+)"), "HP Veer")); + configPatterns.add(new DevicePattern(Pattern.compile("(Touch[Pp]ad)/\\d+\\.\\d+"), "HP TouchPad")); + configPatterns.add(new DevicePattern(Pattern.compile("HPiPAQ([A-Za-z0-9]{1,20})/\\d+\\.\\d+"), "HP iPAQ $1")); + configPatterns.add(new DevicePattern(Pattern.compile("PDA; (PalmOS)/sony/model ([a-z]+)/Revision"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(Apple\\s?TV)"), "AppleTV")); + configPatterns.add(new DevicePattern(Pattern.compile("(QtCarBrowser)"), "Tesla Model S")); + configPatterns.add(new DevicePattern(Pattern.compile("(iPhone|iPad|iPod)(\\d+,\\d+)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(iPad)(?:;| Simulator;)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(iPod)(?:;| touch;| Simulator;)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(iPhone)(?:;| Simulator;)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(Watch)(\\d+,\\d+)"), "Apple $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(Apple Watch)(?:;| Simulator;)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(HomePod)(?:;| Simulator;)"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("iPhone"), "iPhone")); + configPatterns.add(new DevicePattern(Pattern.compile("CFNetwork/.{0,100} Darwin/\\d.{0,100}\\(((?:Mac|iMac|PowerMac|PowerBook)[^\\d]*)(\\d+)(?:,|%2C)(\\d+)"), "$1$2,$3")); + configPatterns.add(new DevicePattern(Pattern.compile("CFNetwork/.{0,100} Darwin/\\d+\\.\\d+\\.\\d+ \\(x86_64\\)"), "Mac")); + configPatterns.add(new DevicePattern(Pattern.compile("CFNetwork/.{0,100} Darwin/\\d"), "iOS-Device")); + configPatterns.add(new DevicePattern(Pattern.compile("Outlook-(iOS)/\\d+\\.\\d+\\.prod\\.iphone"), "iPhone")); + configPatterns.add(new DevicePattern(Pattern.compile("acer_([A-Za-z0-9]+)_"), "Acer $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:ALCATEL|Alcatel)-([A-Za-z0-9\\-]+)"), "Alcatel $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:Amoi|AMOI)\\-([A-Za-z0-9]+)"), "Amoi $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:; |\\/|^)((?:Transformer (?:Pad|Prime) |Transformer |PadFone[ _]?)[A-Za-z0-9]*)"), "Asus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:asus.{0,200}?ASUS|Asus|ASUS|asus)[\\- ;]*((?:Transformer (?:Pad|Prime) |Transformer |Padfone |Nexus[ _]|)[A-Za-z0-9]+)"), "Asus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:ASUS)_([A-Za-z0-9\\-]+)"), "Asus $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\bBIRD[ \\-\\.]([A-Za-z0-9]+)"), "Bird $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\bDell ([A-Za-z0-9]+)"), "Dell $1")); + configPatterns.add(new DevicePattern(Pattern.compile("DoCoMo/2\\.0 ([A-Za-z0-9]+)"), "DoCoMo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("^.{0,50}?([A-Za-z0-9]{1,30})_W;FOMA"), "DoCoMo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("^.{0,50}?([A-Za-z0-9]{1,30});FOMA"), "DoCoMo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(?:HTC/|HTC/[a-z0-9]{1,20}/|)HTC[ _\\-;]? {0,2}(.{0,200}?)(?:-?Mozilla|fingerPrint|[;/\\(\\)]|$)"), "HTC $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Huawei([A-Za-z0-9]+)"), "Huawei $1")); + configPatterns.add(new DevicePattern(Pattern.compile("HUAWEI-([A-Za-z0-9]+)"), "Huawei $1")); + configPatterns.add(new DevicePattern(Pattern.compile("HUAWEI ([A-Za-z0-9\\-]+)"), "Huawei $1")); + configPatterns.add(new DevicePattern(Pattern.compile("vodafone([A-Za-z0-9]+)"), "Huawei Vodafone $1")); + configPatterns.add(new DevicePattern(Pattern.compile("i\\-mate ([A-Za-z0-9]+)"), "i-mate $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Kyocera\\-([A-Za-z0-9]+)"), "Kyocera $1")); + configPatterns.add(new DevicePattern(Pattern.compile("KWC\\-([A-Za-z0-9]+)"), "Kyocera $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Lenovo[_\\-]([A-Za-z0-9]+)"), "Lenovo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+ \\( ?;(LG)E ?;([^;]{0,30})"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(HbbTV)/1\\.1\\.1.{0,200}CE-HTML/1\\.\\d;(Vendor/|)(THOM[^;]{0,200}?)[;\\s].{0,30}(LF[^;]{1,200});?"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(HbbTV)(?:/1\\.1\\.1|) ?(?: \\(;;;;;\\)|); {0,2}CE-HTML(?:/1\\.\\d|); {0,2}([^ ]{1,30}) ([^;]{1,200});"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(HbbTV)/1\\.1\\.1 \\(;;;;;\\) Maple_2011"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+ \\([^;]{0,30}; ?(?:CUS:([^;]{0,200})|([^;]{1,200})) ?; ?([^;]{0,30})"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("LGE; (?:Media\\/|)([^;]{0,200});[^;]{0,200};[^;]{0,200};?\\); \"?LG NetCast(\\.TV|\\.Media|)-\\d+"), "NetCast$2")); + configPatterns.add(new DevicePattern(Pattern.compile("InettvBrowser/[0-9]{1,30}\\.[0-9A-Z]{1,30} \\([^;]{0,200};(Sony)([^;]{0,200});[^;]{0,200};[^\\)]{0,10}\\)"), "Inettv")); + configPatterns.add(new DevicePattern(Pattern.compile("InettvBrowser/[0-9]{1,30}\\.[0-9A-Z]{1,30} \\([^;]{0,200};([^;]{0,200});[^;]{0,200};[^\\)]{0,10}\\)"), "Inettv")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:InettvBrowser|TSBNetTV|NETTV|HBBTV)"), "Inettv")); + configPatterns.add(new DevicePattern(Pattern.compile("Series60/\\d\\.\\d (LG)[\\-]?([A-Za-z0-9 \\-]+)"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("\\b(?:LGE[ \\-]LG\\-(?:AX|)|LGE |LGE?-LG|LGE?[ \\-]|LG[ /\\-]|lg[\\-])([A-Za-z0-9]+)\\b"), "LG $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:^LG[\\-]?|^LGE[\\-/]?)([A-Za-z]+[0-9]+[A-Za-z]*)"), "LG $1")); + configPatterns.add(new DevicePattern(Pattern.compile("^LG([0-9]+[A-Za-z]*)"), "LG $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(KIN\\.[^ ]+) (\\d+)\\.(\\d+)"), "Microsoft $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:MSIE|XBMC).{0,200}\\b(Xbox)\\b"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; ARM; Trident/6\\.0; Touch[\\);]"), "Microsoft Surface RT")); + configPatterns.add(new DevicePattern(Pattern.compile("Motorola\\-([A-Za-z0-9]+)"), "Motorola $1")); + configPatterns.add(new DevicePattern(Pattern.compile("MOTO\\-([A-Za-z0-9]+)"), "Motorola $1")); + configPatterns.add(new DevicePattern(Pattern.compile("MOT\\-([A-z0-9][A-z0-9\\-]*)"), "Motorola $1")); + configPatterns.add(new DevicePattern(Pattern.compile("; (moto[ a-zA-z0-9()]{0,50});((?: Build|.{0,50}\\) AppleWebKit))"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("; {0,2}(moto)(.{0,50})(?: Build|\\) AppleWebKit)"), "Motorola$2")); + configPatterns.add(new DevicePattern(Pattern.compile("Nintendo WiiU"), "Nintendo Wii U")); + configPatterns.add(new DevicePattern(Pattern.compile("Nintendo (DS|3DS|DSi|Wii);"), "Nintendo $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(?:Pantech|PANTECH)[ _-]?([A-Za-z0-9\\-]+)"), "Pantech $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Philips([A-Za-z0-9]+)"), "Philips $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Philips ([A-Za-z0-9]+)"), "Philips $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(SMART-TV); .{0,200} Tizen "), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("SymbianOS/9\\.\\d.{0,200} Samsung[/\\-]([A-Za-z0-9 \\-]+)"), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(Samsung)(SGH)(i[0-9]+)"), "$1 $2$3")); + configPatterns.add(new DevicePattern(Pattern.compile("SAMSUNG-ANDROID-MMS/([^;/]{1,100})"), "$1")); + configPatterns.add(new DevicePattern(Pattern.compile("SAMSUNG(?:; |[ -/])([A-Za-z0-9\\-]+)", Pattern.CASE_INSENSITIVE), "Samsung $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(Dreamcast)"), "Sega $1")); + configPatterns.add(new DevicePattern(Pattern.compile("^SIE-([A-Za-z0-9]+)"), "Siemens $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Softbank/[12]\\.0/([A-Za-z0-9]+)"), "Softbank $1")); + configPatterns.add(new DevicePattern(Pattern.compile("SonyEricsson ?([A-Za-z0-9\\-]+)"), "Ericsson $1")); + configPatterns.add(new DevicePattern(Pattern.compile("Android [^;]{1,200}; ([^ ]+) (Sony)/"), "$2 $1")); + configPatterns.add(new DevicePattern(Pattern.compile("(Sony)(?:BDP\\/|\\/|)([^ /;\\)]+)[ /;\\)]"), "$1 $2")); + configPatterns.add(new DevicePattern(Pattern.compile("Puffin/[\\d\\.]+IT"), "iPad")); + configPatterns.add(new DevicePattern(Pattern.compile("Puffin/[\\d\\.]+IP"), "iPhone")); + configPatterns.add(new DevicePattern(Pattern.compile("Puffin/[\\d\\.]+AT"), "Generic Tablet")); + configPatterns.add(new DevicePattern(Pattern.compile("Puffin/[\\d\\.]+AP"), "Generic Smartphone")); + configPatterns.add(new DevicePattern(Pattern.compile("Android[\\- ][\\d]+\\.[\\d]+; [A-Za-z]{2}\\-[A-Za-z]{0,2}; WOWMobile (.{1,200})( Build[/ ]|\\))"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("Android[\\- ][\\d]+\\.[\\d]+\\-update1; [A-Za-z]{2}\\-[A-Za-z]{0,2} {0,2}; {0,2}(.{1,200}?)( Build[/ ]|\\))"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); {0,2}[A-Za-z]{2}[_\\-][A-Za-z]{0,2}\\-? {0,2}; {0,2}(.{1,200}?)( Build[/ ]|\\))"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); {0,2}[A-Za-z]{0,2}\\- {0,2}; {0,2}(.{1,200}?)( Build[/ ]|\\))"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); {0,2}[a-z]{0,2}[_\\-]?[A-Za-z]{0,2};?( Build[/ ]|\\))"), "Generic Smartphone")); + configPatterns.add(new DevicePattern(Pattern.compile("Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); {0,3}\\-?[A-Za-z]{2}; {0,2}(.{1,50}?)( Build[/ ]|\\))"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]{1,100}?)(?: Build|\\) AppleWebKit).{1,200}? Mobile Safari"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]{1,100}?)(?: Build|\\) AppleWebKit).{1,200}? Safari"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]{1,100}?)(?: Build|\\))"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("(GoogleTV)"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("(WebTV)/\\d+.\\d+"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("^(Roku)/DVP-\\d+\\.\\d+"), null)); + configPatterns.add(new DevicePattern(Pattern.compile("(Android 3\\.\\d|Opera Tablet|Tablet; .{1,100}Firefox/|Android.{0,100}(?:Tab|Pad))", Pattern.CASE_INSENSITIVE), "Generic Tablet")); + configPatterns.add(new DevicePattern(Pattern.compile("(Symbian|\\bS60(Version|V\\d)|\\bS60\\b|\\((Series 60|Windows Mobile|Palm OS|Bada); Opera Mini|Windows CE|Opera Mobi|BREW|Brew|Mobile; .{1,200}Firefox/|iPhone OS|Android|MobileSafari|Windows {0,2}Phone|\\(webOS/|PalmOS)"), "Generic Smartphone")); + configPatterns.add(new DevicePattern(Pattern.compile("(hiptop|avantgo|plucker|xiino|blazer|elaine)", Pattern.CASE_INSENSITIVE), "Generic Smartphone")); + configPatterns.add(new DevicePattern(Pattern.compile("^.{0,100}(bot|BUbiNG|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Daum|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|BingPreview|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\\-agent|Squrl Java|EtaoSpider|PaperLiBot|SputnikBot|A6\\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming Frog|AppEngine-Google|YahooCacheSystem|fast\\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion Crawler|facebookexternalhit|Google.{0,200}/\\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes|NL-Crawler|Pingdom|StatusCake|WhatsApp|masscan|Google Web Preview|Qwantify|Yeti|OgScrper)", Pattern.CASE_INSENSITIVE), "Spider")); + configPatterns.add(new DevicePattern(Pattern.compile("^(1207|3gso|4thp|501i|502i|503i|504i|505i|506i|6310|6590|770s|802s|a wa|acer|acs\\-|airn|alav|asus|attw|au\\-m|aur |aus |abac|acoo|aiko|alco|alca|amoi|anex|anny|anyw|aptu|arch|argo|bmobile|bell|bird|bw\\-n|bw\\-u|beck|benq|bilb|blac|c55/|cdm\\-|chtm|capi|comp|cond|dall|dbte|dc\\-s|dica|ds\\-d|ds12|dait|devi|dmob|doco|dopo|dorado|el(?:38|39|48|49|50|55|58|68)|el[3456]\\d{2}dual|erk0|esl8|ex300|ez40|ez60|ez70|ezos|ezze|elai|emul|eric|ezwa|fake|fly\\-|fly_|g\\-mo|g1 u|g560|gf\\-5|grun|gene|go.w|good|grad|hcit|hd\\-m|hd\\-p|hd\\-t|hei\\-|hp i|hpip|hs\\-c|htc |htc\\-|htca|htcg)", Pattern.CASE_INSENSITIVE), "Generic Feature Phone")); + configPatterns.add(new DevicePattern(Pattern.compile("^(htcp|htcs|htct|htc_|haie|hita|huaw|hutc|i\\-20|i\\-go|i\\-ma|i\\-mobile|i230|iac|iac\\-|iac/|ig01|im1k|inno|iris|jata|kddi|kgt|kgt/|kpt |kwc\\-|klon|lexi|lg g|lg\\-a|lg\\-b|lg\\-c|lg\\-d|lg\\-f|lg\\-g|lg\\-k|lg\\-l|lg\\-m|lg\\-o|lg\\-p|lg\\-s|lg\\-t|lg\\-u|lg\\-w|lg/k|lg/l|lg/u|lg50|lg54|lge\\-|lge/|leno|m1\\-w|m3ga|m50/|maui|mc01|mc21|mcca|medi|meri|mio8|mioa|mo01|mo02|mode|modo|mot |mot\\-|mt50|mtp1|mtv |mate|maxo|merc|mits|mobi|motv|mozz|n100|n101|n102|n202|n203|n300|n302|n500|n502|n505|n700|n701|n710|nec\\-|nem\\-|newg|neon)", Pattern.CASE_INSENSITIVE), "Generic Feature Phone")); + configPatterns.add(new DevicePattern(Pattern.compile("^(netf|noki|nzph|o2 x|o2\\-x|opwv|owg1|opti|oran|ot\\-s|p800|pand|pg\\-1|pg\\-2|pg\\-3|pg\\-6|pg\\-8|pg\\-c|pg13|phil|pn\\-2|pt\\-g|palm|pana|pire|pock|pose|psio|qa\\-a|qc\\-2|qc\\-3|qc\\-5|qc\\-7|qc07|qc12|qc21|qc32|qc60|qci\\-|qwap|qtek|r380|r600|raks|rim9|rove|s55/|sage|sams|sc01|sch\\-|scp\\-|sdk/|se47|sec\\-|sec0|sec1|semc|sgh\\-|shar|sie\\-|sk\\-0|sl45|slid|smb3|smt5|sp01|sph\\-|spv |spv\\-|sy01|samm|sany|sava|scoo|send|siem|smar|smit|soft|sony|t\\-mo|t218|t250|t600|t610|t618|tcl\\-|tdg\\-|telm|tim\\-|ts70|tsm\\-|tsm3|tsm5|tx\\-9|tagt)", Pattern.CASE_INSENSITIVE), "Generic Feature Phone")); + configPatterns.add(new DevicePattern(Pattern.compile("^(talk|teli|topl|tosh|up.b|upg1|utst|v400|v750|veri|vk\\-v|vk40|vk50|vk52|vk53|vm40|vx98|virg|vertu|vite|voda|vulc|w3c |w3c\\-|wapj|wapp|wapu|wapm|wig |wapi|wapr|wapv|wapy|wapa|waps|wapt|winc|winw|wonu|x700|xda2|xdag|yas\\-|your|zte\\-|zeto|aste|audi|avan|blaz|brew|brvw|bumb|ccwa|cell|cldc|cmd\\-|dang|eml2|fetc|hipt|http|ibro|idea|ikom|ipaq|jbro|jemu|jigs|keji|kyoc|kyok|libw|m\\-cr|midp|mmef|moto|mwbp|mywa|newt|nok6|o2im|pant|pdxg|play|pluc|port|prox|rozo|sama|seri|smal|symb|treo|upsi|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|wap\\-|webc|whit|wmlb|xda\\-|xda_)", Pattern.CASE_INSENSITIVE), "Generic Feature Phone")); + configPatterns.add(new DevicePattern(Pattern.compile("^(Ice)$"), "Generic Feature Phone")); + configPatterns.add(new DevicePattern(Pattern.compile("(wap[\\-\\ ]browser|maui|netfront|obigo|teleca|up\\.browser|midp|Opera Mini)", Pattern.CASE_INSENSITIVE), "Generic Feature Phone")); + configPatterns.add(new DevicePattern(Pattern.compile("Mac OS"), "Mac")); + return new CopyOnWriteArrayList<>(configPatterns); + } +} diff --git a/src/main/java/ua_parser/UserAgentParser.java b/src/main/java/ua_parser/UserAgentParser.java index 9a3655f..9478987 100644 --- a/src/main/java/ua_parser/UserAgentParser.java +++ b/src/main/java/ua_parser/UserAgentParser.java @@ -16,10 +16,7 @@ package ua_parser; -import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,22 +29,8 @@ public class UserAgentParser { private final List patterns; - public UserAgentParser(List patterns) { - this.patterns = patterns; - } - - /** - * Constructs a thread-safe UserAgentParser - * @param configList configure a user-agent parser from a list of regexp hashmaps - * @return user-agent parser - */ - public static UserAgentParser fromList(List> configList) { - List configPatterns = new ArrayList<>(); - - for (Map configMap : configList) { - configPatterns.add(UserAgentParser.patternFromMap(configMap)); - } - return new UserAgentParser(new CopyOnWriteArrayList<>(configPatterns)); + public UserAgentParser() { + this.patterns = Regexes.getUserAgentPatterns(); } public UserAgent parse(String agentString) { @@ -64,18 +47,6 @@ public UserAgent parse(String agentString) { return UserAgent.OTHER; } - protected static UAPattern patternFromMap(Map configMap) { - String regex = configMap.get("regex"); - if (regex == null) { - throw new IllegalArgumentException("User agent is missing regex"); - } - - return(new UAPattern(Pattern.compile(regex), - configMap.get("family_replacement"), - configMap.get("v1_replacement"), - configMap.get("v2_replacement"))); - } - protected static class UAPattern { private final Pattern pattern; private final String familyReplacement, v1Replacement, v2Replacement; diff --git a/src/test/java/ua_parser/CachingParserTest.java b/src/test/java/ua_parser/CachingParserTest.java index aedaa72..fcbe03f 100644 --- a/src/test/java/ua_parser/CachingParserTest.java +++ b/src/test/java/ua_parser/CachingParserTest.java @@ -21,13 +21,6 @@ public void initParser() { parser = new CachingParser(); } - @Override - Parser parserFromStringConfig(String configYamlAsString) throws Exception { - InputStream yamlInput = new ByteArrayInputStream( - configYamlAsString.getBytes("UTF8")); - return new CachingParser(yamlInput); - } - @Test public void testCachingParserCorrectSizeInit() throws Exception { parser = new CachingParser(10); @@ -87,11 +80,4 @@ public void testCachedParseAll() { super.testParseAll(); } - @Test - public void testCachedReplacementQuoting() throws Exception { - super.testReplacementQuoting(); - super.testReplacementQuoting(); - super.testReplacementQuoting(); - } - } diff --git a/src/test/java/ua_parser/ParserTest.java b/src/test/java/ua_parser/ParserTest.java index c66f456..bd37e51 100644 --- a/src/test/java/ua_parser/ParserTest.java +++ b/src/test/java/ua_parser/ParserTest.java @@ -35,7 +35,7 @@ */ public class ParserTest { final String TEST_RESOURCE_PATH = "/ua_parser/"; - Yaml yaml = new Yaml(Parser.getDefaultLoaderOptions());; + Yaml yaml = new Yaml(RegexesBuilder.getDefaultLoaderOptions());; Parser parser; @Before @@ -90,30 +90,6 @@ public void testParseAll() { MatcherAssert.assertThat(parser.parse(agentString2), is(expected2)); } - @Test - public void testReplacementQuoting() throws Exception { - String testConfig = "user_agent_parsers:\n" - + " - regex: 'ABC([\\\\0-9]+)'\n" - + " family_replacement: 'ABC ($1)'\n" - + "os_parsers:\n" - + " - regex: 'CatOS OH-HAI=/\\^\\.\\^\\\\='\n" - + " os_replacement: 'CatOS 9000'\n" - + "device_parsers:\n" - + " - regex: 'CashPhone-([\\$0-9]+)\\.(\\d+)\\.(\\d+)'\n" - + " device_replacement: 'CashPhone $1'\n"; - - Parser testParser = parserFromStringConfig(testConfig); - Client result = testParser.parse("ABC12\\34 (CashPhone-$9.0.1 CatOS OH-HAI=/^.^\\=)"); - MatcherAssert.assertThat(result.userAgent.family, is("ABC (12\\34)")); - MatcherAssert.assertThat(result.os.family, is("CatOS 9000")); - MatcherAssert.assertThat(result.device.family, is("CashPhone $9")); - } - - @Test (expected=IllegalArgumentException.class) - public void testInvalidConfigThrows() throws Exception { - parserFromStringConfig("user_agent_parsers:\n - family_replacement: 'a'"); - } - void testUserAgentFromYaml(String filename) { InputStream yamlStream = this.getClass().getResourceAsStream(TEST_RESOURCE_PATH + filename); @@ -159,9 +135,4 @@ void testDeviceFromYaml(String filename) { MatcherAssert.assertThat(uaString, parser.parseDevice(uaString), is(Device.fromMap(testCase))); } } - - Parser parserFromStringConfig(String configYamlAsString) throws Exception { - InputStream yamlInput = new ByteArrayInputStream(configYamlAsString.getBytes("UTF8")); - return new Parser(yamlInput); - } } diff --git a/src/test/java/ua_parser/RegexesBuilder.java b/src/test/java/ua_parser/RegexesBuilder.java new file mode 100644 index 0000000..afbb989 --- /dev/null +++ b/src/test/java/ua_parser/RegexesBuilder.java @@ -0,0 +1,185 @@ +package ua_parser; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; + +public class RegexesBuilder { + + static final String REGEX_YAML_PATH = "/ua_parser/regexes.yaml"; + + public static final int CODE_POINT_LIMIT = 3455764; + + public static LoaderOptions getDefaultLoaderOptions() { + LoaderOptions options = new LoaderOptions(); + options.setCodePointLimit(CODE_POINT_LIMIT); + return options; + } + + public static String buildRegexesContent() { + LoaderOptions loaderOptions = getDefaultLoaderOptions(); + Map>> regexConfig; + try (InputStream is = Parser.class.getResourceAsStream(REGEX_YAML_PATH)) { + regexConfig = readRegexConfig(is, loaderOptions); + } catch (IOException e) { + throw new RuntimeException("failed to initialize parser from regexes.yaml bundled in jar", e); + } + + return buildRegexesContent(regexConfig); + } + + static String buildRegexesContent(Map>> regexConfig) { + List> uaParserConfigs = regexConfig.get("user_agent_parsers"); + if (uaParserConfigs == null) { + throw new IllegalArgumentException("user_agent_parsers is missing from yaml"); + } + + List> osParserConfigs = regexConfig.get("os_parsers"); + if (osParserConfigs == null) { + throw new IllegalArgumentException("os_parsers is missing from yaml"); + } + + List> deviceParserConfigs = regexConfig.get("device_parsers"); + if (deviceParserConfigs == null) { + throw new IllegalArgumentException("device_parsers is missing from yaml"); + } + + StringBuilder sb = new StringBuilder(); + sb.append(""); + sb.append("/**\n"); + sb.append(" * Copyright 2023 Twitter, Inc\n"); + sb.append(" *\n"); + sb.append(" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"); + sb.append(" * you may not use this file except in compliance with the License.\n"); + sb.append(" * You may obtain a copy of the License at\n"); + sb.append(" *\n"); + sb.append(" * http://www.apache.org/licenses/LICENSE-2.0\n"); + sb.append(" *\n"); + sb.append(" * Unless required by applicable law or agreed to in writing, software\n"); + sb.append(" * distributed under the License is distributed on an \"AS IS\" BASIS,\n"); + sb.append(" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); + sb.append(" * See the License for the specific language governing permissions and\n"); + sb.append(" * limitations under the License.\n"); + sb.append(" */\n"); + sb.append("\n"); + sb.append("package ua_parser;\n"); + sb.append("\n"); + sb.append("import java.util.List;\n"); + sb.append("import java.util.concurrent.CopyOnWriteArrayList;\n"); + sb.append("import java.util.regex.Pattern;\n"); + sb.append("\n"); + sb.append("import ua_parser.DeviceParser.DevicePattern;\n"); + sb.append("import ua_parser.OSParser.OSPattern;\n"); + sb.append("import ua_parser.UserAgentParser.UAPattern;\n"); + sb.append("\n"); + sb.append("import java.util.ArrayList;\n"); + sb.append("\n"); + sb.append("/**\n"); + sb.append(" * This class is generated at build time, based on the content of \"uap-core/regexes.yaml\"\n"); + sb.append(" */\n"); + sb.append("class Regexes {\n"); + sb.append("\n"); + sb.append(" public static List getUserAgentPatterns() {\n"); + sb.append(" List configPatterns = new ArrayList<>();\n"); + appendUAPatterns(sb, uaParserConfigs); + sb.append(" return new CopyOnWriteArrayList<>(configPatterns);\n"); + sb.append(" }\n"); + sb.append("\n"); + sb.append(" public static List getOSPatterns() {\n"); + sb.append(" List configPatterns = new ArrayList<>();\n"); + appendOSPatterns(sb, osParserConfigs); + sb.append(" return new CopyOnWriteArrayList<>(configPatterns);\n"); + sb.append(" }\n"); + sb.append("\n"); + sb.append(" public static List getDevicePatterns() {\n"); + sb.append(" List configPatterns = new ArrayList<>();\n"); + appendDevicePatterns(sb, deviceParserConfigs); + sb.append(" return new CopyOnWriteArrayList<>(configPatterns);\n"); + sb.append(" }\n"); + sb.append("}\n"); + return sb.toString(); +} + + + private static void appendUAPatterns(StringBuilder sb, List> configList) { + for (Map configMap : configList) { + String regex = configMap.get("regex"); + if (regex == null) { + throw new IllegalArgumentException("User agent is missing regex"); + } + + sb.append(" configPatterns.add(new UAPattern(Pattern.compile(" + stringToCode(regex) + "), " + + stringToCode(configMap.get("family_replacement"))+ ", " + + stringToCode(configMap.get("v1_replacement")) + ", " + + stringToCode(configMap.get("v2_replacement")) + "));\n"); + } + +} + +private static void appendOSPatterns(StringBuilder sb, List> configList) { + for (Map configMap : configList) { + String regex = configMap.get("regex"); + if (regex == null) { + throw new IllegalArgumentException("OS is missing regex"); + } + + sb.append(" configPatterns.add(new OSPattern(Pattern.compile(" + stringToCode(regex) + "), " + + stringToCode(configMap.get("os_replacement"))+ ", " + + stringToCode(configMap.get("os_v1_replacement")) + ", " + + stringToCode(configMap.get("os_v2_replacement")) + ", " + + stringToCode(configMap.get("os_v3_replacement")) + "));\n"); + } +} + +private static void appendDevicePatterns(StringBuilder sb, List> configList) { + for (Map configMap : configList) { + String regex = configMap.get("regex"); + if (regex == null) { + throw new IllegalArgumentException("Device is missing regex"); + } + + String regexFlag = configMap.get("regex_flag"); + String compileArguments; + if(regexFlag == null) { + compileArguments= ""; + } else if("i".equals(regexFlag)) { + compileArguments= ", Pattern.CASE_INSENSITIVE"; + } else { + // no other flags used (by now) + throw new IllegalArgumentException("Unexpected 'regex_flag' value"); + } + sb.append(" configPatterns.add(new DevicePattern(Pattern.compile(" + stringToCode(regex) + compileArguments + "), " + + stringToCode(configMap.get("device_replacement")) + "));\n"); + } +} + +static Map>> readRegexConfig(InputStream regexYaml, LoaderOptions loaderOptions) { + Yaml yaml = new Yaml(new SafeConstructor(loaderOptions)); + + @SuppressWarnings("unchecked") + Map>> regexConfig = (Map>>) yaml.load(regexYaml); + + return regexConfig; + } + +private static String stringToCode(String value) { + if(value == null) { + return "null"; + } + String sanitizedValue = value + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\t", "\\t") + .replace("\b", "\\b") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\f", "\\f"); +return "\"" + sanitizedValue + "\""; +} + +} diff --git a/src/test/java/ua_parser/RegexesBuilderTest.java b/src/test/java/ua_parser/RegexesBuilderTest.java new file mode 100644 index 0000000..4a8a61d --- /dev/null +++ b/src/test/java/ua_parser/RegexesBuilderTest.java @@ -0,0 +1,170 @@ +package ua_parser; + +import static org.hamcrest.Matchers.is; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.hamcrest.MatcherAssert; +import org.junit.Assert; +import org.junit.Test; + +public class RegexesBuilderTest { + + private static final String CLASS_HEADER = "" + + "/**\n" + + " * Copyright 2023 Twitter, Inc\n" + + " *\n" + + " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" + + " * you may not use this file except in compliance with the License.\n" + + " * You may obtain a copy of the License at\n" + + " *\n" + + " * http://www.apache.org/licenses/LICENSE-2.0\n" + + " *\n" + + " * Unless required by applicable law or agreed to in writing, software\n" + + " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" + + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + + " * See the License for the specific language governing permissions and\n" + + " * limitations under the License.\n" + + " */\n" + + "\n" + + "package ua_parser;\n" + + "\n" + + "import java.util.List;\n" + + "import java.util.concurrent.CopyOnWriteArrayList;\n" + + "import java.util.regex.Pattern;\n" + + "\n" + + "import ua_parser.DeviceParser.DevicePattern;\n" + + "import ua_parser.OSParser.OSPattern;\n" + + "import ua_parser.UserAgentParser.UAPattern;\n" + + "\n" + + "import java.util.ArrayList;\n" + + "\n" + + "/**\n" + + " * This class is generated at build time, based on the content of \"uap-core/regexes.yaml\"\n" + + " */\n"; + + @Test + public void testBuilder() throws Exception { + String testConfig = "user_agent_parsers:\n" + + " - regex: 'Mozilla.{1,200}Android.{1,200}(GSA)/(\\d+)\\.(\\d+)\\.(\\d+)'\n" + + " family_replacement: 'Google'\n" + + " v1_replacement: '2016'\n" + + " v2_replacement: '2017'\n" + + " - regex: '(Crosswalk)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)'\n" + + "\n" + + "os_parsers:\n" + + " - regex: '(Android) Donut'\n" + + " os_replacement: 'Android'\n" + + " os_v1_replacement: '1'\n" + + " os_v2_replacement: '2'\n" + + " os_v3_replacement: '3'\n" + + " - regex: '(Android) Honeycomb'\n" + + " os_v1_replacement: '3'\n" + + "\n" + + "device_parsers:\n" + + " - regex: '; {0,2}(one ?touch) (EVO7|T10|T20)(?: Build|\\) AppleWebKit)'\n" + + " device_replacement: 'Alcatel One Touch $2'\n" + + " brand_replacement: 'Alcatel'\n" + + " model_replacement: 'One Touch $2'\n" + + " - regex: '; {0,2}(andromax[^;/]{1,100}?)(?: Build|\\) AppleWebKit)'\n" + + " regex_flag: 'i'\n" + + " device_replacement: 'Hisense $1'\n" + + " brand_replacement: 'Hisense'\n" + + " model_replacement: '$1'"; + + String content = codeFromStringConfig(testConfig); + String expected = CLASS_HEADER + + "class Regexes {\n" + + "\n" + + " public static List getUserAgentPatterns() {\n" + + " List configPatterns = new ArrayList<>();\n" + + " configPatterns.add(new UAPattern(Pattern.compile(\"Mozilla.{1,200}Android.{1,200}(GSA)/(\\\\d+)\\\\.(\\\\d+)\\\\.(\\\\d+)\"), \"Google\", \"2016\", \"2017\"));\n" + + " configPatterns.add(new UAPattern(Pattern.compile(\"(Crosswalk)/(\\\\d+)\\\\.(\\\\d+)\\\\.(\\\\d+)\\\\.(\\\\d+)\"), null, null, null));\n" + + " return new CopyOnWriteArrayList<>(configPatterns);\n" + + " }\n" + + "\n" + + " public static List getOSPatterns() {\n" + + " List configPatterns = new ArrayList<>();\n" + + " configPatterns.add(new OSPattern(Pattern.compile(\"(Android) Donut\"), \"Android\", \"1\", \"2\", \"3\"));\n" + + " configPatterns.add(new OSPattern(Pattern.compile(\"(Android) Honeycomb\"), null, \"3\", null, null));\n" + + " return new CopyOnWriteArrayList<>(configPatterns);\n" + + " }\n" + + "\n" + + " public static List getDevicePatterns() {\n" + + " List configPatterns = new ArrayList<>();\n" + + " configPatterns.add(new DevicePattern(Pattern.compile(\"; {0,2}(one ?touch) (EVO7|T10|T20)(?: Build|\\\\) AppleWebKit)\"), \"Alcatel One Touch $2\"));\n" + + " configPatterns.add(new DevicePattern(Pattern.compile(\"; {0,2}(andromax[^;/]{1,100}?)(?: Build|\\\\) AppleWebKit)\", Pattern.CASE_INSENSITIVE), \"Hisense $1\"));\n" + + " return new CopyOnWriteArrayList<>(configPatterns);\n" + + " }\n" + + "}\n"; + MatcherAssert.assertThat(content, is(expected)); + } + + @Test + public void testReplacementQuoting() throws Exception { + String testConfig = "user_agent_parsers:\n" + + " - regex: 'ABC([\\\\0-9]+)'\n" + + " family_replacement: 'ABC ($1)'\n" + + "os_parsers:\n" + + " - regex: 'CatOS OH-HAI=/\\^\\.\\^\\\\='\n" + + " os_replacement: 'CatOS 9000'\n" + + "device_parsers:\n" + + " - regex: 'CashPhone-([\\$0-9]+)\\.(\\d+)\\.(\\d+)'\n" + + " device_replacement: 'CashPhone $1'\n"; + + String content = codeFromStringConfig(testConfig); + String expected = CLASS_HEADER + + "class Regexes {\n" + + "\n" + + " public static List getUserAgentPatterns() {\n" + + " List configPatterns = new ArrayList<>();\n" + + " configPatterns.add(new UAPattern(Pattern.compile(\"ABC([\\\\\\\\0-9]+)\"), \"ABC ($1)\", null, null));\n" + + " return new CopyOnWriteArrayList<>(configPatterns);\n" + + " }\n" + + "\n" + + " public static List getOSPatterns() {\n" + + " List configPatterns = new ArrayList<>();\n" + + " configPatterns.add(new OSPattern(Pattern.compile(\"CatOS OH-HAI=/\\\\^\\\\.\\\\^\\\\\\\\=\"), \"CatOS 9000\", null, null, null));\n" + + " return new CopyOnWriteArrayList<>(configPatterns);\n" + + " }\n" + + "\n" + + " public static List getDevicePatterns() {\n" + + " List configPatterns = new ArrayList<>();\n" + + " configPatterns.add(new DevicePattern(Pattern.compile(\"CashPhone-([\\\\$0-9]+)\\\\.(\\\\d+)\\\\.(\\\\d+)\"), \"CashPhone $1\"));\n" + + " return new CopyOnWriteArrayList<>(configPatterns);\n" + + " }\n" + + "}\n"; + MatcherAssert.assertThat(content, is(expected)); + } + + @Test (expected=IllegalArgumentException.class) + public void testInvalidConfigThrows() throws Exception { + codeFromStringConfig("user_agent_parsers:\n - family_replacement: 'a'"); + } + + @Test + public void testCode() throws Exception { + Path file = Paths.get("src/main/java/ua_parser/Regexes.java"); + String actualContent = new String(Files.readAllBytes(file), StandardCharsets.UTF_8); + String expectedContent = RegexesBuilder.buildRegexesContent(); + if(!Objects.equals(actualContent, expectedContent)) { + Files.write(file, expectedContent.getBytes(StandardCharsets.UTF_8)); + Assert.fail("The file 'Regexes.java' does not match with the input '" + RegexesBuilder.REGEX_YAML_PATH + "', run the test and commit the changes"); + } + } + + private String codeFromStringConfig(String configYamlAsString) { + InputStream yamlInput = new ByteArrayInputStream(configYamlAsString.getBytes(StandardCharsets.UTF_8)); + Map>> regexConfig = RegexesBuilder.readRegexConfig(yamlInput, RegexesBuilder.getDefaultLoaderOptions()); + String content = RegexesBuilder.buildRegexesContent(regexConfig); + return content; + } +}