/**
   * Process a distribution root.
   *
   * @param parent the misc root
   * @param distributionRoot the distribution root
   * @param distribution the distribution
   * @throws IOException
   */
  static void process(
      final DistributionContentItem parent, final File distributionRoot, Distribution distribution)
      throws IOException {
    final DistributionProcessor processor = new DistributionProcessor();
    final File[] children = distributionRoot.listFiles();
    if (children != null && children.length != 0) {
      for (final File child : children) {
        processor.processMisc(parent, child, distribution);
      }
    }

    final List<File> mp = new ArrayList<File>();
    final Set<DistributionContentItem> moduleRoots = processor.moduleRoots;
    for (final DistributionContentItem item : moduleRoots) {
      final File file = item.getFile(distributionRoot);
      mp.add(file);
    }

    // Update name and version
    final ModuleLoader loader = new LocalModuleLoader(mp.toArray(new File[mp.size()]));
    final ProductConfig config =
        new ProductConfig(loader, distributionRoot.getAbsolutePath(), Collections.emptyMap());
    distribution.setName(config.resolveName());
    distribution.setVersion(config.resolveVersion());
  }
  /**
   * Process the misc files.
   *
   * @param parent the parent content item
   * @param root the current root
   * @param distribution the distribution
   * @throws IOException
   */
  void processMisc(
      final DistributionContentItem parent, final File root, final Distribution distribution)
      throws IOException {
    final DistributionContentItem item = new DistributionItemFileImpl(root, parent);
    if (distribution.isIgnored(item)) {
      // Skip ignored ... Maybe only files?
      return;
    } else if (distribution.isModuleLookupPath(item)) {
      // Process modules
      final LayeredContext lc = new LayeredModuleContext(distribution);
      processLayeredRoot(item, root, lc);
      return;
    } else if (distribution.isBundleLookupPath(item)) {
      /// Process bundles
      final LayeredContext lc = new LayeredBundleContext(distribution);
      processLayeredRoot(item, root, lc);
      return;
    }

    // Build the misc file tree
    parent.getChildren().add(item);
    // Process the children
    final File[] children = root.listFiles();
    if (children != null && children.length != 0) {
      for (final File child : children) {
        processMisc(item, child, distribution);
      }
    }
  }
  private static void writeChildren(
      final XMLExtendedStreamWriter writer, final Collection<DistributionContentItem> items)
      throws XMLStreamException {
    if (items == null || items.size() == 0) {
      return;
    }

    for (final DistributionContentItem item : items) {

      writer.writeStartElement(Element.NODE.name);
      writer.writeAttribute(Attribute.NAME.name, item.getName());
      if (item.isLeaf()) {
        writer.writeAttribute(
            Attribute.COMPARISON_HASH.name, HashUtils.bytesToHexString(item.getComparisonHash()));
        writer.writeAttribute(
            Attribute.METADATA_HASH.name, HashUtils.bytesToHexString(item.getMetadataHash()));
      }
      writer.writeAttribute(Attribute.DIRECTORY.name, String.valueOf(!item.isLeaf()));

      // Recurse
      final Collection<DistributionContentItem> children = item.getChildren();
      writeChildren(writer, children);

      writer.writeEndElement();
    }
  }
  @Override
  public void writeContent(final XMLExtendedStreamWriter writer, final Distribution distribution)
      throws XMLStreamException {

    // Get started ...
    writer.writeStartDocument();
    writer.writeStartElement(Element.DISTRIBUTION.name);
    writer.writeDefaultNamespace(DistributionXml.Namespace.DISTRIBUTION_1_0.getNamespace());

    final DistributionContentItem root = distribution.getRoot();
    final Collection<DistributionContentItem> children = root.getChildren();

    final Set<String> layers = distribution.getLayers();
    if (!layers.isEmpty()) {
      writer.writeStartElement(Element.LAYERS.name);
      for (final String layerName : layers) {
        final Distribution.ProcessedLayer layer = distribution.getLayer(layerName);
        writeLayer(writer, layer, Element.LAYER);
      }
      writer.writeEndElement();
    }
    final Set<String> addOns = distribution.getAddOns();
    if (!addOns.isEmpty()) {
      writer.writeStartElement(Element.ADD_ONS.name);
      for (final String addOnName : addOns) {
        final Distribution.ProcessedLayer addOn = distribution.getAddOn(addOnName);
        writeLayer(writer, addOn, Element.ADD_ON);
      }
      writer.writeEndElement();
    }

    // The misc tree
    writer.writeStartElement(Element.FILES.name);
    writeChildren(writer, children);
    writer.writeEndElement();

    // Done
    writer.writeEndElement();
    writer.writeEndDocument();
  }
  protected static void readNode(XMLExtendedStreamReader reader, DistributionContentItem parent)
      throws XMLStreamException {

    String name = null;
    String directory = null;
    String comparison = "";
    String metadata = "";

    final int count = reader.getAttributeCount();
    for (int i = 0; i < count; i++) {
      final String value = reader.getAttributeValue(i);
      final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
      switch (attribute) {
        case NAME:
          name = value;
          break;
        case DIRECTORY:
          directory = value;
          break;
        case COMPARISON_HASH:
          comparison = value;
          break;
        case METADATA_HASH:
          metadata = value;
          break;
        default:
          throw unexpectedAttribute(reader, i);
      }
    }

    if (name == null) {
      throw missingRequired(reader, "name");
    }
    final DistributionItemImpl item =
        new DistributionItemImpl(
            parent,
            name,
            hexStringToByteArray(comparison),
            hexStringToByteArray(metadata),
            !Boolean.valueOf(directory));
    while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
      final Element element = Element.forName(reader.getLocalName());
      switch (element) {
        case NODE:
          readNode(reader, item);
          break;
        default:
          throw unexpectedElement(reader);
      }
    }
    parent.getChildren().add(item);
  }