
fiftyone.mobile.detection.Provider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of 51Degrees.detection.core Show documentation
Show all versions of 51Degrees.detection.core Show documentation
51Degrees core detection solution
The newest version!
package fiftyone.mobile.detection;
import fiftyone.properties.MatchMethods;
import fiftyone.mobile.detection.Match.MatchState;
import fiftyone.mobile.detection.entities.Component;
import fiftyone.mobile.detection.entities.Profile;
import fiftyone.properties.DetectionConstants;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;
/* *********************************************************************
* This Source Code Form is copyright of 51Degrees Mobile Experts Limited.
* Copyright 2014 51Degrees Mobile Experts Limited, 5 Charlotte Close,
* Caversham, Reading, Berkshire, United Kingdom RG4 7BY
*
* This Source Code Form is the subject of the following patent
* applications, owned by 51Degrees Mobile Experts Limited of 5 Charlotte
* Close, Caversham, Reading, Berkshire, United Kingdom RG4 7BY:
* European Patent Application No. 13192291.6; and
* United States Patent Application Nos. 14/085,223 and 14/085,301.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0.
*
* If a copy of the MPL was not distributed with this file, You can obtain
* one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, v. 2.0.
* ********************************************************************* */
/**
* Provider used to perform a detection based on a user agent string.
*/
public class Provider {
/**
* A cache for user agents.
*/
private Cache userAgentCache = null;
private boolean recordDetectionTime = false;
/**
* The total number of detections performed by the data set.
* @return total number of detections performed by this data set
*/
public long getDetectionCount() {
return detectionCount.longValue();
}
private AtomicLong detectionCount;
/**
* The number of detections performed using the method.
*/
private final SortedList methodCounts;
/**
* The data set associated with the provider.
*/
public final Dataset dataSet;
private Controller controller;
/**
* Builds a new provider with the embedded data set.
*
* @throws IOException indicates an I/O exception occurred
* @deprecated since embedded data file was removed from package. Use the
* provider constructor that requires a dataset created by the factory
* instead.
*/
@Deprecated
public Provider() throws IOException {
throw new UnsupportedOperationException("No longer supported. Please "
+ "use the provider constructors with Factory created datasets.");
}
/**
* Builds a new provider with the embedded data set and a cache with the
* service internal specified.
*
* @param cacheServiceInterval cache service internal in seconds.
* @throws IOException indicates an I/O exception occurred
* @deprecated since embedded data was removed from package. Use the
* provider constructor that requires a dataset created by the factory
* instead.
*/
@Deprecated
public Provider(int cacheServiceInterval) throws IOException {
throw new UnsupportedOperationException("No longer supported. Please "
+ "use the provider constructors with Factory created datasets.");
}
/**
* Reads the embedded data into a byte array to be used as a byte buffer in
* the factory.
*
* @return
* @throws IOException
* @deprecated since embedded data was removed from the package.
*/
@Deprecated
private static byte[] getEmbeddedByteArray() throws IOException {
throw new UnsupportedOperationException("No longer supported.");
}
/**
* Constructs a new provided using the data set.
*
* @param dataSet Data set to use for device detection
*/
public Provider(Dataset dataSet) {
this(dataSet, new Controller(), 0);
}
/**
* Constructs a new provided using the data set.
*
* @param dataSet Data set to use for device detection
* @param cacheSize
*/
public Provider(Dataset dataSet, int cacheSize) {
this(dataSet, new Controller(), cacheSize);
}
/**
* Constructs a new provider with the dataset, controller and cache
* specified.
* @param dataSet
* @param controller
* @param cacheServiceInternal
*/
Provider(Dataset dataSet, Controller controller, int cacheSize) {
this.detectionCount = new AtomicLong();
this.dataSet = dataSet;
this.controller = controller;
this.methodCounts = new SortedList();
this.methodCounts.add(MatchMethods.CLOSEST, 0l);
this.methodCounts.add(MatchMethods.NEAREST, 0l);
this.methodCounts.add(MatchMethods.NUMERIC, 0l);
this.methodCounts.add(MatchMethods.EXACT, 0l);
this.methodCounts.add(MatchMethods.NONE, 0l);
userAgentCache = cacheSize > 0 ? new Cache(cacheSize) : null;
}
public double getPercentageCacheMisses() {
if (userAgentCache != null) {
return userAgentCache.getPercentageMisses();
} else {
return 0;
}
}
/**
* Returns the number of times the user agents cache was switched.
* @return the number of times the user agents cache was switched.
*/
public long getCacheSwitches() {
if (userAgentCache != null) {
return userAgentCache.getCacheSwitches();
} else {
return 0;
}
}
public double getCacheRequests() {
if (userAgentCache != null) {
return userAgentCache.getCacheRequests();
} else {
return -1;
}
}
public long getCacheMisses() {
if (userAgentCache != null) {
return userAgentCache.getCacheMisses();
} else {
return -1;
}
}
/**
* Creates a new match object to be used for matching.
*
* @return a match object ready to be used with the Match methods
*/
public Match createMatch() {
return new Match(dataSet);
}
/**
* For a given collection of HTTP headers returns a match containing
* information about the capabilities of the device and it's components.
* @param headers List of HTTP headers to use for the detection
* @return a match for the target headers provided
* @throws IOException indicates an I/O exception occurred
*/
public Match match(final Map headers) throws IOException {
return match(headers, createMatch());
}
/**
* For a given collection of HTTP headers returns a match containing
* information about the capabilities of the device and it's components.
* @param headers List of HTTP headers to use for the detection
* @param match object created to store the results of the match
* @return a match for the target headers provided
* @throws IOException indicates an I/O exception occurred
*/
public Match match(final Map headers, Match match) throws IOException {
if (headers == null || headers.isEmpty()) {
// Empty headers all default match result.
Controller.matchDefault(match);
} else {
// Check if the headers passed to this function are also found
// in the headers list of the dataset.
ArrayList importantHeaders = new ArrayList();
for (String datasetHeader : dataSet.getHttpHeaders()) {
// Check that the header from the dataset also exists in the
// provided list of headers.
if (headers.containsKey(datasetHeader)) {
// Now check if this is a duplicate header.
if (!importantHeaders.contains(datasetHeader)) {
importantHeaders.add(datasetHeader);
}
}
}
if (importantHeaders.size() == 1) {
// If only 1 header is important then return a simple single match.
match(headers.get(importantHeaders.get(0)), match);
} else {
// Create matches for each of the headers.
Map matches = matchForHeaders(match, headers, importantHeaders);
// A list of new profiles to use with the match.
Profile[] newProfiles = new Profile[dataSet.components.size()];
int componentIndex = 0;
for(Component component : dataSet.components) {
// See if any of the headers can be used for this
// components profile. As soon as one matches then
// stop and don't look at any more. They are ordered
// in preferred sequence such that the first item is
// the most preferred.
for (String localHeader : component.getHttpheaders()) {
Match headerMatch = matches.get(localHeader);
if (headerMatch != null) {
// Update the statistics about the matching process
// if this header isn't the match instance passed
// to the method.
if (match != headerMatch) {
match.signaturesCompared += headerMatch.signaturesCompared;
match.signaturesRead += headerMatch.signaturesRead;
match.stringsRead += headerMatch.stringsRead;
match.rootNodesEvaluated += headerMatch.rootNodesEvaluated;
match.nodesEvaluated += headerMatch.nodesEvaluated;
match.elapsed += headerMatch.elapsed;
match.setLowestScore(match.getLowestScore() + headerMatch.getDifference());
}
// If the header match used is worst than the
// current one then update the method used for the
// match returned.
if (headerMatch.method.getMatchMethods() > match.method.getMatchMethods()) {
match.method = headerMatch.method;
}
// Set the profile for this component.
for (Profile profile : headerMatch.profiles) {
if (profile.getComponent() == component) {
newProfiles[componentIndex] = profile;
break;
}
}
break;
}
}
// If no profile could be found for the component
// then use the default profile.
if (newProfiles[componentIndex] == null) {
newProfiles[componentIndex] = component.getDefaultProfile();
}
// Move to the next array element.
componentIndex++;
}
// Reset any fields that relate to the profiles assigned
// to the match result or that can't contain a value when
// HTTP headers are used.
match.setSignature(null);
match.results = null;
match.setTargetuserAgent(null);
// Replace the match profiles with the new ones.
match.profiles = newProfiles;
}
}
return match;
}
/**
* For each of the important HTTP headers provides a mapping to a
* match result.
* @param match The single match instance passed into the match method.
* @param headers The HTTP headers available for matching.
* @param importantHeaders HTTP header names important to the match process.
* @return A map of HTTP headers and match instances containing results
* for them.
* @throws IOException
*/
private Map matchForHeaders(
Match match, Map headers, ArrayList importantHeaders)
throws IOException {
// Relates HTTP header names to match resutls.
Map matches = new HashMap();
// Make the first match used the match passed into the method.
// Subsequent matches will use a new instance.
Match currentMatch = match;
// Iterates through the important header names.
for (int i = 0; i < importantHeaders.size(); i++) {
matches.put(importantHeaders.get(i), currentMatch != null ? currentMatch : createMatch());
currentMatch = null;
}
// Using each of the match instances pass the value to the match method
// and set the results.
for (Entry m : matches.entrySet()) {
// At this point we have a collection of the String => Match objects
// where Match objects are empty. Perform the Match for each String
// hence making all matches correspond to the User Agents.
match(headers.get((String)m.getKey()), (Match)m.getValue());
}
return matches;
}
/**
* For a given user agent returns a match containing information about the
* capabilities of the device and it's components.
*
* @param targetUserAgent string representing the user agent to be identified
* @return a match result for the target user agent
* @throws IOException indicates an I/O exception occurred
*/
public Match match(String targetUserAgent) throws IOException {
return match(targetUserAgent, createMatch());
}
/**
* For a given user agent returns a match containing information about the
* capabilities of the device and it's components.
*
* @param targetUserAgent The user agent string to use as the target
* @param match A match object created by a previous match, or via the
* CreateMatch method.
* @return a match containing information about the capabilities of the
* device and it's components
* @throws IOException indicates and I/O exception occurred
*/
public Match match(String targetUserAgent, Match match) throws IOException {
MatchState state;
if (userAgentCache != null && targetUserAgent != null) {
//Increase cache requests.
userAgentCache.incrementRequestsByOne();
state = userAgentCache.tryGetValue(targetUserAgent);
if (state == null) {
// The user agent has not been checked previously. Therefore perform
// the match and store the results in the cache.
match = matchNoCache(targetUserAgent, match);
// Record the match state in the cache for next time.
state = match.new MatchState(match);
//userAgentCache.setActive(targetUserAgent, state);
userAgentCache.active.put(targetUserAgent, state);
//Implement Atomic increase in misses.
userAgentCache.incrementMissesByOne();
} else {
// The state of a previous match exists so the match should
// be configured based on the results of the previous state.
match.setState(state);
}
userAgentCache.addRecent(targetUserAgent, state);
} else {
// The cache does not exist so call the non caching method.
matchNoCache(targetUserAgent, match);
}
return match;
}
private Match matchNoCache(String targetUserAgent, Match match) throws IOException {
match.reset(targetUserAgent);
controller.match(match);
// Update the counts for the provider.
detectionCount.incrementAndGet();
synchronized (methodCounts) {
MatchMethods method = match.getMethod();
Long count = methodCounts.get(method);
long value = count.longValue();
methodCounts.put(method, value++);
}
return match;
}
/**
* Used to check other header fields in case a device user agent is being used
* and returns the devices user agent string.
* @param headers Collection of HTTP headers associated with the request.
* @return the user agent string of the device.
*/
private static String getDeviceUserAgent(Map headers)
{
for(String current : DetectionConstants.DEVICE_USER_AGENT_HEADERS) {
if (headers.get(current.toLowerCase()) != null) {
return headers.get(current.toLowerCase());
}
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy