Author: cbrisson
Date: Thu Apr 18 13:46:25 2019
New Revision: 1857748

URL: http://svn.apache.org/viewvc?rev=1857748&view=rev
Log:
[tools/model] Initial import: utility classes, and shading of commons-codec 
Base64

Added:
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/AESCryptograph.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/ChainedMap.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/Cryptograph.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/GlobToRegex.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/HashMultiMap.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/LogOutputStream.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/MultiMap.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotHashMap.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotMap.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotTreeMap.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/TypeUtils.java
Modified:
    velocity/tools/branches/model/velocity-tools-model/pom.xml

Modified: velocity/tools/branches/model/velocity-tools-model/pom.xml
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/pom.xml?rev=1857748&r1=1857747&r2=1857748&view=diff
==============================================================================
--- velocity/tools/branches/model/velocity-tools-model/pom.xml (original)
+++ velocity/tools/branches/model/velocity-tools-model/pom.xml Thu Apr 18 
13:46:25 2019
@@ -40,6 +40,48 @@
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>3.2.1</version>
+                <executions>
+                    <execution>
+                        <id>shade</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <artifactSet>
+                                <includes>
+                                    
<include>commons-codec:commons-codec</include>
+                                </includes>
+                            </artifactSet>
+                            <filters>
+                                <filter>
+                                    
<artifact>commons-codec:commons-codec</artifact>
+                                    <includes>
+                                        
<include>org/apache/commons/codec/Encoder.class</include>
+                                        
<include>org/apache/commons/codec/Decoder.class</include>
+                                        
<include>org/apache/commons/codec/BinaryEncoder.class</include>
+                                        
<include>org/apache/commons/codec/BinaryDecoder.class</include>
+                                        
<include>org/apache/commons/codec/binary/BaseNCodec.class</include>
+                                        
<include>org/apache/commons/codec/binary/Base64.class</include>
+                                    </includes>
+                                </filter>
+                            </filters>
+                            <relocations>
+                                <relocation>
+                                    <pattern>org.apache.commons.codec</pattern>
+                                    
<shadedPattern>org.apache.velocity.tools.model.util.shaded.commons.codec</shadedPattern>
+                                </relocation>
+                            </relocations>
+                            
<keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope>
+                            <minimizeJar>true</minimizeJar>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
                     <systemProperties>
