
fiftyone.mobile.detection.entities.Signature Maven / Gradle / Ivy
Show all versions of 51Degrees.detection.core Show documentation
package fiftyone.mobile.detection.entities;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import fiftyone.mobile.detection.Dataset;
import fiftyone.mobile.detection.SortedList;
import fiftyone.mobile.detection.readers.BinaryReader;
import fiftyone.properties.DetectionConstants;
import java.util.logging.Level;
import java.util.logging.Logger;
/* *********************************************************************
* 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.
* ********************************************************************* */
/**
* Signature of a user agent. A signature contains those characters of a
* user agent which are relevant for the purposes of device detection. For
* example; most user agents will start with "Mozilla" and therefore these
* characters are of very little use when detecting devices. Other characters
* such as those that represent the model of the hardware are very relevant.
* A signature contains both an array of relevant characters from user agents
* identified when the data was created and the unique complete node identifies
* of relevant sub strings contained in multiple signatures and user agents.
* Together this information is used at detection time to rapidly identify the
* signature matching a target user agent.
Signatures relate to device
* properties via profiles. Each signature relates to one profile for each
* component type.
For more information about signature see
* http://51degrees.com/Support/Documentation/Java
Unlike other entities
* the signature may have a varying number of nodes and profiles associated with
* it depending on the data set. All signatures within a data set will have the
* same number of profiles and nodes associated with them all. As these can
* change across data sets they can't be included in the source code. As such a
* secondary header is used in the data set to indicate the number of profiles
* and nodes in use. The Memory.SignatureList and Stream.SignatureList lists are
* therefore used to manage lists of signatures rather than the generic lists.
*/
/**
* The relevant characters from a user agent structured in a manner to enable
* rapid comparison with a target user agent.
*/
public abstract class Signature extends BaseEntity implements Comparable {
/**
* Offsets to profiles associated with the signature.
*/
private int[] profileOffsets;
/**
* List of the profiles the signature relates to.
*/
private Profile[] profiles;
/**
* An array of nodes associated with the signature.
*/
private Node[] nodes;
/**
* The unique Device Id for the signature.
*/
private String deviceId;
/**
* The length in bytes of the signature.
*/
private int _length;
/**
* The signature as a string.
*/
private String stringValue;
/**
* Values associated with the property names.
*/
private SortedList nameToValues;
/**
* Constructs a new instance of Signature
*
* @param dataSet The data set the node is contained within
* @param index The index in the data structure to the node
* @param reader Reader connected to the source data structure and
* positioned to start reading
*/
public Signature(Dataset dataSet, int index, BinaryReader reader) {
super(dataSet, index);
profileOffsets = readOffsets(dataSet, reader, dataSet.signatureProfilesCount);
this.nodes = null;
this.profiles = null;
this.nameToValues = null;
}
/**
* List of the profiles the signature relates to.
* @return List of the profiles the signature relates to
* @throws IOException indicates an I/O exception occurred
*/
public Profile[] getProfiles() throws IOException {
if (profiles == null) {
synchronized (this) {
if (profiles == null) {
profiles = getProfiles(profileOffsets);
}
}
}
return profiles;
}
/**
* The unique Device Id for the signature.
* @return unique Device Id for the signature
* @throws IOException indicates an I/O exception occurred
*/
public String getDeviceId() throws IOException {
if (deviceId == null) {
synchronized (this) {
if (deviceId == null) {
deviceId = initGetDeviceId();
}
}
}
return deviceId;
}
/**
* Gets the values associated with the property name.
* @param propertyName Name of the property whose values are required.
* @return Value(s) associated with the property, or null if the property
* does not exist.
* @throws java.io.IOException
*/
public Values getValues(String propertyName) throws IOException {
// Does the storage structure already exist?
if (nameToValues == null) {
synchronized (this) {
if (nameToValues == null) {
nameToValues = new SortedList();
}
}
}
// Do the values already exist for the property?
synchronized (nameToValues) {
Values result = nameToValues.get(propertyName);
if (result != null) {
return result;
}
// Does not exist already so get the property.
Property prop = dataSet.get(propertyName);
if (prop != null) {
// Create the list of values.
List vals = new ArrayList();
for (Value v : getValues()) {
if (prop.getIndex() == v.getProperty().getIndex()) {
vals.add(v);
}
}
result = new Values(prop, vals);
if (result.size() == 0) {
result = null;
}
}
// Store for future reference.
nameToValues.add(propertyName, result);
return result;
}
}
public Value[] getValues() throws IOException {
if (values == null) {
synchronized (this) {
if (values == null) {
values = initGetValues();
}
}
}
return values;
}
private Value[] values;
/**
* The length in bytes of the signature.
*
* @return length in bytes of the signature
* @throws IOException indicates an I/O exception occurred
*/
public int getLength() throws IOException {
if (_length == 0) {
synchronized (this) {
if (_length == 0) {
_length = getSignatureLength();
}
}
}
return _length;
}
/**
* Returns an array of nodes associated with the signature.
* @return an array of nodes associated with the signature.
* @throws java.io.IOException
*/
protected Node[] doGetNodes() throws IOException {
Node[] nodesLocal = new Node[getNodeOffsets().length];
for (int i = 0; i < getNodeOffsets().length; i++) {
nodesLocal[i] = dataSet.nodes.get(getNodeOffsets()[i]);
}
return nodesLocal;
}
/**
* An array of nodes associated with the signature.
* @return An array of nodes associated with the signature.
*/
public Node[] getNodes() {
if (nodes == null) {
synchronized(this) {
if (nodes == null) {
try {
nodes = doGetNodes();
} catch (IOException ex) {
Logger.getLogger(Signature.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
return nodes;
}
/**
* Uses the offsets list which must be locked to read in the arrays of nodes
* or profiles that relate to the signature.
*
* @param dataSet The data set the node is contained within
* @param reader Reader connected to the source data structure and
* positioned to start reading
* @param length The number of offsets to read in
* @return An array of the offsets as integers read from the reader
*/
protected int[] readOffsets(Dataset dataSet, BinaryReader reader, int length) {
reader.list.clear();
for (int i = 0; i < length; i++) {
int profileIndex = reader.readInt32();
if (profileIndex >= 0) {
reader.list.add(profileIndex);
}
}
int[] array = new int[reader.list.size()];
for (int i = 0; i < array.length; i++) {
array[i] = reader.list.get(i);
}
return array;
}
/**
* Called after the entire data set has been loaded to ensure any further
* initialisation steps that require other items in the data set can be
* completed.
*
* @throws IOException indicates an I/O exception occurred
*/
public void init() throws IOException {
if (nodes == null)
nodes = getNodes();
if (profiles == null)
profiles = getProfiles();
if (values == null)
values = getValues();
if (deviceId == null)
deviceId = getDeviceId();
}
private String initGetDeviceId() throws IOException {
// Turn them into a string separated by hyphens.
StringBuilder builder = new StringBuilder();
for (int i = 0; i < getProfiles().length; i++) {
builder.append(getProfiles()[i].profileId);
if (i < getProfiles().length - 1) {
builder.append(DetectionConstants.PROFILE_SEPARATOR);
}
}
return builder.toString();
}
/**
* Gets a string list of the properties and names.
*
* @return dictionary of properties and values for the signature
* @throws IOException indicates an I/O exception occurred
*/
public SortedList> getPropertyValuesAsStrings() throws IOException {
SortedList> list = new SortedList>();
for (Value value : getValues()) {
if (!list.containsKey(value.getProperty().getName())) {
list.add(value.getProperty().getName(), new ArrayList());
}
list.get(value.getProperty().getName()).add(value.getName());
}
return list;
}
/**
* Returns an array of values associated with the signature.
* @return an array of values associated with the signature.
* @throws IOException
*/
private Value[] initGetValues() throws IOException {
List result = new ArrayList();
for (Profile profile : getProfiles()) {
result.addAll(Arrays.asList(profile.getValues()));
}
return result.toArray(new Value[result.size()]);
}
/**
* Returns an array of profiles associated with the signature.
*
* @param profileIndexes
* @return an array of profiles associated with the signature.
* @throws IOException
*/
private Profile[] getProfiles(int[] profileIndexes) throws IOException {
List prof = new ArrayList();
for (int index : profileIndexes) {
prof.add(getDataSet().getProfiles().get(index));
}
return prof.toArray(new Profile[prof.size()]);
}
/**
* Compares this signature to a list of node offsets.
*
* @param nodes list of nodes to compare to
* @return Indication of relative value based on the node offsets
*/
public int compareTo(List nodes) {
int length = Math.min(getNodeOffsets().length, nodes.size());
for (int i = 0; i < length; i++) {
int difference = getNodeOffsets()[i] - nodes.get(i).getIndex();
if (difference != 0) {
return difference;
}
}
if (getNodeOffsets().length < nodes.size()) {
return -1;
}
if (getNodeOffsets().length > nodes.size()) {
return 1;
}
return 0;
}
/**
* Compares this signature to another based on the node offsets. The node
* offsets in both signatures must be in ascending order.
*
* @param other The signature to be compared against
* @return Indication of relative value based based on node offsets
*/
@Override
public int compareTo(Signature other) {
int length = Math.min(getNodeOffsets().length, other.getNodeOffsets().length);
for (int i = 0; i < length; i++) {
int difference = getNodeOffsets()[i] - other.getNodeOffsets()[i];
if (difference != 0) {
return difference;
}
}
if (getNodeOffsets().length < other.getNodeOffsets().length) {
return -1;
}
if (getNodeOffsets().length > other.getNodeOffsets().length) {
return 1;
}
return 0;
}
/**
* String representation of the signature where irrelevant characters are
* removed.
* @return The signature as a string
*/
@Override
public String toString() {
if (stringValue == null) {
synchronized (this) {
if (stringValue == null) {
try {
byte[] buffer = new byte[getLength()];
for (Node n : getNodes()) {
n.addCharacters(buffer);
}
for (int i = 0; i < buffer.length; i++) {
if (buffer[i] == 0) {
buffer[i] = ' ';
}
}
stringValue = new String(buffer, "US-ASCII");
} catch (IOException e) {
return e.getMessage();
}
}
}
}
return stringValue;
}
/**
* Array of node offsets associated with the signature.
* @return Array of node offsets associated with the signature.
*/
public abstract int[] getNodeOffsets();
/**
* The number of characters in the signature.
* @return The number of characters in the signature.
*/
protected abstract int getSignatureLength();
/**
* Gets the rank, where a lower number means the signature is more popular,
* of the signature compared to other signatures.
* @return Rank of the signature.
*/
public abstract int getRank();
}