@Override
  public void toggleItemIgnore(ObservableList<TreeItem<Node>> nodesToIgnore, boolean ignored) {
    for (TreeItem<Node> nodeToIgnore : nodesToIgnore) {
      ipmService.ignoreNode(nodeToIgnore.getValue(), ignored);

      view.getErrorLabel().setVisible(false);
    }

    // To aid in being able to unignore multiple items at once we just check the entire tree for
    // validity and then walk the tree to find types that need to be changed.
    if (!ignored
        && !getController().getDomainProfileService().validateTree(controller.getPackageTree())) {
      updateUnassignedNode(controller.getPackageTree());
    }

    // If the tree still isn't valid, try reassigning types to all nodes we changes.
    // This is almost guaranteed to never happen but is here as an ultimate safety check to ensure
    // we always have a valid tree.
    if (!ignored
        && !getController().getDomainProfileService().validateTree(controller.getPackageTree())) {

      nodesToIgnore
          .stream()
          .filter(
              nodeToIgnore ->
                  !getController()
                      .getDomainProfileService()
                      .assignNodeTypes(
                          getController().getPrimaryDomainProfile(), nodeToIgnore.getValue()))
          .forEach(
              nodeToIgnore -> {
                ipmService.ignoreNode(nodeToIgnore.getValue(), true);
                view.getErrorLabel().setText(TextFactory.getText(ErrorKey.UNIGNORE_ERROR));
                view.getErrorLabel().setVisible(true);
              });
    }

    // Rebuild the entire TreeView and reselect the previously selected nodes
    rebuildTreeView();

    for (TreeItem<Node> ignoredNode : nodesToIgnore) {
      view.getArtifactTreeView().getSelectionModel().select(ignoredNode);
    }
  }
  @Override
  public Map<Node, NodeComparison> refreshTreeContent(Node node) {
    Map<Node, NodeComparison> resultMap = new HashMap<>();
    try {
      resultMap = ipmService.refreshTreeContent(node);
    } catch (IOException e) {
      log.error(e.getMessage());
      view.getErrorLabel().setText(TextFactory.getText(ErrorKey.REFRESH_ERROR) + e.getMessage());
      view.getErrorLabel().setVisible(true);
    }

    return resultMap;
  }
  @Override
  public void addToTree(Node parent, Path contentToAdd) {
    try {
      Node node = ipmService.createTreeFromFileSystem(contentToAdd);
      parent.addChild(node);
      controller
          .getDomainProfileService()
          .assignNodeTypes(controller.getPrimaryDomainProfile(), parent);

      // Refresh the tree display
      displayPackageTree();

    } catch (IOException e) {
      log.error(e.getMessage());
      view.getErrorLabel()
          .setText(TextFactory.getText(ErrorKey.ADD_CONTENT_ERROR) + e.getMessage());
      view.getErrorLabel().setVisible(true);
    }
  }
  private void bind() {

    // Displays the file selector, and then saves the package description to the given file.
    view.getSaveButton()
        .setOnAction(
            arg0 -> {
              if (view.getArtifactDetailsWindow() != null
                  && view.getArtifactDetailsWindow().isShowing()) {
                view.getArtifactDetailsWindow().hide();
              }

              try {
                getController().savePackageStateFile();
              } catch (IOException | RDFTransformException e) {
                view.getErrorLabel().setText(TextFactory.getText(Errors.ErrorKey.IO_CREATE_ERROR));
              }
            });

    // Cancels the property popup, which closes the popup with out saving any changes.
    view.getCancelPopupHyperlink()
        .setOnAction(
            arg0 -> {
              if (view.getArtifactDetailsWindow() != null
                  && view.getArtifactDetailsWindow().isShowing()) {
                view.getArtifactDetailsWindow().hide();
              }
            });

    if (view.getArtifactDetailsWindow() != null) {
      view.getArtifactDetailsWindow().setOnCloseRequest(event -> saveCurrentNode());
    }

    // Saves any changes made in the package artifact property popup
    view.getApplyPopupButton()
        .setOnAction(
            arg0 -> {
              saveCurrentNode();
              if (view.getArtifactDetailsWindow() != null
                  && view.getArtifactDetailsWindow().isShowing()) {
                view.getArtifactDetailsWindow().hide();
              }
            });

    // Gets the button that's used to dismiss validation error popup.
    view.getReenableWarningsButton()
        .setOnAction(
            actionEvent ->
                preferences.putBoolean(
                    internalProperties.get(
                        InternalProperties.InternalPropertyKey.HIDE_PROPERTY_WARNING_PREFERENCE),
                    false));

    view.getRefreshPopupPositiveButton()
        .setOnAction(
            event -> {
              ipmService.mergeTree(controller.getPackageTree(), view.getRefreshResult());
              List<Node> currentlyIgnoredNodes = new ArrayList<>();
              getIgnoredNodes(view.getRoot().getValue(), currentlyIgnoredNodes);

              // To assign node types from the refresh we must unignore nodes so all nodes are
              // considered
              for (Node node : currentlyIgnoredNodes) {
                ipmService.ignoreNode(node, false);
              }

              controller
                  .getDomainProfileService()
                  .assignNodeTypes(
                      controller.getPrimaryDomainProfile(), controller.getPackageTree());

              // Once we're done assigning types we'll reignore whatever was previously ignored
              for (Node node : currentlyIgnoredNodes) {
                ipmService.ignoreNode(node, true);
              }

              displayPackageTree();
              view.getRefreshPopup().hide();
            });

    view.getRefreshPopupNegativeButton().setOnAction(event -> view.getRefreshPopup().hide());
  }