| /* |
| * Copyright 2008 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. |
| */ |
| package com.google.gwt.dev.js.ast; |
| |
| import com.google.gwt.dev.util.StringInterner; |
| |
| import java.io.Serializable; |
| import java.util.List; |
| |
| /** |
| * A scope is a factory for creating and allocating {@link com.google.gwt.dev.js.ast.JsName}s. A |
| * JavaScript AST is built in terms of abstract name objects without worrying about obfuscation, |
| * keyword/identifier blacklisting, and so on. |
| * |
| * <p> |
| * |
| * Scopes are associated with {@link com.google.gwt.dev.js.ast.JsFunction}s, but the two are not |
| * equivalent. Functions <i>have</i> scopes, but a scope does not necessarily have an associated |
| * Function. Examples of this include the {@link com.google.gwt.dev.js.ast.JsRootScope} and |
| * synthetic scopes that might be created by a client. |
| * |
| * <p> |
| * |
| * Scopes can have parents to provide constraints when allocating actual identifiers for names. |
| * Specifically, names in child scopes are chosen such that they do not conflict with names in their |
| * parent scopes. The ultimate parent is usually the global scope (see |
| * {@link com.google.gwt.dev.js.ast.JsProgram#getScope()}), but parentless scopes are useful |
| * for managing names that are always accessed with a qualifier and could therefore never be |
| * confused with the global scope hierarchy. |
| */ |
| public abstract class JsScope implements Serializable { |
| |
| private final String description; |
| |
| protected JsScope(String description) { |
| this.description = StringInterner.get().intern(description); |
| } |
| |
| /** |
| * Gets a name object associated with the specified ident in this scope, creating it if necessary. |
| * |
| * @param ident An identifier that is unique within this scope. |
| */ |
| public final JsName declareName(String ident) { |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name != null) { |
| return name; |
| } |
| return doCreateName(ident, ident); |
| } |
| |
| /** |
| * Gets a name object associated with the specified ident in this scope, creating it if necessary. |
| * |
| * @param ident An identifier that is unique within this scope. |
| * @param shortIdent A "pretty" name that does not have to be unique. |
| * @throws IllegalArgumentException if ident already exists in this scope but the requested short |
| * name does not match the existing short name. |
| */ |
| public final JsName declareName(String ident, String shortIdent) { |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name != null) { |
| if (!name.getShortIdent().equals(shortIdent)) { |
| throw new IllegalArgumentException("Requested short name " + shortIdent |
| + " conflicts with preexisting short name " + name.getShortIdent() + " for identifier " |
| + ident); |
| } |
| return name; |
| } |
| return doCreateName(ident, shortIdent); |
| } |
| |
| /** |
| * Gets a name object associated with the specified ident in this scope, creating it if necessary, |
| * and makes it non obfuscatable. |
| * |
| * @param ident An identifier that is unique within this scope. |
| */ |
| public final JsName declareUnobfuscatableName(String ident) { |
| JsName name = declareName(ident); |
| name.setUnobfuscatable(); |
| return name; |
| } |
| |
| /** |
| * Attempts to find the name object for the specified ident, searching in this scope, and if not |
| * found, in the parent scopes. |
| * |
| * @return <code>null</code> if the identifier has no associated name |
| */ |
| public final JsName findExistingName(String ident) { |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name == null && getParent() != null) { |
| return getParent().findExistingName(ident); |
| } |
| return name; |
| } |
| |
| /** |
| * Attempts to find an unobfuscatable name object for the specified ident, searching in this |
| * scope, and if not found, in the parent scopes. |
| * |
| * @return <code>null</code> if the identifier has no associated name |
| */ |
| public final JsName findExistingUnobfuscatableName(String ident) { |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name != null && name.isObfuscatable()) { |
| name = null; |
| } |
| if (name == null && getParent() != null) { |
| return getParent().findExistingUnobfuscatableName(ident); |
| } |
| return name; |
| } |
| |
| /** |
| * Returns an iterable for all the names defined by this scope. |
| */ |
| public abstract Iterable<JsName> getAllNames(); |
| |
| /** |
| * Returns a list of this scope's child scopes. |
| */ |
| public abstract List<JsScope> getChildren(); |
| |
| /** |
| * Returns the descriptive name. |
| */ |
| public String getDescription() { |
| return description; |
| } |
| /** |
| * Returns the parent scope of this scope, or <code>null</code> if this is the root scope. |
| */ |
| public abstract JsScope getParent(); |
| |
| @Override |
| public final String toString() { |
| if (getParent() != null) { |
| return description + "->" + getParent(); |
| } else { |
| return description; |
| } |
| } |
| |
| protected abstract void addChild(JsScope child); |
| |
| /** |
| * Creates a new name in this scope. |
| */ |
| protected abstract JsName doCreateName(String ident, String shortIdent); |
| |
| /** |
| * Attempts to find the name object for the specified ident, searching in this scope only. |
| * |
| * @return <code>null</code> if the identifier has no associated name |
| */ |
| protected abstract JsName findExistingNameNoRecurse(String ident); |
| } |