001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.io.file;
019
020import java.io.IOException;
021import java.nio.file.CopyOption;
022import java.nio.file.FileVisitResult;
023import java.nio.file.Files;
024import java.nio.file.Path;
025import java.nio.file.ProviderMismatchException;
026import java.nio.file.attribute.BasicFileAttributes;
027import java.util.Arrays;
028import java.util.Objects;
029
030import org.apache.commons.io.file.Counters.PathCounters;
031
032/**
033 * Copies a source directory to a target directory.
034 *
035 * @since 2.7
036 */
037public class CopyDirectoryVisitor extends CountingPathVisitor {
038
039    private static CopyOption[] toCopyOption(final CopyOption... copyOptions) {
040        return copyOptions == null ? PathUtils.EMPTY_COPY_OPTIONS : copyOptions.clone();
041    }
042
043    private final CopyOption[] copyOptions;
044    private final Path sourceDirectory;
045    private final Path targetDirectory;
046
047    /**
048     * Constructs a new visitor that deletes files except for the files and directories explicitly given.
049     *
050     * @param pathCounter How to count visits.
051     * @param sourceDirectory The source directory
052     * @param targetDirectory The target directory
053     * @param copyOptions Specifies how the copying should be done.
054     */
055    public CopyDirectoryVisitor(final PathCounters pathCounter, final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) {
056        super(pathCounter);
057        this.sourceDirectory = sourceDirectory;
058        this.targetDirectory = targetDirectory;
059        this.copyOptions = toCopyOption(copyOptions);
060    }
061
062    /**
063     * Constructs a new visitor that deletes files except for the files and directories explicitly given.
064     *
065     * @param pathCounter How to count visits.
066     * @param fileFilter How to filter file paths.
067     * @param dirFilter How to filter directory paths.
068     * @param sourceDirectory The source directory
069     * @param targetDirectory The target directory
070     * @param copyOptions Specifies how the copying should be done.
071     * @since 2.9.0
072     */
073    public CopyDirectoryVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter, final Path sourceDirectory,
074        final Path targetDirectory, final CopyOption... copyOptions) {
075        super(pathCounter, fileFilter, dirFilter);
076        this.sourceDirectory = sourceDirectory;
077        this.targetDirectory = targetDirectory;
078        this.copyOptions = toCopyOption(copyOptions);
079    }
080
081    /**
082     * Copies the sourceFile to the targetFile.
083     *
084     * @param sourceFile the source file.
085     * @param targetFile the target file.
086     * @throws IOException if an I/O error occurs.
087     * @since 2.8.0
088     */
089    protected void copy(final Path sourceFile, final Path targetFile) throws IOException {
090        Files.copy(sourceFile, targetFile, copyOptions);
091    }
092
093    @Override
094    public boolean equals(final Object obj) {
095        if (this == obj) {
096            return true;
097        }
098        if (!super.equals(obj)) {
099            return false;
100        }
101        if (getClass() != obj.getClass()) {
102            return false;
103        }
104        final CopyDirectoryVisitor other = (CopyDirectoryVisitor) obj;
105        return Arrays.equals(copyOptions, other.copyOptions) && Objects.equals(sourceDirectory, other.sourceDirectory)
106            && Objects.equals(targetDirectory, other.targetDirectory);
107    }
108
109    /**
110     * Gets the copy options.
111     *
112     * @return the copy options.
113     * @since 2.8.0
114     */
115    public CopyOption[] getCopyOptions() {
116        return copyOptions.clone();
117    }
118
119    /**
120     * Gets the source directory.
121     *
122     * @return the source directory.
123     * @since 2.8.0
124     */
125    public Path getSourceDirectory() {
126        return sourceDirectory;
127    }
128
129    /**
130     * Gets the target directory.
131     *
132     * @return the target directory.
133     * @since 2.8.0
134     */
135    public Path getTargetDirectory() {
136        return targetDirectory;
137    }
138
139    @Override
140    public int hashCode() {
141        final int prime = 31;
142        int result = super.hashCode();
143        result = prime * result + Arrays.hashCode(copyOptions);
144        result = prime * result + Objects.hash(sourceDirectory, targetDirectory);
145        return result;
146    }
147
148    @Override
149    public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAttributes attributes)
150        throws IOException {
151        final Path newTargetDir = resolveRelativeAsString(directory);
152        if (Files.notExists(newTargetDir)) {
153            Files.createDirectory(newTargetDir);
154        }
155        return super.preVisitDirectory(directory, attributes);
156    }
157
158    /**
159     * Relativizes against {@code sourceDirectory}, then resolves against {@code targetDirectory}.
160     *
161     * We have to call {@link Path#toString()} relative value because we cannot use paths belonging to different
162     * FileSystems in the Path methods, usually this leads to {@link ProviderMismatchException}.
163     *
164     * @param directory the directory to relativize.
165     * @return a new path, relativized against sourceDirectory, then resolved against targetDirectory.
166     */
167    private Path resolveRelativeAsString(final Path directory) {
168        return targetDirectory.resolve(sourceDirectory.relativize(directory).toString());
169    }
170
171    @Override
172    public FileVisitResult visitFile(final Path sourceFile, final BasicFileAttributes attributes) throws IOException {
173        final Path targetFile = resolveRelativeAsString(sourceFile);
174        copy(sourceFile, targetFile);
175        return super.visitFile(targetFile, attributes);
176    }
177
178}