
fiftyone.mobile.detection.TrieProvider 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 java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import fiftyone.mobile.detection.entities.stream.TriePool;
import fiftyone.mobile.detection.readers.TrieReader;
import java.util.Map.Entry;
/* *********************************************************************
* 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.
* ********************************************************************* */
/**
* Decision trie data structure provider.
*/
public abstract class TrieProvider implements IDisposable {
/**
* The type of integers used to represent the offset to the children.
*/
public enum OffsetType {
/**
* The offsets in the node are 16 bit integers.
*/
Bits16,
/**
* The offsets in the node are 32 bit integers.
*/
Bits32,
/**
* The offsets in the node are 64 bit integers.
*/
Bits64;
public static OffsetType fromByte(byte value) {
switch (value) {
case 0:
return OffsetType.Bits16;
case 1:
return OffsetType.Bits32;
case 2:
return OffsetType.Bits64;
}
return null;
}
}
private static final int SIZE_OF_LONG = 8,
SIZE_OF_UINT = 4,
SIZE_OF_INT = 4,
SIZE_OF_USHORT = 2,
SIZE_OF_SHORT = 2,
SIZE_OF_UBYTE = 1,
SIZE_OF_BYTE = 1;
/**
* The copy right notice associated with the data file.
*/
public String copyright;
/**
* Byte array of the Strings available.
*/
private ByteBuffer _Strings;
/**
* Byte array of the available properties.
*/
protected ByteBuffer _properties;
/**
* Byte array of the devices list.
*/
private ByteBuffer _devices;
/**
* Byte array of the look up list loaded into memory.
*/
private short[] _lookupList;
/**
* A pool of readers that can be used in multi threaded operation.
*/
private TriePool pool;
/**
* The position in the source data file of the nodes.
*/
private long _nodesOffset;
/**
* Dictionary of property names to indexes.
*/
protected final Map _propertyIndex = new HashMap();
/**
* List of the available property names.
*/
protected final List _propertyNames = new ArrayList();
/**
* The number of properties available in total.
* TODO: remove
*/
//private int _propertyCount = 0;
/**
* List of Http headers for each property index.
*/
protected final List propertyHttpHeaders = new ArrayList();
/**
* A list of Http headers that the provider can use for device detection.
*/
private List httpHeaders;
/**
* List of all property names for the provider.
* @return list of all property names for the provider
*/
public List propertyNames() {
return _propertyNames;
}
/**
* Constructs a new instance of a tree provider.
*
* @param copyright The copyright notice for the data file.
* @param strings Array containing all Strings in the output.
* @param properties Array of properties.
* @param devices Array of devices.
* @param lookupList Lookups data array.
* @param nodesLength The length of the node data.
* @param nodesOffset The position of the start of the nodes in the file
* provided.
* @param pool Pool connected to the data source.
* @throws FileNotFoundException indicates device data file was not found.
*/
public TrieProvider(String copyright, byte[] strings, byte[] properties, byte[] devices,
short[] lookupList, long nodesLength, long nodesOffset, TriePool pool) throws FileNotFoundException {
this.copyright = copyright;
_Strings = ByteBuffer.wrap(strings);
_properties = ByteBuffer.wrap(properties);
_devices = ByteBuffer.wrap(devices);
_lookupList = lookupList;
_nodesOffset = nodesOffset;
// Creates a pool to use to access the source data file.
this.pool = pool;
_Strings.order(ByteOrder.LITTLE_ENDIAN);
_properties.order(ByteOrder.LITTLE_ENDIAN);
_devices.order(ByteOrder.LITTLE_ENDIAN);
}
/**
* Returns a list of Http headers that the provider can use for
* device detection.
* @return a list of Http headers that the provider can use for
* device detection.
*/
public List getHttpHeaders() {
if (httpHeaders == null) {
synchronized(this) {
if (httpHeaders == null) {
httpHeaders = new ArrayList();
for (String[] sa : propertyHttpHeaders) {
for (String s : sa) {
if (!httpHeaders.contains(s))
httpHeaders.add(s);
}
}
}
}
}
return httpHeaders;
}
/**
* Returns the user agent matched against the one provided.
*
* @param userAgent user agent to match
* @return user agent string that was matched against the user agent provided
* @throws Exception indicates an exception occurred
*/
public String getUserAgent(String userAgent) throws Exception {
StringBuilder matchedUserAgent = new StringBuilder();
TrieReader reader = pool.getReader();
reader.setPos(_nodesOffset);
getDeviceIndex(
reader,
getUserAgentByteArray(userAgent),
0,
0,
matchedUserAgent);
pool.release(reader);
return matchedUserAgent.toString();
}
/**
* Returns the index of the device associated with the given user agent. The
* index returned may vary across different versions of the source data file
* and should not be stored. The "Id" property will remain unique.
*
* @param userAgent user agent to get device index of.
* @return index of the device associated with the given user agent.
* @throws Exception indicates an exception occurred
*/
public int getDeviceIndex(String userAgent) throws Exception {
int index;
TrieReader reader = pool.getReader();
try {
reader.setPos(_nodesOffset);
index = getDeviceIndex(
reader,
getUserAgentByteArray(userAgent),
0,
0
);
} finally {
pool.release(reader);
}
return index;
}
/**
* Returns the device id matching the device index.
*
* @param deviceIndex index of the device whose Id should be returned
* @return device id matching the device index
*/
public String getDeviceId(int deviceIndex) {
return getPropertyValue(deviceIndex, "Id");
}
/**
* Returns a collection of device indexes for each of the relevant HTTP
* headers provided. Those headers which are unrelated to device detection
* are ignored.
* @param headers Collection of HTTP headers and values.
* @return Collection of headers and device indexes for each one.
*/
public Map getDeviceIndexes(final Map headers) {
Map indexes = new TreeMap();
if (headers != null) {
TrieReader reader = null;
try {
reader = pool.getReader();
for (Entry entry : headers.entrySet()) {
String header = entry.getKey().toString();
if (getHttpHeaders().contains(header)) {
indexes.put(header, getDeviceIndex(headers.get(header)));
}
}
} catch (Exception ex) {
Logger.getLogger(TrieProvider.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (reader != null) {
pool.release(reader);
}
}
}
return indexes;
}
/**
* Returns the property value based on the useragent provided.
*
* @param deviceIndex The index of the device whose property should be
* returned.
* @param property the name of the property required.
* @return The value of the property for the given device index.
*/
public String getPropertyValue(int deviceIndex, String property) {
return getPropertyValue(deviceIndex, _propertyIndex.get(property));
}
/**
* Returns the property value based on the device indexes provided.
* @param deviceIndexes Http headers and their device index.
* @param property The name of the property required.
* @return The value of the property for the given device index.
*/
public String getPropertyValue(Map deviceIndexes, String property) {
return getPropertyValue(deviceIndexes, _propertyIndex.get(property));
}
/**
* Returns the value of the property index provided from the device indexes
* provided. Matches the Http header to the property index.
* @param deviceIndexes Indexes for the device.
* @param propertyIndex Index of the property required.
* @return The value of the property for the given device indexes.
*/
public String getPropertyValue(Map deviceIndexes, int propertyIndex) {
Integer deviceIndex;
for (String header : propertyHttpHeaders.get(propertyIndex)) {
deviceIndex = deviceIndexes.get(header);
if (deviceIndex != null) {
return getPropertyValue(deviceIndex, propertyIndex);
}
}
return null;
}
/**
* Returns the value of the property index provided for the device index
* provided.
*
* @param deviceIndex Index for the device.
* @param propertyIndex Index of the property required.
* @return The value of the property index for the given device index.
*/
public String getPropertyValue(int deviceIndex, int propertyIndex) {
int devicePosition = deviceIndex * _propertyNames.size() * SIZE_OF_INT;
int offset = devicePosition + (propertyIndex * SIZE_OF_INT);
return getStringValue(_devices.getInt(offset));
}
/**
* Returns the value of the property for the user agent provided.
* @param headers Collection of HTTP headers and values.
* @param propertyName Name of the property required.
* @return The value of the property for the given user agent.
*/
public String getPropertyValueWithMultiHeaders(Map headers, String propertyName) {
return getPropertyValue(getDeviceIndexes(headers), propertyName);
}
/**
* Returns the value of the property for the user agent provided.
* @param userAgent User agent of the request.
* @param propertyname Name of the property required.
* @return The value of the property for the given user agent.
* @throws Exception
*/
public String getPropertyValue(String userAgent, String propertyname) throws Exception {
return getPropertyValue(getDeviceIndex(userAgent), propertyname);
}
/**
* Disposes of the pool assigned to the provider.
*/
@Override
public void dispose() {
pool.dispose();
}
/**
* Returns the String at the offset provided.
*
* @param offset
*/
protected String getStringValue(int offset) {
int index = 0;
StringBuilder builder = new StringBuilder();
byte current = _Strings.get(offset);
while (current != 0) {
builder.append((char) current);
index++;
current = _Strings.get(offset + index);
}
return builder.toString();
}
/**
* Converts a user agent in to a null terminated byte array.
*
* @param userAgent The useragent to be tested.
* @return A null terminated byte array.
*/
private static byte[] getUserAgentByteArray(String userAgent) {
byte[] result = new byte[userAgent != null ? userAgent.length() + 1 : 0];
if (result.length > 0) {
for (int i = 0; i < userAgent.length(); i++) {
result[i] = userAgent.charAt(i) <= 0x7F ? (byte) userAgent.charAt(i) : (byte) ' ';
}
result[result.length - 1] = 0;
}
return result;
}
/**
* Returns the offset in the node for the current character.
*
* @param lookupOffset The offset in the byte array
* @param value The value to be checked.
* @return The position to move to.
*/
private short getChild(int lookupOffset, byte value) throws ArrayIndexOutOfBoundsException {
try {
short lowest = _lookupList[lookupOffset];
short highest = _lookupList[lookupOffset + 1];
if (value < lowest || value > highest) {
return Byte.MAX_VALUE;
}
// Statement is equivalent to "(lookupOffset + value - lowest) + 2".
int index = lookupOffset + value - lowest + 2;
return _lookupList[index];
} catch (ArrayIndexOutOfBoundsException ex) {
throw ex;
}
}
/**
* The number of bytes each offset takes.
*
* @param offsetType offset type
* @return number of bytes each offset takes.
*/
public static int sizeOfOffsets(OffsetType offsetType) {
switch (offsetType) {
case Bits16:
return SIZE_OF_USHORT;
case Bits32:
return SIZE_OF_UINT;
default:
return SIZE_OF_LONG;
}
}
/**
* Returns the position in the nodes stream of the next node.
*
* @param reader Reader with exclusive access to the underlying file
* @param childIndex
* @param numberOfChildren
* @param offsetType
*/
private void setNextNodePosition(
TrieReader reader,
short childIndex,
short numberOfChildren,
OffsetType offsetType)
throws IOException {
if (childIndex == 0) {
// Move past the children offset to the first child.
long offset = reader.getPos() + (numberOfChildren - 1) * sizeOfOffsets(offsetType);
reader.setPos(offset);
} else {
// Move to the bytes that represent the node position of the next node based on the child index.
reader.setPos(reader.getPos() + (childIndex - 1) * sizeOfOffsets(offsetType));
long pos = reader.getPos();
switch (offsetType) {
case Bits16:
pos += reader.readUShort();
break;
case Bits32:
pos += reader.readUInt();
break;
default:
pos += reader.readLong();
break;
}
reader.setPos(pos);
}
}
/**
* Returns the offset in the device byte array to the device matching the
* useragent provided.
*
* @param reader Reader with exclusive access to the underlying file.
* @param userAgent A null terminated byte array of the user agent to be
* tested.
* @param index The index in the array of the current character.
* @param parentDeviceIndex The device index of the parent node.
* @return The device id with the most number of matching characters.
*/
private int getDeviceIndex(TrieReader reader, byte[] userAgent, int index, int parentDeviceIndex) throws Exception {
// Position the reader for the nodePosition.
// Get the lookup list.
int lookupListOffset = reader.readInt();
// If there are no more characters in the user agent return
// the parent device index.
if (index == userAgent.length)
return parentDeviceIndex;
// Get the index of the child.
int childIndex = getChild(Math.abs(lookupListOffset), userAgent[index]);
// Get the index of the device.
int deviceIndex;
if (lookupListOffset >= 0) {
// The lookup list is positive so the device index
// is contained as the next 4 bytes.
deviceIndex = reader.readInt();
} else {
// The look list is negative so the device index
// of this node is the same as the parent device index.
deviceIndex = parentDeviceIndex;
}
// If the child index indicates no children then
// return the current device.
if (childIndex == Byte.MAX_VALUE) {
return deviceIndex;
}
// Get the number of children and check we're still within
// the range for this node.
byte numberOfChildren = reader.readByte();
if (childIndex >= numberOfChildren) {
return deviceIndex;
}
// If there's only 1 child then it will appear immediately after
// this element. The position will already be set at that position.
if (numberOfChildren == 1) {
return getDeviceIndex(
reader,
userAgent,
index + 1,
deviceIndex);
}
// There's more than 1 child so find the integer type used for the
// offset and then move to that position recognising the 1st child
// always appears at the position immediately after the list of children.
OffsetType offsetType = OffsetType.fromByte(reader.readByte());
setNextNodePosition(reader, (short) childIndex, numberOfChildren, offsetType);
return getDeviceIndex(
reader,
userAgent,
index + 1,
deviceIndex);
}
/**
* Returns the offset in the device byte array to the device matching the
* useragent provided.
*
* @param reader Reader with exclusive access to the underlying file.
* @param userAgent A null terminated byte array of the user agent to be
* tested.
* @param index The index in the array of the current character.
* @param parentDeviceIndex The parent device index to be used if this node
* doesn't have a different one.
* @param matchedUserAgent">The characters of the user agent matched.
* @return The device id with the most number of matching characters.
*/
private int getDeviceIndex(TrieReader reader, byte[] userAgent, int index, int parentDeviceIndex, StringBuilder matchedUserAgent) throws Exception {
// Add the character to the matched user agent.
matchedUserAgent.append((char) userAgent[index]);
// Get the lookup list.
int lookupListOffset = reader.readInt();
// Get the index of the child.
int childIndex = getChild(Math.abs(lookupListOffset), userAgent[index]);
// Get the index of the device.
int deviceIndex;
if (lookupListOffset >= 0) {
// The lookup list is positive so the device index
// is contained as the next 4 bytes.
deviceIndex = reader.readInt();
} else {
// The look list is negative so the device index
// of this node is the same as the parent device index.
deviceIndex = parentDeviceIndex;
}
// If the child index indicates no children then
// return the current device.
if (childIndex == Byte.MAX_VALUE) {
return deviceIndex;
}
// Get the number of children and check we're still within
// the range for this node.
byte numberOfChildren = reader.readByte();
if (childIndex >= numberOfChildren) {
return deviceIndex;
}
// If there's only 1 child then it will appear immediately after
// this element. The position will already be set at that position.
if (numberOfChildren == 1) {
return getDeviceIndex(
reader,
userAgent,
index + 1,
deviceIndex,
matchedUserAgent);
}
// There's more than 1 child so find the integer type used for the
// offset and then move to that position recognising the 1st child
// always appears at the position immediately after the list of children.
OffsetType offsetType = OffsetType.fromByte(reader.readByte());
setNextNodePosition(reader, (short) childIndex, numberOfChildren, offsetType);
return getDeviceIndex(
reader,
userAgent,
index + 1,
deviceIndex,
matchedUserAgent);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy