View Javadoc

1   /*
2    * Version: MPL 1.1
3    *
4    * The contents of this file are subject to the Mozilla Public License Version
5    * 1.1 (the "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    * http://www.mozilla.org/MPL/
8    *
9    * Software distributed under the License is distributed on an "AS IS" basis,
10   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11   * for the specific language governing rights and limitations under the
12   * License.
13   *
14   * The Original Code is Rhino code, released
15   * May 6, 1999.
16   *
17   * The Initial Developer of the Original Code is
18   * Netscape Communications Corporation.
19   * Portions created by the Initial Developer are Copyright (C) 1997-1999
20   * the Initial Developer. All Rights Reserved.
21   *
22   * Contributor(s):
23   *   Alex Russell
24   *   Richard Backhouse
25   */
26   
27  package org.dojotoolkit.shrinksafe;
28  
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  
35  import org.mozilla.javascript.ScriptOrFnNode;
36  import org.mozilla.javascript.ScriptRuntime;
37  import org.mozilla.javascript.Token;
38  
39  public class TokenMapper {
40  	private List functionBracePositions = new ArrayList();
41  
42  	/**
43  	 * Map of all replaced tokens
44  	 */
45  	private List replacedTokens = new ArrayList();
46  
47  	/**
48  	 * Map of each Function node and all the variables in its current function
49  	 * scope, other variables found while traversing the prototype chain and
50  	 * variables found in the top-level scope.
51  	 */
52  	private List functionVarMappings = new ArrayList();
53  	private Map debugDataList = new HashMap();
54  
55  	private int functionNum = 0;
56  
57  	private int parentScope = 0;
58  
59  	private int lastTokenCount = 0;
60  	
61  	public TokenMapper(ScriptOrFnNode parseTree) {
62  		collectFunctionMappings(parseTree);
63  	}
64  
65  	public void incrementFunctionNumber() {
66  		functionNum++;
67  	}
68  	
69  	/**
70  	 * Generate new compressed tokens
71  	 * <p>
72  	 * 
73  	 * @param token
74  	 *            value of the string token
75  	 * @param hasNewMapping
76  	 *            boolean value indicating a new variable binding
77  	 * @return compressed token
78  	 */
79  	private String getMappedToken(String token, boolean hasNewMapping) {
80  		String newToken = null;
81  		Map tokens = null;
82  		String blank = new String("");
83  		int localScope = functionBracePositions.size() - 1;
84  
85  		String oldToken = getPreviousTokenMapping(token, hasNewMapping);
86  
87  		if (!oldToken.equalsIgnoreCase(blank)) {
88  			return oldToken;
89  		} else if ((hasNewMapping || isInScopeChain(token))) {
90  			newToken = new String("_" + Integer.toHexString(++lastTokenCount));
91  			if (token.equals("$super") || (newToken.length() >= token.length() && token.charAt(0) != '_')) {
92  				newToken = token;
93  				lastTokenCount--;
94  			}
95  			tokens = (Map) replacedTokens.get(hasNewMapping ? localScope : parentScope);
96  			tokens.put(token, newToken);
97  			return newToken;
98  		}
99  		return token;
100 	}
101 
102 	/**
103 	 * Checks for variable names in prototype chain
104 	 * <p>
105 	 * 
106 	 * @param token
107 	 *            value of the string token
108 	 * @return boolean value indicating if the token is present in the chained
109 	 *         scope
110 	 */
111 	private boolean isInScopeChain(String token) {
112 		int scope = functionBracePositions.size();
113 		Map chainedScopeVars = (Map) functionVarMappings.get(functionNum);
114 		if (!chainedScopeVars.isEmpty()) {
115 			for (int i = scope; i > 0; i--) {
116 				if (chainedScopeVars.containsKey(new Integer(i))) {
117 					parentScope = i - 1;
118 					List temp = Arrays.asList((String[]) chainedScopeVars.get(new Integer(i)));
119 					if (temp.indexOf(token) != -1) {
120 						return true;
121 					}
122 				}
123 			}
124 		}
125 		return false;
126 	}
127 
128 	/**
129 	 * Checks previous token mapping
130 	 * <p>
131 	 * 
132 	 * @param token
133 	 *            value of the string token
134 	 * @param hasNewMapping
135 	 *            boolean value indicating a new variable binding
136 	 * @return string value of the previous token or blank string
137 	 */
138 	private String getPreviousTokenMapping(String token, boolean hasNewMapping) {
139 		String result = new String("");
140 		int scope = replacedTokens.size() - 1;
141 
142 		if (scope < 0) {
143 			return result;
144 		}
145 
146 		if (hasNewMapping) {
147 			Map tokens = (Map) (replacedTokens.get(scope));
148 			if (tokens.containsKey(token)) {
149 				result = (String) tokens.get(token);
150 				return result;
151 			}
152 		} else {
153 			for (int i = scope; i > -1; i--) {
154 				Map tokens = (Map) (replacedTokens.get(i));
155 				if (tokens.containsKey(token)) {
156 					result = (String) tokens.get(token);
157 					return result;
158 				}
159 			}
160 		}
161 		return result;
162 	}
163 
164 	/**
165 	 * Generate mappings for each Function node and parameters and variables
166 	 * names associated with it.
167 	 * <p>
168 	 * 
169 	 * @param parseTree
170 	 *            Mapping for each function node and corresponding parameters &
171 	 *            variables names
172 	 */
173 	private void collectFunctionMappings(ScriptOrFnNode parseTree) {
174 		int level = -1;
175 		collectFuncNodes(parseTree, level, null);
176 	}
177 
178 	/**
179 	 * Recursive method to traverse all Function nodes
180 	 * <p>
181 	 * 
182 	 * @param parseTree
183 	 *            Mapping for each function node and corresponding parameters &
184 	 *            variables names
185 	 * @param level
186 	 *            scoping level
187 	 */
188 	private void collectFuncNodes(ScriptOrFnNode parseTree, int level, ScriptOrFnNode parent) {
189 		level++;
190 		
191         DebugData debugData = new DebugData();
192         debugData.start = parseTree.getBaseLineno();
193         debugData.end = parseTree.getEndLineno();
194         debugData.paramAndVarNames = parseTree.getParamAndVarNames();
195         debugDataList.put(new Integer(parseTree.getEncodedSourceStart()), debugData);
196         
197 		functionVarMappings.add(new HashMap());
198 
199 		Map bindingNames = (Map) functionVarMappings.get(functionVarMappings.size() - 1);
200 		bindingNames.put(new Integer(level), parseTree.getParamAndVarNames());
201 
202 	    if (parent != null) {
203 	        bindingNames.put(new Integer(level-1), parent.getParamAndVarNames());
204 	    }
205 	    
206 		int nestedCount = parseTree.getFunctionCount();
207 		for (int i = 0; i != nestedCount; ++i) {
208 			collectFuncNodes(parseTree.getFunctionNode(i), level, parseTree);
209 			bindingNames = (Map) functionVarMappings.get(functionVarMappings.size() - 1);
210 			bindingNames.put(new Integer(level), parseTree.getParamAndVarNames());
211         }
212 	}
213 
214 	/**
215 	 * Compress the script
216 	 * <p>
217 	 * 
218 	 * @param encodedSource
219 	 *            encoded source string
220 	 * @param offset
221 	 *            position within the encoded source
222 	 * @param asQuotedString
223 	 *            boolean value indicating a quoted string
224 	 * @param sb
225 	 *            String buffer reference
226 	 * @param prevToken
227 	 *            Previous token in encoded source
228 	 * @param inArgsList
229 	 *            boolean value indicating position inside arguments list
230 	 * @param currentLevel
231 	 *            embeded function level
232 	 * @param parseTree
233 	 *            Mapping of each function node and corresponding parameters &
234 	 *            variables names
235 	 * @return compressed script
236 	 */
237 	public int sourceCompress(String encodedSource, int offset,
238 			boolean asQuotedString, StringBuffer sb, int prevToken,
239 			boolean inArgsList, int currentLevel, ReplacedTokens replacedTokens) {
240 
241 		boolean hasNewMapping = false;
242 
243 		int length = encodedSource.charAt(offset);
244 		++offset;
245 		if ((0x8000 & length) != 0) {
246 			length = ((0x7FFF & length) << 16) | encodedSource.charAt(offset);
247 			++offset;
248 		}
249 		String str = encodedSource.substring(offset, offset + length);
250 		if ((prevToken == Token.VAR) || (inArgsList)) {
251 			hasNewMapping = true;
252 		}
253 		if (sb != null) {
254 			String sourceStr = new String(str);
255 			
256 			if (((functionBracePositions.size() > 0) && 
257 				(currentLevel >= (((Integer) functionBracePositions.get(functionBracePositions.size() - 1)).intValue()))) || 
258 				(inArgsList)) {
259 				if (prevToken != Token.DOT) {
260 					// Look for replacement token in provided lookup object.
261 					str = replacedTokens.find(str);
262 				}
263 			}
264 			if ((!inArgsList) && (asQuotedString)) {
265 				if ((prevToken == Token.LC) || (prevToken == Token.COMMA)) {
266 					str = sourceStr;
267 				}
268 			}
269 			if (!asQuotedString) {
270 				sb.append(str);
271 			} else {
272 				sb.append('"');
273 				sb.append(ScriptRuntime.escapeString(str));
274 				sb.append('"');
275 			}
276 		}
277 		else if (((functionBracePositions.size() > 0) && 
278 				(currentLevel >= (((Integer) functionBracePositions.get(functionBracePositions.size() - 1)).intValue()))) || 
279 				(inArgsList)) {
280 			if (prevToken != Token.DOT) {
281 				getMappedToken(str, hasNewMapping);
282 			}
283 		}
284 		return offset + length;
285 	}
286 
287 	public void enterNestingLevel(int braceNesting) {
288 		functionBracePositions.add(new Integer(braceNesting + 1));
289 		replacedTokens.add(new HashMap());
290 	}
291 
292 	public boolean leaveNestingLevel(int braceNesting) {
293 		boolean tokensRemoved = false;
294 		Integer bn = new Integer(braceNesting);
295 
296 		if ((functionBracePositions.contains(bn)) && (replacedTokens.size() > 0)) {
297 			// remove our mappings now!
298 			int scopedSize = replacedTokens.size();
299 			replacedTokens.remove(scopedSize - 1);
300 			functionBracePositions.remove(bn);
301 			tokensRemoved = true;
302 		}
303 		return tokensRemoved;
304 	}
305 	
306 	public Map getCurrentTokens() {
307 		Map m = null;
308 		if (replacedTokens.size() > 0) {
309 			m = (Map)replacedTokens.get(replacedTokens.size() - 1);
310 		}
311 		return m;
312 	}
313 	
314 	public DebugData getDebugData(Integer functionPosition) {
315 		return (DebugData)debugDataList.get(functionPosition);
316 	}
317 	
318 	public void reset() {
319 		functionNum = 0;
320 		parentScope = 0;
321 		lastTokenCount = 0;
322 		functionBracePositions = new ArrayList();
323 		replacedTokens = new ArrayList();
324 	}
325 }