OS.java
package com.morgan.website.useragent;
 
class OS {
    String name;
    Version version = null;
 
    public OS(String name) {
        this.name = name;
        //this.version = new Version("");
    }
}
package com.morgan.website.useragent;
 
/**
 * 整个userAgent文件夹下的代码大部分都来自于:
 * https://github.com/luolaifeng/ua-parser-java/
 */
public class TestUserAgent {
 
    public static void main(String[] args) {
        UA ua = new UA("Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36");
 
        UserAgent userAgent = ua.detect();
 
        System.out.print(userAgent.getOsName() + "-" + userAgent.getOsVersion() + "_");
 
        System.out.print(userAgent.getBrowserName() + "-" + userAgent.getBrowserVersion() + "-" + userAgent.getBrowserMode() + "_");
 
        System.out.print(userAgent.getEngineName() + "-" + userAgent.getEngineVersion() + "_");
 
        System.out.print(userAgent.getDeviceManufacturer() + "-" + userAgent.getDeviceType() + "-" + userAgent.getDeviceModel());
 
 
    }
}
UA.java
package com.morgan.website.useragent;
 
import org.springframework.util.StringUtils;
 
import java.math.BigDecimal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
 
public class UA {
    String ua;
    Boolean useFeatures = false;
    Boolean detectCamouflage = true;
 
    OS os;
    Engine engine;
    Device device;
    Browser browser;
    String net;
 
    public UA(String ua) {
        this.ua = ua;
        this.os = new OS("");
        this.engine = new Engine("");
        this.device = new Device("");
        this.browser = new Browser("");
        this.net = "";
    }
 
    protected Matcher matcher(String regex, String input, int flags) {
        Pattern p = Pattern.compile(regex, flags);
        Matcher m = p.matcher(input);
        return m;
    }
 
    protected Matcher matcher(String regex, String input) {
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(input);
        return m;
    }
 
    protected Matcher matcher(String regex, int flags) {
        return matcher(regex, ua, flags);
    }
 
    protected Matcher matcher(String regex) {
        return matcher(regex, ua);
    }
 
    protected Boolean find(String regex, String input, int flags) {
        Matcher m = matcher(regex, input, flags);
        return m.find();
    }
 
    protected Boolean find(String regex, String input) {
        Matcher m = matcher(regex, input);
        return m.find();
    }
 
    protected Boolean find(String regex, int flags) {
        return find(regex, ua, flags);
    }
 
    protected Boolean find(String regex) {
        return find(regex, ua);
    }
 
    protected Boolean contains(String partition) {
        return ua.indexOf(partition) >= 0;
    }
 
    protected String cleanupModel(String str) {
        if (str == null) {
            str = "";
        }
        str = str.replaceAll("_TD$", "");
        str = str.replaceAll("_CMCC$", "");
 
        str = str.replaceAll("_", " ");
        str = str.replaceAll("^\\s+|\\s+$", "");
        str = str.replaceAll("/[^/]+$", "");
        str = str.replaceAll("[^/]+ Android.*", "");
 
        str = str.replaceAll("^tita on ", "");
        str = str.replaceAll("Android on ", "");
        str = str.replaceAll("Android for ", "");
        str = str.replaceAll("^ICS AOSP on ", "");
        str = str.replaceAll("^Full AOSP on ", "");
        str = str.replaceAll("^Full Android on ", "");
        str = str.replaceAll("^Full Cappuccino on ", "");
        str = str.replaceAll("^Full MIPS Android on ", "");
        str = str.replaceAll("^Full Android", "");
 
        str = str.replaceAll("(?i)^Acer ?", "");
        str = str.replaceAll("^Iconia ", "");
        str = str.replaceAll("^Ainol ", "");
        str = str.replaceAll("(?i)^Coolpad ?", "Coolpad ");
        str = str.replaceAll("^ALCATEL", "");
        str = str.replaceAll("^Alcatel OT-(.*)", "one touch $1");
        str = str.replaceAll("^YL-", "");
        str = str.replaceAll("^Novo7 ?", "Novo7");
        str = str.replaceAll("^GIONEE", "");
        str = str.replaceAll("^HW-", "");
        str = str.replaceAll("(?i)^Huawei[-\\s]", "Huawei ");
        str = str.replaceAll("(?i)^SAMSUNG[- ]", "");
        str = str.replaceAll("^SonyEricsson", "");
        str = str.replaceAll("^Lenovo Lenovo", "Lenovo");
        str = str.replaceAll("^LNV-Lenovo", "Lenovo");
        str = str.replaceAll("^Lenovo-", "Lenovo ");
        str = str.replaceAll("(LG)[ _/]", "LG-");
        str = str.replaceAll("(HTC.*)\\s(v|V)?[0-9.]+$", "$1");
        str = str.replaceAll("(HTC)[-/]", "HTC ");
        str = str.replaceAll("(HTC)([A-Z][0-9][0-9][0-9])", "$1 $2");
        str = str.replaceAll("(Motorola[\\s|-])", "");
        str = str.replaceAll("(Moto|MOT-)", "");
 
        str = str.replaceAll("(?i)-?(orange(-ls)?|vodafone|bouygues)$", "");
        str = str.replaceAll("(?i)http://.+$", "");
 
        str = str.replaceAll("^\\s+|\\s+$", "");
 
        return str;
    }
 
    protected void parseNetType() {
        Matcher m = matcher("NetType/([0-9a-zA-Z\\+]+)(\\s|$)");
        if (m.find()) {
            this.net = m.group(1).toLowerCase();
        }
    }
 
