├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── driocc │ │ └── devicedetector │ │ ├── Bot.java │ │ ├── BotDetail.java │ │ ├── DetectResult.java │ │ ├── DeviceDetector.java │ │ ├── OperatingSystem.java │ │ ├── ParserAbstract.java │ │ ├── VendorFragment.java │ │ ├── client │ │ ├── Browser.java │ │ ├── ClientParserAbstract.java │ │ ├── FeedReader.java │ │ ├── Library.java │ │ ├── MediaPlayer.java │ │ ├── MobileApp.java │ │ ├── PIM.java │ │ └── engine │ │ │ ├── Engine.java │ │ │ └── Version.java │ │ ├── custom │ │ ├── CompositeDetectResult.java │ │ ├── DetectConsultant.java │ │ └── VersionConsultant.java │ │ ├── device │ │ ├── Camera.java │ │ ├── CarBrowser.java │ │ ├── Console.java │ │ ├── DeviceParserAbstract.java │ │ ├── HbbTv.java │ │ ├── Mobile.java │ │ └── PortableMediaPlayer.java │ │ ├── utils │ │ └── Utils.java │ │ └── yaml │ │ └── YamlParser.java └── resources │ └── regexes │ ├── bots.yml │ ├── client │ ├── browser_engine.yml │ ├── browsers.yml │ ├── feed_readers.yml │ ├── libraries.yml │ ├── mediaplayers.yml │ ├── mobile_apps.yml │ └── pim.yml │ ├── device │ ├── cameras.yml │ ├── car_browsers.yml │ ├── consoles.yml │ ├── mobiles.yml │ ├── portable_media_player.yml │ └── televisions.yml │ ├── oss.yml │ └── vendorfragments.yml └── test └── java └── io └── driocc └── devicedetector ├── BrowserTest.java ├── DetectorTest.java ├── DeviceTest.java ├── EngineTest.java ├── MatchTest.java ├── OperatingSystemTest.java ├── VendorFragmentTest.java └── YamlParserTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | # Log file 4 | *.log 5 | # Package Files # 6 | *.jar 7 | *.war 8 | *.nar 9 | *.ear 10 | *.zip 11 | *.tar.gz 12 | *.rar 13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 14 | hs_err_pid* 15 | #ide 16 | *.project 17 | *.classpath 18 | /target/ 19 | /.settings/ 20 | .idea 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, 八幡 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-device-detector 2 | java port of https://github.com/matomo-org/device-detector 3 | 4 | # Get Start 5 | 1. download this project 6 | 2. get the latest regexes from https://github.com/matomo-org/device-detector/tree/master/regexes 7 | 3. run it 8 | ``` 9 | DeviceDetector d = new DeviceDetector(); 10 | CompositeDetectResult ret = d.parse(ua); 11 | if(ret!=null) { 12 | System.out.println(ret); 13 | } 14 | ``` 15 | 16 | # Customize 17 | 1. By default, DeviceDetector will detect bot, os, client and device 18 | 2. You can create your own DeviceDetector to customize the parsers and detect logic 19 | 20 | # Environment 21 | 1. java8 22 | 23 | # Performance 24 | work in progress 25 | 1. Cache regex objects 26 | 2. Pre-match, tests the useragent against a combination of all regexes -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | io.driocc 5 | java-device-detector 6 | 0.0.1 7 | 8 | 9 | UTF-8 10 | 1.9.0 11 | 12 | 13 | 14 | 15 | 16 | org.apache.maven.plugins 17 | maven-compiler-plugin 18 | 3.6.1 19 | 20 | 1.8 21 | 1.8 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.yaml 30 | snakeyaml 31 | 1.21 32 | 33 | 34 | com.google.guava 35 | guava 36 | 23.6.1-jre 37 | 38 | 39 | org.slf4j 40 | slf4j-api 41 | 1.7.25 42 | 43 | 44 | 45 | junit 46 | junit 47 | 4.12 48 | test 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/Bot.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import io.driocc.devicedetector.yaml.YamlParser; 10 | 11 | /** 12 | * @author kyon 13 | * 14 | */ 15 | public class Bot extends ParserAbstract { 16 | 17 | /** 18 | * 19 | */ 20 | private static final long serialVersionUID = 1L; 21 | 22 | protected static final String FIXTURE_FILE = "regexes/bots.yml"; 23 | protected static final String PARSER = "bot"; 24 | protected Boolean discardDetails = false; 25 | 26 | public Bot() { 27 | super(PARSER,FIXTURE_FILE); 28 | } 29 | public Bot(String type, String file, boolean discardDetails) { 30 | super(type, file); 31 | this.discardDetails = discardDetails; 32 | } 33 | /** 34 | * Enables information discarding 35 | */ 36 | public void setDiscardDetails(boolean discardDetails){ 37 | this.discardDetails = discardDetails; 38 | } 39 | /** 40 | * @return the discardDetails 41 | */ 42 | public boolean getDiscardDetails() { 43 | return discardDetails; 44 | } 45 | /** 46 | * Parses the current UA and checks whether it contains bot information 47 | * 48 | * @see bots.yml for list of detected bots 49 | * 50 | * Step 1: Build a big regex containing all regexes and match UA against it 51 | * -> If no matches found: return 52 | * -> Otherwise: 53 | * Step 2: Walk through the list of regexes in bots.yml and try to match every one 54 | * -> Return the matched data 55 | * 56 | * If $discardDetails is set to TRUE, the Step 2 will be skipped 57 | * $bot will be set to TRUE instead 58 | * 59 | * NOTE: Doing the big match before matching every single regex speeds up the detection 60 | */ 61 | public DetectResult parse(String userAgent) { 62 | DetectResult ret = null; 63 | if (this.preMatchOverall(userAgent)) { 64 | if (this.discardDetails) { 65 | ret = new DetectResult(); 66 | ret.setBot(true); 67 | } else { 68 | List matches = null; 69 | List> list = this.getRegexes(); 70 | for (Map ll : list) { 71 | matches = this.matchUserAgent(userAgent, ll.get("regex").toString()); 72 | if (matches!=null && matches.size()>0) { 73 | ret = new DetectResult(); 74 | ret.setBot(true); 75 | BotDetail bot = new BotDetail(); 76 | bot.setName(ll.get("name").toString()); 77 | bot.setCategory(ll.get("category")!=null?ll.get("category").toString():null); 78 | bot.setUrl(ll.get("url")!=null?ll.get("url").toString():null); 79 | if(ll.get("producer")!=null) { 80 | Map p = (Map)ll.get("producer"); 81 | bot.setProducerName(p.get("name")!=null?p.get("name"):null); 82 | bot.setProducerUrl(p.get("url")!=null?p.get("url"):null); 83 | } 84 | ret.setBotDetail(bot); 85 | break; 86 | } 87 | } 88 | } 89 | } 90 | return ret; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/BotDetail.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import com.google.common.base.MoreObjects; 7 | 8 | /** 9 | * @author kyon 10 | * 11 | */ 12 | public class BotDetail { 13 | private String name; 14 | private String category; 15 | private String url; 16 | private String producerName; 17 | private String producerUrl; 18 | /** 19 | * @return the name 20 | */ 21 | public String getName() { 22 | return name; 23 | } 24 | /** 25 | * @param name the name to set 26 | */ 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | /** 31 | * @return the category 32 | */ 33 | public String getCategory() { 34 | return category; 35 | } 36 | /** 37 | * @param category the category to set 38 | */ 39 | public void setCategory(String category) { 40 | this.category = category; 41 | } 42 | /** 43 | * @return the url 44 | */ 45 | public String getUrl() { 46 | return url; 47 | } 48 | /** 49 | * @param url the url to set 50 | */ 51 | public void setUrl(String url) { 52 | this.url = url; 53 | } 54 | /** 55 | * @return the producerName 56 | */ 57 | public String getProducerName() { 58 | return producerName; 59 | } 60 | /** 61 | * @param producerName the producerName to set 62 | */ 63 | public void setProducerName(String producerName) { 64 | this.producerName = producerName; 65 | } 66 | /** 67 | * @return the producerUrl 68 | */ 69 | public String getProducerUrl() { 70 | return producerUrl; 71 | } 72 | /** 73 | * @param producerUrl the producerUrl to set 74 | */ 75 | public void setProducerUrl(String producerUrl) { 76 | this.producerUrl = producerUrl; 77 | } 78 | 79 | public String toString() { 80 | return MoreObjects.toStringHelper(this) 81 | .add("name", name) 82 | .add("category", category) 83 | .add("url", url) 84 | .add("producerName", producerName) 85 | .add("producerUrl", producerUrl) 86 | .omitNullValues().toString(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/DetectResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import java.io.Serializable; 7 | 8 | import com.google.common.base.MoreObjects; 9 | 10 | /** 11 | * @author kyon 12 | * 13 | */ 14 | public class DetectResult implements Serializable { 15 | 16 | private static final long serialVersionUID = 1L; 17 | //for client 18 | private String type; 19 | private String name; 20 | private String shortName; 21 | private String version; 22 | private String platform; 23 | private String engine; 24 | private String engineVersion; 25 | //for device 26 | private String device; 27 | private Integer deviceType; 28 | private String brand; 29 | private String brandId; 30 | private String model; 31 | //for bot 32 | private boolean isBot; 33 | private BotDetail botDetail; 34 | /** 35 | * @return the name 36 | */ 37 | public String getName() { 38 | return name; 39 | } 40 | /** 41 | * @param name the name to set 42 | */ 43 | public void setName(String name) { 44 | this.name = name; 45 | } 46 | /** 47 | * @return the shortName 48 | */ 49 | public String getShortName() { 50 | return shortName; 51 | } 52 | /** 53 | * @param shortName the shortName to set 54 | */ 55 | public void setShortName(String shortName) { 56 | this.shortName = shortName; 57 | } 58 | /** 59 | * @return the version 60 | */ 61 | public String getVersion() { 62 | return version; 63 | } 64 | /** 65 | * @param version the version to set 66 | */ 67 | public void setVersion(String version) { 68 | this.version = version; 69 | } 70 | /** 71 | * @return the platform 72 | */ 73 | public String getPlatform() { 74 | return platform; 75 | } 76 | /** 77 | * @param platform the platform to set 78 | */ 79 | public void setPlatform(String platform) { 80 | this.platform = platform; 81 | } 82 | /** 83 | * @return the type 84 | */ 85 | public String getType() { 86 | return type; 87 | } 88 | /** 89 | * @param type the type to set 90 | */ 91 | public void setType(String type) { 92 | this.type = type; 93 | } 94 | /** 95 | * @return the engine 96 | */ 97 | public String getEngine() { 98 | return engine; 99 | } 100 | /** 101 | * @param engine the engine to set 102 | */ 103 | public void setEngine(String engine) { 104 | this.engine = engine; 105 | } 106 | /** 107 | * @return the engineVersion 108 | */ 109 | public String getEngineVersion() { 110 | return engineVersion; 111 | } 112 | /** 113 | * @param engineVersion the engineVersion to set 114 | */ 115 | public void setEngineVersion(String engineVersion) { 116 | this.engineVersion = engineVersion; 117 | } 118 | /** 119 | * @return the device 120 | */ 121 | public String getDevice() { 122 | return device; 123 | } 124 | /** 125 | * @param device the device to set 126 | */ 127 | public void setDevice(String device) { 128 | this.device = device; 129 | } 130 | /** 131 | * @return the brand 132 | */ 133 | public String getBrand() { 134 | return brand; 135 | } 136 | /** 137 | * @param brand the brand to set 138 | */ 139 | public void setBrand(String brand) { 140 | this.brand = brand; 141 | } 142 | /** 143 | * @return the brandId 144 | */ 145 | public String getBrandId() { 146 | return brandId; 147 | } 148 | /** 149 | * @param brandId the brandId to set 150 | */ 151 | public void setBrandId(String brandId) { 152 | this.brandId = brandId; 153 | } 154 | /** 155 | * @return the model 156 | */ 157 | public String getModel() { 158 | return model; 159 | } 160 | /** 161 | * @param model the model to set 162 | */ 163 | public void setModel(String model) { 164 | this.model = model; 165 | } 166 | /** 167 | * @return the isBot 168 | */ 169 | public boolean isBot() { 170 | return isBot; 171 | } 172 | /** 173 | * @param isBot the isBot to set 174 | */ 175 | public void setBot(boolean isBot) { 176 | this.isBot = isBot; 177 | } 178 | /** 179 | * @return the botDetail 180 | */ 181 | public BotDetail getBotDetail() { 182 | return botDetail; 183 | } 184 | /** 185 | * @param botDetail the botDetail to set 186 | */ 187 | public void setBotDetail(BotDetail botDetail) { 188 | this.botDetail = botDetail; 189 | } 190 | /** 191 | * @return the deviceType 192 | */ 193 | public Integer getDeviceType() { 194 | return deviceType; 195 | } 196 | /** 197 | * @param deviceType the deviceType to set 198 | */ 199 | public void setDeviceType(Integer deviceType) { 200 | this.deviceType = deviceType; 201 | } 202 | 203 | public String toString() { 204 | return MoreObjects.toStringHelper(this) 205 | .add("type", type) 206 | .add("name", name) 207 | .add("shortName", shortName) 208 | .add("version", version) 209 | .add("platform", platform) 210 | .add("engine", engine) 211 | .add("engineVersion", engineVersion) 212 | .add("device", device) 213 | .add("deviceType", deviceType) 214 | .add("brand", brand) 215 | .add("model", model) 216 | .add("isBot", isBot) 217 | .add("botDetail", botDetail) 218 | .omitNullValues().toString(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/DeviceDetector.java: -------------------------------------------------------------------------------- 1 | package io.driocc.devicedetector; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | import javax.annotation.concurrent.ThreadSafe; 11 | 12 | import io.driocc.devicedetector.client.Browser; 13 | import io.driocc.devicedetector.client.ClientParserAbstract; 14 | import io.driocc.devicedetector.client.FeedReader; 15 | import io.driocc.devicedetector.client.Library; 16 | import io.driocc.devicedetector.client.MediaPlayer; 17 | import io.driocc.devicedetector.client.MobileApp; 18 | import io.driocc.devicedetector.client.PIM; 19 | import io.driocc.devicedetector.custom.CompositeDetectResult; 20 | import io.driocc.devicedetector.custom.DetectConsultant; 21 | import io.driocc.devicedetector.device.Camera; 22 | import io.driocc.devicedetector.device.CarBrowser; 23 | import io.driocc.devicedetector.device.Console; 24 | import io.driocc.devicedetector.device.DeviceParserAbstract; 25 | import io.driocc.devicedetector.device.HbbTv; 26 | import io.driocc.devicedetector.device.Mobile; 27 | import io.driocc.devicedetector.device.PortableMediaPlayer; 28 | import io.driocc.devicedetector.utils.Utils; 29 | 30 | @ThreadSafe 31 | public class DeviceDetector implements Serializable { 32 | 33 | private static final long serialVersionUID = 1L; 34 | 35 | /** 36 | * Current version number of DeviceDetector 37 | */ 38 | //static final String VERSION = "3.10.2"; 39 | 40 | /** 41 | * Holds all registered client types 42 | * @var array 43 | */ 44 | public static List clientTypes = new ArrayList<>(); 45 | protected List clientParsers; 46 | protected List deviceParsers; 47 | protected OperatingSystem osParser; 48 | protected Bot botParser; 49 | protected VendorFragment vendorParser; 50 | private boolean skipBotDetection = false; 51 | /** 52 | * Constructor 53 | * 54 | * @param string $userAgent UA to parse 55 | */ 56 | public DeviceDetector(){ 57 | clientParsers = new ArrayList<>(); 58 | clientParsers.add(new FeedReader()); 59 | clientParsers.add(new MobileApp()); 60 | clientParsers.add(new MediaPlayer()); 61 | clientParsers.add(new PIM()); 62 | clientParsers.add(new Browser()); 63 | clientParsers.add(new Library()); 64 | deviceParsers = new ArrayList<>(); 65 | deviceParsers.add(new HbbTv()); 66 | deviceParsers.add(new Console()); 67 | deviceParsers.add(new CarBrowser()); 68 | deviceParsers.add(new Camera()); 69 | deviceParsers.add(new PortableMediaPlayer()); 70 | deviceParsers.add(new Mobile()); 71 | osParser = new OperatingSystem(); 72 | botParser = new Bot(); 73 | vendorParser = new VendorFragment(); 74 | } 75 | 76 | public List getClientParsers(){ 77 | return this.clientParsers; 78 | } 79 | 80 | public List getDeviceParsers(){ 81 | return this.deviceParsers; 82 | } 83 | public Bot getBotParser(){ 84 | return this.botParser; 85 | } 86 | 87 | /** 88 | * Sets whether to discard additional bot information 89 | * If information is discarded it's only possible check whether UA was detected as bot or not. 90 | * (Discarding information speeds up the detection a bit) 91 | * 92 | * @param bool $discard 93 | */ 94 | public void discardBotInformation(boolean discard){ 95 | botParser.setDiscardDetails(discard); 96 | } 97 | 98 | /** 99 | * Sets whether to skip bot detection. 100 | * It is needed if we want bots to be processed as a simple clients. So we can detect if it is mobile client, 101 | * or desktop, or enything else. By default all this information is not retrieved for the bots. 102 | * 103 | * @param bool $skip 104 | */ 105 | public void skipBotDetection(boolean skip){ 106 | this.skipBotDetection = skip; 107 | } 108 | 109 | /** 110 | * Returns if the parsed UA was identified as a touch enabled device 111 | * 112 | * Note: That only applies to windows 8 tablets 113 | * 114 | * @return bool 115 | */ 116 | public boolean isTouchEnabled(String userAgent){ 117 | String regex = "Touch"; 118 | List ret = this.matchUserAgent(userAgent, regex); 119 | if(ret!=null && ret.size()>0) { 120 | return true; 121 | } 122 | return false; 123 | } 124 | 125 | /** 126 | * Returns if the parsed UA contains the 'Android; Tablet;' fragment 127 | * 128 | * @return bool 129 | */ 130 | protected boolean hasAndroidTableFragment(String userAgent){ 131 | String regex = "Android( [\\.0-9]+)?; Tablet;"; 132 | List ret = this.matchUserAgent(userAgent, regex); 133 | if(ret!=null && ret.size()>0) { 134 | return true; 135 | } 136 | return false; 137 | } 138 | 139 | /** 140 | * Returns if the parsed UA contains the 'Android; Mobile;' fragment 141 | * 142 | * @return bool 143 | */ 144 | protected boolean hasAndroidMobileFragment(String userAgent){ 145 | String regex = "Android( [\\.0-9]+)?; Mobile;"; 146 | List ret = this.matchUserAgent(userAgent, regex); 147 | if(ret!=null && ret.size()>0) { 148 | return true; 149 | } 150 | return false; 151 | } 152 | 153 | private static Pattern LETTER_PATTERN = Pattern.compile("([a-z])", Pattern.CASE_INSENSITIVE); 154 | /** 155 | * Triggers the parsing of the current user agent 156 | */ 157 | public CompositeDetectResult parse(String userAgent) { 158 | CompositeDetectResult ret = null; 159 | // skip parsing for empty useragents or those not containing any letter 160 | if (Utils.isEmpty(userAgent)) { 161 | return null; 162 | } 163 | if(!LETTER_PATTERN.matcher(userAgent).find()) { 164 | return null; 165 | } 166 | DetectResult bot = this.parseBot(userAgent); 167 | if (bot!=null && bot.isBot()) { 168 | ret = new CompositeDetectResult(); 169 | return ret; 170 | } 171 | DetectResult os = this.parseOs(userAgent); 172 | /** 173 | * Parse Clients 174 | * Clients might be browsers, Feed Readers, Mobile Apps, Media Players or 175 | * any other application accessing with an parseable UA 176 | */ 177 | DetectResult client = this.parseClient(userAgent); 178 | DetectResult device = this.parseDevice(userAgent, client, os); 179 | ret = new CompositeDetectResult(); 180 | ret.setClient(client); 181 | ret.setOs(os); 182 | ret.setDevice(device); 183 | return ret; 184 | } 185 | 186 | /** 187 | * Parses the UA for bot information using the Bot parser 188 | */ 189 | protected DetectResult parseBot(String userAgent){ 190 | if (skipBotDetection) { 191 | return null; 192 | } 193 | DetectResult ret = botParser.parse(userAgent); 194 | return ret; 195 | } 196 | protected DetectResult parseClient(String userAgent){ 197 | DetectResult ret = null; 198 | for(ClientParserAbstract p : this.clientParsers) { 199 | ret = p.parse(userAgent); 200 | if(ret!=null) { 201 | break; 202 | } 203 | } 204 | return ret; 205 | } 206 | protected DetectResult parseDevice(String userAgent, DetectResult client, DetectResult os){ 207 | DetectResult ret = null; 208 | for (DeviceParserAbstract p : getDeviceParsers()) { 209 | DetectResult _temp = p.parse(userAgent); 210 | if(_temp!=null) { 211 | ret = _temp; 212 | break; 213 | } 214 | } 215 | if(ret==null) { 216 | ret = new DetectResult(); 217 | } 218 | /** 219 | * If no brand has been assigned try to match by known vendor fragments 220 | */ 221 | String brand = ret.getBrand(); 222 | String brandId = ret.getBrandId(); 223 | if (Utils.isEmpty(ret.getBrand())) { 224 | DetectResult v = vendorParser.parse(userAgent); 225 | if(v!=null) { 226 | brand = v.getBrand(); 227 | brandId = v.getBrandId(); 228 | ret.setBrandId(brandId); 229 | } 230 | } 231 | String osShortName = os!=null?os.getShortName():null; 232 | String osFamily = OperatingSystem.getOsFamily(osShortName); 233 | String osVersion = os!=null?os.getVersion():null; 234 | String clientName = client!=null?client.getName():null; 235 | /** 236 | * Assume all devices running iOS / Mac OS are from Apple 237 | */ 238 | List APPLE = Arrays.asList(new String[] {"ATV", "IOS", "MAC"}); 239 | if (Utils.isEmpty(brand) && APPLE.contains(osShortName)) { 240 | brand = "AP"; 241 | } 242 | /** 243 | * Chrome on Android passes the device type based on the keyword 'Mobile' 244 | * If it is present the device should be a smartphone, otherwise it's a tablet 245 | * See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent 246 | */ 247 | List CHROME = Arrays.asList(new String[] {"Chrome", "Chrome Mobile"}); 248 | String device = ret.getDevice(); 249 | Integer deviceType = ret.getDeviceType(); 250 | if (Utils.isEmpty(device) && "Android".equals(osFamily) && CHROME.contains(client.getName())) { 251 | List m1 = this.matchUserAgent(userAgent, "Chrome/[\\.0-9]* Mobile"); 252 | if(m1!=null && m1.size()>0){ 253 | deviceType = DeviceParserAbstract.DEVICE_TYPE_SMARTPHONE; 254 | } else { 255 | List m2 = this.matchUserAgent(userAgent, "Chrome/[\\.0-9]* (?!Mobile)"); 256 | if(m2!=null && m2.size()>0){ 257 | deviceType = DeviceParserAbstract.DEVICE_TYPE_TABLET; 258 | } 259 | } 260 | } 261 | /** 262 | * Some user agents simply contain the fragment 'Android; Tablet;' or 'Opera Tablet', so we assume those devices as tablets 263 | */ 264 | if (Utils.isEmpty(device) && (this.hasAndroidTableFragment(userAgent) || this.matchUserAgent(userAgent, "Opera Tablet")!=null)) { 265 | deviceType = DeviceParserAbstract.DEVICE_TYPE_TABLET; 266 | } 267 | /** 268 | * Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices as smartphones 269 | */ 270 | if (Utils.isEmpty(device) && this.hasAndroidMobileFragment(userAgent)) { 271 | deviceType = DeviceParserAbstract.DEVICE_TYPE_SMARTPHONE; 272 | } 273 | /** 274 | * Android up to 3.0 was designed for smartphones only. But as 3.0, which was tablet only, was published 275 | * too late, there were a bunch of tablets running with 2.x 276 | * With 4.0 the two trees were merged and it is for smartphones and tablets 277 | * 278 | * So were are expecting that all devices running Android < 2 are smartphones 279 | * Devices running Android 3.X are tablets. Device type of Android 2.X and 4.X+ are unknown 280 | */ 281 | if (Utils.isEmpty(device) && "AND".equals(osShortName) && !"".equals(osVersion)) { 282 | if (Utils.versionCompare(osVersion, "2.0") == -1) { 283 | deviceType = DeviceParserAbstract.DEVICE_TYPE_SMARTPHONE; 284 | } else if (Utils.versionCompare(osVersion, "3.0") >= 0 && Utils.versionCompare(osVersion, "4.0") == -1) { 285 | deviceType = DeviceParserAbstract.DEVICE_TYPE_TABLET; 286 | } 287 | } 288 | /** 289 | * All detected feature phones running android are more likely a smartphone 290 | */ 291 | if (deviceType == DeviceParserAbstract.DEVICE_TYPE_FEATURE_PHONE && "Android".equals(osFamily)) { 292 | deviceType = DeviceParserAbstract.DEVICE_TYPE_SMARTPHONE; 293 | } 294 | /** 295 | * According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx 296 | * Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the 297 | * UA string, the computer has touch capability, and is running Windows 8 (or later). 298 | * This UA string will be transmitted on a touch-enabled system running Windows 8 (RT) 299 | * 300 | * As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that 301 | * all Windows 8 touch devices are tablets. 302 | */ 303 | if (Utils.isEmpty(device) && ("WRT".equals(osShortName) || ("WIN".equals(osShortName) && Utils.versionCompare(osVersion, "8.0")>0) && isTouchEnabled(userAgent))) { 304 | deviceType = DeviceParserAbstract.DEVICE_TYPE_TABLET; 305 | } 306 | /** 307 | * All devices running Opera TV Store are assumed to be a tv 308 | */ 309 | if (this.matchUserAgent(userAgent, "Opera TV Store")!=null) { 310 | deviceType = DeviceParserAbstract.DEVICE_TYPE_TV; 311 | } 312 | /** 313 | * Devices running Kylo or Espital TV Browsers are assumed to be a TV 314 | */ 315 | List Kylo = Arrays.asList(new String[] {"Kylo", "Espial TV Browser"}); 316 | if (Utils.isEmpty(device) && Kylo.contains(clientName)) { 317 | deviceType = DeviceParserAbstract.DEVICE_TYPE_TV; 318 | } 319 | // set device type to desktop for all devices running a desktop os that were not detected as an other device type 320 | if (Utils.isEmpty(device) && client!=null && DetectConsultant.isDesktop(osShortName, client.getType(), client.getShortName())) { 321 | deviceType = DeviceParserAbstract.DEVICE_TYPE_DESKTOP; 322 | } 323 | if(deviceType!=null) { 324 | ret.setDeviceType(deviceType); 325 | ret.setDevice(DeviceParserAbstract.DEVICE_TYPES.inverse().get(deviceType)); 326 | } 327 | return ret; 328 | } 329 | 330 | protected DetectResult parseOs(String userAgent){ 331 | return this.osParser.parse(userAgent); 332 | } 333 | 334 | public List matchUserAgent(String userAgent, String regex){ 335 | if(Utils.isEmpty(userAgent)) { 336 | return null; 337 | } 338 | String regexStr = "(?:^|[^A-Z_-])(?:" + regex + ")"; 339 | Pattern pattern = Pattern.compile(regexStr, Pattern.CASE_INSENSITIVE); 340 | Matcher matcher = pattern.matcher(userAgent); 341 | List matches = null; 342 | while(matcher.find()) { 343 | if(matches==null) { 344 | matches = new ArrayList(); 345 | } 346 | matches.add(matcher.group()); 347 | } 348 | return matches; 349 | } 350 | 351 | } 352 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/OperatingSystem.java: -------------------------------------------------------------------------------- 1 | package io.driocc.devicedetector; 2 | 3 | 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import io.driocc.devicedetector.utils.Utils; 13 | 14 | /** 15 | * 16 | * @author kyon 17 | * 18 | */ 19 | public class OperatingSystem extends ParserAbstract { 20 | 21 | private static final long serialVersionUID = 1L; 22 | private static final String FIXTURE_FILE = "regexes/oss.yml"; 23 | public static final String UNKNOWN = "UNK"; 24 | public static Map OPERATING_SYSTEMS; 25 | public static Map OS_FAMILIES; 26 | public static List DESKTOP_OS = Arrays.asList(new String[] { 27 | "AmigaOS", "IBM", "GNU/Linux", "Mac", "Unix", "Windows", "BeOS", "Chrome OS" 28 | }); 29 | public OperatingSystem() { 30 | super("os", FIXTURE_FILE); 31 | } 32 | public OperatingSystem(String type, String file) { 33 | super(type,file); 34 | } 35 | static { 36 | OPERATING_SYSTEMS = new HashMap(); 37 | OPERATING_SYSTEMS.put("AIX", "AIX"); 38 | OPERATING_SYSTEMS.put("AND", "Android"); 39 | OPERATING_SYSTEMS.put("AMG", "AmigaOS"); 40 | OPERATING_SYSTEMS.put("ATV", "Apple TV"); 41 | OPERATING_SYSTEMS.put("ARL", "Arch Linux"); 42 | OPERATING_SYSTEMS.put("BTR", "BackTrack"); 43 | OPERATING_SYSTEMS.put("SBA", "Bada"); 44 | OPERATING_SYSTEMS.put("BEO", "BeOS"); 45 | OPERATING_SYSTEMS.put("BLB", "BlackBerry OS"); 46 | OPERATING_SYSTEMS.put("QNX", "BlackBerry Tablet OS"); 47 | OPERATING_SYSTEMS.put("BMP", "Brew"); 48 | OPERATING_SYSTEMS.put("CES", "CentOS"); 49 | OPERATING_SYSTEMS.put("COS", "Chrome OS"); 50 | OPERATING_SYSTEMS.put("CYN", "CyanogenMod"); 51 | OPERATING_SYSTEMS.put("DEB", "Debian"); 52 | OPERATING_SYSTEMS.put("DFB", "DragonFly"); 53 | OPERATING_SYSTEMS.put("FED", "Fedora"); 54 | OPERATING_SYSTEMS.put("FOS", "Firefox OS"); 55 | OPERATING_SYSTEMS.put("BSD", "FreeBSD"); 56 | OPERATING_SYSTEMS.put("GNT", "Gentoo"); 57 | OPERATING_SYSTEMS.put("GTV", "Google TV"); 58 | OPERATING_SYSTEMS.put("HPX", "HP-UX"); 59 | OPERATING_SYSTEMS.put("HAI", "Haiku OS"); 60 | OPERATING_SYSTEMS.put("IRI", "IRIX"); 61 | OPERATING_SYSTEMS.put("INF", "Inferno"); 62 | OPERATING_SYSTEMS.put("KNO", "Knoppix"); 63 | OPERATING_SYSTEMS.put("KBT", "Kubuntu"); 64 | OPERATING_SYSTEMS.put("LIN", "GNU/Linux"); 65 | OPERATING_SYSTEMS.put("LBT", "Lubuntu"); 66 | OPERATING_SYSTEMS.put("VLN", "VectorLinux"); 67 | OPERATING_SYSTEMS.put("MAC", "Mac"); 68 | OPERATING_SYSTEMS.put("MAE", "Maemo"); 69 | OPERATING_SYSTEMS.put("MDR", "Mandriva"); 70 | OPERATING_SYSTEMS.put("SMG", "MeeGo"); 71 | OPERATING_SYSTEMS.put("MCD", "MocorDroid"); 72 | OPERATING_SYSTEMS.put("MIN", "Mint"); 73 | OPERATING_SYSTEMS.put("MLD", "MildWild"); 74 | OPERATING_SYSTEMS.put("MOR", "MorphOS"); 75 | OPERATING_SYSTEMS.put("NBS", "NetBSD"); 76 | OPERATING_SYSTEMS.put("MTK", "MTK / Nucleus"); 77 | OPERATING_SYSTEMS.put("WII", "Nintendo"); 78 | OPERATING_SYSTEMS.put("NDS", "Nintendo Mobile"); 79 | OPERATING_SYSTEMS.put("OS2", "OS/2"); 80 | OPERATING_SYSTEMS.put("T64", "OSF1"); 81 | OPERATING_SYSTEMS.put("OBS", "OpenBSD"); 82 | OPERATING_SYSTEMS.put("PSP", "PlayStation Portable"); 83 | OPERATING_SYSTEMS.put("PS3", "PlayStation"); 84 | OPERATING_SYSTEMS.put("RHT", "Red Hat"); 85 | OPERATING_SYSTEMS.put("ROS", "RISC OS"); 86 | OPERATING_SYSTEMS.put("REM", "Remix OS"); 87 | OPERATING_SYSTEMS.put("RZD", "RazoDroiD"); 88 | OPERATING_SYSTEMS.put("SAB", "Sabayon"); 89 | OPERATING_SYSTEMS.put("SSE", "SUSE"); 90 | OPERATING_SYSTEMS.put("SAF", "Sailfish OS"); 91 | OPERATING_SYSTEMS.put("SLW", "Slackware"); 92 | OPERATING_SYSTEMS.put("SOS", "Solaris"); 93 | OPERATING_SYSTEMS.put("SYL", "Syllable"); 94 | OPERATING_SYSTEMS.put("SYM", "Symbian"); 95 | OPERATING_SYSTEMS.put("SYS", "Symbian OS"); 96 | OPERATING_SYSTEMS.put("S40", "Symbian OS Series 40"); 97 | OPERATING_SYSTEMS.put("S60", "Symbian OS Series 60"); 98 | OPERATING_SYSTEMS.put("SY3", "Symbian^3"); 99 | OPERATING_SYSTEMS.put("TDX", "ThreadX"); 100 | OPERATING_SYSTEMS.put("TIZ", "Tizen"); 101 | OPERATING_SYSTEMS.put("UBT", "Ubuntu"); 102 | OPERATING_SYSTEMS.put("WTV", "WebTV"); 103 | OPERATING_SYSTEMS.put("WIN", "Windows"); 104 | OPERATING_SYSTEMS.put("WCE", "Windows CE"); 105 | OPERATING_SYSTEMS.put("WMO", "Windows Mobile"); 106 | OPERATING_SYSTEMS.put("WPH", "Windows Phone"); 107 | OPERATING_SYSTEMS.put("WRT", "Windows RT"); 108 | OPERATING_SYSTEMS.put("XBX", "Xbox"); 109 | OPERATING_SYSTEMS.put("XBT", "Xubuntu"); 110 | OPERATING_SYSTEMS.put("YNS", "YunOs"); 111 | OPERATING_SYSTEMS.put("IOS", "iOS"); 112 | OPERATING_SYSTEMS.put("POS", "palmOS"); 113 | OPERATING_SYSTEMS.put("WOS", "webOS"); 114 | OS_FAMILIES = new HashMap(); 115 | OS_FAMILIES.put("Android", new String[]{"AND", "CYN", "REM", "RZD", "MLD", "MCD", "YNS"}); 116 | OS_FAMILIES.put("AAndroid", new String[]{"AND", "CYN", "REM", "RZD", "MLD", "MCD", "YNS"}); 117 | OS_FAMILIES.put("AAmigaOS", new String[]{"AMG", "MOR"}); 118 | OS_FAMILIES.put("Apple TV", new String[]{"ATV"}); 119 | OS_FAMILIES.put("BlackBerry", new String[]{"BLB", "QNX"}); 120 | OS_FAMILIES.put("Brew", new String[]{"BMP"}); 121 | OS_FAMILIES.put("BeOS", new String[]{"BEO", "HAI"}); 122 | OS_FAMILIES.put("Chrome OS", new String[]{"COS"}); 123 | OS_FAMILIES.put("Firefox OS", new String[]{"FOS"}); 124 | OS_FAMILIES.put("Gaming Console", new String[]{"WII", "PS3"}); 125 | OS_FAMILIES.put("Google TV", new String[]{"GTV"}); 126 | OS_FAMILIES.put("IBM", new String[]{"OS2"}); 127 | OS_FAMILIES.put("iOS", new String[]{"IOS"}); 128 | OS_FAMILIES.put("RISC OS", new String[]{"ROS"}); 129 | OS_FAMILIES.put("GNU/Linux", new String[]{"LIN", "ARL", "DEB", "KNO", "MIN", "UBT", "KBT", "XBT", "LBT", "FED", "RHT", "VLN", "MDR", "GNT", "SAB", "SLW", "SSE", "CES", "BTR", "SAF"}); 130 | OS_FAMILIES.put("Mac", new String[]{"MAC"}); 131 | OS_FAMILIES.put("Mobile Gaming Console", new String[]{"PSP", "NDS", "XBX"}); 132 | OS_FAMILIES.put("Real-time OS", new String[]{"MTK", "TDX"}); 133 | OS_FAMILIES.put("Other Mobile", new String[]{"WOS", "POS", "SBA", "TIZ", "SMG", "MAE"}); 134 | OS_FAMILIES.put("Symbian", new String[]{"SYM", "SYS", "SY3", "S60", "S40"}); 135 | OS_FAMILIES.put("Unix", new String[]{"SOS", "AIX", "HPX", "BSD", "NBS", "OBS", "DFB", "SYL", "IRI", "T64", "INF"}); 136 | OS_FAMILIES.put("WebTV", new String[]{"WTV"}); 137 | OS_FAMILIES.put("Windows", new String[]{"WIN"}); 138 | OS_FAMILIES.put("Windows Mobile", new String[]{"WPH", "WMO", "WCE", "WRT"}); 139 | } 140 | 141 | public Map getAvailableOperatingSystems(){ 142 | return OPERATING_SYSTEMS; 143 | } 144 | public Map getAvailableOperatingSystemFamilies(){ 145 | return OS_FAMILIES; 146 | } 147 | 148 | public DetectResult parse(String userAgent){ 149 | List matches = null; 150 | Map osRegex = null; 151 | for(Map regex : this.getRegexes()) { 152 | matches = this.matchUserAgent(userAgent, regex.get("regex").toString()); 153 | if (matches!=null && matches.size()>0) { 154 | osRegex = regex; 155 | break; 156 | } 157 | } 158 | if (matches==null || matches.size()<1) { 159 | return null; 160 | } 161 | String name = this.buildByMatch(osRegex.get("name").toString(), matches); 162 | String shortName = UNKNOWN; 163 | for (Entry e : OPERATING_SYSTEMS.entrySet()) { 164 | if (e.getValue().toLowerCase().equals(name.toLowerCase())) { 165 | name = e.getValue(); 166 | shortName = e.getKey(); 167 | } 168 | } 169 | DetectResult ret = new DetectResult(); 170 | ret.setType(this.getType()); 171 | ret.setName(name); 172 | ret.setShortName(shortName); 173 | ret.setVersion(this.buildVersionByCaptureGroup(userAgent, osRegex)); 174 | ret.setPlatform(parsePlatform(userAgent)); 175 | return ret; 176 | } 177 | 178 | /** 179 | * @param userAgent 180 | * @param osRegex 181 | * @return 182 | */ 183 | private String buildVersionByCaptureGroup(String userAgent, Map osRegex) { 184 | String ret = null; 185 | Object versionRegex = osRegex.get("version"); 186 | if(versionRegex!=null) { 187 | String versionStr = versionRegex.toString(); 188 | if(versionStr.indexOf("$")>=0) { 189 | String groupStr = versionStr.replaceAll("\\$", ""); 190 | if(Utils.isNumeric(groupStr)) { 191 | Integer cg = Integer.valueOf(groupStr); 192 | String regex = osRegex.get("regex").toString(); 193 | Pattern pattern = Pattern.compile(regex); 194 | Matcher matcher = pattern.matcher(userAgent); 195 | if(matcher.find() && matcher.groupCount()>=cg) { 196 | ret = matcher.group(cg); 197 | } 198 | }else{ 199 | ret = versionStr; 200 | } 201 | }else{ 202 | ret = versionStr; 203 | } 204 | } 205 | return ret; 206 | } 207 | 208 | /** 209 | * cannot get version while using this method 210 | */ 211 | protected String buildVersion(String versionString, List matches) { 212 | return null; 213 | } 214 | 215 | 216 | protected String parsePlatform(String userAgent){ 217 | List matches = null; 218 | matches = this.matchUserAgent(userAgent, "arm"); 219 | if (matches!=null && matches.size()>0) 220 | return "ARM"; 221 | matches = this.matchUserAgent(userAgent, "WOW64|x64|win64|amd64|x86_64"); 222 | if (matches!=null && matches.size()>0) 223 | return "x64"; 224 | matches = this.matchUserAgent(userAgent, "i[0-9]86|i86pc"); 225 | if (matches!=null && matches.size()>0) 226 | return "x86"; 227 | return ""; 228 | } 229 | /** 230 | * Returns the operating system family for the given operating system 231 | * 232 | * @param osLabel 233 | * @return string 234 | */ 235 | public static String getOsFamily(String osLabel){ 236 | for(Map.Entry entry : OS_FAMILIES.entrySet()) { 237 | String family = entry.getKey(); 238 | String[] array = entry.getValue(); 239 | for(String label : array) { 240 | if(label.equals(osLabel)) { 241 | return family; 242 | } 243 | } 244 | } 245 | return null; 246 | } 247 | /** 248 | * Returns the full name for the given short name 249 | * 250 | * @param $os 251 | * @param bool $ver 252 | * 253 | * @return bool|string 254 | */ 255 | public String getNameFromId(String os, String ver){ 256 | String osFullName = OPERATING_SYSTEMS.get(os); 257 | if (!Utils.isEmpty(osFullName)) { 258 | osFullName = osFullName + " " + ver; 259 | return osFullName.trim(); 260 | } 261 | return null; 262 | } 263 | 264 | } 265 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/ParserAbstract.java: -------------------------------------------------------------------------------- 1 | package io.driocc.devicedetector; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import com.google.common.base.Joiner; 12 | 13 | import io.driocc.devicedetector.custom.VersionConsultant; 14 | import io.driocc.devicedetector.utils.Utils; 15 | import io.driocc.devicedetector.yaml.YamlParser; 16 | 17 | public abstract class ParserAbstract implements Serializable { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | private String type; 22 | private String yaml; 23 | private List> regexes; 24 | private VersionConsultant versionConsultant; 25 | public ParserAbstract(String type, String file) { 26 | this.setType(type); 27 | this.setYaml(file); 28 | this.setRegexes(YamlParser.get(file)); 29 | this.versionConsultant = new VersionConsultant(); 30 | } 31 | /** 32 | * @return the type 33 | */ 34 | public String getType() { 35 | return type; 36 | } 37 | /** 38 | * @param type the type to set 39 | */ 40 | public void setType(String type) { 41 | this.type = type; 42 | } 43 | /** 44 | * @return the yaml 45 | */ 46 | public String getYaml() { 47 | return yaml; 48 | } 49 | /** 50 | * @param yaml the yaml to set 51 | */ 52 | public void setYaml(String yaml) { 53 | this.yaml = yaml; 54 | } 55 | /** 56 | * @return the regexes 57 | */ 58 | public List> getRegexes() { 59 | return regexes; 60 | } 61 | /** 62 | * @param regexes the regexes to set 63 | */ 64 | public void setRegexes(List> regexes) { 65 | this.regexes = regexes; 66 | } 67 | 68 | public List matchUserAgent(String userAgent, String regex) { 69 | List matchs = new ArrayList(); 70 | if(Utils.isEmpty(userAgent)) { 71 | return null; 72 | } 73 | String reg = "(?:^|[^A-Z0-9\\-_]|[^A-Z0-9\\-]_|sprd-)(?:" + regex + ")"; 74 | Pattern pattern = Pattern.compile(reg); 75 | Matcher matcher = pattern.matcher(userAgent); 76 | while(matcher.find()) { 77 | matchs.add(matcher.group()); 78 | } 79 | return matchs; 80 | } 81 | 82 | /** 83 | * 84 | * @param item 85 | * @param matches 86 | * @return 87 | */ 88 | protected String buildByMatch(String item, List matches){ 89 | for(int i=1; i<=3; i++) { 90 | String str = "$" + i; 91 | int start = item.indexOf(str); 92 | if(start == -1) { 93 | continue; 94 | } 95 | String startStr = item.substring(0, start); 96 | String repl = ""; 97 | if(i < (matches.size()+1)) { 98 | repl = matches.get(i-1); 99 | } 100 | item = item.replace(startStr + str, repl).trim(); 101 | } 102 | return item; 103 | } 104 | 105 | /** 106 | * Builds the version with the given $versionString and $matches 107 | * 108 | * Example: 109 | * versionString = 'v$2' 110 | * matches = array('version_1_0_1', '1_0_1') 111 | * return value would be v1.0.1 112 | * @param versionString 113 | * @param matches 114 | * @return 115 | */ 116 | protected String buildVersion(String versionString, List matches) { 117 | String ver = buildByMatch(versionString, matches); 118 | ver = Utils.isEmpty(ver) ? "" : ver; 119 | Integer maxMinorParts = this.versionConsultant.getMaxMinorParts(); 120 | if (maxMinorParts!=null && Utils.countChar(versionString, '.') > maxMinorParts) { 121 | String[] versionParts = versionString.split("."); 122 | List newVersionPartsList = Arrays.asList(versionParts).subList(0, maxMinorParts + 1); 123 | ver = Joiner.on(".").join(newVersionPartsList); 124 | } 125 | return ver; 126 | } 127 | 128 | /** 129 | * Tests the useragent against a combination of all regexes 130 | * 131 | * All regexes returned by getRegexes() will be reversed and concated with '|' 132 | * Afterwards the big regex will be tested against the user agent 133 | * 134 | * Method can be used to speed up detections by making a big check before doing checks for every single regex 135 | * @param userAgent 136 | * @return 137 | */ 138 | protected boolean preMatchOverall(String userAgent) { 139 | // TODO Auto-generated method stub 140 | return false; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/VendorFragment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | 10 | import io.driocc.devicedetector.device.DeviceParserAbstract; 11 | 12 | /** 13 | * @author kyon 14 | * 15 | */ 16 | public class VendorFragment extends ParserAbstract { 17 | /** 18 | * 19 | */ 20 | private static final long serialVersionUID = 1L; 21 | 22 | protected static final String FIXTURE_FILE = "regexes/vendorfragments.yml"; 23 | protected static final String PARSER = "vendorfragments"; 24 | public VendorFragment() { 25 | super(PARSER,FIXTURE_FILE); 26 | } 27 | public VendorFragment(String type, String file) { 28 | super(type, file); 29 | } 30 | public DetectResult parse(String userAgent){ 31 | Map rs = this.getRegexes().get(0); 32 | for (Entry e : rs.entrySet()) { 33 | String brand = e.getKey(); 34 | List regexes = (List)e.getValue(); 35 | for (String r : regexes) { 36 | List matches = this.matchUserAgent(userAgent, r + "[^a-z0-9]+"); 37 | if(matches!=null && matches.size()>0) { 38 | String brandId = DeviceParserAbstract.DEVICE_BRANDS.inverse().get(brand); 39 | DetectResult ret = new DetectResult(); 40 | ret.setBrand(brand); 41 | ret.setBrandId(brandId); 42 | return ret; 43 | } 44 | } 45 | } 46 | return null; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/Browser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client; 5 | 6 | import java.util.Arrays; 7 | import java.util.Comparator; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | import java.util.Set; 13 | 14 | import io.driocc.devicedetector.DetectResult; 15 | import io.driocc.devicedetector.client.engine.Engine; 16 | import io.driocc.devicedetector.utils.Utils; 17 | 18 | 19 | /** 20 | * @author kyon 21 | * 22 | */ 23 | public class Browser extends ClientParserAbstract { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | private static final String FIXTURE_FILE = "regexes/client/browsers.yml"; 28 | private static final String PARSER = "browser"; 29 | /** 30 | * Known browsers mapped to their internal short codes 31 | */ 32 | private static Map AVAILABLE_BROWSERS; 33 | /** 34 | * Browser families mapped to the short codes of the associated browsers 35 | */ 36 | private static Map BROWSER_FAMILIES; 37 | /** 38 | * Browsers that are available for mobile devices only 39 | */ 40 | private static List MOBILE_ONLY_BROWSERS; 41 | private Engine engineParser; 42 | public Browser(){ 43 | super(PARSER, FIXTURE_FILE); 44 | engineParser = new Engine(); 45 | } 46 | public Browser(String type, String file){ 47 | super(type, file); 48 | engineParser = new Engine(); 49 | } 50 | static { 51 | AVAILABLE_BROWSERS = new HashMap<>(); 52 | AVAILABLE_BROWSERS.put("36","360 Phone Browser"); 53 | AVAILABLE_BROWSERS.put("3B","360 Browser"); 54 | AVAILABLE_BROWSERS.put("AA","Avant Browser"); 55 | AVAILABLE_BROWSERS.put("AB","ABrowse"); 56 | AVAILABLE_BROWSERS.put("AF","ANT Fresco"); 57 | AVAILABLE_BROWSERS.put("AG","ANTGalio"); 58 | AVAILABLE_BROWSERS.put("AL","Aloha Browser"); 59 | AVAILABLE_BROWSERS.put("AM","Amaya"); 60 | AVAILABLE_BROWSERS.put("AO","Amigo"); 61 | AVAILABLE_BROWSERS.put("AN","Android Browser"); 62 | AVAILABLE_BROWSERS.put("AR","Arora"); 63 | AVAILABLE_BROWSERS.put("AV","Amiga Voyager"); 64 | AVAILABLE_BROWSERS.put("AW","Amiga Aweb"); 65 | AVAILABLE_BROWSERS.put("AT","Atomic Web Browser"); 66 | AVAILABLE_BROWSERS.put("BB","BlackBerry Browser"); 67 | AVAILABLE_BROWSERS.put("BD","Baidu Browser"); 68 | AVAILABLE_BROWSERS.put("BS","Baidu Spark"); 69 | AVAILABLE_BROWSERS.put("BE","Beonex"); 70 | AVAILABLE_BROWSERS.put("BJ","Bunjalloo"); 71 | AVAILABLE_BROWSERS.put("BL","B-Line"); 72 | AVAILABLE_BROWSERS.put("BR","Brave"); 73 | AVAILABLE_BROWSERS.put("BK","BriskBard"); 74 | AVAILABLE_BROWSERS.put("BX","BrowseX"); 75 | AVAILABLE_BROWSERS.put("CA","Camino"); 76 | AVAILABLE_BROWSERS.put("CC","Coc Coc"); 77 | AVAILABLE_BROWSERS.put("CD","Comodo Dragon"); 78 | AVAILABLE_BROWSERS.put("C1","Coast"); 79 | AVAILABLE_BROWSERS.put("CX","Charon"); 80 | AVAILABLE_BROWSERS.put("CF","Chrome Frame"); 81 | AVAILABLE_BROWSERS.put("CH","Chrome"); 82 | AVAILABLE_BROWSERS.put("CI","Chrome Mobile iOS"); 83 | AVAILABLE_BROWSERS.put("CK","Conkeror"); 84 | AVAILABLE_BROWSERS.put("CM","Chrome Mobile"); 85 | AVAILABLE_BROWSERS.put("CN","CoolNovo"); 86 | AVAILABLE_BROWSERS.put("CO","CometBird"); 87 | AVAILABLE_BROWSERS.put("CP","ChromePlus"); 88 | AVAILABLE_BROWSERS.put("CR","Chromium"); 89 | AVAILABLE_BROWSERS.put("CY","Cyberfox"); 90 | AVAILABLE_BROWSERS.put("CS","Cheshire"); 91 | AVAILABLE_BROWSERS.put("DB","dbrowser"); 92 | AVAILABLE_BROWSERS.put("DE","Deepnet Explorer"); 93 | AVAILABLE_BROWSERS.put("DF","Dolphin"); 94 | AVAILABLE_BROWSERS.put("DO","Dorado"); 95 | AVAILABLE_BROWSERS.put("DL","Dooble"); 96 | AVAILABLE_BROWSERS.put("DI","Dillo"); 97 | AVAILABLE_BROWSERS.put("EI","Epic"); 98 | AVAILABLE_BROWSERS.put("EL","Elinks"); 99 | AVAILABLE_BROWSERS.put("EB","Element Browser"); 100 | AVAILABLE_BROWSERS.put("EP","GNOME Web"); 101 | AVAILABLE_BROWSERS.put("ES","Espial TV Browser"); 102 | AVAILABLE_BROWSERS.put("FB","Firebird"); 103 | AVAILABLE_BROWSERS.put("FD","Fluid"); 104 | AVAILABLE_BROWSERS.put("FE","Fennec"); 105 | AVAILABLE_BROWSERS.put("FF","Firefox"); 106 | AVAILABLE_BROWSERS.put("FL","Flock"); 107 | AVAILABLE_BROWSERS.put("FM","Firefox Mobile"); 108 | AVAILABLE_BROWSERS.put("FW","Fireweb"); 109 | AVAILABLE_BROWSERS.put("FN","Fireweb Navigator"); 110 | AVAILABLE_BROWSERS.put("GA","Galeon"); 111 | AVAILABLE_BROWSERS.put("GE","Google Earth"); 112 | AVAILABLE_BROWSERS.put("HJ","HotJava"); 113 | AVAILABLE_BROWSERS.put("IA","Iceape"); 114 | AVAILABLE_BROWSERS.put("IB","IBrowse"); 115 | AVAILABLE_BROWSERS.put("IC","iCab"); 116 | AVAILABLE_BROWSERS.put("I2","iCab Mobile"); 117 | AVAILABLE_BROWSERS.put("I1","Iridium"); 118 | AVAILABLE_BROWSERS.put("ID","IceDragon"); 119 | AVAILABLE_BROWSERS.put("IV","Isivioo"); 120 | AVAILABLE_BROWSERS.put("IW","Iceweasel"); 121 | AVAILABLE_BROWSERS.put("IE","Internet Explorer"); 122 | AVAILABLE_BROWSERS.put("IM","IE Mobile"); 123 | AVAILABLE_BROWSERS.put("IR","Iron"); 124 | AVAILABLE_BROWSERS.put("JS","Jasmine"); 125 | AVAILABLE_BROWSERS.put("JI","Jig Browser"); 126 | AVAILABLE_BROWSERS.put("KI","Kindle Browser"); 127 | AVAILABLE_BROWSERS.put("KM","K-meleon"); 128 | AVAILABLE_BROWSERS.put("KO","Konqueror"); 129 | AVAILABLE_BROWSERS.put("KP","Kapiko"); 130 | AVAILABLE_BROWSERS.put("KY","Kylo"); 131 | AVAILABLE_BROWSERS.put("KZ","Kazehakase"); 132 | AVAILABLE_BROWSERS.put("LB","Liebao"); 133 | AVAILABLE_BROWSERS.put("LG","LG Browser"); 134 | AVAILABLE_BROWSERS.put("LI","Links"); 135 | AVAILABLE_BROWSERS.put("LU","LuaKit"); 136 | AVAILABLE_BROWSERS.put("LS","Lunascape"); 137 | AVAILABLE_BROWSERS.put("LX","Lynx"); 138 | AVAILABLE_BROWSERS.put("MB","MicroB"); 139 | AVAILABLE_BROWSERS.put("MC","NCSA Mosaic"); 140 | AVAILABLE_BROWSERS.put("ME","Mercury"); 141 | AVAILABLE_BROWSERS.put("MF","Mobile Safari"); 142 | AVAILABLE_BROWSERS.put("MI","Midori"); 143 | AVAILABLE_BROWSERS.put("MU","MIUI Browser"); 144 | AVAILABLE_BROWSERS.put("MS","Mobile Silk"); 145 | AVAILABLE_BROWSERS.put("MX","Maxthon"); 146 | AVAILABLE_BROWSERS.put("NB","Nokia Browser"); 147 | AVAILABLE_BROWSERS.put("NO","Nokia OSS Browser"); 148 | AVAILABLE_BROWSERS.put("NV","Nokia Ovi Browser"); 149 | AVAILABLE_BROWSERS.put("NF","NetFront"); 150 | AVAILABLE_BROWSERS.put("NL","NetFront Life"); 151 | AVAILABLE_BROWSERS.put("NP","NetPositive"); 152 | AVAILABLE_BROWSERS.put("NS","Netscape"); 153 | AVAILABLE_BROWSERS.put("OB","Obigo"); 154 | AVAILABLE_BROWSERS.put("OD","Odyssey Web Browser"); 155 | AVAILABLE_BROWSERS.put("OF","Off By One"); 156 | AVAILABLE_BROWSERS.put("OE","ONE Browser"); 157 | AVAILABLE_BROWSERS.put("OI","Opera Mini"); 158 | AVAILABLE_BROWSERS.put("OM","Opera Mobile"); 159 | AVAILABLE_BROWSERS.put("OP","Opera"); 160 | AVAILABLE_BROWSERS.put("ON","Opera Next"); 161 | AVAILABLE_BROWSERS.put("OR","Oregano"); 162 | AVAILABLE_BROWSERS.put("OV","Openwave Mobile Browser"); 163 | AVAILABLE_BROWSERS.put("OW","OmniWeb"); 164 | AVAILABLE_BROWSERS.put("OT","Otter Browser"); 165 | AVAILABLE_BROWSERS.put("PL","Palm Blazer"); 166 | AVAILABLE_BROWSERS.put("PM","Pale Moon"); 167 | AVAILABLE_BROWSERS.put("PR","Palm Pre"); 168 | AVAILABLE_BROWSERS.put("PU","Puffin"); 169 | AVAILABLE_BROWSERS.put("PW","Palm WebPro"); 170 | AVAILABLE_BROWSERS.put("PA","Palmscape"); 171 | AVAILABLE_BROWSERS.put("PX","Phoenix"); 172 | AVAILABLE_BROWSERS.put("PO","Polaris"); 173 | AVAILABLE_BROWSERS.put("PT","Polarity"); 174 | AVAILABLE_BROWSERS.put("PS","Microsoft Edge"); 175 | AVAILABLE_BROWSERS.put("QQ","QQ Browser"); 176 | AVAILABLE_BROWSERS.put("QT","Qutebrowser"); 177 | AVAILABLE_BROWSERS.put("QZ","QupZilla"); 178 | AVAILABLE_BROWSERS.put("RK","Rekonq"); 179 | AVAILABLE_BROWSERS.put("RM","RockMelt"); 180 | AVAILABLE_BROWSERS.put("SB","Samsung Browser"); 181 | AVAILABLE_BROWSERS.put("SA","Sailfish Browser"); 182 | AVAILABLE_BROWSERS.put("SC","SEMC-Browser"); 183 | AVAILABLE_BROWSERS.put("SE","Sogou Explorer"); 184 | AVAILABLE_BROWSERS.put("SF","Safari"); 185 | AVAILABLE_BROWSERS.put("SH","Shiira"); 186 | AVAILABLE_BROWSERS.put("SK","Skyfire"); 187 | AVAILABLE_BROWSERS.put("SS","Seraphic Sraf"); 188 | AVAILABLE_BROWSERS.put("SL","Sleipnir"); 189 | AVAILABLE_BROWSERS.put("SM","SeaMonkey"); 190 | AVAILABLE_BROWSERS.put("SN","Snowshoe"); 191 | AVAILABLE_BROWSERS.put("SR","Sunrise"); 192 | AVAILABLE_BROWSERS.put("SP","SuperBird"); 193 | AVAILABLE_BROWSERS.put("ST","Streamy"); 194 | AVAILABLE_BROWSERS.put("SX","Swiftfox"); 195 | AVAILABLE_BROWSERS.put("TZ","Tizen Browser"); 196 | AVAILABLE_BROWSERS.put("TS","TweakStyle"); 197 | AVAILABLE_BROWSERS.put("UC","UC Browser"); 198 | AVAILABLE_BROWSERS.put("VI","Vivaldi"); 199 | AVAILABLE_BROWSERS.put("VB","Vision Mobile Browser"); 200 | AVAILABLE_BROWSERS.put("WE","WebPositive"); 201 | AVAILABLE_BROWSERS.put("WF","Waterfox"); 202 | AVAILABLE_BROWSERS.put("WO","wOSBrowser"); 203 | AVAILABLE_BROWSERS.put("WT","WeTab Browser"); 204 | AVAILABLE_BROWSERS.put("YA","Yandex Browser"); 205 | AVAILABLE_BROWSERS.put("XI","Xiino"); 206 | 207 | /** 208 | * Browser families mapped to the short codes of the associated browsers 209 | * 210 | * @var array 211 | */ 212 | BROWSER_FAMILIES = new HashMap<>(); 213 | BROWSER_FAMILIES.put("Android Browser", new String[] {"AN", "MU"}); 214 | BROWSER_FAMILIES.put("BlackBerry Browser",new String[] {"BB"}); 215 | BROWSER_FAMILIES.put("Baidu", new String[] {"BD", "BS"}); 216 | BROWSER_FAMILIES.put("Amiga", new String[] {"AV", "AW"}); 217 | BROWSER_FAMILIES.put("Chrome", new String[] {"CH", "BR", "CC", "CD", "CM", "CI", "CF", "CN", "CR", "CP", "IR", "RM", "AO", "TS", "VI", "PT"}); 218 | BROWSER_FAMILIES.put("Firefox", new String[] {"FF", "FE", "FM", "SX", "FB", "PX", "MB", "EI", "WF"}); 219 | BROWSER_FAMILIES.put("Internet Explorer", new String[] {"IE", "IM", "PS"}); 220 | BROWSER_FAMILIES.put("Konqueror", new String[] {"KO"}); 221 | BROWSER_FAMILIES.put("NetFront", new String[] {"NF"}); 222 | BROWSER_FAMILIES.put("Nokia Browser", new String[] {"NB", "NO", "NV", "DO"}); 223 | BROWSER_FAMILIES.put("Opera", new String[] {"OP", "OM", "OI", "ON"}); 224 | BROWSER_FAMILIES.put("Safari", new String[] {"SF", "MF"}); 225 | BROWSER_FAMILIES.put("Sailfish Browser", new String[] {"SA"}); 226 | 227 | MOBILE_ONLY_BROWSERS = Arrays.asList(new String[] { 228 | "36", "PU", "SK", "MF", "OI", "OM", "DB", "ST", "BL", "IV", "FM", "C1", "AL"}); 229 | }; 230 | 231 | /** 232 | * @param browserLabel 233 | * @return 234 | */ 235 | public static String getBrowserFamily(String browserLabel){ 236 | for(Entry e : BROWSER_FAMILIES.entrySet()) { 237 | String[] labels = e.getValue(); 238 | if(labels==null)return null; 239 | for (String label : labels) { 240 | if(label.equals(browserLabel)) { 241 | return e.getKey(); 242 | } 243 | } 244 | } 245 | return null; 246 | } 247 | /** 248 | * Returns if the given browser is mobile only 249 | * 250 | * @param string $browser Label or name of browser 251 | * @return bool 252 | */ 253 | public static boolean isMobileOnlyBrowser(String browser){ 254 | if(Utils.isEmpty(browser))return false; 255 | if(MOBILE_ONLY_BROWSERS.contains(browser)) { 256 | return true; 257 | } 258 | for(Entry e : AVAILABLE_BROWSERS.entrySet()) { 259 | if(e.getValue().equals(browser)) { 260 | if(MOBILE_ONLY_BROWSERS.contains(e.getKey())){ 261 | return true; 262 | } 263 | } 264 | } 265 | return false; 266 | } 267 | public DetectResult parse(String userAgent){ 268 | List matches = null; 269 | Map currentRegexObj = null; 270 | for (Map regexObj : this.getRegexes()) { 271 | matches = this.matchUserAgent(userAgent, regexObj.get("regex").toString()); 272 | if (matches!=null && matches.size()>0) { 273 | currentRegexObj = regexObj; 274 | break; 275 | } 276 | } 277 | if (matches==null || matches.size()<1) { 278 | return null; 279 | } 280 | String name = this.buildByMatch(currentRegexObj.get("name").toString(), matches); 281 | DetectResult ret = null; 282 | for(Entry browserShort : AVAILABLE_BROWSERS.entrySet()) { 283 | if (name.toLowerCase().equals(browserShort.getValue().toLowerCase())) { 284 | String version = this.buildVersionByCaptureGroup(userAgent, currentRegexObj); 285 | DetectResult engine = null; 286 | ret = new DetectResult(); 287 | ret.setType("browser"); 288 | ret.setName(name); 289 | ret.setShortName(browserShort.getKey()); 290 | ret.setVersion(version); 291 | Object engineObj = currentRegexObj.get("engine"); 292 | if(currentRegexObj.containsKey("engine")) { 293 | engine = this.buildEngine(userAgent, (Map)engineObj, version); 294 | ret.setEngine(engine.getEngine()); 295 | ret.setEngineVersion(engine.getEngineVersion()); 296 | } 297 | break; 298 | } 299 | } 300 | return ret; 301 | } 302 | protected DetectResult buildEngine(String userAgent, Map engineData, String browserVersion){ 303 | DetectResult ret =null; 304 | String engine = null; 305 | String ver = null; 306 | // if an engine is set as default 307 | if (engineData.containsKey("default")) { 308 | engine = engineData.get("default").toString(); 309 | } 310 | // check if engine is set for browser version 311 | Object versionObj = engineData.get("versions"); 312 | if (versionObj!=null && versionObj instanceof Map) { 313 | Map versions = (Map)versionObj; 314 | Set versionNums = versions.keySet(); 315 | List versionList = Arrays.asList(versionNums.toArray()); 316 | versionList.sort(new Comparator() { 317 | @Override 318 | public int compare(Object o1, Object o2) { 319 | return Utils.versionCompare(o1.toString(), o2.toString()); 320 | }}); 321 | for(Object version : versionList) { 322 | String versionStr = version.toString(); 323 | if (Utils.versionCompare(browserVersion, versionStr) >= 0) { 324 | //System.out.println(browserVersion + " vs " + version.toString()); 325 | //get the left part and compare, true if equal 326 | String browserVersionLeft = browserVersion.contains(".")?browserVersion.split("\\.")[0]:browserVersion; 327 | String versionLeft = versionStr.contains(".")?versionStr.split(".")[0]:versionStr; 328 | if(Utils.isNumeric(browserVersionLeft) && Utils.isNumeric(versionLeft)) { 329 | if(Integer.valueOf(browserVersionLeft).compareTo(Integer.valueOf(versionLeft))==0) { 330 | engine = versions.get(version).toString(); 331 | ver = version.toString(); 332 | } 333 | } 334 | } 335 | } 336 | } 337 | // try to detect the engine using the regexes 338 | if (engine==null || "".equals(engine)) { 339 | ret = engineParser.parse(userAgent); 340 | } else { 341 | ret = new DetectResult(); 342 | ret.setEngine(engine); 343 | ret.setEngineVersion(ver); 344 | } 345 | return ret; 346 | } 347 | 348 | /** 349 | * we don't need this function 350 | * @param userAgent 351 | * @param engine 352 | * @return 353 | */ 354 | protected String buildEngineVersion(String userAgent, String engine){ 355 | return null; 356 | } 357 | 358 | } 359 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/ClientParserAbstract.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client; 5 | 6 | import java.util.Comparator; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | import java.util.stream.Collectors; 13 | 14 | import io.driocc.devicedetector.DetectResult; 15 | import io.driocc.devicedetector.ParserAbstract; 16 | import io.driocc.devicedetector.utils.Utils; 17 | 18 | /** 19 | * @author kyon 20 | * 21 | */ 22 | public class ClientParserAbstract extends ParserAbstract{ 23 | 24 | /** 25 | * @param type 26 | * @param file 27 | */ 28 | public ClientParserAbstract(String type, String file) { 29 | super(type, file); 30 | } 31 | 32 | private static final long serialVersionUID = 1L; 33 | 34 | public DetectResult parse(String userAgent) { 35 | DetectResult ret = null; 36 | if (this.preMatchOverall(userAgent)) { 37 | for(Map regexObj : this.getRegexes()) { 38 | List matches = this.matchUserAgent(userAgent, regexObj.get("regex").toString()); 39 | if (matches!=null && matches.size()>0) { 40 | ret = new DetectResult(); 41 | String name = buildByMatch(regexObj.get("name").toString(), matches); 42 | String version = buildVersion(regexObj.get("version").toString(), matches); 43 | ret.setType(this.getType()); 44 | ret.setName(name); 45 | ret.setVersion(version); 46 | } 47 | } 48 | } 49 | return ret; 50 | } 51 | 52 | /** 53 | * Returns all names defined in the regexes 54 | * 55 | * Attention: This method might not return all names of detected clients 56 | * 57 | * @return array 58 | */ 59 | public List getAvailableClients(){ 60 | List> regexes = this.getRegexes(); 61 | final List nameList = new LinkedList<>(); 62 | regexes.forEach(x->nameList.add(x.get("name").toString())); 63 | List ret = nameList.stream().distinct().collect(Collectors.toList()); 64 | ret.sort(Comparator.naturalOrder()); 65 | return ret; 66 | } 67 | 68 | /** 69 | * @param userAgent 70 | * @param osRegex 71 | * @return 72 | */ 73 | protected String buildVersionByCaptureGroup(String userAgent, Map osRegex) { 74 | String ret = null; 75 | Object versionRegex = osRegex.get("version"); 76 | if(versionRegex!=null) { 77 | String versionStr = versionRegex.toString(); 78 | if(versionStr.indexOf("$")>=0) { 79 | String groupStr = versionStr.replaceAll("\\$", ""); 80 | if(Utils.isNumeric(groupStr)) { 81 | Integer cg = Integer.valueOf(groupStr); 82 | String regex = osRegex.get("regex").toString(); 83 | Pattern pattern = Pattern.compile(regex); 84 | Matcher matcher = pattern.matcher(userAgent); 85 | if(matcher.find() && matcher.groupCount()>=cg) { 86 | ret = matcher.group(cg); 87 | } 88 | }else{ 89 | ret = versionStr; 90 | } 91 | }else{ 92 | ret = versionStr; 93 | } 94 | } 95 | return ret; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/FeedReader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client; 5 | 6 | /** 7 | * @author kyon 8 | * 9 | */ 10 | public class FeedReader extends ClientParserAbstract { 11 | 12 | private static final long serialVersionUID = 1L; 13 | protected static final String FIXTURE_FILE = "regexes/client/feed_readers.yml"; 14 | protected static final String PARSER = "feed reader"; 15 | 16 | public FeedReader(){ 17 | super(PARSER, FIXTURE_FILE); 18 | } 19 | public FeedReader(String type, String file){ 20 | super(type, file); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/Library.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client; 5 | 6 | /** 7 | * @author kyon 8 | * 9 | */ 10 | public class Library extends ClientParserAbstract { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | protected static final String FIXTURE_FILE = "regexes/client/libraries.yml"; 15 | protected static final String PARSER = "libraries"; 16 | 17 | public Library(){ 18 | super(PARSER, FIXTURE_FILE); 19 | } 20 | public Library(String type, String file){ 21 | super(type, file); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/MediaPlayer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client; 5 | 6 | /** 7 | * @author kyon 8 | * 9 | */ 10 | public class MediaPlayer extends ClientParserAbstract { 11 | 12 | private static final long serialVersionUID = 1L; 13 | protected static final String FIXTURE_FILE = "regexes/client/mediaplayers.yml"; 14 | protected static final String PARSER = "mediaplayers"; 15 | 16 | public MediaPlayer(){ 17 | super(PARSER, FIXTURE_FILE); 18 | } 19 | public MediaPlayer(String type, String file){ 20 | super(type, file); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/MobileApp.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client; 5 | 6 | /** 7 | * @author kyon 8 | * 9 | */ 10 | public class MobileApp extends ClientParserAbstract { 11 | 12 | private static final long serialVersionUID = 1L; 13 | protected static final String FIXTURE_FILE = "regexes/client/mobile_apps.yml"; 14 | protected static final String PARSER = "mobile apps"; 15 | 16 | public MobileApp(){ 17 | super(PARSER, FIXTURE_FILE); 18 | } 19 | public MobileApp(String type, String file){ 20 | super(type, file); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/PIM.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client; 5 | 6 | /** 7 | * @author kyon 8 | * 9 | */ 10 | public class PIM extends ClientParserAbstract { 11 | 12 | private static final long serialVersionUID = 1L; 13 | protected static final String FIXTURE_FILE = "regexes/client/pim.yml"; 14 | protected static final String PARSER = "pim"; 15 | 16 | public PIM(){ 17 | super(PARSER, FIXTURE_FILE); 18 | } 19 | public PIM(String type, String file){ 20 | super(type, file); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/engine/Engine.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client.engine; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import io.driocc.devicedetector.DetectResult; 13 | import io.driocc.devicedetector.client.ClientParserAbstract; 14 | 15 | /** 16 | * @author kyon 17 | * 18 | */ 19 | public class Engine extends ClientParserAbstract { 20 | /** 21 | * 22 | */ 23 | private static final long serialVersionUID = 1L; 24 | private static final String FIXTURE_FILE = "regexes/client/browser_engine.yml"; 25 | public Engine() { 26 | super("browserengine", FIXTURE_FILE); 27 | } 28 | public Engine(String type, String file) { 29 | super(type, file); 30 | } 31 | /** 32 | * Known browser engines mapped to their internal short codes 33 | * 34 | * @var array 35 | */ 36 | protected List availableEngines = Arrays.asList(new String[] { 37 | "WebKit", 38 | "Blink", 39 | "Trident", 40 | "Text-based", 41 | "Dillo", 42 | "iCab", 43 | "Elektra", 44 | "Presto", 45 | "Gecko", 46 | "KHTML", 47 | "NetFront", 48 | "Edge" 49 | }); 50 | /** 51 | * Returns list of all available browser engines 52 | * @return array 53 | */ 54 | public List getAvailableEngines(){ 55 | return this.availableEngines; 56 | } 57 | public DetectResult parse(String userAgent){ 58 | List matches = null; 59 | Map thisRegex = null; 60 | for (Map regex : this.getRegexes()) { 61 | matches = this.matchUserAgent(userAgent, regex.get("regex").toString()); 62 | if (matches!=null && matches.size()>0) { 63 | thisRegex = regex; 64 | break; 65 | } 66 | } 67 | if (matches==null || matches.size()<1) { 68 | return null; 69 | } 70 | String name = this.buildByMatch(thisRegex.get("name").toString(), matches); 71 | for(String engine : this.getAvailableEngines()){ 72 | if (engine.toLowerCase().equals(name.toLowerCase())) { 73 | name = engine; 74 | } 75 | } 76 | String browserEngineVersion = buildEngineVersion(userAgent, name); 77 | DetectResult ret = new DetectResult(); 78 | ret.setEngine(name); 79 | ret.setEngineVersion(browserEngineVersion); 80 | return ret; 81 | } 82 | public String buildEngineVersion(String userAgent, String engine) { 83 | if(engine==null || "".equals(engine)) { 84 | return null; 85 | } 86 | String ret = null; 87 | String reg = engine+"\\s*/?\\s*(((?=\\d+\\.\\d)\\d+[.\\d]*)|(.\\d{1,7}(?=(?:\\D|$))))"; 88 | Pattern pattern = Pattern.compile(reg); 89 | Matcher matcher = pattern.matcher(userAgent); 90 | if(matcher.find()){ 91 | ret = matcher.group(); 92 | } 93 | return ret; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/client/engine/Version.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.client.engine; 5 | 6 | /** 7 | * we dont need this clazz, @see io.driocc.devicedetector.client.engine.Engine.buildEngineVersion(String, String) 8 | * @author kyon 9 | * 10 | */ 11 | public class Version { 12 | private Version() {} 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/custom/CompositeDetectResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.custom; 5 | 6 | import com.google.common.base.MoreObjects; 7 | 8 | import io.driocc.devicedetector.DetectResult; 9 | 10 | /** 11 | * @author kyon 12 | * 13 | */ 14 | public class CompositeDetectResult { 15 | private DetectResult bot; 16 | private DetectResult os; 17 | private DetectResult client; 18 | private DetectResult device; 19 | /** 20 | * @return the bot 21 | */ 22 | public DetectResult getBot() { 23 | return bot; 24 | } 25 | /** 26 | * @param bot the bot to set 27 | */ 28 | public void setBot(DetectResult bot) { 29 | this.bot = bot; 30 | } 31 | /** 32 | * @return the os 33 | */ 34 | public DetectResult getOs() { 35 | return os; 36 | } 37 | /** 38 | * @param os the os to set 39 | */ 40 | public void setOs(DetectResult os) { 41 | this.os = os; 42 | } 43 | /** 44 | * @return the client 45 | */ 46 | public DetectResult getClient() { 47 | return client; 48 | } 49 | /** 50 | * @param client the client to set 51 | */ 52 | public void setClient(DetectResult client) { 53 | this.client = client; 54 | } 55 | /** 56 | * @return the device 57 | */ 58 | public DetectResult getDevice() { 59 | return device; 60 | } 61 | /** 62 | * @param device the device to set 63 | */ 64 | public void setDevice(DetectResult device) { 65 | this.device = device; 66 | } 67 | 68 | public String toString() { 69 | return MoreObjects.toStringHelper(this) 70 | .add("bot", bot) 71 | .add("os", os) 72 | .add("client", client) 73 | .add("device", device) 74 | .omitNullValues().toString(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/custom/DetectConsultant.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.custom; 5 | 6 | import io.driocc.devicedetector.OperatingSystem; 7 | import io.driocc.devicedetector.client.Browser; 8 | import io.driocc.devicedetector.device.DeviceParserAbstract; 9 | import io.driocc.devicedetector.utils.Utils; 10 | 11 | /** 12 | * @author kyon 13 | * 14 | */ 15 | public class DetectConsultant { 16 | 17 | protected static boolean usesMobileBrowser(String clientType, String clientShortName){ 18 | return "browser".equals(clientType) && Browser.isMobileOnlyBrowser(clientShortName); 19 | } 20 | 21 | public static boolean isMobile(Integer deviceType, String clientType, String clientShortName, String osShortName, Boolean isBot){ 22 | // Mobile device types 23 | if (deviceType!=null) { 24 | return DeviceParserAbstract.isMobile(deviceType); 25 | } 26 | // Check for browsers available for mobile devices only 27 | if (usesMobileBrowser(clientType, clientShortName)) { 28 | return true; 29 | } 30 | return !isBot && !isDesktop(osShortName, clientType, clientShortName); 31 | } 32 | /** 33 | * Returns if the parsed UA was identified as desktop device 34 | * Desktop devices are all devices with an unknown type that are running a desktop os 35 | * 36 | * @return bool 37 | */ 38 | public static boolean isDesktop(String osShortName, String clientType, String clientShortName){ 39 | if(Utils.isEmpty(osShortName) || OperatingSystem.UNKNOWN.equals(osShortName))return false; 40 | // Check for browsers available for mobile devices only 41 | if (usesMobileBrowser(clientType, clientShortName)) { 42 | return false; 43 | } 44 | String decodedFamily = OperatingSystem.getOsFamily(osShortName); 45 | return OperatingSystem.DESKTOP_OS.contains(decodedFamily); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/custom/VersionConsultant.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.custom; 5 | 6 | /** 7 | * @author kyon 8 | * 9 | */ 10 | public class VersionConsultant { 11 | 12 | /** 13 | * Indicates how deep versioning will be detected 14 | * if $maxMinorParts is 0 only the major version will be returned 15 | * @var int 16 | */ 17 | protected Integer maxMinorParts = 1; 18 | /** 19 | * Versioning constant used to set max versioning to major version only 20 | * Version examples are: 3, 5, 6, 200, 123, ... 21 | */ 22 | public static final Integer VERSION_TRUNCATION_MAJOR = 0; 23 | /** 24 | * Versioning constant used to set max versioning to minor version 25 | * Version examples are: 3.4, 5.6, 6.234, 0.200, 1.23, ... 26 | */ 27 | public static final Integer VERSION_TRUNCATION_MINOR = 1; 28 | /** 29 | * Versioning constant used to set max versioning to path level 30 | * Version examples are: 3.4.0, 5.6.344, 6.234.2, 0.200.3, 1.2.3, ... 31 | */ 32 | public static final Integer VERSION_TRUNCATION_PATCH = 2; 33 | /** 34 | * Versioning constant used to set versioning to build number 35 | * Version examples are: 3.4.0.12, 5.6.334.0, 6.234.2.3, 0.200.3.1, 1.2.3.0, ... 36 | */ 37 | public static final Integer VERSION_TRUNCATION_BUILD = 3; 38 | /** 39 | * Versioning constant used to set versioning to unlimited (no truncation) 40 | */ 41 | public static final Integer VERSION_TRUNCATION_NONE = null; 42 | 43 | public void setVersionTruncation(Integer type){ 44 | this.maxMinorParts = type; 45 | } 46 | 47 | /** 48 | * @return the maxMinorParts 49 | */ 50 | public Integer getMaxMinorParts() { 51 | return maxMinorParts; 52 | } 53 | 54 | /** 55 | * @param maxMinorParts the maxMinorParts to set 56 | */ 57 | public void setMaxMinorParts(Integer maxMinorParts) { 58 | this.maxMinorParts = maxMinorParts; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/device/Camera.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.device; 5 | 6 | import io.driocc.devicedetector.DetectResult; 7 | 8 | /** 9 | * @author kyon 10 | * 11 | */ 12 | public class Camera extends DeviceParserAbstract { 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | private static final String FIXTURE_FILE = "regexes/device/cameras.yml"; 18 | private static final String PARSER = "camera"; 19 | public Camera(){ 20 | super(PARSER, FIXTURE_FILE); 21 | } 22 | public Camera(String type, String file){ 23 | super(type, file); 24 | } 25 | public DetectResult parse(String userAgent) { 26 | if (this.preMatchOverall(userAgent)) { 27 | return null; 28 | } 29 | return super.parse(userAgent); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/device/CarBrowser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.device; 5 | 6 | import io.driocc.devicedetector.DetectResult; 7 | 8 | /** 9 | * @author kyon 10 | * 11 | */ 12 | public class CarBrowser extends DeviceParserAbstract { 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | private static final String FIXTURE_FILE = "regexes/device/car_browsers.yml"; 18 | private static final String PARSER = "car browser"; 19 | public CarBrowser(){ 20 | super(PARSER, FIXTURE_FILE); 21 | } 22 | public CarBrowser(String type, String file){ 23 | super(type, file); 24 | } 25 | public DetectResult parse(String userAgent) { 26 | if (this.preMatchOverall(userAgent)) { 27 | return null; 28 | } 29 | return super.parse(userAgent); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/device/Console.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.device; 5 | 6 | import io.driocc.devicedetector.DetectResult; 7 | 8 | /** 9 | * @author kyon 10 | * 11 | */ 12 | public class Console extends DeviceParserAbstract { 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | private static final String FIXTURE_FILE = "regexes/device/consoles.yml"; 18 | private static final String PARSER = "console"; 19 | public Console(){ 20 | super(PARSER, FIXTURE_FILE); 21 | } 22 | public Console(String type, String file){ 23 | super(type, file); 24 | } 25 | public DetectResult parse(String userAgent) { 26 | if (this.preMatchOverall(userAgent)) { 27 | return null; 28 | } 29 | return super.parse(userAgent); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/device/DeviceParserAbstract.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.device; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import com.google.common.collect.BiMap; 11 | import com.google.common.collect.HashBiMap; 12 | 13 | import io.driocc.devicedetector.DetectResult; 14 | import io.driocc.devicedetector.ParserAbstract; 15 | 16 | /** 17 | * @author kyon 18 | * 19 | */ 20 | public class DeviceParserAbstract extends ParserAbstract { 21 | /** 22 | * 23 | */ 24 | private static final long serialVersionUID = 1L; 25 | public static final Integer DEVICE_TYPE_DESKTOP = 0; 26 | public static final Integer DEVICE_TYPE_SMARTPHONE = 1; 27 | public static final Integer DEVICE_TYPE_TABLET = 2; 28 | public static final Integer DEVICE_TYPE_FEATURE_PHONE = 3; 29 | public static final Integer DEVICE_TYPE_CONSOLE = 4; 30 | public static final Integer DEVICE_TYPE_TV = 5; // including set top boxes, blu-ray players,... 31 | public static final Integer DEVICE_TYPE_CAR_BROWSER = 6; 32 | public static final Integer DEVICE_TYPE_SMART_DISPLAY = 7; 33 | public static final Integer DEVICE_TYPE_CAMERA = 8; 34 | public static final Integer DEVICE_TYPE_PORTABLE_MEDIA_PAYER = 9; 35 | public static final Integer DEVICE_TYPE_PHABLET = 10; 36 | public static BiMap DEVICE_TYPES = HashBiMap.create(); 37 | public static BiMap DEVICE_BRANDS = HashBiMap.create(); 38 | public static List MOBILES = Arrays.asList(new Integer[] { 39 | DEVICE_TYPE_FEATURE_PHONE, 40 | DEVICE_TYPE_SMARTPHONE, 41 | DEVICE_TYPE_TABLET, 42 | DEVICE_TYPE_PHABLET, 43 | DEVICE_TYPE_CAMERA, 44 | DEVICE_TYPE_PORTABLE_MEDIA_PAYER 45 | }); 46 | public DeviceParserAbstract(String type, String file) { 47 | super(type, file); 48 | } 49 | static{ 50 | DEVICE_TYPES.put("desktop", DEVICE_TYPE_DESKTOP); 51 | DEVICE_TYPES.put("smartphone", DEVICE_TYPE_SMARTPHONE); 52 | DEVICE_TYPES.put("tablet", DEVICE_TYPE_TABLET); 53 | DEVICE_TYPES.put("feature phone", DEVICE_TYPE_FEATURE_PHONE); 54 | DEVICE_TYPES.put("console", DEVICE_TYPE_CONSOLE); 55 | DEVICE_TYPES.put("tv", DEVICE_TYPE_TV); 56 | DEVICE_TYPES.put("car browser", DEVICE_TYPE_CAR_BROWSER); 57 | DEVICE_TYPES.put("smart display", DEVICE_TYPE_SMART_DISPLAY); 58 | DEVICE_TYPES.put("camera", DEVICE_TYPE_CAMERA); 59 | DEVICE_TYPES.put("portable media player", DEVICE_TYPE_PORTABLE_MEDIA_PAYER); 60 | DEVICE_TYPES.put("phablet", DEVICE_TYPE_PHABLET); 61 | DEVICE_BRANDS.put("3Q", "3Q"); 62 | DEVICE_BRANDS.put("AC", "Acer"); 63 | DEVICE_BRANDS.put("AZ", "Ainol"); 64 | DEVICE_BRANDS.put("AI", "Airness"); 65 | DEVICE_BRANDS.put("AL", "Alcatel"); 66 | DEVICE_BRANDS.put("A2", "Allview"); 67 | DEVICE_BRANDS.put("A1", "Altech UEC"); 68 | DEVICE_BRANDS.put("AN", "Arnova"); 69 | DEVICE_BRANDS.put("KN", "Amazon"); 70 | DEVICE_BRANDS.put("AO", "Amoi"); 71 | DEVICE_BRANDS.put("AP", "Apple"); 72 | DEVICE_BRANDS.put("AR", "Archos"); 73 | DEVICE_BRANDS.put("AS", "ARRIS"); 74 | DEVICE_BRANDS.put("AT", "Airties"); 75 | DEVICE_BRANDS.put("AU", "Asus"); 76 | DEVICE_BRANDS.put("AV", "Avvio"); 77 | DEVICE_BRANDS.put("AX", "Audiovox"); 78 | DEVICE_BRANDS.put("AY", "Axxion"); 79 | DEVICE_BRANDS.put("BB", "BBK"); 80 | DEVICE_BRANDS.put("BE", "Becker"); 81 | DEVICE_BRANDS.put("BI", "Bird"); 82 | DEVICE_BRANDS.put("BL", "Beetel"); 83 | DEVICE_BRANDS.put("BM", "Bmobile"); 84 | DEVICE_BRANDS.put("BN", "Barnes & Noble"); 85 | DEVICE_BRANDS.put("BO", "BangOlufsen"); 86 | DEVICE_BRANDS.put("BQ", "BenQ"); 87 | DEVICE_BRANDS.put("BS", "BenQ-Siemens"); 88 | DEVICE_BRANDS.put("BU", "Blu"); 89 | DEVICE_BRANDS.put("BW", "Boway"); 90 | DEVICE_BRANDS.put("BX", "bq"); 91 | DEVICE_BRANDS.put("BV", "Bravis"); 92 | DEVICE_BRANDS.put("BR", "Brondi"); 93 | DEVICE_BRANDS.put("B1", "Bush"); 94 | DEVICE_BRANDS.put("CB", "CUBOT"); 95 | DEVICE_BRANDS.put("CF", "Carrefour"); 96 | DEVICE_BRANDS.put("CP", "Captiva"); 97 | DEVICE_BRANDS.put("CS", "Casio"); 98 | DEVICE_BRANDS.put("CA", "Cat"); 99 | DEVICE_BRANDS.put("CE", "Celkon"); 100 | DEVICE_BRANDS.put("CC", "ConCorde"); 101 | DEVICE_BRANDS.put("C2", "Changhong"); 102 | DEVICE_BRANDS.put("CH", "Cherry Mobile"); 103 | DEVICE_BRANDS.put("CK", "Cricket"); 104 | DEVICE_BRANDS.put("C1", "Crosscall"); 105 | DEVICE_BRANDS.put("CL", "Compal"); 106 | DEVICE_BRANDS.put("CN", "CnM"); 107 | DEVICE_BRANDS.put("CM", "Crius Mea"); 108 | DEVICE_BRANDS.put("CR", "CreNova"); 109 | DEVICE_BRANDS.put("CT", "Capitel"); 110 | DEVICE_BRANDS.put("CQ", "Compaq"); 111 | DEVICE_BRANDS.put("CO", "Coolpad"); 112 | DEVICE_BRANDS.put("CW", "Cowon"); 113 | DEVICE_BRANDS.put("CU", "Cube"); 114 | DEVICE_BRANDS.put("CY", "Coby Kyros"); 115 | DEVICE_BRANDS.put("DA", "Danew"); 116 | DEVICE_BRANDS.put("DT", "Datang"); 117 | DEVICE_BRANDS.put("DE", "Denver"); 118 | DEVICE_BRANDS.put("DS", "Desay"); 119 | DEVICE_BRANDS.put("DB", "Dbtel"); 120 | DEVICE_BRANDS.put("DC", "DoCoMo"); 121 | DEVICE_BRANDS.put("DI", "Dicam"); 122 | DEVICE_BRANDS.put("DL", "Dell"); 123 | DEVICE_BRANDS.put("DN", "DNS"); 124 | DEVICE_BRANDS.put("DM", "DMM"); 125 | DEVICE_BRANDS.put("DO", "Doogee"); 126 | DEVICE_BRANDS.put("DV", "Doov"); 127 | DEVICE_BRANDS.put("DP", "Dopod"); 128 | DEVICE_BRANDS.put("DU", "Dune HD"); 129 | DEVICE_BRANDS.put("EB", "E-Boda"); 130 | DEVICE_BRANDS.put("EA", "EBEST"); 131 | DEVICE_BRANDS.put("EC", "Ericsson"); 132 | DEVICE_BRANDS.put("ES", "ECS"); 133 | DEVICE_BRANDS.put("EI", "Ezio"); 134 | DEVICE_BRANDS.put("EL", "Elephone"); 135 | DEVICE_BRANDS.put("EP", "Easypix"); 136 | DEVICE_BRANDS.put("E1", "Energy Sistem"); 137 | DEVICE_BRANDS.put("ER", "Ericy"); 138 | DEVICE_BRANDS.put("EN", "Eton"); 139 | DEVICE_BRANDS.put("ET", "eTouch"); 140 | DEVICE_BRANDS.put("EV", "Evertek"); 141 | DEVICE_BRANDS.put("EX", "Explay"); 142 | DEVICE_BRANDS.put("EZ", "Ezze"); 143 | DEVICE_BRANDS.put("FA", "Fairphone"); 144 | DEVICE_BRANDS.put("FL", "Fly"); 145 | DEVICE_BRANDS.put("FO", "Foxconn"); 146 | DEVICE_BRANDS.put("FU", "Fujitsu"); 147 | DEVICE_BRANDS.put("GM", "Garmin-Asus"); 148 | DEVICE_BRANDS.put("GA", "Gateway"); 149 | DEVICE_BRANDS.put("GD", "Gemini"); 150 | DEVICE_BRANDS.put("GI", "Gionee"); 151 | DEVICE_BRANDS.put("GG", "Gigabyte"); 152 | DEVICE_BRANDS.put("GS", "Gigaset"); 153 | DEVICE_BRANDS.put("GC", "GOCLEVER"); 154 | DEVICE_BRANDS.put("GL", "Goly"); 155 | DEVICE_BRANDS.put("GO", "Google"); 156 | DEVICE_BRANDS.put("GR", "Gradiente"); 157 | DEVICE_BRANDS.put("GU", "Grundig"); 158 | DEVICE_BRANDS.put("HA", "Haier"); 159 | DEVICE_BRANDS.put("HS", "Hasee"); 160 | DEVICE_BRANDS.put("HI", "Hisense"); 161 | DEVICE_BRANDS.put("HL", "Hi-Level"); 162 | DEVICE_BRANDS.put("HM", "Homtom"); 163 | DEVICE_BRANDS.put("HO", "Hosin"); 164 | DEVICE_BRANDS.put("HP", "HP"); 165 | DEVICE_BRANDS.put("HT", "HTC"); 166 | DEVICE_BRANDS.put("HU", "Huawei"); 167 | DEVICE_BRANDS.put("HX", "Humax"); 168 | DEVICE_BRANDS.put("HY", "Hyrican"); 169 | DEVICE_BRANDS.put("HN", "Hyundai"); 170 | DEVICE_BRANDS.put("IA", "Ikea"); 171 | DEVICE_BRANDS.put("IB", "iBall"); 172 | DEVICE_BRANDS.put("IJ", "i-Joy"); 173 | DEVICE_BRANDS.put("IY", "iBerry"); 174 | DEVICE_BRANDS.put("IK", "iKoMo"); 175 | DEVICE_BRANDS.put("IM", "i-mate"); 176 | DEVICE_BRANDS.put("I1", "iOcean"); 177 | DEVICE_BRANDS.put("IW", "iNew"); 178 | DEVICE_BRANDS.put("IF", "Infinix"); 179 | DEVICE_BRANDS.put("IN", "Innostream"); 180 | DEVICE_BRANDS.put("II", "Inkti"); 181 | DEVICE_BRANDS.put("IX", "Intex"); 182 | DEVICE_BRANDS.put("IO", "i-mobile"); 183 | DEVICE_BRANDS.put("IQ", "INQ"); 184 | DEVICE_BRANDS.put("IT", "Intek"); 185 | DEVICE_BRANDS.put("IV", "Inverto"); 186 | DEVICE_BRANDS.put("IZ", "iTel"); 187 | DEVICE_BRANDS.put("JI", "Jiayu"); 188 | DEVICE_BRANDS.put("JO", "Jolla"); 189 | DEVICE_BRANDS.put("KA", "Karbonn"); 190 | DEVICE_BRANDS.put("KD", "KDDI"); 191 | DEVICE_BRANDS.put("K1", "Kiano"); 192 | DEVICE_BRANDS.put("KI", "Kingsun"); 193 | DEVICE_BRANDS.put("KO", "Konka"); 194 | DEVICE_BRANDS.put("KM", "Komu"); 195 | DEVICE_BRANDS.put("KB", "Koobee"); 196 | DEVICE_BRANDS.put("KT", "K-Touch"); 197 | DEVICE_BRANDS.put("KH", "KT-Tech"); 198 | DEVICE_BRANDS.put("KP", "KOPO"); 199 | DEVICE_BRANDS.put("KR", "Koridy"); 200 | DEVICE_BRANDS.put("KU", "Kumai"); 201 | DEVICE_BRANDS.put("KY", "Kyocera"); 202 | DEVICE_BRANDS.put("KZ", "Kazam"); 203 | DEVICE_BRANDS.put("LV", "Lava"); 204 | DEVICE_BRANDS.put("LA", "Lanix"); 205 | DEVICE_BRANDS.put("LC", "LCT"); 206 | DEVICE_BRANDS.put("L1", "LeEco"); 207 | DEVICE_BRANDS.put("LE", "Lenovo"); 208 | DEVICE_BRANDS.put("LN", "Lenco"); 209 | DEVICE_BRANDS.put("LP", "Le Pan"); 210 | DEVICE_BRANDS.put("LG", "LG"); 211 | DEVICE_BRANDS.put("LI", "Lingwin"); 212 | DEVICE_BRANDS.put("LO", "Loewe"); 213 | DEVICE_BRANDS.put("LM", "Logicom"); 214 | DEVICE_BRANDS.put("LX", "Lexibook"); 215 | DEVICE_BRANDS.put("MJ", "Majestic"); 216 | DEVICE_BRANDS.put("MA", "Manta Multimedia"); 217 | DEVICE_BRANDS.put("MB", "Mobistel"); 218 | DEVICE_BRANDS.put("M3", "Mecer"); 219 | DEVICE_BRANDS.put("MD", "Medion"); 220 | DEVICE_BRANDS.put("M2", "MEEG"); 221 | DEVICE_BRANDS.put("M1", "Meizu"); 222 | DEVICE_BRANDS.put("ME", "Metz"); 223 | DEVICE_BRANDS.put("MX", "MEU"); 224 | DEVICE_BRANDS.put("MI", "MicroMax"); 225 | DEVICE_BRANDS.put("M5", "MIXC"); 226 | DEVICE_BRANDS.put("MC", "Mediacom"); 227 | DEVICE_BRANDS.put("MK", "MediaTek"); 228 | DEVICE_BRANDS.put("MO", "Mio"); 229 | DEVICE_BRANDS.put("MM", "Mpman"); 230 | DEVICE_BRANDS.put("M4", "Modecom"); 231 | DEVICE_BRANDS.put("MF", "Mofut"); 232 | DEVICE_BRANDS.put("MR", "Motorola"); 233 | DEVICE_BRANDS.put("MS", "Microsoft"); 234 | DEVICE_BRANDS.put("MZ", "MSI"); 235 | DEVICE_BRANDS.put("MU", "Memup"); 236 | DEVICE_BRANDS.put("MT", "Mitsubishi"); 237 | DEVICE_BRANDS.put("ML", "MLLED"); 238 | DEVICE_BRANDS.put("MQ", "M.T.T."); 239 | DEVICE_BRANDS.put("MY", "MyPhone"); 240 | DEVICE_BRANDS.put("NE", "NEC"); 241 | DEVICE_BRANDS.put("NA", "Netgear"); 242 | DEVICE_BRANDS.put("NG", "NGM"); 243 | DEVICE_BRANDS.put("NO", "Nous"); 244 | DEVICE_BRANDS.put("NI", "Nintendo"); 245 | DEVICE_BRANDS.put("N1", "Noain"); 246 | DEVICE_BRANDS.put("NK", "Nokia"); 247 | DEVICE_BRANDS.put("NM", "Nomi"); 248 | DEVICE_BRANDS.put("NN", "Nikon"); 249 | DEVICE_BRANDS.put("NW", "Newgen"); 250 | DEVICE_BRANDS.put("NX", "Nexian"); 251 | DEVICE_BRANDS.put("NT", "NextBook"); 252 | DEVICE_BRANDS.put("OD", "Onda"); 253 | DEVICE_BRANDS.put("ON", "OnePlus"); 254 | DEVICE_BRANDS.put("OP", "OPPO"); 255 | DEVICE_BRANDS.put("OR", "Orange"); 256 | DEVICE_BRANDS.put("OT", "O2"); 257 | DEVICE_BRANDS.put("OK", "Ouki"); 258 | DEVICE_BRANDS.put("OU", "OUYA"); 259 | DEVICE_BRANDS.put("OO", "Opsson"); 260 | DEVICE_BRANDS.put("PA", "Panasonic"); 261 | DEVICE_BRANDS.put("PE", "PEAQ"); 262 | DEVICE_BRANDS.put("PG", "Pentagram"); 263 | DEVICE_BRANDS.put("PH", "Philips"); 264 | DEVICE_BRANDS.put("PI", "Pioneer"); 265 | DEVICE_BRANDS.put("PL", "Polaroid"); 266 | DEVICE_BRANDS.put("PM", "Palm"); 267 | DEVICE_BRANDS.put("PO", "phoneOne"); 268 | DEVICE_BRANDS.put("PT", "Pantech"); 269 | DEVICE_BRANDS.put("PY", "Ployer"); 270 | DEVICE_BRANDS.put("PV", "Point of View"); 271 | DEVICE_BRANDS.put("PP", "PolyPad"); 272 | DEVICE_BRANDS.put("P2", "Pomp"); 273 | DEVICE_BRANDS.put("PS", "Positivo"); 274 | DEVICE_BRANDS.put("PR", "Prestigio"); 275 | DEVICE_BRANDS.put("P1", "ProScan"); 276 | DEVICE_BRANDS.put("PU", "PULID"); 277 | DEVICE_BRANDS.put("QI", "Qilive"); 278 | DEVICE_BRANDS.put("QT", "Qtek"); 279 | DEVICE_BRANDS.put("QM", "QMobile"); 280 | DEVICE_BRANDS.put("QU", "Quechua"); 281 | DEVICE_BRANDS.put("OV", "Overmax"); 282 | DEVICE_BRANDS.put("OY", "Oysters"); 283 | DEVICE_BRANDS.put("RA", "Ramos"); 284 | DEVICE_BRANDS.put("RC", "RCA Tablets"); 285 | DEVICE_BRANDS.put("RB", "Readboy"); 286 | DEVICE_BRANDS.put("RI", "Rikomagic"); 287 | DEVICE_BRANDS.put("RM", "RIM"); 288 | DEVICE_BRANDS.put("RK", "Roku"); 289 | DEVICE_BRANDS.put("RO", "Rover"); 290 | DEVICE_BRANDS.put("SA", "Samsung"); 291 | DEVICE_BRANDS.put("SD", "Sega"); 292 | DEVICE_BRANDS.put("SE", "Sony Ericsson"); 293 | DEVICE_BRANDS.put("S1", "Sencor"); 294 | DEVICE_BRANDS.put("SF", "Softbank"); 295 | DEVICE_BRANDS.put("SX", "SFR"); 296 | DEVICE_BRANDS.put("SG", "Sagem"); 297 | DEVICE_BRANDS.put("SH", "Sharp"); 298 | DEVICE_BRANDS.put("SI", "Siemens"); 299 | DEVICE_BRANDS.put("SN", "Sendo"); 300 | DEVICE_BRANDS.put("S6", "Senseit"); 301 | DEVICE_BRANDS.put("SK", "Skyworth"); 302 | DEVICE_BRANDS.put("SC", "Smartfren"); 303 | DEVICE_BRANDS.put("SO", "Sony"); 304 | DEVICE_BRANDS.put("SP", "Spice"); 305 | DEVICE_BRANDS.put("SU", "SuperSonic"); 306 | DEVICE_BRANDS.put("S5", "Supra"); 307 | DEVICE_BRANDS.put("SV", "Selevision"); 308 | DEVICE_BRANDS.put("SY", "Sanyo"); 309 | DEVICE_BRANDS.put("SM", "Symphony"); 310 | DEVICE_BRANDS.put("SR", "Smart"); 311 | DEVICE_BRANDS.put("S7", "Smartisan"); 312 | DEVICE_BRANDS.put("S4", "Star"); 313 | DEVICE_BRANDS.put("ST", "Storex"); 314 | DEVICE_BRANDS.put("S2", "Stonex"); 315 | DEVICE_BRANDS.put("S3", "SunVan"); 316 | DEVICE_BRANDS.put("SZ", "Sumvision"); 317 | DEVICE_BRANDS.put("TA", "Tesla"); 318 | DEVICE_BRANDS.put("T5", "TB Touch"); 319 | DEVICE_BRANDS.put("TC", "TCL"); 320 | DEVICE_BRANDS.put("TE", "Telit"); 321 | DEVICE_BRANDS.put("T4", "ThL"); 322 | DEVICE_BRANDS.put("TH", "TiPhone"); 323 | DEVICE_BRANDS.put("TB", "Tecno Mobile"); 324 | DEVICE_BRANDS.put("TD", "Tesco"); 325 | DEVICE_BRANDS.put("TI", "TIANYU"); 326 | DEVICE_BRANDS.put("TL", "Telefunken"); 327 | DEVICE_BRANDS.put("T2", "Telenor"); 328 | DEVICE_BRANDS.put("TM", "T-Mobile"); 329 | DEVICE_BRANDS.put("TN", "Thomson"); 330 | DEVICE_BRANDS.put("T1", "Tolino"); 331 | DEVICE_BRANDS.put("TO", "Toplux"); 332 | DEVICE_BRANDS.put("TS", "Toshiba"); 333 | DEVICE_BRANDS.put("TT", "TechnoTrend"); 334 | DEVICE_BRANDS.put("T3", "Trevi"); 335 | DEVICE_BRANDS.put("TU", "Tunisie Telecom"); 336 | DEVICE_BRANDS.put("TR", "Turbo-X"); 337 | DEVICE_BRANDS.put("TV", "TVC"); 338 | DEVICE_BRANDS.put("TX", "TechniSat"); 339 | DEVICE_BRANDS.put("TZ", "teXet"); 340 | DEVICE_BRANDS.put("UN", "Unowhy"); 341 | DEVICE_BRANDS.put("US", "Uniscope"); 342 | DEVICE_BRANDS.put("UT", "UTStarcom"); 343 | DEVICE_BRANDS.put("VA", "Vastking"); 344 | DEVICE_BRANDS.put("VD", "Videocon"); 345 | DEVICE_BRANDS.put("VE", "Vertu"); 346 | DEVICE_BRANDS.put("VI", "Vitelcom"); 347 | DEVICE_BRANDS.put("VK", "VK Mobile"); 348 | DEVICE_BRANDS.put("VS", "ViewSonic"); 349 | DEVICE_BRANDS.put("VT", "Vestel"); 350 | DEVICE_BRANDS.put("VV", "Vivo"); 351 | DEVICE_BRANDS.put("V1", "Voto"); 352 | DEVICE_BRANDS.put("VO", "Voxtel"); 353 | DEVICE_BRANDS.put("VF", "Vodafone"); 354 | DEVICE_BRANDS.put("VZ", "Vizio"); 355 | DEVICE_BRANDS.put("VW", "Videoweb"); 356 | DEVICE_BRANDS.put("WA", "Walton"); 357 | DEVICE_BRANDS.put("WE", "WellcoM"); 358 | DEVICE_BRANDS.put("WY", "Wexler"); 359 | DEVICE_BRANDS.put("WI", "Wiko"); 360 | DEVICE_BRANDS.put("WL", "Wolder"); 361 | DEVICE_BRANDS.put("WO", "Wonu"); 362 | DEVICE_BRANDS.put("WX", "Woxter"); 363 | DEVICE_BRANDS.put("XI", "Xiaomi"); 364 | DEVICE_BRANDS.put("XO", "Xolo"); 365 | DEVICE_BRANDS.put("YA", "Yarvik"); 366 | DEVICE_BRANDS.put("YU", "Yuandao"); 367 | DEVICE_BRANDS.put("YS", "Yusun"); 368 | DEVICE_BRANDS.put("YT", "Ytone"); 369 | DEVICE_BRANDS.put("ZE", "Zeemi"); 370 | DEVICE_BRANDS.put("ZO", "Zonda"); 371 | DEVICE_BRANDS.put("ZP", "Zopo"); 372 | DEVICE_BRANDS.put("ZT", "ZTE"); 373 | DEVICE_BRANDS.put("WB", "Web TV"); 374 | DEVICE_BRANDS.put("XX", "Unknown"); 375 | } 376 | /** 377 | * @param userAgent 378 | * @return 379 | */ 380 | public DetectResult parse(String userAgent) { 381 | //yaml of device default to a single map 382 | Map regexes = this.getRegexes().get(0); 383 | Map thisRegex = null; 384 | List matches = null; 385 | String brand = null; 386 | String brandId = null; 387 | for (Map.Entry e : regexes.entrySet()) { 388 | Map regex = (Map)e.getValue(); 389 | matches = this.matchUserAgent(userAgent, regex.get("regex").toString()); 390 | if (matches!=null && matches.size()>0) { 391 | thisRegex = regex; 392 | brand = e.getKey(); 393 | break; 394 | } 395 | } 396 | if (matches==null || matches.size()<1) { 397 | return null; 398 | } 399 | if (!"Unknown".equals(brand)) { 400 | brandId = DEVICE_BRANDS.inverse().get(brand); 401 | } 402 | Object regexDevice = thisRegex.get("device"); 403 | String device = null; 404 | Integer deviceType = null; 405 | if(regexDevice!=null && DEVICE_TYPES.containsKey(regexDevice.toString())) { 406 | device = regexDevice.toString(); 407 | deviceType = DEVICE_TYPES.get(device); 408 | } 409 | String model = ""; 410 | if (thisRegex.get("model")!=null) { 411 | String regexModel = thisRegex.get("model").toString(); 412 | model = this.buildModel(regexModel, matches); 413 | } 414 | if (thisRegex.get("models")!=null) { 415 | List> regexModels = (List>)thisRegex.get("models"); 416 | List modelMatches = null; 417 | Map thisModelRegex = null; 418 | for(Map modelRegex : regexModels) { 419 | modelMatches = this.matchUserAgent(userAgent, modelRegex.get("regex").toString()); 420 | if (modelMatches!=null && modelMatches.size()>0) { 421 | thisModelRegex = modelRegex; 422 | break; 423 | } 424 | } 425 | if (modelMatches!=null && thisModelRegex!=null) { 426 | model = this.buildModel(thisModelRegex.get("model").toString(), modelMatches); 427 | if (thisModelRegex.get("brand")!=null && DEVICE_BRANDS.containsValue(thisModelRegex.get("brand").toString())) { 428 | brand = thisModelRegex.get("brand").toString(); 429 | brandId = DEVICE_BRANDS.inverse().get(brand); 430 | } 431 | if (thisModelRegex.get("device")!=null && DEVICE_TYPES.containsKey(thisModelRegex.get("device"))) { 432 | deviceType = DEVICE_TYPES.get(thisModelRegex.get("device")); 433 | } 434 | 435 | } 436 | } 437 | DetectResult ret = new DetectResult(); 438 | ret.setDevice(device); 439 | ret.setDeviceType(deviceType); 440 | ret.setBrand(brand); 441 | ret.setBrandId(brandId); 442 | ret.setModel(model); 443 | return ret; 444 | } 445 | /** 446 | * @param regexModel 447 | * @param matches 448 | * @return 449 | */ 450 | private String buildModel(String regexModel, List matches) { 451 | String model = null; 452 | model = this.buildByMatch(regexModel, matches); 453 | model = model.replaceAll("_", ""); 454 | model = model.replaceAll("/ TD$/i", ""); 455 | if ("Build".equals(model)) { 456 | return null; 457 | } 458 | return model; 459 | } 460 | 461 | public static boolean isMobile(Integer deviceType){ 462 | if(MOBILES.contains(deviceType)) { 463 | return true; 464 | } 465 | return false; 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/device/HbbTv.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.device; 5 | 6 | import io.driocc.devicedetector.DetectResult; 7 | 8 | /** 9 | * @author kyon 10 | * 11 | */ 12 | public class HbbTv extends DeviceParserAbstract { 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | private static final String FIXTURE_FILE = "regexes/device/televisions.yml"; 18 | private static final String PARSER = "tv"; 19 | public HbbTv(){ 20 | super(PARSER, FIXTURE_FILE); 21 | } 22 | public HbbTv(String type, String file){ 23 | super(type, file); 24 | } 25 | public DetectResult parse(String userAgent) { 26 | if (this.preMatchOverall(userAgent)) { 27 | return null; 28 | } 29 | return super.parse(userAgent); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/device/Mobile.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.device; 5 | 6 | /** 7 | * @author kyon 8 | * 9 | */ 10 | public class Mobile extends DeviceParserAbstract { 11 | /** 12 | * 13 | */ 14 | private static final long serialVersionUID = 1L; 15 | private static final String FIXTURE_FILE = "regexes/device/mobiles.yml"; 16 | private static final String PARSER = "mobile"; 17 | public Mobile(){ 18 | super(PARSER, FIXTURE_FILE); 19 | } 20 | public Mobile(String type, String file){ 21 | super(type, file); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/device/PortableMediaPlayer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector.device; 5 | 6 | import io.driocc.devicedetector.DetectResult; 7 | 8 | /** 9 | * @author kyon 10 | * 11 | */ 12 | public class PortableMediaPlayer extends DeviceParserAbstract { 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | private static final String FIXTURE_FILE = "regexes/device/portable_media_player.yml"; 18 | private static final String PARSER = "PortableMediaPlayer"; 19 | public PortableMediaPlayer(){ 20 | super(PARSER, FIXTURE_FILE); 21 | } 22 | public PortableMediaPlayer(String type, String file){ 23 | super(type, file); 24 | } 25 | 26 | public DetectResult parse(String userAgent) { 27 | if (this.preMatchOverall(userAgent)) { 28 | return null; 29 | } 30 | return super.parse(userAgent); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/driocc/devicedetector/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package io.driocc.devicedetector.utils; 2 | 3 | public class Utils { 4 | 5 | public static boolean isNumeric(final CharSequence cs) { 6 | if (isEmpty(cs)) { 7 | return false; 8 | } 9 | final int sz = cs.length(); 10 | for (int i = 0; i < sz; i++) { 11 | if (!Character.isDigit(cs.charAt(i))) { 12 | return false; 13 | } 14 | } 15 | return true; 16 | } 17 | 18 | public static boolean isEmpty(final CharSequence cs) { 19 | return cs == null || cs.length() == 0; 20 | } 21 | 22 | /** 23 | * @param browserVersion 24 | * @param version 25 | * @return 26 | */ 27 | public static int versionCompare(String leftVersion, String rightVersion) { 28 | if(Utils.isEmpty(leftVersion))return -1; 29 | String[] vals1 = leftVersion.split("\\."); 30 | String[] vals2 = rightVersion.split("\\."); 31 | int i = 0; 32 | // set index to first non-equal ordinal or length of shortest version string 33 | while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) { 34 | i++; 35 | } 36 | // compare first non-equal ordinal number 37 | if (i < vals1.length && i < vals2.length) { 38 | int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); 39 | return Integer.signum(diff); 40 | } 41 | // the strings are equal or one string is a substring of the other 42 | // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" 43 | return Integer.signum(vals1.length - vals2.length); 44 | } 45 | 46 | public static int countChar(String s, char c) { 47 | int counter = 0; 48 | for( int i=0; i>> CACHE = new ConcurrentHashMap<>(); 22 | private YamlParser() {} 23 | @SuppressWarnings("unchecked") 24 | public static List> get(String yamlPath) { 25 | List> regexes = null; 26 | try { 27 | if(CACHE.containsKey(yamlPath)) { 28 | regexes = CACHE.get(yamlPath); 29 | } else { 30 | Yaml yaml = new Yaml(); 31 | InputStream is = YamlParser.class.getClassLoader().getResourceAsStream(yamlPath); 32 | //it is a LinkedHashMap 33 | Object obj = yaml.load(is); 34 | if(obj instanceof List){ 35 | regexes = (List>)obj; 36 | }else if(obj instanceof Map){ 37 | regexes = new LinkedList<>(); 38 | Map e = (Map)obj; 39 | regexes.add(e); 40 | } 41 | CACHE.put(yamlPath, regexes); 42 | } 43 | }catch(Exception e) { 44 | e.printStackTrace(); 45 | } 46 | return regexes; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/resources/regexes/bots.yml: -------------------------------------------------------------------------------- 1 | ############### 2 | # Device Detector - The Universal Device Detection library for parsing User Agents 3 | # 4 | # @link http://piwik.org 5 | # @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later 6 | ############### 7 | 8 | - regex: '360Spider(-Image|-Video)?' 9 | name: '360Spider' 10 | category: 'Search bot' 11 | url: 'http://www.so.com/help/help_3_2.html' 12 | producer: 13 | name: 'Online Media Group, Inc.' 14 | url: '' 15 | 16 | - regex: 'Aboundex' 17 | name: 'Aboundexbot' 18 | category: 'Search bot' 19 | url: 'http://www.aboundex.com/crawler/' 20 | producer: 21 | name: 'Aboundex.com' 22 | url: 'http://www.aboundex.com' 23 | 24 | - regex: 'AcoonBot' 25 | name: 'Acoon' 26 | category: 'Search bot' 27 | url: 'http://www.acoon.de/robot.asp' 28 | producer: 29 | name: 'Acoon GmbH' 30 | url: 'http://www.acoon.de' 31 | 32 | - regex: 'AddThis\.com' 33 | name: 'AddThis.com' 34 | category: 'Social Media Agent' 35 | url: '' 36 | producer: 37 | name: 'Clearspring Technologies, Inc.' 38 | url: 'http://www.clearspring.com' 39 | 40 | - regex: 'AhrefsBot' 41 | name: 'aHrefs Bot' 42 | category: 'Crawler' 43 | url: 'http://ahrefs.com/robot' 44 | producer: 45 | name: 'Ahrefs Pte Ltd' 46 | url: 'http://ahrefs.com/robot' 47 | 48 | - regex: 'ia_archiver|alexabot|verifybot' 49 | name: 'Alexa Crawler' 50 | category: 'Search bot' 51 | url: 'https://alexa.zendesk.com/hc/en-us/sections/200100794-Crawlers' 52 | producer: 53 | name: 'Alexa Internet' 54 | url: 'http://www.alexa.com' 55 | 56 | - regex: 'AmorankSpider' 57 | name: 'Amorank Spider' 58 | category: 'Crawler' 59 | url: 'http://amorank.com/webcrawler.html' 60 | producer: 61 | name: 'Amorank' 62 | url: 'http://www.amorank.com' 63 | 64 | - regex: 'ApacheBench' 65 | name: 'ApacheBench' 66 | category: 'Benchmark' 67 | url: 'https://httpd.apache.org/docs/2.4/programs/ab.html' 68 | producer: 69 | name: 'The Apache Software Foundation' 70 | url: 'http://www.apache.org/foundation/' 71 | 72 | - regex: 'Applebot' 73 | name: 'Applebot' 74 | category: 'Crawler' 75 | url: 'http://www.apple.com/go/applebot' 76 | producer: 77 | name: 'Apple Inc' 78 | url: 'http://www.apple.com' 79 | 80 | - regex: 'Castro 2, Episode Duration Lookup' 81 | name: 'Castro 2' 82 | category: 'Service Agent' 83 | url: 'http://supertop.co/castro/' 84 | producer: 85 | name: 'Supertop' 86 | url: 'http://supertop.co' 87 | 88 | - regex: 'Curious George' 89 | name: 'Analytics SEO Crawler' 90 | category: 'Crawler' 91 | url: 'http://www.analyticsseo.com/crawler' 92 | producer: 93 | name: 'Analytics SEO' 94 | url: 'http://www.analyticsseo.com' 95 | 96 | - regex: 'archive\.org_bot|special_archiver' 97 | name: 'archive.org bot' 98 | category: 'Crawler' 99 | url: 'http://www.archive.org/details/archive.org_bot' 100 | producer: 101 | name: 'The Internet Archive' 102 | url: 'http://www.archive.org' 103 | 104 | - regex: 'Ask Jeeves/Teoma' 105 | name: 'Ask Jeeves' 106 | category: 'Search bot' 107 | url: '' 108 | producer: 109 | name: 'Ask Jeeves Inc.' 110 | url: 'http://www.ask.com' 111 | 112 | - regex: 'Backlink-Check\.de' 113 | name: 'Backlink-Check.de' 114 | category: 'Crawler' 115 | url: 'http://www.backlink-check.de/bot.html' 116 | producer: 117 | name: 'Mediagreen Medienservice' 118 | url: 'http://www.backlink-check.de' 119 | 120 | - regex: 'BacklinkCrawler' 121 | name: 'BacklinkCrawler' 122 | category: 'Crawler' 123 | url: 'http://www.backlinktest.com/crawler.html' 124 | producer: 125 | name: '2.0Promotion GbR' 126 | url: 'http://www.backlinktest.com' 127 | 128 | - regex: 'baiduspider(-image)?|baidu Transcoder|baidu.*spider' 129 | name: 'Baidu Spider' 130 | category: 'Search bot' 131 | url: 'http://www.baidu.com/search/spider.htm' 132 | producer: 133 | name: 'Baidu' 134 | url: 'http://www.baidu.com' 135 | 136 | - regex: 'BazQux' 137 | name: 'BazQux Reader' 138 | url: 'https://bazqux.com/fetcher' 139 | category: 'Feed Fetcher' 140 | producer: 141 | name: '' 142 | url: '' 143 | 144 | - regex: 'MSNBot|msrbot|bingbot|BingPreview|msnbot-(UDiscovery|NewsBlogs)|adidxbot' 145 | name: 'BingBot' 146 | category: 'Search bot' 147 | url: 'http://search.msn.com/msnbot.htmn' 148 | producer: 149 | name: 'Microsoft Corporation' 150 | url: 'http://www.microsoft.com' 151 | 152 | - regex: 'Blekkobot' 153 | name: 'Blekkobot' 154 | category: 'Search bot' 155 | url: 'http://blekko.com/about/blekkobot' 156 | producer: 157 | name: 'Blekko' 158 | url: 'http://blekko.com' 159 | 160 | - regex: 'BLEXBot(Test)?' 161 | name: 'BLEXBot Crawler' 162 | category: 'Crawler' 163 | url: 'http://webmeup-crawler.com' 164 | producer: 165 | name: 'WebMeUp' 166 | url: 'http://webmeup.com' 167 | 168 | - regex: 'Bloglovin' 169 | name: 'Bloglovin' 170 | url: 'http://www.bloglovin.com' 171 | category: 'Feed Fetcher' 172 | producer: 173 | name: '' 174 | url: '' 175 | 176 | - regex: 'Blogtrottr' 177 | name: 'Blogtrottr' 178 | url: '' 179 | category: 'Feed Fetcher' 180 | producer: 181 | name: 'Blogtrottr Ltd' 182 | url: 'https://blogtrottr.com/' 183 | 184 | - regex: 'BountiiBot' 185 | name: 'Bountii Bot' 186 | category: 'Search bot' 187 | url: 'http://bountii.com/contact.php' 188 | producer: 189 | name: 'Bountii Inc.' 190 | url: 'http://bountii.com' 191 | 192 | - regex: 'Browsershots' 193 | name: 'Browsershots' 194 | category: 'Service Agent' 195 | url: 'http://browsershots.org/faq' 196 | producer: 197 | name: 'Browsershots.org' 198 | url: 'http://browsershots.org' 199 | 200 | - regex: 'BUbiNG' 201 | name: 'BUbiNG' 202 | category: 'Crawler' 203 | url: 'http://law.di.unimi.it/BUbiNG.html' 204 | producer: 205 | name: 'The Laboratory for Web Algorithmics (LAW)' 206 | url: 'http://law.di.unimi.it/software.php#buging' 207 | 208 | - regex: '(? end of file 150 | 151 | ########## 152 | # webOS 153 | ########## 154 | - regex: '(?:webOS|Palm webOS)(?:/(\d+[\.\d]+))?' 155 | name: 'webOS' 156 | version: '$1' 157 | 158 | - regex: '(?:PalmOS|Palm OS)(?:[/ ](\d+[\.\d]+))?|Palm' 159 | name: 'palmOS' 160 | version: '$1' 161 | 162 | - regex: 'Xiino(?:.*v\. (\d+[\.\d]+))?' # palmOS only browser 163 | name: 'palmOS' 164 | version: '$1' 165 | 166 | 167 | - regex: 'MorphOS(?:[ /](\d+[\.\d]+))?' 168 | name: 'MorphOS' 169 | version: '$1' 170 | 171 | 172 | ########## 173 | # Windows 174 | ########## 175 | - regex: 'CYGWIN_NT-10.0|Windows NT 10.0|Windows 10' 176 | name: 'Windows' 177 | version: '10' 178 | 179 | - regex: 'CYGWIN_NT-6.4|Windows NT 6.4|Windows 10' 180 | name: 'Windows' 181 | version: '10' 182 | 183 | - regex: 'CYGWIN_NT-6.3|Windows NT 6.3|Windows 8.1' 184 | name: 'Windows' 185 | version: '8.1' 186 | 187 | 188 | - regex: 'CYGWIN_NT-6.2|Windows NT 6.2|Windows 8' 189 | name: 'Windows' 190 | version: '8' 191 | 192 | 193 | - regex: 'CYGWIN_NT-6.1|Windows NT 6.1|Windows 7' 194 | name: 'Windows' 195 | version: '7' 196 | 197 | 198 | - regex: 'CYGWIN_NT-6.0|Windows NT 6.0|Windows Vista' 199 | name: 'Windows' 200 | version: 'Vista' 201 | 202 | 203 | - regex: 'CYGWIN_NT-5.2|Windows NT 5.2|Windows Server 2003 / XP x64' 204 | name: 'Windows' 205 | version: 'Server 2003' 206 | 207 | 208 | - regex: 'CYGWIN_NT-5.1|Windows NT 5.1|Windows XP' 209 | name: 'Windows' 210 | version: 'XP' 211 | 212 | 213 | - regex: 'CYGWIN_NT-5.0|Windows NT 5.0|Windows 2000' 214 | name: 'Windows' 215 | version: '2000' 216 | 217 | 218 | - regex: 'CYGWIN_NT-4.0|Windows NT 4.0|WinNT|Windows NT' 219 | name: 'Windows' 220 | version: 'NT' 221 | 222 | 223 | - regex: 'CYGWIN_ME-4.90|Win 9x 4.90|Windows ME' 224 | name: 'Windows' 225 | version: 'ME' 226 | 227 | 228 | - regex: 'CYGWIN_98-4.10|Win98|Windows 98' 229 | name: 'Windows' 230 | version: '98' 231 | 232 | 233 | - regex: 'CYGWIN_95-4.0|Win32|Win95|Windows 95|Windows_95' 234 | name: 'Windows' 235 | version: '95' 236 | 237 | 238 | - regex: 'Windows 3.1' 239 | name: 'Windows' 240 | version: '3.1' 241 | 242 | 243 | - regex: 'Windows' 244 | name: 'Windows' 245 | version: '' 246 | 247 | 248 | 249 | ########## 250 | # iOS 251 | ########## 252 | - regex: 'CFNetwork/758\.4\.3' 253 | name: 'iOS' 254 | version: '9.3.2' 255 | 256 | - regex: 'CFNetwork/758\.3\.15' 257 | name: 'iOS' 258 | version: '9.3' 259 | 260 | - regex: 'CFNetwork/758\.2\.[78]' 261 | name: 'iOS' 262 | version: '9.2' 263 | 264 | - regex: 'CFNetwork/758\.1\.6' 265 | name: 'iOS' 266 | version: '9.1' 267 | 268 | - regex: 'CFNetwork/758\.0\.2' 269 | name: 'iOS' 270 | version: '9.0' 271 | 272 | - regex: 'CFNetwork/711\.5\.6' 273 | name: 'iOS' 274 | version: '8.4.1' 275 | 276 | - regex: 'CFNetwork/711\.4\.6' 277 | name: 'iOS' 278 | version: '8.4' 279 | 280 | - regex: 'CFNetwork/711\.3\.18' 281 | name: 'iOS' 282 | version: '8.3' 283 | 284 | - regex: 'CFNetwork/711\.2\.23' 285 | name: 'iOS' 286 | version: '8.2' 287 | 288 | - regex: 'CFNetwork/711\.1\.1[26]' 289 | name: 'iOS' 290 | version: '8.1' 291 | 292 | - regex: 'CFNetwork/711\.0\.6' 293 | name: 'iOS' 294 | version: '8.0' 295 | 296 | - regex: 'CFNetwork/672\.1' 297 | name: 'iOS' 298 | version: '7.1' 299 | 300 | - regex: 'CFNetwork/672\.0' 301 | name: 'iOS' 302 | version: '7.0' 303 | 304 | - regex: 'CFNetwork/609\.1' 305 | name: 'iOS' 306 | version: '6.1' 307 | 308 | - regex: 'CFNetwork/60[29]' 309 | name: 'iOS' 310 | version: '6.0' 311 | 312 | - regex: 'CFNetwork/548\.1' 313 | name: 'iOS' 314 | version: '5.1' 315 | 316 | - regex: 'CFNetwork/548\.0' 317 | name: 'iOS' 318 | version: '5.0' 319 | 320 | - regex: 'CFNetwork/485\.13' 321 | name: 'iOS' 322 | version: '4.3' 323 | 324 | - regex: 'CFNetwork/485\.12' 325 | name: 'iOS' 326 | version: '4.2' 327 | 328 | - regex: 'CFNetwork/485\.10' 329 | name: 'iOS' 330 | version: '4.1' 331 | 332 | - regex: 'CFNetwork/485\.2' 333 | name: 'iOS' 334 | version: '4.0' 335 | 336 | - regex: 'CFNetwork/467\.12' 337 | name: 'iOS' 338 | version: '3.2' 339 | 340 | - regex: 'CFNetwork/459' 341 | name: 'iOS' 342 | version: '3.1' 343 | 344 | - regex: '(?:CPU OS|iPh(?:one)?[ _]OS|iOS)[ _/](\d+(?:[_\.]\d+)*)' 345 | name: 'iOS' 346 | version: '$1' 347 | 348 | - regex: '(?:Apple-)?(?:iPhone|iPad|iPod)(?:.*Mac OS X.*Version/(\d+\.\d+)|; Opera)?' 349 | name: 'iOS' 350 | version: '$1' 351 | 352 | - regex: 'Podcasts/(?:[\d\.]+)|Instacast(?:HD)?/(?:\d\.[\d\.abc]+)|Pocket Casts, iOS|Overcast|Castro|Podcat|i[cC]atcher' 353 | name: 'iOS' 354 | version: '' 355 | 356 | - regex: 'iTunes-(iPod|iPad|iPhone)/(?:[\d\.]+)' 357 | name: 'iOS' 358 | version: '' 359 | 360 | 361 | ########## 362 | # Mac 363 | ########## 364 | 365 | - regex: 'CFNetwork/760' 366 | name: 'Mac' 367 | version: '10.11' 368 | 369 | - regex: 'CFNetwork/720' 370 | name: 'Mac' 371 | version: '10.10' 372 | 373 | - regex: 'CFNetwork/673' 374 | name: 'Mac' 375 | version: '10.9' 376 | 377 | - regex: 'CFNetwork/596' 378 | name: 'Mac' 379 | version: '10.8' 380 | 381 | - regex: 'CFNetwork/520' 382 | name: 'Mac' 383 | version: '10.7' 384 | 385 | - regex: 'CFNetwork/454' 386 | name: 'Mac' 387 | version: '10.6' 388 | 389 | - regex: 'CFNetwork/(?:438|422|339|330|221|220|217)' 390 | name: 'Mac' 391 | version: '10.5' 392 | 393 | - regex: 'CFNetwork/12[89]' 394 | name: 'Mac' 395 | version: '10.4' 396 | 397 | - regex: 'CFNetwork/1\.2' 398 | name: 'Mac' 399 | version: '10.3' 400 | 401 | - regex: 'CFNetwork/1\.1' 402 | name: 'Mac' 403 | version: '10.2' 404 | 405 | - regex: 'Mac OS X(?: (?:Version )?(\d+(?:[_\.]\d+)+))?' 406 | name: 'Mac' 407 | version: '$1' 408 | 409 | - regex: 'Mac (\d+(?:[_\.]\d+)+)' 410 | name: 'Mac' 411 | version: '$1' 412 | 413 | - regex: 'Darwin|Macintosh|Mac_PowerPC|PPC|Mac PowerPC|iMac|MacBook' 414 | name: 'Mac' 415 | version: '' 416 | 417 | 418 | 419 | ########## 420 | # ChromeOS 421 | ########## 422 | - regex: 'CrOS [a-z0-9_]+ .* Chrome/(\d+[\.\d]+)' 423 | name: 'Chrome OS' 424 | version: '$1' 425 | 426 | 427 | 428 | ########## 429 | # BlackBerry 430 | ########## 431 | - regex: '(?:BB10;.+Version|Black[Bb]erry[0-9a-z]+|Black[Bb]erry.+Version)/(\d+[\.\d]+)' 432 | name: 'BlackBerry OS' 433 | version: '$1' 434 | 435 | 436 | - regex: 'RIM Tablet OS (\d+[\.\d]+)' 437 | name: 'BlackBerry Tablet OS' 438 | version: '$1' 439 | 440 | 441 | - regex: 'RIM Tablet OS|QNX|Play[Bb]ook' 442 | name: 'BlackBerry Tablet OS' 443 | version: '' 444 | 445 | 446 | - regex: 'BlackBerry' 447 | name: 'BlackBerry OS' 448 | version: '' 449 | 450 | - regex: 'bPod' 451 | name: 'BlackBerry OS' 452 | version: '' 453 | 454 | 455 | ########## 456 | # Haiku OS 457 | ########## 458 | - regex: 'Haiku' 459 | name: 'Haiku OS' 460 | version: '' 461 | 462 | 463 | ########## 464 | # BeOS 465 | ########## 466 | - regex: 'BeOS' 467 | name: 'BeOS' 468 | version: '' 469 | 470 | 471 | 472 | 473 | ########## 474 | # Symbian 475 | ########## 476 | - regex: 'Symbian/3.+NokiaBrowser/7\.3' 477 | name: 'Symbian^3' 478 | version: 'Anna' 479 | 480 | 481 | - regex: 'Symbian/3.+NokiaBrowser/7\.4' 482 | name: 'Symbian^3' 483 | version: 'Belle' 484 | 485 | 486 | - regex: 'Symbian/3' 487 | name: 'Symbian^3' 488 | version: '' 489 | 490 | 491 | - regex: '(?:Series ?60|SymbOS|S60)(?:[ /]?(\d+[\.\d]+|V\d+))?' 492 | name: 'Symbian OS Series 60' 493 | version: '$1' 494 | 495 | 496 | - regex: 'Series40' 497 | name: 'Symbian OS Series 40' 498 | version: '' 499 | 500 | 501 | - regex: 'SymbianOS/(\d+[\.\d]+)' 502 | name: 'Symbian OS' 503 | version: '$1' 504 | 505 | 506 | - regex: 'MeeGo|WeTab' 507 | name: 'MeeGo' 508 | version: '' 509 | 510 | 511 | - regex: 'Symbian(?: OS)?|SymbOS' 512 | name: 'Symbian OS' 513 | version: '' 514 | 515 | 516 | - regex: 'Nokia' 517 | name: 'Symbian' 518 | version: '' 519 | 520 | 521 | 522 | ########## 523 | # Firefox OS 524 | ########## 525 | - regex: '(?:Mobile|Tablet);.+Firefox/\d+\.\d+' 526 | name: 'Firefox OS' 527 | version: '' 528 | 529 | 530 | ########## 531 | # RISC OS 532 | ########## 533 | - regex: 'RISC OS(?:-NC)?(?:[ /](\d+[\.\d]+))?' 534 | name: 'RISC OS' 535 | version: '$1' 536 | 537 | 538 | ########## 539 | # Inferno 540 | ########## 541 | - regex: 'Inferno(?:[ /](\d+[\.\d]+))?' 542 | name: 'Inferno' 543 | version: '$1' 544 | 545 | 546 | ########## 547 | # Bada 548 | ########## 549 | - regex: 'bada(?:[ /](\d+[\.\d]+))' 550 | name: 'Bada' 551 | version: '$1' 552 | 553 | 554 | - regex: 'bada' 555 | name: 'Bada' 556 | version: '' 557 | 558 | 559 | ########## 560 | # Brew 561 | ########## 562 | - regex: '(?:Brew MP|BREW|BMP)(?:[ /](\d+[\.\d]+))' 563 | name: 'Brew' 564 | version: '$1' 565 | 566 | 567 | - regex: 'Brew MP|BREW|BMP' 568 | name: 'Brew' 569 | version: '' 570 | 571 | 572 | ########## 573 | # Web TV 574 | ########## 575 | - regex: 'GoogleTV(?:[ /](\d+[\.\d]+))?' 576 | name: 'Google TV' 577 | version: '$1' 578 | 579 | 580 | - regex: 'AppleTV(?:/?(\d+[\.\d]+))?' 581 | name: 'Apple TV' 582 | version: '$1' 583 | 584 | 585 | - regex: 'WebTV/(\d+[\.\d]+)' 586 | name: 'WebTV' 587 | version: '$1' 588 | 589 | 590 | ########## 591 | # Remix OS 592 | ########## 593 | - regex: 'RemixOS 5.1.1' 594 | name: 'Remix OS' 595 | version: '1' 596 | 597 | - regex: 'RemixOS 6.0' 598 | name: 'Remix OS' 599 | version: '2' 600 | 601 | - regex: 'RemixOS' 602 | name: 'Remix OS' 603 | version: '' 604 | 605 | 606 | ########## 607 | # Unix 608 | ########## 609 | - regex: '(?:SunOS|Solaris)(?:[/ ](\d+[\.\d]+))?' 610 | name: 'Solaris' 611 | version: '$1' 612 | 613 | 614 | - regex: 'AIX(?:[/ ]?(\d+[\.\d]+))?' 615 | name: 'AIX' 616 | version: '$1' 617 | 618 | 619 | - regex: 'HP-UX(?:[/ ]?(\d+[\.\d]+))?' 620 | name: 'HP-UX' 621 | version: '$1' 622 | 623 | 624 | - regex: 'FreeBSD(?:[/ ]?(\d+[\.\d]+))?' 625 | name: 'FreeBSD' 626 | version: '$1' 627 | 628 | 629 | - regex: 'NetBSD(?:[/ ]?(\d+[\.\d]+))?' 630 | name: 'NetBSD' 631 | version: '$1' 632 | 633 | 634 | - regex: 'OpenBSD(?:[/ ]?(\d+[\.\d]+))?' 635 | name: 'OpenBSD' 636 | version: '$1' 637 | 638 | 639 | - regex: 'DragonFly(?:[/ ]?(\d+[\.\d]+))?' 640 | name: 'DragonFly' 641 | version: '$1' 642 | 643 | 644 | - regex: 'Syllable(?:[/ ]?(\d+[\.\d]+))?' 645 | name: 'Syllable' 646 | version: '$1' 647 | 648 | 649 | - regex: 'IRIX(?:;64)?(?:[/ ]?(\d+[\.\d]+))' 650 | name: 'IRIX' 651 | version: '$1' 652 | 653 | 654 | - regex: 'OSF1(?:[/ ]?v?(\d+[\.\d]+))?' 655 | name: 'OSF1' 656 | version: '$1' 657 | 658 | 659 | 660 | ########## 661 | # Gaming Console 662 | ########## 663 | - regex: 'Nintendo Wii' 664 | name: 'Nintendo' 665 | version: 'Wii' 666 | 667 | 668 | - regex: 'PlayStation ?([3|4])' 669 | name: 'PlayStation' 670 | version: '$1' 671 | 672 | 673 | - regex: 'Xbox|KIN\.(?:One|Two)' 674 | name: 'Xbox' 675 | version: '360' 676 | 677 | 678 | 679 | ########## 680 | # Mobile Gaming Console 681 | ########## 682 | - regex: 'Nitro|Nintendo ([3]?DS[i]?)' 683 | name: 'Nintendo Mobile' 684 | version: '$1' 685 | 686 | 687 | - regex: 'PlayStation ((?:Portable|Vita))' 688 | name: 'PlayStation Portable' 689 | version: '$1' 690 | 691 | 692 | 693 | ########## 694 | # IBM 695 | ########## 696 | - regex: 'OS/2' 697 | name: 'OS/2' 698 | version: '' 699 | 700 | 701 | 702 | ########### 703 | # Linux (Generic) 704 | ########### 705 | - regex: 'Linux(?:OS)?[^a-z]' 706 | name: 'GNU/Linux' 707 | version: '' 708 | 709 | 710 | -------------------------------------------------------------------------------- /src/main/resources/regexes/vendorfragments.yml: -------------------------------------------------------------------------------- 1 | ############### 2 | # Device Detector - The Universal Device Detection library for parsing User Agents 3 | # 4 | # @link http://piwik.org 5 | # @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later 6 | ############### 7 | 8 | Dell: 9 | - 'MDDR(JS)?' 10 | - 'MDDC(JS)?' 11 | - 'MDDS(JS)?' 12 | 13 | Acer: 14 | - 'MAAR(JS)?' 15 | 16 | Sony: 17 | - 'MASE(JS)?' 18 | - 'MASP(JS)?' 19 | - 'MASA(JS)?' 20 | 21 | Asus: 22 | - 'MAAU' 23 | - 'NP0[6789]' 24 | - 'ASJB' 25 | - 'ASU2(JS)?' 26 | 27 | Samsung: 28 | - 'MASM(JS)?' 29 | - 'SMJB' 30 | 31 | Lenovo: 32 | - 'MALC(JS)?' 33 | - 'MALE(JS)?' 34 | - 'MALN(JS)?' 35 | - 'LCJB' 36 | - 'LEN2' 37 | 38 | Toshiba: 39 | - 'MATM(JS)?' 40 | - 'MATB(JS)?' 41 | - 'MATP(JS)?' 42 | - 'TNJB' 43 | - 'TAJB' 44 | 45 | Medion: 46 | - 'MAMD' 47 | 48 | MSI: 49 | - 'MAMI(JS)?' 50 | - 'MAM3' 51 | 52 | Gateway: 53 | - 'MAGW(JS)?' 54 | 55 | Fujitsu: 56 | - 'MAFS(JS)?' 57 | - 'FSJB' 58 | 59 | Compaq: 60 | - 'CPDTDF' 61 | - 'CPNTDF(JS?)' 62 | - 'CMNTDF(JS)?' 63 | - 'CMDTDF(JS)?' 64 | 65 | HP: 66 | - 'HPCMHP' 67 | - 'HPNTDF(JS)?' 68 | - 'HPDTDF(JS)?' 69 | 70 | Hyrican: 71 | - 'MANM(JS)?' 72 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/BrowserTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import org.junit.Test; 10 | 11 | import io.driocc.devicedetector.client.Browser; 12 | 13 | /** 14 | * @author kyon 15 | * 16 | */ 17 | public class BrowserTest { 18 | @Test 19 | public void test() { 20 | List uas = new ArrayList(); 21 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; OPPO R9S Plus Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043313 Safari/537.36 "); 22 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; MI 5 Build/MXB48T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043313 Safari/537.36 MicroMessenger/6.5.10.1080 NetType/WIFI Language/zh_CN"); 23 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; MI NOTE LTE Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 Mobile Safari/537.36; unicom{version:android@5.31,desmobile:15587929251}"); 24 | uas.add("Mozilla/5.0 (Linux; Android 5.1.1; vivo X7 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 Fanxing2/6.6665.3.6.0 NetType/WIFI FXBannerActivity/1"); 25 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; vivo Y66 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36 WindVane/8.0.0 yk_web_sdk_1.0.4.1 Youku/6.9.0 (Android 6.0.1; Bridge_SDK; GUID a468f55b9f049e7be2bd3fd60b7e9534; UTDID WWNf+UIMaZwDAKoq3S+v27v4;)"); 26 | uas.add("Mozilla/5.0 (Linux; Android 6.0; Le X620 Build/HEXCNFN5902606111S; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043313 Safari/537.36 V1_AND_SQ_7.1.5_708_YYB_D gamecenter QQ/7.1.5.3215 NetType/WIFI WebP/0.3.0 Pixel/1080"); 27 | uas.add("Mozilla/5.0 (Linux; Android 6.0; GN8003 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile x5InKaraoke/6.2 TBS/043409 Safari/537.36 QQJSSDK/1.3 qua/V1_AND_KG_4.0.0_273_SJTX_D qmkege/4.0.0"); 28 | uas.add("Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-G9350 Build/MMB29M"); 29 | uas.add("Dalvik/1.6.0 (Linux; U; Android 4.2.1; Lenovo S898t MIUI/5.3.2)"); 30 | uas.add("Dalvik/1.6.0 (Linux; U; Android 4.4.4; OPPO R7st Build/KTU84P"); 31 | uas.add("Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53"); 32 | uas.add("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); 33 | 34 | Browser b = new Browser(); 35 | for(String ua : uas) { 36 | DetectResult ret = b.parse(ua); 37 | if(ret!=null) { 38 | System.out.println(ret.getName() + "," + ret.getShortName() + "," + ret.getVersion() + "," + ret.getEngine() + "," + ret.getEngineVersion()); 39 | }else { 40 | System.out.println("cannot detect"); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/DetectorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import org.junit.Test; 10 | 11 | import io.driocc.devicedetector.custom.CompositeDetectResult; 12 | 13 | /** 14 | * @author kyon 15 | * 16 | */ 17 | public class DetectorTest { 18 | 19 | @Test 20 | public void test() { 21 | List uas = new ArrayList(); 22 | uas.add("Android/6.0.1 (samsung SM-G9280;zh_CN) App/3.4.8 AliApp(DingTalk/3.4.8) com.alibaba.android.rimet/0 Channel/10002068 language/zh-CN"); 23 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; OPPO R9S Plus Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043313 Safari/537.36 "); 24 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; MI 5 Build/MXB48T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043313 Safari/537.36 MicroMessenger/6.5.10.1080 NetType/WIFI Language/zh_CN"); 25 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; MI NOTE LTE Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 Mobile Safari/537.36; unicom{version:android@5.31,desmobile:15587929251}"); 26 | uas.add("Mozilla/5.0 (Linux; Android 5.1.1; vivo X7 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 Fanxing2/6.6665.3.6.0 NetType/WIFI FXBannerActivity/1"); 27 | uas.add("Mozilla/5.0 (Linux; Android 6.0.1; vivo Y66 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36 WindVane/8.0.0 yk_web_sdk_1.0.4.1 Youku/6.9.0 (Android 6.0.1; Bridge_SDK; GUID a468f55b9f049e7be2bd3fd60b7e9534; UTDID WWNf+UIMaZwDAKoq3S+v27v4;)"); 28 | uas.add("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); 29 | uas.add("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"); 30 | uas.add("PlayStation 4"); 31 | DeviceDetector d = new DeviceDetector(); 32 | for(String ua : uas) { 33 | CompositeDetectResult ret = d.parse(ua); 34 | if(ret!=null) { 35 | System.out.println(ret); 36 | } 37 | System.out.println("--------------------------"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/DeviceTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import org.junit.Test; 7 | 8 | import io.driocc.devicedetector.device.Mobile; 9 | 10 | /** 11 | * @author kyon 12 | * 13 | */ 14 | public class DeviceTest { 15 | String ua = "Mozilla/5.0 (Linux; Android 8.0.0; G8342 Build/47.1.A.12.205) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.70 Mobile Safari/537.36"; 16 | @Test 17 | public void test() { 18 | DetectResult ret = new Mobile().parse(ua); 19 | if(ret!=null) { 20 | System.out.println(ret.getDevice() + "," + ret.getModel() + "," + ret.getBrand() + "," + ret.getBrandId()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/EngineTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import org.junit.Test; 7 | 8 | import io.driocc.devicedetector.client.engine.Engine; 9 | 10 | /** 11 | * @author kyon 12 | * 13 | */ 14 | public class EngineTest { 15 | @Test 16 | public void test() { 17 | String ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"; 18 | Engine e = new Engine(); 19 | DetectResult ret = e.parse(ua); 20 | System.out.println(ret.getEngine()); 21 | System.out.println(ret.getEngineVersion()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/MatchTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import javax.annotation.MatchesPattern; 12 | 13 | import org.junit.Test; 14 | 15 | import io.driocc.devicedetector.utils.Utils; 16 | 17 | /** 18 | * @author kyon 19 | * 20 | */ 21 | public class MatchTest { 22 | public List matchUserAgent(String userAgent, String regex){ 23 | List matches = null; 24 | if(Utils.isEmpty(userAgent)) { 25 | return null; 26 | } 27 | String regexStr = "(?:^|[^A-Z_-])(?:" + regex + ")"; 28 | Pattern pattern = Pattern.compile(regexStr, Pattern.CASE_INSENSITIVE); 29 | Matcher matcher = pattern.matcher(userAgent); 30 | matcher.matches(); 31 | while(matcher.find()) { 32 | if(matches!=null){ 33 | matches = new ArrayList(); 34 | } 35 | matches.add(matcher.group()); 36 | } 37 | return matches; 38 | } 39 | @Test 40 | public void t() { 41 | String ua = "Dalvik/2.1.0 (Linux; U; Android 6.0.1; Mobile; SM-G9350 Build/MMB29M"; 42 | matchUserAgent(ua, "Android( [\\.0-9]+)?; Mobile;"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/OperatingSystemTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import org.junit.Test; 7 | import org.junit.Assert; 8 | 9 | /** 10 | * @author kyon 11 | * 12 | */ 13 | public class OperatingSystemTest { 14 | 15 | @Test 16 | public void test() { 17 | String ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"; 18 | OperatingSystem os = new OperatingSystem(); 19 | DetectResult ret = os.parse(ua); 20 | Assert.assertEquals("gg", "10", ret.getVersion()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/VendorFragmentTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import org.junit.Test; 7 | 8 | import io.driocc.devicedetector.device.Mobile; 9 | 10 | /** 11 | * @author kyon 12 | * 13 | */ 14 | public class VendorFragmentTest { 15 | String ua = "Mozilla/5.0 (Linux; Android 8.0.0; G8342 Build/47.1.A.12.205) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.70 Mobile Safari/537.36"; 16 | @Test 17 | public void test() { 18 | DetectResult ret = new VendorFragment().parse(ua); 19 | if(ret!=null) { 20 | System.out.println(ret.getDevice() + "," + ret.getModel() + "," + ret.getBrand() + "," + ret.getBrandId()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/io/driocc/devicedetector/YamlParserTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.driocc.devicedetector; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.junit.Test; 10 | 11 | import io.driocc.devicedetector.yaml.YamlParser; 12 | 13 | /** 14 | * @author kyon 15 | * 16 | */ 17 | public class YamlParserTest { 18 | @Test 19 | public void test() throws Exception { 20 | String path = "regexes/device/cameras.yml"; 21 | List> list = YamlParser.get(path); 22 | for(Map m : list) { 23 | System.out.println(m); 24 | } 25 | } 26 | } 27 | --------------------------------------------------------------------------------