    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 final CopyOption[] copyOptions;
    040    private final Path sourceDirectory;
    041    private final Path targetDirectory;
    042
    043    /**
    044     * Constructs a new visitor that deletes files except for the files and directories explicitly given.
    045     *
    046     * @param pathCounter How to count visits.
    047     * @param sourceDirectory The source directory
    048     * @param targetDirectory The target directory
    049     * @param copyOptions Specifies how the copying should be done.
    050     */
    051    public CopyDirectoryVisitor(final PathCounters pathCounter, final Path sourceDirectory, final Path targetDirectory,
    052        final CopyOption... copyOptions) {
    053        super(pathCounter);
    054        this.sourceDirectory = sourceDirectory;
    055        this.targetDirectory = targetDirectory;
    056        this.copyOptions = copyOptions == null ? PathUtils.EMPTY_COPY_OPTIONS : copyOptions.clone();
    057    }
    058
    059    /**
    060     * Constructs a new visitor that deletes files except for the files and directories explicitly given.
    061     *
    062     * @param pathCounter How to count visits.
    063     * @param fileFilter How to filter file paths.
    064     * @param dirFilter How to filter directory paths.
    065     * @param sourceDirectory The source directory
    066     * @param targetDirectory The target directory
    067     * @param copyOptions Specifies how the copying should be done.
    068     * @since 2.9.0
    069     */
    070    public CopyDirectoryVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter,
    071        final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) {
    072        super(pathCounter, fileFilter, dirFilter);
    073        this.sourceDirectory = sourceDirectory;
    074        this.targetDirectory = targetDirectory;
    075        this.copyOptions = copyOptions == null ? PathUtils.EMPTY_COPY_OPTIONS : copyOptions.clone();
    076    }
    077
    078    /**
    079     * Copies the sourceFile to the targetFile.
    080     *
    081     * @param sourceFile the source file.
    082     * @param targetFile the target file.
    083     * @throws IOException if an I/O error occurs.
    084     * @since 2.8.0
    085     */
    086    protected void copy(final Path sourceFile, final Path targetFile) throws IOException {
    087        Files.copy(sourceFile, targetFile, copyOptions);
    088    }
    089
    090    @Override
    091    public boolean equals(final Object obj) {
    092        if (this == obj) {
    093            return true;
    094        }
    095        if (!super.equals(obj)) {
    096            return false;
    097        }
    098        if (getClass() != obj.getClass()) {
    099            return false;
    100        }
    101        final CopyDirectoryVisitor other = (CopyDirectoryVisitor) obj;
    102        return Arrays.equals(copyOptions, other.copyOptions) && Objects.equals(sourceDirectory, other.sourceDirectory)
    103            && Objects.equals(targetDirectory, other.targetDirectory);
    104    }
    105
    106    /**
    107     * Gets the copy options.
    108     *
    109     * @return the copy options.
    110     * @since 2.8.0
    111     */
    112    public CopyOption[] getCopyOptions() {
    113        return copyOptions.clone();
    114    }
    115
    116    /**
    117     * Gets the source directory.
    118     *
    119     * @return the source directory.
    120     * @since 2.8.0
    121     */
    122    public Path getSourceDirectory() {
    123        return sourceDirectory;
    124    }
    125
    126    /**
    127     * Gets the target directory.
    128     *
    129     * @return the target directory.
    130     * @since 2.8.0
    131     */
    132    public Path getTargetDirectory() {
    133        return targetDirectory;
    134    }
    135
    136    @Override
    137    public int hashCode() {
    138        final int prime = 31;
    139        int result = super.hashCode();
    140        result = prime * result + Arrays.hashCode(copyOptions);
    141        result = prime * result + Objects.hash(sourceDirectory, targetDirectory);
    142        return result;
    143    }
    144
    145    @Override
    146    public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAttributes attributes)
    147        throws IOException {
    148        final Path newTargetDir = resolveRelativeAsString(directory);
    149        if (Files.notExists(newTargetDir)) {
    150            Files.createDirectory(newTargetDir);
    151        }
    152        return super.preVisitDirectory(directory, attributes);
    153    }
    154
    155    /**
    156     * Relativizes against {@code sourceDirectory}, then resolves against {@code targetDirectory}.
    157     *
    158     * We have to call {@link Path#toString()} relative value because we cannot use paths belonging to different
    159     * FileSystems in the Path methods, usually this leads to {@link ProviderMismatchException}.
    160     *
    161     * @param directory the directory to relativize.
    162     * @return a new path, relativized against sourceDirectory, then resolved against targetDirectory.
    163     */
    164    private Path resolveRelativeAsString(final Path directory) {
    165        return targetDirectory.resolve(sourceDirectory.relativize(directory).toString());
    166    }
    167
    168    @Override
    169    public FileVisitResult visitFile(final Path sourceFile, final BasicFileAttributes attributes) throws IOException {
    170        final Path targetFile = resolveRelativeAsString(sourceFile);
    171        copy(sourceFile, targetFile);
    172        return super.visitFile(targetFile, attributes);
    173    }
    174
    175}