EMMA Coverage Report (generated Thu Jan 20 11:39:44 EST 2011)
[all classes][com.hammurapi.common]

COVERAGE SUMMARY FOR SOURCE FILE [TokenExpander.java]

nameclass, %method, %block, %line, %
TokenExpander.java100% (1/1)67%  (4/6)53%  (162/308)59%  (39.7/67)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class TokenExpander100% (1/1)67%  (4/6)53%  (162/308)59%  (39.7/67)
TokenExpander (TokenExpander$TokenSource, char, char, char): void 0%   (0/1)0%   (0/16)0%   (0/5)
expandToken (String, String): String 0%   (0/1)0%   (0/8)0%   (0/1)
expandToken (String, String, Set): String 100% (1/1)43%  (20/46)56%  (5/9)
parse (String, Set): String 100% (1/1)57%  (120/211)62%  (26.7/43)
TokenExpander (TokenExpander$TokenSource): void 100% (1/1)77%  (17/22)88%  (7/8)
expand (String): String 100% (1/1)100% (5/5)100% (1/1)

1package com.hammurapi.common;
2 
3import java.util.HashSet;
4import java.util.Set;
5import java.util.StringTokenizer;
6 
7/**
8 * Expands tokens like $[myToken] to token values.
9 * Nested tokens are supported, i.e. if myToken value contains $[someOtherToken]
10 * then it will also be expanded. $[ can be escaped by adding extra $, e.g. $$[ will be replaced with $[.
11 * Token can contain default value separated from the token name by pipe, e.g. $[myToken|tokenDefaultValue].
12 * @author Pavel Vlasov
13 */
14public class TokenExpander {
15        
16        /**
17         * Source of tokens.
18         * @author Pavel Vlasov
19         */
20        public interface TokenSource {
21                
22                /**
23                 * @param name Token name.
24                 * @return Token value or null.
25                 */
26                String getToken(String name);
27 
28        }
29                
30    public static final String TOKEN_CLOSING_CHAR = "}";
31        public static final String TOKEN_SECOND_OPENING_CHAR = "{";
32        public static final String TOKEN_FIRST_OPENING_CHAR = "$";
33        
34    private String tokenClosingChar = TOKEN_CLOSING_CHAR;
35        private String tokenSecondOpeningChar = TOKEN_SECOND_OPENING_CHAR;
36        private String tokenFirstOpeningChar = TOKEN_FIRST_OPENING_CHAR;
37        
38        private TokenSource tokens;
39    
40    /** 
41     * Creates a new instance of PropertyParser
42     * @param properties Properties
43     * @param useNameAsDefault If true then property name will be used
44     * as property default value. 
45     */
46    public TokenExpander(TokenSource tokens) {
47        if (tokens==null) {
48                throw new NullPointerException("Tokens map is null");
49        }
50        this.tokens=tokens;
51    }    
52    
53    /** 
54     * Creates a new instance of TokenExpander.
55     * @param tokens 
56     * @param tokenFirstOpeningChar Token first opening char. Default is $.
57     * @param tokenSecondOpeningChar Token second opening char. Default is {.
58     * @param tokenClosingChar Token closing char. Default is }.
59     */
60    public TokenExpander(TokenSource tokens, char tokenFirstOpeningChar, char tokenSecondOpeningChar, char tokenClosingChar) {
61            this(tokens);
62        this.tokenClosingChar=String.valueOf(tokenClosingChar);
63        this.tokenFirstOpeningChar=String.valueOf(tokenFirstOpeningChar);
64        this.tokenSecondOpeningChar=String.valueOf(tokenSecondOpeningChar);
65    }
66            
67    /**
68     * Property parsing. 
69     * Replaces string ${<property name>} with property value.
70     * If property value contains ${<other property name>} it
71     * will be parsed. 
72     */
73    public String expandToken(String token, String defaultValue) throws TokenExpansionException {
74        return expandToken(token, defaultValue, new HashSet<String>());
75    }
76    
77    /**
78     * Parses a string by replacing occurences of ${&lt;property name&gt;} 
79     * with property values.
80     * If property value contains ${&lt;other property name&gt;} it
81     * will be parsed. 
82     */
83    public String expand(String str) throws TokenExpansionException {
84        return parse(str, null);
85    }
86 
87    private String expandToken(String token, String defaultValue, Set<String> stack) throws TokenExpansionException {
88            String value = tokens.getToken(token);
89        if (value==null) {
90                if (defaultValue==null) {
91                        throw new TokenExpansionException("Token not found: "+token);
92                } 
93                value = defaultValue;
94        }
95        
96        if (stack.contains(token)) {
97                throw new TokenExpansionException("Circular reference in token substitution, token: "+token);
98        }
99        
100        stack.add(token);
101        
102        return parse(value, stack);
103    }
104    
105    private String parse(String str, Set<String> stack) throws TokenExpansionException {
106        if (str==null) {
107                return null;
108        }
109        
110        StringTokenizer st=new StringTokenizer(str,tokenFirstOpeningChar+tokenSecondOpeningChar+tokenClosingChar,true);
111        
112        StringBuilder ret=new StringBuilder();
113        
114        /**
115         * Parser state:
116         * 0: Text
117         * 1: $
118         * 2: ${
119         */
120        final int TEXT = 0;
121        final int OPEN_FIRST = 1;
122        final int OPEN_SECOND = 2;
123                
124        int state=0;
125        
126        String propTxt=null;
127        while (st.hasMoreTokens()) {
128            String tkn=st.nextToken();
129            switch (state) {
130                case TEXT:
131                    if (tokenFirstOpeningChar.equals(tkn))
132                        state=OPEN_FIRST;
133                    else
134                        ret.append(tkn);
135                    break;
136                case OPEN_FIRST:
137                    if (tokenSecondOpeningChar.equals(tkn))
138                        state=OPEN_SECOND;
139                    else {
140                        state=TEXT;
141                        ret.append(tokenFirstOpeningChar);
142                        if (tkn.equals(tokenFirstOpeningChar)) {
143                                String next = st.hasMoreTokens() ? st.nextToken() : null;
144                                if (next==null) {
145                                        ret.append(tkn);
146                                } else {
147                                        if (!next.equals(tokenSecondOpeningChar)) {
148                                                ret.append(tkn);
149                                        }
150                                               ret.append(next);
151                                }
152                        } else {
153                                ret.append(tkn);
154                        }
155                    }
156                    break;
157                case OPEN_SECOND:
158                    if (tokenClosingChar.equals(tkn)) {
159                            int pipeIdx = propTxt==null ? -1 : propTxt.indexOf('|');                            
160                        String propVal=expandToken(pipeIdx==-1 ? propTxt : propTxt.substring(0, pipeIdx), pipeIdx==-1 ? null : propTxt.substring(pipeIdx+1), stack==null ? new HashSet<String>() : stack);
161                        ret.append(propVal);
162                        propTxt=null;
163                        state=0;
164                    } else {
165                        if (propTxt==null) {
166                            propTxt=tkn;
167                        } else {
168                            propTxt+=tkn;
169                        }
170                    }
171                    break;
172                default:
173                    throw new IllegalStateException("Illegal parser state: "+state);
174            }
175        }
176        
177        if (state==OPEN_SECOND) {
178            ret.append(tokenFirstOpeningChar+tokenSecondOpeningChar+propTxt);
179        }
180        
181        return ret.toString();
182    }        
183}

[all classes][com.hammurapi.common]
EMMA 2.0.5312 EclEmma Fix 2 (C) Vladimir Roubtsov