DataBuilder.java

  1. /*******************************************************************************
  2.  * Copyright 2013 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.internal.data;

  17. import java.util.ArrayList;
  18. import java.util.HashMap;
  19. import java.util.HashSet;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Map.Entry;
  23. import java.util.Set;
  24. import java.util.SortedMap;
  25. import java.util.SortedSet;
  26. import java.util.TreeMap;
  27. import java.util.TreeSet;

  28. import javax.annotation.Nonnull;
  29. import javax.annotation.concurrent.NotThreadSafe;

  30. import net.sf.qualitycheck.Check;
  31. import net.sf.qualitycheck.exception.IllegalStateOfArgumentException;
  32. import net.sf.uadetector.internal.data.domain.Browser;
  33. import net.sf.uadetector.internal.data.domain.BrowserOperatingSystemMapping;
  34. import net.sf.uadetector.internal.data.domain.BrowserPattern;
  35. import net.sf.uadetector.internal.data.domain.BrowserType;
  36. import net.sf.uadetector.internal.data.domain.Device;
  37. import net.sf.uadetector.internal.data.domain.DevicePattern;
  38. import net.sf.uadetector.internal.data.domain.OperatingSystem;
  39. import net.sf.uadetector.internal.data.domain.OperatingSystemPattern;
  40. import net.sf.uadetector.internal.data.domain.Robot;

  41. import org.slf4j.Logger;
  42. import org.slf4j.LoggerFactory;

  43. /**
  44.  * This class is intended to create instances of {@code Data}.
  45.  *
  46.  * @author André Rouél
  47.  */
  48. @NotThreadSafe
  49. public class DataBuilder {

  50.     private static final Logger LOG = LoggerFactory.getLogger(DataBuilder.class);

  51.     private static void addOperatingSystemToBrowser(final Map<Integer, Browser.Builder> browserBuilders,
  52.             final Map<Integer, OperatingSystem> operatingSystems, final Map<Integer, Integer> browserOsMap) {
  53.         Browser.Builder browserBuilder;
  54.         for (final Map.Entry<Integer, Integer> entry : browserOsMap.entrySet()) {
  55.             if (browserBuilders.containsKey(entry.getKey())) {
  56.                 browserBuilder = browserBuilders.get(entry.getKey());
  57.                 if (operatingSystems.containsKey(entry.getValue())) {
  58.                     browserBuilder.setOperatingSystem(operatingSystems.get(entry.getValue()));
  59.                 } else {
  60.                     LOG.warn("Can not find an operating system with ID '" + entry.getValue() + "' for browser '"
  61.                             + browserBuilder.getProducer() + " " + browserBuilder.getFamily() + "'.");
  62.                 }
  63.             } else {
  64.                 LOG.warn("Can not find a browser with ID '" + entry.getKey() + "'.");
  65.             }
  66.         }
  67.     }

  68.     private static void addPatternToBrowser(final Map<Integer, Browser.Builder> builders,
  69.             final Map<Integer, SortedSet<BrowserPattern>> patterns) {
  70.         for (final Map.Entry<Integer, Browser.Builder> entry : builders.entrySet()) {
  71.             if (patterns.containsKey(entry.getKey())) {
  72.                 entry.getValue().setPatterns(patterns.get(entry.getKey()));
  73.             } else {
  74.                 LOG.warn("No pattern available for '" + entry.getValue().getProducer() + " " + entry.getValue().getFamily() + "'.");
  75.             }
  76.         }
  77.     }

  78.     private static void addPatternToDevice(final Map<Integer, Device.Builder> builders,
  79.             final Map<Integer, SortedSet<DevicePattern>> patterns) {
  80.         for (final Map.Entry<Integer, Device.Builder> entry : builders.entrySet()) {
  81.             if (patterns.containsKey(entry.getKey())) {
  82.                 entry.getValue().setPatterns(patterns.get(entry.getKey()));
  83.             } else {
  84.                 LOG.debug("No pattern available for '" + entry.getValue().getName() + "'.");
  85.             }
  86.         }
  87.     }

  88.     private static void addPatternToOperatingSystem(final Map<Integer, OperatingSystem.Builder> builders,
  89.             final Map<Integer, SortedSet<OperatingSystemPattern>> patterns) {
  90.         for (final Map.Entry<Integer, OperatingSystem.Builder> entry : builders.entrySet()) {
  91.             final SortedSet<OperatingSystemPattern> patternSet = patterns.get(entry.getKey());
  92.             if (patternSet != null) {
  93.                 entry.getValue().addPatterns(patternSet);
  94.             } else {
  95.                 LOG.debug("No patterns for operating system entry (with id '" + entry.getKey() + "') available.");
  96.             }
  97.         }
  98.     }

  99.     private static void addTypeToBrowser(final Map<Integer, Browser.Builder> builders, final Map<Integer, BrowserType> types) {
  100.         int typeId;
  101.         for (final Map.Entry<Integer, Browser.Builder> entry : builders.entrySet()) {
  102.             typeId = entry.getValue().getTypeId();
  103.             if (types.containsKey(typeId)) {
  104.                 entry.getValue().setType(types.get(typeId));
  105.             } else {
  106.                 LOG.warn("No type available for '" + entry.getValue().getProducer() + " " + entry.getValue().getFamily() + "'.");
  107.             }
  108.         }
  109.     }

  110.     private static Set<Browser> buildBrowsers(final Map<Integer, Browser.Builder> browserBuilders) {
  111.         final Set<Browser> browsers = new HashSet<Browser>();
  112.         for (final Map.Entry<Integer, Browser.Builder> entry : browserBuilders.entrySet()) {
  113.             try {
  114.                 browsers.add(entry.getValue().build());
  115.             } catch (final Exception e) {
  116.                 LOG.warn("Can not build browser: " + e.getLocalizedMessage());
  117.             }
  118.         }
  119.         return browsers;
  120.     }

  121.     private static Set<Device> buildDevices(final Map<Integer, Device.Builder> deviceBuilders) {
  122.         final Set<Device> devices = new HashSet<Device>();
  123.         for (final Map.Entry<Integer, Device.Builder> entry : deviceBuilders.entrySet()) {
  124.             try {
  125.                 devices.add(entry.getValue().build());
  126.             } catch (final Exception e) {
  127.                 LOG.warn("Can not build device '" + entry.getValue().getName() + "': " + e.getLocalizedMessage());
  128.             }
  129.         }
  130.         return devices;
  131.     }

  132.     private static Map<Integer, OperatingSystem> buildOperatingSystems(final Map<Integer, OperatingSystem.Builder> osBuilders) {
  133.         final Map<Integer, OperatingSystem> operatingSystems = new HashMap<Integer, OperatingSystem>();
  134.         for (final Map.Entry<Integer, OperatingSystem.Builder> entry : osBuilders.entrySet()) {
  135.             try {
  136.                 operatingSystems.put(entry.getKey(), entry.getValue().build());
  137.             } catch (final Exception e) {
  138.                 LOG.warn("Can not build operating system: " + e.getLocalizedMessage());
  139.             }
  140.         }
  141.         return operatingSystems;
  142.     }

  143.     private static SortedMap<BrowserPattern, Browser> buildPatternToBrowserMap(final Set<Browser> browserSet) {
  144.         final SortedMap<BrowserPattern, Browser> patternBrowser = new TreeMap<BrowserPattern, Browser>(BROWSER_PATTERN_COMPARATOR);
  145.         for (final Browser browser : browserSet) {
  146.             for (final BrowserPattern pattern : browser.getPatterns()) {
  147.                 patternBrowser.put(pattern, browser);
  148.             }
  149.         }
  150.         return patternBrowser;
  151.     }

  152.     private static SortedMap<DevicePattern, Device> buildPatternToDeviceMap(final Set<Device> devices) {
  153.         final SortedMap<DevicePattern, Device> patternDevice = new TreeMap<DevicePattern, Device>(DEVICE_PATTERN_COMPARATOR);
  154.         for (final Device device : devices) {
  155.             for (final DevicePattern pattern : device.getPatterns()) {
  156.                 patternDevice.put(pattern, device);
  157.             }
  158.         }
  159.         return patternDevice;
  160.     }

  161.     private static SortedMap<OperatingSystemPattern, OperatingSystem> buildPatternToOperatingSystemMap(final Set<OperatingSystem> osSet) {
  162.         final SortedMap<OperatingSystemPattern, OperatingSystem> map = new TreeMap<OperatingSystemPattern, OperatingSystem>(
  163.                 OS_PATTERN_COMPARATOR);
  164.         for (final OperatingSystem os : osSet) {
  165.             for (final OperatingSystemPattern pattern : os.getPatterns()) {
  166.                 map.put(pattern, os);
  167.             }
  168.         }
  169.         return map;
  170.     }

  171.     private static Map<Integer, Integer> convertBrowserOsMapping(final Set<BrowserOperatingSystemMapping> browserOperatingSystemMappings) {
  172.         final Map<Integer, Integer> result = new HashMap<Integer, Integer>();
  173.         for (final BrowserOperatingSystemMapping mapping : browserOperatingSystemMappings) {
  174.             result.put(mapping.getBrowserId(), mapping.getOperatingSystemId());
  175.         }
  176.         return result;
  177.     }

  178.     private static Set<OperatingSystem> convertOperatingSystems(final Map<Integer, OperatingSystem> operatingSystems) {
  179.         final Set<OperatingSystem> result = new HashSet<OperatingSystem>();
  180.         for (final Entry<Integer, OperatingSystem> entry : operatingSystems.entrySet()) {
  181.             result.add(entry.getValue());
  182.         }
  183.         return result;
  184.     }

  185.     @Nonnull
  186.     private final Map<Integer, BrowserType> browserTypes = new HashMap<Integer, BrowserType>();

  187.     @Nonnull
  188.     private final Map<Integer, SortedSet<BrowserPattern>> browserPatterns = new HashMap<Integer, SortedSet<BrowserPattern>>();

  189.     @Nonnull
  190.     private final Map<Integer, SortedSet<OperatingSystemPattern>> operatingSystemPatterns = new HashMap<Integer, SortedSet<OperatingSystemPattern>>();

  191.     @Nonnull
  192.     private final Map<Integer, Browser.Builder> browserBuilders = new HashMap<Integer, Browser.Builder>();

  193.     @Nonnull
  194.     private final Set<Browser> browsers = new HashSet<Browser>();

  195.     @Nonnull
  196.     private final Set<Device> devices = new HashSet<Device>();

  197.     @Nonnull
  198.     private final Map<Integer, Device.Builder> deviceBuilders = new HashMap<Integer, Device.Builder>();

  199.     @Nonnull
  200.     private final Map<Integer, SortedSet<DevicePattern>> devicePatterns = new HashMap<Integer, SortedSet<DevicePattern>>();

  201.     @Nonnull
  202.     private final Map<Integer, OperatingSystem.Builder> operatingSystemBuilders = new HashMap<Integer, OperatingSystem.Builder>();

  203.     @Nonnull
  204.     private final Set<OperatingSystem> operatingSystems = new HashSet<OperatingSystem>();

  205.     @Nonnull
  206.     private final List<Robot> robots = new ArrayList<Robot>();

  207.     private String version;

  208.     @Nonnull
  209.     private final Set<BrowserOperatingSystemMapping> browserToOperatingSystemMap = new HashSet<BrowserOperatingSystemMapping>();

  210.     private static final OrderedPatternComparator<BrowserPattern> BROWSER_PATTERN_COMPARATOR = new OrderedPatternComparator<BrowserPattern>();

  211.     private static final OrderedPatternComparator<DevicePattern> DEVICE_PATTERN_COMPARATOR = new OrderedPatternComparator<DevicePattern>();

  212.     private static final OrderedPatternComparator<OperatingSystemPattern> OS_PATTERN_COMPARATOR = new OrderedPatternComparator<OperatingSystemPattern>();

  213.     public DataBuilder appendBrowser(@Nonnull final Browser browser) {
  214.         Check.notNull(browser, "browser");

  215.         browsers.add(browser);
  216.         return this;
  217.     }

  218.     /**
  219.      * Appends a copy of the given {@code Browser.Builder} to the internal data structure.
  220.      *
  221.      * @param browserBuilder
  222.      *            {@code Browser.Builder} to be copied and appended
  223.      * @return this {@code Builder}, for chaining
  224.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  225.      *             if the given argument is {@code null}
  226.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  227.      *             if the ID of the given builder is invalid
  228.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  229.      *             if a builder with the same ID already exists
  230.      */
  231.     @Nonnull
  232.     public DataBuilder appendBrowserBuilder(@Nonnull final Browser.Builder browserBuilder) {
  233.         Check.notNull(browserBuilder, "browserBuilder");
  234.         Check.notNegative(browserBuilder.getId(), "browserBuilder.getId()");
  235.         if (browserBuilder.getType() == null && browserBuilder.getTypeId() < 0) {
  236.             throw new IllegalStateOfArgumentException("A Type or Type-ID of argument 'browserBuilder' must be set.");
  237.         }
  238.         if (browserBuilders.containsKey(browserBuilder.getId())) {
  239.             throw new IllegalStateOfArgumentException("The browser builder '" + browserBuilder.getProducer() + " "
  240.                     + browserBuilder.getFamily() + "' is already in the map.");
  241.         }

  242.         final Browser.Builder builder = browserBuilder.copy();
  243.         browserBuilders.put(builder.getId(), builder);
  244.         return this;
  245.     }

  246.     @Nonnull
  247.     public DataBuilder appendBrowserOperatingSystemMapping(@Nonnull final BrowserOperatingSystemMapping browserOsMapping) {
  248.         Check.notNull(browserOsMapping, "browserOsMapping");

  249.         browserToOperatingSystemMap.add(browserOsMapping);
  250.         return this;
  251.     }

  252.     /**
  253.      * Appends a browser pattern to the map of pattern sorted by ID.
  254.      *
  255.      * @param pattern
  256.      *            a pattern for a browser
  257.      * @return itself
  258.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  259.      *             if the given argument is {@code null}
  260.      */
  261.     @Nonnull
  262.     public DataBuilder appendBrowserPattern(@Nonnull final BrowserPattern pattern) {
  263.         Check.notNull(pattern, "pattern");
  264.         if (!browserPatterns.containsKey(pattern.getId())) {
  265.             browserPatterns.put(pattern.getId(), new TreeSet<BrowserPattern>(BROWSER_PATTERN_COMPARATOR));
  266.         }

  267.         browserPatterns.get(pattern.getId()).add(pattern);
  268.         return this;
  269.     }

  270.     @Nonnull
  271.     public DataBuilder appendBrowserType(@Nonnull final BrowserType type) {
  272.         Check.notNull(type, "type");

  273.         browserTypes.put(type.getId(), type);
  274.         return this;
  275.     }

  276.     public DataBuilder appendDevice(@Nonnull final Device device) {
  277.         Check.notNull(device, "device");

  278.         devices.add(device);
  279.         return this;
  280.     }

  281.     /**
  282.      * Appends a copy of the given {@code Device.Builder} to the internal data structure.
  283.      *
  284.      * @param deviceBuilder
  285.      *            {@code Device.Builder} to be copied and appended
  286.      * @return this {@code Builder}, for chaining
  287.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  288.      *             if the given argument is {@code null}
  289.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  290.      *             if the ID of the given builder is invalid
  291.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  292.      *             if a builder with the same ID already exists
  293.      */
  294.     @Nonnull
  295.     public DataBuilder appendDeviceBuilder(@Nonnull final Device.Builder deviceBuilder) {
  296.         Check.notNull(deviceBuilder, "deviceBuilder");
  297.         Check.notNegative(deviceBuilder.getId(), "deviceBuilder.getId()");
  298.         if (deviceBuilders.containsKey(deviceBuilder.getId())) {
  299.             throw new IllegalStateOfArgumentException("The device builder '" + deviceBuilder.getName() + "' is already in the map.");
  300.         }

  301.         final Device.Builder builder = deviceBuilder.copy();
  302.         deviceBuilders.put(builder.getId(), builder);
  303.         return this;
  304.     }

  305.     /**
  306.      * Appends a device pattern to the map of pattern sorted by ID.
  307.      *
  308.      * @param pattern
  309.      *            a pattern for a device
  310.      * @return itself
  311.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  312.      *             if the given argument is {@code null}
  313.      */
  314.     @Nonnull
  315.     public DataBuilder appendDevicePattern(@Nonnull final DevicePattern pattern) {
  316.         Check.notNull(pattern, "pattern");
  317.         if (!devicePatterns.containsKey(pattern.getId())) {
  318.             devicePatterns.put(pattern.getId(), new TreeSet<DevicePattern>(DEVICE_PATTERN_COMPARATOR));
  319.         }

  320.         devicePatterns.get(pattern.getId()).add(pattern);
  321.         return this;
  322.     }

  323.     @Nonnull
  324.     public DataBuilder appendOperatingSystem(@Nonnull final OperatingSystem operatingSystem) {
  325.         Check.notNull(operatingSystem, "operatingSystem");

  326.         operatingSystems.add(operatingSystem);
  327.         return this;
  328.     }

  329.     /**
  330.      * Appends a copy of the given {@code OperatingSystem.Builder} to the internal data structure.
  331.      *
  332.      * @param operatingSystemBuilder
  333.      *            {@code OperatingSystem.Builder} to be copied and appended
  334.      * @return this {@code Builder}, for chaining
  335.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  336.      *             if the given argument is {@code null}
  337.      * @throws net.sf.qualitycheck.exception.IllegalNegativeArgumentException
  338.      *             if the ID of the given builder is negative
  339.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  340.      *             if a builder with the same ID already exists
  341.      */
  342.     @Nonnull
  343.     public DataBuilder appendOperatingSystemBuilder(@Nonnull final OperatingSystem.Builder operatingSystemBuilder) {
  344.         Check.notNull(operatingSystemBuilder, "operatingSystemBuilder");
  345.         Check.notNegative(operatingSystemBuilder.getId(), "operatingSystemBuilder.getId()");
  346.         Check.stateIsTrue(!operatingSystemBuilders.containsKey(operatingSystemBuilder.getId()),
  347.                 "Operating system builder with ID '%s' already exists.", operatingSystemBuilder.getId());

  348.         final OperatingSystem.Builder builder = operatingSystemBuilder.copy();
  349.         operatingSystemBuilders.put(builder.getId(), builder);
  350.         return this;
  351.     }

  352.     /**
  353.      * Appends an operating system pattern to the map of pattern sorted by ID.
  354.      *
  355.      * @param pattern
  356.      *            a pattern for a browser
  357.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  358.      *             if the pattern is {@code null}
  359.      * @return itself
  360.      */
  361.     @Nonnull
  362.     public DataBuilder appendOperatingSystemPattern(@Nonnull final OperatingSystemPattern pattern) {
  363.         Check.notNull(pattern, "pattern");

  364.         if (!operatingSystemPatterns.containsKey(pattern.getId())) {
  365.             operatingSystemPatterns.put(pattern.getId(), new TreeSet<OperatingSystemPattern>(OS_PATTERN_COMPARATOR));
  366.         }

  367.         operatingSystemPatterns.get(pattern.getId()).add(pattern);
  368.         return this;
  369.     }

  370.     @Nonnull
  371.     public DataBuilder appendRobot(@Nonnull final Robot robot) {
  372.         Check.notNull(robot, "robot");

  373.         robots.add(robot);
  374.         return this;
  375.     }

  376.     @Nonnull
  377.     public Data build() {
  378.         addTypeToBrowser(browserBuilders, browserTypes);
  379.         addPatternToBrowser(browserBuilders, browserPatterns);
  380.         addPatternToOperatingSystem(operatingSystemBuilders, operatingSystemPatterns);
  381.         addPatternToDevice(deviceBuilders, devicePatterns);

  382.         final Map<Integer, OperatingSystem> systems = buildOperatingSystems(operatingSystemBuilders);
  383.         addOperatingSystemToBrowser(browserBuilders, systems, convertBrowserOsMapping(browserToOperatingSystemMap));

  384.         final Set<OperatingSystem> osSet = convertOperatingSystems(systems);
  385.         osSet.addAll(operatingSystems);

  386.         final Set<Browser> browserSet = buildBrowsers(browserBuilders);
  387.         browserSet.addAll(browsers);

  388.         final Set<Device> deviceSet = buildDevices(deviceBuilders);
  389.         deviceSet.addAll(devices);

  390.         final SortedMap<BrowserPattern, Browser> patternToBrowserMap = buildPatternToBrowserMap(browserSet);
  391.         final SortedMap<OperatingSystemPattern, OperatingSystem> patternToOperatingSystemMap = buildPatternToOperatingSystemMap(osSet);
  392.         final SortedMap<DevicePattern, Device> patternToDeviceMap = buildPatternToDeviceMap(deviceSet);

  393.         return new Data(browserSet, browserPatterns, browserTypes, patternToBrowserMap, browserToOperatingSystemMap, osSet,
  394.                 operatingSystemPatterns, patternToOperatingSystemMap, robots, deviceSet, devicePatterns, patternToDeviceMap, version);
  395.     }

  396.     @Nonnull
  397.     public DataBuilder setVersion(@Nonnull final String version) {
  398.         Check.notNull(version, "version");

  399.         this.version = version;
  400.         return this;
  401.     }

  402. }