diff --git a/src/main/java/ua_parser/Client.java b/src/main/java/ua_parser/Client.java index 0f682f4..575b95f 100644 --- a/src/main/java/ua_parser/Client.java +++ b/src/main/java/ua_parser/Client.java @@ -45,10 +45,10 @@ public boolean equals(Object other) { @Override public int hashCode() { - int h = userAgent == null ? 0 : userAgent.hashCode(); - h += os == null ? 0 : os.hashCode(); - h += device == null ? 0 : device.hashCode(); - return h; + int result = userAgent != null ? userAgent.hashCode() : 0; + result = 31 * result + (os != null ? os.hashCode() : 0); + result = 31 * result + (device != null ? device.hashCode() : 0); + return result; } @Override diff --git a/src/main/java/ua_parser/Device.java b/src/main/java/ua_parser/Device.java index e318ef2..46ea6c7 100644 --- a/src/main/java/ua_parser/Device.java +++ b/src/main/java/ua_parser/Device.java @@ -25,13 +25,17 @@ */ public class Device { public final String family; + public final String brand; + public final String model; - public Device(String family) { + public Device(String family, String brand, String model) { this.family = family; + this.brand = brand; + this.model = model; } public static Device fromMap(Map m) { - return new Device((String) m.get("family")); + return new Device(m.get("family"), m.get("brand"), m.get("model")); } @Override @@ -40,17 +44,28 @@ public boolean equals(Object other) { if (!(other instanceof Device)) return false; Device o = (Device) other; - return (this.family != null && this.family.equals(o.family)) || this.family == o.family; + + if (brand != null ? !brand.equals(o.brand) : o.brand != null) return false; + if (this.family != null ? !this.family.equals(o.family) : o.family != null) return false; + if (model != null ? !model.equals(o.model) : o.model != null) return false; + + return true; } @Override public int hashCode() { - return family == null ? 0 : family.hashCode(); + int result = family != null ? family.hashCode() : 0; + result = 31 * result + (brand != null ? brand.hashCode() : 0); + result = 31 * result + (model != null ? model.hashCode() : 0); + return result; } @Override public String toString() { - return String.format("{\"family\": %s}", - family == null ? Constants.EMPTY_STRING : '"' + family + '"'); + return String.format("{\"family\": %s, \"brand\": %s, \"model\": %s}", + family == null ? Constants.EMPTY_STRING : '"' + family + '"', + brand == null ? Constants.EMPTY_STRING : '"' + brand + '"', + model == null ? Constants.EMPTY_STRING : '"' + model + '"'); } + } \ No newline at end of file diff --git a/src/main/java/ua_parser/DeviceParser.java b/src/main/java/ua_parser/DeviceParser.java index 04d9955..293e098 100644 --- a/src/main/java/ua_parser/DeviceParser.java +++ b/src/main/java/ua_parser/DeviceParser.java @@ -28,7 +28,7 @@ * @author Steve Jiang (@sjiang) */ public class DeviceParser { - List patterns; + final List patterns; public DeviceParser(List patterns) { this.patterns = patterns; @@ -39,15 +39,14 @@ public Device parse(String agentString) { return null; } - String device = null; + Device device; for (DevicePattern p : patterns) { if ((device = p.match(agentString)) != null) { - break; + return device; } } - if (device == null) device = "Other"; - return new Device(device); + return new Device("Other", null, null); } public static DeviceParser fromList(List> configList) { @@ -60,40 +59,102 @@ public static DeviceParser fromList(List> configList) { protected static DevicePattern patternFromMap(Map configMap) { String regex = configMap.get("regex"); + String regex_flag = configMap.get("regex_flag"); if (regex == null) { throw new IllegalArgumentException("Device is missing regex"); } - return new DevicePattern(Pattern.compile(regex), - configMap.get("device_replacement")); + + Pattern pattern; + if (regex_flag != null && regex_flag.equals("i")) { + pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); + } else { + pattern = Pattern.compile(regex); + } + + /** + * To maintains backwards compatibility the familyReplacement + * field has been named "device_replacement" + */ + return new DevicePattern(pattern, + configMap.get("device_replacement"), + configMap.get("brand_replacement"), + configMap.get("model_replacement")); } protected static class DevicePattern { private final Pattern pattern; private final String familyReplacement; + private final String brandReplacement; + private final String modelReplacement; - public DevicePattern(Pattern pattern, String familyReplacement) { + private static final Pattern GROUP_PATTERN = Pattern.compile("\\$(\\d+)"); + + public DevicePattern(Pattern pattern, String familyReplacement, String brandReplacement, String modelReplacement) { this.pattern = pattern; this.familyReplacement = familyReplacement; + this.brandReplacement = brandReplacement; + this.modelReplacement = modelReplacement; + } + + private String performReplacement(Matcher matcher, String replacement) { + int count = matcher.groupCount(); + StringBuffer buffer = new StringBuffer(); + Matcher replaceMatcher = GROUP_PATTERN.matcher(replacement); + while (replaceMatcher.find()) { + String group = null; + try { + int id = Integer.parseInt(replaceMatcher.group(1)); + if (id >= 0 && id <= count) { + group = matcher.group(id); + } + } + catch (NumberFormatException ignored) {} + catch (IllegalArgumentException ignored) {} + catch (IndexOutOfBoundsException ignored) {} + replaceMatcher.appendReplacement(buffer, group == null ? "" : Matcher.quoteReplacement(group)); + } + replacement = buffer.toString(); + return replacement; } - public String match(String agentString) { + private String replace(Matcher matcher, String replacement, int position) { + if (replacement == null) { + if (position > 0) { + replacement = "$" + position; + } else { + return null; + } + } + + if (replacement.contains("$")) { + replacement = performReplacement(matcher, replacement); + } + replacement = replacement.trim(); + if (replacement.length() == 0) { + return null; + } else { + return replacement; + } + } + + public Device match(String agentString) { + Matcher matcher = pattern.matcher(agentString); if (!matcher.find()) { return null; } - String family = null; - if (familyReplacement != null) { - if (familyReplacement.contains("$1") && matcher.groupCount() >= 1 && matcher.group(1) != null) { - family = familyReplacement.replaceFirst("\\$1", Matcher.quoteReplacement(matcher.group(1))); - } else { - family = familyReplacement; - } - } else if (matcher.groupCount() >= 1) { - family = matcher.group(1); + String family = replace(matcher, familyReplacement, 1); + String brand = replace(matcher, brandReplacement, -1); + String model = replace(matcher, modelReplacement, 1); + + if (family != null) { + return new Device(family, brand, model); + } else { + return null; } - return family; + } } diff --git a/src/main/java/ua_parser/OS.java b/src/main/java/ua_parser/OS.java index d51f261..05c2eea 100644 --- a/src/main/java/ua_parser/OS.java +++ b/src/main/java/ua_parser/OS.java @@ -53,15 +53,15 @@ public boolean equals(Object other) { @Override public int hashCode() { - int h = family == null ? 0 : family.hashCode(); - h += major == null ? 0 : major.hashCode(); - h += minor == null ? 0 : minor.hashCode(); - h += patch == null ? 0 : patch.hashCode(); - h += patchMinor == null ? 0 : patchMinor.hashCode(); - return h; + int result = family != null ? family.hashCode() : 0; + result = 31 * result + (major != null ? major.hashCode() : 0); + result = 31 * result + (minor != null ? minor.hashCode() : 0); + result = 31 * result + (patch != null ? patch.hashCode() : 0); + result = 31 * result + (patchMinor != null ? patchMinor.hashCode() : 0); + return result; } - @Override + @Override public String toString() { return String.format("{\"family\": %s, \"major\": %s, \"minor\": %s, \"patch\": %s, \"patch_minor\": %s}", family == null ? Constants.EMPTY_STRING : '"' + family + '"', diff --git a/src/main/java/ua_parser/UserAgent.java b/src/main/java/ua_parser/UserAgent.java index 5a5b530..d5d81ae 100644 --- a/src/main/java/ua_parser/UserAgent.java +++ b/src/main/java/ua_parser/UserAgent.java @@ -51,11 +51,11 @@ public boolean equals(Object other) { @Override public int hashCode() { - int h = family == null ? 0 : family.hashCode(); - h += major == null ? 0 : major.hashCode(); - h += minor == null ? 0 : minor.hashCode(); - h += patch == null ? 0 : patch.hashCode(); - return h; + int result = family != null ? family.hashCode() : 0; + result = 31 * result + (major != null ? major.hashCode() : 0); + result = 31 * result + (minor != null ? minor.hashCode() : 0); + result = 31 * result + (patch != null ? patch.hashCode() : 0); + return result; } @Override diff --git a/src/test/java/ua_parser/DeviceTest.java b/src/test/java/ua_parser/DeviceTest.java index 0ecfba4..4da3a97 100644 --- a/src/test/java/ua_parser/DeviceTest.java +++ b/src/test/java/ua_parser/DeviceTest.java @@ -24,11 +24,13 @@ public class DeviceTest extends DataTest { protected Device getRandomInstance(long seed, StringGenerator g) { random.setSeed(seed); String family = g.getString(256); - return new Device(family); + String brand = g.getString(256); + String model = g.getString(256); + return new Device(family, brand, model); } @Override protected Device getBlankInstance() { - return new Device(null); + return new Device(null, null, null); } } \ No newline at end of file diff --git a/src/test/java/ua_parser/ParserTest.java b/src/test/java/ua_parser/ParserTest.java index 525a092..13d3025 100644 --- a/src/test/java/ua_parser/ParserTest.java +++ b/src/test/java/ua_parser/ParserTest.java @@ -81,10 +81,10 @@ public void testParseAll() { Client expected1 = new Client(new UserAgent("Firefox", "3", "5", "5"), new OS("Mac OS X", "10", "4", null, null), - new Device("Other")); + new Device("Other", null, null)); Client expected2 = new Client(new UserAgent("Mobile Safari", "5", "1", null), new OS("iOS", "5", "1", "1", null), - new Device("iPhone")); + new Device("iPhone", "Apple", "iPhone")); assertThat(parser.parse(agentString1), is(expected1)); assertThat(parser.parse(agentString2), is(expected2)); @@ -100,7 +100,9 @@ public void testReplacementQuoting() throws Exception { + " os_replacement: 'CatOS 9000'\n" + "device_parsers:\n" + " - regex: 'CashPhone-([\\$0-9]+)\\.(\\d+)\\.(\\d+)'\n" - + " device_replacement: 'CashPhone $1'\n"; + + " device_replacement: 'CashPhone $1'\n" + + " brand_replacement: 'CashPhone'\n" + + " model_replacement: '$1'\n"; Parser testParser = parserFromStringConfig(testConfig); Client result = testParser.parse("ABC12\\34 (CashPhone-$9.0.1 CatOS OH-HAI=/^.^\\=)");