/******************************************************************************* * Copyright 2013 André Rouél * * 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 net.sf.uadetector.internal.data; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import javax.annotation.Nonnull; import javax.annotation.concurrent.NotThreadSafe; import net.sf.qualitycheck.Check; import net.sf.qualitycheck.exception.IllegalStateOfArgumentException; import net.sf.uadetector.internal.data.domain.Browser; import net.sf.uadetector.internal.data.domain.BrowserOperatingSystemMapping; import net.sf.uadetector.internal.data.domain.BrowserPattern; import net.sf.uadetector.internal.data.domain.BrowserType; import net.sf.uadetector.internal.data.domain.Device; import net.sf.uadetector.internal.data.domain.DevicePattern; import net.sf.uadetector.internal.data.domain.OperatingSystem; import net.sf.uadetector.internal.data.domain.OperatingSystemPattern; import net.sf.uadetector.internal.data.domain.Robot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is intended to create instances of {@code Data}. * * @author André Rouél */ @NotThreadSafe public class DataBuilder { private static final Logger LOG = LoggerFactory.getLogger(DataBuilder.class); private static void addOperatingSystemToBrowser(final Map<Integer, Browser.Builder> browserBuilders, final Map<Integer, OperatingSystem> operatingSystems, final Map<Integer, Integer> browserOsMap) { Browser.Builder browserBuilder; for (final Map.Entry<Integer, Integer> entry : browserOsMap.entrySet()) { if (browserBuilders.containsKey(entry.getKey())) { browserBuilder = browserBuilders.get(entry.getKey()); if (operatingSystems.containsKey(entry.getValue())) { browserBuilder.setOperatingSystem(operatingSystems.get(entry.getValue())); } else { LOG.warn("Can not find an operating system with ID '" + entry.getValue() + "' for browser '" + browserBuilder.getProducer() + " " + browserBuilder.getFamily() + "'."); } } else { LOG.warn("Can not find a browser with ID '" + entry.getKey() + "'."); } } } private static void addPatternToBrowser(final Map<Integer, Browser.Builder> builders, final Map<Integer, SortedSet<BrowserPattern>> patterns) { for (final Map.Entry<Integer, Browser.Builder> entry : builders.entrySet()) { if (patterns.containsKey(entry.getKey())) { entry.getValue().setPatterns(patterns.get(entry.getKey())); } else { LOG.warn("No pattern available for '" + entry.getValue().getProducer() + " " + entry.getValue().getFamily() + "'."); } } } private static void addPatternToDevice(final Map<Integer, Device.Builder> builders, final Map<Integer, SortedSet<DevicePattern>> patterns) { for (final Map.Entry<Integer, Device.Builder> entry : builders.entrySet()) { if (patterns.containsKey(entry.getKey())) { entry.getValue().setPatterns(patterns.get(entry.getKey())); } else { LOG.debug("No pattern available for '" + entry.getValue().getName() + "'."); } } } private static void addPatternToOperatingSystem(final Map<Integer, OperatingSystem.Builder> builders, final Map<Integer, SortedSet<OperatingSystemPattern>> patterns) { for (final Map.Entry<Integer, OperatingSystem.Builder> entry : builders.entrySet()) { final SortedSet<OperatingSystemPattern> patternSet = patterns.get(entry.getKey()); if (patternSet != null) { entry.getValue().addPatterns(patternSet); } else { LOG.debug("No patterns for operating system entry (with id '" + entry.getKey() + "') available."); } } } private static void addTypeToBrowser(final Map<Integer, Browser.Builder> builders, final Map<Integer, BrowserType> types) { int typeId; for (final Map.Entry<Integer, Browser.Builder> entry : builders.entrySet()) { typeId = entry.getValue().getTypeId(); if (types.containsKey(typeId)) { entry.getValue().setType(types.get(typeId)); } else { LOG.warn("No type available for '" + entry.getValue().getProducer() + " " + entry.getValue().getFamily() + "'."); } } } private static Set<Browser> buildBrowsers(final Map<Integer, Browser.Builder> browserBuilders) { final Set<Browser> browsers = new HashSet<Browser>(); for (final Map.Entry<Integer, Browser.Builder> entry : browserBuilders.entrySet()) { try { browsers.add(entry.getValue().build()); } catch (final Exception e) { LOG.warn("Can not build browser: " + e.getLocalizedMessage()); } } return browsers; } private static Set<Device> buildDevices(final Map<Integer, Device.Builder> deviceBuilders) { final Set<Device> devices = new HashSet<Device>(); for (final Map.Entry<Integer, Device.Builder> entry : deviceBuilders.entrySet()) { try { devices.add(entry.getValue().build()); } catch (final Exception e) { LOG.warn("Can not build device '" + entry.getValue().getName() + "': " + e.getLocalizedMessage()); } } return devices; } private static Map<Integer, OperatingSystem> buildOperatingSystems(final Map<Integer, OperatingSystem.Builder> osBuilders) { final Map<Integer, OperatingSystem> operatingSystems = new HashMap<Integer, OperatingSystem>(); for (final Map.Entry<Integer, OperatingSystem.Builder> entry : osBuilders.entrySet()) { try { operatingSystems.put(entry.getKey(), entry.getValue().build()); } catch (final Exception e) { LOG.warn("Can not build operating system: " + e.getLocalizedMessage()); } } return operatingSystems; } private static SortedMap<BrowserPattern, Browser> buildPatternToBrowserMap(final Set<Browser> browserSet) { final SortedMap<BrowserPattern, Browser> patternBrowser = new TreeMap<BrowserPattern, Browser>(BROWSER_PATTERN_COMPARATOR); for (final Browser browser : browserSet) { for (final BrowserPattern pattern : browser.getPatterns()) { patternBrowser.put(pattern, browser); } } return patternBrowser; } private static SortedMap<DevicePattern, Device> buildPatternToDeviceMap(final Set<Device> devices) { final SortedMap<DevicePattern, Device> patternDevice = new TreeMap<DevicePattern, Device>(DEVICE_PATTERN_COMPARATOR); for (final Device device : devices) { for (final DevicePattern pattern : device.getPatterns()) { patternDevice.put(pattern, device); } } return patternDevice; } private static SortedMap<OperatingSystemPattern, OperatingSystem> buildPatternToOperatingSystemMap(final Set<OperatingSystem> osSet) { final SortedMap<OperatingSystemPattern, OperatingSystem> map = new TreeMap<OperatingSystemPattern, OperatingSystem>( OS_PATTERN_COMPARATOR); for (final OperatingSystem os : osSet) { for (final OperatingSystemPattern pattern : os.getPatterns()) { map.put(pattern, os); } } return map; } private static Map<Integer, Integer> convertBrowserOsMapping(final Set<BrowserOperatingSystemMapping> browserOperatingSystemMappings) { final Map<Integer, Integer> result = new HashMap<Integer, Integer>(); for (final BrowserOperatingSystemMapping mapping : browserOperatingSystemMappings) { result.put(mapping.getBrowserId(), mapping.getOperatingSystemId()); } return result; } private static Set<OperatingSystem> convertOperatingSystems(final Map<Integer, OperatingSystem> operatingSystems) { final Set<OperatingSystem> result = new HashSet<OperatingSystem>(); for (final Entry<Integer, OperatingSystem> entry : operatingSystems.entrySet()) { result.add(entry.getValue()); } return result; } @Nonnull private final Map<Integer, BrowserType> browserTypes = new HashMap<Integer, BrowserType>(); @Nonnull private final Map<Integer, SortedSet<BrowserPattern>> browserPatterns = new HashMap<Integer, SortedSet<BrowserPattern>>(); @Nonnull private final Map<Integer, SortedSet<OperatingSystemPattern>> operatingSystemPatterns = new HashMap<Integer, SortedSet<OperatingSystemPattern>>(); @Nonnull private final Map<Integer, Browser.Builder> browserBuilders = new HashMap<Integer, Browser.Builder>(); @Nonnull private final Set<Browser> browsers = new HashSet<Browser>(); @Nonnull private final Set<Device> devices = new HashSet<Device>(); @Nonnull private final Map<Integer, Device.Builder> deviceBuilders = new HashMap<Integer, Device.Builder>(); @Nonnull private final Map<Integer, SortedSet<DevicePattern>> devicePatterns = new HashMap<Integer, SortedSet<DevicePattern>>(); @Nonnull private final Map<Integer, OperatingSystem.Builder> operatingSystemBuilders = new HashMap<Integer, OperatingSystem.Builder>(); @Nonnull private final Set<OperatingSystem> operatingSystems = new HashSet<OperatingSystem>(); @Nonnull private final List<Robot> robots = new ArrayList<Robot>(); private String version; @Nonnull private final Set<BrowserOperatingSystemMapping> browserToOperatingSystemMap = new HashSet<BrowserOperatingSystemMapping>(); private static final OrderedPatternComparator<BrowserPattern> BROWSER_PATTERN_COMPARATOR = new OrderedPatternComparator<BrowserPattern>(); private static final OrderedPatternComparator<DevicePattern> DEVICE_PATTERN_COMPARATOR = new OrderedPatternComparator<DevicePattern>(); private static final OrderedPatternComparator<OperatingSystemPattern> OS_PATTERN_COMPARATOR = new OrderedPatternComparator<OperatingSystemPattern>(); public DataBuilder appendBrowser(@Nonnull final Browser browser) { Check.notNull(browser, "browser"); browsers.add(browser); return this; } /** * Appends a copy of the given {@code Browser.Builder} to the internal data structure. * * @param browserBuilder * {@code Browser.Builder} to be copied and appended * @return this {@code Builder}, for chaining * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if the given argument is {@code null} * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException * if the ID of the given builder is invalid * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException * if a builder with the same ID already exists */ @Nonnull public DataBuilder appendBrowserBuilder(@Nonnull final Browser.Builder browserBuilder) { Check.notNull(browserBuilder, "browserBuilder"); Check.notNegative(browserBuilder.getId(), "browserBuilder.getId()"); if (browserBuilder.getType() == null && browserBuilder.getTypeId() < 0) { throw new IllegalStateOfArgumentException("A Type or Type-ID of argument 'browserBuilder' must be set."); } if (browserBuilders.containsKey(browserBuilder.getId())) { throw new IllegalStateOfArgumentException("The browser builder '" + browserBuilder.getProducer() + " " + browserBuilder.getFamily() + "' is already in the map."); } final Browser.Builder builder = browserBuilder.copy(); browserBuilders.put(builder.getId(), builder); return this; } @Nonnull public DataBuilder appendBrowserOperatingSystemMapping(@Nonnull final BrowserOperatingSystemMapping browserOsMapping) { Check.notNull(browserOsMapping, "browserOsMapping"); browserToOperatingSystemMap.add(browserOsMapping); return this; } /** * Appends a browser pattern to the map of pattern sorted by ID. * * @param pattern * a pattern for a browser * @return itself * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if the given argument is {@code null} */ @Nonnull public DataBuilder appendBrowserPattern(@Nonnull final BrowserPattern pattern) { Check.notNull(pattern, "pattern"); if (!browserPatterns.containsKey(pattern.getId())) { browserPatterns.put(pattern.getId(), new TreeSet<BrowserPattern>(BROWSER_PATTERN_COMPARATOR)); } browserPatterns.get(pattern.getId()).add(pattern); return this; } @Nonnull public DataBuilder appendBrowserType(@Nonnull final BrowserType type) { Check.notNull(type, "type"); browserTypes.put(type.getId(), type); return this; } public DataBuilder appendDevice(@Nonnull final Device device) { Check.notNull(device, "device"); devices.add(device); return this; } /** * Appends a copy of the given {@code Device.Builder} to the internal data structure. * * @param deviceBuilder * {@code Device.Builder} to be copied and appended * @return this {@code Builder}, for chaining * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if the given argument is {@code null} * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException * if the ID of the given builder is invalid * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException * if a builder with the same ID already exists */ @Nonnull public DataBuilder appendDeviceBuilder(@Nonnull final Device.Builder deviceBuilder) { Check.notNull(deviceBuilder, "deviceBuilder"); Check.notNegative(deviceBuilder.getId(), "deviceBuilder.getId()"); if (deviceBuilders.containsKey(deviceBuilder.getId())) { throw new IllegalStateOfArgumentException("The device builder '" + deviceBuilder.getName() + "' is already in the map."); } final Device.Builder builder = deviceBuilder.copy(); deviceBuilders.put(builder.getId(), builder); return this; } /** * Appends a device pattern to the map of pattern sorted by ID. * * @param pattern * a pattern for a device * @return itself * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if the given argument is {@code null} */ @Nonnull public DataBuilder appendDevicePattern(@Nonnull final DevicePattern pattern) { Check.notNull(pattern, "pattern"); if (!devicePatterns.containsKey(pattern.getId())) { devicePatterns.put(pattern.getId(), new TreeSet<DevicePattern>(DEVICE_PATTERN_COMPARATOR)); } devicePatterns.get(pattern.getId()).add(pattern); return this; } @Nonnull public DataBuilder appendOperatingSystem(@Nonnull final OperatingSystem operatingSystem) { Check.notNull(operatingSystem, "operatingSystem"); operatingSystems.add(operatingSystem); return this; } /** * Appends a copy of the given {@code OperatingSystem.Builder} to the internal data structure. * * @param operatingSystemBuilder * {@code OperatingSystem.Builder} to be copied and appended * @return this {@code Builder}, for chaining * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if the given argument is {@code null} * @throws net.sf.qualitycheck.exception.IllegalNegativeArgumentException * if the ID of the given builder is negative * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException * if a builder with the same ID already exists */ @Nonnull public DataBuilder appendOperatingSystemBuilder(@Nonnull final OperatingSystem.Builder operatingSystemBuilder) { Check.notNull(operatingSystemBuilder, "operatingSystemBuilder"); Check.notNegative(operatingSystemBuilder.getId(), "operatingSystemBuilder.getId()"); Check.stateIsTrue(!operatingSystemBuilders.containsKey(operatingSystemBuilder.getId()), "Operating system builder with ID '%s' already exists.", operatingSystemBuilder.getId()); final OperatingSystem.Builder builder = operatingSystemBuilder.copy(); operatingSystemBuilders.put(builder.getId(), builder); return this; } /** * Appends an operating system pattern to the map of pattern sorted by ID. * * @param pattern * a pattern for a browser * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if the pattern is {@code null} * @return itself */ @Nonnull public DataBuilder appendOperatingSystemPattern(@Nonnull final OperatingSystemPattern pattern) { Check.notNull(pattern, "pattern"); if (!operatingSystemPatterns.containsKey(pattern.getId())) { operatingSystemPatterns.put(pattern.getId(), new TreeSet<OperatingSystemPattern>(OS_PATTERN_COMPARATOR)); } operatingSystemPatterns.get(pattern.getId()).add(pattern); return this; } @Nonnull public DataBuilder appendRobot(@Nonnull final Robot robot) { Check.notNull(robot, "robot"); robots.add(robot); return this; } @Nonnull public Data build() { addTypeToBrowser(browserBuilders, browserTypes); addPatternToBrowser(browserBuilders, browserPatterns); addPatternToOperatingSystem(operatingSystemBuilders, operatingSystemPatterns); addPatternToDevice(deviceBuilders, devicePatterns); final Map<Integer, OperatingSystem> systems = buildOperatingSystems(operatingSystemBuilders); addOperatingSystemToBrowser(browserBuilders, systems, convertBrowserOsMapping(browserToOperatingSystemMap)); final Set<OperatingSystem> osSet = convertOperatingSystems(systems); osSet.addAll(operatingSystems); final Set<Browser> browserSet = buildBrowsers(browserBuilders); browserSet.addAll(browsers); final Set<Device> deviceSet = buildDevices(deviceBuilders); deviceSet.addAll(devices); final SortedMap<BrowserPattern, Browser> patternToBrowserMap = buildPatternToBrowserMap(browserSet); final SortedMap<OperatingSystemPattern, OperatingSystem> patternToOperatingSystemMap = buildPatternToOperatingSystemMap(osSet); final SortedMap<DevicePattern, Device> patternToDeviceMap = buildPatternToDeviceMap(deviceSet); return new Data(browserSet, browserPatterns, browserTypes, patternToBrowserMap, browserToOperatingSystemMap, osSet, operatingSystemPatterns, patternToOperatingSystemMap, robots, deviceSet, devicePatterns, patternToDeviceMap, version); } @Nonnull public DataBuilder setVersion(@Nonnull final String version) { Check.notNull(version, "version"); this.version = version; return this; } }