/**
   * Allows the collection of node types to be unregistered if they are not referenced by other node
   * types as supertypes, default primary types of child nodes, or required primary types of child
   * nodes.
   *
   * @param nodeTypeNames the names of the node types to be unregistered
   * @param failIfNodeTypesAreUsed true if this method should fail to unregister the named node
   *     types if any of the node types are still in use by nodes, or false if this method should
   *     not perform such a check
   * @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered
   *     node type
   * @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot
   *     be unregistered because they are the supertype, one of the required primary types, or a
   *     default primary type of a node type that is not being unregistered.
   * @throws RepositoryException if any other error occurs
   */
  void unregisterNodeType(Collection<Name> nodeTypeNames, boolean failIfNodeTypesAreUsed)
      throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException {
    CheckArg.isNotNull(nodeTypeNames, "nodeTypeNames");
    if (nodeTypeNames.isEmpty()) return;

    if (failIfNodeTypesAreUsed) {
      long start = System.nanoTime();
      // Search the content graph to make sure that this type isn't being used
      for (Name nodeTypeName : nodeTypeNames) {
        if (isNodeTypeInUse(nodeTypeName)) {
          String name = nodeTypeName.getString(context.getNamespaceRegistry());
          throw new InvalidNodeTypeDefinitionException(
              JcrI18n.cannotUnregisterInUseType.text(name));
        }
      }
      long time =
          TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start), TimeUnit.NANOSECONDS);
      logger.debug(
          "{0} milliseconds to check if any of these node types are unused before unregistering them: {1}",
          time, nodeTypeNames);
    }

    try {
      /*
       * Grab an exclusive lock on this data to keep other nodes from being added/saved while the unregistration checks are occurring
       */
      List<JcrNodeType> removedNodeTypes = new ArrayList<JcrNodeType>(nodeTypeNames.size());
      nodeTypesLock.writeLock().lock();
      final NodeTypes nodeTypes = this.nodeTypesCache;

      for (Name nodeTypeName : nodeTypeNames) {
        /*
         * Check that the type names are valid
         */
        if (nodeTypeName == null) {
          throw new NoSuchNodeTypeException(JcrI18n.invalidNodeTypeName.text());
        }
        String name = nodeTypeName.getString(context.getNamespaceRegistry());

        JcrNodeType foundNodeType = nodeTypes.getNodeType(nodeTypeName);
        if (foundNodeType == null) {
          throw new NoSuchNodeTypeException(JcrI18n.noSuchNodeType.text(name));
        }
        removedNodeTypes.add(foundNodeType);

        /*
         * Check that no other node definitions have dependencies on any of the named types
         */
        for (JcrNodeType nodeType : nodeTypes.getAllNodeTypes()) {
          // If this node is also being unregistered, don't run checks against it
          if (nodeTypeNames.contains(nodeType.getInternalName())) {
            continue;
          }

          for (JcrNodeType supertype : nodeType.supertypes()) {
            if (nodeTypeName.equals(supertype.getInternalName())) {
              throw new InvalidNodeTypeDefinitionException(
                  JcrI18n.cannotUnregisterSupertype.text(name, supertype.getName()));
            }
          }

          for (JcrNodeDefinition childNode : nodeType.childNodeDefinitions()) {
            NodeType defaultPrimaryType = childNode.getDefaultPrimaryType();
            if (defaultPrimaryType != null && name.equals(defaultPrimaryType.getName())) {
              throw new InvalidNodeTypeDefinitionException(
                  JcrI18n.cannotUnregisterDefaultPrimaryType.text(
                      name, nodeType.getName(), childNode.getName()));
            }
            if (childNode.requiredPrimaryTypeNameSet().contains(nodeTypeName)) {
              throw new InvalidNodeTypeDefinitionException(
                  JcrI18n.cannotUnregisterRequiredPrimaryType.text(
                      name, nodeType.getName(), childNode.getName()));
            }
          }
        }
      }

      // Create the new cache ...
      NodeTypes newNodeTypes = nodeTypes.without(removedNodeTypes);

      // Remove the node types from persistent storage ...
      SessionCache system = repository.createSystemSession(context, false);
      SystemContent systemContent = new SystemContent(system);
      systemContent.unregisterNodeTypes(
          removedNodeTypes.toArray(new JcrNodeType[removedNodeTypes.size()]));
      systemContent.save();

      // Now change the cache ...
      this.nodeTypesCache = newNodeTypes;
      this.schemata = null;
    } finally {
      nodeTypesLock.writeLock().unlock();
    }
  }