/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2013, The THYMELEAF team (http://www.thymeleaf.org)
 * 
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * 
 * =============================================================================
 */
package org.thymeleaf.standard.expression;

/**
 * 
 * @author Daniel Fern&aacute;ndez
 * 
 * @since 1.1
 *
 */
public abstract class Token extends SimpleExpression {

    
    private static final long serialVersionUID = 4357087922344497120L;
    
    
    private final Object value;

    
    protected Token(final Object value) {
        super();
        this.value = value;
    }
    
    
    public Object getValue() {
        return this.value;
    }
    
    public String getStringRepresentation() {
        return this.value.toString(); // Tokens are fine not using the conversion service
    }
    
    
    @Override
    public String toString() {
        return getStringRepresentation();
    }

    


    public static boolean isTokenChar(final String context, final int pos) {

        /*
         * TOKEN chars: A-Za-z0-9[]._ (and '-' in some contexts)
         * (additionally, also, a series of internationalized characters: accents, other alphabets, etc.)
         *
         * '-' can also be a numeric operator, so it will only be considered a token char if:
          *    * there are immediately previous chars which we can consider a token, but not a numeric token
          *    * there are immediately following chars which we can consider a token, but not a numeric token
         *
         */

        final char c = context.charAt(pos);

        /*
         * First, the most common true's
         */
        if (c >= 'a' && c <= 'z') {
            return true;
        }
        if (c >= 'A' && c <= 'Z') {
            return true;
        }
        if (c >= '0' && c <= '9') {
            return true;
        }
        /*
         * The most common false's now, for failing fast
         */
        if (c == ' ' || c == '\n' || c == '(' || c == ')' || c == '\'' || c == '"' ||
                c == '<' || c == '>' || c == '{' || c == '}' ||
                c == '=' || c == ',' || c == ';' || c == ':' || c == '+' ||
                c == '*' || c == '$' || c == '%' || c == '&' || c == '#') {
            return false;
        }
        /*
         * Some more (less common) true's
         */
        if (c == '[' || c == ']' || c == '.' || c == '_') {
            return true;
        }
        /*
         * The special case: the dash
         */
        if (c == '-') {

            if (pos > 0) {
                // let's scan backwards, looking for a token char that is not a number

                for (int i = pos - 1; i >= 0; i--) {
                    if (!isTokenChar(context,i)) {
                        break;
                    }
                    final char cc = context.charAt(i);
                    if (!((cc >= '0' && cc <= '9') || cc == '.')) {
                        // It is a token, but not a digit or ., so the dash is not an operator, it is a token char
                        return true;
                    }
                }

            }

            final int contextLen = context.length();
            if (pos + 1 < contextLen) {
                // let's scan forward, looking for a token char that is not a number

                for (int i = pos + 1; i < contextLen; i++) {
                    if (!isTokenChar(context,i)) {
                        break;
                    }
                    final char cc = context.charAt(i);
                    if (!((cc >= '0' && cc <= '9') || cc == '.')) {
                        // It is a token, but not a digit or ., so the dash is not an operator, it is a token char
                        return true;
                    }
                }

            }

            return false;

        }
        /*
         * Finally, the rest of the true's
         */
        if (c == '\u00B7') {
            return true;
        }
        if (c >= '\u00C0' && c <= '\u00D6') {
            return true;
        }
        if (c >= '\u00D8' && c <= '\u00F6') {
            return true;
        }
        if (c >= '\u00F8' && c <= '\u02FF') {
            return true;
        }
        if (c >= '\u0300' && c <= '\u036F') {
            return true;
        }
        if (c >= '\u0370' && c <= '\u037D') {
            return true;
        }
        if (c >= '\u037F' && c <= '\u1FFF') {
            return true;
        }
        if (c >= '\u200C' && c <= '\u200D') {
            return true;
        }
        if (c >= '\u203F' && c <= '\u2040') {
            return true;
        }
        if (c >= '\u2070' && c <= '\u218F') {
            return true;
        }
        if (c >= '\u2C00' && c <= '\u2FEF') {
            return true;
        }
        if (c >= '\u3001' && c <= '\uD7FF') {
            return true;
        }
        if (c >= '\uF900' && c <= '\uFDCF') {
            return true;
        }
        if (c >= '\uFDF0' && c <= '\uFFFD') {
            return true;
        }
        return (c >= '\uFDF0' && c <= '\uFFFD');
    }





    public static final class TokenParsingTracer {

        public static final char TOKEN_SUBSTITUTE = '#';

        private TokenParsingTracer() {
            super();
        }

        public static String trace(final String input) {

            final StringBuilder strBuilder = new StringBuilder();

            final int inputLen = input.length();
            for (int i = 0; i < inputLen; i++) {
                if (isTokenChar(input, i)) {
                    strBuilder.append(TOKEN_SUBSTITUTE);
                } else {
                    strBuilder.append(input.charAt(i));
                }
            }

            return strBuilder.toString();

        }

    }




}

@@ -3971,32 +3971,286 @@
 textLen; i++) %7B%0A
+                    final char cc = context.charAt(i);%0A                    if (cc == '-') %7B%0A                        // We need to avoid cycles (which would happen if we call %22isTokenChar%22 again)%0A                        return true;%0A                    %7D%0A
                 
@@ -4341,63 +4341,8 @@
   %7D%0A
-                    final char cc = context.charAt(i);%0A
     

Fix #244. Infinite loop when parsing token expressions with --