    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 *      https://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 an instance that copies all files.
    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 an instance that copies files matching the given file and directory filters.
    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        return prime * result + Objects.hash(sourceDirectory, targetDirectory);
    145    }
    146
    147    @Override
    148    public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAttributes attributes)
    149        throws IOException {
    150        final Path newTargetDir = resolveRelativeAsString(directory);
    151        if (Files.notExists(newTargetDir)) {
    152            Files.createDirectory(newTargetDir);
    153        }
    154        return super.preVisitDirectory(directory, attributes);
    155    }
    156
    157    /**
    158     * Relativizes against {@code sourceDirectory}, then resolves against {@code targetDirectory}.
    159     * <p>
    160     * We call {@link Path#toString()} on the relativized value because we cannot use paths from different FileSystems which throws
    161     * {@link ProviderMismatchException}. Special care is taken to handle differences in file system separators.
    162     * </p>
    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 PathUtils.resolve(targetDirectory, sourceDirectory.relativize(directory));
    169    }
    170
    171    @Override
    172    public FileVisitResult visitFile(final Path sourceFile, final BasicFileAttributes attributes) throws IOException {
    173        final Path targetFile = resolveRelativeAsString(sourceFile);
    174        if (accept(sourceFile, attributes)) {
    175            copy(sourceFile, targetFile);
    176            updateFileCounters(targetFile, attributes);
    177        }
    178        return FileVisitResult.CONTINUE;
    179    }
    180
    181}