All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fiftyone.mobile.detection.MatchState Maven / Gradle / Ivy

The newest version!
/* *********************************************************************
 * 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;

import fiftyone.mobile.detection.cache.IValueLoader;
import fiftyone.mobile.detection.entities.Node;
import fiftyone.mobile.detection.entities.Profile;
import fiftyone.mobile.detection.entities.Signature;
import fiftyone.properties.MatchMethods;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

/**
 * Used to persist the match results to the cache. Used with the SetState
 * method of the match class to retrieve the state.
 */
public class MatchState extends MatchResult 
    implements IValueLoader {

    /**
     * Sets the elapsed time for the match.
     */    
    void setElapsed(long value) {
        super.elapsed = value;
    }

    /**
     * Sets the method used to obtain the match. MatchMethods provides 
     * descriptions of the possible return values. When used with multi HTTP 
     * headers the worst method used for all the HTTP headers.
     */    
    void setMethod(MatchMethods value) {
        super.method = value;
    }
   
    /**
     * Increments the nodes evaluated by one.
     */
    public void incrNodesEvaluated() {
        super.nodesEvaluated++;
    }    

    /**
     * Increments the number of root nodes checked against the target 
     * User-Agent by one.
     */        
    void incrRootNodesEvaluated() {
        super.rootNodesEvaluated++;
    }        

    /**
     * Sets the signature with the closest match to the target User-Agent 
     * provided.
     */
    void setSignature(Signature value) {
        super.signature = value;
    }

    /**
     * Increments the number of signatures that were compared against the target 
     * User-Agent if the Closest match method was used.
     */    
    void incrSignaturesCompared(int value) {
        super.signaturesCompared += value;
    }
    
    /**
     * Increments the number of signatures that were compared against the target 
     * User-Agent if the Closest match method was used by one.
     */        
    void incrSignaturesCompared() {
        super.signaturesCompared++;
    }

    /**
     * Increments the number of signatures read during the detection.
     */    
    void incrSignaturesRead(int value) {
        super.signaturesRead += value;
    }
    
    /**
     * Increments the number of signatures read during the detection by one.
     */       
    void incrSignaturesRead() {
        super.signaturesRead++;
    }    

    /**
     * Increments the strings read by one.
     */
    public void incrStringsRead() {
        super.stringsRead++;
    }

    /**
     * Increments the number of closest signatures returned for evaluation.
     */    
    void setClosestSignaturesCount(int value) {
        super.closestSignaturesCount = value;
    }

    /**
     * Increments the lowest score recorded for the signature that was found.
     * 
     * @param value to increment by.
     */    
    public void incrLowestScore(int value) {
        super.lowestScore += value;
    }
    
    /**
     * Sets the lowest score to the value provided.
     * @param value of new lowest score
     */
    public void setLowestScore(int value) {
        super.lowestScore = value;
    }

    /**
     * Sets the target User-Agent string used for the detection where a 
     * single User-Agent was provided. If multiple HTTP headers were provided 
     * then this value will be null.
     */    
    void setTargetUserAgent(String value) {
        super.targetUserAgent = value;
    }
   
    /**
     * @return An array of the nodes associated with the match result. Used for 
     * further analysis of the results and gaining a string representation of 
     * the match.
     */    
    @Override
    Node[] getNodes() {
        Node[] result = new Node[nodesList.size()];
        nodesList.toArray(result);
        return result;
    }
    public ArrayList getNodesList() {
        return nodesList;
    }
    final ArrayList nodesList = new ArrayList();

    /**
     * During the process of matching the profiles may vary, for example when
     * multiple HTTP headers are used. The property handle will default to 
     * the profiles associated with a Signature if available, or provides an
     * empty list for profiles to be added to.
     * @return profiles associated with the match state.
     * @throws java.io.IOException if there was a problem accessing data file.
     */
    @SuppressWarnings("DoubleCheckedLocking")
    public ArrayList getExplicitProfiles() throws IOException {
        ArrayList result = explicitProfiles;
        if (result == null) {
            synchronized(this) {
                result = explicitProfiles;
                if (result == null) {
                    if (getSignature() != null) {
                        result = new ArrayList();
                        result.addAll(Arrays.asList(
                                getSignature().getProfiles()));
                    }
                    else {
                        result = new ArrayList();
                    }
                    explicitProfiles = result;
                }
            }
        }
        return result;
    }
    volatile private ArrayList explicitProfiles;

    /**
     * @return Array of profiles associated with the device that was found.
     * @throws IOException if there was a problem accessing data file.
     */    
    @Override
    public Profile[] getProfiles() throws IOException {
        Profile[] result = new Profile[getExplicitProfiles().size()];
        getExplicitProfiles().toArray(result);
        return result;
    }
    
    /**
     * @return The target User-Agent represented as an array of bytes.
     */    
    public byte[] getTargetUserAgentArray() {
        return targetUserAgentArray;
    }
    private byte[] targetUserAgentArray;
    
    int nextCharacterPositionIndex;

    final ArrayList signatures = new ArrayList();

    final Match match;

    Dataset getDataSet() {
        return match.getDataSet();
    }
    
    /**
     * Constructs a new instance of MatchState.
     * @param match instance the state relates to
     */
    MatchState(Match match) {
        super(match.getDataSet());
        this.match = match;
        super.method = MatchMethods.NONE;
    }

    /**
     * Constructs a new instance of MatchState initialised with the target
     * User-Agent provided.
     * @param match instance the state relates to
     * @param targetUserAgent
     * @throws UnsupportedEncodingException 
     */
    MatchState(Match match, String targetUserAgent)
            throws UnsupportedEncodingException {
        this(match);
        init(targetUserAgent);
    }
    
    /**
     * Resets the fields to default values. Used to avoid having to reallocate 
     * memory for data structures when a lot of detections are being performed.
     */    
    void reset() {
        method = MatchMethods.NONE;
        nodesEvaluated = 0;
        rootNodesEvaluated = 0;
        signaturesCompared = 0;
        signaturesRead = 0;
        stringsRead = 0;
        signature = null;
        signatures.clear();
        nodesList.clear();
        explicitProfiles = null;
    }
    
    /**
     * Resets the match for the User-Agent returning all the fields to the
     * values they would have when the match was first constructed. Used to
     * avoid having to reallocate memory for data structures when a lot of
     * detections are being performed.
     *
     * @param targetUserAgent
     * @throws UnsupportedEncodingException
     */    
    void reset(String targetUserAgent) throws UnsupportedEncodingException {
        reset();
        init(targetUserAgent);
    }
    
    /**
     * Initialises the match object ready for detection.
     *
     * @param targetUserAgent
     * @throws UnsupportedEncodingException
     */
    final void init(String targetUserAgent) throws UnsupportedEncodingException {
        if (targetUserAgent != null && targetUserAgent.length() > 0) {
            this.targetUserAgentArray = targetUserAgent.getBytes("US-ASCII");
        } else {
            this.targetUserAgentArray = new byte[0];
        }
        
        // Null check to ensure no down stream problems.
        this.targetUserAgent = targetUserAgent == null ? "" : targetUserAgent;

        resetNextCharacterPositionIndex();
    }
    
    /**
     * Reset the next character position index based on the length of the target
     * User-Agent and the root nodes.
     */
    void resetNextCharacterPositionIndex() {
        // Start checking on the far right of the User-Agent.
        nextCharacterPositionIndex = Math.min(
                targetUserAgentArray.length - 1,
                getDataSet().rootNodes.size() - 1);
    }
    
    /**
     * Inserts the node into the list checking to find it's correct position in
     * the list first.
     *
     * @param node The node to be added to the match list
     * @return The index of the node inserted into the list
     */
    int insertNode(Node node) {
        int index = ~Collections.binarySearch(nodesList, node);
        nodesList.add(index, node);
        return index;
    }
    
    /**
     * Returns the start character position of the node within the target user
     * agent, or -1 if the node does not exist.
     *
     * @param node
     * @return
     * @throws IOException
     */
    int getIndexOf(Node node) throws IOException {
        byte[] characters = node.getCharacters();
        int finalIndex = characters.length - 1;
        for (int index = 0; index < getTargetUserAgentArray().length - characters.length; index++) {
            for (int nodeIndex = 0, targetIndex = index;
                    nodeIndex < characters.length && targetIndex < getTargetUserAgentArray().length;
                    nodeIndex++, targetIndex++) {
                if (characters[nodeIndex] != getTargetUserAgentArray()[targetIndex]) {
                    break;
                } else if (nodeIndex == finalIndex) {
                    return index;
                }
            }
        }
        return -1;
    }    
    
    /**
     * Merges the other match state with this instance. Used when processing
     * multiple HTTP headers to produce a state that includes data from all 
     * HTTP headers.
     * @param other state to be merged with this instance
     */
    void merge(MatchState other) {

        signaturesCompared += other.getSignaturesCompared();
        signaturesRead += other.getSignaturesRead();
        stringsRead += other.getStringsRead();
        rootNodesEvaluated += other.getRootNodesEvaluated();
        nodesEvaluated += other.getNodesEvaluated();
        elapsed += other.getElapsed();
        lowestScore += other.getLowestScore();

        // If the header match used is worst than the current one
        // then update the method used for the match returned.
        if (other.getMethod().getMatchMethods() > 
            method.getMatchMethods()) {
            method = other.getMethod();
        }        
    }

    @Override
    public MatchResult load(String key) throws IOException {
        match.provider.matchNoCache(key, this);
        return new MatchResult(this);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy