Package com.google.dart.engine.internal.html.polymer

Source Code of com.google.dart.engine.internal.html.polymer.PolymerHtmlUnitBuilder$FoundTagDartElementError

/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.dart.engine.internal.html.polymer;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementAnnotation;
import com.google.dart.engine.element.ExternalHtmlScriptElement;
import com.google.dart.engine.element.FieldElement;
import com.google.dart.engine.element.HtmlScriptElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.polymer.PolymerAttributeElement;
import com.google.dart.engine.element.polymer.PolymerTagDartElement;
import com.google.dart.engine.element.polymer.PolymerTagHtmlElement;
import com.google.dart.engine.element.visitor.RecursiveElementVisitor;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.error.ErrorCode;
import com.google.dart.engine.error.PolymerCode;
import com.google.dart.engine.html.ast.HtmlScriptTagNode;
import com.google.dart.engine.html.ast.HtmlUnit;
import com.google.dart.engine.html.ast.XmlAttributeNode;
import com.google.dart.engine.html.ast.XmlTagNode;
import com.google.dart.engine.html.ast.visitor.RecursiveXmlVisitor;
import com.google.dart.engine.html.scanner.Token;
import com.google.dart.engine.internal.context.InternalAnalysisContext;
import com.google.dart.engine.internal.element.HtmlElementImpl;
import com.google.dart.engine.internal.element.polymer.PolymerAttributeElementImpl;
import com.google.dart.engine.internal.element.polymer.PolymerTagDartElementImpl;
import com.google.dart.engine.internal.element.polymer.PolymerTagHtmlElementImpl;
import com.google.dart.engine.internal.resolver.TypeProvider;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.utilities.source.LineInfo;

import java.util.List;
import java.util.Set;

/**
* Instances of the class {@link PolymerHtmlUnitBuilder} build Polymer specific elements.
*/
public class PolymerHtmlUnitBuilder extends RecursiveXmlVisitor<Void> {
  private static class FoundTagDartElementError extends Error {
    private final PolymerTagDartElementImpl result;

    public FoundTagDartElementError(PolymerTagDartElementImpl result) {
      this.result = result;
    }
  }

  private class NameToken {
    private final int offset;
    private final String value;

    public NameToken(int offset, String value) {
      this.offset = offset;
      this.value = value;
    }
  }

  /**
   * These names are forbidden to use as a custom tag name.
   * <p>
   * http://w3c.github.io/webcomponents/spec/custom/#concepts
   */
  private static final Set<String> FORBIDDEN_TAG_NAMES = Sets.newHashSet(new String[] {
      "annotation-xml", "color-profile", "font-face", "font-face-src", "font-face-uri",
      "font-face-format", "font-face-name", "missing-glyph",});

  @VisibleForTesting
  public static boolean isValidAttributeName(String name) {
    // cannot be empty
    if (name.isEmpty()) {
      return false;
    }
    // check characters
    int length = name.length();
    for (int i = 0; i < length; i++) {
      char c = name.charAt(i);
      if (i == 0) {
        if (!Character.isLetter(c)) {
          return false;
        }
      } else {
        if (!(Character.isLetterOrDigit(c) || c == '_')) {
          return false;
        }
      }
    }
    return true;
  }

  @VisibleForTesting
  public static boolean isValidTagName(String name) {
    // cannot be empty
    if (name.isEmpty()) {
      return false;
    }
    // check for forbidden name
    if (FORBIDDEN_TAG_NAMES.contains(name)) {
      return false;
    }
    // check characters
    int length = name.length();
    boolean hasDash = false;
    for (int i = 0; i < length; i++) {
      char c = name.charAt(i);
      // check for '-'
      if (c == '-') {
        hasDash = true;
      }
      // check character
      if (i == 0) {
        if (hasDash) {
          return false;
        }
        if (!Character.isLetter(c)) {
          return false;
        }
      } else {
        if (!(Character.isLetterOrDigit(c) || c == '-' || c == '_')) {
          return false;
        }
      }
    }
    if (!hasDash) {
      return false;
    }
    return true;
  }

  private final InternalAnalysisContext context;
  private final TypeProvider typeProvider;
  private final AnalysisErrorListener errorListener;
  private final Source source;
  private final LineInfo lineInfo;
  private final HtmlUnit unit;

  private final List<PolymerTagHtmlElement> tagHtmlElements = Lists.newArrayList();

  private XmlTagNode elementNode;
  private String elementName;
  private PolymerTagHtmlElementImpl htmlElement;
  private PolymerTagDartElementImpl dartElement;

