/**
  * Helper to determine if a give MXMLUnitData is the only Text child of an MXMLTagData This
  * implies special, different processing from normal Text Datas.
  */
 private boolean isOnlyTextChild(MXMLUnitData child) {
   if (child instanceof MXMLTextData
       && ((MXMLTextData) child).getTextType() == IMXMLTextData.TextType.TEXT) {
     MXMLUnitData p = child.getParentUnitData();
     MXMLTagData parent = p instanceof MXMLTagData ? (MXMLTagData) p : null;
     if (parent != null) {
       return parent.getFirstChildUnit() == child && child.getNextSiblingUnit() == null;
     }
   }
   return false;
 }
 /** Generate an AET Name that corresponds to the tag passed in */
 private Name getNameForTag(MXMLTagData tag) {
   if (tag == rootTag) {
     return new Name(parent.getEffectiveID());
   } else {
     String uri = tag.getURI();
     if (uri != null) {
       return new Name(new Namespace(ABCConstants.CONSTANT_Namespace, uri), tag.getShortName());
     } else {
       return new Name(tag.getShortName());
     }
   }
 }
  /**
   * Get the index of a text data. Grabs the parent, and iterates it's children to find out what the
   * index of the text data passed in should be
   */
  private int getIndexOfText(MXMLTextData text) {
    MXMLUnitData parent = text.getParentUnitData();

    MXMLTagData parentTag = parent instanceof MXMLTagData ? (MXMLTagData) parent : null;
    int index = 0;

    if (parentTag != null) {
      for (MXMLUnitData d = parentTag.getFirstChildUnit(); d != null; d = d.getNextSiblingUnit()) {
        if (d == text) break;
        else if (d instanceof MXMLTextData
            && ((MXMLTextData) d).getTextType() == IMXMLTextData.TextType.CDATA) ++index;
      }
    }
    return index;
  }
 /**
  * Do a lookup of a prefix, starting with the specified tag. This method will record if the prefix
  * is defined outside of the <fx:XML> tag. If it is defined outside of the tag, then the prefix
  * will need to be added to the root XML tag as an 'xmlns' so that it will still be accessible.
  *
  * @param prefix the prefix to look for
  * @param tag the tag to start looking in
  */
 void lookupPrefix(String prefix, MXMLTagData tag) {
   while (tag != null) {
     PrefixMap pm = tag.getPrefixMap();
     if (pm != null && pm.containsPrefix(prefix))
       // we found the prefix, and haven't gone past the root tag, so don't need to do anything
       // special
       return;
     else if (tag == rootTag)
       // don't look past the root tag
       tag = null;
     else tag = tag.getParentTag();
   }
   // We will only get here if we have traversed past the root XML tag, and haven't found the
   // prefix yet
   // in that case, record the prefix if its defined somewhere else in the MXML document, so we can
   // make sure it ends up in the resulting XML literal.
   if (externalPrefixes.containsPrefix(prefix)) referencedPrefixes.add(prefix);
 }
  /**
   * Get the index of a tag. Grabs the parent tag, and iterates it's children to find out what the
   * index of the tag passed in should be
   */
  private int getIndexOfTag(MXMLTagData tag) {
    MXMLTagData parent = tag.getParentTag();

    int index = 0;
    for (MXMLTagData d = parent.getFirstChild(true); d != null; d = d.getNextSibling(true)) {
      if (d == tag) break;
      else if (d.getName().equals(tag.getName())) ++index;
    }
    return index;
  }
  /**
   * Process an MXMLTagData - this will write a String representation of the tag into the
   * StringWriter passed in. This will strip out any databinding expressions from the String, TODO:
   * databinding - add the databinding expressions as children of the MXMLXMLNode, and also record
   * what the TODO: target expressions for those are (these are the expressions to set the value in
   * the XML object when the TODO: PropertyChange event fires).
   */
  void processNode(MXMLTagData tag, StringWriter sw) {
    sw.write('<');
    if (tag.isCloseTag()) sw.write('/');
    sw.write(tag.getName());
    String tagPrefix = tag.getPrefix();

    // lookup the prefix in case it's defined elsewhere in the document outside the XML tag
    if (tagPrefix != null) lookupPrefix(tagPrefix, tag);

    List<MXMLTagAttributeData> attrs = getAttributes(tag);
    for (MXMLTagAttributeData attr : attrs) {
      sw.write(' ');
      sw.write(attr.getName());
      sw.write('=');
      sw.write('"');
      sw.write(attr.getRawValue());
      sw.write('"');

      // lookup the prefix in case it's defined outside the XML tag
      String prefix = attr.getPrefix();
      if (prefix != null) lookupPrefix(prefix, tag);
    }

    StringWriter childrenSW = new StringWriter();
    for (MXMLUnitData unit = tag.getFirstChildUnit();
        unit != null;
        unit = unit.getNextSiblingUnit()) {
      processNode(unit, childrenSW);
    }

    if (tag == rootTag) {
      // If we're the root tag, then add an xmlns for each prefix that was referenced by one of our
      // children, but was defined elsewhere in the document (like in the Application tag).
      for (String prefix : referencedPrefixes) {
        String uri = externalPrefixes.getNamespaceForPrefix(prefix);
        if (uri != null) {
          sw.write(" xmlns:");
          sw.write(prefix);
          sw.write("=\"");
          sw.write(uri);
          sw.write('\"');
        }
      }
    }
    if (tag.isEmptyTag()) {
      sw.write("/>");
    } else {
      sw.write('>');
    }
    sw.write(childrenSW.toString());

    MXMLTagData endTag = tag.findMatchingEndTag();
    if (endTag != null) {
      processNode(endTag, sw);
    }
  }
  /**
   * Get a list of attributes, filtering out the attributes that have data binding values.
   *
   * @param tag The
   * @return
   */
  List<MXMLTagAttributeData> getAttributes(MXMLTagData tag) {
    MXMLTagAttributeData[] rawAttrs = tag.getAttributeDatas();
    if (rawAttrs != null) {
      ArrayList<MXMLTagAttributeData> attrs = new ArrayList<MXMLTagAttributeData>(rawAttrs.length);

      for (MXMLTagAttributeData attr : rawAttrs) {
        IMXMLSingleDataBindingNode db = null;
        if ((db = parseBindingExpression(attr)) != null) {
          //   do databinding stuff:
          //      1.  Walk up parent chain to compute target expression
          //      2.  Parse databinding expression
          //      3.  Save off both those pieces of data for use during codegen

          databindings.add(generateBindingNode(attr, db));
        } else {
          attrs.add(attr);
        }
      }
      return attrs;
    }
    return Collections.emptyList();
  }
  @Override
  protected void processChildTag(
      MXMLTreeBuilder builder, MXMLTagData tag, MXMLTagData childTag, MXMLNodeInfo info) {
    if (info.hasSpecifierWithName(childTag.getShortName(), childTag.getStateName())) {
      ICompilerProblem problem = new MXMLDuplicateChildTagProblem(childTag);
      builder.addProblem(problem);
      return;
    }

    FlexProject project = builder.getProject();

    // Handle child tags that are property/style/event specifiers.
    MXMLSpecifierNodeBase childNode = createSpecifierNode(builder, childTag.getShortName());
    if (childNode != null) {
      // This tag is not part of the default property value.
      processNonDefaultPropertyContentUnit(builder, info);

      childNode.setSuffix(builder, childTag.getStateName());
      childNode.initializeFromTag(builder, childTag);
      info.addChildNode(childNode);
    } else if (builder.getFileScope().isScriptTag(childTag)
        && (builder.getMXMLDialect().isEqualToOrBefore(MXMLDialect.MXML_2009))) {
      // In MXML 2006 and 2009, allow a <Script> tag
      // inside any class reference tag

      if (!processingDefaultProperty) {
        // Not processing the default property, just make a script
        // node and put it in the tree.
        MXMLScriptNode scriptNode = new MXMLScriptNode(this);
        scriptNode.initializeFromTag(builder, childTag);
        info.addChildNode(scriptNode);
      } else {
        // We are processing a default property.  Script nodes need
        // to be a child of that default specifier nodes so that
        // finding a node by offset works properly.
        // See: http://bugs.adobe.com/jira/browse/CMP-955
        processDefaultPropertyContentUnit(builder, childTag, info);
      }

    } else if (builder.getFileScope().isReparentTag(childTag)) {
      MXMLReparentNode reparentNode = new MXMLReparentNode(this);
      reparentNode.initializeFromTag(builder, childTag);
      info.addChildNode(reparentNode);
    } else {
      IDefinition definition = builder.getFileScope().resolveTagToDefinition(childTag);
      if (definition instanceof ClassDefinition) {
        // Handle child tags that are instance tags.

        IVariableDefinition defaultPropertyDefinition = getDefaultPropertyDefinition(builder);
        if (defaultPropertyDefinition != null && !processedDefaultProperty) {
          // Since there is a default property and we haven't already processed it,
          // assume this child instance tag is part of its value.
          processDefaultPropertyContentUnit(builder, childTag, info);
        } else {
          // This tag is not part of the default property value.
          processNonDefaultPropertyContentUnit(builder, info);

          MXMLInstanceNode instanceNode =
              MXMLInstanceNode.createInstanceNode(builder, definition.getQualifiedName(), this);
          instanceNode.setClassReference(
              project,
              (IClassDefinition) definition); // TODO Move this logic to initializeFromTag().
          instanceNode.initializeFromTag(builder, childTag);
          info.addChildNode(instanceNode);
        }
      } else {
        // Handle child tags that are something other than property/style/event tags
        // or instance tags.

        // This tag is not part of the default property value.
        processNonDefaultPropertyContentUnit(builder, info);

        super.processChildTag(builder, tag, childTag, info);
      }
    }
  }