velocity-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cbris...@apache.org
Subject svn commit: r1768141 [1/4] - in /velocity/tools/trunk: velocity-tools-browser/ velocity-tools-view/src/main/java/org/apache/velocity/tools/view/ velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/ velocity-tools-view/src/test/java/or...
Date Sat, 05 Nov 2016 02:11:49 GMT
Author: cbrisson
Date: Sat Nov  5 02:11:49 2016
New Revision: 1768141

URL: http://svn.apache.org/viewvc?rev=1768141&view=rev
Log:
[tools] and reincorporate the BrowserTool in velocity-tools-view after its little escape, since it hasn't any more external dependency

Added:
    velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
    velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java
    velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/UAParser.java
    velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt
    velocity/tools/trunk/velocity-tools-view/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java
    velocity/tools/trunk/velocity-tools-view/src/test/resources/user-agents/
    velocity/tools/trunk/velocity-tools-view/src/test/resources/user-agents/browsers.txt
    velocity/tools/trunk/velocity-tools-view/src/test/resources/user-agents/devices.txt
    velocity/tools/trunk/velocity-tools-view/src/test/resources/user-agents/operating_systems.txt
Removed:
    velocity/tools/trunk/velocity-tools-browser/
Modified:
    velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/tools.xml

Added: velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserTool.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserTool.java?rev=1768141&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserTool.java (added)
+++ velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserTool.java Sat Nov  5 02:11:49 2016
@@ -0,0 +1,617 @@
+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.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 tv</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>
+ *     Notes on implementation:
+ *     <ul>
+ *         <li>The parsing algorithm is mainly empirical. Used rules are rather generic, so shouldn't need recent updates to be accurate, but accuracy remains far from guaranteed for new devices.</li>
+ *         <li>Parsing should be fast, as the parser only uses a single regex iteration on the user agent string.</li>
+ *         <li>Game consoles, e-readers, etc... are for now classified as <i>mobile</i> devices (but can sometimes be identified by their operating system).</li>
+ *         <li>Needless to say, the frontier between different device types can be very thin...</li>
+ *     </ul>
+ * </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://whichbrowser.net/data/</li>
+ * </ul>
+ * </p>
+ * <p>
+ *     TODO:
+ *     <ul>
+ *         <li>parse X-Wap-Profile header if present</li>
+ *         <li>parse X-Requested-With header if present</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 getDevice()
+    {
+        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 boolean isTV()
+    {
+        return userAgent == null && userAgent.getDeviceType() == DeviceType.TV;
+    }
+
+    /**
+     * @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-view/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java?rev=1768141&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java (added)
+++ velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/BrowserToolDeprecatedMethods.java Sat Nov  5 02:11:49 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-view/src/main/java/org/apache/velocity/tools/view/UAParser.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/UAParser.java?rev=1768141&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/UAParser.java (added)
+++ velocity/tools/trunk/velocity-tools-view/src/main/java/org/apache/velocity/tools/view/UAParser.java Sat Nov  5 02:11:49 2016
@@ -0,0 +1,595 @@
+package org.apache.velocity.tools.view;
+
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.shaded.commons.lang3.tuple.ImmutablePair;
+import org.apache.velocity.shaded.commons.lang3.tuple.Pair;
+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, in order of growing precedence */
+    public enum DeviceType
+    {
+        UNKNOWN,
+        DESKTOP,
+        MOBILE,
+        TABLET,
+        TV,
+        ROBOT
+    };
+
+
+    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("nokiae", "Nokia");
+        osTranslationMap.put("nokiax2", "Nokia");
+        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");
+        osTranslationMap.put("win98", "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)
+        {
+            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;
+        }
+    }
+
+    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, Pair<EntityType, DeviceType>> entityMap = new HashMap<String, Pair<EntityType, DeviceType>>();
+
+    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);
+                String val = line.substring(eq + 1).toUpperCase();
+                int coma = val.indexOf(',');
+                DeviceType device = null;
+                if (coma != -1)
+                {
+                    device = DeviceType.valueOf(val.substring(coma + 1));
+                    val = val.substring(0, coma);
+                }
+                EntityType entity = val.length() > 0 ? EntityType.valueOf(val) : null;
+                entityMap.put(key, new ImmutablePair<EntityType, DeviceType>(entity , device));
+                ++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();
+
+            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 separator = matcher.group(2);
+                String major = matcher.group(3);
+                String minor = matcher.group(4);
+                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
+                        {
+                            Pair<EntityType,DeviceType> pair = entityMap.get(merged.toLowerCase());
+                            EntityType mergedType = pair == null ? null : pair.getLeft();
+                            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;
+                    }
+                    Pair<EntityType, DeviceType> identity = entityMap.get(entity.toLowerCase());
+                    EntityType entityType = null;
+                    DeviceType deviceType = null;
+                    if (identity == null)
+                    {
+                        /* try again with major version appended */
+                        String alternateEntity = entity + separator + major;
+                        identity = entityMap.get((entity + separator + major).toLowerCase());
+                        if (identity != null)
+                        {
+                            entity = alternateEntity;
+                        }
+                    }
+                    if (identity != null)
+                    {
+                        entityType = identity.getLeft();
+                        deviceType = identity.getRight();
+                        DeviceType previousDeviceType = ua.getDeviceType();
+                        /* only overwrite device types of lower precedence */
+                        if (deviceType != null && (previousDeviceType == null || deviceType.compareTo(previousDeviceType) > 0))
+                        {
+                            ua.setDeviceType(deviceType); // may be overwritten by 'robot' device type
+                        }
+                    }
+                    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);
+                        }
+                        else if (entity.startsWith("MID") && !entity.startsWith("MIDP") && (ua.getDeviceType() == null || DeviceType.TABLET.compareTo(ua.getDeviceType()) > 0 ))
+                        {
+                            ua.setDeviceType(DeviceType.TABLET);
+                        }
+                        else if (entity.startsWith("CoolPad") && (ua.getDeviceType() == null || DeviceType.MOBILE.compareTo(ua.getDeviceType()) > 0 ))
+                        {
+                            ua.setDeviceType(DeviceType.MOBILE);
+                        }
+                        else if (entity.startsWith("LG-") && (ua.getDeviceType() == null || DeviceType.MOBILE.compareTo(ua.getDeviceType()) > 0 ))
+                        {
+                            ua.setDeviceType(DeviceType.MOBILE);
+                        }
+                        else if (entity.startsWith("SonyEricsson"))
+                        {
+                            ua.setDeviceType(DeviceType.MOBILE);
+                        }
+                    }
+                }
+            }
+            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 (ua.getDeviceType() == DeviceType.ROBOT || maybeRobot)
+                {
+                    ua.setBrowser("robot", "0", "0");
+                    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()));
+                }
+                else
+                {
+                    ua.setBrowser("unknown", "0", "0");
+                }
+            }
+            if (ua.getOperatingSystem() == null)
+            {
+                if (ua.getDeviceType() == DeviceType.ROBOT || maybeRobot)
+                {
+                    ua.setOperatingSystem("robot", "0", "0");
+                    ua.setDeviceType(DeviceType.ROBOT);
+                }
+                else
+                {
+                    ua.setOperatingSystem("unknown", "0", "0");
+                }
+            }
+            if (ua.getDeviceType() == null)
+            {
+                if (ua.getOperatingSystem() != null && "Android".equals(ua.getOperatingSystem().getName()))
+                {
+                    ua.setDeviceType(DeviceType.MOBILE);
+                }
+                else
+                {
+                    /* make Desktop the default device */
+                    ua.setDeviceType(DeviceType.DESKTOP);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.error("BrowserTool: Could not parse browser for User-Agent: {}", userAgentString, e);
+            ua = null;
+        }
+        return ua;
+    }
+}

Modified: velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/tools.xml
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/tools.xml?rev=1768141&r1=1768140&r2=1768141&view=diff
==============================================================================
--- velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/tools.xml (original)
+++ velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/tools.xml Sat Nov  5 02:11:49 2016
@@ -36,4 +36,7 @@
         <!-- move this to request scope -->
         <tool class="org.apache.velocity.tools.generic.ResourceTool"/>
     </toolbox>
+    <toolbox scope="session" createSession="false">
+        <tool class="org.apache.velocity.tools.view.BrowserTool"/>
+    </toolbox>
 </tools>

Added: velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt?rev=1768141&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt (added)
+++ velocity/tools/trunk/velocity-tools-view/src/main/resources/org/apache/velocity/tools/view/ua-keywords.txt Sat Nov  5 02:11:49 2016
@@ -0,0 +1,546 @@
+# format:
+# keyword=[<entity>][,<device>]
+# where <entity> is one of:
+# - browser
+# - browser_os: browser and operating system
+# - engine: rendering engine
+# - force_browser: browser, with greater precedence
+# - force_os: operating system, with greater precedence
+# - ignore: keyword to be ignored
+# - maybe_browser: browser, with lesser precedence
+# - maybe_os: operating system, with lesser precedence
+# - maybe_robot: robot, with lesser precedence
+# - merge: keyword to be merged to the next one
+# - merge_or_browser: browser if followed by a version number, otherwise merge to the next keyword
+# - merge_or_os: operating system if followed by a version number, otherwise merge to the next keyword
+# - os: operating system
+# - robot
+# and <device> is one of:
+# - desktop,
+# - mobile,
+# - tablet,
+# - tv,
+# - robot
+# (with a growing precedence)
+
+# browsers
+abrowse=browser,desktop
+acoo=merge
+acoo browser=browser,desktop
+amaya=browser
+america=merge
+america online=merge
+america online browser=browser,desktop
+amigavoyager=browser
+aol=browser,desktop
+arora=browser,desktop
+avant=merge
+avant browser=browser,desktop
+avantbrowser=browser,desktop
+beonex=browser,desktop
+blazer=browser,desktop
+bolt=browser,desktop
+browserng=browser,mobile
+bunjalloo=browser
+camino=browser,desktop
+chrome=browser
+chromeplus=browser
+cometbird=browser,desktop
+conkeror=browser,desktop
+crazy=merge
+crazy browser=force_browser,desktop
+deepnet=merge
+deepnet explorer=browser,desktop
+dillo=browser
+dooble=browser
+doris=browser
+dorothy=browser
+edge=browser,desktop
+element=merge
+element browser=browser,desktop
+elinks=browser,desktop
+enigmafox=browser,desktop
+epiphany=browser,desktop
+escape=browser,desktop
+fennec=browser
+firebird=browser
+firefox=maybe_browser
+fireweb=merge
+fireweb navigator=browser,desktop
+flock=force_browser,desktop
+fluid=browser,desktop
+galaxy=browser,mobile
+galeon=browser,desktop
+gngr.info=browser
+gobrowser=browser
+granparadiso=browser,desktop
+greenbrowser=browser,desktop
+hana=browser,desktop
+ibrowse=browser
+icab=browser,desktop
+iceape=browser,desktop
+icecat=browser,desktop
+iceweasel=browser,desktop
+iemobile=browser,mobile
+inet=merge
+inet browser=browser,mobile
+irider=browser,desktop
+iris=browser,desktop
+iron=force_browser
+itunes=browser
+k-meleon=browser
+k-ninja=browser,desktop
+kapiko=browser,desktop
+kazehakase=browser,desktop
+kkman=browser,desktop
+kmlite=browser,desktop
+konqueror=browser,desktop
+like=merge
+like gecko=ignore
+links=browser
+lobo=browser
+lolifox=browser,desktop
+lunascape=browser,desktop
+lynx=browser
+maemo=merge
+maemo browser=browser,mobile
+maxthon=browser
+midori=browser
+minefield=browser,desktop
+minimo=browser
+mobile=merge,mobile
+mobile safari=maybe_browser,mobile
+mozilla=maybe_browser
+msie=maybe_browser
+namoroka=browser,desktop
+navigator=browser
+netnewswire=browser,desktop
+netpositive=browser,desktop
+netscape=browser
+netscape6=browser
+nokia5250=force_browser,mobile
+omniweb=browser
+opera=merge_or_browser
+opera mini=force_browser
+opera mobi=browser,mobile
+orca=browser,desktop
+oregano=browser
+palemoon=browser,desktop
+prism=browser,desktop
+puffin=browser,mobile
+qtweb=merge
+qtweb internet=merge
+qtweb internet browser=browser,desktop
+rekonq=browser,desktop
+retawq=browser
+rockmelt=force_browser,desktop
+rv=maybe_browser
+safari=maybe_browser
+samsungbrowser=browser,mobile
+seamonkey=browser,desktop
+semc-browser=browser
+shiretoko=browser,desktop
+silk=browser,mobile
+skyfire=browser,desktop
+sleipnir=browser
+slimbrowser=browser,desktop
+stainless=browser,desktop
+sundance=browser
+sunrise=browser,desktop
+sunrisebrowser=browser,desktop
+surf=browser,desktop
+sylera=force_browser,desktop
+teashark=browser,desktop
+teleca=browser,desktop
+thunderbird=browser,desktop
+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,desktop
+
+# 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
+bing=robot
+binget=robot
+bingpreview=robot
+bloglines=robot
+boitho.com-dc=robot
+cerberian=robot
+charlotte=robot
+cocoal.icio.us=browser
+control=robot
+cosmos=robot
+csscheck=robot
+curl=robot
+cynthia=robot
+dataparksearch=robot
+emailsiphon=robot
+feedfetcher-google=robot
+findlinks=robot
+google=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
+link=merge
+link checker=robot
+link valet=robot
+link validity=robot
+linkwalker=robot
+linkexaminer=robot
+lwp-trivial=robot
+magpierss=robot
+mediapartners-google=robot
+metauri=robot
+mnogosearch=robot
+morning=merge
+morning paper=robot
+mrchrome=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
+reciprocal=merge
+reciprocal link=merge
+reciprocal link system=robot
+sbider=robot
+scoutjet=robot
+screenshot-generator=robot
+scrubby=robot
+searchsight=robot
+semanticdiscovery=robot
+seznam=robot
+shopwiki=robot
+sitebar=robot
+snappy=robot
+snoopy=robot
+sosospider+=robot
+sqworm=robot
+stackrambler=robot
+susie=robot
+teoma=robot
+tineye=robot
+tocrawl=robot
+truwogps=robot
+updated=robot
+universalfeedparser=robot
+urd-magpie=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
+xenu=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,desktop
+android=os
+bada=force_os
+bb10=force_os
+beos=os,desktop
+blackberry=force_os,mobile
+blackberry8100=force_os,mobile
+blackberry8300=force_os,mobile
+blackberry8520=force_os,mobile
+blackberry8530=force_os,mobile
+blackberry8700=force_os,mobile
+blackberry9100=force_os,mobile
+blackberry9300=force_os,mobile
+blackberry9320=force_os,mobile
+blackberry9360=force_os,mobile
+blackberry9380=force_os,mobile
+blackberry9500=force_os,mobile
+blackberry9700=force_os,mobile
+blackberry9780=force_os,mobile
+blackberry9790=force_os,mobile
+blackberry9860=force_os,mobile
+blackberry9900=force_os,mobile
+bsd=os,desktop
+centos=os,desktop
+cros=force_os,desktop
+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,desktop
+elementary=merge
+elementary os=force_os,desktop
+fedora=force_os,desktop
+remi=force_os
+freebsd=os,desktop
+fxos=force_os
+gentoo=force_os,desktop
+haiku=os
+hp-ux=os,desktop
+hpwos=os
+ios=force_os
+kindle=maybe_os,tablet
+like mac=merge
+like mac os=merge
+like mac os x=ignore
+like os=merge
+like os x=ignore
+linux=maybe_os
+mint=os,desktop
+tizen=os
+mac=merge
+mac os=merge
+mac os x=maybe_os,desktop
+macos=merge
+macos x=maybe_os
+mandriva=force_os
+meego=force_os
+netbsd=os
+newstockrom=os
+nintendo=merge
+nintendo ds=os
+nokiae=os,mobile
+openbsd=os
+os=merge
+os x=os
+palm=merge
+palm os=os
+palmos=os
+palmsource=os
+playbook=os,tablet
+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,mobile
+series40=force_os,mobile
+series60=force_os,mobile
+series80=force_os,mobile
+series90=force_os,mobile
+startos=os
+sunos=os
+supergamer=os
+syllable=os
+symbian=os
+symbianos=force_os
+symbos=os
+tigeros=os
+tt=os
+ubuntu=os,desktop
+unicos=merge,desktop
+unicos lclinux=os
+unix=merge_or_os
+unix bsd=os,desktop
+unixware=os,desktop
+webos=os
+wii=merge,mobile
+wii libnup=browser_os
+win98=os,desktop
+windows=merge_or_os
+windows nt=os,desktop
+windows xp=os,desktop
+windows phone=os,mobile
+windows mobile=os,mobile
+xbox=os,mobile
+
+# tvs
+apple=merge
+apple tv=,tv
+dlnadoc=,tv
+googletv=os,tv
+gtv100=,tv
+hbbtv=,tv
+iconbit=,tv
+itunes-appletv=,tv
+mk903v=,tv
+mt7001=,tv
+mx2=,tv
+pov_tv=,tv
+pov_tv-hdmi=,tv
+roku=,tv
+smart-tv=,tv
+smarttv=,tv
+tv=,tv
+
+# tablets
+allview=,tablet
+arc=,tablet
+at100=,tablet
+at10le=,tablet
+ax922=,tablet
+b1=,tablet
+cm_tenderloin=,tablet
+cyclops=,tablet
+elcano=,tablet
+f-01=,tablet
+freetab=,tablet
+gt-n=,tablet
+gt-p=,tablet
+gt-s=,tablet
+hp-tablet=,tablet
+huaweimediapad=,tablet
+ideatab=,tablet
+ideataba1000=,tablet
+ideataba1000l=,tablet
+ipad=maybe_os,tablet
+kfthwi=maybe_os,tablet
+kfjwi=maybe_os,tablet
+kftt=os,tablet
+lenovo=merge
+lenovo b8000=,tablet
+lenovo s5000=,tablet
+lenovoa3300=,tablet
+lg-v=,tablet
+lifetab_e=,tablet
+lifetab_p=,tablet
+lifetab_s=,tablet
+m785=,tablet
+me172v=,tablet
+me173x=,tablet
+me176c=,tablet
+me176cx=,tablet
+me269c=,tablet
+me301t=,tablet
+me302c=,tablet
+mp-1041=,tablet
+mz601=,tablet
+mz604=,tablet
+nettab=,tablet
+nexus 7=,tablet
+nexus 9=,tablet
+nexus 10=,tablet
+nookcolor=,tablet
+nookhd=,tablet
+pad=,tablet
+pc1038=,tablet
+pmp3670b=,tablet
+pov_tab=,tablet
+pov_tab-p=,tablet
+pov_tab-protab=,tablet
+prime=,tablet
+rct6077w=,tablet
+samsung-gt-s=,tablet
+sch-i=,tablet
+sgp311=,tablet
+sgp312=,tablet
+sgp321=,tablet
+sgp341=,tablet
+sh-06=,tablet
+sk-mtek=,tablet
+sm-p=,tablet
+sm-t=,tablet
+tab=,tablet
+tab10=,tablet
+tab465euk=,tablet
+tab9dualc=,tablet
+tablet=,tablet
+tb=,tablet
+transformer=,tablet
+x98=,tablet
+xoom=,tablet
+
+# mobiles
+apple-iphone=,mobile
+gt-i=,mobile
+htc=,mobile
+huawei=,mobile
+iphone=maybe_os,mobile
+ipod=maybe_os,mobile
+j2me=,mobile
+lenovo-a=,mobile
+nokia=,mobile
+nokia300=,mobile
+nokia5310xpressmusic=,mobile
+nokiax2=os,mobile
+samsung=,mobile
+smartphone=,mobile
+
+qt=,desktop

