/**
   * Returns a pseudo-set (a case insensitive map where the values are always true) for all the
   * embedded component ids in the component.
   *
   * @param componentModel
   * @return map whose keys are the embedded component ids
   */
  private Map<String, Boolean> collectedEmbeddedComponentIds(ComponentModel componentModel) {
    Map<String, Boolean> result = CollectionFactory.newCaseInsensitiveMap();

    for (String id : componentModel.getEmbeddedComponentIds()) result.put(id, true);

    return result;
  }
  public Map<String, Binding> getInformalParameterBindings() {
    Map<String, Binding> result = CollectionFactory.newMap();

    for (String name : NamedSet.getNames(bindings)) {
      if (componentModel.getParameterModel(name) != null) continue;

      result.put(name, bindings.get(name));
    }

    return result;
  }
  /**
   * Invoked when a component's end tag is reached, to check and process informal parameters as per
   * the {@link org.apache.tapestry5.model.EmbeddedComponentModel#getInheritInformalParameters()}
   * flag.
   *
   * @param loadingComponent the container component that was loaded
   * @param model
   * @param newComponent
   * @param newComponentBindings
   */
  private void handleInformalParameters(
      ComponentPageElement loadingComponent,
      EmbeddedComponentModel model,
      ComponentPageElement newComponent,
      ComponentModel newComponentModel,
      Map<String, Binding> newComponentBindings) {

    Map<String, Binding> informals = loadingComponent.getInformalParameterBindings();

    for (String name : informals.keySet()) {
      if (newComponentModel.getParameterModel(name) != null) continue;

      Binding binding = informals.get(name);

      newComponent.bindParameter(name, binding);
      newComponentBindings.put(name, binding);
    }
  }
  private void startComponent(StartComponentToken token) {
    String elementName = token.getElementName();

    // Initial guess: the type from the token (but this may be null in many cases).
    String embeddedType = token.getComponentType();

    // This may be null for an anonymous component.
    String embeddedId = token.getId();

    String embeddedComponentClassName = null;

    final EmbeddedComponentModel embeddedModel =
        embeddedId == null ? null : loadingComponentModel.getEmbeddedComponentModel(embeddedId);

    // We know that if embeddedId is null, embeddedType is not.

    if (embeddedId == null) embeddedId = generateEmbeddedId(embeddedType, idAllocator);

    if (embeddedModel != null) {
      String modelType = embeddedModel.getComponentType();

      if (InternalUtils.isNonBlank(modelType) && embeddedType != null)
        throw new TapestryException(
            ServicesMessages.compTypeConflict(embeddedId, embeddedType, modelType), token, null);

      embeddedType = modelType;
      embeddedComponentClassName = embeddedModel.getComponentClassName();
    }

    // We only have the embeddedModel if the embeddedId was specified.  If embeddedType was ommitted
    // and

    if (InternalUtils.isBlank(embeddedType) && embeddedModel == null)
      throw new TapestryException(
          ServicesMessages.noTypeForEmbeddedComponent(
              embeddedId, loadingComponentModel.getComponentClassName()),
          token,
          null);

    final ComponentPageElement newComponent =
        pageElementFactory.newComponentElement(
            page,
            loadingElement,
            embeddedId,
            embeddedType,
            embeddedComponentClassName,
            elementName,
            token.getLocation());

    addMixinsToComponent(newComponent, embeddedModel, token.getMixins());

    final Map<String, Binding> newComponentBindings = CollectionFactory.newMap();
    componentIdToBindingMap.put(newComponent.getCompleteId(), newComponentBindings);

    if (embeddedModel != null)
      bindParametersFromModel(embeddedModel, loadingElement, newComponent, newComponentBindings);

    addToBody(newComponent);

    // Remember to load the template for this new component
    componentQueue.push(newComponent);

    // Any attribute tokens that immediately follow should be
    // used to bind parameters.

    addAttributesAsComponentBindings = true;

    // Any attributes (including component parameters) that come up belong on this component.

    activeElementStack.push(newComponent);

    // Set things up so that content inside the component is added to the component's body.

    bodyPageElementStack.push(newComponent);

    // And clean that up when the end element is reached.

    final ComponentModel newComponentModel =
        newComponent.getComponentResources().getComponentModel();

    // If the component was from an embedded @Component annotation, and it is inheritting informal
    // parameters,
    // and the component in question supports informal parameters, than get those inheritted
    // informal parameters ...
    // but later (this helps ensure that <t:parameter> elements that may provide informal parameters
    // are
    // visible when the informal parameters are copied to the child component).

    if (embeddedModel != null
        && embeddedModel.getInheritInformalParameters()
        && newComponentModel.getSupportsInformalParameters()) {
      final ComponentPageElement loadingElement = this.loadingElement;

      Runnable finalizer =
          new Runnable() {
            public void run() {
              handleInformalParameters(
                  loadingElement,
                  embeddedModel,
                  newComponent,
                  newComponentModel,
                  newComponentBindings);
            }
          };

      finalization.add(finalizer);
    }

    Runnable cleanup =
        new Runnable() {
          public void run() {
            // May need a separate queue for this, to execute at the very end of page loading.

            activeElementStack.pop();
            bodyPageElementStack.pop();
          }
        };

    // The start tag is not added to the body of the component, so neither should
    // the end tag.
    configureEnd(true, cleanup);
  }
  /**
   * Do you smell something? I'm smelling that this class needs to be redesigned to not need a
   * central method this large and hard to test. I think a lot of instance and local variables need
   * to be bundled up into some kind of process object. This code is effectively too big to be
   * tested except through integration testing.
   */
  private void loadTemplateForComponent(final ComponentPageElement loadingElement) {
    this.loadingElement = loadingElement;
    loadingComponentModel = loadingElement.getComponentResources().getComponentModel();

    String componentClassName = loadingComponentModel.getComponentClassName();
    ComponentTemplate template = templateSource.getTemplate(loadingComponentModel, locale);

    // When the template for a component is missing, we pretend it consists of just a RenderBody
    // phase. Missing is not an error ... many component simply do not have a template.

    if (template.isMissing()) {
      this.loadingElement.addToTemplate(new RenderBodyElement(this.loadingElement));
      return;
    }

    // Pre-allocate ids to avoid later name collisions.

    // Don't have a case-insensitive Set, so we'll make due with a Map
    Map<String, Boolean> embeddedIds = collectedEmbeddedComponentIds(loadingComponentModel);

    idAllocator.clear();

    final Map<String, Location> componentIdsMap = template.getComponentIds();

    for (String id : componentIdsMap.keySet()) {
      idAllocator.allocateId(id);
      embeddedIds.remove(id);
    }

    if (!embeddedIds.isEmpty())
      throw new RuntimeException(
          ServicesMessages.embeddedComponentsNotInTemplate(
              embeddedIds.keySet(), componentClassName, template.getResource()));

    addAttributesAsComponentBindings = false;

    // The outermost elements of the template belong in the loading component's template list,
    // not its body list. This shunt allows everyone else to not have to make that decision,
    // they can add to the "body" and (if there isn't an active component), the shunt will
    // add the element to the component's template.

    BodyPageElement shunt =
        new BodyPageElement() {
          public void addToBody(PageElement element) {
            loadingElement.addToTemplate(element);
          }
        };

    bodyPageElementStack.push(shunt);

    for (TemplateToken token : template.getTokens()) {
      switch (token.getTokenType()) {
        case TEXT:
          text((TextToken) token);
          break;

        case EXPANSION:
          expansion((ExpansionToken) token);
          break;

        case BODY:
          body();
          break;

        case START_ELEMENT:
          startElement((StartElementToken) token);
          break;

        case START_COMPONENT:
          startComponent((StartComponentToken) token);
          break;

        case ATTRIBUTE:
          attribute((AttributeToken) token);
          break;

        case END_ELEMENT:
          endElement();
          break;

        case COMMENT:
          comment((CommentToken) token);
          break;

        case BLOCK:
          block((BlockToken) token);
          break;

        case PARAMETER:
          parameter((ParameterToken) token);
          break;

        case DTD:
          dtd((DTDToken) token);
          break;

        case DEFINE_NAMESPACE_PREFIX:
          defineNamespacePrefix((DefineNamespacePrefixToken) token);
          break;

        case CDATA:
          cdata((CDATAToken) token);
          break;

        default:
          throw new IllegalStateException("Not implemented yet: " + token);
      }
    }

    // For neatness / symmetry:

    bodyPageElementStack.pop(); // the shunt

    // TODO: Check that all stacks are empty. That should never happen, as long
    // as the ComponentTemplate is valid.
  }
 public Resource getBaseResource() {
   return componentModel.getBaseResource();
 }
 public Logger getLogger() {
   return componentModel.getLogger();
 }