001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.js;
017:
018: import com.google.gwt.dev.js.ast.JsName;
019: import com.google.gwt.dev.js.ast.JsProgram;
020: import com.google.gwt.dev.js.ast.JsRootScope;
021: import com.google.gwt.dev.js.ast.JsScope;
022:
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Set;
027:
028: /**
029: * A namer that uses short, readable idents to maximize reability.
030: */
031: public class JsPrettyNamer {
032:
033: public static void exec(JsProgram program) {
034: new JsPrettyNamer(program).execImpl();
035: }
036:
037: /**
038: * Communicates to a parent scope all the idents used by all child scopes.
039: */
040: private Set<String> childIdents = null;
041:
042: private final JsProgram program;
043:
044: public JsPrettyNamer(JsProgram program) {
045: this .program = program;
046: }
047:
048: private void execImpl() {
049: visit(program.getRootScope());
050: }
051:
052: private boolean isLegal(JsScope scope, Set<String> childIdents,
053: String newIdent) {
054: if (JsKeywords.isKeyword(newIdent)) {
055: return false;
056: }
057: if (childIdents.contains(newIdent)) {
058: // one of my children already claimed this ident
059: return false;
060: }
061: /*
062: * Never obfuscate a name into an identifier that conflicts with an existing
063: * unobfuscatable name! It's okay if it conflicts with an existing
064: * obfuscatable name; that name will get obfuscated out of the way.
065: */
066: return (scope.findExistingUnobfuscatableName(newIdent) == null);
067: }
068:
069: private void visit(JsScope scope) {
070: // Save off the childIdents which is currently being computed for my parent.
071: Set<String> myChildIdents = childIdents;
072:
073: /*
074: * Visit my children first. Reset childIdents so that my children will get a
075: * clean slate: I do not communicate to my children.
076: */
077: childIdents = new HashSet<String>();
078: List<JsScope> children = scope.getChildren();
079: for (Iterator<JsScope> it = children.iterator(); it.hasNext();) {
080: visit(it.next());
081: }
082:
083: JsRootScope rootScope = program.getRootScope();
084: if (scope == rootScope) {
085: return;
086: }
087:
088: // Visit all my idents.
089: for (Iterator<JsName> it = scope.getAllNames(); it.hasNext();) {
090: JsName name = it.next();
091: if (!name.isObfuscatable()) {
092: // Unobfuscatable names become themselves.
093: name.setShortIdent(name.getIdent());
094: continue;
095: }
096:
097: String newIdent = name.getShortIdent();
098: if (!isLegal(scope, childIdents, newIdent)) {
099: String checkIdent = newIdent;
100: for (int i = 0; true; ++i) {
101: checkIdent = newIdent + "_" + i;
102: if (isLegal(scope, childIdents, checkIdent)) {
103: break;
104: }
105: }
106: name.setShortIdent(checkIdent);
107: } else {
108: // nothing to do; the short name is already good
109: }
110: childIdents.add(name.getShortIdent());
111: }
112: myChildIdents.addAll(childIdents);
113: childIdents = myChildIdents;
114: }
115: }
|