You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devicemap.apache.org by wk...@apache.org on 2015/11/30 00:36:19 UTC

svn commit: r1717136 [1/4] - in /devicemap/trunk/clients/1.0/java: ./ client/ client/src/ client/src/main/ client/src/main/java/ client/src/main/java/org/ client/src/main/java/org/apache/ client/src/main/java/org/apache/devicemap/ client/src/main/java/...

Author: wkeil
Date: Sun Nov 29 23:36:18 2015
New Revision: 1717136

URL: http://svn.apache.org/viewvc?rev=1717136&view=rev
Log:
DMAP-54: Improve Console example 

Task-Url: https://issues.apache.org/jira/browse/DMAP-54
DMAP-155: UA changes with Windows 10 update 

Task-Url: https://issues.apache.org/jira/browse/DMAP-155

Added:
    devicemap/trunk/clients/1.0/java/client/   (with props)
    devicemap/trunk/clients/1.0/java/client/pom.xml
    devicemap/trunk/clients/1.0/java/client/src/
    devicemap/trunk/clients/1.0/java/client/src/main/
    devicemap/trunk/clients/1.0/java/client/src/main/java/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapClient.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapFactory.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/cmd/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/cmd/Main.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Device.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/DeviceType.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Pattern.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/PatternSet.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Loader.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderFactory.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderOption.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Resource.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/DDRLoader.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/NoopLoader.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/UninitializedLoader.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/JsonParser.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/XMLParser.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/FileResource.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/JarResource.java
    devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/URLResource.java
    devicemap/trunk/clients/1.0/java/client/src/test/
    devicemap/trunk/clients/1.0/java/client/src/test/java/
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientFileTestOptional.java
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientJarTest.java
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientTest.java
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientUnitTest.java
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientUrlTestOptional.java
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/loader/
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/loader/parser/
    devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/loader/parser/XMLParserTest.java
    devicemap/trunk/clients/1.0/java/client/src/test/resources/
    devicemap/trunk/clients/1.0/java/client/src/test/resources/uas.data
    devicemap/trunk/clients/1.0/java/console/   (with props)
    devicemap/trunk/clients/1.0/java/console/pom.xml
    devicemap/trunk/clients/1.0/java/console/src/
    devicemap/trunk/clients/1.0/java/console/src/main/
    devicemap/trunk/clients/1.0/java/console/src/main/java/
    devicemap/trunk/clients/1.0/java/console/src/main/java/org/
    devicemap/trunk/clients/1.0/java/console/src/main/java/org/apache/
    devicemap/trunk/clients/1.0/java/console/src/main/java/org/apache/devicemap/
    devicemap/trunk/clients/1.0/java/console/src/main/java/org/apache/devicemap/console/
    devicemap/trunk/clients/1.0/java/console/src/main/java/org/apache/devicemap/console/Main.java
Removed:
    devicemap/trunk/clients/1.0/java/src/
Modified:
    devicemap/trunk/clients/1.0/java/pom.xml

Propchange: devicemap/trunk/clients/1.0/java/client/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Sun Nov 29 23:36:18 2015
@@ -0,0 +1,4 @@
+target
+.settings
+.classpath
+.project

Added: devicemap/trunk/clients/1.0/java/client/pom.xml
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/pom.xml?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/pom.xml (added)
+++ devicemap/trunk/clients/1.0/java/client/pom.xml Sun Nov 29 23:36:18 2015
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  
+  <parent>
+    <groupId>org.apache.devicemap</groupId>
+    <artifactId>devicemap-java</artifactId>
+    <version>1.1.1-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.apache.devicemap</groupId>
+  <artifactId>devicemap-client</artifactId>
+  <packaging>jar</packaging>
+  <name>Apache DeviceMap Java Client</name>
+  <description>Apache DeviceMap Java API</description>
+  <url>http://devicemap.apache.org/</url>
+  
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/devicemap/trunk/devicemap/java/classifier</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/devicemap/trunk/devicemap/java/classifier</developerConnection>
+    <url>http://svn.apache.org/viewvc/devicemap/trunk/devicemap/java/classifier</url>
+  </scm>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.devicemap</groupId>
+      <artifactId>devicemap-data</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+          <debug>true</debug>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+  
+</project>

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapClient.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapClient.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapClient.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapClient.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,198 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.devicemap.data.Device;
+import org.apache.devicemap.loader.LoaderOption;
+import org.apache.devicemap.data.DeviceType;
+import org.apache.devicemap.data.Pattern;
+import org.apache.devicemap.loader.LoaderFactory;
+
+public class DeviceMapClient {
+
+    private final static Logger LOG = Logger.getLogger(DeviceMapClient.class.getName());
+    private final static java.util.regex.Pattern TEXT_SPLIT_PATTERN = java.util.regex.Pattern.compile(" |-|_|/|\\\\|\\[|\\]|\\(|\\)|;");
+
+    private static long initCount = 0;
+
+    //indexes
+    private Map<String, DeviceType> devices;
+    private Map<String, List<DeviceType>> patterns;
+
+    private final Device unknown;
+
+    public DeviceMapClient() {
+        devices = null;
+        patterns = null;
+        Map<String, String> uAttributes = new HashMap<String, String>();
+        uAttributes.put("id", Device.UNKNOWN_ID);
+        uAttributes = Collections.unmodifiableMap(uAttributes);
+        unknown = new Device(Device.UNKNOWN_ID, uAttributes);
+    }
+
+    public void initDeviceData(LoaderOption option) throws IOException {
+        initDeviceData(option, null);
+    }
+
+    public synchronized void initDeviceData(LoaderOption option, String path) throws IOException {
+        devices = LoaderFactory.getLoader(option, path).getData();
+
+        initCount++;
+
+        if (initCount % 1000 == 0) {
+            LOG.log(Level.WARNING, "Possible device data over-initialization detected");
+        }
+
+        if (devices == null) {
+            patterns = null;
+            return;
+        }
+
+        createIndex();
+    }
+
+    private void createIndex() {
+        patterns = new HashMap<String, List<DeviceType>>(8000);
+
+        for (DeviceType device : devices.values()) {
+            for (Pattern pattern : device.getPatternSet().getPatterns()) {
+                for (int i = 0; i < pattern.getPatternParts().size(); i++) {
+                    String part = pattern.getPatternParts().get(i);
+
+                    //duplicate
+                    if (patterns.get(part) != null) {
+                        if (i == (pattern.getPatternParts().size() - 1) && !patterns.get(part).contains(device)) {
+                            patterns.get(part).add(device);
+                        }
+                    } else {
+                        List<DeviceType> single = new ArrayList<DeviceType>();
+                        single.add(device);
+                        patterns.put(part, single);
+                    }
+                }
+            }
+        }
+    }
+
+    public Map<String, String> classify(String text) {
+        if (devices == null) {
+            throw new RuntimeException("Uninitialized device index");
+        }
+
+        if (text == null) {
+            return null;
+        }
+
+        Set<String> hitPatterns = new HashSet<String>();
+        Set<DeviceType> hitDevices = new HashSet<DeviceType>();
+        DeviceType winner = null;
+        Pattern winnerPattern = null;
+
+        LOG.log(Level.FINE, "classify: ''{0}''", text);
+
+        List<String> parts = split(text);
+
+        //generate ngrams upto size 4
+        for (int i = 0; i < parts.size(); i++) {
+            String pattern = "";
+            for (int j = 0; j < 4 && (j + i) < parts.size(); j++) {
+                pattern += parts.get(i + j);
+                List<DeviceType> dlist = patterns.get(pattern);
+                if (dlist != null) {
+                    hitPatterns.add(pattern);
+                    hitDevices.addAll(dlist);
+                    for (DeviceType device : dlist) {
+                        LOG.log(Level.FINER, "Hit found: ''{0}'' => id: ''{1}'' {2}", new Object[]{pattern, device.getId(), device.getPatternSet()});
+                    }
+                }
+            }
+        }
+
+        //look for the strongest hit
+        for (DeviceType device : hitDevices) {
+            Pattern pattern = device.getPatternSet().isValid(hitPatterns);
+            if (pattern == null) {
+                continue;
+            }
+
+            LOG.log(Level.FINER, "Hit candidate: ''{0}'' => ({1},{2})", new Object[]{device.getId(), pattern.getType(), pattern.getRank()});
+
+            if (winnerPattern == null || pattern.getRank() > winnerPattern.getRank()) {
+                winner = device;
+                winnerPattern = pattern;
+            }
+        }
+
+        if (winner != null) {
+            LOG.log(Level.FINE, "Result: {0}", winner);
+            return winner.getAttributes();
+        } else {
+            return null;
+        }
+    }
+
+    private static List<String> split(String text) {
+        String[] parts = TEXT_SPLIT_PATTERN.split(text);
+        List<String> nonemptyParts = new ArrayList<String>();
+        for (String part : parts) {
+            String normalizedPart = Pattern.normalize(part);
+            if (normalizedPart != null && !normalizedPart.isEmpty())
+                nonemptyParts.add(normalizedPart);
+        }
+        return nonemptyParts;
+    }
+
+    public Device classifyDevice(String text) {
+        Map<String, String> m = classify(text);
+        if (m == null) {
+            return unknown;
+        }
+        return new Device(m.get("id"), m);
+    }
+
+    public int getDeviceCount() {
+        if (devices == null) {
+            return -1;
+        }
+        return devices.size();
+    }
+
+    public int getPatternCount() {
+        if (patterns == null) {
+            return -1;
+        }
+        return patterns.size();
+    }
+
+    public long getNodeCount() {
+        if (patterns == null) {
+            return -1;
+        }
+        long count = 0;
+        for (List<DeviceType> pDevices : patterns.values()) {
+            count += pDevices.size();
+        }
+        return count;
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapFactory.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapFactory.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapFactory.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/DeviceMapFactory.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,62 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap;
+
+import java.io.IOException;
+import org.apache.devicemap.loader.LoaderOption;
+
+public class DeviceMapFactory {
+
+    private static DeviceMapClient client = null;
+    private static volatile boolean initialized = false;
+
+    private static final LoaderOption DEFAULT = LoaderOption.JAR;
+
+    private DeviceMapFactory() {
+    }
+
+    public static DeviceMapClient getClient() {
+        return getClient(DEFAULT, null);
+    }
+
+    public static DeviceMapClient getClient(LoaderOption option) {
+        return getClient(option, null);
+    }
+
+    public static DeviceMapClient getClient(LoaderOption option, String path) {
+        if (!initialized) {
+            synchronized (DeviceMapFactory.class) {
+                if (!initialized) {
+                    client = new DeviceMapClient();
+                    try {
+                        client.initDeviceData(option, path);
+                    } catch (IOException ex) {
+                        throw new RuntimeException(ex);
+                    }
+                    initialized = true;
+                }
+            }
+        }
+        return client;
+    }
+    
+    public static void resetClient() {
+        initialized = false;
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/cmd/Main.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/cmd/Main.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/cmd/Main.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/cmd/Main.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,153 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.cmd;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.devicemap.DeviceMapClient;
+import org.apache.devicemap.data.Device;
+import org.apache.devicemap.loader.LoaderOption;
+import org.apache.devicemap.loader.impl.DDRLoader;
+
+/**
+ * @author Reza Naghibi
+ * @author Werner Keil
+ */
+public class Main {
+
+    public static void main(String[] args) throws Exception {
+
+        System.out.println("DeviceMap Java Console " + DeviceMapClient.class.getPackage().getImplementationVersion());
+        
+        if(args.length == 0) {
+            System.out.println("Usage: -h for help");
+        }
+
+        String loaderPath = null;
+        LoaderOption option = LoaderOption.UNINITIALIZED;
+        String parameter = null;
+        boolean debug = true;
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].equals("-f")) {
+                option = LoaderOption.FOLDER;
+                if (args.length > (++i)) {
+                    loaderPath = args[i];
+                }
+            } else if (args[i].equals("-u")) {
+                option = LoaderOption.URL;
+                if (args.length > (++i)) {
+                    loaderPath = args[i];
+                }
+            } else if (args[i].equals("-j")) {
+                option = LoaderOption.JAR;
+            } else if (args[i].equals("-d")) {
+                debug = false;
+            } else if (args[i].startsWith("-h") || args[i].startsWith("--h")) {
+                System.out.println("Usage: " + Main.class.getName() + " [OPTIONS] [FILE|STRING]\n");
+                System.out.println("  -f <path>            load DeviceMap resouces from folder or \"default\"");
+                System.out.println("  -j                   load DeviceMap resouces from jar file in classpath");
+                System.out.println("  -u <url>             load DeviceMap resouces from URL or \"default\"");
+                System.out.println("  -d                   no debug logging");
+                System.out.println("  FILE                 text file of strings");
+                System.out.println("  STRING               test string");
+
+                return;
+            } //[test string] | [test file]
+            else if(!args[i].isEmpty()) {
+                parameter = args[i];
+            }
+        }
+        
+        if(debug) {
+            Logger.getLogger(DeviceMapClient.class.getName()).setLevel(Level.ALL);
+            Logger.getLogger(DDRLoader.class.getName()).setLevel(Level.ALL);
+            for (Handler h : Logger.getLogger(DeviceMapClient.class.getName()).getParent().getHandlers()) {
+                if (h instanceof ConsoleHandler) {
+                  h.setLevel(Level.ALL);
+                }
+            }
+        }
+
+        if ("default".equals(loaderPath)) {
+            loaderPath = null;
+        }
+
+        DeviceMapClient client = new DeviceMapClient();
+        final long start = System.currentTimeMillis();
+        client.initDeviceData(option, loaderPath);
+        long diff = System.currentTimeMillis() - start;
+
+        System.out.println("Loaded " + client.getDeviceCount() + " devices with " + client.getPatternCount() +
+                " patterns and " + client.getNodeCount() + " nodes in " + diff + "ms");
+
+        final String test = "Mozilla/5.0 (Linux; U; Android 2.2; en; HTC Aria A6380 Build/ERE27) AppleWebKit/540.13+ (KHTML, like Gecko) Version/3.1 Mobile Safari/524.15.0";
+        long startn = System.nanoTime();
+        Device device = client.classifyDevice(test);
+        long diffn = (System.nanoTime() - startn) / 1000;
+
+        System.out.println("Test lookup: '" + device.getId() + "' time: " + diffn + "usec");
+
+        if (parameter == null) {
+        } else if ((new File(parameter)).exists()) {
+            System.out.println("Text file: " + parameter);
+
+            int count = 0;
+            int total = 0;
+
+            BufferedReader in = new BufferedReader(new FileReader(parameter));
+            String line;
+
+            while ((line = in.readLine()) != null) {
+                System.out.println("Text: '" + line + "'");
+                startn = System.nanoTime();
+                device = client.classifyDevice(line);
+                diffn = System.nanoTime() - startn;
+                total += diffn;
+                count++;
+
+                System.out.println("Text lookup " + count + ": '" + device.getId() + "' time: " + (diffn / 1000) + "usec");
+            }
+
+            in.close();
+
+            if (count == 0) {
+                count = 1;
+            }
+
+            total /= count;
+
+            System.out.println("TOTAL lookups: " + count + ", average time: " + (total / 1000) + "usec");
+        } else {
+            System.out.println("Text: '" + parameter + "'");
+
+            startn = System.nanoTime();
+            device = client.classifyDevice(parameter);
+            diffn = System.nanoTime() - startn;
+            System.out.println("Text lookup: '" + device.getId() + "' time: " + (diffn / 1000) + "usec");
+            System.out.println("DeviceMap JSON => " + device.toString());
+        }
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Device.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Device.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Device.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Device.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,60 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.data;
+
+import java.util.Map;
+import org.apache.devicemap.loader.parser.JsonParser;
+
+public class Device {
+    
+    public static final String UNKNOWN_ID = "unknown";
+    
+    private final String id;
+
+    private final Map<String, String> attributes;
+    
+    public Device(String id, Map<String, String> attributes) {
+        this.id = id;
+        this.attributes = attributes;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append('{');
+        sb.append(JsonParser.outputKeyValue("id",id)).append(',');
+        sb.append(JsonParser.outputString("attributes")).append(':').append(JsonParser.outputMap(attributes));
+        sb.append('}');
+        
+        return sb.toString();
+    }
+    
+    public String getId() {
+        return id;
+    }
+    
+    public String getAttribute(String key) {
+        return attributes.get(key);
+    }
+    
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/DeviceType.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/DeviceType.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/DeviceType.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/DeviceType.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,84 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.data;
+
+import java.util.Collections;
+import java.util.Map;
+import org.apache.devicemap.loader.parser.JsonParser;
+
+public class DeviceType {
+
+    private String id;
+
+    private String parentId;
+
+    private final PatternSet pattern;
+
+    private Map<String, String> attributes;
+
+    public DeviceType() {
+        pattern = new PatternSet();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append('{');
+        sb.append(JsonParser.outputKeyValue("id", id)).append(',');
+        sb.append(JsonParser.outputKeyValue("parentId", parentId)).append(',');
+        sb.append(JsonParser.outputString("pattern")).append(':').append(pattern.toString()).append(',');
+        sb.append(JsonParser.outputString("attributes")).append(':').append(JsonParser.outputMap(attributes));
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(String parentId) {
+        this.parentId = parentId;
+    }
+
+    public PatternSet getPatternSet() {
+        return pattern;
+    }
+
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(Map<String, String> attributes) {
+        this.attributes = attributes;
+    }
+
+    public void lockAttributes() {
+        attributes = Collections.unmodifiableMap(attributes);
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Pattern.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Pattern.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Pattern.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/Pattern.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,121 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.data;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import org.apache.devicemap.loader.parser.JsonParser;
+
+public class Pattern {
+
+    private final List<String> pattern;
+
+    private final String type;
+    
+    private final int rank;
+    
+    private final int boost;
+
+    public Pattern(String pattern, String type, int boost) {
+        this(Arrays.asList(pattern), type, boost);
+    }
+
+    public Pattern(List<String> pattern, String type, int boost) {
+        this.pattern = pattern;
+        this.type = type;
+        this.boost = boost;
+        rank = genRank();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append('{');
+        sb.append(JsonParser.outputKeyValue("type", type)).append(',');
+        sb.append(JsonParser.outputKeyRValue("rank", rank)).append(',');
+        sb.append(JsonParser.outputKeyRValue("boost", boost)).append(',');
+        sb.append(JsonParser.outputString("pattern")).append(':').append(JsonParser.outputList(pattern, true));
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    private int genRank() {
+        int r = 0;
+        
+        if ("weak".equals(type)) {
+            r += 1000;
+        }
+
+        r += pattern.size() * 100;
+
+        for (String part : pattern) {
+            r += part.length();
+        }
+        
+        return r;
+    }
+    
+    public int getRank() {
+        return rank + boost;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public List<String> getPatternParts() {
+        return pattern;
+    }
+
+    public boolean isValid(Set<String> patternsToMatch) {
+        for (String part : pattern) {
+            if (!patternsToMatch.contains(part)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+     * normalizes a pattern
+     */
+    public static String normalize(String p) {
+        if (p == null) {
+            return p;
+        }
+
+        p = p.toLowerCase().trim();
+
+        p = p.replaceAll("\\[bb\\]", "b");
+
+        StringBuilder ret = new StringBuilder();
+
+        for (int i = 0; i < p.length(); i++) {
+            Character c = p.charAt(i);
+            if (Character.isLetter(c) || Character.isDigit(c)) {
+                ret.append(c);
+            }
+        }
+
+        return ret.toString();
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/PatternSet.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/PatternSet.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/PatternSet.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/data/PatternSet.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,87 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.apache.devicemap.loader.parser.JsonParser;
+
+public class PatternSet {
+
+    private final List<Pattern> patterns;
+
+    public PatternSet() {
+        patterns = new ArrayList<Pattern>();
+    }
+
+    @Override
+    public String toString() {
+        List<String> orPatterns = new ArrayList<String>();
+        for (Pattern pattern : patterns) {
+            orPatterns.add(pattern.toString());
+        }
+        return JsonParser.outputList(orPatterns, false);
+    }
+
+    /*
+     * add the patterns together as an inner list (AND)
+     */
+    public void setAndPattern(List<String> patterns, String type) {
+        this.patterns.add(new Pattern(patterns, type, 0));
+    }
+
+    /*
+     * add each pattern on its own list (OR)
+     */
+    public void setOrPattern(List<String> patterns, String type) {
+        for (String pattern : patterns) {
+            setPattern(pattern, type);
+        }
+    }
+
+    /*
+     * add a single pattern (OR)
+     */
+    public void setPattern(String pattern, String type) {
+        patterns.add(new Pattern(pattern, type, 0));
+    }
+
+    public List<Pattern> getPatterns() {
+        return patterns;
+    }
+
+    /*
+     * does a pattern match
+     */
+    public Pattern isValid(Set<String> patternsToMatch) {
+        Pattern winner = null;
+
+        for (Pattern pattern : this.patterns) {
+            if (!pattern.isValid(patternsToMatch)) {
+                continue;
+            }
+            if (winner == null || pattern.getRank() > winner.getRank()) {
+                winner = pattern;
+            }
+        }
+
+        return winner;
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Loader.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Loader.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Loader.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Loader.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,29 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.devicemap.data.DeviceType;
+
+public interface Loader {
+  
+    public Map<String, DeviceType> getData() throws IOException;
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderFactory.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderFactory.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderFactory.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderFactory.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,48 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader;
+
+import org.apache.devicemap.loader.impl.DDRLoader;
+import org.apache.devicemap.loader.resource.JarResource;
+import org.apache.devicemap.loader.resource.FileResource;
+import org.apache.devicemap.loader.impl.NoopLoader;
+import org.apache.devicemap.loader.resource.URLResource;
+import org.apache.devicemap.loader.impl.UninitializedLoader;
+
+public class LoaderFactory {
+
+    public static Loader getLoader(LoaderOption option, String path) {
+        switch (option) {
+            case JAR: {
+                return new DDRLoader(new JarResource(path));
+            }
+            case FOLDER: {
+                return new DDRLoader(new FileResource(path));
+            }
+            case URL: {
+                return new DDRLoader(new URLResource(path));
+            }
+            case NOOP: {
+                return new NoopLoader();
+            }
+        }
+        return new UninitializedLoader();
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderOption.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderOption.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderOption.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/LoaderOption.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,23 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader;
+
+public enum LoaderOption {
+    JAR, FOLDER, URL, NOOP, UNINITIALIZED;
+};

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Resource.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Resource.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Resource.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/Resource.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,27 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface Resource {
+
+    public InputStream getResource(String file) throws IOException;
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/DDRLoader.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/DDRLoader.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/DDRLoader.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/DDRLoader.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,255 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.impl;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.devicemap.data.DeviceType;
+import org.apache.devicemap.data.Pattern;
+import org.apache.devicemap.loader.Loader;
+import org.apache.devicemap.loader.Resource;
+import org.apache.devicemap.loader.parser.XMLParser;
+
+public class DDRLoader implements Loader {
+
+    private final static Logger LOG = Logger.getLogger(DDRLoader.class.getName());
+
+    private static final String DEVICE_DATA = "DeviceDataSource.xml";
+    private static final String DEVICE_DATA_PATCH = "DeviceDataSourcePatch.xml";
+    private static final String BUILDER_DATA = "BuilderDataSource.xml";
+    private static final String BUILDER_DATA_PATCH = "BuilderDataSourcePatch.xml";
+
+    private final Map<String, DeviceType> devices;
+
+    private final Resource resourceLoader;
+
+    public DDRLoader(Resource resourceLoader) {
+        devices = new HashMap<String, DeviceType>(5000);
+        this.resourceLoader = resourceLoader;
+    }
+
+    @Override
+    public Map<String, DeviceType> getData() throws IOException {
+        long start = System.currentTimeMillis();
+
+        BufferedReader ddin = new BufferedReader(new InputStreamReader(resourceLoader.getResource(DEVICE_DATA), "UTF-8"));
+        loadDeviceData(ddin);
+        ddin.close();
+
+        long diff = System.currentTimeMillis() - start;
+        LOG.log(Level.FINE, "Loaded " + DEVICE_DATA + " in {0}ms", diff);
+
+        try {
+            start = System.currentTimeMillis();
+
+            BufferedReader ddpin = new BufferedReader(new InputStreamReader(resourceLoader.getResource(DEVICE_DATA_PATCH), "UTF-8"));
+            loadDeviceData(ddpin);
+            ddpin.close();
+
+            diff = System.currentTimeMillis() - start;
+            LOG.log(Level.FINE, "Loaded " + DEVICE_DATA_PATCH + " in {0}ms", diff);
+        } catch (FileNotFoundException ex) {
+            LOG.log(Level.WARNING, "File not found " + DEVICE_DATA_PATCH + ": {0}", ex.toString());
+        }
+
+        setParentAttributes();
+
+        start = System.currentTimeMillis();
+
+        BufferedReader bin = new BufferedReader(new InputStreamReader(resourceLoader.getResource(BUILDER_DATA), "UTF-8"));
+        loadDevicePatterns(bin);
+        bin.close();
+
+        diff = System.currentTimeMillis() - start;
+        LOG.log(Level.FINE, "Loaded " + BUILDER_DATA + " in {0}ms", diff);
+
+        try {
+            start = System.currentTimeMillis();
+
+            BufferedReader bpin = new BufferedReader(new InputStreamReader(resourceLoader.getResource(BUILDER_DATA_PATCH), "UTF-8"));
+            loadDevicePatterns(bpin);
+            bpin.close();
+
+            diff = System.currentTimeMillis() - start;
+            LOG.log(Level.FINE, "Loaded " + BUILDER_DATA_PATCH + " in {0}ms", diff);
+        } catch (FileNotFoundException ex) {
+            LOG.log(Level.WARNING, "File not found " + BUILDER_DATA_PATCH + ": {0}", ex.toString());
+        }
+
+        return getDevices();
+    }
+
+    /*
+     * loads device data from an InputStreamReader
+     */
+    private void loadDeviceData(Reader in) throws IOException {
+        XMLParser parser = new XMLParser(in);
+        String tag;
+        DeviceType device = new DeviceType();
+        Map<String, String> attributes = new HashMap<String, String>();
+
+        while (!(tag = parser.getNextTag()).isEmpty()) {
+            //new device found
+            if (tag.startsWith("<device ")) {
+                device.setId(XMLParser.getAttribute(tag, "id"));
+                device.setParentId(XMLParser.getAttribute(tag, "parentId"));
+            } else if (tag.equals("</device>")) {
+
+                //add the device
+                if (device.getId() != null && !device.getId().isEmpty()) {
+                    attributes.put("id", device.getId());
+                    device.setAttributes(attributes);
+                    devices.put(device.getId(), device);
+                }
+
+                //reset the device
+                device = new DeviceType();
+                attributes = new HashMap<String, String>();
+            } else if (tag.startsWith("<property ")) {
+                //add the property to the device
+                String key = XMLParser.getAttribute(tag, "name");
+                String value = XMLParser.getAttribute(tag, "value");
+
+                attributes.put(key, value);
+            }
+        }
+    }
+
+    /*
+     * loads patterns from an InputStreamReader
+     */
+    private void loadDevicePatterns(Reader in) throws IOException {
+        XMLParser parser = new XMLParser(in);
+        String tag;
+        String builder = "";
+        String type = "";
+        DeviceType device = null;
+        String id = "";
+        List<String> patterns = new ArrayList<String>();
+
+        while (!(tag = parser.getNextTag()).isEmpty()) {
+            //new builder found
+            if (tag.startsWith("<builder ")) {
+                builder = XMLParser.getAttribute(tag, "class");
+
+                if (builder.lastIndexOf(".") >= 0) {
+                    builder = builder.substring(builder.lastIndexOf(".") + 1);
+                }
+
+                type = "weak";
+                if (builder.equals("SimpleDeviceBuilder")) {
+                    type = "simple";
+                }
+            } else if (tag.startsWith("<device ")) {
+                //new device found
+                id = XMLParser.getAttribute(tag, "id");
+                device = devices.get(id);
+            } else if (tag.equals("</device>")) {
+                //add the device
+                if (device != null) {
+                    //TwoStep is an AND pattern, also index the unigram
+                    if (builder.equals("TwoStepDeviceBuilder")) {
+                        device.getPatternSet().setAndPattern(patterns, type);
+
+                        String unigram = "";
+
+                        for (String pattern : patterns) {
+                            if (pattern.contains(unigram)) {
+                                unigram = pattern;
+                            } else {
+                                unigram += pattern;
+                            }
+                        }
+
+                        device.getPatternSet().setPattern(unigram, type);
+                    } else {
+                        device.getPatternSet().setOrPattern(patterns, type);
+                    }
+                } else {
+                    LOG.log(Level.FINE, "ERROR: device not found: ''{0}''", id);
+                }
+
+                //reset the device
+                device = null;
+                id = "";
+                patterns = new ArrayList<String>();
+            } else if (tag.equals("<value>")) {
+                //add the pattern to the device
+                String pattern = Pattern.normalize(parser.getTagValue());
+
+                if (pattern.isEmpty()) {
+                    continue;
+                }
+
+                patterns.add(pattern);
+            }
+        }
+    }
+
+    /**
+     * Sets attributes from parents
+     */
+    private void setParentAttributes() {
+        for (DeviceType device : devices.values()) {
+            mergeParent(device);
+            device.lockAttributes();
+        }
+    }
+
+    private void mergeParent(DeviceType device) {
+        String parentId = device.getParentId();
+
+        if (parentId == null) {
+            return;
+        }
+
+        DeviceType parent = devices.get(parentId);
+
+        if (parent == null) {
+            return;
+        }
+
+        mergeParent(parent);
+
+        for (String key : parent.getAttributes().keySet()) {
+            String value = parent.getAttributes().get(key);
+
+            if (!device.getAttributes().containsKey(key)) {
+                device.getAttributes().put(key, value);
+            }
+        }
+    }
+
+    /**
+     * @return the devices
+     */
+    private Map<String, DeviceType> getDevices() {
+        return devices;
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/NoopLoader.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/NoopLoader.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/NoopLoader.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/NoopLoader.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,34 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.impl;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.devicemap.data.DeviceType;
+import org.apache.devicemap.loader.Loader;
+
+public class NoopLoader implements Loader {
+
+    @Override
+    public Map<String, DeviceType> getData() throws IOException {
+        return Collections.emptyMap();
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/UninitializedLoader.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/UninitializedLoader.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/UninitializedLoader.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/impl/UninitializedLoader.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,33 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.impl;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.devicemap.data.DeviceType;
+import org.apache.devicemap.loader.Loader;
+
+public class UninitializedLoader implements Loader {
+
+    @Override
+    public Map<String, DeviceType> getData() throws IOException {
+        return null;
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/JsonParser.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/JsonParser.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/JsonParser.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/JsonParser.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,81 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.parser;
+
+import java.util.List;
+import java.util.Map;
+
+public class JsonParser {
+
+    public static String outputString(String s) {
+        if (s == null) {
+            return "null";
+        }
+        return "\"" + s.replace("\"", "\\\"").replace("\n", " ") + "\"";
+    }
+
+    public static String outputKeyValue(String key, String value) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(outputString(key));
+        sb.append(':');
+        sb.append(outputString(value));
+        return sb.toString();
+    }
+
+    public static String outputKeyRValue(String key, Object value) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(outputString(key));
+        sb.append(':');
+        sb.append(value);
+        return sb.toString();
+    }
+    
+    public static String outputMap(Map<String, String> map) {
+        StringBuilder sb = new StringBuilder();
+        for (String key : map.keySet()) {
+            String value = map.get(key);
+            if (sb.length() == 0) {
+                sb.append('{');
+            } else {
+                sb.append(',');
+            }
+            sb.append(outputKeyValue(key, value));
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public static String outputList(List<String> list, boolean format) {
+        StringBuilder sb = new StringBuilder();
+        for (String value : list) {
+            if (sb.length() == 0) {
+                sb.append('[');
+            } else {
+                sb.append(',');
+            }
+            if (format) {
+                sb.append(outputString(value));
+            } else {
+                sb.append(value);
+            }
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/XMLParser.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/XMLParser.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/XMLParser.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/parser/XMLParser.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,129 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+
+public class XMLParser {
+
+    private final Reader in;
+
+    private char pre;
+
+    public XMLParser(Reader in) {
+        this.in = in;
+        pre = 0;
+    }
+
+    public String getNextTag() throws IOException {
+        StringBuilder ret = new StringBuilder();
+
+        int i;
+        boolean start = false;
+
+        if (pre == '<') {
+            ret.append(pre);
+            pre = 0;
+            start = true;
+        }
+
+        while ((i = in.read()) != -1) {
+            char c = (char) i;
+            if (c == '<') {
+                start = true;
+                ret.append(c);
+            } else if (start) {
+                ret.append(c);
+            }
+            
+            if(c == '>') {
+                if(ret.lastIndexOf("<!--") == 0 && (ret.lastIndexOf("-->") + 3) != ret.length()) {
+                    continue;
+                }
+                break;
+            }
+        }
+        
+        if(ret.lastIndexOf("<!--") == 0) {
+            if((ret.lastIndexOf("-->") + 3) == ret.length()) {
+                return getNextTag();
+            } else {
+                return "";
+            }
+        }
+
+        return ret.toString();
+    }
+
+    public String getTagValue() throws IOException {
+        StringBuilder ret = new StringBuilder();
+
+        int i;
+
+        while ((i = in.read()) != -1) {
+            char c = (char) i;
+            if (c == '<') {
+                pre = '<';
+                break;
+            } else {
+                ret.append(c);
+            }
+        }
+
+        return parseEntities(ret.toString().trim());
+    }
+
+    public static String getAttribute(String tag, String name) {
+        int retpos = tag.toLowerCase().indexOf(name.toLowerCase() + "=");
+
+        if (retpos == -1) {
+            return "";
+        }
+
+        String ret = tag.substring(retpos + name.length() + 1);
+
+        if (ret.startsWith("\"")) {
+            ret = ret.substring(1);
+            int endpos = ret.indexOf("\"");
+
+            if (endpos == -1) {
+                return "";
+            }
+
+            ret = ret.substring(0, endpos);
+        } else {
+            int endpos = ret.indexOf(" ");
+
+            if (endpos == -1) {
+                return "";
+            }
+
+            ret = ret.substring(0, endpos);
+        }
+
+        return parseEntities(ret);
+    }
+    
+    private static String parseEntities(String s) {
+        return s.replace("&quot;", "\"").replace("&amp;", "&")
+                .replace("&apos;", "'").replace("&lt;", "<")
+                .replace("&gt;", ">");
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/FileResource.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/FileResource.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/FileResource.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/FileResource.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,50 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.devicemap.loader.Resource;
+
+public class FileResource implements Resource {
+
+    private static final String DEFAULT_PATH = "";
+
+    private final String path;
+
+    public FileResource(String path) {
+        if (path == null) {
+            this.path = DEFAULT_PATH;
+        } else if (path.isEmpty()) {
+            this.path = "";
+        } else {
+            this.path = path + File.separatorChar;
+        }
+    }
+
+    @Override
+    public InputStream getResource(String file) throws IOException {
+        String rpath = path + file;
+        return new FileInputStream(rpath);
+    }
+
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/JarResource.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/JarResource.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/JarResource.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/JarResource.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,53 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.resource;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.devicemap.loader.Resource;
+
+public class JarResource implements Resource {
+
+    private static final String DEFAULT_PATH = "/devicedata/";
+
+    private final String path;
+
+    public JarResource(String path) {
+        if (path == null) {
+            this.path = DEFAULT_PATH;
+        } else {
+            this.path = path;
+        }
+    }
+
+    @Override
+    public InputStream getResource(String file) throws IOException {
+        String rpath = path + file;
+
+        InputStream in = JarResource.class.getResourceAsStream(rpath);
+
+        if (in == null) {
+            throw new FileNotFoundException("Jar resource not found: " + path);
+        }
+
+        return in;
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/URLResource.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/URLResource.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/URLResource.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/main/java/org/apache/devicemap/loader/resource/URLResource.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,48 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap.loader.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.devicemap.loader.Resource;
+
+public class URLResource implements Resource {
+
+    //TODO: put on CDN of some kind
+    private static final String DEFAULT_PATH = "http://devicemap-vm.apache.org/data/latest";
+
+    private final String path;
+
+    public URLResource(String path) {
+        if (path == null) {
+            this.path = DEFAULT_PATH;
+        } else {
+            this.path = path;
+        }
+    }
+
+    @Override
+    public InputStream getResource(String file) throws IOException {
+        String rpath = path + "/" + file;
+        return new URL(rpath).openStream();
+    }
+
+}

Added: devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientFileTestOptional.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientFileTestOptional.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientFileTestOptional.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientFileTestOptional.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,69 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap;
+
+import java.io.FileNotFoundException;
+import org.apache.devicemap.data.Device;
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.devicemap.loader.LoaderOption;
+
+public class DeviceMapClientFileTestOptional {
+
+    @Test
+    public void deviceMapClientFolderTest() throws Exception {
+        DeviceMapClient client = new DeviceMapClient();
+
+        client.initDeviceData(LoaderOption.FOLDER, "../../../data/device-data/src/main/resources/devicedata");
+
+        String test = "Mozilla/5.0 (Linux; U; Android 2.2; en; HTC Aria A6380 Build/ERE27) AppleWebKit/540.13+ (KHTML, like Gecko) Version/3.1 Mobile Safari/524.15.0";
+
+        Device device = client.classifyDevice(test);
+
+        Assert.assertEquals("test ua not htc aria", "HTC Aria", device.getId());
+    }
+    
+    @Test
+    public void deviceMapClientFolderFailureTest() throws Exception {
+        DeviceMapClient client = new DeviceMapClient();
+
+        try {
+            client.initDeviceData(LoaderOption.FOLDER,"fail");
+            Assert.fail("FileNotFoundException expected with invalid folder path");
+        } catch(FileNotFoundException fnf) {
+            //pass
+        } catch(Exception ex) {
+            Assert.fail("FileNotFoundException expected with invalid folder path, got: "+ex.toString());
+        }
+    }
+    
+    @Test
+    public void deviceMapFactoryFileTest() throws Exception {
+        String test = "Mozilla/5.0 (Linux; U; Android 2.2; en; HTC Aria A6380 Build/ERE27) AppleWebKit/540.13+ (KHTML, like Gecko) Version/3.1 Mobile Safari/524.15.0";
+
+        Device device = DeviceMapFactory.getClient(LoaderOption.FOLDER, "../../../data/device-data/src/main/resources/devicedata").classifyDevice(test);
+
+        Assert.assertEquals("test ua not htc aria", "HTC Aria", device.getId());
+        
+        device = DeviceMapFactory.getClient(LoaderOption.FOLDER, "../../../data/device-data/src/main/resources/devicedata").classifyDevice(test);
+        
+        Assert.assertEquals("2nd test ua not htc aria", "HTC Aria", device.getId());
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientJarTest.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientJarTest.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientJarTest.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientJarTest.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,69 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap;
+
+import java.io.FileNotFoundException;
+import org.apache.devicemap.data.Device;
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.devicemap.loader.LoaderOption;
+
+public class DeviceMapClientJarTest {
+
+    @Test
+    public void deviceMapClientJarTest() throws Exception {
+        DeviceMapClient client = new DeviceMapClient();
+
+        client.initDeviceData(LoaderOption.JAR);
+
+        String test = "Mozilla/5.0 (Linux; U; Android 2.2; en; HTC Aria A6380 Build/ERE27) AppleWebKit/540.13+ (KHTML, like Gecko) Version/3.1 Mobile Safari/524.15.0";
+
+        Device device = client.classifyDevice(test);
+
+        Assert.assertEquals("test ua not htc aria", "HTC Aria", device.getId());
+    }
+    
+    @Test
+    public void deviceMapClientJarFailureTest() throws Exception {
+        DeviceMapClient client = new DeviceMapClient();
+
+        try {
+            client.initDeviceData(LoaderOption.JAR,"fail");
+            Assert.fail("FileNotFoundException expected with invalid JAR path");
+        } catch(FileNotFoundException fnf) {
+            //pass
+        } catch(Exception ex) {
+            Assert.fail("FileNotFoundException expected with invalid JAR path, got: "+ex.toString());
+        }
+    }
+    
+    @Test
+    public void deviceMapFactoryJarTest() throws Exception {
+        String test = "Mozilla/5.0 (Linux; U; Android 2.2; en; HTC Aria A6380 Build/ERE27) AppleWebKit/540.13+ (KHTML, like Gecko) Version/3.1 Mobile Safari/524.15.0";
+
+        Device device = DeviceMapFactory.getClient(LoaderOption.JAR).classifyDevice(test);
+
+        Assert.assertEquals("test ua not htc aria", "HTC Aria", device.getId());
+        
+        device = DeviceMapFactory.getClient(LoaderOption.JAR).classifyDevice(test);
+        
+        Assert.assertEquals("2nd test ua not htc aria", "HTC Aria", device.getId());
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientTest.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientTest.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientTest.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientTest.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,76 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.apache.devicemap.data.Device;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import org.apache.devicemap.loader.LoaderOption;
+
+@RunWith(value = Parameterized.class)
+public class DeviceMapClientTest {
+
+    private static DeviceMapClient client;
+
+    @BeforeClass
+    public static void setupDeviceMapClient() throws Exception {
+        client = new DeviceMapClient();
+        client.initDeviceData(LoaderOption.JAR);
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() throws Exception {
+        Collection<Object[]> params = new ArrayList<Object[]>();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(DeviceMapClientTest.class.getResourceAsStream("/uas.data")));
+        String line;
+
+        while ((line = reader.readLine()) != null) {
+            params.add(line.split("\\|\\|"));
+        }
+
+        reader.close();
+
+        return params;
+    }
+
+    private final String testString;
+    private final String resultId;
+
+    public DeviceMapClientTest(String testString, String resultId) {
+        this.testString = testString;
+        this.resultId = resultId;
+    }
+
+    @Test
+    public void testDeviceMapClient() throws Exception {
+        Device device = client.classifyDevice(testString);
+
+        Assert.assertEquals("classification failed for '" + testString + "'", resultId, device.getId());
+    }
+}

Added: devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientUnitTest.java
URL: http://svn.apache.org/viewvc/devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientUnitTest.java?rev=1717136&view=auto
==============================================================================
--- devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientUnitTest.java (added)
+++ devicemap/trunk/clients/1.0/java/client/src/test/java/org/apache/devicemap/DeviceMapClientUnitTest.java Sun Nov 29 23:36:18 2015
@@ -0,0 +1,84 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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 org.apache.devicemap;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.apache.devicemap.data.Device;
+
+public class DeviceMapClientUnitTest {
+    @Test
+    public void deviceMapClientUninitializedTest() throws Exception {
+        DeviceMapClient uninit = new DeviceMapClient();
+
+        try {
+            uninit.classifyDevice("");
+            Assert.fail("Uninitialized device index exception expected");
+        } catch (RuntimeException re) {
+            //pass
+        } catch (Exception ex) {
+            Assert.fail("Uninitialized device index exception expected, got: " + ex.toString());
+        }
+    }
+
+    @Test
+    public void deviceMapClientUnknownTest() throws Exception {
+        Device device = DeviceMapFactory.getClient().classifyDevice("");
+
+        Assert.assertEquals("test ua not unknown", Device.UNKNOWN_ID, device.getId());
+
+        device = DeviceMapFactory.getClient().classifyDevice(null);
+
+        Assert.assertEquals("test ua not unknown", Device.UNKNOWN_ID, device.getId());
+    }
+
+    @Test
+    public void deviceMapClientMutateTest() throws Exception {
+        String test = "Mozilla/5.0 (Linux; U; Android 2.2; en; HTC Aria A6380 Build/ERE27) AppleWebKit/540.13+ (KHTML, like Gecko) Version/3.1 Mobile Safari/524.15.0";
+
+        Device device = DeviceMapFactory.getClient().classifyDevice(test);
+
+        Assert.assertEquals("test ua not htc aria", "HTC Aria", device.getId());
+
+        try {
+            device.getAttributes().put("mutate", "true");
+            Assert.fail("UnsupportedOperationException expected when changing result map");
+        } catch (UnsupportedOperationException uoe) {
+            //pass
+        } catch (Exception ex) {
+            Assert.fail("UnsupportedOperationException expected when changing result map, got: " + ex.toString());
+        }
+    }
+
+    @Test
+    public void deviceMapClientMutateUnknownTest() throws Exception {
+        Device device = DeviceMapFactory.getClient().classifyDevice("unknown");
+
+        Assert.assertEquals("test ua not unknown", Device.UNKNOWN_ID, device.getId());
+
+        try {
+            device.getAttributes().put("mutate", "true");
+            Assert.fail("UnsupportedOperationException expected when changing result map");
+        } catch (UnsupportedOperationException uoe) {
+            //pass
+        } catch (Exception ex) {
+            Assert.fail("UnsupportedOperationException expected when changing result map, got: " + ex.toString());
+        }
+    }
+}