/**
   * Writes this configuration to stream<br>
   * Note: Closes the stream when finished
   *
   * @param stream to write to
   */
  public void saveToStream(OutputStream stream) throws IOException {
    // Get rid of newline characters in text - Bukkit bug prevents proper saving
    for (String key : this.getSource().getKeys(true)) {
      Object value = this.getSource().get(key);
      if (value instanceof String) {
        String text = (String) value;
        if (text.contains("\n")) {
          this.getSource().set(key, Arrays.asList(text.split("\n", -1)));
        }
      }
    }

    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
    try {
      // Write the top header
      writeHeader(true, writer, this.getHeader(), 0);

      // Write other headers and the nodes
      IntHashMap<String> anchorData = new IntHashMap<String>();
      int indent;
      int anchStart, anchEnd, anchId = -1, anchDepth = 0, anchIndent = 0;
      boolean wasAnchor;
      int refStart, refEnd, refId;
      StringBuilder refData = new StringBuilder();
      NodeBuilder node = new NodeBuilder(this.getIndent());
      for (String line : this.getSource().saveToString().split("\n", -1)) {
        line = StringUtil.colorToAmp(line);
        indent = StringUtil.getSuccessiveCharCount(line, ' ');
        line = line.substring(indent);
        wasAnchor = false;
        // ===== Logic start =====
        // Get rid of the unneeded '-characters around certain common names
        if (line.equals("'*':")) {
          line = "*:";
        }

        // Handle a node
        if (node.handle(line, indent)) {
          // Store old anchor data
          if (anchId >= 0 && node.getDepth() <= anchDepth) {
            anchorData.put(anchId, refData.toString());
            refData.setLength(0);
            anchId = refId = -1;
          }

          // Saving a new node: Write the node header
          writeHeader(false, writer, this.getHeader(node.getPath()), indent);

          // Check if the value denotes a reference
          refStart = line.indexOf("*id", node.getName().length());
          refEnd = line.indexOf(' ', refStart);
          if (refEnd == -1) {
            refEnd = line.length();
          }
          if (refStart > 0 && refEnd > refStart) {
            // This is a reference pointer: get id
            refId = ParseUtil.parseInt(line.substring(refStart + 3, refEnd), -1);
            if (refId >= 0) {
              // Obtain the reference data
              String data = anchorData.get(refId);
              if (data != null) {
                // Replace the line with the new data
                line = StringUtil.trimEnd(line.substring(0, refStart)) + " " + data;
              }
            }
          }

          // Check if the value denotes a data anchor
          anchStart = line.indexOf("&id", node.getName().length());
          anchEnd = line.indexOf(' ', anchStart);
          if (anchEnd == -1) {
            anchEnd = line.length();
          }
          if (anchStart > 0 && anchEnd > anchStart) {
            // This is a reference node anchor: get id
            anchId = ParseUtil.parseInt(line.substring(anchStart + 3, anchEnd), -1);
            anchDepth = node.getDepth();
            anchIndent = indent;
            if (anchId >= 0) {
              // Fix whitespace after anchor identifier
              anchEnd += StringUtil.getSuccessiveCharCount(line.substring(anchEnd), ' ');

              // Store the data of this anchor
              refData.append(line.substring(anchEnd));

              // Remove the variable reference from saved data
              line = StringUtil.replace(line, anchStart, anchEnd, "");
            }
            wasAnchor = true;
          }
        }
        if (!wasAnchor && anchId >= 0) {
          // Not an anchor: append anchor data
          refData
              .append('\n')
              .append(StringUtil.getFilledString(" ", indent - anchIndent))
              .append(line);
        }
        // Write the data
        if (LogicUtil.containsChar('\n', line)) {
          for (String part : line.split("\n", -1)) {
            StreamUtil.writeIndent(writer, indent);
            writer.write(part);
            writer.newLine();
          }
        } else {
          StreamUtil.writeIndent(writer, indent);
          writer.write(line);
          writer.newLine();
        }
      }
    } finally {
      writer.close();
    }
  }
  @Override
  public void execute(SignActionEvent info) {
    if (!info.isAction(
        SignActionType.MEMBER_ENTER, SignActionType.REDSTONE_ON, SignActionType.GROUP_ENTER)) {
      return;
    }
    // parse the sign
    boolean docart =
        info.isAction(SignActionType.MEMBER_ENTER, SignActionType.REDSTONE_ON)
            && info.isCartSign()
            && info.hasMember();
    boolean dotrain =
        !docart
            && info.isAction(SignActionType.GROUP_ENTER, SignActionType.REDSTONE_ON)
            && info.isTrainSign()
            && info.hasGroup();
    if (!docart && !dotrain) return;
    if (!info.isPowered()) return;

    // get nearby chests
    int radius = ParseUtil.parseInt(info.getLine(1), TrainCarts.defaultTransferRadius);
    List<Chest> chests = new ArrayList<Chest>();
    for (BlockState state : TransferSignUtil.getBlockStates(info, radius, radius)) {
      if (state instanceof Chest) {
        chests.add((Chest) state);
      }
    }
    if (chests.isEmpty()) {
      return;
    }

    List<MinecartMember<?>> carts;
    if (dotrain) {
      carts = info.getGroup();
    } else {
      carts = new ArrayList<MinecartMember<?>>(1);
      carts.add(info.getMember());
    }

    int i;
    boolean found = false;
    for (MinecartMember<?> cart : carts) {
      if (!(cart instanceof MinecartMemberFurnace)) {
        continue;
      }
      MinecartMemberFurnace member = (MinecartMemberFurnace) cart;
      if (!member.getEntity().hasFuel()) {
        found = false;
        for (Chest chest : chests) {
          Inventory inv = chest.getInventory();
          for (i = 0; i < inv.getSize(); i++) {
            org.bukkit.inventory.ItemStack item = inv.getItem(i);
            if (!LogicUtil.nullOrEmpty(item) && item.getType() == Material.COAL) {
              ItemUtil.subtractAmount(item, 1);
              inv.setItem(i, item);
              found = true;
              member.addFuelTicks(3600);
              if (TrainCarts.showTransferAnimations) {
                ItemAnimation.start(chest, member, new ItemStack(Material.COAL, 1));
              }
              break;
            }
          }
          if (found) {
            break;
          }
        }
      }
    }
  }