  public PolymerHtmlUnitBuilder(InternalAnalysisContext context,
      AnalysisErrorListener errorListener, Source source, LineInfo lineInfo, HtmlUnit unit)
      throws AnalysisException {
    this.context = context;
    this.typeProvider = context.getTypeProvider();
    this.errorListener = errorListener;
    this.source = source;
    this.lineInfo = lineInfo;
    this.unit = unit;
  }

  /**
   * Builds Polymer specific HTML elements.
   */
  public void build() throws AnalysisException {
    unit.accept(this);
    // set Polymer tags
    HtmlElementImpl unitElement = (HtmlElementImpl) unit.getElement();
    unitElement.setPolymerTags(tagHtmlElements.toArray(new PolymerTagHtmlElement[tagHtmlElements.size()]));
  }

  @Override
  public Void visitXmlTagNode(XmlTagNode node) {
    if (node.getTag().equals("polymer-element")) {
      createTagHtmlElement(node);
    }
    // visit children
    return super.visitXmlTagNode(node);
  }

  private void createAttributeElements() {
    // prepare "attributes" attribute
    XmlAttributeNode attributesAttribute = elementNode.getAttribute("attributes");
    if (attributesAttribute == null) {
      return;
    }
    // check if there is a Dart part to resolve against it
    if (dartElement == null) {
      // TODO(scheglov) maybe report error (if it is allowed at all to have element without Dart part)
      return;
    }
    // prepare value of the "attributes" attribute
    String attributesText = attributesAttribute.getText();
    if (attributesText.trim().isEmpty()) {
      reportErrorForAttribute(attributesAttribute, PolymerCode.EMPTY_ATTRIBUTES);
      return;
    }
    // prepare attribute name tokens
    List<NameToken> nameTokens = Lists.newArrayList();
    {
      int index = 0;
      int textOffset = attributesAttribute.getTextOffset();
      int nameOffset = -1;
      StringBuilder nameBuilder = new StringBuilder();
      while (index < attributesText.length()) {
        char c = attributesText.charAt(index++);
        if (Character.isWhitespace(c)) {
          if (nameOffset != -1) {
            nameTokens.add(new NameToken(nameOffset, nameBuilder.toString()));
            nameBuilder = new StringBuilder();
            nameOffset = -1;
          }
          continue;
        }
        if (nameOffset == -1) {
          nameOffset = textOffset + index - 1;
        }
        nameBuilder.append(c);
      }
      if (nameOffset != -1) {
        nameTokens.add(new NameToken(nameOffset, nameBuilder.toString()));
        nameBuilder = new StringBuilder();
      }
    }
    // create attributes for name tokens
    List<PolymerAttributeElement> attributes = Lists.newArrayList();
    Set<String> definedNames = Sets.newHashSet();
    ClassElement classElement = dartElement.getClassElement();
    for (NameToken nameToken : nameTokens) {
      int offset = nameToken.offset;
      // prepare name
      String name = nameToken.value;
      if (!isValidAttributeName(name)) {
        reportErrorForNameToken(nameToken, PolymerCode.INVALID_ATTRIBUTE_NAME, name);
        continue;
      }
      if (!definedNames.add(name)) {
        reportErrorForNameToken(nameToken, PolymerCode.DUPLICATE_ATTRIBUTE_DEFINITION, name);
        continue;
      }
      // create attribute
      PolymerAttributeElementImpl attribute = new PolymerAttributeElementImpl(name, offset);
      attributes.add(attribute);
      // resolve field
      FieldElement field = classElement.getField(name);
      if (field == null) {
        reportErrorForNameToken(
            nameToken,
            PolymerCode.UNDEFINED_ATTRIBUTE_FIELD,
            name,
            classElement.getDisplayName());
        continue;
      }
      if (!isPublishedField(field)) {
        reportErrorForNameToken(
            nameToken,
            PolymerCode.ATTRIBUTE_FIELD_NOT_PUBLISHED,
            name,
            classElement.getDisplayName());
      }
      attribute.setField(field);
    }
    htmlElement.setAttributes(attributes.toArray(new PolymerAttributeElement[attributes.size()]));
  }

  private void createTagHtmlElement(XmlTagNode node) {
    this.elementNode = node;
    this.elementName = null;
    this.htmlElement = null;
    this.dartElement = null;
    // prepare 'name' attribute
    XmlAttributeNode nameAttribute = node.getAttribute("name");
    if (nameAttribute == null) {
      reportErrorForToken(node.getTagToken(), PolymerCode.MISSING_TAG_NAME);
      return;
    }
    // prepare name
    elementName = nameAttribute.getText();
    if (!isValidTagName(elementName)) {
      reportErrorForAttributeValue(nameAttribute, PolymerCode.INVALID_TAG_NAME, elementName);
      return;
    }
    // TODO(scheglov) Maybe check that at least one of "template" or "script" children.
    // TODO(scheglov) Maybe check if more than one top-level "template".
    // create HTML element
    int nameOffset = nameAttribute.getTextOffset();
    htmlElement = new PolymerTagHtmlElementImpl(elementName, nameOffset);
    // bind to the corresponding Dart element
    dartElement = findTagDartElement();
    if (dartElement != null) {
      htmlElement.setDartElement(dartElement);
      dartElement.setHtmlElement(htmlElement);
    }
    // TODO(scheglov) create attributes
    createAttributeElements();
    // done
    tagHtmlElements.add(htmlElement);
  }

  /**
   * Returns the {@link PolymerTagDartElement} that corresponds to the Polymer custom tag declared
   * by the given {@link XmlTagNode}.
   */
  private PolymerTagDartElementImpl findTagDartElement() {
    LibraryElement dartLibraryElement = getDartUnitElement();
    if (dartLibraryElement == null) {
      return null;
    }
    return findTagDartElement_inLibrary(dartLibraryElement);
  }

  /**
   * Returns the {@link PolymerTagDartElementImpl} declared in the given {@link LibraryElement} with
   * the {@link #elementName}. Maybe {@code null}.
   */
  private PolymerTagDartElementImpl findTagDartElement_inLibrary(LibraryElement library) {
    try {
      library.accept(new RecursiveElementVisitor<Void>() {
        @Override
        public Void visitPolymerTagDartElement(PolymerTagDartElement element) {
          if (element.getName().equals(elementName)) {
            throw new FoundTagDartElementError((PolymerTagDartElementImpl) element);
          }
          return null;
        }
      });
    } catch (FoundTagDartElementError e) {
      return e.result;
    }
    return null;
  }

  /**
   * Returns the only {@link LibraryElement} referenced by a direct {@code script} child. Maybe
   * {@code null} if none.
   */
  private LibraryElement getDartUnitElement() {
    // TODO(scheglov) Maybe check if more than one "script".
    for (XmlTagNode child : elementNode.getTagNodes()) {
      if (child instanceof HtmlScriptTagNode) {
        HtmlScriptElement scriptElement = ((HtmlScriptTagNode) child).getScriptElement();
        if (scriptElement instanceof ExternalHtmlScriptElement) {
          Source scriptSource = ((ExternalHtmlScriptElement) scriptElement).getScriptSource();
          if (scriptSource != null) {
            return context.getLibraryElement(scriptSource);
          }
        }
      }
    }
    return null;
  }

  private boolean isPublishedAnnotation(ElementAnnotation annotation) {
    Element element = annotation.getElement();
    if (element != null && element.getName().equals("published")) {
      return true;
    }
    return false;
  }

  private boolean isPublishedField(FieldElement field) {
    ElementAnnotation[] annotations = field.getMetadata();
    for (ElementAnnotation annotation : annotations) {
      if (isPublishedAnnotation(annotation)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Reports an error on the attribute's value, or (if absent) on the attribute's name.
   */
  private void reportErrorForAttribute(XmlAttributeNode node, ErrorCode errorCode,
      Object... arguments) {
    reportErrorForOffset(node.getOffset(), node.getLength(), errorCode, arguments);
  }

  /**
   * Reports an error on the attribute's value, or (if absent) on the attribute's name.
   */
  private void reportErrorForAttributeValue(XmlAttributeNode node, ErrorCode errorCode,
      Object... arguments) {
    Token valueToken = node.getValueToken();
    if (valueToken == null || valueToken.isSynthetic()) {
      reportErrorForAttribute(node, errorCode, arguments);
    } else {
      reportErrorForToken(valueToken, errorCode, arguments);
    }
  }

  private void reportErrorForNameToken(NameToken token, ErrorCode errorCode, Object... arguments) {
    int offset = token.offset;
    int length = token.value.length();
    reportErrorForOffset(offset, length, errorCode, arguments);
  }

  private void reportErrorForOffset(int offset, int length, ErrorCode errorCode,
      Object... arguments) {
    errorListener.onError(new AnalysisError(source, offset, length, errorCode, arguments));
  }

  private void reportErrorForToken(Token token, ErrorCode errorCode, Object... arguments) {
    int offset = token.getOffset();
    int length = token.getLength();
    reportErrorForOffset(offset, length, errorCode, arguments);
  }
}
TOP

Related Classes of com.google.dart.engine.internal.html.polymer.PolymerHtmlUnitBuilder$FoundTagDartElementError

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.