You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by cb...@apache.org on 2016/10/20 20:35:23 UTC

svn commit: r1765879 [1/4] - in /velocity/tools/trunk: ./ velocity-tools-browser/ velocity-tools-browser/src/ velocity-tools-browser/src/main/ velocity-tools-browser/src/main/java/ velocity-tools-browser/src/main/java/org/ velocity-tools-browser/src/ma...

Author: cbrisson
Date: Thu Oct 20 20:35:23 2016
New Revision: 1765879

URL: http://svn.apache.org/viewvc?rev=1765879&view=rev
Log:
[tools] upgrading of BrowserTool tool, and make it a subjar, to isolate dependency towards Apache DeviceMap

Added:
    velocity/tools/trunk/velocity-tools-browser/
    velocity/tools/trunk/velocity-tools-browser/pom.xml
    velocity/tools/trunk/velocity-tools-browser/src/
    velocity/tools/trunk/velocity-tools-browser/src/main/
    velocity/tools/trunk/velocity-tools-browser/src/main/java/
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java
    velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/UAParser.java
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/browser/
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/browser/tools.xml
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/view/
    velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt
    velocity/tools/trunk/velocity-tools-browser/src/test/
    velocity/tools/trunk/velocity-tools-browser/src/test/java/
    velocity/tools/trunk/velocity-tools-browser/src/test/java/org/
    velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/
    velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/
    velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/tools/
    velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/tools/view/
    velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java
    velocity/tools/trunk/velocity-tools-browser/src/test/resources/
    velocity/tools/trunk/velocity-tools-browser/src/test/resources/browsers.txt
    velocity/tools/trunk/velocity-tools-browser/src/test/resources/operating_systems.txt
    velocity/tools/trunk/velocity-tools-browser/src/test/resources/robots.txt
Removed:
    velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
Modified:
    velocity/tools/trunk/pom.xml
    velocity/tools/trunk/velocity-tools-examples/velocity-tools-examples-showcase/pom.xml
    velocity/tools/trunk/velocity-tools-generic/pom.xml
    velocity/tools/trunk/velocity-tools-generic/src/main/java/org/apache/velocity/tools/config/ConfigurationUtils.java
    velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/tools.xml

Modified: velocity/tools/trunk/pom.xml
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/pom.xml?rev=1765879&r1=1765878&r2=1765879&view=diff
==============================================================================
--- velocity/tools/trunk/pom.xml (original)
+++ velocity/tools/trunk/pom.xml Thu Oct 20 20:35:23 2016
@@ -46,7 +46,7 @@
     <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <junit.version>4.12</junit.version>
-       <slf4j.version>1.7.21</slf4j.version>
+      <slf4j.version>1.7.21</slf4j.version>
       <surefire.plugin.version>2.19.1</surefire.plugin.version>
       <servlet.api.version>3.0.1</servlet.api.version>
     </properties>
@@ -119,6 +119,7 @@
         <module>velocity-tools-generic</module>
         <module>velocity-tools-xml</module>
         <module>velocity-tools-view</module>
+        <module>velocity-tools-browser</module>
         <module>velocity-tools-view-jsp</module>
         <module>velocity-tools-struts</module>
         <module>velocity-tools-uberjar</module>

Added: velocity/tools/trunk/velocity-tools-browser/pom.xml
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-browser/pom.xml?rev=1765879&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-browser/pom.xml (added)
+++ velocity/tools/trunk/velocity-tools-browser/pom.xml Thu Oct 20 20:35:23 2016
@@ -0,0 +1,114 @@
+<?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/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>velocity-tools-parent</artifactId>
+    <groupId>org.apache.velocity</groupId>
+    <version>3.0-SNAPSHOT</version>
+  </parent>
+  <groupId>org.apache.velocity</groupId>
+  <artifactId>velocity-tools-browser</artifactId>
+  <name>Apache Velocity Tools - Browser tools</name>
+  <description>Tools for client browser user agent parsing.</description>
+  <dependencies>
+      <dependency>
+          <groupId>org.apache.velocity</groupId>
+          <artifactId>velocity-tools-view</artifactId>
+          <version>${project.version}</version>
+      </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>${servlet.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+      <dependency>
+          <groupId>org.apache.devicemap</groupId>
+          <artifactId>devicemap-client</artifactId>
+          <version>1.1.0</version>
+      </dependency>
+      <dependency>
+          <groupId>org.apache.devicemap</groupId>
+          <artifactId>devicemap-data</artifactId>
+          <version>1.0.3</version>
+      </dependency>
+      <dependency>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+          <version>4.12</version>
+          <scope>test</scope>
+      </dependency>
+      <!-- to reuse some test classes from velocity-tools-generic -->
+      <dependency>
+        <groupId>org.apache.velocity</groupId>
+        <artifactId>velocity-tools-generic</artifactId>
+        <version>${project.version}</version>
+        <type>test-jar</type>
+        <scope>test</scope>
+      </dependency>
+  </dependencies>
+
+  <build>
+    <defaultGoal>install</defaultGoal>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire.plugin.version}</version>
+        <configuration>
+          <skip>${maven.test.skip}</skip>
+          <systemProperties>
+            <property>
+              <name>test</name>
+              <value>${test}</value>
+            </property>
+            <property>
+              <name>test.output.dir</name>
+              <value>${project.build.testOutputDirectory}</value>
+            </property>
+            <property>
+              <name>test.result.dir</name>
+              <value>${project.build.directory}/results</value>
+            </property>
+            <property>
+              <name>org.slf4j.simpleLogger.defaultLogLevel</name>
+              <value>warn</value>
+              </property>
+          </systemProperties>
+        </configuration>
+        <executions>
+          <execution>
+            <id>integration-test</id>
+            <phase>integration-test</phase>
+            <goals>
+              <goal>test</goal>
+            </goals>
+            <configuration>
+              <skip>false</skip>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

Added: velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserTool.java?rev=1765879&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserTool.java (added)
+++ velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserTool.java Thu Oct 20 20:35:23 2016
@@ -0,0 +1,596 @@
+package org.apache.velocity.tools.view;
+
+/*
+ * 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.
+ */
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.*;
+
+import org.apache.devicemap.DeviceMapClient;
+import org.apache.devicemap.DeviceMapFactory;
+import org.apache.devicemap.data.Device;
+import org.apache.devicemap.loader.LoaderOption;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.ConversionUtils;
+import static org.apache.velocity.tools.view.UAParser.*;
+
+import org.slf4j.Logger;
+
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.config.DefaultKey;
+import org.apache.velocity.tools.config.InvalidScope;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ *  <p>userAgent.getBrowser()-sniffing tool (session or request scope requested, session scope advised).</p>
+ *  <p></p>
+ * <p><b>Usage:</b></p>
+ * <p>BrowserTool defines properties that are used to test the client userAgent.getBrowser(), operating system, device, language...</p>
+ * <p>All properties are boolean, excpet those in italic which are strings (and major/minor versions which are integers)</p>
+ * <p>The following properties are available:</p>
+ * <ul>
+ * <li><b>Device: </b><i>device</i> robot mobile tablet desktop</li>
+ * <li><b>Features:</b>css3 dom3</li>
+ * <li><b>Browser:</b><i>userAgent.getBrowser().name userAgent.getBrowser().majorVersion userAgent.getBrowser().minorVersion</i></li>
+ * <li><b>Rendering engine: </b><i>renderingEngine.name renderingEngine.minorVersion renderingEngine.majorVersion</i></li>
+ * <li><b>Operating system: </b><i>operatingsystem.name operatingsystem.majorVersion operatingsystem.minorVersion</i></li>
+ * <li><b>Specific userAgent.getBrowser() tests:</b>netscape firefox safari MSIE opera links mozilla konqueror chrome</li>
+ * <li><b>Specific rendering engine tests:</b>gecko webKit KHTML trident blink edgeHTML presto</li>
+ * <li><b>Specific OS tests:</b>windows OSX linux unix BSD android iOS symbian</li>
+ * <li><b>Languages</b>: <i>preferredLanguageTag</i> (a string like 'en', 'da', 'en-US', ...), <i>preferredLocale</i> (a java Locale)</li>
+ * </ul>
+ *
+ * <p>Language properties are filtered by the languagesFilter tool param, if present, which is here to specify which languages are acceptable on the server side.
+ * If no matching language is found, or if there is no
+ * matching language, the tools defaut locale (or the first value of languagesFilter) is returned.
+ * Their value is guarantied to belong to the set provided in languagesFilter, if any.</p>
+ *
+ * <p>Thanks to Lee Semel (lee@semel.net), the author of the HTTP::BrowserDetect Perl module.</p>
+ * <p>See also:
+ * <ul>
+ *   <li>http://www.zytrax.com/tech/web/userAgent.getBrowser()_ids.htm</li>
+ *   <li>http://en.wikipedia.org/wiki/User_agent</li>
+ *   <li>http://www.user-agents.org/</li>
+ *   <li>https://github.com/OpenDDR</li>
+ *   <li>https://devicemap.apache.org/</li>
+ *   <li>http://www.useragentstring.com/pages/useragentstring.php?name=All</li>
+ *   <li>https://en.wikipedia.org/wiki/Comparison_of_layout_engines_(Cascading_Style_Sheets)</li>
+ *   <li>https://en.wikipedia.org/wiki/Comparison_of_layout_engines_(Document_Object_Model)</li>
+ *   <li>http://www.webapps-online.com/online-tools/user-agent-strings</li>
+ *   <li>https://whichuserAgent.getBrowser().net/data/</li>
+ * </ul>
+ * </p>
+ *
+ * @author <a href="mailto:claude@renegat.net">Claude Brisson</a>
+ * @since VelocityTools 2.0
+ * @version $Revision$ $Date$
+ */
+@DefaultKey("userAgent.getBrowser()")
+@InvalidScope(Scope.APPLICATION)
+public class BrowserTool extends BrowserToolDeprecatedMethods implements java.io.Serializable
+{
+    private static final long serialVersionUID = 1734529350532353339L;
+
+    protected Logger LOG = null;
+
+    /* User-Agent */
+    private String userAgentString = null;
+    private String lowercaseUserAgentString = null;
+    private UserAgent userAgent = null;
+
+    /* Accept-Language header variables */
+    private String acceptLanguage = null;
+    private SortedMap<Float,List<String>> languageRangesByQuality = null;
+    private String starLanguageRange = null;
+    // pametrizable filter of retained laguages
+    private List<String> languagesFilter = null;
+    private String preferredLanguage = null;
+
+    private static Pattern quality = Pattern.compile("^q\\s*=\\s*(\\d(?:0(?:.\\d{0,3})?|1(?:.0{0,3}))?)$");
+
+    /* parsing helpers */
+    private static UAParser uaParser = null;
+    /**
+     * Retrieves the User-Agent header from the request (if any).
+     * @see #setUserAgentString
+     */
+    public void setRequest(HttpServletRequest request)
+    {
+        if (request != null)
+        {
+            setUserAgentString(request.getHeader("User-Agent"));
+            setAcceptLanguage(request.getHeader("Accept-Language"));
+        }
+        else
+        {
+            setUserAgentString(null);
+            setAcceptLanguage(null);
+        }
+    }
+
+    /**
+     * Set log.
+     */
+    public void setLog(Logger log)
+    {
+        if (log == null)
+        {
+            throw new NullPointerException("BrowserTool: log should not be set to null");
+        }
+        this.LOG = log;
+        uaParser = new UAParser(LOG);
+    }
+
+
+    /**
+     * Sets the User-Agent string to be parsed for info.  If null, the string
+     * will be empty and everything will return false or null.  Otherwise,
+     * it will set the whole string to lower case before storing to simplify
+     * parsing.
+     */
+    public void setUserAgentString(String ua)
+    {
+        /* reset internal state */
+        userAgentString = null;
+        userAgent = null;
+        acceptLanguage = preferredLanguage = null;
+        languageRangesByQuality = null;
+        starLanguageRange = null;
+
+        if (ua == null)
+        {
+            lowercaseUserAgentString = "";
+        }
+        else
+        {
+            userAgentString = ua;
+            lowercaseUserAgentString = ua.toLowerCase();
+            if (uaParser == null)
+            {
+                /* can't run without a logger, can we? */
+                throw new VelocityException("BrowserTool: no logger was defined");
+            }
+            userAgent = uaParser.parseUserAgent(ua);
+        }
+    }
+
+    public void setAcceptLanguage(String al)
+    {
+        if(al == null)
+        {
+            acceptLanguage = "";
+        }
+        else
+        {
+            acceptLanguage = al.toLowerCase();
+        }
+    }
+
+    public void setLanguagesFilter(String filter)
+    {
+        if(filter == null || filter.length() == 0)
+        {
+            languagesFilter = null;
+        }
+        else
+        {
+            languagesFilter = Arrays.asList(filter.split(","));
+        }
+        // clear preferred language cache
+        preferredLanguage = null;
+    }
+
+    public String getLanguagesFilter()
+    {
+        return languagesFilter.toString();
+    }
+
+    @Override
+    public String toString()
+    {
+        return this.getClass().getSimpleName()+"[ua="+ userAgentString +"]";
+    }
+
+
+    /* Generic getter for unknown tests
+     */
+    public boolean get(String key)
+    {
+        return test(key);
+    }
+
+    public String getUserAgentString()
+    {
+	    return userAgentString;
+    }
+
+    public String getAcceptLanguage()
+    {
+        return acceptLanguage;
+    }
+
+    /* device type */
+
+    /**
+     * @Since VelocityTools 3.0
+     */
+    public String getDeviceType()
+    {
+        return userAgent == null ? null : userAgent.getDeviceType().toString().toLowerCase();
+    }
+
+    public boolean isRobot()
+    {
+        return userAgent != null && userAgent.getDeviceType() == DeviceType.ROBOT;
+    }
+
+    /**
+     * @Since VelocityTools 3.0
+     */
+    public boolean isTablet()
+    {
+        return userAgent == null && userAgent.getDeviceType() == DeviceType.TABLET;
+    }
+
+    /**
+     * @Since VelocityTools 3.0
+     */
+    public boolean isMobile()
+    {
+        return userAgent == null && userAgent.getDeviceType() == DeviceType.MOBILE;
+    }
+
+    /**
+     * @Since VelocityTools 3.0
+     */
+    public boolean isDesktop()
+    {
+        return userAgent == null && userAgent.getDeviceType() == DeviceType.DESKTOP;
+    }
+
+    /**
+     * @Since VelocityTools 3.0
+     */
+    public UAEntity getBrowser()
+    {
+        return userAgent == null ? null : userAgent.getBrowser();
+    }
+
+    /**
+     * @Since VelocityTools 3.0
+     */
+    public UAEntity getRenderingEngine()
+    {
+        return userAgent == null ? null : userAgent.getRenderingEngine();
+    }
+
+    /**
+     * @Since VelocityTools 3.0
+     */
+    public UAEntity getOperatingSystem()
+    {
+        return userAgent == null ? null : userAgent.getOperatingSystem();
+    }
+
+    /* Specific rendering engines */
+
+    public boolean isGecko()
+    {
+        return getRenderingEngine() != null && "Gecko".equals(getRenderingEngine().getName());
+    }
+
+    public boolean isWebKit()
+    {
+        return getRenderingEngine() != null && "AppleWebKit".equals(getRenderingEngine().getName());
+    }
+
+    public boolean isKHTML()
+    {
+        return getRenderingEngine() != null && "KHTML".equals(getRenderingEngine().getName());
+    }
+
+    public boolean isTrident()
+    {
+        return getRenderingEngine() != null && "Trident".equals(getRenderingEngine().getName());
+    }
+
+    public boolean isBlink()
+    {
+        return getRenderingEngine() != null && "Blink".equals(getRenderingEngine().getName());
+    }
+
+    public boolean isEdgeHTML()
+    {
+        return getRenderingEngine() != null && "EdgeHTML".equals(getRenderingEngine().getName());
+    }
+
+    public boolean isPresto()
+    {
+        return getRenderingEngine() != null && "Presto".equals(getRenderingEngine().getName());
+    }
+
+    /* Specific userAgent.getBrowser()s */
+
+    public boolean isChrome()
+    {
+        return getBrowser() != null && ("Chrome".equals(getBrowser().getName()) || "Chromium".equals(getBrowser().getName()));
+    }
+
+    public boolean isMSIE()
+    {
+        return getBrowser() != null && "MSIE".equals(getBrowser().getName());
+    }
+
+    public boolean isFirefox()
+    {
+        return getBrowser() != null && ("Firefox".equals(getBrowser().getName()) || "Iceweasel".equals(getBrowser().getName()));
+    }
+
+    public boolean isOpera()
+    {
+        return getBrowser() != null && ("Opera".equals(getBrowser().getName()) || "Opera Mobile".equals(getBrowser().getName()));
+    }
+
+    public boolean isSafari()
+    {
+        return getBrowser() != null && "Safari".equals(getBrowser().getName());
+    }
+
+    public boolean isNetscape()
+    {
+        return getBrowser() != null && "Netscape".equals(getBrowser().getName());
+    }
+
+    public boolean isKonqueror()
+    {
+        return getBrowser() != null && "Konqueror".equals(getBrowser().getName());
+    }
+
+    public boolean isLinks()
+    {
+        return getBrowser() != null && "Links".equals(getBrowser().getName());
+    }
+
+    public boolean isMozilla()
+    {
+        return getBrowser() != null && "Mozilla".equals(getBrowser().getName());
+    }
+
+    /* Operating System */
+
+    public boolean isWindows()
+    {
+        return getOperatingSystem() != null && getOperatingSystem().getName().startsWith("Windows");
+    }
+
+    public boolean isOSX()
+    {
+        return getOperatingSystem() != null && (getOperatingSystem().getName().equals("OS X") || getOperatingSystem().getName().equals("iOS"));
+    }
+
+    private static Set<String> linuxDistros = null;
+    static
+    {
+        linuxDistros = new HashSet<String>();
+        linuxDistros.add("Ubuntu");
+        linuxDistros.add("Debian");
+        linuxDistros.add("Red Hat");
+        linuxDistros.add("Fedora");
+        linuxDistros.add("Slackware");
+        linuxDistros.add("SUSE");
+        linuxDistros.add("ArchLinux");
+        linuxDistros.add("Gentoo");
+        linuxDistros.add("openSUSE");
+        linuxDistros.add("Manjaro");
+        linuxDistros.add("Mandriva");
+        linuxDistros.add("PCLinuxOS");
+        linuxDistros.add("CentOS");
+        linuxDistros.add("Tizen");
+        linuxDistros.add("Mint");
+        linuxDistros.add("StartOS");
+    }
+
+    public boolean isLinux()
+    {
+        return getOperatingSystem() != null && (getOperatingSystem().getName().startsWith("Linux") || linuxDistros.contains(getOperatingSystem().getName()));
+    }
+
+    public boolean isBSD()
+    {
+        return getOperatingSystem() != null && getOperatingSystem().getName().endsWith("BSD");
+    }
+
+    public boolean isUnix()
+    {
+        if (getOperatingSystem() != null)
+        {
+            String osname = getOperatingSystem().getName().toLowerCase();
+            return osname.indexOf("unix") != -1 || osname.equals("bsd") || osname.equals("sunos");
+        }
+        return false;
+    }
+
+    public boolean isAndroid()
+    {
+        return getOperatingSystem() != null && getOperatingSystem().getName().startsWith("Android");
+    }
+
+    public boolean isIOS()
+    {
+        if (getOperatingSystem() != null)
+        {
+            String osName = getOperatingSystem().getName();
+            return osName.startsWith("iOS") || osName.startsWith("iPhone") || osName.startsWith("iPad");
+        }
+        return false;
+    }
+
+    public boolean isSymbian()
+    {
+        return getOperatingSystem() != null && getOperatingSystem().getName().startsWith("Symb");
+    }
+
+    public boolean isBlackberry()
+    {
+        return getOperatingSystem() != null &&
+                (getOperatingSystem().getName().startsWith("BlackBerry") ||
+                        getOperatingSystem().getName().equals("PlayBook"));
+    }
+
+    /* Features */
+
+    /* Since support of those features is often partial, the sniffer returns true
+        when a consequent subset is supported. */
+
+    public boolean getCss3()
+    {
+        return isTrident() && getRenderingEngine().getMajorVersion() >= 9 ||
+                isEdgeHTML() ||
+                isGecko() && (getRenderingEngine().getMajorVersion() >=2 || getRenderingEngine().getMinorVersion() >= 9) ||
+                isWebKit() && getRenderingEngine().getMajorVersion() >= 85 ||
+                isKHTML() && (getRenderingEngine().getMajorVersion() >= 4 || getRenderingEngine().getMajorVersion() == 3 && getRenderingEngine().getMinorVersion() >= 4) ||
+                isPresto() && getRenderingEngine().getMajorVersion() >= 2;
+    }
+
+    public boolean getDom3()
+    {
+        return isEdgeHTML() ||
+                isTrident() && getRenderingEngine().getMajorVersion() >= 9 ||
+                isGecko() && (getRenderingEngine().getMajorVersion() >=2 || getRenderingEngine().getMinorVersion() >= 7) ||
+                isWebKit() && getRenderingEngine().getMajorVersion() >= 601;
+    }
+
+    /* Languages */
+
+    public String getPreferredLanguage()
+    {
+        if(preferredLanguage != null) return preferredLanguage;
+
+        parseAcceptLanguage();
+        if(languageRangesByQuality.size() == 0)
+        {
+            preferredLanguage = starLanguageRange; // may be null
+        }
+        else
+        {
+            List<List<String>> lists = new ArrayList<List<String>>(languageRangesByQuality.values());
+            Collections.reverse(lists);
+            for(List<String> lst : lists) // sorted by quality (treemap)
+            {
+                for(String l : lst)
+                {
+                    preferredLanguage = filterLanguageTag(l);
+                    if(preferredLanguage != null) break;
+                }
+                if(preferredLanguage != null) break;
+            }
+        }
+        // fallback
+        if(preferredLanguage == null)
+        {
+            preferredLanguage = filterLanguageTag(languagesFilter == null ? getLocale().getDisplayName() : languagesFilter.get(0));
+        }
+        // preferredLanguage should now never be null
+        assert(preferredLanguage != null);
+        return preferredLanguage;
+    }
+
+    public Locale getPreferredLocale()
+    {
+        return ConversionUtils.toLocale(getPreferredLanguage());
+    }
+
+    /* Helpers */
+
+    protected boolean test(String key)
+    {
+        return lowercaseUserAgentString.indexOf(key) != -1;
+    }
+
+    private void parseAcceptLanguage()
+    {
+        if(languageRangesByQuality != null)
+        {
+            // already done
+            return;
+        }
+
+        languageRangesByQuality = new TreeMap<Float,List<String>>();
+        StringTokenizer languageTokenizer = new StringTokenizer(acceptLanguage, ",");
+        while (languageTokenizer.hasMoreTokens())
+        {
+            String language = languageTokenizer.nextToken().trim();
+            int qValueIndex = language.indexOf(';');
+            if(qValueIndex == -1)
+            {
+                language = language.replace('-','_');
+                List<String> l = languageRangesByQuality.get(1.0f);
+                if(l == null)
+                {
+                    l = new ArrayList<String>();
+                    languageRangesByQuality.put(1.0f,l);
+                }
+                l.add(language);
+            }
+            else
+            {
+                String code = language.substring(0,qValueIndex).trim().replace('-','_');
+                String qval = language.substring(qValueIndex + 1).trim();
+                if("*".equals(qval))
+                {
+                    starLanguageRange = code;
+                }
+                else
+                {
+                    Matcher m = quality.matcher(qval);
+                    if(m.matches())
+                    {
+                        Float q = Float.valueOf(m.group(1));
+                        List<String> al = languageRangesByQuality.get(q);
+                        if(al == null)
+                        {
+                            al = new ArrayList<String>();
+                            languageRangesByQuality.put(q,al);
+                        }
+                        al.add(code);
+                    }
+                    else
+                    {
+                        LOG.error("BrowserTool: could not parse language quality value: {}", language);
+                    }
+                }
+            }
+        }
+    }
+
+    private String filterLanguageTag(String languageTag)
+    {
+        languageTag = languageTag.replace('-','_');
+        if(languagesFilter == null) return languageTag;
+        if(languagesFilter.contains(languageTag)) return languageTag;
+        if(languageTag.contains("_"))
+        {
+            String[] parts = languageTag.split("_");
+            if(languagesFilter.contains(parts[0])) return parts[0];
+        }
+        return null;
+    }
+}

Added: velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java?rev=1765879&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java (added)
+++ velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java Thu Oct 20 20:35:23 2016
@@ -0,0 +1,697 @@
+package org.apache.velocity.tools.view;
+
+import org.apache.velocity.tools.generic.FormatConfig;
+import static org.apache.velocity.tools.view.UAParser.UAEntity;
+
+
+@Deprecated
+public abstract class BrowserToolDeprecatedMethods extends FormatConfig
+{
+    public abstract UAEntity getBrowser();
+    public abstract UAEntity getRenderingEngine();
+    public abstract UAEntity getOperatingSystem();
+    public abstract boolean isMSIE();
+    public abstract boolean isNetscape();
+    public abstract boolean isOpera();
+    public abstract boolean isOSX();
+    public abstract boolean isGecko();
+    public abstract boolean isKonqueror();
+    public abstract boolean isSafari();
+    public abstract boolean isChrome();
+    public abstract boolean isLinks();
+    public abstract boolean isWindows();
+    public abstract boolean isMozilla();
+    public abstract boolean isFirefox();
+    public abstract boolean isLinux();
+    protected abstract boolean test(String str);
+
+    /**
+     * @deprecated use {@link #getBrowser()} version getters
+     */
+    @Deprecated
+    public String getVersion()
+    {
+        return getBrowser().getMajorVersion() + "." + getBrowser().getMinorVersion();
+    }
+
+    /**
+     * @deprecated use {@link #getBrowser()}.getMajorVersion()
+     */
+    @Deprecated
+    public int getMajorVersion()
+    {
+        return getBrowser().getMajorVersion();
+    }
+
+    /**
+     * @deprecated use {@link #getBrowser()}.getMinorVersion()
+     */
+    @Deprecated
+    public int getMinorVersion()
+    {
+        return getBrowser().getMinorVersion();
+    }
+
+    /**
+     * @deprecated use {@link #getRenderingEngine()} and version getters
+     */
+    @Deprecated
+    public String getGeckoVersion()
+    {
+        UAEntity renderingEngine = getRenderingEngine();
+        return
+                renderingEngine != null && "Gecko".equals(renderingEngine.getName()) ?
+                        renderingEngine.getMajorVersion() + "." + renderingEngine.getMinorVersion() :
+                        null;
+    }
+
+    /**
+     * @deprecated use {@link #getRenderingEngine()} and version getters
+     */
+    public int getGeckoMajorVersion()
+    {
+        UAEntity renderingEngine = getRenderingEngine();
+        return
+                renderingEngine != null && "Gecko".equals(renderingEngine.getName()) ?
+                        renderingEngine.getMajorVersion() :
+                        0;
+    }
+
+    /**
+     * @deprecated use {@link #getRenderingEngine()} version getters
+     */
+    public int getGeckoMinorVersion()
+    {
+        UAEntity renderingEngine = getRenderingEngine();
+        return
+                renderingEngine != null && "Gecko".equals(renderingEngine.getName()) ?
+                        renderingEngine.getMajorVersion() :
+                        0;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav2()
+    {
+        return isNetscape() && getBrowser().getMajorVersion() == 2;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav3()
+    {
+        return isNetscape() && getMajorVersion() == 3;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav4()
+    {
+        return isNetscape() && getMajorVersion() == 4;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav4up()
+    {
+        return isNetscape() && getMajorVersion() >= 4;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav45()
+    {
+        return isNetscape() && getMajorVersion() == 4 &&
+                getMinorVersion() == 5;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav45up()
+    {
+        return isNetscape() && getMajorVersion() >= 5 ||
+                getNav4() && getMinorVersion() >= 5;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNavgold()
+    {
+        return test("gold");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav6()
+    {
+        return isNetscape() && getMajorVersion() == 5; /* sic */
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getNav6up()
+    {
+        return isNetscape() && getMajorVersion() >= 5;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe()
+    {
+        return isMSIE();
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe3()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() < 4;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe4()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() == 4;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe4up()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() >= 4;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe5()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() == 5;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe5up()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() >= 5;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe55()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() == 5 && getBrowser().getMinorVersion() >= 5;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe55up()
+    {
+        return (getIe5() && getBrowser().getMinorVersion() >= 5) ||
+                (isMSIE() && getBrowser().getMajorVersion() >= 6);
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe6()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() == 6;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe6up()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() >= 6;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe7()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() == 7;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe7up()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() >= 7;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe8()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() == 8;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getIe8up()
+    {
+        return isMSIE() && getBrowser().getMajorVersion() >= 8;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getOpera3()
+    {
+        return isOpera() && getBrowser().getMajorVersion() == 3;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getOpera4()
+    {
+        return isOpera() && getBrowser().getMajorVersion() == 4;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getOpera5()
+    {
+        return isOpera() && getBrowser().getMajorVersion() == 5;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getOpera6()
+    {
+        return isOpera() && getBrowser().getMajorVersion() == 6;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getOpera7()
+    {
+        return isOpera() && getBrowser().getMajorVersion() == 7;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getOpera8()
+    {
+        return isOpera() && getBrowser().getMajorVersion() == 8;
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getOpera9()
+    {
+        return test("opera/9");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWin16()
+    {
+        return test("win16") || test("16bit") || test("windows 3") ||
+                test("windows 16-bit");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWin3x()
+    {
+        return test("win16") || test("windows 3") || test("windows 16-bit");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWin31()
+    {
+        return test("win16") || test("windows 3.1") || test("windows 16-bit");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWin95()
+    {
+        return test("win95") || test("windows 95");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWin98()
+    {
+        return test("win98") || test("windows 98");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWinnt()
+    {
+        return test("winnt") || test("windows nt") || test("nt4") || test("nt3");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWin2k()
+    {
+        return test("nt 5.0") || test("nt5");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWinxp()
+    {
+        return test("nt 5.1");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getVista()
+    {
+        return test("nt 6.0");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getDotnet()
+    {
+        return test(".net clr");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWinme()
+    {
+        return test("win 9x 4.90");
+    }
+
+    /**
+     * @deprecated
+     */
+    public boolean getWin32()
+    {
+        return getWin95() || getWin98() || getWinnt() || getWin2k() ||
+                getWinxp() || getWinme() || test("win32");
+    }
+
+    /**
+     * @deprecated use isOSX()
+     */
+    @Deprecated
+    public boolean isMac()
+    {
+        return isOSX();
+    }
+
+    @Deprecated
+    public boolean isMac68k()
+    {
+        return isMac() && (test("68k") || test("68000"));
+    }
+
+    @Deprecated
+    public boolean isMacppc()
+    {
+        return isMac() && (test("ppc") || test("powerpc"));
+    }
+
+    @Deprecated
+    public boolean isAmiga()
+    {
+        return test("amiga");
+    }
+
+    @Deprecated
+    public boolean isEmacs()
+    {
+        return test("emacs");
+    }
+
+    @Deprecated
+    public boolean isOs2()
+    {
+        return test("os/2");
+    }
+
+    @Deprecated
+    public boolean isSun()
+    {
+        return test("sun");
+    }
+
+    @Deprecated
+    public boolean isSun4()
+    {
+        return test("sunos 4");
+    }
+
+    @Deprecated
+    public boolean isSun5()
+    {
+        return test("sunos 5");
+    }
+
+    @Deprecated
+    public boolean isSuni86()
+    {
+        return isSun() && test("i86");
+    }
+
+    @Deprecated
+    public boolean isIrix()
+    {
+        return test("irix");
+    }
+
+    @Deprecated
+    public boolean isIrix5()
+    {
+        return test("irix5");
+    }
+
+    @Deprecated
+    public boolean isIrix6()
+    {
+        return test("irix6");
+    }
+
+    @Deprecated
+    public boolean isHpux()
+    {
+        return test("hp-ux");
+    }
+
+    @Deprecated
+    public boolean isHpux9()
+    {
+        return isHpux() && test("09.");
+    }
+
+    @Deprecated
+    public boolean isHpux10()
+    {
+        return isHpux() && test("10.");
+    }
+
+    @Deprecated
+    public boolean isAix()
+    {
+        return test("aix");
+    }
+
+    @Deprecated
+    public boolean isAix1()
+    {
+        return test("aix 1");
+    }
+
+    @Deprecated
+    public boolean isAix2()
+    {
+        return test("aix 2");
+    }
+
+    @Deprecated
+    public boolean isAix3()
+    {
+        return test("aix 3");
+    }
+
+    @Deprecated
+    public boolean isAix4()
+    {
+        return test("aix 4");
+    }
+
+    @Deprecated
+    public boolean isSco()
+    {
+        return test("sco") || test("unix_sv");
+    }
+
+    @Deprecated
+    public boolean isUnixware()
+    {
+        return test("unix_system_v");
+    }
+
+    @Deprecated
+    public boolean isMpras()
+    {
+        return test("ncr");
+    }
+
+    @Deprecated
+    public boolean isReliant()
+    {
+        return test("reliantunix");
+    }
+
+    @Deprecated
+    public boolean isDec()
+    {
+        return test("dec") || test("osf1") || test("delalpha") ||
+                test("alphaserver") || test("ultrix") || test("alphastation");
+    }
+
+    @Deprecated
+    public boolean isSinix()
+    {
+        return test("sinix");
+    }
+
+    @Deprecated
+    public boolean isFreebsd()
+    {
+        return test("freebsd");
+    }
+
+    @Deprecated
+    public boolean isBsd()
+    {
+        return test("bsd");
+    }
+
+    @Deprecated
+    public boolean isX11()
+    {
+        return test("x11");
+    }
+
+    @Deprecated
+    public boolean isVMS()
+    {
+        return test("vax") || test("openvms");
+    }
+
+    @Deprecated
+    public boolean getCss()
+    {
+        return (isMSIE() && getBrowser().getMajorVersion() >= 4) ||
+                (isNetscape() && getBrowser().getMajorVersion() >= 4) ||
+                isGecko() ||
+                isKonqueror() ||
+                (isOpera() && getBrowser().getMajorVersion() >= 3) ||
+                isSafari() ||
+                isChrome() ||
+                isLinks();
+    }
+
+    @Deprecated
+    public boolean getCss1()
+    {
+        return getCss();
+    }
+
+    @Deprecated
+    public boolean getCss2()
+    {
+        int maj = getBrowser() != null ? getBrowser().getMajorVersion() : 0;
+        return
+                (isOSX() && maj >= 5) ||
+                        (isWindows() && getOperatingSystem().getMajorVersion() >= 6) ||
+                        isGecko() || // && version >= ?
+                        (isOpera() && maj >= 4) ||
+                        (isSafari() && maj >= 2) ||
+                        (isKonqueror() && maj >= 2) ||
+                        isChrome();
+    }
+
+    @Deprecated
+    public boolean getDom0()
+    {
+        int maj = getBrowser() != null ? getBrowser().getMajorVersion() : 0;
+        return (isMSIE() && maj >= 3) ||
+                (isNetscape() && maj >= 2) ||
+                (isOpera() && maj >= 3) ||
+                isGecko() ||
+                isSafari() ||
+                isChrome() ||
+                isKonqueror();
+    }
+
+    @Deprecated
+    public boolean getDom1()
+    {
+        int maj = getBrowser() != null ? getBrowser().getMajorVersion() : 0;
+        return (isMSIE() && getBrowser().getMajorVersion() >= 5) ||
+                isGecko() ||
+                (isSafari() && maj >= 2) ||
+                (isOpera() && maj >= 4) ||
+                (isKonqueror() && maj >= 2)
+                || isChrome();
+    }
+
+    @Deprecated
+    public boolean getDom2()
+    {
+        int maj = getBrowser() != null ? getBrowser().getMajorVersion() : 0;
+        return (isMSIE() && maj >= 6) ||
+                (isMozilla() && maj >= 5.0) ||
+                (isOpera() && maj >= 7) ||
+                isFirefox() ||
+                isChrome();
+    }
+
+    @Deprecated
+    public boolean getJavascript()
+    {
+        return getDom0(); // good approximation
+    }
+}

Added: velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/UAParser.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/UAParser.java?rev=1765879&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/UAParser.java (added)
+++ velocity/tools/trunk/velocity-tools-browser/src/main/java/org/apache/velocity/tools/view/UAParser.java Thu Oct 20 20:35:23 2016
@@ -0,0 +1,590 @@
+package org.apache.velocity.tools.view;
+
+import org.apache.devicemap.DeviceMapClient;
+import org.apache.devicemap.DeviceMapFactory;
+import org.apache.devicemap.data.Device;
+import org.apache.devicemap.loader.LoaderOption;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.ClassUtils;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class UAParser
+{
+    public UAParser(Logger log)
+    {
+        this.LOG = log;
+    }
+
+    protected org.slf4j.Logger LOG = null;
+    
+    public static class UAEntity
+    {
+        private String name = null;
+        private int majorVersion = -1;
+        private int minorVersion = -1;
+
+        public UAEntity(String n, String maj, String min)
+        {
+            name = n;
+            try
+            {
+                majorVersion = maj == null ? -1 : Integer.valueOf(maj);
+                minorVersion = maj == null ? -1 : Integer.valueOf(min);
+            }
+            catch (NumberFormatException nfe)
+            {
+                majorVersion = minorVersion = -1;
+            }
+        }
+
+        public String getName()
+        {
+            return name;
+        }
+
+        public int getMajorVersion()
+        {
+            return majorVersion;
+        }
+
+        public int getMinorVersion()
+        {
+            return minorVersion;
+        }
+    }
+
+    /* device */
+    public enum DeviceType
+    {
+        DESKTOP,
+        MOBILE,
+        TABLET,
+        ROBOT,
+        UNKNOWN
+    };
+
+    private static DeviceMapClient deviceMapClient = null;
+
+    static
+    {
+        try
+        {
+            deviceMapClient = DeviceMapFactory.getClient(LoaderOption.JAR);
+        }
+        catch (Exception e)
+        {
+            /* no logging context yet, but a warning will be displayed at tool initialization */
+        }
+    }
+
+    /**
+     * devicemap classification
+     */
+    private Device doDeviceMapParsing(String userAgentString)
+    {
+        Device device = null;
+        if (deviceMapClient != null)
+        {
+            try
+            {
+                /* classify the userAgent */
+                device = deviceMapClient.classifyDevice(userAgentString.toLowerCase());
+            }
+            catch (Exception e)
+            {
+                LOG.error("BrowerTool: exception while querying DeviceMap:", e);
+            }
+        }
+        return device;
+    }
+
+    private static Map<String,String> browserTranslationMap = null;
+    private static Map<String,String> osTranslationMap = null;
+    static
+    {
+        browserTranslationMap = new HashMap<String,String>();
+        browserTranslationMap.put("navigator","Netscape");
+        browserTranslationMap.put("nokia5250", "Nokia Browser");
+
+        osTranslationMap = new HashMap<String,String>();
+        osTranslationMap.put("android", "Android");
+        osTranslationMap.put("bada", "Bada");
+        osTranslationMap.put("bb10", "BlackBerry");
+        osTranslationMap.put("blackberry", "BlackBerry");
+        osTranslationMap.put("cros", "Chrome OS");
+        osTranslationMap.put("fxos", "Firefox OS");
+        osTranslationMap.put("hpwos", "WebOS");
+        osTranslationMap.put("ipad", "iOS");
+        osTranslationMap.put("iphone", "iOS");
+        osTranslationMap.put("ipod", "iOS");
+        osTranslationMap.put("kfthwi", "Kindle");
+        osTranslationMap.put("kftt", "Kindle");
+        osTranslationMap.put("mac os x", "OS X");
+        osTranslationMap.put("macos x", "OS X");
+        osTranslationMap.put("remi", "Fedora");
+        osTranslationMap.put("rhel", "Red Hat");
+        osTranslationMap.put("series40", "Symbian");
+        osTranslationMap.put("series60", "Symbian");
+        osTranslationMap.put("series80", "Symbian");
+        osTranslationMap.put("series90", "Symbian");
+        osTranslationMap.put("series 40", "Symbian");
+        osTranslationMap.put("series 60", "Symbian");
+        osTranslationMap.put("series 80", "Symbian");
+        osTranslationMap.put("series 90", "Symbian");
+        osTranslationMap.put("symbianos", "Symbian");
+        osTranslationMap.put("symbos", "Symbian");
+        osTranslationMap.put("tigeros", "OS X");
+        osTranslationMap.put("tizen", "Tizen");
+        osTranslationMap.put("tt", "Android");
+        osTranslationMap.put("unix", "Unix");
+        osTranslationMap.put("unix bsd", "BSD");
+        osTranslationMap.put("unixware", "Unix");
+        osTranslationMap.put("webos", "WebOS");
+        osTranslationMap.put("windows nt", "Windows");
+    }
+
+    public static class UserAgent
+    {
+        private DeviceType deviceType = null;
+        private UAEntity operatingSystem = null;
+        private UAEntity browser = null;
+        private UAEntity renderingEngine = null;
+        private boolean isRobot = false;
+
+        public DeviceType getDeviceType() { return deviceType; }
+        public UAEntity getOperatingSystem() { return operatingSystem; }
+        public UAEntity getBrowser() { return browser; }
+        public UAEntity getRenderingEngine() { return renderingEngine; }
+
+        protected void setOperatingSystem(String entity, String major, String minor)
+        {
+            if (entity.equals("Series") && major != null)
+            {
+                entity += major;
+                major = minor = null;
+            }
+            String alternate = osTranslationMap.get(entity.toLowerCase());
+            if (alternate != null) entity = alternate;
+            if (entity.startsWith("BlackBerry")) { entity = "BlackBerry"; }
+            operatingSystem = new UAEntity(entity, major, minor);
+        }
+
+        protected void setBrowser(String entity, String major, String minor)
+        {
+            if (deviceType == DeviceType.ROBOT) return;
+            String alternate = browserTranslationMap.get(entity.toLowerCase());
+            if (alternate != null) { entity = alternate; }
+            if ("Navigator".equals(entity)) { entity = "Netscape"; }
+            browser = new UAEntity(entity, major, minor);
+            if ("Edge".equals(entity) && renderingEngine == null) { renderingEngine = new UAEntity("EdgeHTML", major, minor); }
+        }
+
+        protected void setRenderingEngine(String entity, String major, String minor)
+        {
+            if (deviceType == DeviceType.ROBOT) return;
+            renderingEngine = new UAEntity(entity, major, minor);
+        }
+        
+        protected void setDeviceType(DeviceType deviceType)
+        {
+            this.deviceType = deviceType;
+            if (deviceType == DeviceType.ROBOT)
+            {
+                browser = renderingEngine = null;
+            }
+        }
+    }
+
+    private enum EntityType
+    {
+        BROWSER,
+        BROWSER_OS,
+        ENGINE,
+        FORCE_BROWSER,
+        FORCE_OS,
+        IGNORE,
+        MAYBE_BROWSER,
+        MAYBE_OS,
+        MAYBE_ROBOT,
+        MERGE,
+        MERGE_OR_BROWSER,
+        MERGE_OR_OS,
+        OS,
+        ROBOT
+    };
+
+    private static final String UA_KEYWORDS = "/org/apache/velocity/tools/view/ua-keywords.txt";
+
+    private static Map<String, EntityType> entityMap = new HashMap<String, EntityType>();
+
+    static
+    {
+        try
+        {
+            Properties properties = new Properties();
+            InputStream stream = ClassUtils.getResourceAsStream(UA_KEYWORDS, BrowserTool.class);
+            if (stream == null) { throw new IOException("could not find org.apache.velocity.tools.view.ua-keywords.txt resource"); }
+            BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+            String line = null;
+            int num = 1;
+            while ((line = reader.readLine()) != null)
+            {
+                line = line.trim();
+                if (line.length() == 0 || line.startsWith("#")) { ++num; continue; }
+                int eq = line.indexOf('=');
+                if (eq == -1) { throw new IOException("invalid line format in ua-keywords.txt at line " + num); }
+                String key = line.substring(0, eq);
+                EntityType value = EntityType.valueOf(line.substring(eq + 1).toUpperCase());
+                entityMap.put(key, value);
+                ++num;
+            }
+        }
+        catch(Exception e)
+        {
+            throw new VelocityException("BrowserTool: static initialization failed", e);
+        }
+    }
+
+    private static final String nonMergeSep = "(;/)";
+
+    private static Pattern versionPattern = Pattern.compile(
+            /* entity name */
+            "([a-z]+(?:(?=[;()@]|$)|(?:[0-9]+(?!\\.)[a-z]*)|(?:[!_+.\\-][a-z]+)+|(?=[/ ,\\-:0-9+!_=])))" +
+            /* potential version */
+                    "(?:(?:[/ ,\\-:+_=])?(?:v?(\\d+)(?:\\.(\\d+))?[a-z+]*)?)",
+            Pattern.CASE_INSENSITIVE);
+
+    private static boolean isRobotToken(String token)
+    {
+        token = token.toLowerCase();
+        return token.endsWith("bot") || token.endsWith("crawler") || token.endsWith("spider") || token.endsWith("agent") || token.endsWith("validator");
+    }
+
+    /* the big hairy parsing method */
+    public UserAgent parseUserAgent(String userAgentString)
+    {
+        UserAgent ua = null;
+        try
+        {
+            ua = new UserAgent();
+
+            Device device = doDeviceMapParsing(userAgentString);
+            if (Boolean.valueOf(device.getAttribute("is_robot")))
+            {
+                ua.setDeviceType(DeviceType.ROBOT);
+            }
+            else if (Boolean.valueOf(device.getAttribute("is_tablet")))
+            {
+                ua.setDeviceType(DeviceType.TABLET);
+            }
+            else if (Boolean.valueOf(device.getAttribute("is_wireless_device")))
+            {
+                ua.setDeviceType(DeviceType.MOBILE);
+            }
+            else if (Boolean.valueOf(device.getAttribute("is_desktop")))
+            {
+                ua.setDeviceType(DeviceType.DESKTOP);
+            }
+            else
+            {
+                ua.setDeviceType(DeviceType.UNKNOWN);
+            }
+
+            if ("Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT Build/IML74K) AppleWebKit/535.19 (KHTML, like Gecko) Silk/2.1 Mobile Safari/535.19 Silk-Accelerated=true".equals(userAgentString))
+            {
+                LOG.debug("breakpoint");
+            }
+
+            Matcher matcher = versionPattern.matcher(userAgentString);
+            String merge = null;
+            EntityType mergeTarget = null;
+            boolean maybeBrowser = true;
+            boolean maybeOS = true;
+            boolean maybeRobot = false;
+            boolean forcedBrowser = false;
+            boolean forcedOS = false;
+            while (matcher.find())
+            {
+                String entity = matcher.group(1);
+                String major = matcher.group(2);
+                String minor = matcher.group(3);
+                char next = userAgentString.length() == matcher.end(1) ? ';' : userAgentString.charAt(matcher.end(1));
+                if (entity != null)
+                {
+                    if (merge != null)
+                    {
+                        String merged = merge + " " + entity;
+                        if (mergeTarget == null)
+                        {
+                            entity = merged;
+                        }
+                        else
+                        {
+                            EntityType mergedType = entityMap.get(merged.toLowerCase());
+                            if (mergedType != null && (
+                                    mergeTarget == mergedType ||
+                                            mergeTarget == EntityType.BROWSER && (mergedType == EntityType.MAYBE_BROWSER || mergedType == EntityType.FORCE_BROWSER) ||
+                                            mergeTarget == EntityType.OS && (mergedType == EntityType.MAYBE_OS || mergedType == EntityType.FORCE_OS)
+                            ))
+                            {
+                                entity = merged;
+                            }
+                            else
+                            {
+                                /* It means the merge failed, so revert it */
+                                switch (mergeTarget)
+                                {
+                                    case BROWSER:
+                                        ua.setBrowser(merge, null, null);
+                                        break;
+                                    case OS:
+                                        ua.setOperatingSystem(merge, null, null);
+                                        break;
+                                    default:
+                                        throw new VelocityException("BrowserTool: unhandled case!");
+                                }
+                            }
+                        }
+                        merge = null;
+                        mergeTarget = null;
+                    }
+                    EntityType entityType = entityMap.get(entity.toLowerCase());
+                    if (entityType != null)
+                    {
+                        switch (entityType)
+                        {
+                            case BROWSER:
+                            {
+                                if (ua.getBrowser() == null || !forcedBrowser)
+                                {
+                                    ua.setBrowser(entity, major, minor);
+                                    maybeBrowser = false;
+                                }
+                                break;
+                            }
+                            case BROWSER_OS:
+                            {
+                                ua.setBrowser(entity, major, minor);
+                                maybeBrowser = false;
+                                ua.setOperatingSystem(entity, major, minor);
+                                maybeOS = false;
+                                break;
+                            }
+                            case ENGINE:
+                            {
+                                if (!"KHTML".equals(entity) || major != null || ua.getRenderingEngine() == null)
+                                {
+                                    ua.setRenderingEngine(entity, major, minor);
+                                }
+                                break;
+                            }
+                            case FORCE_BROWSER:
+                            {
+                                if (!forcedBrowser)
+                                {
+                                    ua.setBrowser(entity, major, minor);
+                                    maybeBrowser = false;
+                                    forcedBrowser = true;
+                                }
+                                break;
+                            }
+                            case FORCE_OS:
+                            {
+                                if (!forcedOS)
+                                {
+                                    ua.setOperatingSystem(entity, major, minor);
+                                    maybeOS = false;
+                                    forcedOS = true;
+                                }
+                                break;
+                            }
+                            case IGNORE:
+                            {
+                                break;
+                            }
+                            case MAYBE_BROWSER:
+                            {
+                                if (maybeBrowser)
+                                {
+                                    if ("rv".equals(entity))
+                                    {
+                                        if (ua.getBrowser() != null && ua.getBrowser().getName().equals("Mozilla"))
+                                        {
+                                            entity = "Mozilla";
+                                        }
+                                        else
+                                        {
+                                            entity = null;
+                                        }
+                                    }
+                                    else if ("Version".equals(entity))
+                                    {
+                                        if (ua.getBrowser() != null && ua.getBrowser().getName().startsWith("Opera"))
+                                        {
+                                            entity = ua.getBrowser().getName();
+                                        }
+                                        else if (ua.getBrowser() != null && ua.getBrowser().getName().equals("Mozilla"))
+                                        {
+                                            entity = "Safari";
+                                        }
+                                        else
+                                        {
+                                            entity = null;
+                                        }
+                                    }
+                                    else if ("Safari".equals(entity) && ua.getBrowser() != null && "Safari".equals(ua.getBrowser().getName()))
+                                    {
+                                        entity = null;
+                                    }
+                                    if (entity != null)
+                                    {
+                                        ua.setBrowser(entity, major, minor);
+                                    }
+                                }
+                                break;
+                            }
+                            case MAYBE_OS:
+                            {
+                                if (maybeOS)
+                                {
+                                    ua.setOperatingSystem(entity, major, minor);
+                                }
+                                break;
+                            }
+                            case MAYBE_ROBOT:
+                            {
+                                maybeRobot = true;
+                                break;
+                            }
+                            case MERGE:
+                            {
+                                if (major == null)
+                                {
+                                    if (nonMergeSep.indexOf(next) == -1)
+                                    {
+                                        merge = merge == null ? entity : merge + " " + entity;
+                                    }
+                                    else
+                                    {
+                                        if ("Mobile".equals(entity) && ua.getOperatingSystem() != null)
+                                        {
+                                            if (ua.getOperatingSystem().getName().equals("Ubuntu"))
+                                            {
+                                                ua.setOperatingSystem("Ubuntu Mobile", String.valueOf(ua.getOperatingSystem().getMajorVersion()), String.valueOf(ua.getOperatingSystem().getMinorVersion()));
+                                            }
+                                            else if (ua.getOperatingSystem().getName().equals("Linux"))
+                                            {
+                                                ua.setOperatingSystem("Android", null, null);
+                                            }
+                                        }
+                                    }
+                                }
+                                break;
+                            }
+                            case MERGE_OR_BROWSER:
+                            {
+                                if (!forcedBrowser)
+                                {
+                                    if (major != null || nonMergeSep.indexOf(next) != -1)
+                                    {
+                                        ua.setBrowser(entity, major, minor);
+                                    }
+                                    else
+                                    {
+                                        merge = entity;
+                                        mergeTarget = EntityType.BROWSER;
+                                    }
+                                }
+                                break;
+                            }
+                            case MERGE_OR_OS:
+                            {
+                                if (!forcedOS)
+                                {
+                                    if (major != null || nonMergeSep.indexOf(next) != -1)
+                                    {
+                                        ua.setOperatingSystem(entity, major, minor);
+                                    }
+                                    else
+                                    {
+                                        merge = entity;
+                                        mergeTarget = EntityType.OS;
+                                    }
+                                }
+                                break;
+                            }
+                            case OS:
+                            {
+                                if (ua.getOperatingSystem() == null || !forcedOS)
+                                {
+                                    ua.setOperatingSystem(entity, major, minor);
+                                    maybeOS = false;
+                                }
+                                break;
+                            }
+                            case ROBOT:
+                            {
+                                ua.setDeviceType(DeviceType.ROBOT);
+                                break;
+                            }
+                            default:
+                            {
+                                throw new VelocityException("BrowserTool: unhandled case: " + entityType);
+                            }
+                        }
+                    } else
+                    {
+                        if (entity.startsWith("Linux") && !forcedOS)
+                        {
+                            ua.setOperatingSystem("Linux", null, null);
+                        }
+                        else if (isRobotToken(entity))
+                        {
+                            ua.setDeviceType(DeviceType.ROBOT);
+                        }
+                    }
+                }
+            }
+            if (ua.getOperatingSystem() != null && "Windows".equals(ua.getOperatingSystem().getName()) && (ua.getOperatingSystem().getMajorVersion() == 98 || ua.getOperatingSystem().getMajorVersion() == 2000))
+            {
+                if (ua.getOperatingSystem().getMajorVersion() == 98)
+                {
+                    ua.setOperatingSystem("Windows 98", "4", "90");
+                }
+                else if (ua.getOperatingSystem().getMajorVersion() == 2000)
+                {
+                    ua.setOperatingSystem("Windows 2000", "5", "0");
+                }
+            }
+            if (ua.getBrowser() == null)
+            {
+                if (maybeRobot)
+                {
+                    ua.setDeviceType(DeviceType.ROBOT);
+                }
+                else if (ua.getOperatingSystem() != null && ua.getOperatingSystem().getName().equals("Symbian"))
+                {
+                    ua.setBrowser("Nokia Browser", String.valueOf(ua.getOperatingSystem().getMajorVersion()), String.valueOf(ua.getOperatingSystem().getMinorVersion()));
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.error("BrowserTool: Could not parse browser for User-Agent: {}", userAgentString, e);
+            ua = null;
+        }
+        return ua;
+    }
+
+
+}

Added: velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/browser/tools.xml
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/browser/tools.xml?rev=1765879&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/browser/tools.xml (added)
+++ velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/browser/tools.xml Thu Oct 20 20:35:23 2016
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!--
+ 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.    
+-->
+
+<tools> 
+    <data type="boolean" key="BROWSER_TOOL_AVAILABLE" value="true"/>
+    <toolbox scope="session" createSession="false">
+        <tool class="org.apache.velocity.tools.view.BrowserTool"/>
+    </toolbox>
+</tools>

Added: velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt?rev=1765879&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt (added)
+++ velocity/tools/trunk/velocity-tools-browser/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt Thu Oct 20 20:35:23 2016
@@ -0,0 +1,397 @@
+# browsers
+abrowse=browser
+acoo=merge
+acoo browser=browser
+amaya=browser
+america=merge
+america online=merge
+america online browser=browser
+amigavoyager=browser
+aol=browser
+arora=browser
+avant=merge
+avant browser=browser
+avantbrowser=browser
+beonex=browser
+blazer=browser
+bolt=browser
+browserng=browser
+bunjalloo=browser
+camino=browser
+chrome=browser
+chromeplus=browser
+cometbird=browser
+conkeror=browser
+crazy=merge
+crazy browser=force_browser
+deepnet=merge
+deepnet explorer=browser
+dillo=browser
+dooble=browser
+doris=browser
+dorothy=browser
+edge=browser
+element=merge
+element browser=browser
+elinks=browser
+enigmafox=browser
+epiphany=browser
+escape=browser
+fennec=browser
+firebird=browser
+firefox=maybe_browser
+fireweb=merge
+fireweb navigator=browser
+flock=force_browser
+fluid=browser
+galaxy=browser
+galeon=browser
+gngr.info=browser
+gobrowser=browser
+granparadiso=browser
+greenbrowser=browser
+hana=browser
+ibrowse=browser
+icab=browser
+iceape=browser
+icecat=browser
+iceweasel=browser
+iemobile=browser
+inet=merge
+inet browser=browser
+irider=browser
+iris=browser
+iron=force_browser
+itunes=browser
+k-meleon=browser
+k-ninja=browser
+kapiko=browser
+kazehakase=browser
+kkman=browser
+kmlite=browser
+konqueror=browser
+like=merge
+like gecko=ignore
+links=browser
+lobo=browser
+lolifox=browser
+lunascape=browser
+lynx=browser
+maemo=merge
+maemo browser=browser
+maxthon=browser
+midori=browser
+minefield=browser
+minimo=browser
+mobile=merge
+mobile safari=maybe_browser
+mozilla=maybe_browser
+msie=maybe_browser
+namoroka=browser
+navigator=browser
+netnewswire=browser
+netpositive=browser
+netscape=browser
+netscape6=browser
+nokia5250=force_browser
+omniweb=browser
+opera=merge_or_browser
+opera mini=force_browser
+opera mobi=browser
+orca=browser
+oregano=browser
+palemoon=browser
+prism=browser
+puffin=browser
+qtweb=merge
+qtweb internet=merge
+qtweb internet browser=browser
+rekonq=browser
+retawq=browser
+rockmelt=force_browser
+rv=maybe_browser
+safari=maybe_browser
+samsungbrowser=browser
+seamonkey=browser
+semc-browser=browser
+shiretoko=browser
+silk=browser
+skyfire=browser
+sleipnir=browser
+slimbrowser=browser
+stainless=browser
+sundance=browser
+sunrise=browser
+sunrisebrowser=browser
+surf=browser
+sylera=force_browser
+teashark=browser
+teleca=browser
+thunderbird=browser
+tencenttraveler=browser
+tenfourfox=browser
+theworld=browser
+ucbrowser=browser
+uzbl=browser
+version=maybe_browser
+vimprobable=browser
+vimprobable2=browser
+vonkeror=browser
+w3m=browser
+webpositive=force_browser
+weltweitimnetzbrowser=browser
+windows-media-player=robot
+wyzo=browser
+
+# robots (+everyone ending with 'bot', 'crawler', 'spider', 'agent' or 'validator')
+amaya=robot
+appengine-google=robot
+accoona-ai-agent=robot
+arachmo=robot
+b-l-i-t-z-b-o-t=robot
+baiduspider+=robot
+binget=robot
+bloglines=robot
+boitho.com-dc=robot
+cerberian=robot
+charlotte=robot
+cocoal.icio.us=browser
+cosmos=robot
+csscheck=robot
+curl=robot
+cynthia=robot
+dataparksearch=robot
+emailsiphon=robot
+feedfetcher-google=robot
+findlinks=robot
+googlebot-image=robot
+greatnews=robot
+gregarius=robot
+holmes=robot
+htdig=robot
+html=merge
+htmlparser=robot
+ia_archiver=robot
+ichiro=robot
+igdespyder=robot
+java=maybe_robot
+larbin=robot
+lftp=robot
+libwww=robot
+libwww-perl=robot
+linkwalker=robot
+link=robot
+linkexaminer=robot
+lwp-trivial=robot
+magpierss=robot
+mediapartners-google=robot
+metauri=robot
+mnogosearch=robot
+morning=merge
+morning paper=robot
+mvaclient=robot
+netresearchserver=robot
+netseer=merge
+netseer crawler=robot
+newsgator=robot
+nfreader=robot
+ng-search=robot
+nitro=robot
+notifixious=robot
+nusearch=merge
+nutchcvs=robot
+nymesis=robot
+obot=robot
+oegp=robot
+offline=merge
+offline explorer=robot
+orbiter=robot
+p3p=robot
+peach=robot
+peew=robot
+php=robot
+pompos=robot
+postpost=robot
+pxyscand=robot
+pycurl=robot
+python-urllib=robot
+qseero=robot
+radian=robot
+sbider=robot
+scoutjet=robot
+scrubby=robot
+searchsight=robot
+semanticdiscovery=robot
+shopwiki=robot
+sitebar=robot
+snappy=robot
+snoopy=robot
+sosospider+=robot
+sqworm=robot
+stackrambler=robot
+susie=robot
+teoma=robot
+tineye=robot
+truwogps=robot
+updated=robot
+universalfeedparser=robot
+urd-magpie=robot
+control=robot
+vagabondo=robot
+vortex=robot
+voyager=robot
+vyu2=robot
+w3c-checklink=robot
+web=merge
+web downloader=robot
+webcapture=robot
+webcollage=robot
+webcopier=robot
+webis=robot
+websquash.com=robot
+webzip=robot
+wget=robot
+womlpefactory=robot
+yacy=robot
+yahoo=merge
+yahoo slurp=robot
+yahooseeker=robot
+yahooseeker-testing=robot
+yandeximages=robot
+yeti=robot
+yooglifetchagent=robot
+zao=robot
+zeller=robot
+zyborg=robot
+
+# rendering engines
+applewebkit=engine
+blink=engine
+khtml=engine
+martha=engine
+presto=engine
+prince=engine
+trident=engine
+
+# operating systems
+amigaos=os
+android=os
+bada=force_os
+bb10=force_os
+beos=os
+blackberry=force_os
+blackberry8100=force_os
+blackberry8300=force_os
+blackberry8520=force_os
+blackberry8700=force_os
+blackberry9100=force_os
+blackberry9300=force_os
+blackberry9320=force_os
+blackberry9360=force_os
+blackberry9380=force_os
+blackberry9500=force_os
+blackberry9700=force_os
+blackberry9780=force_os
+blackberry9790=force_os
+blackberry9860=force_os
+blackberry9900=force_os
+bsd=os
+centos=os
+cros=force_os
+cpu=merge
+cpu like=merge
+cpu like mac=merge
+cpu like mac os=merge
+cpu like mac os x=ignore
+cpu like os=merge
+cpu like os x=ignore
+cpu os=merge
+cpu os x=ignore
+darwin=os
+debian=os
+elementary=merge
+elementary os=force_os
+fedora=force_os
+remi=force_os
+freebsd=os
+fxos=force_os
+gentoo=force_os
+googletv=os
+haiku=os
+hp-ux=os
+hpwos=os
+ios=force_os
+ipad=maybe_os
+iphone=maybe_os
+ipod=maybe_os
+kfthwi=os
+kftt=os
+kindle=os
+like mac=merge
+like mac os=merge
+like mac os x=ignore
+like os=merge
+like os x=ignore
+linux=maybe_os
+mint=os
+tizen=os
+mac=merge
+mac os=merge
+mac os x=maybe_os
+macos=merge
+macos x=maybe_os
+mandriva=force_os
+meego=force_os
+netbsd=os
+newstockrom=os
+nintendo=merge
+nintendo ds=os
+nokiae=os
+openbsd=os
+os=merge
+os x=os
+palm=merge
+palm os=os
+palmos=os
+palmsource=os
+playbook=os
+playstation=browser_os
+psp=browser_os
+red=merge
+red hat=os
+rhel=os
+risc=merge
+risc os=os
+sailfish=force_os
+slackware=force_os
+sonyericssonk=os
+suse=os
+series=force_os
+series40=force_os
+series60=force_os
+series80=force_os
+series90=force_os
+startos=os
+sunos=os
+supergamer=os
+syllable=os
+symbian=os
+symbianos=force_os
+symbos=os
+tigeros=os
+tt=os
+ubuntu=os
+unicos=merge
+unicos lclinux=os
+unix=merge_or_os
+unix bsd=os
+unixware=os
+webos=os
+wii=merge
+wii libnup=browser_os
+windows=merge_or_os
+windows nt=os
+windows xp=os
+windows phone=os
+windows mobile=os
+xbox=os

Added: velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java?rev=1765879&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java (added)
+++ velocity/tools/trunk/velocity-tools-browser/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java Thu Oct 20 20:35:23 2016
@@ -0,0 +1,155 @@
+package org.apache.velocity.tools.view;
+
+/*
+ * 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.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static org.apache.velocity.tools.view.UAParser.UAEntity;
+
+import org.apache.velocity.tools.generic.MockLogger;
+import org.junit.Test;
+
+/**
+ * <p>Tests for BrowserTool</p>
+ *
+ * @author Claude Brisson
+ * @since VelocityTools 3.0
+ * @version $Id$
+ */
+public class BrowserToolTests {
+
+    private static final String TEST_OUTPUT_DIR = System.getProperty("test.output.dir");
+
+    protected Map<String, String> readUAs(String filename) throws Exception
+    {
+        Map result = new TreeMap<String, String>();
+        BufferedReader reader = new BufferedReader(new FileReader(TEST_OUTPUT_DIR + "/" + filename));
+        String line;
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.length() == 0) continue;
+            int hash = line.indexOf("#");
+            if (hash == -1) continue;
+            result.put(line.substring(hash + 2), line.substring(0, hash - 1));
+        }
+        return result;
+    }
+
+    protected void checkBrowsers(BrowserTool tool, Map<String, String> uas)
+    {
+        int num = 0;
+        for (Map.Entry<String, String> entry : uas.entrySet())
+        {
+            ++num;
+            String expected = entry.getValue();
+            String ua = entry.getKey();
+            tool.setUserAgentString(ua);
+            UAEntity browser = tool.getBrowser();
+            StringBuilder builder = new StringBuilder();
+
+            assertNotNull("entry " + num + "/" + uas.size()+": browser was not detected: browser={"+expected+"}, ua={"+ua+"}", browser);
+            builder.append(browser.getName());
+            if (browser.getMajorVersion() != -1)
+            {
+                builder.append(' ');
+                builder.append(browser.getMajorVersion());
+                if (browser.getMinorVersion() != -1)
+                {
+                    builder.append('.');
+                    builder.append(browser.getMinorVersion());
+                }
+            }
+            String found = builder.toString();
+            assertTrue("entry " + num + "/" + uas.size()+": wrong browser detected: browser={"+expected+"}, found={"+found+"}, ua={"+ua+"}", expected.toLowerCase().startsWith(found.toLowerCase()));
+        }
+    }
+
+    protected void checkRobots(BrowserTool tool, Map<String, String> uas)
+    {
+        int num = 0;
+        for (Map.Entry<String, String> entry : uas.entrySet())
+        {
+            ++num;
+            String robot = entry.getValue();
+            String ua = entry.getKey();
+            tool.setUserAgentString(ua);
+            assertTrue("entry " + num + "/" + uas.size()+": robot not detected: robot={"+robot+"}, ua={"+ua+"}", tool.isRobot());
+        }
+    }
+
+    protected void checkOperatingSystems(BrowserTool tool, Map<String, String> uas)
+    {
+        int num = 0;
+        for (Map.Entry<String, String> entry : uas.entrySet())
+        {
+            ++num;
+            String expected = entry.getValue();
+            String ua = entry.getKey();
+            tool.setUserAgentString(ua);
+            UAEntity os = tool.getOperatingSystem();
+            assertNotNull("entry " + num + "/" + uas.size()+": operating system not detected: os={"+expected+"}, ua={" + ua + "}", os);
+            assertEquals("entry " + num + "/" + uas.size()+": operating system not correctly detected: os={" + expected + "}, found={" + os.getName() + "}, ua={"+ua+"}", expected.toLowerCase(), os.getName().toLowerCase());
+        }
+    }
+
+    public @Test void ctorBrowserTool() throws Exception
+    {
+        try
+        {
+            new BrowserTool();
+        }
+        catch (Exception e)
+        {
+            fail("Constructor 'BrowserTool()' failed due to: " + e);
+        }
+    }
+
+    public @Test void testBrowserParsing() throws Exception
+    {
+        BrowserTool tool = new BrowserTool();
+        tool.setLog(new MockLogger(false, false));
+        Map uas = readUAs("browsers.txt");
+        checkBrowsers(tool, uas);
+    }
+
+    public @Test void testRobotParsing() throws Exception
+    {
+        BrowserTool tool = new BrowserTool();
+        tool.setLog(new MockLogger(false, false));
+        Map uas = readUAs("robots.txt");
+        checkRobots(tool, uas);
+    }
+
+    public @Test void testOperatingSystemParsing() throws Exception
+    {
+        BrowserTool tool = new BrowserTool();
+        tool.setLog(new MockLogger(false, false));
+        Map uas = readUAs("operating_systems.txt");
+        checkOperatingSystems(tool, uas);
+    }
+}