Added: velocity/tools/trunk/velocity-tools-view/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-view/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java?rev=1768141&view=auto
==============================================================================
--- velocity/tools/trunk/velocity-tools-view/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java (added)
+++ velocity/tools/trunk/velocity-tools-view/src/test/java/org/apache/velocity/tools/view/BrowserToolTests.java Sat Nov  5 02:11:49 2016
@@ -0,0 +1,160 @@
+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.io.FileWriter;
+import java.io.PrintWriter;
+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;
+import org.slf4j.Logger;
+
+/**
+ * <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 checkDevices(BrowserTool tool, Map<String, String> uas)
+    {
+        int num = 0;
+        for (Map.Entry<String, String> entry : uas.entrySet())
+        {
+            ++num;
+            String device = entry.getValue();
+            String ua = entry.getKey();
+            tool.setUserAgentString(ua);
+            String found = tool.getDevice();
+            assertTrue("entry " + num + "/" + uas.size() + ": wrong device detected: device={" + device + "}, found={" + found + "}, ua={" + ua + "}", device.equals(found));
+        }
+    }
+
+    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 testDeviceParsing() throws Exception
+    {
+        BrowserTool tool = new BrowserTool();
+        tool.setLog(new MockLogger(false, false));
+        Map uas = readUAs("devices.txt");
+        checkDevices(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);
+    }
+
+}



Mime
View raw message