/* * Copyright 2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gradle.api.internal.file.copy; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import groovy.lang.Closure; import org.gradle.api.Transformer; import org.gradle.api.file.ContentFilterable; import org.gradle.api.file.DuplicatesStrategy; import org.gradle.api.file.RelativePath; import org.gradle.api.internal.file.AbstractFileTreeElement; import org.gradle.api.internal.file.CopyActionProcessingStreamAction; import org.gradle.api.tasks.WorkResult; import org.gradle.internal.nativeintegration.filesystem.Chmod; import java.io.File; import java.io.FilterReader; import java.io.InputStream; import java.util.*; /** * A {@link CopyAction} which cleans up the tree as it is visited. Removes duplicate directories and adds in missing directories. Removes empty directories if instructed to do so by copy * spec. */ public class NormalizingCopyActionDecorator implements CopyAction { private final CopyAction delegate; private final Chmod chmod; public NormalizingCopyActionDecorator(CopyAction delegate, Chmod chmod) { this.delegate = delegate; this.chmod = chmod; } public WorkResult execute(final CopyActionProcessingStream stream) { final Set<RelativePath> visitedDirs = new HashSet<RelativePath>(); final ListMultimap<RelativePath, FileCopyDetailsInternal> pendingDirs = ArrayListMultimap.create(); WorkResult result = delegate.execute(new CopyActionProcessingStream() { public void process(final CopyActionProcessingStreamAction action) { stream.process(new CopyActionProcessingStreamAction() { public void processFile(FileCopyDetailsInternal details) { if (details.isDirectory()) { RelativePath path = details.getRelativePath(); if (!visitedDirs.contains(path)) { pendingDirs.put(path, details); } } else { maybeVisit(details.getRelativePath().getParent(), details.isIncludeEmptyDirs(), action); action.processFile(details); } } }); for (RelativePath path : new LinkedHashSet<RelativePath>(pendingDirs.keySet())) { List<FileCopyDetailsInternal> detailsList = new ArrayList<FileCopyDetailsInternal>(pendingDirs.get(path)); for (FileCopyDetailsInternal details : detailsList) { if (details.isIncludeEmptyDirs()) { maybeVisit(path, details.isIncludeEmptyDirs(), action); } } } visitedDirs.clear(); pendingDirs.clear(); } private void maybeVisit(RelativePath path, boolean includeEmptyDirs, CopyActionProcessingStreamAction delegateAction) { if (path == null || path.getParent() == null || !visitedDirs.add(path)) { return; } maybeVisit(path.getParent(), includeEmptyDirs, delegateAction); List<FileCopyDetailsInternal> detailsForPath = pendingDirs.removeAll(path); FileCopyDetailsInternal dir; if (detailsForPath.isEmpty()) { // TODO - this is pretty nasty, look at avoiding using a time bomb stub here dir = new StubbedFileCopyDetails(path, includeEmptyDirs, chmod); } else { dir = detailsForPath.get(0); } delegateAction.processFile(dir); } }); return result; } private static class StubbedFileCopyDetails extends AbstractFileTreeElement implements FileCopyDetailsInternal { private final RelativePath path; private final boolean includeEmptyDirs; private long lastModified = System.currentTimeMillis(); private StubbedFileCopyDetails(RelativePath path, boolean includeEmptyDirs, Chmod chmod) { super(chmod); this.path = path; this.includeEmptyDirs = includeEmptyDirs; } public boolean isIncludeEmptyDirs() { return includeEmptyDirs; } @Override public String getDisplayName() { return path.toString(); } public File getFile() { throw new UnsupportedOperationException(); } public boolean isDirectory() { return !path.isFile(); } public long getLastModified() { return lastModified; } public long getSize() { throw new UnsupportedOperationException(); } public InputStream open() { throw new UnsupportedOperationException(); } public RelativePath getRelativePath() { return path; } public void exclude() { throw new UnsupportedOperationException(); } public void setName(String name) { throw new UnsupportedOperationException(); } public void setPath(String path) { throw new UnsupportedOperationException(); } public void setRelativePath(RelativePath path) { throw new UnsupportedOperationException(); } public void setMode(int mode) { throw new UnsupportedOperationException(); } public void setDuplicatesStrategy(DuplicatesStrategy strategy) { throw new UnsupportedOperationException(); } public DuplicatesStrategy getDuplicatesStrategy() { throw new UnsupportedOperationException(); } public String getSourceName() { throw new UnsupportedOperationException(); } public String getSourcePath() { throw new UnsupportedOperationException(); } public RelativePath getRelativeSourcePath() { throw new UnsupportedOperationException(); } public ContentFilterable filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) { throw new UnsupportedOperationException(); } public ContentFilterable filter(Class<? extends FilterReader> filterType) { throw new UnsupportedOperationException(); } public ContentFilterable filter(Closure closure) { throw new UnsupportedOperationException(); } public ContentFilterable filter(Transformer<String, String> transformer) { throw new UnsupportedOperationException(); } public ContentFilterable expand(Map<String, ?> properties) { throw new UnsupportedOperationException(); } } }