public static void removeDuplicates(@NotNull CompositePackagingElement<?> parent) {
    List<PackagingElement<?>> prevChildren = new ArrayList<PackagingElement<?>>();

    List<PackagingElement<?>> toRemove = new ArrayList<PackagingElement<?>>();
    for (PackagingElement<?> child : parent.getChildren()) {
      if (child instanceof CompositePackagingElement<?>) {
        removeDuplicates((CompositePackagingElement<?>) child);
      }
      boolean merged = false;
      for (PackagingElement<?> prevChild : prevChildren) {
        if (child.isEqualTo(prevChild)) {
          if (child instanceof CompositePackagingElement<?>) {
            for (PackagingElement<?> childElement :
                ((CompositePackagingElement<?>) child).getChildren()) {
              ((CompositePackagingElement<?>) prevChild).addOrFindChild(childElement);
            }
          }
          merged = true;
          break;
        }
      }
      if (merged) {
        toRemove.add(child);
      } else {
        prevChildren.add(child);
      }
    }

    for (PackagingElement<?> child : toRemove) {
      parent.removeChild(child);
    }
  }
 public static void copyChildren(
     CompositePackagingElement<?> oldParent,
     CompositePackagingElement<?> newParent,
     @NotNull Project project) {
   for (PackagingElement<?> child : oldParent.getChildren()) {
     newParent.addOrFindChild(copyWithChildren(child, project));
   }
 }
 private static <E extends PackagingElement<?>> boolean processElementRecursively(
     @NotNull PackagingElement<?> element,
     @Nullable PackagingElementType<E> type,
     @NotNull PackagingElementProcessor<? super E> processor,
     @NotNull PackagingElementResolvingContext resolvingContext,
     final boolean processSubstitutions,
     ArtifactType artifactType,
     @NotNull PackagingElementPath path,
     Set<PackagingElement<?>> processed) {
   if (!processor.shouldProcess(element) || !processed.add(element)) {
     return true;
   }
   if (type == null || element.getType().equals(type)) {
     if (!processor.process((E) element, path)) {
       return false;
     }
   }
   if (element instanceof CompositePackagingElement<?>) {
     final CompositePackagingElement<?> composite = (CompositePackagingElement<?>) element;
     return processElementsRecursively(
         composite.getChildren(),
         type,
         processor,
         resolvingContext,
         processSubstitutions,
         artifactType,
         path.appendComposite(composite),
         processed);
   } else if (element instanceof ComplexPackagingElement<?> && processSubstitutions) {
     final ComplexPackagingElement<?> complexElement = (ComplexPackagingElement<?>) element;
     if (processor.shouldProcessSubstitution(complexElement)) {
       final List<? extends PackagingElement<?>> substitution =
           complexElement.getSubstitution(resolvingContext, artifactType);
       if (substitution != null) {
         return processElementsRecursively(
             substitution,
             type,
             processor,
             resolvingContext,
             processSubstitutions,
             artifactType,
             path.appendComplex(complexElement),
             processed);
       }
     }
   }
   return true;
 }
  public static void removeChildrenRecursively(
      @NotNull CompositePackagingElement<?> element,
      @NotNull Condition<PackagingElement<?>> condition) {
    List<PackagingElement<?>> toRemove = new ArrayList<PackagingElement<?>>();
    for (PackagingElement<?> child : element.getChildren()) {
      if (child instanceof CompositePackagingElement<?>) {
        final CompositePackagingElement<?> compositeChild = (CompositePackagingElement<?>) child;
        removeChildrenRecursively(compositeChild, condition);
        if (compositeChild.getChildren().isEmpty()) {
          toRemove.add(child);
        }
      } else if (condition.value(child)) {
        toRemove.add(child);
      }
    }

    element.removeChildren(toRemove);
  }
  public static boolean processElementsByRelativePath(
      @NotNull final CompositePackagingElement<?> parent,
      @NotNull String relativePath,
      @NotNull final PackagingElementResolvingContext context,
      @NotNull final ArtifactType artifactType,
      @NotNull PackagingElementPath parentPath,
      @NotNull final PackagingElementProcessor<PackagingElement<?>> processor) {
    relativePath = StringUtil.trimStart(relativePath, "/");
    if (relativePath.length() == 0) {
      return true;
    }

    int i = relativePath.indexOf('/');
    final String firstName = i != -1 ? relativePath.substring(0, i) : relativePath;
    final String tail = i != -1 ? relativePath.substring(i + 1) : "";

    return processElementsWithSubstitutions(
        parent.getChildren(),
        context,
        artifactType,
        parentPath.appendComposite(parent),
        new PackagingElementProcessor<PackagingElement<?>>() {
          @Override
          public boolean process(
              @NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
            boolean process = false;
            if (element instanceof CompositePackagingElement
                && firstName.equals(((CompositePackagingElement<?>) element).getName())) {
              process = true;
            } else if (element instanceof FileCopyPackagingElement) {
              final FileCopyPackagingElement fileCopy = (FileCopyPackagingElement) element;
              if (firstName.equals(fileCopy.getOutputFileName())) {
                process = true;
              }
            }

            if (process) {
              if (tail.length() == 0) {
                if (!processor.process(element, path)) return false;
              } else if (element instanceof CompositePackagingElement<?>) {
                return processElementsByRelativePath(
                    (CompositePackagingElement) element,
                    tail,
                    context,
                    artifactType,
                    path,
                    processor);
              }
            }
            return true;
          }
        });
  }
  public static List<VirtualFile> findSourceFilesByOutputPath(
      CompositePackagingElement<?> parent,
      final String outputPath,
      final PackagingElementResolvingContext context,
      final ArtifactType artifactType) {
    final String path = StringUtil.trimStart(outputPath, "/");
    if (path.length() == 0) {
      return Collections.emptyList();
    }

    int i = path.indexOf('/');
    final String firstName = i != -1 ? path.substring(0, i) : path;
    final String tail = i != -1 ? path.substring(i + 1) : "";

    final List<VirtualFile> result = new SmartList<VirtualFile>();
    processElementsWithSubstitutions(
        parent.getChildren(),
        context,
        artifactType,
        PackagingElementPath.EMPTY,
        new PackagingElementProcessor<PackagingElement<?>>() {
          @Override
          public boolean process(
              @NotNull PackagingElement<?> element, @NotNull PackagingElementPath elementPath) {
            // todo[nik] replace by method findSourceFile() in PackagingElement
            if (element instanceof CompositePackagingElement) {
              final CompositePackagingElement<?> compositeElement =
                  (CompositePackagingElement<?>) element;
              if (firstName.equals(compositeElement.getName())) {
                result.addAll(
                    findSourceFilesByOutputPath(compositeElement, tail, context, artifactType));
              }
            } else if (element instanceof FileCopyPackagingElement) {
              final FileCopyPackagingElement fileCopyElement = (FileCopyPackagingElement) element;
              if (firstName.equals(fileCopyElement.getOutputFileName()) && tail.length() == 0) {
                ContainerUtil.addIfNotNull(fileCopyElement.findFile(), result);
              }
            } else if (element instanceof DirectoryCopyPackagingElement
                || element instanceof ExtractedDirectoryPackagingElement) {
              final VirtualFile sourceRoot =
                  ((FileOrDirectoryCopyPackagingElement<?>) element).findFile();
              if (sourceRoot != null) {
                ContainerUtil.addIfNotNull(sourceRoot.findFileByRelativePath(path), result);
              }
            } else if (element instanceof ModuleOutputPackagingElement) {
              final CompilerConfiguration compilerConfiguration =
                  CompilerConfiguration.getInstance(context.getProject());
              for (VirtualFile sourceRoot :
                  ((ModuleOutputPackagingElement) element).getSourceRoots(context)) {
                final VirtualFile sourceFile = sourceRoot.findFileByRelativePath(path);
                if (sourceFile != null && compilerConfiguration.isResourceFile(sourceFile)) {
                  result.add(sourceFile);
                }
              }
            }
            return true;
          }
        });

    return result;
  }