|
1 | 1 | package com.blueconic.browscap;
|
2 | 2 |
|
3 |
| -import static java.util.Arrays.asList; |
4 |
| - |
5 |
| -import java.util.ArrayList; |
6 |
| -import java.util.BitSet; |
7 |
| -import java.util.Comparator; |
8 |
| -import java.util.List; |
9 |
| -import java.util.function.Predicate; |
10 |
| - |
11 |
| -import com.blueconic.browscap.domain.Capabilities; |
12 |
| - |
13 |
| -/** |
14 |
| - * This class is responsible for determining the best matching useragent rule to determine the properties for a |
15 |
| - * useragent string. |
16 |
| - */ |
17 |
| -public class UserAgentParser { |
18 |
| - |
19 |
| - // Common substrings to filter irrelevant rules and speed up processing |
20 |
| - static final String[] COMMON = {"-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "profile", "player", |
21 |
| - "compatible", "android", "google", "tab", "transformer", "lenovo", "micro", "edge", "safari", "opera", |
22 |
| - "chrome", "firefox", "msie", "chromium", "cpu os ", "cpu iphone os ", "windows nt ", "mac os x ", "linux", |
23 |
| - "bsd", "windows phone", "iphone", "pad", "blackberry", "nokia", "alcatel", "ucbrowser", "mobile", "ie", |
24 |
| - "mercury", "samsung", "browser", "wow64", "silk", "lunascape", "crios", "epiphany", "konqueror", "version", |
25 |
| - "rv:", "build", "bot", "like gecko", "applewebkit", "trident", "mozilla", "windows nt 4", "windows nt 5.0", |
26 |
| - "windows nt 5.1", "windows nt 5.2", "windows nt 6.0", "windows nt 6.1", "windows nt 6.2", "windows nt 6.3", |
27 |
| - "windows nt 10.0", "android?4.0", "android?4.1", "android?4.2", "android?4.3", "android?4.4", "android?2.3", |
28 |
| - "android?5"}; |
29 |
| - |
30 |
| - // Common prefixes to filter irrelevant rules and speed up processing |
31 |
| - static final String[] FILTER_PREFIXES = {"mozilla/5.0", "mozilla/4"}; |
32 |
| - |
33 |
| - // All useragent rule ordered by size and alphabetically |
34 |
| - private final Rule[] myRules; |
35 |
| - |
36 |
| - // Filters for filtering irrelevant rules and speed up processing |
37 |
| - private final Filter[] myFilters; |
38 |
| - |
39 |
| - /** |
40 |
| - * Creates a new parser based on a collection of rules. |
41 |
| - * @param rules The rules, ordered by priority |
42 |
| - */ |
43 |
| - UserAgentParser(final Rule[] rules) { |
44 |
| - myRules = getOrderedRules(rules); |
45 |
| - myFilters = buildFilters(); |
46 |
| - } |
| 3 | +public interface UserAgentParser { |
47 | 4 |
|
48 | 5 | /**
|
49 |
| - * Parses a User-Agent header value. |
| 6 | + * Parses a User-Agent header value into a Capabilities object. |
50 | 7 | * @param userAgent The user agent
|
51 | 8 | * @return The capabilities of the best matching rule
|
52 | 9 | */
|
53 |
| - public Capabilities parse(final String userAgent) { |
54 |
| - |
55 |
| - final SearchableString searchString = new SearchableString(userAgent.toLowerCase()); |
56 |
| - |
57 |
| - final BitSet includes = getIncludeRules(searchString, myFilters); |
58 |
| - |
59 |
| - for (int i = includes.nextSetBit(0); i >= 0; i = includes.nextSetBit(i + 1)) { |
60 |
| - final Rule rule = myRules[i]; |
61 |
| - if (rule.matches(searchString)) { |
62 |
| - return rule.getCapabilities(); |
63 |
| - } |
64 |
| - } |
65 |
| - |
66 |
| - return Capabilities.DEFAULT; |
67 |
| - } |
68 |
| - |
69 |
| - BitSet getIncludeRules(final SearchableString searchString, final Filter[] filters) { |
70 |
| - |
71 |
| - final BitSet excludes = new BitSet(myRules.length); |
72 |
| - for (final Filter filter : filters) { |
73 |
| - filter.applyExcludes(searchString, excludes); |
74 |
| - } |
75 |
| - |
76 |
| - // Convert flip the excludes to determine the includes |
77 |
| - final BitSet includes = excludes; |
78 |
| - includes.flip(0, myRules.length); |
79 |
| - return includes; |
80 |
| - } |
81 |
| - |
82 |
| - // Sort by size and alphabet, so the first match can be returned immediately |
83 |
| - static Rule[] getOrderedRules(final Rule[] rules) { |
84 |
| - final Comparator<Rule> c = Comparator.comparing(Rule::getSize).reversed().thenComparing(Rule::getPattern); |
85 |
| - |
86 |
| - final List<Rule> orderedRules = new ArrayList<>(asList(rules)); |
87 |
| - orderedRules.sort(c); |
88 |
| - return orderedRules.toArray(new Rule[0]); |
89 |
| - } |
90 |
| - |
91 |
| - Filter[] buildFilters() { |
92 |
| - |
93 |
| - final List<Filter> result = new ArrayList<>(); |
94 |
| - |
95 |
| - // Build filters for specific prefix constraints |
96 |
| - for (final String pattern : FILTER_PREFIXES) { |
97 |
| - result.add(createPrefixFilter(pattern)); |
98 |
| - } |
99 |
| - |
100 |
| - // Build filters for specific contains constraints |
101 |
| - for (final String pattern : COMMON) { |
102 |
| - result.add(createContainsFilter(pattern)); |
103 |
| - } |
104 |
| - |
105 |
| - return result.toArray(new Filter[0]); |
106 |
| - } |
107 |
| - |
108 |
| - Filter createContainsFilter(final String pattern) { |
109 |
| - final Literal literal = new Literal(pattern); |
110 |
| - |
111 |
| - final Predicate<SearchableString> pred = c -> c.getIndices(literal).length > 0; |
112 |
| - |
113 |
| - final Predicate<Rule> matches = rule -> rule.requires(pattern); |
114 |
| - |
115 |
| - return new Filter(pred, matches); |
116 |
| - } |
117 |
| - |
118 |
| - Filter createPrefixFilter(final String pattern) { |
119 |
| - final Literal literal = new Literal(pattern); |
120 |
| - |
121 |
| - final Predicate<SearchableString> pred = s -> s.startsWith(literal); |
122 |
| - |
123 |
| - final Predicate<Rule> matches = rule -> { |
124 |
| - final Literal prefix = rule.getPrefix(); |
125 |
| - return prefix != null && prefix.toString().startsWith(pattern); |
126 |
| - }; |
127 |
| - |
128 |
| - return new Filter(pred, matches); |
129 |
| - } |
130 |
| - |
131 |
| - /** |
132 |
| - * Filter expression to can exclude a number of rules if a useragent doesn't meet it's predicate. |
133 |
| - */ |
134 |
| - class Filter { |
135 |
| - |
136 |
| - private final Predicate<SearchableString> myUserAgentPredicate; |
137 |
| - private final BitSet myMask; |
138 |
| - |
139 |
| - /** |
140 |
| - * Creates a the filter. |
141 |
| - * @param userAgentPredicate The predicate for matching user agents. |
142 |
| - * @param patternPredicate The corresponding predicate for matching rule |
143 |
| - */ |
144 |
| - Filter(final Predicate<SearchableString> userAgentPredicate, final Predicate<Rule> patternPredicate) { |
145 |
| - myUserAgentPredicate = userAgentPredicate; |
146 |
| - myMask = new BitSet(myRules.length); |
147 |
| - for (int i = 0; i < myRules.length; i++) { |
148 |
| - if (patternPredicate.test(myRules[i])) { |
149 |
| - myMask.set(i); |
150 |
| - } |
151 |
| - } |
152 |
| - } |
153 |
| - |
154 |
| - void applyExcludes(final SearchableString userAgent, final BitSet resultExcludes) { |
155 |
| - if (!myUserAgentPredicate.test(userAgent)) { |
156 |
| - resultExcludes.or(myMask); |
157 |
| - } |
158 |
| - } |
159 |
| - } |
| 10 | + Capabilities parse(String userAgent); |
160 | 11 | }
|
0 commit comments