DataBuilder.java
/*******************************************************************************
* 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;
}
}