@@ -64,6 +106,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.12</version>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/AESCryptograph.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/AESCryptograph.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/AESCryptograph.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/AESCryptograph.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,97 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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 org.apache.velocity.tools.config.ConfigurationException;
+
+/**
+ * TODO - document secure random requirement
+ */
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.Charset;
+
+import static javax.crypto.Cipher.DECRYPT_MODE;
+import static javax.crypto.Cipher.ENCRYPT_MODE;
+
+/**
+ * Basic AES encryption. Please note that it uses the ECB block mode, which 
has the advantage
+ * to not require random bytes, thus providing some *persistence* for the 
encrypted data, but
+ * at the expense of some security weaknesses. The purpose here is just to 
discourage editing
+ * id values in URLs, not to protect state secrets.
+ */
+
+public class AESCryptograph implements Cryptograph
+{
+    @Override
+    public void init(String key)
+    {
+        byte[] bytes = key.getBytes(Charset.defaultCharset());
+        if (bytes.length < 16)
+        {
+            throw new ConfigurationException("not enough secret bytes");
+        }
+        SecretKey secret = new SecretKeySpec(bytes, 0, 16, ALGORITHM);
+        try
+        {
+            encrypt = Cipher.getInstance(CIPHER);
+            encrypt.init(ENCRYPT_MODE, secret);
+            decrypt = Cipher.getInstance(CIPHER);
+            decrypt.init(DECRYPT_MODE, secret);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("cyptograph initialization failed", e);
+        }
+    }
+
+    @Override
+    public byte[] encrypt(String str)
+    {
+        try
+        {
+            return encrypt.doFinal(str.getBytes(Charset.defaultCharset()));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("encryption failed failed", e);
+        }
+    }
+
+    @Override
+    public String decrypt(byte[] bytes)
+    {
+        try
+        {
+            return new String(decrypt.doFinal(bytes), 
Charset.defaultCharset());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("encryption failed failed", e);
+        }
+    }
+
+    private final String CIPHER = "AES/ECB/PKCS5Padding";
+    private final String ALGORITHM = "AES";
+    private Cipher encrypt, decrypt;
+
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/ChainedMap.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/ChainedMap.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/ChainedMap.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/ChainedMap.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,135 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+public class ChainedMap implements Map
+{
+    protected static Logger logger = LoggerFactory.getLogger(ChainedMap.class);
+
+    private Map source;
+    private Map parameters;
+    
+    public ChainedMap(Map source, Map parameters)
+    {
+        this.source = source;
+        this.parameters = parameters;
+    }
+
+    public Map getSource()
+    {
+        return source;
+    }
+
+    public Map getParameters()
+    {
+        return parameters;
+    }
+    
+    public void clear()
+    {
+        logger.error("trying to modify a read-only ChainedMap");
+    }
+
+    public boolean containsKey(Object key)
+    {
+        return source.containsKey(key) || parameters.containsKey(key);
+    }
+
+    public boolean containsValue(Object value)
+    {
+        return source.containsValue(value) || parameters.containsValue(value);
+    }
+
+    public Set<Map.Entry> entrySet()
+    {
+        Set<Map.Entry> ret = source.entrySet();
+        ret.addAll(parameters.entrySet());
+        return ret;
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == null || !(o instanceof ChainedMap)) return false;
+        ChainedMap other = (ChainedMap)o;
+        return source.equals(other.getSource()) && 
parameters.equals(other.getParameters());
+    }
+
+    public Object get(Object key)
+    {
+        Object ret = parameters.get(key);
+        return ret == null ? source.get(key) : ret;
+    }
+
+    public int hashCode()
+    {
+        return source.hashCode() ^ parameters.hashCode();
+    }
+
+    public boolean isEmpty()
+    {
+        return source.isEmpty() && parameters.isEmpty();
+    }
+
+    public Set keySet()
+    {
+        Set ret = source.keySet();
+        ret.addAll(parameters.keySet());
+        return ret;
+    }
+
+    public Object put(Object key, Object value)
+    {
+        logger.error("trying to modify a read-only ChainedMap");
+        return null;
+    }
+
+    public void putAll(Map m)
+    {
+        logger.error("trying to modify a read-only ChainedMap");
+    }
+
+    public Serializable remove(Object key)
+    {
+        logger.error("trying to modify a read-only ChainedMap");
+        return null;
+    }
+
+    public int size()
+    {
+        return source.size() + parameters.size();
+    }
+
+    public Collection values()
+    {
+        ArrayList ret = new ArrayList();
+        ret.addAll(source.values());
+        ret.addAll(parameters.values());
+        return ret;
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/Cryptograph.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/Cryptograph.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/Cryptograph.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/Cryptograph.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,51 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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.io.Serializable;
+
+/**
+ * Cryptograph - used to encrypt and decrypt strings.
+ *
+ *  @author <a href=mailto:[email protected]>Claude Brisson</a>
+ */
+
+public interface Cryptograph extends Serializable
+{
+    /**
+     * init.
+     * @param random random string
+     */
+    void init(String random);
+
+    /**
+     * encrypt.
+     * @param str string to encrypt
+     * @return encrypted string
+     */
+    byte[] encrypt(String str);
+
+    /**
+     * decrypt.
+     * @param bytes to decrypt
+     * @return decrypted string
+     */
+    String decrypt(byte[] bytes);
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/GlobToRegex.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/GlobToRegex.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/GlobToRegex.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/GlobToRegex.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,558 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.velocity.tools.config.ConfigurationException;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Translates globs to regex patterns.
+ * Borrowed from com.google.common.jimfs.
+ *
+ * @author Colin Decker
+ */
+
+public final class GlobToRegex
+{
+
+    /**
+     * Converts the given glob to a regular expression pattern. The given 
separators determine what
+     * characters the resulting expression breaks on for glob expressions such 
as * which should not
+     * cross directory boundaries.
+     * <p>
+     * <p>Basic conversions (assuming / as only separator):
+     * <p>
+     * <pre>{@code
+     * ?        = [^/]
+     * *        = [^/]*
+     * **       = .*
+     * [a-z]    = [[^/]&&[a-z]]
+     * [!a-z]   = [[^/]&&[^a-z]]
+     * {a,b,c}  = (a|b|c)
+     * }</pre>
+     */
+    public static String toRegex(String glob, String separators)
+    {
+        return new GlobToRegex(glob, separators).convert();
+    }
+
+    /**
+     * Char matcher
+     *
+     * @author Colin Decker
+     */
+    final static class InternalCharMatcher
+    {
+
+        private final char[] chars;
+
+        public InternalCharMatcher(String chars)
+        {
+            this.chars = chars.toCharArray();
+            Arrays.sort(this.chars);
+        }
+
+        public boolean matches(char c)
+        {
+            return Arrays.binarySearch(chars, c) >= 0;
+        }
+    }
+
+    public static InternalCharMatcher anyOf(String chars)
+    {
+        return new InternalCharMatcher(chars);
+    }
+
+    private static final InternalCharMatcher REGEX_RESERVED = 
anyOf("^$.?+*\\[]{}()");
+
+    private final String glob;
+    private final String separators;
+    private final InternalCharMatcher separatorMatcher;
+
+    private final StringBuilder builder = new StringBuilder();
+    private final Deque<State> states = new ArrayDeque<>();
+    private int index;
+
+    private GlobToRegex(String glob, String separators)
+    {
+        if (glob == null)
+        {
+            throw new ConfigurationException("null glob pattern");
+        }
+        this.glob = glob;
+        this.separators = separators;
+        this.separatorMatcher = anyOf(separators);
+    }
+
+    /**
+     * Converts the glob to a regex one character at a time. A state stack 
(states) is maintained,
+     * with the state at the top of the stack being the current state at any 
given time. The current
+     * state is always used to process the next character. When a state 
processes a character, it may
+     * pop the current state or push a new state as the current state. The 
resulting regex is written
+     * to {@code builder}.
+     */
+    private String convert()
+    {
+        pushState(NORMAL);
+        for (index = 0; index < glob.length(); index++)
+        {
+            currentState().process(this, glob.charAt(index));
+        }
+        currentState().finish(this);
+        return builder.toString();
+    }
+
+    /**
+     * Enters the given state. The current state becomes the previous state.
+     */
+    private void pushState(State state)
+    {
+        states.push(state);
+    }
+
+    /**
+     * Returns to the previous state.
+     */
+    private void popState()
+    {
+        states.pop();
+    }
+
+    /**
+     * Returns the current state.
+     */
+    private State currentState()
+    {
+        return states.peek();
+    }
+
+    /**
+     * Throws a {@link PatternSyntaxException}.
+     */
+    private PatternSyntaxException syntaxError(String desc)
+    {
+        throw new PatternSyntaxException(desc, glob, index);
+    }
+
+    /**
+     * Appends the given character as-is to the regex.
+     */
+    private void appendExact(char c)
+    {
+        builder.append(c);
+    }
+
+    /**
+     * Appends the regex form of the given normal character or separator from 
the glob.
+     */
+    private void append(char c)
+    {
+        if (separatorMatcher.matches(c))
+        {
+            appendSeparator();
+        }
+        else
+        {
+            appendNormal(c);
+        }
+    }
+
+    /**
+     * Appends the regex form of the given normal character from the glob.
+     */
+    private void appendNormal(char c)
+    {
+        if (REGEX_RESERVED.matches(c))
+        {
+            builder.append('\\');
+        }
+        builder.append(c);
+    }
+
+    /**
+     * Appends the regex form matching the separators for the path type.
+     */
+    private void appendSeparator()
+    {
+        if (separators.length() == 1)
+        {
+            appendNormal(separators.charAt(0));
+        }
+        else
+        {
+            builder.append('[');
+            for (int i = 0; i < separators.length(); i++)
+            {
+                appendInBracket(separators.charAt(i));
+            }
+            builder.append("]");
+        }
+    }
+
+    /**
+     * Appends the regex form that matches anything except the separators for 
the path type.
+     */
+    private void appendNonSeparator()
+    {
+        builder.append("[^");
+        for (int i = 0; i < separators.length(); i++)
+        {
+            appendInBracket(separators.charAt(i));
+        }
+        builder.append(']');
+    }
+
+    /**
+     * Appends the regex form of the glob ? character.
+     */
+    private void appendQuestionMark()
+    {
+        appendNonSeparator();
+    }
+
+    /**
+     * Appends the regex form of the glob * character.
+     */
+    private void appendStar()
+    {
+        appendNonSeparator();
+        builder.append('*');
+    }
+
+    /**
+     * Appends the regex form of the glob ** pattern.
+     */
+    private void appendStarStar()
+    {
+        builder.append(".*");
+    }
+
+    /**
+     * Appends the regex form of the start of a glob [] section.
+     */
+    private void appendBracketStart()
+    {
+        builder.append('[');
+        appendNonSeparator();
+        builder.append("&&[");
+    }
+
+    /**
+     * Appends the regex form of the end of a glob [] section.
+     */
+    private void appendBracketEnd()
+    {
+        builder.append("]]");
+    }
+
+    /**
+     * Appends the regex form of the given character within a glob [] section.
+     */
+    private void appendInBracket(char c)
+    {
+        // escape \ in regex character class
+        if (c == '\\')
+        {
+            builder.append('\\');
+        }
+
+        builder.append(c);
+    }
+
+    /**
+     * Appends the regex form of the start of a glob {} section.
+     */
+    private void appendCurlyBraceStart()
+    {
+        builder.append('(');
+    }
+
+    /**
+     * Appends the regex form of the separator (,) within a glob {} section.
+     */
+    private void appendSubpatternSeparator()
+    {
+        builder.append('|');
+    }
+
+    /**
+     * Appends the regex form of the end of a glob {} section.
+     */
+    private void appendCurlyBraceEnd()
+    {
+        builder.append(')');
+    }
+
+    /**
+     * Converter state.
+     */
+    private abstract static class State
+    {
+        /**
+         * Process the next character with the current state, transitioning 
the converter to a new
+         * state if necessary.
+         */
+        abstract void process(GlobToRegex converter, char c);
+
+        /**
+         * Called after all characters have been read.
+         */
+        void finish(GlobToRegex converter)
+        {
+        }
+    }
+
+    /**
+     * Normal state.
+     */
+    private static final State NORMAL =
+        new State()
+        {
+            @Override
+            void process(GlobToRegex converter, char c)
+            {
+                switch (c)
+                {
+                    case '?':
+                        converter.appendQuestionMark();
+                        return;
+                    case '[':
+                        converter.appendBracketStart();
+                        converter.pushState(BRACKET_FIRST_CHAR);
+                        return;
+                    case '{':
+                        converter.appendCurlyBraceStart();
+                        converter.pushState(CURLY_BRACE);
+                        return;
+                    case '*':
+                        converter.pushState(STAR);
+                        return;
+                    case '\\':
+                        converter.pushState(ESCAPE);
+                        return;
+                    default:
+                        converter.append(c);
+                }
+            }
+
+            @Override
+            public String toString()
+            {
+                return "NORMAL";
+            }
+        };
+
+    /**
+     * State following the reading of a single \.
+     */
+    private static final State ESCAPE =
+        new State()
+        {
+            @Override
+            void process(GlobToRegex converter, char c)
+            {
+                converter.append(c);
+                converter.popState();
+            }
+
+            @Override
+            void finish(GlobToRegex converter)
+            {
+                throw converter.syntaxError("Hanging escape (\\) at end of 
pattern");
+            }
+
+            @Override
+            public String toString()
+            {
+                return "ESCAPE";
+            }
+        };
+
+    /**
+     * State following the reading of a single *.
+     */
+    private static final State STAR =
+        new State()
+        {
+            @Override
+            void process(GlobToRegex converter, char c)
+            {
+                if (c == '*')
+                {
+                    converter.appendStarStar();
+                    converter.popState();
+                }
+                else
+                {
+                    converter.appendStar();
+                    converter.popState();
+                    converter.currentState().process(converter, c);
+                }
+            }
+
+            @Override
+            void finish(GlobToRegex converter)
+            {
+                converter.appendStar();
+            }
+
+            @Override
+            public String toString()
+            {
+                return "STAR";
+            }
+        };
+
+    /**
+     * State immediately following the reading of a [.
+     */
+    private static final State BRACKET_FIRST_CHAR =
+        new State()
+        {
+            @Override
+            void process(GlobToRegex converter, char c)
+            {
+                if (c == ']')
+                {
+                    // A glob like "[]]" or "[]q]" is apparently fine in Unix 
(when used with ls for example)
+                    // but doesn't work for the default java.nio.file 
implementations. In the cases of "[]]" it
+                    // produces:
+                    // java.util.regex.PatternSyntaxException: Unclosed 
character class near index 13
+                    // ^[[^/]&&[]]\]$
+                    //              ^
+                    // The error here is slightly different, but trying to 
make this work would require some
+                    // kind of lookahead and break the simplicity of 
char-by-char conversion here. Also, if
+                    // someone wants to include a ']' inside a character 
class, they should escape it.
+                    throw converter.syntaxError("Empty []");
+                }
+                if (c == '!')
+                {
+                    converter.appendExact('^');
+                }
+                else if (c == '-')
+                {
+                    converter.appendExact(c);
+                }
+                else
+                {
+                    converter.appendInBracket(c);
+                }
+                converter.popState();
+                converter.pushState(BRACKET);
+            }
+
+            @Override
+            void finish(GlobToRegex converter)
+            {
+                throw converter.syntaxError("Unclosed [");
+            }
+
+            @Override
+            public String toString()
+            {
+                return "BRACKET_FIRST_CHAR";
+            }
+        };
+
+    /**
+     * State inside [brackets], but not at the first character inside the 
brackets.
+     */
+    private static final State BRACKET =
+        new State()
+        {
+            @Override
+            void process(GlobToRegex converter, char c)
+            {
+                if (c == ']')
+                {
+                    converter.appendBracketEnd();
+                    converter.popState();
+                }
+                else
+                {
+                    converter.appendInBracket(c);
+                }
+            }
+
+            @Override
+            void finish(GlobToRegex converter)
+            {
+                throw converter.syntaxError("Unclosed [");
+            }
+
+            @Override
+            public String toString()
+            {
+                return "BRACKET";
+            }
+        };
+
+    /**
+     * State inside {curly braces}.
+     */
+    private static final State CURLY_BRACE =
+        new State()
+        {
+            @Override
+            void process(GlobToRegex converter, char c)
+            {
+                switch (c)
+                {
+                    case '?':
+                        converter.appendQuestionMark();
+                        break;
+                    case '[':
+                        converter.appendBracketStart();
+                        converter.pushState(BRACKET_FIRST_CHAR);
+                        break;
+                    case '{':
+                        throw converter.syntaxError("{ not allowed in 
subpattern group");
+                    case '*':
+                        converter.pushState(STAR);
+                        break;
+                    case '\\':
+                        converter.pushState(ESCAPE);
+                        break;
+                    case '}':
+                        converter.appendCurlyBraceEnd();
+                        converter.popState();
+                        break;
+                    case ',':
+                        converter.appendSubpatternSeparator();
+                        break;
+                    default:
+                        converter.append(c);
+                }
+            }
+
+            @Override
+            void finish(GlobToRegex converter)
+            {
+                throw converter.syntaxError("Unclosed {");
+            }
+
+            @Override
+            public String toString()
+            {
+                return "CURLY_BRACE";
+            }
+        };
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/HashMultiMap.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/HashMultiMap.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/HashMultiMap.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/HashMultiMap.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,270 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A MultiMap is a Map allowing multiple occurrences of keys.
+ *
+ * @author Mark Richters
+ * @see java.util.Map
+ */
+public class HashMultiMap implements MultiMap, Serializable
+{
+    /**
+     * inner map.
+     */
+    private Map map;    // (Object key -> List values)
+
+    /**
+     * total number of values.
+     */
+    private transient int sizeAll;
+
+    /**
+     * build a new HashMultiMap.
+     */
+    public HashMultiMap()
+    {
+        map = new HashMap();
+    }
+
+    // Query Operations
+
+    /**
+     * Returns the number of values in this multimap.
+     *
+     * @return totla number of values
+     */
+    public int size()
+    {
+        return sizeAll;
+    }
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains no mappings.
+     *
+     * @return empty status
+     */
+    public boolean isEmpty()
+    {
+        return sizeAll == 0;
+    }
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains a mapping for
+     * the specified key.
+     *
+     * @param key
+     */
+    public boolean containsKey(Object key)
+    {
+        return map.containsKey(key);
+    }
+
+    /**
+     * Returns <tt>true</tt> if this multimap maps one or more keys to
+     * the specified value.
+     */
+    public boolean containsValue(Object value)
+    {
+        Iterator it = map.values().iterator();
+
+        while(it.hasNext())
+        {
+            List l = (List)it.next();
+
+            if(l.contains(value))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns a list of values to which this multimap maps the specified
+     * key.
+     *
+     * @return the list of values to which this map maps the specified key, the
+     *     list may be empty if the multimap contains no mapping for this key.
+     */
+    public List get(Object key)
+    {
+        List l = (List)map.get(key);
+
+        if(l == null)
+        {
+            l = new ArrayList();
+        }
+        return l;
+    }
+
+    // Modification Operations
+
+    /**
+     * Adds the specified value with the specified key to this multimap.
+     * @param key
+     * @param value
+     */
+    public void put(Object key, Object value)
+    {
+        List l = (List)map.get(key);
+
+        if(l == null)
+        {
+            l = new ArrayList();
+            map.put(key, l);
+        }
+        l.add(value);
+        sizeAll++;
+    }
+
+    /**
+     * Copies all entries from the specified multimap to this
+     * multimap.
+     * @param t source multimap
+     */
+    public void putAll(MultiMap t)
+    {
+        Iterator it = t.keySet().iterator();
+
+        while(it.hasNext())
+        {
+            Object key = it.next();
+            List tl = t.get(key);
+            List l = (List)map.get(key);
+
+            if(l == null)
+            {
+                l = new ArrayList();
+                map.put(key, l);
+            }
+            l.addAll(tl);
+            sizeAll += tl.size();
+        }
+    }
+
+    /**
+     * Removes all mappings for this key from this multimap if present.
+     * @param key
+     */
+    public void remove(Object key)
+    {
+        List l = (List)map.get(key);
+
+        if(l != null)
+        {
+            sizeAll -= l.size();
+        }
+        map.remove(key);
+    }
+
+    /**
+     * Removes the specified key/value mapping from this multimap if present.
+     * @param key
+     * @param value
+     */
+    public void remove(Object key, Object value)
+    {
+        List l = (List)map.get(key);
+
+        if(l != null)
+        {
+            if(l.remove(value))
+            {
+                sizeAll--;
+            }
+        }
+    }
+
+    // Bulk Operations
+
+    /**
+     * Removes all mappings from this map (optional operation).
+     */
+    public void clear()
+    {
+        map.clear();
+        sizeAll = 0;
+    }
+
+    // Views
+
+    /**
+     * Returns a set view of the keys contained in this multimap.
+     */
+    public Set keySet()
+    {
+        return map.keySet();
+    }
+
+    // Comparison and hashing
+
+    /**
+     * Compares the specified object with this multimap for equality.
+     * @param o
+     */
+    public boolean equals(Object o)
+    {
+        if(o == this)
+        {
+            return true;
+        }
+
+        // FIXME: use MultiMap interface only
+        if(!(o instanceof HashMultiMap))
+        {
+            return false;
+        }
+
+        HashMultiMap c = (HashMultiMap)o;
+
+        if(c.size() != size())
+        {
+            return false;
+        }
+        return map.equals(c.map);
+    }
+
+    /**
+     * Returns the hash code value for this multimap.
+     */
+    public int hashCode()
+    {
+        int h = 0;
+        Iterator it = map.entrySet().iterator();
+
+        while(it.hasNext())
+        {
+            Object obj = it.next();
+
+            h += obj.hashCode();
+        }
+        return h;
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/LogOutputStream.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/LogOutputStream.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/LogOutputStream.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/LogOutputStream.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,101 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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 org.slf4j.Logger;
+import org.slf4j.spi.LocationAwareLogger;
+
+import java.io.OutputStream;
+import java.util.function.Consumer;
+
+public class LogOutputStream extends OutputStream
+{
+    private Logger logger;
+
+    private StringBuffer buffer;
+
+    /** slf4j levels */
+    public static final int TRACE = LocationAwareLogger.TRACE_INT;
+    public static final int DEBUG = LocationAwareLogger.DEBUG_INT;
+    public static final int INFO = LocationAwareLogger.INFO_INT;
+    public static final int WARN = LocationAwareLogger.WARN_INT;
+    public static final int ERROR = LocationAwareLogger.ERROR_INT;
+
+    public LogOutputStream(Logger logger, int level)
+    {
+        this.logger = logger;
+        switch (level)
+        {
+            case TRACE: log = this::trace; break;
+            case DEBUG: log = this::debug; break;
+            case INFO : log = this::info; break;
+            case WARN: log = this::warn; break;
+            case ERROR: log = this::error; break;
+        }
+    }
+
+    private Consumer<String> log;
+
+    private final void trace(String log)
+    {
+        logger.trace(log);
+    }
+
+    private final void debug(String log)
+    {
+        logger.debug(log);
+    }
+
+    private final void info(String log)
+    {
+        logger.info(log);
+    }
+
+    private final void warn(String log)
+    {
+        logger.warn(log);
+    }
+
+    private final void error(String log)
+    {
+        logger.error(log);
+    }
+
+    @Override
+    public void write (int c)
+    {
+        char ch = (char)(c & 0xFF);
+        if (ch == '\n')
+        {
+            log.accept(buffer.toString());
+            buffer = new StringBuffer();
+        }
+        else
+        {
+            buffer.append(ch);
+        }
+    }
+
+    @Override
+    public void flush()
+    {
+        // I prefer just waiting for \n
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/MultiMap.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/MultiMap.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/MultiMap.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/MultiMap.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,134 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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.List;
+import java.util.Set;
+
+/**
+ * A MultiMap is a Map allowing multiple occurrences of keys.
+ *
+ * @author     Mark Richters
+ * @see         java.util.Map
+ */
+public interface MultiMap
+{
+    // Query Operations
+
+    /**
+     * Returns the number of values in this multimap.
+     */
+    int size();
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains no mappings.
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns <tt>true</tt> if this multimap contains a mapping for
+     * the specified key.
+     * @param key
+     * @return a boolean
+     */
+    boolean containsKey(Object key);
+
+    /**
+     * Returns <tt>true</tt> if this multimap maps one or more keys to
+     * the specified value.
+     * @param value
+     * @return a boolean
+     */
+    boolean containsValue(Object value);
+
+    /**
+     * Returns a list of values to which this multimap maps the specified
+     * key.
+     * @param key
+     * @return the list of values to which this map maps the specified
+     *         key, the list may be empty if the multimap contains no
+     *         mapping for this key.
+     */
+    List get(Object key);
+
+    // Modification Operations
+
+    /**
+     * Adds the specified value with the specified key to this multimap.
+     *
+     * @param key
+     * @param value
+     */
+    void put(Object key, Object value);
+
+    /**
+     * Copies all entries from the specified multimap to this
+     * multimap.
+     * @param t multimap
+     */
+    void putAll(MultiMap t);
+
+    /**
+     * Removes all mappings for this key from this multimap if present.
+     * @param key
+     */
+    void remove(Object key);
+
+    /**
+     * Removes the specified key/value mapping from this multimap if present.
+     * @param key
+     * @param value
+     */
+    void remove(Object key, Object value);
+
+    // Bulk Operations
+
+    /**
+     * Removes all mappings from this map (optional operation).
+     */
+    void clear();
+
+    // Views
+
+    /**
+     * Returns a set view of the keys contained in this multimap.
+     * @return key set
+     */
+    Set keySet();
+
+    /*
+     * Returns a collection view of the values contained in this map.
+     */
+
+    // Collection values();
+    // Comparison and hashing
+
+    /**
+     * Compares the specified object with this multimap for equality.
+     * @param o other object
+     * @return a boolean
+     */
+    boolean equals(Object o);
+
+    /**
+     * Returns the hash code value for this map.
+     */
+    int hashCode();
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotHashMap.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotHashMap.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotHashMap.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotHashMap.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,34 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SlotHashMap extends HashMap<String,Serializable> implements 
SlotMap
+{
+    public SlotHashMap() {}
+
+    public SlotHashMap(Map<? extends String, ? extends Serializable> m)
+    {
+        super(m);
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotMap.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotMap.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotMap.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotMap.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,27 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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.io.Serializable;
+import java.util.Map;
+
+public interface SlotMap extends Map<String,Serializable>, Serializable
+{
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotTreeMap.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotTreeMap.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotTreeMap.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/SlotTreeMap.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,39 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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.io.Serializable;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class SlotTreeMap extends TreeMap<String,Serializable> implements 
SlotMap
+{
+    public SlotTreeMap() {}
+
+    public SlotTreeMap(java.util.Comparator<java.lang.String> comparator)
+    {
+        super(comparator);
+    }
+
+    public SlotTreeMap(Map<? extends String, ? extends Serializable> m)
+    {
+        super(m);
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/TypeUtils.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/TypeUtils.java?rev=1857748&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/TypeUtils.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/util/TypeUtils.java
 Thu Apr 18 13:46:25 2019
@@ -0,0 +1,249 @@
+package org.apache.velocity.tools.model.util;
+
+/*
+ * 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 org.apache.commons.codec.binary.Base64;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+public class TypeUtils
+{
+    public static String toString(Object value)
+    {
+        return value == null ? null : value.toString();
+    }
+
+    public static Boolean toBoolean(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof Boolean)
+        {
+            return (Boolean)value;
+        }
+        if (value instanceof String)
+        {
+            String str = (String)value;
+            if ("true".equals(str))
+            {
+                return true;
+            }
+            if ("false".equals(str))
+            {
+                return false;
+            }
+            try
+            {
+                value = Long.valueOf(str);
+            }
+            catch (NumberFormatException nfe)
+            {
+                return false;
+            }
+        }
+        if (value instanceof Number)
+        {
+            return ((Number)value).longValue() != 0l;
+        }
+        return false;
+    }
+
+    public static Short toShort(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number)value).shortValue();
+        }
+        if (value instanceof String)
+        {
+            try
+            {
+                return Short.valueOf((String)value);
+            }
+            catch (NumberFormatException nfe)
+            {
+            }
+        }
+        return null;
+    }
+
+    public static Integer toInteger(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number)value).intValue();
+        }
+        if (value instanceof String)
+        {
+            try
+            {
+                return Integer.valueOf((String)value);
+            }
+            catch (NumberFormatException nfe)
+            {
+            }
+        }
+        return null;
+    }
+
+    public static Long toLong(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number)value).longValue();
+        }
+        if (value instanceof String)
+        {
+            try
+            {
+                return Long.valueOf((String)value);
+            }
+            catch (NumberFormatException nfe)
+            {
+            }
+        }
+        return null;
+    }
+
+    public static Float toFloat(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number)value).floatValue();
+        }
+        if (value instanceof String)
+        {
+            try
+            {
+                return Float.valueOf((String)value);
+            }
+            catch (NumberFormatException nfe)
+            {
+            }
+        }
+        return null;
+    }
+
+    public static Double toDouble(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number)value).doubleValue();
+        }
+        if (value instanceof String)
+        {
+            try
+            {
+                return Double.valueOf((String)value);
+            }
+            catch (NumberFormatException nfe)
+            {
+            }
+        }
+        return null;
+    }
+
+    public static Date toDate(Object value)
+    {
+        if (value == null || value instanceof Date)
+        {
+            return (Date)value;
+        }
+        if (value instanceof Calendar)
+        {
+            return ((Calendar)value).getTime();
+        }
+        return null;
+    }
+
+    public static Calendar toCalendar(Object value)
+    {
+        if (value == null || value instanceof Calendar)
+        {
+            return (Calendar)value;
+        }
+        if (value instanceof Date)
+        {
+            // CB TODO - use model locale
+            Calendar calendar = GregorianCalendar.getInstance();
+            calendar.setTime((Date)value);
+            return calendar;
+        }
+        return null;
+    }
+
+    public static byte[] toBytes(Object value)
+    {
+        if (value == null || value instanceof byte[])
+        {
+            return (byte[])value;
+        }
+        return String.valueOf(value).getBytes(StandardCharsets.UTF_8);
+    }
+
+    public static String base64Encode(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        byte[] decoded = toBytes(value);
+        byte[] encoded = Base64.encodeBase64URLSafe(decoded);
+        return new String(encoded, StandardCharsets.UTF_8);
+    }
+
+    public static byte[] base64Decode(Object value)
+    {
+        if (value == null)
+        {
+            return null;
+        }
+        String encoded = toString(value);
+        return Base64.decodeBase64(encoded);
+
+    }
+}


Reply via email to