fiftyone.mobile.detection.entities.Profile Maven / Gradle / Ivy
/* *********************************************************************
* This Source Code Form is copyright of 51Degrees Mobile Experts Limited.
* Copyright © 2017 51Degrees Mobile Experts Limited, 5 Charlotte Close,
* Caversham, Reading, Berkshire, United Kingdom RG4 7BY
*
* This Source Code Form is the subject of the following patents and patent
* applications, owned by 51Degrees Mobile Experts Limited of 5 Charlotte
* Close, Caversham, Reading, Berkshire, United Kingdom RG4 7BY:
* European Patent No. 2871816;
* European Patent Application No. 17184134.9;
* United States Patent Nos. 9,332,086 and 9,350,823; and
* United States Patent Application No. 15/686,066.
*
* 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.
* ********************************************************************* */
package fiftyone.mobile.detection.entities;
import fiftyone.mobile.detection.Dataset;
import fiftyone.mobile.detection.readers.BinaryReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
/**
* Profile is a collection of {@link Value values} for a single
* {@link Component} gathered under a unique Id.
*
* All profiles belong to one of the four components: hardware, software,
* crawler and browser and have a number of values associated with each profile.
* The contents of existing profiles should not change over time. Each value
* contains a getter method for the corresponding {@link Property}.
*
* Profiles make up the unique Id of a device. Each device Id consists of
*
* Each profile relates to one or more {@link Signature}.
*
* New profiles are added with each data file update. Some profiles may be
* removed if we do not see any use of the profile for a long period of time.
* Premium and Enterprise data contain a lot more profiles and hence provide
* better detection results, especially for less common devices.
*
* Compare data options.
*
* Objects of this class should not be created directly as they are part of the
* internal logic. Use the relevant {@link Dataset} method to access these
* objects.
*
* For more information see:
*
* 51Degrees pattern data model.
*/
public abstract class Profile extends BaseEntity implements Comparable {
/**
* Index of the component the profile belongs to.
*/
private final int componentIndex;
/**
* An array of the signature indexes associated with the profile.
*/
@SuppressWarnings("VolatileArrayField")
protected volatile int[] signatureIndexes;
/**
* Unique Id of the profile. Does not change between different data sets.
*/
public final int profileId;
/**
* A list of the indexes of the values associated with the profile.
*/
@SuppressWarnings("VolatileArrayField")
protected volatile int[] valueIndexes;
/**
* Returned when the property has no values in the provide.
*/
private final Value[] emptyValues = new Value[0];
/**
* Map used to relate property index to the values associated with it for
* this profile. Used to speed up the retrieval of values associated with
* the profile and property.
*/
private final ConcurrentHashMap propertyIndexToValues =
new ConcurrentHashMap();
/**
* Constructs a new instance of the Profile
*
* @param dataSet The data set the profile will be contained within.
* @param offset The offset of the profile in the source data structure.
* @param reader Reader connected to the input stream.
*/
public Profile(Dataset dataSet, int offset, BinaryReader reader) {
super(dataSet, offset);
this.componentIndex = reader.readByte();
this.profileId = reader.readInt32();
}
/**
* Compares this profile to another using the numeric ProfileId field.
*
* @param other The component to be compared against.
* @return Indication of relative value based on ProfileId field.
*/
@Override
public int compareTo(Profile other) {
return profileId - other.profileId;
}
/**
* The component the profile belongs to.
*
* @return The {@link Component} the profile belongs to.
* @throws java.io.IOException if there was a problem accessing data file.
*/
@SuppressWarnings("DoubleCheckedLocking")
public Component getComponent() throws IOException {
Component localComponent = component;
if (localComponent == null) {
synchronized (this) {
localComponent = component;
if (localComponent == null) {
component = localComponent =
getDataSet().getComponents().get(componentIndex);
}
}
}
return localComponent;
}
private volatile Component component;
/**
* Returns an array of properties the profile relates to.
*
* @return array of {@link Property properties} associated with this profile.
* @throws java.io.IOException if there was a problem accessing data file.
*/
@SuppressWarnings("DoubleCheckedLocking")
public Property[] getProperties() throws IOException {
Property[] localProperties = properties;
if (localProperties == null) {
synchronized (this) {
localProperties = properties;
if (localProperties == null) {
properties = localProperties = doGetProperties();
}
}
}
return localProperties;
}
@SuppressWarnings("VolatileArrayField")
private volatile Property[] properties;
/**
* Returns an array of properties the profile relates to. Used by the
* getProperties method.
*
* @return Returns an array of properties the profile relates to.
*/
private Property[] doGetProperties() throws IOException {
Set tree = new TreeSet(
new Comparator() {
@Override
public int compare(Property o1, Property o2) {
try {
return o1.getName().compareTo(o2.getName());
} catch (IOException ex) {
return 0;
}
}
});
for (Value value : getValues()) {
tree.add(value.getProperty());
}
return tree.toArray(new Property[tree.size()]);
}
/**
* Gets the values associated with the property name.
*
* @param propertyName string name of the {@link Property} required.
* @return Array of the {@link Values} associated with the property, or null
* if the property does not exist.
* @throws IOException if there was a problem accessing data file.
*/
public Values getValues(String propertyName) throws IOException {
return getValues(dataSet.properties.get(propertyName));
}
/**
* Gets the values associated with the property.
*
* @param property the {@link Property} whose values are required.
* @return Array of the {@link Values} associated with the property, or null
* if the property does not exist.
* @throws java.io.IOException if there was a problem accessing data file.
*/
public Values getValues(Property property) throws IOException {
Values localValues, newValues;
localValues = propertyIndexToValues.get(property.getIndex());
if (localValues == null) {
newValues = getPropertyValues(property);
localValues = propertyIndexToValues.putIfAbsent(
property.getIndex(),
newValues);
if (localValues == null) {
localValues = newValues;
}
}
return localValues;
}
/**
* Gets the values associated with the property for this profile.
*
* @param property Property to be returned.
* @return Array of values associated with the property and profile.
*/
private Values getPropertyValues(Property property) throws IOException {
// Work out the start and end index in the values associated
// with the profile that relate to this property.
Value[] result;
int start = Arrays.binarySearch(
getValueIndexes(),
property.firstValueIndex);
// If the start is negative then the first value doesn't exist.
// Take the complement and use this as the first index which will
// relate to the first value associated with the profile for the
// property.
if (start < 0) {
start = ~start;
}
int end = Arrays.binarySearch(getValueIndexes(),
start,
(getValueIndexes().length),
property.getLastIndexValue());
// If the end is negative then the last value doesn't exist. Take
// the complement and use this as the last index. However if this
// value doesn't relate to the property then it's the first value
// for the next property and we need to move back one in the list.
if (end < 0) {
end = ~end;
if (end >= getValueIndexes().length ||
getValueIndexes()[end] > property.getLastIndexValue()) {
end--;
}
}
// If start is greater than end then there are no values for this
// property in this profile. Return an empty array.
if (start > end) {
result = emptyValues;
} else {
result = new Value[end - start + 1];
for (int i = start, v = 0; i <= end; i++, v++) {
Value value = dataSet.values.get(getValueIndexes()[i]);
result[v] = value;
}
}
// Create the array and populate it with the values for the profile
// and property.
return new Values(property, result);
}
/**
* Gets the signatures related to the profile.
*
* @return array of {@link Signature signatures} associated with this profile.
* @throws java.io.IOException if there was a problem accessing data file.
*/
@SuppressWarnings("DoubleCheckedLocking")
public Signature[] getSignatures() throws IOException {
Signature[] localSignatures = signatures;
if (localSignatures == null) {
synchronized (this) {
localSignatures = signatures;
if (localSignatures == null) {
signatures = localSignatures = doGetSignatures();
}
}
}
return localSignatures;
}
@SuppressWarnings("VolatileArrayField")
private volatile Signature[] signatures;
/**
* Gets the signatures related to the profile.
* @return Returns an array of signatures the profile relates to.
*/
private Signature[] doGetSignatures() throws IOException {
Signature[] array = new Signature[signatureIndexes.length];
for (int i = 0; i < signatureIndexes.length; i++) {
array[i] = getDataSet().getSignatures().get(signatureIndexes[i]);
}
return array;
}
/**
* Gets the values associated with the profile.
*
* @return array of {@link Value values} associated with this profile.
* @throws java.io.IOException if there was a problem accessing data file.
*/
@SuppressWarnings("DoubleCheckedLocking")
public Value[] getValues() throws IOException {
Value[] localValues = values;
if (localValues == null) {
synchronized (this) {
localValues = values;
if (localValues == null) {
values = localValues = doGetValues();
}
}
}
return localValues;
}
@SuppressWarnings("VolatileArrayField")
private volatile Value[] values;
/**
* @return Returns an array of values the profile relates to.
*/
private Value[] doGetValues() throws IOException {
Value[] array = new Value[getValueIndexes().length];
for (int i = 0; i < array.length; i++) {
array[i] = getDataSet().getValues().get(getValueIndexes()[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.
*
* This method should not be called as it is part of the internal logic.
*
* @throws java.io.IOException if there was a problem accessing data file.
*/
public void init() throws IOException {
if (properties == null)
properties = doGetProperties();
if (values == null)
values = doGetValues();
if (signatures == null)
signatures = doGetSignatures();
if (component == null)
component = getDataSet().getComponents().get(componentIndex);
for(Property property : properties) {
propertyIndexToValues.put(
property.getIndex(),
getPropertyValues(property));
}
}
/**
* A string representation of the profiles display values.
*
* @return the profile as a string.
*/
@Override
@SuppressWarnings("DoubleCheckedLocking")
public String toString() {
String localStringValue = stringValue;
if (localStringValue == null) {
synchronized (this) {
localStringValue = stringValue;
if (localStringValue == null) {
List list = new ArrayList();
try {
for (int i = 0; i < getValues().length; i++) {
Value value = getValues()[i];
if (value.getProperty().displayOrder > 0
&& value.getName().contains("Unknown") == false) {
int tempIndex = Collections.binarySearch(
list, value, propertyComparator);
if (tempIndex < 0) {
list.add(~tempIndex, value);
}
}
}
if (list.size() > 0) {
// Values with a display order were found. Sort
// them and then concatenate before returning.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
sb.append(list.get(i).toString());
if (i < list.size() - 1) {
sb.append("/");
}
}
stringValue = localStringValue = sb.toString();
} else {
stringValue =
localStringValue = String.valueOf(profileId);
}
} catch (IOException e) {
stringValue =
localStringValue = String.valueOf(profileId);
}
}
}
}
return localStringValue;
}
private volatile String stringValue;
/**
* Comparator used to order the properties by descending display order.
*/
private static final Comparator propertyComparator = new Comparator() {
@Override
public int compare(Value v1, Value v2) {
try {
if (v1.getProperty().displayOrder < v2.getProperty().displayOrder) {
return 1;
}
if (v1.getProperty().displayOrder > v2.getProperty().displayOrder) {
return -1;
}
return 0;
} catch (IOException e) {
return 0;
}
}
};
/**
* A array of the indexes of the values associated with the profile in order
* of value index in the data set values list.
*
* @return array of the indexes of the values associated with the profile.
* @throws java.io.IOException if there was a problem accessing data file.
*/
public abstract int[] getValueIndexes() throws IOException;
/**
* An array of the signature indexes associated with the profile.
*
* @return An array of the signature indexes associated with the profile.
* @throws java.io.IOException if there was a problem accessing data file.
*/
public abstract int[] getSignatureIndexes() throws IOException;
}