collection.PhysicalWebCollection.java Source code

Java tutorial

Introduction

Here is the source code for collection.PhysicalWebCollection.java

Source

/*
 * Copyright 2015 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package collection;

import org.apache.commons.codec.binary.Base64;
import org.json.JSONArray;
import org.json.JSONObject;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Collection of Physical Web URL devices and related metadata.
 */
public class PhysicalWebCollection {
    private static final int SCHEMA_VERSION = 1;
    private static final String SCHEMA_VERSION_KEY = "schema";
    private static final String DEVICES_KEY = "devices";
    private static final String METADATA_KEY = "metadata";
    private static final String ICON_MAP_KEY = "iconmap";
    private PwsClient mPwsClient;
    private Map<String, UrlDevice> mDeviceIdToUrlDeviceMap;
    private Map<String, PwsResult> mBroadcastUrlToPwsResultMap;
    private Map<String, byte[]> mIconUrlToIconMap;
    private Set<String> mPendingBroadcastUrls;
    private Set<String> mPendingIconUrls;
    private Set<String> mFailedResolveUrls;

    /**
     * Construct a PhysicalWebCollection.
     */
    public PhysicalWebCollection() {
        mPwsClient = new PwsClient();
        mDeviceIdToUrlDeviceMap = new HashMap<>();
        mBroadcastUrlToPwsResultMap = new HashMap<>();
        mIconUrlToIconMap = new HashMap<>();
        mPendingBroadcastUrls = new HashSet<>();
        mPendingIconUrls = new HashSet<>();
        mFailedResolveUrls = new HashSet<>();
    }

    /**
     * Add a UrlDevice to the collection.
     * @param urlDevice The UrlDevice to add.
     * @return true if the device already existed in the map
     */
    public boolean addUrlDevice(UrlDevice urlDevice) {
        boolean alreadyFound = mDeviceIdToUrlDeviceMap.containsKey(urlDevice.getId());
        mDeviceIdToUrlDeviceMap.put(urlDevice.getId(), urlDevice);
        return alreadyFound;
    }

    /**
     * Remove a UrlDevice from the collection.
     * @param urlDevice The UrlDevice to remove.
     */
    public void removeUrlDevice(UrlDevice urlDevice) {
        mDeviceIdToUrlDeviceMap.remove(urlDevice.getId());
    }

    /**
     * Add URL metadata to the collection.
     * @param pwsResult The PwsResult to add.
     */
    public void addMetadata(PwsResult pwsResult) {
        mBroadcastUrlToPwsResultMap.put(pwsResult.getRequestUrl(), pwsResult);
    }

    /**
     * Add an Icon to the collection.
     * @param url The url of the icon.
     * @param icon The bitmap of the icon.
     */
    public void addIcon(String url, byte[] icon) {
        mIconUrlToIconMap.put(url, icon);
    }

    /**
     * Clear results and devices.
     */
    public void clear() {
        mDeviceIdToUrlDeviceMap.clear();
        mBroadcastUrlToPwsResultMap.clear();
        mIconUrlToIconMap.clear();
        mPendingBroadcastUrls.clear();
        mPendingIconUrls.clear();
        mFailedResolveUrls.clear();
    }

    /**
     * Get an Icon from the collection.
     * @param url The url of the icon.
     * @return The associated icon.  This will be null if there is no icon.
     */
    public byte[] getIcon(String url) {
        return mIconUrlToIconMap.get(url);
    }

    /**
     * Fetches a UrlDevice by its ID.
     * @param id The ID of the UrlDevice.
     * @return the UrlDevice with the given ID.
     */
    public UrlDevice getUrlDeviceById(String id) {
        return mDeviceIdToUrlDeviceMap.get(id);
    }

    /**
     * Fetches cached URL metadata using the URL broadcasted by the Physical Web device.
     * @param broadcastUrl The URL broadcasted by the device.
     * @return Cached metadata relevant to the given URL.
     */
    public PwsResult getMetadataByBroadcastUrl(String broadcastUrl) {
        return mBroadcastUrlToPwsResultMap.get(broadcastUrl);
    }

    /**
     * Gets all UrlDevices stored in the collection.
     * @return List of UrlDevices
     */
    public List<UrlDevice> getUrlDevices() {
        return (List) new ArrayList(mDeviceIdToUrlDeviceMap.values());
    }