    protected void parseOSAndDevice() {
        // Unix
        if (contains("Unix")) {
            this.os.name = "Unix";
        }
        if (contains("FreeBSD")) {
            this.os.name = "FreeBSD";
        }
        if (contains("OpenBSD")) {
            this.os.name = "OpenBSD";
        }
        if (contains("NetBSD")) {
            this.os.name = "NetBSD";
        }
        if (contains("SunOS")) {
            this.os.name = "Solaris";
        }
 
        // Linux
        if (contains("Linux")) {
            this.os.name = "Linux";
 
            if (contains("CentOS")) {
                this.os.name = "CentOS";
                Matcher m = matcher("CentOS/[0-9\\.\\-]+el([0-9_]+)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1).replace("_", "."));
                }
            }
 
            if (contains("Debian")) {
                this.os.name = "Debian";
            }
 
            if (contains("Fedora")) {
                this.os.name = "Fedora";
                Matcher m = matcher("Fedora/[0-9\\.\\-]+fc([0-9]+)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                }
            }
 
            if (contains("Gentoo")) {
                this.os.name = "Gentoo";
            }
 
            if (contains("Kubuntu")) {
                this.os.name = "Kubuntu";
            }
 
            if (contains("Mandriva Linux")) {
                this.os.name = "Mandriva";
                Matcher m = matcher("Mandriva Linux/[0-9\\.\\-]+mdv([0-9]+)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                }
            }
 
            if (contains("Mageia")) {
                this.os.name = "Mageia";
                Matcher m = matcher("Mageia/[0-9\\.\\-]+mga([0-9]+)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                }
            }
 
            if (contains("Red Hat")) {
                this.os.name = "Red Hat";
                Matcher m = matcher("Red Hat[^/]*/[0-9\\.\\-]+el([0-9_]+)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1).replace("_", "."));
                }
            }
 
            if (contains("Slackware")) {
                this.os.name = "Slackware";
            }
 
            if (contains("SUSE")) {
                this.os.name = "SUSE";
            }
 
            if (contains("Turbolinux")) {
                this.os.name = "Turbolinux";
            }
 
            if (contains("Ubuntu")) {
                this.os.name = "Ubuntu";
                Matcher m = matcher("Ubuntu/([0-9.]*)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                }
            }
        }
 
        // IOS
        if (find("iPhone( Simulator)?;") || contains("iPad;") || contains("iPod;")) {
            this.os.name = "iOS";
            this.os.version = new Version("1.0");
 
            Matcher m = matcher("OS (.*) like Mac OS X");
            if (m.find()) {
                this.os.version = new Version(m.group(1).replace("_", "."));
            } else {
                m = matcher("iOS ([0-9.]*); Scale");
                if (m.find()) {
                    this.os.version = new Version(m.group(1).replace("_", "."));
                }
            }
 
            if (contains("iPhone Simulator")) {
                this.device.type = "emulator";
            } else if (contains("iPod;")) {
                this.device.type = "media";
                this.device.manufacturer = "Apple";
                this.device.model = "iPod Touch";
            } else if (contains("iPhone")) {
                this.device.type = "mobile";
                this.device.manufacturer = "Apple";
                this.device.model = "iPhone";
            } else {
                this.device.type = "tablet";
                this.device.manufacturer = "Apple";
                this.device.model = "iPad";
            }
            this.device.identified = true;
        } else if (contains("Mac OS X")) { // MacOS X
            this.os.name = "Mac OS X";
            Matcher m = matcher("Mac OS X (10[0-9\\._]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1).replace("_", "."));
            }
        }
 
        // Windows
        if (contains("Windows")) {
            this.os.name = "Windows";
            Matcher match = matcher("Windows NT ([0-9]\\.[0-9])");
            if (match.find()) {
                String version = match.group(1);
                version = match.group(1);
                //self.os['version'] = parseVersion(version)
                if (version.equals("6.2")) {
                    this.os.version = new Version(version, "8");
                } else if (version.equals("6.1")) {
                    this.os.version = new Version(version, "7");
                } else if (version.equals("6.0")) {
                    this.os.version = new Version(version, "Vista");
                } else if (version.equals("5.2")) {
                    this.os.version = new Version(version, "Server 2003");
                } else if (version.equals("5.1")) {
                    this.os.version = new Version(version, "XP");
                } else if (version.equals("5.0")) {
                    this.os.version = new Version(version, "2000");
                } else {
                    this.os.version = new Version(version, "NT " + String.valueOf(version));
                }
            }
 
            if (contains("Windows 95") || contains("Win95") || contains("Win 9x 4.00")) {
                this.os.version = new Version("4.0", "95");
            }
 
            if (contains("Windows 98") || contains("Win98") || contains("Win 9x 4.10")) {
                this.os.version = new Version("4.1", "98");
            }
 
            if (contains("Windows ME") || contains("WinME") || contains("Win 9x 4.90")) {
                this.os.version = new Version("4.9", "ME");
            }
 
            if (contains("Windows XP") || contains("WinXP")) {
                this.os.version = new Version("5.1", "XP");
            }
 
            if (contains("WP7")) {
                this.os.name = "Windows Phone";
                this.os.version = new Version("7.0");
                this.os.version.details = 2;
                this.device.type = "mobile";
                this.browser.mode = "desktop";
            }
 
            if (contains("Windows CE") || contains("WinCE") || contains("WindowsCE")) {
                if (contains(" IEMobile")) {
                    this.os.name = "Windows Mobile";
                    if (contains(" IEMobile 8")) {
                        this.os.version = new Version("6.5");
                        this.os.version.details = 2;
                    }
                    if (contains(" IEMobile 7")) {
                        this.os.version = new Version("6.1");
                        this.os.version.details = 2;
                    }
                    if (contains(" IEMobile 6")) {
                        this.os.version = new Version("6.0");
                        this.os.version.details = 2;
                    }
                } else {
                    this.os.name = "Windows CE";
                    Matcher m = matcher("WindowsCEOS/([0-9.]*)");
                    if (m.find()) {
                        this.os.version = new Version(m.group(1));
                        this.os.version.details = 2;
                    }
                    m = matcher("Windows CE ([0-9.]*)");
                    if (m.find()) {
                        this.os.version = new Version(m.group(1));
                        this.os.version.details = 2;
                    }
                    this.device.type = "mobile";
                }
            }
 
            if (contains("Windows Mobile")) {
                this.os.name = "Windows Mobile";
                this.device.type = "mobile";
            }
 
            Matcher m = matcher("WindowsMobile/([0-9.]*)");
            if (m.find()) {
                this.os.name = "Windows Mobile";
                this.os.version = new Version(m.group(1));
                this.os.version.details = 2;
                this.device.type = "mobile";
            }
 
            m = matcher("Windows Phone [0-9]");
            if (m.find()) {
                this.os.name = "Windows Mobile";
                Matcher m1 = matcher("Windows Phone ([0-9.]*)");
                m1.find();
                this.os.version = new Version(m1.group(1));
                this.os.version.details = 2;
                this.device.type = "mobile";
            }
 
            if (contains("Windows Phone OS")) {
                this.os.name = "Windows Phone";
                Matcher m1 = matcher("Windows Phone OS ([0-9.]*)");
                m1.find();
                this.os.version = new Version(m1.group(1));
                this.os.version.details = 2;
                try {
                    if (Integer.parseInt(this.os.version.value) < 7) {
                        this.os.name = "Windows Mobile";
                    }
                } catch (NumberFormatException e) {
                }
 
                m = matcher("IEMobile/[^;]+; ([^;]+); ([^;]+)[;|\\)]");
                if (m.find()) {
                    this.device.manufacturer = m.group(1);
                    this.device.model = m.group(2);
                }
 
                this.device.type = "mobile";
 
                String manufacturer = this.device.manufacturer;
                String model = cleanupModel(this.device.model);
 
                if (MANUFACTURER.WINDOWS_PHONE_MODELS.containsKey(manufacturer)) {
                    this.device.manufacturer = MANUFACTURER.WINDOWS_PHONE_MODELS.get(manufacturer).get(model)[0];
                    this.device.model = MANUFACTURER.WINDOWS_PHONE_MODELS.get(manufacturer).get(model)[1];
                    this.device.identified = true;
                }
 
                if (manufacturer.equals("Microsoft") && model.equals("XDeviceEmulator")) {
                    this.device.manufacturer = "";
                    this.device.model = "";
                    this.device.type = "emulator";
                    this.device.identified = true;
                }
            }
        }
 
        // Android
        if (contains("Android")) {
            this.os.name = "Android";
            this.os.version = new Version("");
 
            Matcher m = matcher("Android(?: )?(?:AllPhone_|CyanogenMod_)?(?:/)?v?([0-9.]+)", ua.replace("-update", "."));
            if (m.find()) {
                this.os.version.value = m.group(1);
                this.os.version.details = 3;
            }
 
            if (contains("Android Eclair")) {
                this.os.version.value = "2.0";
                this.os.version.details = 3;
            }
 
            this.device.type = "mobile";
            try {
                String[] arr = this.os.version.value.split("\\.");
                if (arr.length > 0) {
                    if (Integer.parseInt(arr[0]) >= 3) {
                        this.device.type = "tablet";
                    }
                    if (Integer.parseInt(arr[0]) >= 4) {
                        this.device.type = "mobile";
                    }
                }
            } catch (NumberFormatException ex) {
            }
 
            if (find("Eclair; (?:[a-zA-Z][a-zA-Z](?:[-_][a-zA-Z][a-zA-Z])?) Build/([^/]*)/")) {
                m = matcher("Eclair; (?:[a-zA-Z][a-zA-Z](?:[-_][a-zA-Z][a-zA-Z])?) Build/([^/]*)/");
                m.find();
                this.device.model = m.group(1);
            } else if (find("; ([^;]*[^;\\s])\\s+Build")) {
                m = matcher("; ([^;]*[^;\\s])\\s+Build");
                m.find();
                this.device.model = m.group(1);
            } else if (find("[a-zA-Z][a-zA-Z](?:[-_][a-zA-Z][a-zA-Z])?; ([^;]*[^;\\s]);\\s+Build")) {
                m = matcher("[a-zA-Z][a-zA-Z](?:[-_][a-zA-Z][a-zA-Z])?; ([^;]*[^;\\s]);\\s+Build");
                m.find();
                this.device.model = m.group(1);
            } else if (find("\\(([^;]+);U;Android/[^;]+;[0-9]+\\*[0-9]+;CTC/2.0\\)")) {
                m = matcher("\\(([^;]+);U;Android/[^;]+;[0-9]+\\*[0-9]+;CTC/2.0\\)");
                m.find();
                this.device.model = m.group(1);
            } else if (find(";\\s?([^;]+);\\s?[0-9]+\\*[0-9]+;\\s?CTC2.0")) {
                m = matcher(";\\s?([^;]+);\\s?[0-9]+\\*[0-9]+;\\s?CTC2.0");
                m.find();
                this.device.model = m.group(1);
            } else if (find("zh-cn;\\s*(.*?)(/|build)/", Pattern.CASE_INSENSITIVE)) {
                m = matcher("zh-cn;\\s*(.*?)(/|build)/", Pattern.CASE_INSENSITIVE);
                m.find();
                this.device.model = m.group(1);
            } else if (find("Android [^;]+; (?:[a-zA-Z][a-zA-Z](?:[-_][a-zA-Z][a-zA-Z])?; )?([^)]+)\\)")) {
                m = matcher("Android [^;]+; (?:[a-zA-Z][a-zA-Z](?:[-_][a-zA-Z][a-zA-Z])?; )?([^)]+)\\)");
                if (!find("[a-zA-Z][a-zA-Z](?:[-_][a-zA-Z][a-zA-Z])?")) {
                    m.find();
                    this.device.model = m.group(1);
                }
            } else if (find("(.+?)/\\S+", Pattern.CASE_INSENSITIVE)) {
                m = matcher("(.+?)/\\S+", Pattern.CASE_INSENSITIVE);
                m.find();
                this.device.model = m.group(1);
            }
            // Sometimes we get a model name that starts with Android, in that case it is mismatch and we should ignore it
            if (this.device.model != null && this.device.model.indexOf("Android") == 0) {
                this.device.model = null;
            }
            if (this.device.model != null) {
                String model = cleanupModel(this.device.model);
                detectAndroidModel(model);
 
                if ((model.equals("Emulator")) || (model.equals("x86 Emulator")) || (model.equals("x86 VirtualBox")) || (model.equals("vm"))) {
                    this.device.manufacturer = null;
                    this.device.model = null;
                    this.device.type = "emulator";
                    this.device.identified = true;
                }
            }
 
            if (contains("HP eStation")) {
                this.device.manufacturer = "HP";
                this.device.model = "eStation";
                this.device.type = "tablet";
                this.device.identified = true;
            }
 
            if (contains("Pre/1.0")) {
                this.device.manufacturer = "Palm";
                this.device.model = "Pre";
                this.device.identified = true;
            }
 
            if (contains("Pre/1.1")) {
                this.device.manufacturer = "Palm";
                this.device.model = "Pre Plus";
                this.device.identified = true;
            }
 
            if (contains("Pre/1.2")) {
                this.device.manufacturer = "Palm";
                this.device.model = "Pre 2";
                this.device.identified = true;
            }
 
            if (contains("Pre/3.0")) {
                this.device.manufacturer = "HP";
                this.device.model = "Pre 3";
                this.device.identified = true;
            }
 
            if (contains("Pixi/1.0")) {
                this.device.manufacturer = "Palm";
                this.device.model = "Pixi";
                this.device.identified = true;
            }
 
            if (contains("Pixi/1.1")) {
                this.device.manufacturer = "Palm";
                this.device.model = "Pixi Plus";
                this.device.identified = true;
            }
 
            if (contains("P160UN/1.0")) {
                this.device.manufacturer = "HP";
                this.device.model = "Veer";
                this.device.identified = true;
            }
        }
 
        // Google TV
        if (contains("GoogleTV")) {
            this.os.name = "Google TV";
 
            if (contains("Chrome/5.")) {
                this.os.version = new Version("1");
            }
 
            if (contains("Chrome/11.")) {
                this.os.version = new Version("2");
            }
 
            this.device.type = "television";
        }
 
        // WoPhone
        if (contains("WoPhone")) {
            this.os.name = "WoPhone";
            Matcher m = matcher("WoPhone/([0-9\\.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
            this.device.type = "mobile";
        }
 
        // BlackBerry
        if (contains("BlackBerry")) {
            this.os.name = "BlackBerry OS";
 
            if (!contains("Opera")) {
                Matcher m = matcher("BlackBerry([0-9]*)/([0-9.]*)");
                if (m.find()) {
                    this.device.model = m.group(1);
                    this.os.version = new Version(m.group(2));
                    this.os.version.details = 2;
                }
 
                m = matcher("; BlackBerry ([0-9]*)");
                if (m.find()) {
                    this.device.model = m.group(1);
                }
 
                m = matcher("Version/([0-9.]*)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                    this.os.version.details = 2;
                }
 
                if (this.device.model != null) {
                    try {
                        String model = this.device.model;
                        if (MANUFACTURER.BLACKBERRY_MODELS.containsKey(model)) {
                            this.device.model = "BlackBerry " + MANUFACTURER.BLACKBERRY_MODELS.get(model) + " " + model;
                        } else {
                            this.device.model = "BlackBerry " + model;
                        }
                    } catch (NumberFormatException ex) {
                    }
                }
            } else {
                this.device.model = "BlackBerry";
            }
 
            this.device.manufacturer = "RIM";
            this.device.type = "mobile";
            this.device.identified = true;
        }
 
        // BlackBerry PlayBook
        if (contains("RIM Tablet OS")) {
            this.os.name = "BlackBerry Tablet OS";
 
            Matcher m = matcher("RIM Tablet OS ([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
                this.os.version.details = 2;
            }
            this.device.manufacturer = "RIM";
            this.device.model = "BlackBerry PlayBook";
            this.device.type = "tablet";
            this.device.identified = true;
        } else if (contains("PlayBook")) {
            Matcher m = matcher("Version/(10[0-9.]*)");
            if (m.find()) {
                this.os.name = "BlackBerry";
                this.os.version = new Version(m.group(1));
                this.os.version.details = 2;
                this.device.manufacturer = "RIM";
                this.device.model = "BlackBerry PlayBook";
                this.device.type = "tablet";
                this.device.identified = true;
            }
        }
 
        // WebOS
        if (find("(?:web|hpw)OS")) {
            this.os.name = "webOS";
            Matcher m = matcher("(?:web|hpw)OS/([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
            if (contains("tablet")) {
                this.device.type = "tablet";
            } else {
                this.device.type = "mobile";
            }
 
            if (contains("hpwOS")) {
                this.device.manufacturer = "HP";
            } else {
                this.device.manufacturer = "Palm";
            }
 
            if (contains("Pre/1.0")) {
                this.device.model = "Pre";
            }
            if (contains("Pre/1.1")) {
                this.device.model = "Pre Plus";
            }
            if (contains("Pre/1.2")) {
                this.device.model = "Pre2";
            }
            if (contains("Pre/3.0")) {
                this.device.model = "Pre3";
            }
            if (contains("Pixi/1.0")) {
                this.device.model = "Pixi";
            }
            if (contains("Pixi/1.1")) {
                this.device.model = "Pixi Plus";
            }
            if (find("P160UN?A?/1.0")) {
                this.device.model = "Veer";
            }
            if (contains("TouchPad/1.0")) {
                this.device.model = "TouchPad";
            }
            if (contains("Emulator/") && contains("Desktop/")) {
                this.device.type = "emulator";
                this.device.manufacturer = null;
                this.device.model = null;
            }
            this.device.identified = true;
        }
 
        // S60
        if (contains("Symbian") || contains("S60") || find("Series[ ]?60")) {
            this.os.name = "Series60";
 
            if (contains("SymbianOS/9.1") && !contains("Series60")) {
                this.os.version = new Version("3.0");
            }
 
            Matcher m = matcher("Series60/([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
 
            m = matcher("Nokia([^/;]+)[/|;]");
            if (m.find()) {
                if (!m.group(1).equals("Browser")) {
                    this.device.manufacturer = "Nokia";
                    this.device.model = m.group(1);
                    this.device.identified = true;
                }
            }
 
            m = matcher("Vertu([^/;]+)[/|;]");
            if (m.find()) {
                this.device.manufacturer = "Vertu";
                this.device.model = m.group(1);
                this.device.identified = true;
            }
 
            m = matcher("/Symbian; U; ([;]+); [a-z][a-z]\\-[a-z][a-z]", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.device.manufacturer = "Nokia";
                this.device.model = m.group(1);
                this.device.identified = true;
            }
 
            m = matcher("Samsung/([^;]*);");
            if (m.find()) {
                this.device.manufacturer = MANUFACTURER.STRINGS_SAMSUNG;
                this.device.model = m.group(1);
                this.device.identified = true;
            }
 
            this.device.type = "mobile";
        }
 
        // S40
        if (contains("Series40")) {
            this.os.name = "Series40";
            Matcher m = matcher("Nokia([^/]+)/");
            if (m.find()) {
                this.device.manufacturer = "Nokia";
                this.device.model = m.group(1);
                this.device.identified = true;
            }
            this.device.type = "mobile";
        }
 
        //MeeGo
        if (contains("MeeGo")) {
            this.os.name = "MeeGo";
            this.device.type = "mobile";
 
            Matcher m = matcher("Nokia([^\\)]+)\\)");
            if (m.find()) {
                this.device.manufacturer = "Nokia";
                this.device.model = m.group(1);
                this.device.identified = true;
            }
        }
 
        // Maemo
        if (contains("Maemo")) {
            this.os.name = "Maemo";
            this.device.type = "mobile";
 
            Matcher m = matcher("(N[0-9]+)");
            if (m.find()) {
                this.device.manufacturer = "Nokia";
                this.device.model = m.group(1);
                this.device.identified = true;
            }
        }
 
        // Tizen
        if (contains("Tizen")) {
            this.os.name = "Tizen";
            Matcher m = matcher("Tizen[/ ]([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
            this.device.type = "Mobile";
            m = matcher("\\(([^;]+); ([^/]+)/");
            if (m.find()) {
                if (!m.group(1).equals("Linux")) {
                    String manufacturer = m.group(1);
                    String model = m.group(2);
                    this.device.manufacturer = manufacturer;
                    this.device.model = model;
                    if (MANUFACTURER.TIZEN_MODELS.containsKey(manufacturer) && MANUFACTURER.TIZEN_MODELS.get(manufacturer).containsKey(model)) {
                        this.device.manufacturer = MANUFACTURER.TIZEN_MODELS.get(manufacturer).get(model)[0];
                        this.device.model = MANUFACTURER.TIZEN_MODELS.get(manufacturer).get(model)[1];
                        this.device.identified = true;
                    }
                }
            }
        }
 
        // Bada
        if (contains("Bada") || contains("bada")) {
            this.os.name = "Bada";
            Matcher m = matcher("[b|B]ada/([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
            this.device.type = "mobile";
            m = matcher("\\(([^;]+); ([^/]+)/");
            if (m.find()) {
                this.device.manufacturer = m.group(1);
                this.device.model = cleanupModel(m.group(2));
            }
 
            String manufacturer = this.device.manufacturer;
            String model = this.device.model;
 
            if (MANUFACTURER.BADA_MODELS.containsKey(manufacturer) && MANUFACTURER.BADA_MODELS.get(manufacturer).containsKey(model)) {
                this.device.manufacturer = MANUFACTURER.BADA_MODELS.get(manufacturer).get(model)[0];
                this.device.model = MANUFACTURER.BADA_MODELS.get(manufacturer).get(model)[1];
                this.device.identified = true;
            }
        }
 
        // Brew
        if (ua.toLowerCase().indexOf("brew") >= 0 && contains("BMP; U")) {
            this.os.name = "Brew";
            this.device.type = "mobile";
 
            if (find("BREW; U; ([0-9.]*)")) {
                Matcher m = matcher("BREW; U; ([0-9.]*)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                }
            } else if (find(";BREW/([0-9.]*)", Pattern.CASE_INSENSITIVE)) {
                Matcher m = matcher(";BREW/([0-9.]*)", Pattern.CASE_INSENSITIVE);
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                }
            }
 
            Matcher m = matcher("\\(([^;]+);U;REX[^;]+BREW/[^;]+;(?:.*;)?[0-9]+\\*[0-9]+;CTC/2.0\\)");
            if (m.find()) {
                this.device.model = m.group(1);
            }
            if (this.device.model != null) {
                String model = cleanupModel(this.device.model);
                if (MANUFACTURER.BREW_MODELS.containsKey(model)) {
                    this.device.manufacturer = MANUFACTURER.BREW_MODELS.get(model)[0];
                    this.device.model = MANUFACTURER.BREW_MODELS.get(model)[1];
                    this.device.identified = true;
                }
            }
        }
 
        // MTK
        if (find("\\(MTK;")) {
            this.os.name = "MTK";
            this.device.type = "mobile";
        }
 
        // CrOS
        if (contains("CrOS")) {
            this.os.name = "Chrome OS";
            this.device.type = "desktop";
        }
 
        // Joli OS
        if (contains("Joli OS")) {
            this.os.name = "Joli OS";
            this.device.type = "desktop";
            Matcher m = matcher("Joli OS/([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
        }
 
        // Haiku
        if (contains("Haiku")) {
            this.os.name = "Haiku";
            this.device.type = "desktop";
        }
 
        // QNX
        if (contains("QNX")) {
            this.os.name = "QNX";
            this.device.type = "mobile";
        }
 
        // OS/2 Warp
        if (contains("OS/2; Warp")) {
            this.os.name = "OS/2 Warp";
            this.device.type = "desktop";
            Matcher m = matcher("OS/2; Warp ([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
        }
 
        // Grid OS
        if (contains("Grid OS")) {
            this.os.name = "Grid OS";
            this.device.type = "tablet";
            Matcher m = matcher("Grid OS ([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
        }
 
        // AmigaOS
        if (ua.toLowerCase().indexOf("amigaos") >= 0) {
            this.os.name = "AmigaOS";
            this.device.type = "desktop";
            Matcher m = matcher("AmigaOS ([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
        }
 
        // MorphOS
        if (ua.toLowerCase().indexOf("morphos") >= 0) {
            this.os.name = "MorphOS";
            this.device.type = "desktop";
            Matcher m = matcher("MorphOS ([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
        }
 
        // Kindle
        if (contains("Kindle") && !contains("Fire")) {
            this.os.name = "";
            this.device.manufacturer = "Amazon";
            this.device.model = "Kindle";
            this.device.type = "ereader";
            if (contains("Kindle/2.0")) {
                this.device.model = "Kindle 2";
            }
            if (contains("Kindle/3.0")) {
                this.device.model = "Kindle 3 or later";
            }
            this.device.identified = true;
        }
 
        // NOOK
        if (contains("nook browser")) {
            this.os.name = "Android";
            this.device.manufacturer = "Barnes & Noble";
            this.device.model = "NOOK";
            this.device.type = "ereader";
            this.device.identified = true;
        }
 
        // Bookeen
        if (contains("bookeen/cybook")) {
            this.os.name = "";
            this.device.manufacturer = "Bookeen";
            this.device.model = "Cybook";
            this.device.type = "ereader";
            if (contains("Orizon")) {
                this.device.model = "Cybook Orizon";
            }
            this.device.identified = true;
        }
 
        // Sony Reader
        if (contains("EBRD1101")) {
            this.os.name = "";
            this.device.manufacturer = "Sony";
            this.device.model = "Reader";
            this.device.type = "ereader";
            this.device.identified = true;
        }
 
        // iRiver
        if (contains("Iriver")) {
            this.os.name = "";
            this.device.manufacturer = "iRiver";
            this.device.model = "Story";
            this.device.type = "ereader";
            if (contains("EB07")) {
                this.device.model = "Story HD EB07";
            }
            this.device.identified = true;
        }
 
        /**
         * Nintendo
         * Opera/9.30 (Nintendo Wii; U; ; 3642; en)
         * Opera/9.30 (Nintendo Wii; U; ; 2047-7; en)
         * Opera/9.50 (Nintendo DSi; Opera/507; U; en-US)
         * Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7455.US
         * Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7455.EU
         */
        if (contains("Nintendo Wii")) {
            this.os.name = "";
            this.device.manufacturer = "Nintendo";
            this.device.model = "Wii";
            this.device.type = "gaming";
            this.device.identified = true;
        }
 
        if (contains("Nintendo DSi")) {
            this.os.name = "";
            this.device.manufacturer = "Nintendo";
            this.device.model = "DSi";
            this.device.type = "gaming";
            this.device.identified = true;
        }
 
        if (contains("Nintendo 3DS")) {
            this.os.name = "";
            this.device.manufacturer = "Nintendo";
            this.device.model = "3DS";
            this.device.type = "gaming";
            Matcher m = matcher("Version/([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
            this.device.identified = true;
        }
 
        // Sony PlayStation
        if (contains("PlayStation Portable")) {
            this.os.name = "";
            this.device.manufacturer = "Sony";
            this.device.model = "PlayStation Portable";
            this.device.type = "gaming";
            this.device.identified = true;
        }
        if (contains("PlayStation Vita")) {
            this.os.name = "";
            Matcher m = matcher("PlayStation Vita ([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
            this.device.manufacturer = "Sony";
            this.device.model = "PlayStation Vita";
            this.device.type = "gaming";
            this.device.identified = true;
        }
        if (ua.toLowerCase().indexOf("playstation 3") >= 0) {
            this.os.name = "";
            Matcher m = matcher("PLAYSTATION 3;? ([0-9.]*)");
            if (m.find()) {
                this.os.version = new Version(m.group(1));
            }
            this.device.manufacturer = "Sony";
            this.device.model = "PlayStation 3";
            this.device.type = "gaming";
            this.device.identified = true;
        }
 
        /**
         * Panasonic Smart Viera
         *  Mozilla/5.0 (FreeBSD; U; Viera; ja-JP) AppleWebKit/535.1 (KHTML, like Gecko) Viera/1.2.4 Chrome/14.0.835.202 Safari/535.1
         */
        if (contains("Viera")) {
            this.os.name = "";
            this.device.manufacturer = "Panasonic";
            this.device.model = "Smart Viera";
            this.device.type = "television";
            this.device.identified = true;
        }
 
        /**
         * Sharp AQUOS TV
         *  Mozilla/5.0 (DTV) AppleWebKit/531.2  (KHTML, like Gecko) AQUOSBrowser/1.0 (US00DTV;V;0001;0001)
         *  Mozilla/5.0 (DTV) AppleWebKit/531.2+ (KHTML, like Gecko) Espial/6.0.4 AQUOSBrowser/1.0 (CH00DTV;V;0001;0001)
         *  Opera/9.80 (Linux armv6l; U; en) Presto/2.8.115 Version/11.10 AQUOS-AS/1.0 LC-40LE835X
         */
        if (contains("AQUOSBrowser") || contains("AQUOS-AS")) {
            this.os.name = "";
            this.device.manufacturer = MANUFACTURER.STRINGS_SHARP;
            this.device.model = "Aquos TV";
            this.device.type = "television";
            this.device.identified = true;
        }
 
        /**
         * Samsung Samsung TV
         *  Mozilla/5.0 (SmartHub; SMART-TV; U; Linux/SmartTV; Maple2012) AppleWebKit/534.7 (KHTML, like Gecko) SmartTV Safari/534.7
         *  Mozilla/5.0 (SmartHub; SMART-TV; U; Linux/SmartTV) AppleWebKit/531.2+ (KHTML, like Gecko) WebBrowser/1.0 SmartTV Safari/531.2+
         */
        if (contains("SMART-TV")) {
            this.os.name = "";
            this.device.manufacturer = MANUFACTURER.STRINGS_SAMSUNG;
            this.device.model = "Smart TV";
            this.device.type = "television";
            this.device.identified = true;
            Matcher m = matcher("Maple([0-9]*)");
            if (m.find()) {
                this.device.model = " " + m.group(1);
            }
        }
 
        /**
         * Sony Internet TV
         *  Opera/9.80 (Linux armv7l; U; InettvBrowser/2.2(00014A;SonyDTV115;0002;0100) KDL-46EX640; CC/USA; en) Presto/2.8.115 Version/11.10
         *  Opera/9.80 (Linux armv7l; U; InettvBrowser/2.2(00014A;SonyDTV115;0002;0100) KDL-40EX640; CC/USA; en) Presto/2.10.250 Version/11.60
         *  Opera/9.80 (Linux armv7l; U; InettvBrowser/2.2(00014A;SonyDTV115;0002;0100) N/A; CC/USA; en) Presto/2.8.115 Version/11.10
         *  Opera/9.80 (Linux mips; U; InettvBrowser/2.2 (00014A;SonyDTV115;0002;0100) ; CC/JPN; en) Presto/2.9.167 Version/11.50
         *  Opera/9.80 (Linux mips; U; InettvBrowser/2.2 (00014A;SonyDTV115;0002;0100) AZ2CVT2; CC/CAN; en) Presto/2.7.61 Version/11.00
         *  Opera/9.80 (Linux armv6l; Opera TV Store/4207; U; (SonyBDP/BDV11); en) Presto/2.9.167 Version/11.50
         *  Opera/9.80 (Linux armv6l ; U; (SonyBDP/BDV11); en) Presto/2.6.33 Version/10.60
         *  Opera/9.80 (Linux armv6l; U; (SonyBDP/BDV11); en) Presto/2.8.115 Version/11.10
         *  Sony Internet TV
         */
        if (find("SonyDTV|SonyBDP|SonyCEBrowser")) {
            this.os.name = "";
            this.device.manufacturer = "Sony";
            this.device.model = "Internet TV";
            this.device.type = "television";
            this.device.identified = true;
        }
 
        /**
         * Philips Net TV
         *
         *  Opera/9.70 (Linux armv6l ; U; CE-HTML/1.0 NETTV/2.0.2; en) Presto/2.2.1
         *  Opera/9.80 (Linux armv6l ; U; CE-HTML/1.0 NETTV/3.0.1;; en) Presto/2.6.33 Version/10.60
         *  Opera/9.80 (Linux mips; U; CE-HTML/1.0 NETTV/3.0.1; PHILIPS-AVM-2012; en) Presto/2.9.167 Version/11.50
         *  Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.1.0; en) Presto/2.6.33 Version/10.70
         *  Opera/9.80 (Linux i686; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.1.0; en) Presto/2.9.167 Version/11.50
         */
        if (contains("NETTV/")) {
            this.os.name = "";
            this.device.manufacturer = "Philips";
            this.device.model = "Net TV";
            this.device.type = "television";
            this.device.identified = true;
        }
 
        /**
         * LG NetCast TV
         *
         *  Mozilla/5.0 (DirectFB; Linux armv7l) AppleWebKit/534.26+ (KHTML, like Gecko) Version/5.0 Safari/534.26+ LG Browser/5.00.00(+mouse+3D+SCREEN+TUNER; LGE; GLOBAL-PLAT4; 03.09.22; 0x00000001;); LG NetCast.TV-2012
         *  Mozilla/5.0 (DirectFB; Linux armv7l) AppleWebKit/534.26+ (KHTML, like Gecko) Version/5.0 Safari/534.26+ LG Browser/5.00.00(+SCREEN+TUNER; LGE; GLOBAL-PLAT4; 01.00.00; 0x00000001;); LG NetCast.TV-2012
         *  Mozilla/5.0 (DirectFB; U; Linux armv6l; en) AppleWebKit/531.2  (KHTML, like Gecko) Safari/531.2  LG Browser/4.1.4( BDP; LGE; Media/BD660; 6970; abc;); LG NetCast.Media-2011
         *  Mozilla/5.0 (DirectFB; U; Linux 7631; en) AppleWebKit/531.2  (KHTML, like Gecko) Safari/531.2  LG Browser/4.1.4( NO_NUM; LGE; Media/SP520; ST.3.97.409.F; 0x00000001;); LG NetCast.Media-2011
         *  Mozilla/5.0 (DirectFB; U; Linux 7630; en) AppleWebKit/531.2  (KHTML, like Gecko) Safari/531.2  LG Browser/4.1.4( 3D BDP NO_NUM; LGE; Media/ST600; LG NetCast.Media-2011
         *  (LGSmartTV/1.0) AppleWebKit/534.23 OBIGO-T10/2.0
         */
        if (find("LG NetCast\\.(?:TV|Media)-([0-9]*)")) {
            Matcher m = matcher("LG NetCast\\.(?:TV|Media)-([0-9]*)");
            this.os.name = "";
            this.device.manufacturer = MANUFACTURER.STRINGS_LG;
            if (m.find()) {
                this.device.model = "NetCast TV " + m.group(1);
            }
            this.device.type = "television";
            this.device.identified = true;
        }
 
        if (contains("LGSmartTV")) {
            this.os.name = "";
            this.device.manufacturer = MANUFACTURER.STRINGS_LG;
            this.device.model = "Smart TV";
            this.device.type = "television";
            this.device.identified = true;
        }
 
        /**
         * Toshiba Smart TV
         *
         *  Mozilla/5.0 (Linux mipsel; U; HbbTV/1.1.1 (; TOSHIBA; DTV_RL953; 56.7.66.7; t12; ) ; ToshibaTP/1.3.0 (+VIDEO_MP4+VIDEO_X_MS_ASF+AUDIO_MPEG+AUDIO_MP4+DRM+NATIVELAUNCH) ; en) AppleWebKit/534.1 (KHTML, like Gecko)
         *  Mozilla/5.0 (DTV; TSBNetTV/T32013713.0203.7DD; TVwithVideoPlayer; like Gecko) NetFront/4.1 DTVNetBrowser/2.2 (000039;T32013713;0203;7DD) InettvBrowser/2.2 (000039;T32013713;0203;7DD)
         *  Mozilla/5.0 (Linux mipsel; U; HbbTV/1.1.1 (; TOSHIBA; 40PX200; 0.7.3.0.; t12; ) ; Toshiba_TP/1.3.0 (+VIDEO_MP4+AUDIO_MPEG+AUDIO_MP4+VIDEO_X_MS_ASF+OFFLINEAPP) ; en) AppleWebKit/534.1 (KHTML, like Gec
         */
        if (find("Toshiba_?TP/") || find("TSBNetTV/")) {
            this.os.name = "";
            this.device.manufacturer = "Toshiba";
            this.device.model = "Smart TV";
            this.device.type = "television";
            this.device.identified = true;
        }
 
        // MachBlue XT
        if (find("mbxtWebKit/([0-9.]*)")) {
            this.os.name = "";
            Matcher m = matcher("mbxtWebKit/([0-9.]*)");
            this.browser.name = "MachBlue XT";
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = 2;
            }
            this.device.type = "television";
        }
 
        // ADB
        if (find("\\(ADB; ([^\\)]+)\\)")) {
            this.os.name = "";
            Matcher m = matcher("\\(ADB; ([^\\)]+)\\)");
            this.device.manufacturer = "ADB";
            if (m.find()) {
                if (m.group(1).equals("Unknown")) {
                    this.device.model = m.group(1).replace("ADB", "") + "IPTV receiver";
                } else {
                    this.device.model = "IPTV receiver";
                }
            }
            this.device.type = "television";
            this.device.identified = true;
        }
 
        // MStar
        if (contains("MStar;OWB")) {
            this.os.name = "";
            this.device.manufacturer = "MStar";
            this.device.model = "PVR";
            this.device.type = "television";
            this.device.identified = true;
            this.browser.name = "Origyn Web Browser";
        }
 
        // TechniSat
        if (find("\\\\TechniSat ([^;]+);")) {
            this.os.name = "";
            Matcher m = matcher("\\\\TechniSat ([^;]+);");
            this.device.manufacturer = "TechniSat";
            if (m.find()) {
                this.device.model = m.group(1);
            }
            this.device.type = "television";
            this.device.identified = true;
        }
 
        // Technicolor
        if (find("\\\\Technicolor_([^;]+);")) {
            this.os.name = "";
            Matcher m = matcher("\\\\Technicolor_([^;]+);");
            this.device.manufacturer = "Technicolor";
            if (m.find()) {
                this.device.model = m.group(1);
            }
            this.device.type = "television";
            this.device.identified = true;
        }
 
        // Winbox Evo2
        if (contains("Winbox Evo2")) {
            this.os.name = "";
            this.device.manufacturer = "Winbox";
            this.device.model = "Evo2";
            this.device.type = "television";
            this.device.identified = true;
        }
 
        // Roku
        if (find("^Roku/DVP-([0-9]+)")) {
            this.device.manufacturer = "Roku";
            this.device.type = "television";
            Matcher m = matcher("^Roku/DVP-([0-9]+)");
            if (m.find()) {
                String group = m.group(1);
                if (group.equals("2000")) {
                    this.device.model = "HD";
                } else if (group.equals("2050")) {
                    this.device.model = "XD";
                } else if (group.equals("2100")) {
                    this.device.model = "XDS";
                } else if (group.equals("2400")) {
                    this.device.model = "LT";
                } else if (group.equals("3000")) {
                    this.device.model = "2 HD";
                } else if (group.equals("3050")) {
                    this.device.model = "2 XD";
                } else if (group.equals("3100")) {
                    this.device.model = "2 XS";
                }
            }
            this.device.identified = true;
        }
 
        if (find("HbbTV/1.1.1 \\([^;]*;\\s*([^;]*)\\s*;\\s*([^;]*)\\s*;")) {
            this.device.type = "television";
            Matcher m = matcher("HbbTV/1.1.1 \\([^;]*;\\s*([^;]*)\\s*;\\s*([^;]*)\\s*;");
            if (m.find()) {
                String vendorName = m.group(1).trim();
                String modelName = m.group(2).trim();
                if (this.device.manufacturer != null && vendorName != "" && !vendorName.equals("vendorName")) {
                    if (vendorName.equals("LGE")) {
                        this.device.manufacturer = "LG";
                    } else if (vendorName.equals("TOSHIBA")) {
                        this.device.manufacturer = "Toshiba";
                    } else if (vendorName.equals("smart")) {
                        this.device.manufacturer = "Smart";
                    } else if (vendorName.equals("tv2n")) {
                        this.device.manufacturer = "TV2N";
                    } else {
                        this.device.manufacturer = vendorName;
                    }
 
                    if (this.device.model != null && modelName != "" && !modelName.equals("modelName")) {
                        if (modelName.equals("GLOBAL_PLAT3")) {
                            this.device.model = "NetCast TV";
                        } else if (modelName.equals("SmartTV2012")) {
                            this.device.model = "Smart TV 2012";
                        } else if (modelName.equals("videoweb")) {
                            this.device.model = "Videoweb";
                        } else {
                            this.device.model = modelName;
                        }
 
                        if (vendorName.equals("Humax")) {
                            this.device.model = this.device.model.toUpperCase();
                        }
                        this.device.identified = true;
                        this.os.name = "";
                    }
                }
            }
        }
 
        // Detect type based on common identifiers
        if (contains("InettvBrowser")) {
            this.device.type = "television";
        }
        if (contains("MIDP")) {
            this.device.type = "mobile";
        }
 
        // Try to detect any devices based on common locations of model ids
        if (this.device.model == null && this.device.manufacturer == null) {
            List<String> candidates = new ArrayList<String>();
            if (!find("^(Mozilla|Opera)")) {
                Matcher m = matcher("^(?:MQQBrowser/[0-9\\.]+/)?([^\\s]+)");
                if (m.find()) {
                    String group = m.group(1);
                    group = group.replaceAll("_TD$", "");
                    group = group.replaceAll("_CMCC$", "");
                    group = group.replaceAll("[_ ]Mozilla$", "");
                    group = group.replaceAll(" Linux$", "");
                    group = group.replaceAll(" Opera$", "");
                    group = group.replaceAll("/[0-9].*$", "");
                    candidates.add(group);
                }
            }
 
            Matcher m = matcher("[0-9]+x[0-9]+; ([^;]+)");
            if (m.find()) {
                candidates.add(m.group(1));
            }
 
            m = matcher("[0-9]+X[0-9]+ ([^;/\\(\\)]+)");
            if (m.find()) {
                candidates.add(m.group(1));
            }
 
            m = matcher("Windows NT 5.1; ([^;]+); Windows Phone");
            if (m.find()) {
                candidates.add(m.group(1));
            }
 
            m = matcher("\\) PPC; (?:[0-9]+x[0-9]+; )?([^;/\\(\\)]+)");
            if (m.find()) {
                candidates.add(m.group(1));
            }
 
            m = matcher("\\(([^;]+); U; Windows Mobile");
            if (m.find()) {
                candidates.add(m.group(1));
            }
 
            m = matcher("Vodafone/1.0/([^/]+)");
            if (m.find()) {
                candidates.add(m.group(1));
            }
 
            m = matcher("\\\\ ([^\\s]+)$");
            if (m.find()) {
                candidates.add(m.group(1));
            }
 
            for (String candidate : candidates) {
                Boolean result = false;
                if (this.device.model == null && this.device.manufacturer == null) {
                    String model = cleanupModel(candidate);
                    if (this.os.name.equals("Android")) {
                        if (MANUFACTURER.ANDROID_MODELS.containsKey(model)) {
                            this.device.manufacturer = MANUFACTURER.ANDROID_MODELS.get(model)[0];
                            this.device.model = MANUFACTURER.ANDROID_MODELS.get(model)[1];
                            if (MANUFACTURER.ANDROID_MODELS.get(model).length > 2) {
                                this.device.type = MANUFACTURER.ANDROID_MODELS.get(model)[2];
                            }
                            this.device.identified = true;
                            result = true;
                        }
                    }
 
                    if ((this.os.name == null || this.os.name.isEmpty()) || this.os.name.equals("Windows") || this.os.name.equals("Windows Mobile") || this.os.name.equals("Windows CE")) {
                        if (MANUFACTURER.WINDOWS_MOBILE_MODELS.containsKey(model)) {
                            this.device.manufacturer = MANUFACTURER.WINDOWS_MOBILE_MODELS.get(model)[0];
                            this.device.model = MANUFACTURER.WINDOWS_MOBILE_MODELS.get(model)[1];
                            this.device.type = "mobile";
                            this.device.identified = true;
 
                            if (!this.os.name.equals("Windows Mobile")) {
                                this.os.name = "Windows Mobile";
                                this.os.version = null;
                            }
 
                            result = true;
                        }
                    }
                }
 
                if (!result) {
                    m = matcher("^GIONEE-([^\\s]+)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = "Gionee";
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
                    }
 
                    m = matcher("^HTC_?([^/_]+)(?:/|_|$)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = MANUFACTURER.STRINGS_HTC;
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
                    }
 
                    m = matcher("^HUAWEI-([^/]*)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = MANUFACTURER.STRINGS_HUAWEI;
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
                    }
 
                    m = matcher("(?:^|\\()LGE?(?:/|-|_|\\s)([^\\s]*)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = MANUFACTURER.STRINGS_LG;
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
                    }
 
                    m = matcher("^MOT-([^/_]+)(?:/|_|$)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = MANUFACTURER.STRINGS_MOTOROLA;
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
                    }
 
                    m = matcher("^Motorola_([^/_]+)(?:/|_|$)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = MANUFACTURER.STRINGS_MOTOROLA;
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
                    }
 
                    m = matcher("^Nokia([^/]+)(?:/|$)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = "Nokia";
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
 
                        if (this.os.name == "" || this.os.name == null) {
                            this.os.name = "Series40";
                        }
                    }
 
                    m = matcher("^SonyEricsson([^/_]+)(?:/|_|$)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = MANUFACTURER.STRINGS_SONY_ERICSSON;
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
                        this.device.identified = true;
                    }
 
                    m = matcher("^SAMSUNG-([^/_]+)(?:/|_|$)", candidate);
                    if (m.find()) {
                        this.device.manufacturer = MANUFACTURER.STRINGS_SAMSUNG;
                        this.device.model = cleanupModel(m.group(1));
                        this.device.type = "mobile";
 
                        if (this.os.name.equals("Bada")) {
                            String manufacturer = "SAMSUNG";
                            String model = cleanupModel(this.device.model);
 
                            if (MANUFACTURER.BADA_MODELS.containsKey(manufacturer) && MANUFACTURER.BADA_MODELS.get(manufacturer).containsKey(model)) {
                                this.device.manufacturer = MANUFACTURER.BADA_MODELS.get(manufacturer).get(model)[0];
                                this.device.model = MANUFACTURER.BADA_MODELS.get(manufacturer).get(model)[1];
                                this.device.identified = true;
                            }
                        } else if (find("Jasmine/([0-9.]*)")) {
                            m = matcher("Jasmine/([0-9.]*)");
                            m.find();
                            String version = m.group(1);
                            String manufacturer = "SAMSUNG";
                            String model = cleanupModel(this.device.model);
 
                            if (MANUFACTURER.TOUCHWIZ_MODELS.containsKey(manufacturer) && MANUFACTURER.TOUCHWIZ_MODELS.get(manufacturer).containsKey(model)) {
                                this.device.manufacturer = MANUFACTURER.TOUCHWIZ_MODELS.get(manufacturer).get(model)[0];
                                this.device.model = MANUFACTURER.TOUCHWIZ_MODELS.get(manufacturer).get(model)[1];
                                this.device.identified = true;
                                this.os.name = "Touchwiz";
                                this.os.version = new Version("2.0");
                            }
                        } else if (find("Dolfin/([0-9.]*)")) {
                            m = matcher("Dolfin/([0-9.]*)");
                            m.find();
                            String version = m.group(1);
                            String manufacturer = "SAMSUNG";
                            String model = cleanupModel(this.device.model);
                            if (MANUFACTURER.BADA_MODELS.containsKey(manufacturer) && MANUFACTURER.BADA_MODELS.get(manufacturer).containsKey(model)) {
                                this.device.manufacturer = MANUFACTURER.BADA_MODELS.get(manufacturer).get(model)[0];
                                this.device.model = MANUFACTURER.BADA_MODELS.get(manufacturer).get(model)[1];
                                this.device.identified = true;
                                this.os.name = "Bada";
                                if (version.equals("2.0")) {
                                    this.os.version = new Version("1.0");
                                } else if (version.equals("2.2")) {
                                    this.os.version = new Version("1.2");
                                } else if (version.equals("3.0")) {
                                    this.os.version = new Version("2.0");
                                }
                            }
                            if (MANUFACTURER.TOUCHWIZ_MODELS.containsKey(manufacturer) && MANUFACTURER.TOUCHWIZ_MODELS.get(manufacturer).containsKey(model)) {
                                this.device.manufacturer = MANUFACTURER.TOUCHWIZ_MODELS.get(manufacturer).get(model)[0];
                                this.device.model = MANUFACTURER.TOUCHWIZ_MODELS.get(manufacturer).get(model)[1];
                                this.device.identified = true;
                                this.os.name = "Touchwiz";
                                if (version.equals("1.0")) {
                                    this.os.version = new Version("1.0");
                                } else if (version.equals("1.5")) {
                                    this.os.version = new Version("2.0");
                                } else if (version.equals("2.0")) {
                                    this.os.version = new Version("3.0");
                                }
                            }
                        }
                    }
                }
            }
        }
 
        if (find("\\((?:LG[-|/])(.*) (?:Browser/)?AppleWebkit")) {
            Matcher m = matcher("\\((?:LG[-|/])(.*) (?:Browser/)?AppleWebkit");
            m.find();
            this.device.manufacturer = MANUFACTURER.STRINGS_LG;
            this.device.model = m.group(1);
            this.device.type = "mobile";
            this.device.identified = true;
        }
        if (find("^Mozilla/5.0 \\((?:Nokia|NOKIA)(?:\\s?)([^\\)]+)\\)UC AppleWebkit\\(like Gecko\\) Safari/530$")) {
            Matcher m = matcher("^Mozilla/5.0 \\((?:Nokia|NOKIA)(?:\\s?)([^\\)]+)\\)UC AppleWebkit\\(like Gecko\\) Safari/530$");
            m.find();
            this.device.manufacturer = "Nokia";
            this.device.model = m.group(1);
            this.device.type = "mobile";
            this.device.identified = true;
            this.os.name = "Series60";
        }
    }
 
    protected void parseBrowser() {
        // Safari
        if (contains("Safari")) {
            if (this.os.name.equals("iOS")) {
                this.browser.stock = true;
                this.browser.hidden = true;
                this.browser.name = "Safari";
                this.browser.version = null;
            }
 
            if (this.os.name.equals("Mac OS X") || this.os.name.equals("Windows")) {
                this.browser.name = "Safari";
                this.browser.stock = (this.os.name.equals("Windows"));
 
                Matcher m = matcher("Version/([0-9\\.]+)");
                if (m.find()) {
                    this.os.version = new Version(m.group(1));
                }
 
                m = matcher("AppleWebKit/[0-9\\.]+\\+");
                if (m.find()) {
                    this.browser.name = "WebKit Nightly Build";
                    this.browser.version = null;
                }
            }
        }
 
        // Internet Explorer
        if (contains("MSIE")) {
            this.browser.name = "Internet Explorer";
            if (contains("IEMobile") || contains("Windows CE") || contains("Windows Phone") || contains("WP7")) {
                this.browser.name = "Mobile Internet Explorer";
            }
            Matcher m = matcher("MSIE ([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Opera
        if (ua.toLowerCase().indexOf("opera") >= 0) {
            this.browser.stock = false;
            this.browser.name = "Opera";
 
            Matcher m = matcher("Opera[/| ]([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            m = matcher("Version/([0-9.]*)");
            if (m.find()) {
                try {
                    if (Integer.parseInt(m.group(1)) >= 10) {
                        this.browser.version = new Version(m.group(1));
                    } else {
                        this.browser.version = null;
                    }
                } catch (NumberFormatException ex) {
                    this.browser.version = null;
                }
            }
 
            if (this.browser.version != null && contains("Edition Labs")) {
                this.browser.version.type = "alpha";
                this.browser.channel = "Labs";
            }
 
            if (this.browser.version != null && contains("Edition Next")) {
                this.browser.version.type = "alpha";
                this.browser.channel = "Next";
            }
 
            if (contains("Opera Tablet")) {
                this.browser.name = "Opera Mobile";
                this.device.type = "tablet";
            }
 
            if (contains("Opera Mobi")) {
                this.browser.name = "Opera Mobile";
                this.device.type = "mobile";
            }
 
            if (contains("Opera Mini;")) {
                this.browser.name = "Opera Mini";
                this.browser.version = null;
                this.browser.mode = "proxy";
                this.device.type = "mobile";
            }
 
            m = matcher("Opera Mini/(?:att/)?([0-9.]*)");
            if (m.find()) {
                this.browser.name = "Opera Mini";
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = -1;
                this.browser.mode = "proxy";
                this.device.type = "mobile";
            }
 
            if (this.browser.name.equals("Opera") && this.device.type.equals("mobile")) {
                this.browser.name = "Opera Mobile";
                if (contains("BER")) {
                    this.browser.name = "Opera Mini";
                    this.browser.version = null;
                }
            }
 
            if (contains("InettvBrowser")) {
                this.device.type = "television";
            }
 
            if (contains("Opera TV") || contains("Opera-TV")) {
                this.browser.name = "Opera";
                this.device.type = "television";
            }
 
            if (contains("Linux zbov")) {
                this.browser.name = "Opera Mobile";
                this.browser.mode = "desktop";
                this.device.type = "mobile";
                this.os.name = null;
                this.os.version = null;
            }
 
            if (contains("Linux zvav")) {
                this.browser.name = "Opera Mini";
                this.browser.version = null;
                this.browser.mode = "desktop";
                this.device.type = "mobile";
                this.os.name = null;
                this.os.version = null;
            }
        }
 
        // Firefox
        if (contains("Firefox")) {
            this.browser.stock = false;
            this.browser.name = "Firefox";
 
            Matcher m = matcher("Firefox/([0-9ab.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            if (this.browser.version != null && this.browser.version.type.equals("alpha")) {
                this.browser.channel = "Aurora";
            }
            if (this.browser.version != null && this.browser.version.type.equals("beta")) {
                this.browser.channel = "Beta";
            }
 
            if (contains("Fennec")) {
                this.device.type = "mobile";
            }
            if (contains("Mobile; rv")) {
                this.device.type = "mobile";
            }
            if (contains("Tablet; rv")) {
                this.device.type = "tablet";
            }
 
            if (this.device.type.equals("mobile") || this.device.type.equals("tablet")) {
                this.browser.name = "Firefox Mobile";
            }
        }
 
        if (contains("Namoroka")) {
            this.browser.stock = false;
            this.browser.name = "Firefox";
            Matcher m = matcher("/Namoroka/([0-9ab.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            this.browser.channel = "Namoroka";
        }
 
        if (contains("Shiretoko")) {
            this.browser.stock = false;
            this.browser.name = "Firefox";
            Matcher m = matcher("Shiretoko/([0-9ab.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            this.browser.channel = "Shiretoko";
        }
 
        if (contains("Minefield")) {
            this.browser.stock = false;
            this.browser.name = "Firefox";
            Matcher m = matcher("Minefield/([0-9ab.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            this.browser.channel = "Minefield";
        }
 
        if (contains("Firebird")) {
            this.browser.stock = false;
            this.browser.name = "Firebird";
            Matcher m = matcher("Firebird/([0-9ab.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // SeaMonkey
        if (contains("SeaMonkey")) {
            this.browser.stock = false;
            this.browser.name = "SeaMonkey";
            Matcher m = matcher("SeaMonkey/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Netscape
        if (contains("Netscape")) {
            this.browser.stock = false;
            this.browser.name = "Netscape";
            Matcher m = matcher("Netscape[0-9]?/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Konqueror
        if (ua.toLowerCase().indexOf("konqueror") >= 0) {
            this.browser.name = "Konqueror";
            Matcher m = matcher("[k|K]onqueror/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Chrome
        if (find("(?:Chrome|CrMo|CriOS)/([0-9.]*)")) {
            Matcher m = matcher("(?:Chrome|CrMo|CriOS)/([0-9.]*)");
            m.find();
            this.browser.stock = false;
            this.browser.name = "Chrome";
            this.browser.version = new Version(m.group(1));
 
            String version_prefix = "";
            for (String v : Arrays.copyOfRange(m.group(1).split("\\."), 0, 3)) {
                version_prefix += "." + v;
            }
            version_prefix = version_prefix.substring(1);
            if (this.os.name.equals("Android")) {
                if (version_prefix.equals("16.0.912")) {
                    this.browser.channel = "Beta";
                } else if (version_prefix.equals("18.0.1025")) {
                    this.browser.version.details = 1;
                } else {
                    this.browser.channel = "Nightly";
                }
            }
        }
 
        // Chrome Frame
        if (contains("chromeframe")) {
            this.browser.stock = false;
            this.browser.name = "Chrome Frame";
            Matcher m = matcher("chromeframe/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Chromium
        if (contains("Chromium")) {
            this.browser.stock = false;
            this.browser.channel = "";
            this.browser.name = "Chromium";
            Matcher m = matcher("Chromium/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
        // BrowserNG
        if (contains("BrowserNG")) {
            this.browser.name = "Nokia Browser";
            Matcher m = matcher("BrowserNG/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = 3;
            }
        }
 
        // Nokia Browser
        if (contains("NokiaBrowser")) {
            this.browser.name = "Nokia Browser";
            Matcher m = matcher("NokiaBrowser/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = 3;
            }
        }
 
        // MicroB
        if (find("Maemo[ |_]Browser")) {
            this.browser.name = "MicroB";
            Matcher m = matcher("Maemo[ |_]Browser[ |_]([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = 3;
            }
        }
 
        // NetFront
        if (contains("NetFront")) {
            this.browser.name = "NetFront";
            this.device.type = "mobile";
            Matcher m = matcher("NetFront/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            if (contains("InettvBrowser")) {
                this.device.type = "television";
            }
        }
 
        // Silk
        if (contains("Silk-Accelerated")) {
            this.browser.name = "Silk";
            Matcher m = matcher("Silk/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = 2;
            }
            this.device.manufacturer = "Amazon";
            this.device.model = "Kindle Fire";
            this.device.type = "tablet";
            this.device.identified = true;
 
            if (!this.os.name.equals("Android")) {
                this.os.name = "Android";
                this.os.version = null;
            }
        }
 
        // Dolfin
        if (contains("Dolfin")) {
            this.browser.name = "Dolfin";
            Matcher m = matcher("Dolfin/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Iris
        if (contains("Iris")) {
            this.browser.name = "Iris";
            this.device.type = "mobile";
            this.device.model = null;
            this.device.manufacturer = null;
            this.os.name = "Windows Mobile";
            this.os.version = null;
            Matcher m = matcher("Iris/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            m = matcher(" WM([0-9])");
            if (m.find()) {
                this.browser.version = new Version(m.group(1) + ".0");
            } else {
                this.browser.mode = "desktop";
            }
        }
 
        // Jasmine
        if (contains("Jasmine")) {
            this.browser.name = "Jasmine";
            Matcher m = matcher("Jasmine/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Boxee
        if (contains("Boxee")) {
            this.browser.name = "Boxee";
            this.device.type = "television";
            Matcher m = matcher("Boxee/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // Espial
        if (contains("Espial")) {
            this.browser.name = "Espial";
            this.os.name = "";
            this.os.version = null;
            if (!this.device.type.equals("television")) {
                this.device.type = "television";
                this.device.model = null;
                this.device.manufacturer = null;
            }
            Matcher m = matcher("Espial/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
        }
 
        // ANT Galio
        if (find("ANTGalio/([0-9.]*)")) {
            Matcher m = matcher("ANTGalio/([0-9.]*)");
            m.find();
            this.browser.name = "ANT Galio";
            this.browser.version = new Version(m.group(1));
            this.browser.version.details = 3;
            this.device.type = "television";
        }
 
        // NetFront NX
        if (find("NX/([0-9.]*)")) {
            Matcher m = matcher("NX/([0-9.]*)");
            m.find();
            this.browser.name = "NetFront NX";
            this.browser.version = new Version(m.group(1));
            this.browser.version.details = 2;
            if (ua.toUpperCase().indexOf("DTV") >= 0) {
                this.device.type = "television";
            } else if (ua.toLowerCase().indexOf("mobile") >= 0) {
                this.device.type = "mobile";
            } else {
                this.device.type = "desktop";
            }
            this.os.name = null;
            this.os.version = null;
        }
 
        // Obigo
        if (ua.toLowerCase().indexOf("obigo") >= 0) {
            this.browser.name = "Obigo";
 
            Matcher m = matcher("Obigo/([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            m = matcher("Obigo/([A-Z])([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.browser.name = "Obigo " + m.group(1);
                this.browser.version = new Version(m.group(2));
            }
            m = matcher("Obigo-([A-Z])([0-9.]*)/", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.browser.name = "Obigo " + m.group(1);
                this.browser.version = new Version(m.group(2));
            }
        }
 
        // UC Web
        if (contains("UCWEB")) {
            this.browser.stock = false;
            this.browser.name = "UC Browser";
 
            Matcher m = matcher("UCWEB([0-9]*[.][0-9]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = 3;
            }
 
            if (this.os.name.equals("Linux")) {
                this.os.name = "";
            }
 
            this.device.type = "mobile";
 
            m = matcher("^IUC \\(U;\\s?iOS ([0-9\\.]+);");
            if (m.find()) {
                this.os.name = "iOS";
                this.os.version = new Version(m.group(1));
            }
 
            m = matcher("^JUC \\(Linux; U; ([0-9\\.]+)[^;]*; [^;]+; ([^;]*[^\\s])\\s*; [0-9]+\\*[0-9]+\\)");
            if (m.find()) {
                String model = cleanupModel(m.group(2));
                this.os.name = "Android";
                this.os.version = new Version(m.group(1));
 
                detectAndroidModel(model);
            }
 
            m = matcher("\\) UC");
            if (m.find()) {
                this.browser.stock = false;
                this.browser.name = "UC Browser";
            }
 
            m = matcher("UCBrowser/([0-9.]*)");
            if (m.find()) {
                this.browser.stock = false;
                this.browser.name = "UC Browser";
                this.browser.version = new Version(m.group(1));
                this.browser.version.details = 2;
            }
        }
 
        // NineSky
        if (find("NineSky(?:-android-mobile(?:-cn)?)?/([0-9.]*)")) {
            Matcher m = matcher("NineSky(?:-android-mobile(?:-cn)?)?/([0-9.]*)");
            m.find();
            this.browser.name = "NineSky";
            this.browser.version = new Version(m.group(1));
            if (!this.os.name.equals("Android")) {
                this.os.name = "Android";
                this.os.version = null;
                this.device.manufacturer = null;
                this.device.model = null;
            }
        }
 
        // Skyfire
        if (find("Skyfire/([0-9.]*)")) {
            Matcher m = matcher("Skyfire/([0-9.]*)");
            if (m.find()) {
                this.browser.version = new Version(m.group(1));
            }
            this.browser.name = "Skyfire";
            this.device.type = "mobile";
            this.os.name = "Android";
            this.os.version = null;
        }
 
        // Dolphin HD
        if (find("DolphinHDCN/([0-9.]*)")) {
            Matcher m = matcher("DolphinHDCN/([0-9.]*)");
            m.find();
            this.browser.name = "Dolphin";
            this.browser.version = new Version(m.group(1));
            this.device.type = "mobile";
            if (!this.os.name.equals("Android")) {
                this.os.name = "Android";
                this.os.version = null;
            }
        }
        if (find("Dolphin/INT")) {
            this.browser.name = "Dolphin";
            this.device.type = "mobile";
        }
 
        // QQ Browser
        if (find("(M?QQBrowser)/([0-9.]*)")) {
            Matcher m = matcher("(M?QQBrowser)/([0-9.]*)");
            this.browser.name = "QQ Browser";
            m.find();
            String version = m.group(2);
            if (find("^[0-9][0-9]$", version)) {
                version = version.charAt(0) + "." + version.charAt(1);
            }
            this.browser.version = new Version(version);
            this.browser.version.details = 2;
            this.browser.channel = "";
            if ((this.os.name == null || this.os.name.isEmpty()) || m.group(1).equals("QQBrowser")) {
                this.os.name = "Windows";
            }
        }
 
        // iBrowser
        if (find("(iBrowser)/([0-9.]*)")) {
            Matcher m = matcher("(iBrowser)/([0-9.]*)");
            m.find();
            this.browser.name = "iBrowser";
            String version = m.group(2);
            if (find("^[0-9][0-9]$", version)) {
                version = version.charAt(0) + "." + version.charAt(1);
            }
            this.browser.version = new Version(version);
            this.browser.version.details = 2;
            this.browser.channel = "";
        }
 
        // Puffin
        if (find("Puffin/([0-9.]*)")) {
            Matcher m = matcher("Puffin/([0-9.]*)");
            m.find();
            this.browser.name = "Puffin";
            this.browser.version = new Version(m.group(1));
            this.browser.version.details = 2;
            this.device.type = "mobile";
            if (this.os.name.equals("Linux")) {
                this.os.name = null;
                this.os.version = null;
            }
        }
 
        // 360 Extreme Explorer
        if (contains("360EE")) {
            this.browser.stock = false;
            this.browser.name = "360 Extreme Explorer";
            this.browser.version = null;
        }
 
        // Midori
        if (find("Midori/([0-9.]*)")) {
            Matcher m = matcher("Midori/([0-9.]*)");
            m.find();
            this.browser.name = "Midori";
            this.browser.version = new Version(m.group(1));
            if (!this.os.name.equals("Linux")) {
                this.os.name = "Linux";
                this.os.version = null;
            }
            this.device.manufacturer = null;
            this.device.model = null;
            this.device.type = "desktop";
        }
 
        // Others
        for (Map<String, String> b : MANUFACTURER.OTHER_BROWSERS) {
            Matcher m = null;
            if (b.containsKey("flag")) {
                try {
                    m = matcher(b.get("regexp"), Integer.parseInt(b.get("flag")));
                } catch (NumberFormatException ex) {
                }
            } else {
                m = matcher(b.get("regexp"));
            }
            if (m != null && m.find()) {
                this.browser.name = b.get("name");
                this.browser.channel = "";
                this.browser.stock = false;
 
                if (m.groupCount() > 0) {
                    this.browser.version = new Version(m.group(1));
                    if (b.containsKey("details")) {
                        try {
                            this.browser.version.details = Integer.parseInt(b.get("details"));
                        } catch (NumberFormatException ex) {
                        }
                    }
                } else {
                    this.browser.version = null;
                }
            }
        }
    }
 
    private void detectAndroidModel(String model) {
        if (MANUFACTURER.ANDROID_MODELS.containsKey(model)) {
            this.device.manufacturer = MANUFACTURER.ANDROID_MODELS.get(model)[0];
            this.device.model = MANUFACTURER.ANDROID_MODELS.get(model)[1];
            if (MANUFACTURER.ANDROID_MODELS.get(model).length > 2) {
                this.device.type = MANUFACTURER.ANDROID_MODELS.get(model)[2];
            }
            this.device.identified = true;
        }
    }
 
    protected void parseEngine() {
        // WebKit
        if (find("WebKit/([0-9.]*)", Pattern.CASE_INSENSITIVE)) {
            this.engine.name = "WebKit";
            Matcher m = matcher("WebKit/([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.engine.version = new Version(m.group(1));
            }
        }
        if (find("Browser/AppleWebKit([0-9.]*)", Pattern.CASE_INSENSITIVE)) {
            this.engine.name = "WebKit";
            Matcher m = matcher("Browser/AppleWebKit([0-9.]*)", Pattern.CASE_INSENSITIVE);
            if (m.find()) {
                this.engine.version = new Version(m.group(1));
            }
        }
 
        // KHTML
        if (find("KHTML/([0-9.]*)")) {
            this.engine.name = "KHTML";
            Matcher m = matcher("KHTML/([0-9.]*)");
            if (m.find()) {
                this.engine.version = new Version(m.group(1));
            }
        }
 
        // Gecko
        if (contains("Gecko") && ua.toLowerCase().indexOf("like gecko") < 0) {
            this.engine.name = "Gecko";
            Matcher m = matcher("; rv:([^\\)]+)\\)");
            if (m.find()) {
                this.engine.version = new Version(m.group(1));
            }
        }
 
        // Presto
        if (find("Presto/([0-9.]*)")) {
            this.engine.name = "Presto";
            Matcher m = matcher("Presto/([0-9.]*)");
            if (m.find()) {
                this.engine.version = new Version(m.group(1));
            }
        }
 
        // Trident
        if (find("Trident/([0-9.]*)")) {
            Matcher m = matcher("Trident/([0-9.]*)");
            this.engine.name = "Trident";
            if (m.find()) {
                this.engine.version = new Version(m.group(1));
            }
            if (this.browser.name.equals("Internet Explorer")) {
 
                float engineVersion = Version.parseVersion(this.engine.version);
                BigDecimal engineVersionBD = BigDecimal.valueOf(engineVersion);
 
                float browserVersion = Version.parseVersion(this.browser.version);
                BigDecimal browserVersionBD = BigDecimal.valueOf(browserVersion);
 
 
                if (BigDecimal.valueOf(6.0).equals(engineVersionBD) && BigDecimal.valueOf(10.0).compareTo(browserVersionBD) > 0) {
                    this.browser.version.value = "10.0";
                    this.browser.mode = "compat";
                }
                if (BigDecimal.valueOf(5.0).equals(engineVersionBD) && BigDecimal.valueOf(9.0).compareTo(browserVersionBD) > 0) {
                    this.browser.version.value = "9.0";
                    this.browser.mode = "compat";
                }
                if (BigDecimal.valueOf(4.0).equals(engineVersionBD) && BigDecimal.valueOf(8.0).compareTo(browserVersionBD) > 0) {
                    this.browser.version.value = "8.0";
                    this.browser.mode = "compat";
                }
            }
            if (this.os.name.equals("Windows Phone")) {
                float engineVersion = Version.parseVersion(this.engine.version);
                BigDecimal engineVersionBD = BigDecimal.valueOf(engineVersion);
 
                float browserVersion = Version.parseVersion(this.browser.version);
                BigDecimal browserVersionBD = BigDecimal.valueOf(browserVersion);
 
                if (BigDecimal.valueOf(5.0).equals(engineVersionBD) && BigDecimal.valueOf(7.5).compareTo(browserVersionBD) > 0) {
                    this.os.version = new Version("7.5");
                }
            }
        }
    }
 
    protected void clearCamouflage() {
        // Corrections
        if (this.os.name != null && this.os.name.equals("Android") && this.browser.stock) {
            this.browser.hidden = true;
        }
        if (this.os.name != null && this.os.name.equals("iOS") && this.browser.name.equals("Opera Mini")) {
            this.os.version = null;
        }
        if (this.browser.name.equals("Midori") && this.engine.name.equals("Webkit")) {
            this.engine.name = "Webkit";
            this.engine.version = null;
        }
        if (this.device.type.equals("television") && this.browser.name.equals("Opera")) {
            this.browser.name = "Opera Devices";
            if (this.engine.version != null) {
                if (this.engine.version.value.equals("2.10")) {
                    this.engine.version = new Version("3.2");
                } else if (this.engine.version.value.equals("2.9")) {
                    this.engine.version = new Version("3.1");
                } else if (this.engine.version.value.equals("2.8")) {
                    this.engine.version = new Version("3.0");
                } else if (this.engine.version.value.equals("2.7")) {
                    this.engine.version = new Version("2.9");
                } else if (this.engine.version.value.equals("2.6")) {
                    this.engine.version = new Version("2.8");
                } else if (this.engine.version.value.equals("2.4")) {
                    this.engine.version = new Version("10.3");
                } else if (this.engine.version.value.equals("2.3")) {
                    this.engine.version = new Version("10");
                } else if (this.engine.version.value.equals("2.2")) {
                    this.engine.version = new Version("9.7");
                } else if (this.engine.version.value.equals("2.1")) {
                    this.engine.version = new Version("9.6");
                } else {
                    this.engine.version = null;
                }
            }
            this.os.name = null;
            this.os.version = null;
        }
 
        // Camouflage
        if (this.detectCamouflage) {
            Matcher m = matcher("Mac OS X 10_6_3; ([^;]+); [a-z]{2}-(?:[a-z]{2})\\)");
            if (m.find()) {
                this.browser.name = "";
                this.browser.version = null;
                this.browser.mode = "desktop";
 
                this.os.name = "Android";
                this.os.version = null;
 
                this.engine.name = "Webkit";
                this.engine.version = null;
 
                this.device.model = m.group(1);
                this.device.type = "mobile";
 
                String model = cleanupModel(this.device.model);
                detectAndroidModel(model);
            }
 
            m = matcher("Linux Ventana; [a-z]{2}-[a-z]{2}; (.+) Build");
            if (m.find()) {
                this.browser.name = "";
                this.browser.version = null;
                this.browser.mode = "desktop";
 
                this.os.name = "Android";
                this.os.version = null;
 
                this.device.model = m.group(1);
                this.device.type = "mobile";
 
                String model = cleanupModel(this.device.model);
                detectAndroidModel(model);
            }
        }
    }
 
    /**
     * correct mobile device
     */
    protected void correctDevice() {
        Matcher match;
        Matcher tmpMatch;
 
        if (this.device.type.equals("mobile") || this.device.type.equals("tablet")) {
            // get manufacturer through key words
            match = matcher("(ZTE|Samsung|Motorola|HTC|Coolpad|Huawei|Lenovo|LG|Sony Ericsson|Oppo|TCL|Vivo|Sony|Meizu|Nokia)", Pattern.CASE_INSENSITIVE);
            if (match.find()) {
                this.device.manufacturer = match.group(1);
                if (this.device.model != null && this.device.model.indexOf(match.group(1)) >= 0) {
                    this.device.model = this.device.model.replace(match.group(1), "");
                }
            }
            if (find("(iPod|iPad|iPhone)", Pattern.CASE_INSENSITIVE)) {
                match = matcher("(iPod|iPad|iPhone)", Pattern.CASE_INSENSITIVE);
                match.find();
                // handle Apple
                // 苹果就这3种:iPod、iPad、iPhone
                this.device.manufacturer = "Apple";
                this.device.model = match.group(1);
            } else if (find("[-\\s](Galaxy[-\\s_]nexus|Galaxy[-\\s_]\\w*[-\\s_]\\w*|Galaxy[-\\s_]\\w*|SM-\\w*|GT-\\w*|s[cgp]h-\\w*|shw-\\w*|ATIV|i9070|omnia|s7568|A3000|A3009|A5000|A5009|A7000|A7009|A8000|C101|C1116|C1158|E400|E500F|E7000|E7009|G3139D|G3502|G3502i|G3508|G3508J|G3508i|G3509|G3509i|G3558|G3559|G3568V|G3586V|G3589W|G3606|G3608|G3609|G3812|G388F|G5108|G5108Q|G5109|G5306W|G5308W|G5309W|G550|G600|G7106|G7108|G7108V|G7109|G7200|G720NO|G7508Q|G7509|G8508S|G8509V|G9006V|G9006W|G9008V|G9008W|G9009D|G9009W|G9198|G9200|G9208|G9209|G9250|G9280|I535|I679|I739|I8190N|I8262|I879|I879E|I889|I9000|I9060|I9082|I9082C|I9082i|I9100|I9100G|I9108|I9128|I9128E|I9128i|I9152|I9152P|I9158|I9158P|I9158V|I9168|I9168i|I9190|I9192|I9195|I9195I|I9200|I9208|I9220|I9228|I9260|I9268|I9300|I9300i|I9305|I9308|I9308i|I939|I939D|I939i|I9500|I9502|I9505|I9507V|I9508|I9508V|I959|J100|J110|J5008|J7008|N7100|N7102|N7105|N7108|N7108D|N719|N750|N7505|N7506V|N7508V|N7509V|N900|N9002|N9005|N9006|N9008|N9008S|N9008V|N9009|N9100|N9106W|N9108V|N9109W|N9150|N916|N9200|P709|P709E|P729|S6358|S7278|S7278U|S7562C|S7562i|S7898i|b9388)[\\s\\)]", Pattern.CASE_INSENSITIVE)) {
                // handle Samsung
                // 特殊型号可能以xxx-开头 或者直接空格接型号 兼容build结尾或直接)结尾
                // Galaxy nexus才是三星 nexus是google手机
                // 三星手机类型:galaxy xxx|SM-xxx|GT-xxx|SCH-xxx|SGH-xxx|SPH-xxx|SHW-xxx  若这些均未匹配到,则启用在中关村在线爬取到的机型白名单进行判断
                match = matcher("[-\\s](Galaxy[-\\s_]nexus|Galaxy[-\\s_]\\w*[-\\s_]\\w*|Galaxy[-\\s_]\\w*|SM-\\w*|GT-\\w*|s[cgp]h-\\w*|shw-\\w*|ATIV|i9070|omnia|s7568|A3000|A3009|A5000|A5009|A7000|A7009|A8000|C101|C1116|C1158|E400|E500F|E7000|E7009|G3139D|G3502|G3502i|G3508|G3508J|G3508i|G3509|G3509i|G3558|G3559|G3568V|G3586V|G3589W|G3606|G3608|G3609|G3812|G388F|G5108|G5108Q|G5109|G5306W|G5308W|G5309W|G550|G600|G7106|G7108|G7108V|G7109|G7200|G720NO|G7508Q|G7509|G8508S|G8509V|G9006V|G9006W|G9008V|G9008W|G9009D|G9009W|G9198|G9200|G9208|G9209|G9250|G9280|I535|I679|I739|I8190N|I8262|I879|I879E|I889|I9000|I9060|I9082|I9082C|I9082i|I9100|I9100G|I9108|I9128|I9128E|I9128i|I9152|I9152P|I9158|I9158P|I9158V|I9168|I9168i|I9190|I9192|I9195|I9195I|I9200|I9208|I9220|I9228|I9260|I9268|I9300|I9300i|I9305|I9308|I9308i|I939|I939D|I939i|I9500|I9502|I9505|I9507V|I9508|I9508V|I959|J100|J110|J5008|J7008|N7100|N7102|N7105|N7108|N7108D|N719|N750|N7505|N7506V|N7508V|N7509V|N900|N9002|N9005|N9006|N9008|N9008S|N9008V|N9009|N9100|N9106W|N9108V|N9109W|N9150|N916|N9200|P709|P709E|P729|S6358|S7278|S7278U|S7562C|S7562i|S7898i|b9388)[\\s\\)]", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Samsung";
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z]+[0-9]+[A-Z]*, 例如 G9006 G9006V 其实应该是G9006 另外三星只保留3位
                String model = match.group(1).replaceAll("(?i)Galaxy S VI", "Galaxy S6");
                model = model.replaceAll("(?i)Galaxy V", "Galaxy S5");
                model = model.replaceAll("(?i)Galaxy IV", "Galaxy S4");
                model = model.replaceAll("(?i)Galaxy s III", "Galaxy S3");
                model = model.replaceAll("(?i)Galaxy S II", "Galaxy S2");
                model = model.replaceAll("(?i)Galaxy S I", "Galaxy S1");
                model = model.replaceAll("(?i)([a-z]+[0-9]{3})[0-9]?[a-z]*", "$1");
                this.device.model = model;
            } else if (this.device.manufacturer != null && this.device.manufacturer.toLowerCase().equals("samsung") && this.device.model != null) {
                // 针对三星已经匹配出的数据做处理
                String model = this.device.model.replaceAll("(?i)Galaxy S VI", "Galaxy S6");
                model = model.replaceAll("(?i)Galaxy V", "Galaxy S5");
                model = model.replaceAll("(?i)Galaxy IV", "Galaxy S4");
                model = model.replaceAll("(?i)Galaxy s III", "Galaxy S3");
                model = model.replaceAll("(?i)Galaxy S II", "Galaxy S2");
                model = model.replaceAll("(?i)Galaxy S I", "Galaxy S1");
                model = model.replaceAll("(?i)([a-z]+[0-9]{3})[0-9]?[a-z]*", "$1");
                this.device.model = model;
            } else if (find("(Huawei[-\\s_](\\w*[-_]?\\w*)|\\s(7D-\\w*|ALE-\\w*|ATH-\\w*|CHE-\\w*|CHM-\\w*|Che1-\\w*|Che2-\\w*|D2-\\w*|G616-\\w*|G620S-\\w*|G621-\\w*|G660-\\w*|G750-\\w*|GRA-\\w*|Hol-\\w*|MT2-\\w*|MT7-\\w*|PE-\\w*|PLK-\\w*|SC-\\w*|SCL-\\w*|H60-\\w*|H30-\\w*)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle Huawei
                // 兼容build结尾或直接)结尾
                // 华为机型特征:Huawei[-\s_](\w*[-_]?\w*)  或者以 7D-  ALE-  CHE-等开头
                match = matcher("(Huawei[-\\s_](\\w*[-_]?\\w*)|\\s(7D-\\w*|ALE-\\w*|ATH-\\w*|CHE-\\w*|CHM-\\w*|Che1-\\w*|Che2-\\w*|D2-\\w*|G616-\\w*|G620S-\\w*|G621-\\w*|G660-\\w*|G750-\\w*|GRA-\\w*|Hol-\\w*|MT2-\\w*|MT7-\\w*|PE-\\w*|PLK-\\w*|SC-\\w*|SCL-\\w*|H60-\\w*|H30-\\w*)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Huawei";
                if (match.groupCount() >= 2 && !StringUtils.isEmpty(match.group(2))) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() >= 3 && !StringUtils.isEmpty(match.group(3))) {
                    this.device.model = match.group(3);
                }
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:xxx-[A-Z][0-9]+ 例如  H30-L01  H30-L00  H30-L20  都应该是 H30-L
                // h30-l  h30-h  h30-t 都是H30
                match = matcher("(\\w*)[-\\s_]+[a-z0-9]+", this.device.model, Pattern.CASE_INSENSITIVE);
                if (match.find()) {
                    this.device.model = match.group(1);
                }
            } else if (find(";\\s(mi|m1|m2|m3|m4|hm)(\\s*\\w*)[\\s\\)]", Pattern.CASE_INSENSITIVE)) {
                // handle Xiaomi
                // 兼容build结尾或直接)结尾 以及特殊的HM处理方案(build/hm2013011)
                // xiaomi手机类型: mi m1 m2 m3 hm 开头
                // hongmi有特殊判断build/hm2015011
                match = matcher(";\\s(mi|m1|m2|m3|m4|hm)(\\s*\\w*)[\\s\\)]", Pattern.CASE_INSENSITIVE);
                match.find();
                tmpMatch = matcher("(meitu|MediaPad)", Pattern.CASE_INSENSITIVE);
                if (tmpMatch.find()) { // 美图手机名字冒充小米 比如 meitu m4 mizhi
                    this.device.manufacturer = tmpMatch.group(1);
                    this.device.model = "";
                } else if (match.groupCount() >= 2 && match.group(2) != null && match.group(2).length() > 0 && !find("\\s", match.group(2))) {
                    // 若匹配出的 match[2]没空格 会出现很多例如 mizi mizhi miha 但也会出现mi3 minote之类 特殊处理下
                    tmpMatch = matcher("(\\d)", match.group(2), Pattern.CASE_INSENSITIVE);
                    if (tmpMatch.find()) {
                        this.device.model = match.group(1) + "-" + tmpMatch.group(1);
                    }
                } else {
                    this.device.manufacturer = "Xiaomi";
                    if (match.groupCount() >= 2 && match.group(2) != null && match.group(2).length() > 0) {
                        String m = match.group(2).replaceAll("\\s", "");
                        this.device.model = (match.group(1).substring(match.group(1).length() - 2, match.group(1).length()) + "-" + m).replaceAll("(\\d)-", "MI-$1");
                    } else {
                        this.device.model = match.group(1).substring(match.group(1).length() - 2, match.group(1).length()).replaceAll("(\\d)", "MI-$1");
                    }
                    // 解决移动联通等不同发行版导致的机型不同问题
                    // 特征:mi-3c,例如mi-4LTE mi-4 其实应该是 mi-4
                    if (find("(mi|hm)(-\\d)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                        if (find("(mi|hm)(-\\ds)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                            // 看看是不是 MI-3S  MI-4S....
                            match = matcher("(mi|hm)(-\\ds)", this.device.model, Pattern.CASE_INSENSITIVE);
                            if (match.find()) {
                                this.device.model = match.group(1) + match.group(2);
                            }
                        } else if (find("(mi|hm)(-\\d{2})", this.device.model, Pattern.CASE_INSENSITIVE)) {
                            // 防止 MI-20150XX等滥竽充数成为MI-2
                            match = matcher("(mi|hm)(-\\d{2})", this.device.model, Pattern.CASE_INSENSITIVE);
                            if (match.find()) {
                                this.device.model = match.group(1);
                            }
                        } else if (find("(mi|hm)(-\\d)[A-Z]", this.device.model, Pattern.CASE_INSENSITIVE)) {
                            // 将mi-3c mi-3a mi-3w等合为mi-3
                            match = matcher("(mi|hm)(-\\d)[A-Z]", this.device.model, Pattern.CASE_INSENSITIVE);
                            if (match.find()) {
                                this.device.model = match.group(1) + match.group(2);
                            }
                        }
                    }
                    // 去除 mi-4g这样的东西
                    match = matcher("(mi|hm)(-\\dg)", this.device.model, Pattern.CASE_INSENSITIVE);
                    if (match.find()) {
                        this.device.model = match.group(1);
                    }
                }
            } else if (find("build/HM\\d{0,7}\\)", Pattern.CASE_INSENSITIVE)) {
                this.device.manufacturer = "Xiaomi";
                this.device.model = "HM";
            } else if (this.device.manufacturer != null && this.device.manufacturer.toLowerCase().equals("xiaomi") && this.device.model != null) {
                // 针对通过base库判断出数据时命名风格不同。特殊处理适配如下
                if (find("mi-one", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    this.device.model = "MI-1";
                } else if (find("mi-two", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    // mi 2
                    this.device.model = "MI-2";
                } else if (find("\\d{6}", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    // 20150xxx2014501
                    this.device.model = "";
                } else if (find("redmi", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    this.device.model = this.device.model.toUpperCase().replaceAll("(?i)redmi", "HM");
                } else if (find("(m\\d)[-\\s_](s?)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    // m1 m2 m3 写法不标准 另外判断是否是 m1-s
                    match = matcher("(m\\d)[-\\s_](s?)", this.device.model, Pattern.CASE_INSENSITIVE);
                    if (match.find()) {
                        this.device.model = match.group(1).replace("m", "MI-");
                    }
                } else if (find("(hm|mi)[-\\s_](\\d?)[a-rt-z]", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    // mi-2w  mi-3w 等格式化为mi-2  mi-3
                    match = matcher("(hm|mi)[-\\s_](\\d?)[a-rt-z]", this.device.model, Pattern.CASE_INSENSITIVE);
                    tmpMatch = matcher("(mi|hm)[-\\s_](note|pad)(\\d?s?)", this.device.model, Pattern.CASE_INSENSITIVE);
                    match.find();
                    if (tmpMatch.find()) {
                        this.device.model = tmpMatch.group(1) + "-" + tmpMatch.group(2) + tmpMatch.group(3);
                    } else {
                        if (match.groupCount() > 1) {
                            this.device.model = match.group(1) + "-" + match.group(2);
                        } else {
                            this.device.model = match.group(1);
                        }
                    }
                } else if (find("hm", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    // 处理hm
                    if (find("(hm)[-\\s_](\\d{2})", this.device.model, Pattern.CASE_INSENSITIVE)) {
                        // 判断是不是 hm-201xxx充数
                        this.device.model = "HM";
                    } else if (find("(hm)[-\\s_](\\ds)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                        // 判断是不是 hm-2s hm-1s
                        match = matcher("(hm)[-\\s_](\\ds)", this.device.model, Pattern.CASE_INSENSITIVE);
                        match.find();
                        this.device.model = "HM-" + match.group(2);
                    } else if (find("(hm)[-\\s_](\\d)[a-z]", this.device.model, Pattern.CASE_INSENSITIVE)) {
                        match = matcher("(hm)[-\\s_](\\d)[a-z]", this.device.model, Pattern.CASE_INSENSITIVE);
                        match.find();
                        this.device.model = "HM-" + match.group(2);
                    } else {
                        this.device.model = "HM";
                    }
                    if (find("hm-\\dg", this.device.model)) {
                        this.device.model = "HM";
                    }
                }
            } else if (find("(vivo[-\\s_](\\w*)|\\s(E1\\w?|E3\\w?|E5\\w?|V1\\w?|V2\\w?|S11\\w?|S12\\w?|S1\\w?|S3\\w?|S6\\w?|S7\\w?|S9\\w?|X1\\w?|X3\\w?|X520\\w?|X5\\w?|X5Max|X5Max+|X5Pro|X5SL|X710F|X710L|Xplay|Xshot|Xpaly3S|Y11\\w?|Y11i\\w?|Y11i\\w?|Y13\\w?|Y15\\w?|Y17\\w?|Y18\\w?|Y19\\w?|Y1\\w?|Y20\\w?|Y22\\w?|Y22i\\w?|Y23\\w?|Y27\\w?|Y28\\w?|Y29\\w?|Y33\\w?|Y37\\w?|Y3\\w?|Y613\\w?|Y622\\w?|Y627\\w?|Y913\\w?|Y923\\w?|Y927\\w?|Y928\\w?|Y929\\w?|Y937\\w?)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle Vivo
                // 兼容build结尾或直接)结尾
                // vivo机型特征: Vivo[-\s_](\w*)  或者以 E1  S11t  S7t 等开头
                match = matcher("(vivo[-\\s_](\\w*)|\\s(E1\\w?|E3\\w?|E5\\w?|V1\\w?|V2\\w?|S11\\w?|S12\\w?|S1\\w?|S3\\w?|S6\\w?|S7\\w?|S9\\w?|X1\\w?|X3\\w?|X520\\w?|X5\\w?|X5Max|X5Max+|X5Pro|X5SL|X710F|X710L|Xplay|Xshot|Xpaly3S|Y11\\w?|Y11i\\w?|Y11i\\w?|Y13\\w?|Y15\\w?|Y17\\w?|Y18\\w?|Y19\\w?|Y1\\w?|Y20\\w?|Y22\\w?|Y22i\\w?|Y23\\w?|Y27\\w?|Y28\\w?|Y29\\w?|Y33\\w?|Y37\\w?|Y3\\w?|Y613\\w?|Y622\\w?|Y627\\w?|Y913\\w?|Y923\\w?|Y927\\w?|Y928\\w?|Y929\\w?|Y937\\w?)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Vivo";
                // 首先剔除 viv-  vivo-  bbg- 等打头的内容
                this.device.model = match.group(1).replaceAll("(?i)(viv[-\\s_]|vivo[-\\s_]|bbg[-\\s_])", "");
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z][0-9]+[A-Z] 例如  X5F X5L X5M X5iL 都应该是 X5
                match = matcher("([a-z]+[0-9]+)i?[a-z]?[-\\s_]?", this.device.model, Pattern.CASE_INSENSITIVE);
                if (match.find()) {
                    this.device.model = match.group(1);
                }
            } else if (find("(Oppo[-\\s_](\\w*)|\\s(1100|1105|1107|3000|3005|3007|6607|A100|A103|A105|A105K|A109|A109K|A11|A113|A115|A115K|A121|A125|A127|A129|A201|A203|A209|A31|A31c|A31t|A31u|A51kc|A520|A613|A615|A617|E21W|Find|Mirror|N5110|N5117|N5207|N5209|R2010|R2017|R6007|R7005|R7007|R7c|R7t|R8000|R8007|R801|R805|R807|R809T|R8107|R8109|R811|R811W|R813T|R815T|R815W|R817|R819T|R8200|R8205|R8207|R821T|R823T|R827T|R830|R830S|R831S|R831T|R833T|R850|Real|T703|U2S|U521|U525|U529|U539|U701|U701T|U705T|U705W|X9000|X9007|X903|X905|X9070|X9077|X909|Z101|R829T)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle Oppo
                match = matcher("(Oppo[-\\s_](\\w*)|\\s(1100|1105|1107|3000|3005|3007|6607|A100|A103|A105|A105K|A109|A109K|A11|A113|A115|A115K|A121|A125|A127|A129|A201|A203|A209|A31|A31c|A31t|A31u|A51kc|A520|A613|A615|A617|E21W|Find|Mirror|N5110|N5117|N5207|N5209|R2010|R2017|R6007|R7005|R7007|R7c|R7t|R8000|R8007|R801|R805|R807|R809T|R8107|R8109|R811|R811W|R813T|R815T|R815W|R817|R819T|R8200|R8205|R8207|R821T|R823T|R827T|R830|R830S|R831S|R831T|R833T|R850|Real|T703|U2S|U521|U525|U529|U539|U701|U701T|U705T|U705W|X9000|X9007|X903|X905|X9070|X9077|X909|Z101|R829T)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Oppo";
                if (match.groupCount() >= 2 && match.group(2) != "" && match.group(2) != null) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() >= 3 && match.group(3) != "" && match.group(3) != null) {
                    this.device.model = match.group(3);
                }
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z][0-9]+[A-Z] 例如  A31c A31s 都应该是 A31
                // 对 Plus 做特殊处理
                if (find("([a-z]+[0-9]+)-?(plus)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    Matcher m = matcher("([a-z]+[0-9]+)-?(plus)", this.device.model, Pattern.CASE_INSENSITIVE);
                    m.find();
                    this.device.model = m.group(1) + "-" + m.group(2);
                } else if (find("(\\w*-?[a-z]+[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    Matcher m = matcher("(\\w*-?[a-z]+[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE);
                    m.find();
                    this.device.model = m.group(1);
                }
            } else if (this.device.manufacturer != null && this.device.manufacturer.toLowerCase().equals("oppo") && this.device.model != null) {
                // 针对base库的数据做数据格式化处理
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z][0-9]+[A-Z] 例如  A31c A31s 都应该是 A31
                // 对 Plus 做特殊处理
                if (find("([a-z]+[0-9]+)-?(plus)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    match = matcher("([a-z]+[0-9]+)-?(plus)", this.device.model, Pattern.CASE_INSENSITIVE);
                    match.find();
                    this.device.model = match.group(1) + "-" + match.group(2);
                } else if (find("(\\w*-?[a-z]+[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    match = matcher("(\\w*-?[a-z]+[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE);
                    match.find();
                    this.device.model = match.group(1);
                }
            } else if (find("(Lenovo[-\\s_](\\w*[-_]?\\w*)|\\s(A3580|A3860|A5500|A5600|A5860|A7600|A806|A800|A808T|A808T-I|A936|A938t|A788t|K30-E|K30-T|K30-W|K50-T3s|K50-T5|K80M|K910|K910e|K920|S90-e|S90-t|S90-u|S968T|X2-CU|X2-TO|Z90-3|Z90-7)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle Lenovo
                // 兼容build结尾或直接)结尾 兼容Lenovo-xxx/xxx以及Leveno xxx build等
                match = matcher("(Lenovo[-\\s_](\\w*[-_]?\\w*)|\\s(A3580|A3860|A5500|A5600|A5860|A7600|A806|A800|A808T|A808T-I|A936|A938t|A788t|K30-E|K30-T|K30-W|K50-T3s|K50-T5|K80M|K910|K910e|K920|S90-e|S90-t|S90-u|S968T|X2-CU|X2-TO|Z90-3|Z90-7)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Lenovo";
                if (match.groupCount() >= 2 && match.group(2) != "" && match.group(2) != null) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() >= 3 && match.group(3) != "" && match.group(3) != null) {
                    this.device.model = match.group(3);
                }
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z][0-9]+[A-Z] 例如  A360t A360 都应该是 A360
                match = matcher("([a-z]+[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE);
                if (match.find()) {
                    this.device.model = match.group(1);
                }
            } else if (find("(Coolpad[-\\s_](\\w*)|\\s(7295C|7298A|7620L|8908|8085|8970L|9190L|Y80D)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle coolpad
                match = matcher("(Coolpad[-\\s_](\\w*)|\\s(7295C|7298A|7620L|8908|8085|8970L|9190L|Y80D)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Coolpad";
                if (match.groupCount() >= 2 && match.group(2) != "" && match.group(2) != null) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() >= 2 && match.group(3) != "" && match.group(3) != null) {
                    this.device.model = match.group(3);
                }
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z][0-9]+[A-Z] 例如  8297-t01 8297-c01 8297w 都应该是 8297
                match = matcher("([a-z]?[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE);
                if (match.find()) {
                    this.device.model = match.group(1);
                }
            } else if (this.device.manufacturer != null && this.device.manufacturer.toLowerCase().equals("coolpad") && this.device.model != null) {
                // base 库适配
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z][0-9]+[A-Z] 例如  8297-t01 8297-c01 8297w 都应该是 8297
                match = matcher("([a-z]?[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE);
                if (match.find()) {
                    this.device.model = match.group(1);
                }
            } else if (find("\\s(mx\\d*\\w*|mz-(\\w*))\\s(\\w*)\\s*\\w*\\s*build", Pattern.CASE_INSENSITIVE)) {
                // handle meizu
                match = matcher("\\s(mx\\d*\\w*|mz-(\\w*))\\s(\\w*)\\s*\\w*\\s*build", Pattern.CASE_INSENSITIVE);
                this.device.manufacturer = "Meizu";
                match.find();
                String model = "";
                if (match.groupCount() > 1 && match.group(2) != null && !match.group(2).isEmpty()) {
                    model = match.group(2);
                } else {
                    model = match.group(1);
                }
                if (match.groupCount() > 2 && match.group(3) != null && !match.group(3).isEmpty()) {
                    this.device.model = model + "-" + match.group(3);
                } else {
                    this.device.model = model + "";
                }
            } else if (find("(M463C|M35)\\d", Pattern.CASE_INSENSITIVE)) {
                match = matcher("(M463C|M35)\\d", Pattern.CASE_INSENSITIVE);
                this.device.manufacturer = "Meizu";
                match.find();
                this.device.model = match.group(1);
            } else if (find("(Htc[-_\\s](\\w*)|\\s(601e|606w|608t|609d|610t|6160|619d|620G|626d|626s|626t|626w|709d|801e|802d|802t|802w|809D|816d|816e|816t|816v|816w|826d|826s|826t|826w|828w|901e|919d|A310e|A50AML|A510e|A620d|A620e|A620t|A810e|A9191|Aero|C620d|C620e|C620t|D316d|D516d|D516t|D516w|D820mt|D820mu|D820t|D820ts|D820u|D820us|E9pt|E9pw|E9sw|E9t|HD7S|M8Et|M8Sd|M8St|M8Sw|M8d|M8e|M8s|M8si|M8t|M8w|M9W|M9ew|Phablet|S510b|S510e|S610d|S710d|S710e|S720e|S720t|T327t|T328d|T328t|T328w|T329d|T329t|T329w|T528d|T528t|T528w|T8698|WF5w|X315e|X710e|X715e|X720d|X920e|Z560e|Z710e|Z710t|Z715e)[\\s\\)])")) {
                match = matcher("(Htc[-_\\s](\\w*)|\\s(601e|606w|608t|609d|610t|6160|619d|620G|626d|626s|626t|626w|709d|801e|802d|802t|802w|809D|816d|816e|816t|816v|816w|826d|826s|826t|826w|828w|901e|919d|A310e|A50AML|A510e|A620d|A620e|A620t|A810e|A9191|Aero|C620d|C620e|C620t|D316d|D516d|D516t|D516w|D820mt|D820mu|D820t|D820ts|D820u|D820us|E9pt|E9pw|E9sw|E9t|HD7S|M8Et|M8Sd|M8St|M8Sw|M8d|M8e|M8s|M8si|M8t|M8w|M9W|M9ew|Phablet|S510b|S510e|S610d|S710d|S710e|S720e|S720t|T327t|T328d|T328t|T328w|T329d|T329t|T329w|T528d|T528t|T528w|T8698|WF5w|X315e|X710e|X715e|X720d|X920e|Z560e|Z710e|Z710t|Z715e)[\\s\\)])");
                match.find();
                this.device.manufacturer = "Htc";
                this.device.model = match.group(1);
            } else if (find("(Gionee[-\\s_](\\w*)|\\s(GN\\d+\\w*)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle Gionee
                this.device.manufacturer = "GinDream";
                match = matcher("(Gionee[-\\s_](\\w*)|\\s(GN\\d+\\w*)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                if (match.groupCount() > 1 && match.group(2) != null && !match.group(2).isEmpty()) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() > 2) {
                    this.device.model = match.group(3);
                }
            } else if (find("(LG[-_](\\w*)|\\s(D728|D729|D802|D855|D856|D857|D858|D859|E985T|F100L|F460|H778|H818|H819|P895|VW820)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle LG
                match = matcher("(LG[-_](\\w*)|\\s(D728|D729|D802|D855|D856|D857|D858|D859|E985T|F100L|F460|H778|H818|H819|P895|VW820)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Lg";
                if (match.groupCount() > 1) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() > 2) {
                    this.device.model = match.group(3);
                }
            } else if (find("(Tcl[-\\s_](\\w*)|\\s(H916T|P588L|P618L|P620M|P728M)[\\s\\)])")) {
                match = matcher("(Tcl[-\\s_](\\w*)|\\s(H916T|P588L|P618L|P620M|P728M)[\\s\\)])");
                match.find();
                this.device.manufacturer = "Tcl";
                this.device.model = match.group(1);
            } else if (find("(V9180|N918)", Pattern.CASE_INSENSITIVE)) {
                // ZTE
                match = matcher("(V9180|N918)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Zte";
                this.device.model = match.group(1);
            } else if (this.device.manufacturer != null && this.device.manufacturer.toLowerCase().equals("zte") && this.device.model != null) {
                // base 库适配
                // 解决移动联通等不同发行版导致的机型不同问题
                // 特征:[A-Z][0-9]+[A-Z] 例如  Q505T Q505u 都应该是 Q505
                match = matcher("([a-z]?[0-9]+)", this.device.model, Pattern.CASE_INSENSITIVE);
                if (match.find()) {
                    this.device.model = match.group(1);
                }
            } else if (find("(UIMI\\w*|umi\\w*)[-\\s_](\\w*)\\s*\\w*\\s*build", Pattern.CASE_INSENSITIVE)) {
                // UIMI
                match = matcher("(UIMI\\w*|umi\\w*)[-\\s_](\\w*)\\s*\\w*\\s*build", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Uimi";
                if (match.groupCount() > 1) {
                    this.device.model = match.group(1) + "-" + match.group(2);
                } else {
                    this.device.model = match.group(1) + "";
                }
            } else if (find("eton[-\\s_](\\w*)", Pattern.CASE_INSENSITIVE)) {
                // eton
                match = matcher("eton[-\\s_](\\w*)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Eton";
                this.device.model = match.group(1);
            } else if (find("(SM705|SM701|YQ601|YQ603)", Pattern.CASE_INSENSITIVE)) {
                // Smartisan
                match = matcher("(SM705|SM701|YQ601|YQ603)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Smartisan";
                String model = match.group(1);
                if (model.equals("SM701") || model.equals("SM705")) {
                    this.device.model = "T1";
                } else if (model.equals("YQ601") || model.equals("YQ603")) {
                    this.device.model = "U1";
                } else {
                    this.device.model = model;
                }
            } else if (find("(Asus[-\\s_](\\w*)|\\s(A500CG|A500KL|A501CG|A600CG|PF400CG|PF500KL|T001|X002|X003|ZC500TG|ZE550ML|ZE551ML)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle Asus
                match = matcher("(Asus[-\\s_](\\w*)|\\s(A500CG|A500KL|A501CG|A600CG|PF400CG|PF500KL|T001|X002|X003|ZC500TG|ZE550ML|ZE551ML)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Asus";
                if (match.groupCount() > 1) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() > 2) {
                    this.device.model = match.group(3);
                }
            } else if (find("(Nubia[-_\\s](\\w*)|(NX501|NX505J|NX506J|NX507J|NX503A|nx\\d+\\w*)[\\s\\)])", Pattern.CASE_INSENSITIVE)) {
                // handle nubia
                match = matcher("(Nubia[-_\\s](\\w*)|(NX501|NX505J|NX506J|NX507J|NX503A|nx\\d+\\w*)[\\s\\)])", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Nubia";
                if (match.groupCount() > 1 && match.group(2) != null && !match.group(2).isEmpty()) {
                    this.device.model = match.group(2);
                } else if (match.groupCount() > 2) {
                    this.device.model = match.group(3);
                }
            } else if (find("(HT-\\w*)|Haier[-\\s_](\\w*-?\\w*)", Pattern.CASE_INSENSITIVE)) {
                // handle haier
                match = matcher("(HT-\\w*)|Haier[-\\s_](\\w*-?\\w*)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Haier";
                if (match.groupCount() > 0) {
                    this.device.model = match.group(1);
                } else if (match.groupCount() > 1) {
                    this.device.model = match.group(2);
                }
            } else if (find("K-Touch[-\\s_](tou\\s?ch\\s?(\\d)|\\w*)", Pattern.CASE_INSENSITIVE)) {
                // tianyu
                match = matcher("K-Touch[-\\s_](tou\\s?ch\\s?(\\d)|\\w*)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "K-Touch";
                if (match.groupCount() > 1 && match.group(2) != null && !match.group(2).isEmpty()) {
                    this.device.model = "Ktouch" + match.group(2);
                } else {
                    this.device.model = match.group(1);
                }
            } else if (find("Doov[-\\s_](\\w*)", Pattern.CASE_INSENSITIVE)) {
                // DOOV
                match = matcher("Doov[-\\s_](\\w*)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Doov";
                this.device.model = match.group(1);
            } else if (find("koobee", Pattern.CASE_INSENSITIVE)) {
                // coobee
                this.device.manufacturer = "koobee";
            } else if (find("C69", Pattern.CASE_INSENSITIVE)) {
                // Sony
                this.device.manufacturer = "Sony";
            } else if (find("N787|N818S", Pattern.CASE_INSENSITIVE)) {
                // haojixing
                this.device.manufacturer = "Haojixing";
            } else if (find("(hs-|Hisense[-\\s_])(\\w*)", Pattern.CASE_INSENSITIVE)) {
                // haojixing
                match = matcher("(hs-|Hisense[-\\s_])(\\w*)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.device.manufacturer = "Hisense";
                this.device.model = match.group(2);
            }
 
            // format the style of manufacturer
            if (this.device.manufacturer != null && this.device.manufacturer.length() > 0) {
                String manufacturer = this.device.manufacturer;
                this.device.manufacturer = Character.toUpperCase(manufacturer.charAt(0)) + manufacturer.substring(1).toLowerCase();
            }
 
            // format the style of model
            if (!StringUtils.isEmpty(this.device.model)) {
                /*
                model = re.sub(r'-+|_+|\s+', ' ', uaData.device['model'].upper())
                model = re.search(r'\s*(\w*\s*\w*)', model).group(1)
                model = re.sub(r'\s+', '-', model)
                uaData.device['model'] = model
                */
                String model = this.device.model.toUpperCase().replaceAll("-+|_+|\\s+", " ");
                Matcher m = matcher("\\s*(\\w*\\s*\\w*)", model);
                if (m.find()) {
                    model = m.group(1);
                }
                model = model.replaceAll("\\s+", "-");
 
                // 针对三星、华为做去重的特殊处理
                Map<String, String> modelDict = new HashMap<String, String>();
                if (this.device.manufacturer.equals("Samsung")) {
                    modelDict.put("SCH-I95", "GT-I950");
                    modelDict.put("SCH-I93", "GT-I930");
                    modelDict.put("SCH-I86", "GT-I855");
                    modelDict.put("SCH-N71", "GT-N710");
                    modelDict.put("SCH-I73", "GT-S789");
                    modelDict.put("SCH-P70", "GT-I915");
                    if (modelDict.containsKey(model)) {
                        model = modelDict.get(model);
                    }
                } else if (this.device.manufacturer.equals("Huawei")) {
                    modelDict.put("CHE1", "CHE");
                    modelDict.put("CHE2", "CHE");
                    modelDict.put("G620S", "G621");
                    modelDict.put("C8817D", "G621");
                    if (modelDict.containsKey(model)) {
                        model = modelDict.get(model);
                    }
                }
                this.device.model = model;
            }
 
            // 针对xiaomi 的部分数据没有格式化成功,格式化1次
            if (this.device.manufacturer != null && this.device.manufacturer.equals("Xiaomi")) {
                if (find("(hm|mi)-(note)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    match = matcher("(hm|mi)-(note)", this.device.model, Pattern.CASE_INSENSITIVE);
                    match.find();
                    this.device.model = match.group(1) + "-" + match.group(2);
                } else if (find("(hm|mi)-(\\ds?)", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    match = matcher("(hm|mi)-(\\ds?)", this.device.model, Pattern.CASE_INSENSITIVE);
                    match.find();
                    this.device.model = match.group(1) + "-" + match.group(2);
                } else if (find("(hm|mi)-(\\d)[a-rt-z]", this.device.model, Pattern.CASE_INSENSITIVE)) {
                    match = matcher("(hm|mi)-(\\d)[a-rt-z]", this.device.model, Pattern.CASE_INSENSITIVE);
                    match.find();
                    this.device.model = match.group(1) + "-" + match.group(2);
                }
            }
        }
    }
 
    /**
     * handle browser
     */
    protected void correctBrowser() {
        Matcher match;
        Matcher tmpMatch;
 
        if (this.device.type.equals("desktop")) {
            if (find("360se(?:[ /]([\\w.]+))?", Pattern.CASE_INSENSITIVE)) { // 360 security Explorer
                match = matcher("360se(?:[ /]([\\w.]+))?", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "360 security Explorer";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("the world(?:[ /]([\\w.]+))?", Pattern.CASE_INSENSITIVE)) { // the world
                match = matcher("the world(?:[ /]([\\w.]+))?", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "the world";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("tencenttraveler ([\\w.]+)", Pattern.CASE_INSENSITIVE)) { // tencenttraveler
                match = matcher("tencenttraveler ([\\w.]+)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "tencenttraveler";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            }
        } else if (this.device.type.equals("mobile") || this.device.type.equals("tablet")) {
            if (find("BaiduHD\\s+([\\w.]+)", Pattern.CASE_INSENSITIVE)) { // BaiduHD
                match = matcher("BaiduHD\\s+([\\w.]+)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "BaiduHD";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("360.s*aphone\\s*browser\\s*\\(version\\s*([\\w.]+)\\)", Pattern.CASE_INSENSITIVE)) { // 360 Browser
                match = matcher("360.s*aphone\\s*browser\\s*\\(version\\s*([\\w.]+)\\)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "360 Browser";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("flyflow/([\\w.]+)", Pattern.CASE_INSENSITIVE)) { // Baidu Browser
                match = matcher("flyflow/([\\w.]+)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "Baidu Browser";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("baiduhd ([\\w.]+)", Pattern.CASE_INSENSITIVE)) { // Baidu HD
                match = matcher("baiduhd ([\\w.]+)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "Baidu HD";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("LieBaoFast/([\\w.]+)", Pattern.CASE_INSENSITIVE)) { // LieBaoFast
                match = matcher("LieBaoFast/([\\w.]+)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "LieBao Fast";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("LieBao/([\\w.]+)", Pattern.CASE_INSENSITIVE)) { // LieBao
                match = matcher("LieBao/([\\w.]+)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "LieBao";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (this.os.name != null && this.os.name.equals("Android") && find("safari", Pattern.CASE_INSENSITIVE) && find("version/([0-9\\.]+)", Pattern.CASE_INSENSITIVE)) { // Android Google Browser
                match = matcher("version/([0-9\\.]+)", Pattern.CASE_INSENSITIVE);
                match.find();
                this.browser.name = "Google Browser";
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            } else if (find("(ipad|iphone).* applewebkit/.* mobile", Pattern.CASE_INSENSITIVE)) {
                // 'Mozilla/5.0 (iPad; CPU OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206' belongs to Safari
                this.browser.name = "Safari";
            }
        }
 
        if (find("baiduboxapp", Pattern.CASE_INSENSITIVE)) {
            this.browser.name = "百度框";
        } else if (find("BaiduLightAppRuntime", Pattern.CASE_INSENSITIVE)) {
            this.browser.name = "轻应用runtime";
        } else if (find("Weibo", Pattern.CASE_INSENSITIVE)) {
            this.browser.name = "微博";
        } else if (find("MQQ", Pattern.CASE_INSENSITIVE)) {
            this.browser.name = "手机QQ";
        } else if (find("hao123", Pattern.CASE_INSENSITIVE)) {
            this.browser.name = "hao123";
        }
 
        match = matcher("MicroMessenger/([\\w.]+)", Pattern.CASE_INSENSITIVE);
        if (match.find()) {
            this.browser.name = "微信";
            String tmpVersion = match.group(1).replaceAll("_", ".");
            tmpMatch = matcher("(\\d+\\.\\d+\\.\\d+\\.\\d+)", tmpVersion);
            if (tmpMatch.find()) {
                tmpVersion = tmpMatch.group(1);
            }
            this.browser.version = new Version(tmpVersion);
            this.browser.version.original = tmpVersion;
        }
 
        match = matcher("UCBrowser/([\\w.]+)", Pattern.CASE_INSENSITIVE);
        if (match.find()) {
            this.browser.name = "UC Browser";
            this.browser.version = new Version(match.group(1));
            this.browser.version.original = match.group(1);
        }
 
        if (find("OPR/([\\w.]+)", Pattern.CASE_INSENSITIVE)) {
            match = matcher("OPR/([\\w.]+)", Pattern.CASE_INSENSITIVE);
            match.find();
            this.browser.name = "Opera";
            this.browser.version = new Version(match.group(1));
            this.browser.version.original = match.group(1);
 
        } else if (find("Trident/7", Pattern.CASE_INSENSITIVE) && find("rv:11", Pattern.CASE_INSENSITIVE)) { // IE 11
            this.browser.name = "Internet Explorer";
            this.browser.version = new Version("11");
            this.browser.version.original = "11";
 
        } else if (find("Edge/12", Pattern.CASE_INSENSITIVE) && find("Windows Phone|Windows NT", Pattern.CASE_INSENSITIVE)) { // Microsoft Edge
            this.browser.name = "Microsoft Edge";
            this.browser.version = new Version("12");
            this.browser.version.original = "12";
 
        } else if (find("miuibrowser/([\\w.]+)", Pattern.CASE_INSENSITIVE)) { // miui browser
            match = matcher("miuibrowser/([\\w.]+)", Pattern.CASE_INSENSITIVE);
            match.find();
            this.browser.name = "miui browser";
            this.browser.version = new Version(match.group(1));
            this.browser.version.original = match.group(1);
        }
 
        if (this.browser.name == null || "".equals(this.browser.name)) {
            if (find("Safari/([\\w.]+)", Pattern.CASE_INSENSITIVE) && find("Version", Pattern.CASE_INSENSITIVE)) {
                this.browser.name = "Safari";
            }
        }
 
        if (this.browser.name != null && this.browser.version == null) {
            match = matcher("Version/([\\w.]+)", Pattern.CASE_INSENSITIVE);
            if (match.find()) {
                this.browser.version = new Version(match.group(1));
                this.browser.version.original = match.group(1);
            }
        }
    }
 
    /**
     * handle os
     */
    protected void correctOS() {
        Matcher match;
 
        if (this.os.name != null && this.os.name.equals("Windows") || find("Windows", Pattern.CASE_INSENSITIVE)) {
            this.os.name = "Windows";
            if (find("NT 6.3", Pattern.CASE_INSENSITIVE)) {
                this.os.version = new Version("8.1");
                this.os.version.alias = "8.1";
                this.os.version.original = "8.1";
            } else if (find("NT 6.4", Pattern.CASE_INSENSITIVE) && find("NT 10.0", Pattern.CASE_INSENSITIVE)) {
                this.os.version = new Version("10");
                this.os.version.alias = "10";
                this.os.version.original = "10";
            }
        } else if (this.os.name != null && this.os.name.equals("Mac OS X")) {
            match = matcher("Mac OS X[\\s\\_\\-/](\\d+[\\.\\-\\_]\\d+[\\.\\-\\_]?\\d*)", Pattern.CASE_INSENSITIVE);
            if (match.find()) {
                this.os.version = new Version(match.group(1).replace("_", "."));
                this.os.version.original = match.group(1).replace("_", ".");
                this.os.version.alias = match.group(1).replace("_", ".");
            } else {
                this.os.version = new Version("");
                this.os.version.original = "";
                this.os.version.alias = "";
            }
        } else if (this.os.name != null && find("Android", this.os.name, Pattern.CASE_INSENSITIVE)) {
            match = matcher("Android[\\s\\_\\-/i686]?[\\s\\_\\-/](\\d+[\\.\\-\\_]\\d+[\\.\\-\\_]?\\d*)", Pattern.CASE_INSENSITIVE);
            if (match.find()) {
                this.os.version = new Version(match.group(1));
                this.os.version.alias = match.group(1);
                this.os.version.original = match.group(1);
            }
        }
    }
 
    protected void parse() {
        parseNetType();
        parseOSAndDevice();
        parseBrowser();
        parseEngine();
        clearCamouflage();
    }
 
    protected void correct() {
        correctDevice();
        correctBrowser();
        correctOS();
    }
 
    public void reset() {
        this.ua = "";
        this.os = new OS("");
        this.engine = new Engine("");
        this.device = new Device("");
        this.browser = new Browser("");
        this.net = "";
    }
 
    public UserAgent detect() {
        UserAgent userAgent = new UserAgent();
        if (ua != null) {
            parse();
            correct();
        }
        userAgent.setOS(this.os);
        userAgent.setEngine(this.engine);
        userAgent.setBrowser(this.browser);
        userAgent.setDevice(this.device);
        userAgent.setNet(this.net);
        return userAgent;
    }
}
UserAgent.java
package com.eif.website.useragent;
 
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
 
public class UserAgent {
 
    /*private String osName = "";
    private String osVersion = "";
 
    private String engineName = "";
    private String engineVersion = "";
 
    private String browserName = "";
    private String browserVersion = "";
    private String browserMode = "";
 
    private String deviceType = "";
    private String deviceModel = "";
    private String deviceManufacturer = "";*/
 
 
    Map<String, String> os = new HashMap<String, String>();
    Map<String, String> engine = new HashMap<String, String>();
    Map<String, String> browser = new HashMap<String, String>();
    Map<String, String> device = new HashMap<String, String>();
    String net = "";
 
    public UserAgent() {
        os.put("name", "");
        os.put("version", "");
 
        engine.put("name", "");
        engine.put("version", "");
 
        browser.put("name", "");
        browser.put("version", "");
        browser.put("mode", "");
 
        device.put("type", "");
        device.put("model", "");
        device.put("manufacturer", "");
    }
 
    public Map<String, String> getOS() {
        return this.os;
    }
 
    public Map<String, String> getEngine() {
        return this.engine;
    }
 
    public Map<String, String> getBrowser() {
        return this.browser;
    }
 
    public Map<String, String> getDevice() {
        return this.device;
    }
 
    public String getNettype() {
        return this.net;
    }
 
    protected String strip(String input, String suffix) {
        input = input.trim();
        while (input.endsWith(suffix)) {
            input = input.substring(0, input.length() - 1).trim();
        }
        return input;
    }
 
    void setOS(OS os) {
        if (os.name != null) {
            this.os.put("name", os.name);
        }
        if (os.version != null) {
            this.os.put("version", os.version.value);
        }
    }
 
    void setEngine(Engine engine) {
        if (engine.name != null) {
            this.engine.put("name", engine.name);
        }
        if (engine.version != null) {
            this.engine.put("version", engine.version.value);
        }
    }
 
    void setBrowser(Browser browser) {
        if (browser.name != null) {
            this.browser.put("name", browser.name);
        }
        if (browser.version != null) {
            this.browser.put("version", browser.version.value);
        }
        if (browser.mode != null) {
            this.browser.put("mode", browser.mode);
        }
    }
 
    void setDevice(Device device) {
        if (device.type != null) {
            this.device.put("type", device.type);
        }
        if (device.model != null) {
            this.device.put("model", strip(device.model, "-"));
        }
        if (device.manufacturer != null) {
            this.device.put("manufacturer", device.manufacturer);
        }
    }
 
    void setNet(String net) {
        this.net = net;
    }
 
    public String prettyPrint() {
        String output = "{\n";
        String indent = "    ";
 
        output += indent + "\"" + "os" + "\" : {\n";
        output += indent + indent + "\"name\" : \"" + this.os.get("name") + "\",\n";
        output += indent + indent + "\"version\" : \"" + this.os.get("version") + "\"\n";
        output += indent + "},\n";
 
        output += indent + "\"" + "engine" + "\" : {\n";
        output += indent + indent + "\"name\" : \"" + this.engine.get("name") + "\",\n";
        output += indent + indent + "\"version\" : \"" + this.engine.get("version") + "\"\n";
        output += indent + "},\n";
 
        output += indent + "\"" + "browser" + "\" : {\n";
        output += indent + indent + "\"name\" : \"" + this.browser.get("name") + "\",\n";
        output += indent + indent + "\"version\" : \"" + this.browser.get("version") + "\",\n";
        output += indent + indent + "\"mode\" : \"" + this.browser.get("mode") + "\"\n";
        output += indent + "},\n";
 
        output += indent + "\"" + "device" + "\" : {\n";
        output += indent + indent + "\"type\" : \"" + this.device.get("type") + "\",\n";
        output += indent + indent + "\"model\" : \"" + this.device.get("model") + "\",\n";
        output += indent + indent + "\"manufacturer\" : \"" + this.device.get("manufacturer") + "\"\n";
        output += indent + "},\n";
 
        output += indent + "\"" + "nettype" + "\" : " + this.net + "\n";
 
        output += "}";
        return output;
    }
 
    public String compactPrint() {
        String output = "";
 
        output += "name=" + this.os.get("name") + ",version=" + this.os.get("version") + ";";
        output += "name=" + this.engine.get("name") + ",version=" + this.engine.get("version") + ";";
        output += "name=" + this.browser.get("name") + ",version=" + this.browser.get("version") + ",mode=" + this.browser.get("mode") + ";";
        output += "model=" + this.device.get("model") + ",type=" + this.device.get("type") + ",manufacturer=" + this.device.get("manufacturer") + ";";
        output += "net=" + this.net;
 
        return output;
    }
 
    public Map<String, Object> toObject() {
        Map<String, Object> obj = new HashMap<String, Object>() {
            {
                put("os", os);
                put("engine", engine);
                put("browser", browser);
                put("device", device);
                put("nettype", net);
            }
        };
 
        return obj;
    }
 
    public Boolean equals(UserAgent ua) {
 
        if (ua != null && ua.os != null
            && ua.engine != null && ua.browser != null
            && ua.device != null) {
            return this.os.get("name").equals(ua.os.get("name"))
                && this.os.get("version").equals(ua.os.get("version"))
                && this.engine.get("name").equals(ua.engine.get("name"))
                && this.engine.get("version").equals(ua.engine.get("version"))
                && this.browser.get("name").equals(ua.browser.get("name"))
                && this.browser.get("version").equals(ua.browser.get("version"))
                && this.browser.get("mode").equals(ua.browser.get("mode"))
                && this.device.get("type").equals(ua.device.get("type"))
                && this.device.get("manufacturer").equals(ua.device.get("manufacturer"))
                && this.device.get("model").equals(ua.device.get("model"));
        } else {
            return false;
        }
    }
 
    public String getOsName() {
        return this.os.get("name");
    }
 
    public String getOsVersion() {
        return this.os.get("version");
    }
 
    public String getEngineName() {
        return this.engine.get("name");
    }
 
    public String getEngineVersion() {
        return this.engine.get("version");
    }
 
    public String getBrowserName() {
        return this.browser.get("name");
    }
 
 
    public String getBrowserVersion() {
        return this.browser.get("version");
    }
 
 
    public String getBrowserMode() {
        return this.browser.get("mode");
    }
 
 
    public String getDeviceType() {
        return this.device.get("type");
    }
 
    public String getDeviceModel() {
        return this.device.get("model");
    }
 
    public String getDeviceManufacturer() {
        return this.device.get("manufacturer");
    }
 
}
Version.java
package com.morgan.website.useragent;
 
import org.apache.commons.lang.StringUtils;
 
import java.util.Arrays;
 
class Version {
    String value;
    String original = "";
    String alias = "";
    String type = "";
    int details;
 
    public Version(String value) {
        this(value, value);
    }
 
    public Version(String value, String alias) {
        this.value = value;
        this.alias = alias;
    }
 
    static protected float parseVersion(String version) {
        String[] components = version.split("\\.");
        if (components.length == 0) {
            return 0.0f;
        }
        String major = components[0];
        if (components.length == 1) {
            return Float.valueOf(major);
        }
        String precision = StringUtils.join(Arrays.copyOfRange(components, 1, components.length), "");
        version = major + "." + precision;
        return Float.valueOf(version);
    }
 
    static protected float parseVersion(Version version) {
        if (version == null) {
            return 0.0f;
        }
        return parseVersion(version.value);
    }
 
    public static void main(String[] args) {
        float test = Version.parseVersion("1.1");
        System.out.println(test);
        float test2 = Version.parseVersion("1.1.1");
        System.out.println(test2);
    }
}