AbstractUserAgentStringParser.java

  1. /*******************************************************************************
  2.  * Copyright 2012 André Rouél
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *   http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  ******************************************************************************/
  16. package net.sf.uadetector.parser;

  17. import java.util.Map.Entry;
  18. import java.util.regex.Matcher;

  19. import javax.annotation.Nonnull;

  20. import net.sf.uadetector.DeviceCategory;
  21. import net.sf.uadetector.ReadableDeviceCategory.Category;
  22. import net.sf.uadetector.UserAgent;
  23. import net.sf.uadetector.UserAgentStringParser;
  24. import net.sf.uadetector.UserAgentType;
  25. import net.sf.uadetector.VersionNumber;
  26. import net.sf.uadetector.datastore.DataStore;
  27. import net.sf.uadetector.internal.data.Data;
  28. import net.sf.uadetector.internal.data.domain.Browser;
  29. import net.sf.uadetector.internal.data.domain.BrowserPattern;
  30. import net.sf.uadetector.internal.data.domain.Device;
  31. import net.sf.uadetector.internal.data.domain.DevicePattern;
  32. import net.sf.uadetector.internal.data.domain.OperatingSystem;
  33. import net.sf.uadetector.internal.data.domain.OperatingSystemPattern;
  34. import net.sf.uadetector.internal.data.domain.Robot;

  35. public abstract class AbstractUserAgentStringParser implements UserAgentStringParser {

  36.     /**
  37.      * The number of capturing groups if nothing matches
  38.      */
  39.     private static final int ZERO_MATCHING_GROUPS = 0;

  40.     /**
  41.      * Examines the user agent string whether it is a browser.
  42.      *
  43.      * @param userAgent
  44.      *            String of an user agent
  45.      * @param builder
  46.      *            Builder for an user agent information
  47.      */
  48.     private static void examineAsBrowser(final UserAgent.Builder builder, final Data data) {
  49.         Matcher matcher;
  50.         VersionNumber version = VersionNumber.UNKNOWN;
  51.         for (final Entry<BrowserPattern, Browser> entry : data.getPatternToBrowserMap().entrySet()) {
  52.             matcher = entry.getKey().getPattern().matcher(builder.getUserAgentString());
  53.             if (matcher.find()) {

  54.                 entry.getValue().copyTo(builder);

  55.                 // try to get the browser version from the first subgroup
  56.                 if (matcher.groupCount() > ZERO_MATCHING_GROUPS) {
  57.                     version = VersionNumber.parseVersion(matcher.group(1) != null ? matcher.group(1) : "");
  58.                 }
  59.                 builder.setVersionNumber(version);

  60.                 break;
  61.             }
  62.         }
  63.     }

  64.     /**
  65.      * Examines the user agent string whether it is a robot.
  66.      *
  67.      * @param userAgent
  68.      *            String of an user agent
  69.      * @param builder
  70.      *            Builder for an user agent information
  71.      * @return {@code true} if it is a robot, otherwise {@code false}
  72.      */
  73.     private static boolean examineAsRobot(final UserAgent.Builder builder, final Data data) {
  74.         boolean isRobot = false;
  75.         VersionNumber version;
  76.         for (final Robot robot : data.getRobots()) {
  77.             if (robot.getUserAgentString().equals(builder.getUserAgentString())) {
  78.                 isRobot = true;
  79.                 robot.copyTo(builder);

  80.                 // try to get the version from the last found group
  81.                 version = VersionNumber.parseLastVersionNumber(robot.getName());
  82.                 builder.setVersionNumber(version);

  83.                 break;
  84.             }
  85.         }
  86.         return isRobot;
  87.     }

  88.     /**
  89.      * Examines the user agent string whether has a specific device category.
  90.      *
  91.      * @param userAgent
  92.      *            String of an user agent
  93.      * @param builder
  94.      *            Builder for an user agent information
  95.      */
  96.     private static void examineDeviceCategory(final UserAgent.Builder builder, final Data data) {

  97.         // a robot will be classified as 'Other'
  98.         if (UserAgentType.ROBOT == builder.getType()) {
  99.             final DeviceCategory category = findDeviceCategoryByValue(Category.OTHER, data);
  100.             builder.setDeviceCategory(category);
  101.             return;
  102.         }

  103.         // classification depends on matching order
  104.         for (final Entry<DevicePattern, Device> entry : data.getPatternToDeviceMap().entrySet()) {
  105.             final Matcher matcher = entry.getKey().getPattern().matcher(builder.getUserAgentString());
  106.             if (matcher.find()) {
  107.                 final Category category = Category.evaluate(entry.getValue().getName());
  108.                 final DeviceCategory deviceCategory = findDeviceCategoryByValue(category, data);
  109.                 builder.setDeviceCategory(deviceCategory);
  110.                 return;
  111.             }
  112.         }

  113.         // an unknown user agent type should lead to an unknown device
  114.         if (UserAgentType.UNKNOWN == builder.getType()) {
  115.             builder.setDeviceCategory(DeviceCategory.EMPTY);
  116.             return;
  117.         }

  118.         // if no pattern is available but the type is Other, Library, Validator or UA Anonymizer
  119.         // than classify it as 'Other'
  120.         if (UserAgentType.OTHER == builder.getType() || UserAgentType.LIBRARY == builder.getType()
  121.                 || UserAgentType.VALIDATOR == builder.getType() || UserAgentType.USERAGENT_ANONYMIZER == builder.getType()) {
  122.             final DeviceCategory category = findDeviceCategoryByValue(Category.OTHER, data);
  123.             builder.setDeviceCategory(category);
  124.             return;
  125.         }

  126.         // if no pattern is available but the type is a mobile or WAP browser than classify it as 'Smartphone'
  127.         if (UserAgentType.MOBILE_BROWSER == builder.getType() || UserAgentType.WAP_BROWSER == builder.getType()) {
  128.             final DeviceCategory category = findDeviceCategoryByValue(Category.SMARTPHONE, data);
  129.             builder.setDeviceCategory(category);
  130.             return;
  131.         }

  132.         final DeviceCategory category = findDeviceCategoryByValue(Category.PERSONAL_COMPUTER, data);
  133.         builder.setDeviceCategory(category);
  134.     }

  135.     /**
  136.      * Examines the operating system of the user agent string, if not available.
  137.      *
  138.      * @param userAgent
  139.      *            String of an user agent
  140.      * @param builder
  141.      *            Builder for an user agent information
  142.      */
  143.     private static void examineOperatingSystem(final UserAgent.Builder builder, final Data data) {
  144.         if (net.sf.uadetector.OperatingSystem.EMPTY.equals(builder.getOperatingSystem())) {
  145.             for (final Entry<OperatingSystemPattern, OperatingSystem> entry : data.getPatternToOperatingSystemMap().entrySet()) {
  146.                 final Matcher matcher = entry.getKey().getPattern().matcher(builder.getUserAgentString());
  147.                 if (matcher.find()) {
  148.                     entry.getValue().copyTo(builder);
  149.                     break;
  150.                 }
  151.             }
  152.         }
  153.     }

  154.     private static DeviceCategory findDeviceCategoryByValue(@Nonnull final Category category, @Nonnull final Data data) {
  155.         for (final Device device : data.getDevices()) {
  156.             if (category == device.getCategory()) {
  157.                 return new DeviceCategory(category, device.getIcon(), device.getInfoUrl(), device.getName());
  158.             }
  159.         }
  160.         return DeviceCategory.EMPTY;
  161.     }

  162.     /**
  163.      * Gets the data store of this parser.
  164.      *
  165.      * @return data store of this parser
  166.      */
  167.     protected abstract DataStore getDataStore();

  168.     @Override
  169.     public String getDataVersion() {
  170.         return getDataStore().getData().getVersion();
  171.     }

  172.     @Override
  173.     public UserAgent parse(final String userAgent) {
  174.         final UserAgent.Builder builder = new UserAgent.Builder(userAgent);

  175.         // work during the analysis always with the same reference of data
  176.         final Data data = getDataStore().getData();

  177.         if (!examineAsRobot(builder, data)) {
  178.             examineAsBrowser(builder, data);
  179.             examineOperatingSystem(builder, data);
  180.         }
  181.         examineDeviceCategory(builder, data);
  182.         return builder.build();
  183.     }

  184.     @Override
  185.     public void shutdown() {
  186.         // nothing to shutdown
  187.     }

  188. }