    /**
     * Create a JSON object that represents this data structure.
     * @return a JSON serialization of this data structure.
     */
    public JSONObject jsonSerialize() {
        JSONObject jsonObject = new JSONObject();

        // Serialize the UrlDevices
        JSONArray urlDevices = new JSONArray();
        for (UrlDevice urlDevice : mDeviceIdToUrlDeviceMap.values()) {
            urlDevices.put(urlDevice.jsonSerialize());
        }
        jsonObject.put(DEVICES_KEY, urlDevices);

        // Serialize the URL metadata
        JSONArray metadata = new JSONArray();
        for (PwsResult pwsResult : mBroadcastUrlToPwsResultMap.values()) {
            metadata.put(pwsResult.jsonSerialize());
        }
        jsonObject.put(METADATA_KEY, metadata);

        JSONObject iconMap = new JSONObject();
        for (String iconUrl : mIconUrlToIconMap.keySet()) {
            iconMap.put(iconUrl, new String(Base64.encodeBase64(getIcon(iconUrl)), Charset.forName("UTF-8")));
        }
        jsonObject.put(ICON_MAP_KEY, iconMap);

        jsonObject.put(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
        return jsonObject;
    }

    /**
     * Populate this data structure with UrlDevices represented by a given JSON object.
     * @param jsonObject a serialized PhysicalWebCollection.
     * @return The PhysicalWebCollection represented by the serialized object.
     * @throws PhysicalWebCollectionException on invalid or unrecognized input
     */
    public static PhysicalWebCollection jsonDeserialize(JSONObject jsonObject)
            throws PhysicalWebCollectionException {
        // Check the schema version
        int schemaVersion = jsonObject.getInt(SCHEMA_VERSION_KEY);
        if (schemaVersion > SCHEMA_VERSION) {
            throw new PhysicalWebCollectionException("Cannot handle schema version " + schemaVersion + ".  "
                    + "This library only knows of schema version " + SCHEMA_VERSION);
        }
        PhysicalWebCollection collection = new PhysicalWebCollection();

        // Deserialize the UrlDevices
        JSONArray urlDevices = jsonObject.getJSONArray(DEVICES_KEY);
        for (int i = 0; i < urlDevices.length(); i++) {
            JSONObject urlDeviceJson = urlDevices.getJSONObject(i);
            UrlDevice urlDevice = UrlDevice.jsonDeserialize(urlDeviceJson);
            collection.addUrlDevice(urlDevice);
        }

        // Deserialize the URL metadata
        JSONArray metadata = jsonObject.getJSONArray(METADATA_KEY);
        for (int i = 0; i < metadata.length(); i++) {
            JSONObject pwsResultJson = metadata.getJSONObject(i);
            PwsResult pwsResult = PwsResult.jsonDeserialize(pwsResultJson);
            collection.addMetadata(pwsResult);
        }

        JSONObject iconMap = jsonObject.getJSONObject(ICON_MAP_KEY);
        for (Iterator<String> iconUrls = iconMap.keys(); iconUrls.hasNext();) {
            String iconUrl = iconUrls.next();
            collection.addIcon(iconUrl,
                    Base64.decodeBase64(iconMap.getString(iconUrl).getBytes(Charset.forName("UTF-8"))));
        }
        return collection;
    }

    /**
     * Return a list of PwPairs sorted by rank in descending order.
     * These PwPairs will be deduplicated by siteUrls (favoring the PwPair with
     * the highest rank).
     * @param comparator to sort pairs by
     * @return a sorted list of PwPairs.
     */
    public List<PwPair> getPwPairsSortedByRank(Comparator<PwPair> comparator) {
        // Get all valid PwPairs.
        List<PwPair> allPwPairs = getPwPairs();

        // Sort the list in descending order.
        Collections.sort(allPwPairs, comparator);

        // Filter the list.
        return removeDuplicateSiteUrls(allPwPairs);
    }

    /**
     * Return a list of PwPairs sorted by rank in descending order, including only the top-ranked
     * pair from each group.
     * @param comparator to sort pairs by
     * @return a sorted list of PwPairs.
     */
    public List<PwPair> getGroupedPwPairsSortedByRank(Comparator<PwPair> comparator) {
        // Get all valid PwPairs.
        List<PwPair> allPwPairs = getPwPairs();

        // Group pairs with the same groupId, keeping only the top-ranked PwPair.
        List<PwPair> groupedPwPairs = removeDuplicateGroupIds(allPwPairs, null, comparator);

        // Sort by descending rank.
        Collections.sort(groupedPwPairs, comparator);

        // Remove duplicate site URLs.
        return removeDuplicateSiteUrls(groupedPwPairs);
    }

    /**
     * Return a list of all pairs of valid URL devices and corresponding URL metadata.
     * @return list of PwPairs.
     */
    public List<PwPair> getPwPairs() {
        List<PwPair> allPwPairs = new ArrayList<>();
        for (UrlDevice urlDevice : mDeviceIdToUrlDeviceMap.values()) {
            PwsResult pwsResult = mBroadcastUrlToPwsResultMap.get(urlDevice.getUrl());
            if (pwsResult != null) {
                allPwPairs.add(new PwPair(urlDevice, pwsResult));
            }
        }
        return allPwPairs;
    }

    /**
     * Return the top-ranked PwPair for a given group ID.
     * @param groupId
     * @param comparator to sort pairs by
     * @return a PwPair.
     */
    public PwPair getTopRankedPwPairByGroupId(String groupId, Comparator<PwPair> comparator) {
        for (PwPair pwPair : getGroupedPwPairsSortedByRank(comparator)) {
            if (pwPair.getPwsResult().getGroupId().equals(groupId)) {
                return pwPair;
            }
        }
        return null;
    }

    /**
     * If a site URL appears multiple times in the pairs list, keep only the first example.
     * @param allPwPairs input PwPairs list.
     * @return filtered PwPairs list with all duplicated site URLs removed.
     */
    private static List<PwPair> removeDuplicateSiteUrls(List<PwPair> allPwPairs) {
        List<PwPair> filteredPwPairs = new ArrayList<>();
        Set<String> siteUrls = new HashSet<>();
        for (PwPair pwPair : allPwPairs) {
            String siteUrl = pwPair.getPwsResult().getSiteUrl();
            if (!siteUrls.contains(siteUrl)) {
                siteUrls.add(siteUrl);
                filteredPwPairs.add(pwPair);
            }
        }
        return filteredPwPairs;
    }

    /**
     * Given a list of PwPairs, return a filtered list such that only one PwPair from each group
     * is included.
     * @param allPairs Input PwPairs list.
     * @param outGroupMap Optional output map from discovered group IDs to UrlGroups, may be null.
     * @return Filtered PwPairs list.
     */
    private static List<PwPair> removeDuplicateGroupIds(List<PwPair> allPairs, Map<String, UrlGroup> outGroupMap,
            Comparator<PwPair> comparator) {
        List<PwPair> filteredPairs = new ArrayList<>();
        Map<String, UrlGroup> groupMap = outGroupMap;
        if (groupMap == null) {
            groupMap = new HashMap<>();
        } else {
            groupMap.clear();
        }

        for (PwPair pwPair : allPairs) {
            PwsResult pwsResult = pwPair.getPwsResult();
            String groupId = pwsResult.getGroupId();
            if (groupId == null || groupId.equals("")) {
                // Pairs without a group are always included
                filteredPairs.add(pwPair);
            } else {
                // Create the group if it doesn't exist
                UrlGroup urlGroup = groupMap.get(groupId);
                if (urlGroup == null) {
                    urlGroup = new UrlGroup(groupId);
                    groupMap.put(groupId, urlGroup);
                }
                urlGroup.addPair(pwPair);
            }
        }

        for (UrlGroup urlGroup : groupMap.values()) {
            filteredPairs.add(urlGroup.getTopPair(comparator));
        }

        return filteredPairs;
    }

    /**
     * Set the URL, the API version, the API Key for making PWS requests.
     * @param pwsEndpoint The new PWS endpoint.
     * @param pwsApiVersion The new PWS API version.
     */
    public void setPwsEndpoint(String pwsEndpoint, int pwsApiVersion) {
        mPwsClient.setEndpoint(pwsEndpoint, pwsApiVersion);
    }

    /**
     * Set the URL, the API version, the API Key for making PWS requests.
     * @param pwsEndpoint The new PWS endpoint.
     * @param pwsApiVersion The new PWS API version.
     * @param pwsApiKey The new PWS API key.
     */
    public void setPwsEndpoint(String pwsEndpoint, int pwsApiVersion, String pwsApiKey) {
        mPwsClient.setEndpoint(pwsEndpoint, pwsApiVersion, pwsApiKey);
    }

    private class AugmentedPwsResultIconCallback extends PwsResultIconCallback {
        private String mUrl;
        private PwsResultIconCallback mCallback;

        AugmentedPwsResultIconCallback(String url, PwsResultIconCallback callback) {
            mUrl = url;
            mCallback = callback;
        }

        @Override
        public void onIcon(byte[] icon) {
            mPendingIconUrls.remove(mUrl);
            addIcon(mUrl, icon);
            mCallback.onIcon(icon);
        }

        @Override
        public void onError(int httpResponseCode, Exception e) {
            mPendingIconUrls.remove(mUrl);
            mCallback.onError(httpResponseCode, e);
        }
    }

    /**
     * Triggers an HTTP request to be made to the PWS.
     * This method fetches a results from the PWS for all broadcast URLs,
     * depending on the supplied parameters.
     * @param pwsResultCallback The callback to run when we get an HTTPResponse.
     * If this value is null, we will not fetch the PwsResults, only icons.
     * @param pwsResultIconCallback The callback to run when we get a favicon.
     * If this value is null, we will not fetch the icons.
     */
    public void fetchPwsResults(final PwsResultCallback pwsResultCallback,
            final PwsResultIconCallback pwsResultIconCallback) {
        // Get new URLs to fetch.
        Set<String> newResolveUrls = new HashSet<>();
        Set<String> newIconUrls = new HashSet<>();
        for (UrlDevice urlDevice : mDeviceIdToUrlDeviceMap.values()) {
            String url = urlDevice.getUrl();
            if (!mPendingBroadcastUrls.contains(url) && !mFailedResolveUrls.contains(url)) {
                PwsResult pwsResult = mBroadcastUrlToPwsResultMap.get(url);
                if (pwsResult == null) {
                    newResolveUrls.add(url);
                    mPendingBroadcastUrls.add(url);
                } else if (pwsResult.hasIconUrl() && !mPendingIconUrls.contains(pwsResult.getIconUrl())
                        && !mIconUrlToIconMap.containsKey(pwsResult.getIconUrl())) {
                    newIconUrls.add(pwsResult.getIconUrl());
                    mPendingIconUrls.add(pwsResult.getIconUrl());
                }
            }
        }

        // Make the resolve request.
        final Set<String> finalResolveUrls = newResolveUrls;
        PwsResultCallback augmentedCallback = new PwsResultCallback() {
            @Override
            public void onPwsResult(PwsResult pwsResult) {
                addMetadata(pwsResult);
                if (pwsResultIconCallback != null) {
                    PwsResultIconCallback augmentedIconCallback = new AugmentedPwsResultIconCallback(
                            pwsResult.getIconUrl(), pwsResultIconCallback);
                    mPwsClient.downloadIcon(pwsResult.getIconUrl(), augmentedIconCallback);
                }
                pwsResultCallback.onPwsResult(pwsResult);
            }

            @Override
            public void onPwsResultAbsent(String url) {
                mFailedResolveUrls.add(url);
                pwsResultCallback.onPwsResultAbsent(url);
            }

            @Override
            public void onPwsResultError(Collection<String> urls, int httpResponseCode, Exception e) {
                pwsResultCallback.onPwsResultError(urls, httpResponseCode, e);
            }

            @Override
            public void onResponseReceived(long durationMillis) {
                for (String url : finalResolveUrls) {
                    mPendingBroadcastUrls.remove(url);
                }
                pwsResultCallback.onResponseReceived(durationMillis);
            }
        };
        if (pwsResultCallback != null && newResolveUrls.size() > 0) {
            mPwsClient.resolve(newResolveUrls, augmentedCallback);
        }

        // Make the icon requests.
        if (pwsResultIconCallback != null) {
            for (final String iconUrl : newIconUrls) {
                PwsResultIconCallback augmentedIconCallback = new AugmentedPwsResultIconCallback(iconUrl,
                        pwsResultIconCallback);
                mPwsClient.downloadIcon(iconUrl, augmentedIconCallback);
            }
        }
    }

    /**
     * Cancel all current HTTP requests.
     */
    public void cancelAllRequests() {
        mPwsClient.cancelAllRequests();
    }
}