All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.rhino.QualifiedName Maven / Gradle / Ivy

/*
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Norris Boyd
 *   Roger Lawrence
 *   Mike McCabe
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package com.google.javascript.rhino;

import com.google.common.collect.ImmutableList;
import javax.annotation.Nullable;

/**
 * Abstraction over a qualified name. Unifies Node-based qualified names and string-based names,
 * allowing to lazily parse strings and represent a pre-parsed qualified name without the overhead
 * of a whole Node. Essentially, a qualified name is a linked list of {@linkplain #getComponent
 * components}, starting from the outermost property access and ending with the root of the name,
 * which is a {@linkplain #isSimple simple name} with no {@linkplain #getOwner owner}.
 */
public abstract class QualifiedName {

  // All subclasses must be defined in this file.
  private QualifiedName() {}

  public static QualifiedName of(String string) {
    int lastIndex = 0;
    int index;
    ImmutableList.Builder builder = ImmutableList.builder();
    do {
      index = string.indexOf('.', lastIndex);
      builder.add(string.substring(lastIndex, index < 0 ? string.length() : index).intern());
      lastIndex = index + 1;
    } while (index >= 0);
    ImmutableList terms = builder.build();
    return new StringListQname(terms, terms.size());
  }

  /**
   * Returns the qualified name of the owner, or null for simple names. For the name "foo.bar.baz",
   * this returns an object representing "foo.bar".
   */
  @Nullable
  public abstract QualifiedName getOwner();

  /**
   * Returns outer-most term of this qualified name, or the entire name for simple names. For the
   * name "foo.bar.baz", this returns "baz".
   */
  public abstract String getComponent();

  /** Returns true if this is a simple name. */
  public abstract boolean isSimple();

  // TODO(sdh): We'll probably want a getRoot() method, which would be useful once this is
  // integrated
  // further into TypedScopeCreator.

  /** Appends the joined qualified name to the given StringBuilder. */
  abstract void appendTo(StringBuilder sb);

  /** Checks whether the given node matches this name. */
  public abstract boolean matches(Node n);

  /** Returns the root of this name, e.g. "foo" from "foo.bar.baz". */
  public String getRoot() {
    QualifiedName name = this;
    while (!name.isSimple()) {
      name = name.getOwner();
    }
    return name.getComponent();
  }

  /**
   * Returns the components of this name as an iterable of strings, starting at the root. For the
   * qualified name foo.bar.baz, this returns ["foo", "bar", "baz"].
   */
  public Iterable components() {
    ImmutableList.Builder components = ImmutableList.builder();
    buildComponents(components);
    return components.build();
  }

  private void buildComponents(ImmutableList.Builder builder) {
    QualifiedName owner = getOwner();
    if (owner != null) {
      owner.buildComponents(builder);
    }
    builder.add(getComponent());
  }

  /** Returns the qualified name as a string. */
  public String join() {
    StringBuilder sb = new StringBuilder();
    appendTo(sb);
    return sb.toString();
  }

  /**
   * Returns a new qualified name object with {@code this} name as the owner and the given string as
   * the property name.
   */
  public QualifiedName getprop(String propertyName) {
    return new GetpropQname(this, propertyName);
  }

  /** A qualified name based on a list of string terms. */
  private static class StringListQname extends QualifiedName {
    final ImmutableList terms;
    final int size;

    StringListQname(ImmutableList terms, int size) {
      this.terms = terms;
      this.size = size;
    }

    @Override
    public QualifiedName getOwner() {
      return size > 1 ? new StringListQname(terms, size - 1) : null;
    }

    @Override
    public String getComponent() {
      return terms.get(size - 1);
    }

    @Override
    public boolean isSimple() {
      return size == 1;
    }

    @Override
    void appendTo(StringBuilder sb) {
      for (int i = 0; i < size; i++) {
        if (i > 0) {
          sb.append('.');
        }
        sb.append(terms.get(i));
      }
    }

    @Override
    public Iterable components() {
      return terms.subList(0, size);
    }

    @Override
    public boolean matches(Node n) {
      int pos = size - 1;
      while (pos > 0 && n.isGetProp()) {
        // NOTE: these strings are all interned, so we can do identity comparison.
        if (n.getLastChild().getString() != terms.get(pos)) {
          return false;
        }
        pos--;
        n = n.getFirstChild();
      }
      if (pos > 0) {
        return false;
      }
      switch (n.getToken()) {
        case NAME:
        case MEMBER_FUNCTION_DEF:
          return terms.get(0) == n.getString();
        case THIS:
          return terms.get(0) == THIS;
        case SUPER:
          return terms.get(0) == SUPER;
        default:
          return false;
      }
    }
  }

  /** A qualified name built with an extra property access on an existing qualified name. */
  private static class GetpropQname extends QualifiedName {
    final QualifiedName owner;
    final String prop;

    GetpropQname(QualifiedName owner, String prop) {
      this.owner = owner;
      this.prop = prop.intern();
    }

    @Override
    public QualifiedName getOwner() {
      return owner;
    }

    @Override
    public String getComponent() {
      return prop;
    }

    @Override
    public boolean isSimple() {
      return false;
    }

    @Override
    void appendTo(StringBuilder sb) {
      owner.appendTo(sb);
      sb.append('.').append(prop);
    }

    @Override
    public boolean matches(Node n) {
      return n.isGetProp()
          && n.getLastChild().getString() == prop
          && owner.matches(n.getFirstChild());
    }
  }

  /**
   * A qualified name from a node. The precondition that the node is actually a qualified name is
   * not actually checked here, though it will throw IllegalStateException eventually if it is not.
   */
  static final class NodeQname extends QualifiedName {
    private final Node node;

    NodeQname(Node n) {
      this.node = n;
    }

    @Override
    public QualifiedName getOwner() {
      return node.isGetProp() ? new NodeQname(node.getFirstChild()) : null;
    }

    @Override
    public String getComponent() {
      switch (node.getToken()) {
        case GETPROP:
          return node.getLastChild().getString();
        case THIS:
          return THIS;
        case SUPER:
          return SUPER;
        case NAME:
        case MEMBER_FUNCTION_DEF:
          return node.getString();
        default:
          throw new IllegalStateException("Not a qualified name: " + node);
      }
    }

    @Override
    public boolean isSimple() {
      return !node.isGetProp();
    }

    @Override
    void appendTo(StringBuilder sb) {
      sb.append(join());
    }

    @Override
    public String join() {
      return node.getQualifiedName();
    }

    @Override
    public boolean matches(Node n) {
      return n.matchesQualifiedName(node);
    }
  }

  private static final String THIS = "this".intern();
  private static final String SUPER = "super".intern();
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy