Add filesystem watcher support
Add a filesystem watcher that can be used to inform listeners of changes (add, delete, modify) to files in specific source folders. The implementation uses a background thread rather than the WatchService API to remain compatible with Java 6 and because WatchService is slow on OSX. See gh-3084
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A single file that has changed.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see ChangedFiles
|
||||
*/
|
||||
public final class ChangedFile {
|
||||
|
||||
private final File sourceFolder;
|
||||
|
||||
private final File file;
|
||||
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
* Create a new {@link ChangedFile} instance.
|
||||
* @param sourceFolder the source folder
|
||||
* @param file the file
|
||||
* @param type the type of change
|
||||
*/
|
||||
public ChangedFile(File sourceFolder, File file, Type type) {
|
||||
Assert.notNull(sourceFolder, "SourceFolder must not be null");
|
||||
Assert.notNull(file, "File must not be null");
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
this.sourceFolder = sourceFolder;
|
||||
this.file = file;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file that was changed.
|
||||
* @return the file
|
||||
*/
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of change.
|
||||
* @return the type of change
|
||||
*/
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the file relative to the source folder.
|
||||
* @return the relative name
|
||||
*/
|
||||
public String getRelativeName() {
|
||||
String folderName = this.sourceFolder.getAbsoluteFile().getPath();
|
||||
String fileName = this.file.getAbsoluteFile().getPath();
|
||||
Assert.state(fileName.startsWith(folderName), "The file " + fileName
|
||||
+ " is not contained in the source folder " + folderName);
|
||||
return fileName.substring(folderName.length() + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.file.hashCode() * 31 + this.type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof ChangedFile) {
|
||||
ChangedFile other = (ChangedFile) obj;
|
||||
return this.file.equals(other.file) && this.type.equals(other.type);
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.file + " (" + this.type + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Change types.
|
||||
*/
|
||||
public static enum Type {
|
||||
|
||||
/**
|
||||
* A new file has been added.
|
||||
*/
|
||||
ADD,
|
||||
|
||||
/**
|
||||
* An existing file has been modified.
|
||||
*/
|
||||
MODIFY,
|
||||
|
||||
/**
|
||||
* An existing file has been deleted.
|
||||
*/
|
||||
DELETE
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A collections of files from a specific source folder that have changed.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see FileChangeListener
|
||||
* @see ChangedFiles
|
||||
*/
|
||||
public final class ChangedFiles implements Iterable<ChangedFile> {
|
||||
|
||||
private final File sourceFolder;
|
||||
|
||||
private final Set<ChangedFile> files;
|
||||
|
||||
public ChangedFiles(File sourceFolder, Set<ChangedFile> files) {
|
||||
this.sourceFolder = sourceFolder;
|
||||
this.files = Collections.unmodifiableSet(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* The source folder being watched.
|
||||
* @return the source folder
|
||||
*/
|
||||
public File getSourceFolder() {
|
||||
return this.sourceFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ChangedFile> iterator() {
|
||||
return getFiles().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* The files that have been changed.
|
||||
* @return the changed files
|
||||
*/
|
||||
public Set<ChangedFile> getFiles() {
|
||||
return this.files;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.files.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof ChangedFiles) {
|
||||
ChangedFiles other = (ChangedFiles) obj;
|
||||
return this.sourceFolder.equals(other.sourceFolder)
|
||||
&& this.files.equals(other.files);
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.sourceFolder + " " + this.files;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Callback interface when file changes are detected.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface FileChangeListener {
|
||||
|
||||
/**
|
||||
* Called when files have been changed.
|
||||
* @param changeSet a set of the {@link ChangedFiles}
|
||||
*/
|
||||
void onChange(Set<ChangedFiles> changeSet);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A snapshot of a File at a given point in time.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class FileSnapshot {
|
||||
|
||||
private final File file;
|
||||
|
||||
private final boolean exists;
|
||||
|
||||
private final long length;
|
||||
|
||||
private final long lastModified;
|
||||
|
||||
public FileSnapshot(File file) {
|
||||
Assert.notNull(file, "File must not be null");
|
||||
Assert.isTrue(file.isFile() || !file.exists(), "File must not be a folder");
|
||||
this.file = file;
|
||||
this.exists = file.exists();
|
||||
this.length = file.length();
|
||||
this.lastModified = file.lastModified();
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof FileSnapshot) {
|
||||
FileSnapshot other = (FileSnapshot) obj;
|
||||
boolean equals = this.file.equals(other.file);
|
||||
equals &= this.exists == other.exists;
|
||||
equals &= this.length == other.length;
|
||||
equals &= this.lastModified == other.lastModified;
|
||||
return equals;
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = this.file.hashCode();
|
||||
hashCode = 31 * hashCode + (this.exists ? 1231 : 1237);
|
||||
hashCode = 31 * hashCode + (int) (this.length ^ (this.length >>> 32));
|
||||
hashCode = 31 * hashCode + (int) (this.lastModified ^ (this.lastModified >>> 32));
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.file.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Watches specific folders for file changes.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Phillip Webb
|
||||
* @see FileChangeListener
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class FileSystemWatcher {
|
||||
|
||||
private static final long DEFAULT_IDLE_TIME = 400;
|
||||
|
||||
private static final long DEFAULT_QUIET_TIME = 200;
|
||||
|
||||
private List<FileChangeListener> listeners = new ArrayList<FileChangeListener>();
|
||||
|
||||
private final boolean daemon;
|
||||
|
||||
private final long idleTime;
|
||||
|
||||
private final long quietTime;
|
||||
|
||||
private Thread watchThread;
|
||||
|
||||
private AtomicInteger remainingScans = new AtomicInteger(-1);
|
||||
|
||||
private Map<File, FolderSnapshot> folders = new LinkedHashMap<File, FolderSnapshot>();
|
||||
|
||||
/**
|
||||
* Create a new {@link FileSystemWatcher} instance.
|
||||
*/
|
||||
public FileSystemWatcher() {
|
||||
this(true, DEFAULT_IDLE_TIME, DEFAULT_QUIET_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FileSystemWatcher} instance.
|
||||
* @param daemon if a daemon thread used to monitor changes
|
||||
* @param idleTime the amount of time to wait between checking for changes
|
||||
* @param quietTime the amount of time required after a change has been detected to
|
||||
* ensure that updates have completed
|
||||
*/
|
||||
public FileSystemWatcher(boolean daemon, long idleTime, long quietTime) {
|
||||
this.daemon = daemon;
|
||||
this.idleTime = idleTime;
|
||||
this.quietTime = quietTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener for file change events. Cannot be called after the watcher has been
|
||||
* {@link #start() started}.
|
||||
* @param fileChangeListener the listener to add
|
||||
*/
|
||||
public synchronized void addListener(FileChangeListener fileChangeListener) {
|
||||
Assert.notNull(fileChangeListener, "FileChangeListener must not be null");
|
||||
checkNotStarted();
|
||||
this.listeners.add(fileChangeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a source folder to monitor. Cannot be called after the watcher has been
|
||||
* {@link #start() started}.
|
||||
* @param folder the folder to monitor
|
||||
*/
|
||||
public synchronized void addSourceFolder(File folder) {
|
||||
Assert.notNull(folder, "Folder must not be null");
|
||||
Assert.isTrue(folder.isDirectory(), "Folder must not be a file");
|
||||
checkNotStarted();
|
||||
this.folders.put(folder, null);
|
||||
}
|
||||
|
||||
private void checkNotStarted() {
|
||||
Assert.state(this.watchThread == null, "FileSystemWatcher already started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring the source folder for changes.
|
||||
*/
|
||||
public synchronized void start() {
|
||||
saveInitalSnapshots();
|
||||
if (this.watchThread == null) {
|
||||
this.watchThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
int remainingScans = FileSystemWatcher.this.remainingScans.get();
|
||||
while (remainingScans > 0 || remainingScans == -1) {
|
||||
try {
|
||||
if (remainingScans > 0) {
|
||||
FileSystemWatcher.this.remainingScans.decrementAndGet();
|
||||
}
|
||||
scan();
|
||||
remainingScans = FileSystemWatcher.this.remainingScans.get();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
this.watchThread.setName("File Watcher");
|
||||
this.watchThread.setDaemon(this.daemon);
|
||||
this.remainingScans = new AtomicInteger(-1);
|
||||
this.watchThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveInitalSnapshots() {
|
||||
for (File folder : this.folders.keySet()) {
|
||||
this.folders.put(folder, new FolderSnapshot(folder));
|
||||
}
|
||||
}
|
||||
|
||||
private void scan() throws InterruptedException {
|
||||
Thread.sleep(this.idleTime - this.quietTime);
|
||||
Set<FolderSnapshot> previous;
|
||||
Set<FolderSnapshot> current = new HashSet<FolderSnapshot>(this.folders.values());
|
||||
do {
|
||||
previous = current;
|
||||
current = getCurrentSnapshots();
|
||||
Thread.sleep(this.quietTime);
|
||||
}
|
||||
while (!previous.equals(current));
|
||||
updateSnapshots(current);
|
||||
}
|
||||
|
||||
private Set<FolderSnapshot> getCurrentSnapshots() {
|
||||
Set<FolderSnapshot> snapshots = new LinkedHashSet<FolderSnapshot>();
|
||||
for (File folder : this.folders.keySet()) {
|
||||
snapshots.add(new FolderSnapshot(folder));
|
||||
}
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
private void updateSnapshots(Set<FolderSnapshot> snapshots) {
|
||||
Map<File, FolderSnapshot> updated = new LinkedHashMap<File, FolderSnapshot>();
|
||||
Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
|
||||
for (FolderSnapshot snapshot : snapshots) {
|
||||
FolderSnapshot previous = this.folders.get(snapshot.getFolder());
|
||||
updated.put(snapshot.getFolder(), snapshot);
|
||||
ChangedFiles changedFiles = previous.getChangedFiles(snapshot);
|
||||
if (!changedFiles.getFiles().isEmpty()) {
|
||||
changeSet.add(changedFiles);
|
||||
}
|
||||
}
|
||||
if (!changeSet.isEmpty()) {
|
||||
fireListeners(Collections.unmodifiableSet(changeSet));
|
||||
}
|
||||
this.folders = updated;
|
||||
}
|
||||
|
||||
private void fireListeners(Set<ChangedFiles> changeSet) {
|
||||
for (FileChangeListener listener : this.listeners) {
|
||||
listener.onChange(changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop monitoring the source folders.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
stopAfter(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop monitoring the source folders.
|
||||
* @param remainingScans the number of scans remaming
|
||||
*/
|
||||
synchronized void stopAfter(int remainingScans) {
|
||||
Thread thread = this.watchThread;
|
||||
if (thread != null) {
|
||||
this.remainingScans.set(remainingScans);
|
||||
try {
|
||||
thread.join();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
this.watchThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFile.Type;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A snapshot of a folder at a given point in time.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class FolderSnapshot {
|
||||
|
||||
private static final Set<String> DOT_FOLDERS = Collections
|
||||
.unmodifiableSet(new HashSet<String>(Arrays.asList(".", "..")));
|
||||
|
||||
private final File folder;
|
||||
|
||||
private final Date time;
|
||||
|
||||
private Set<FileSnapshot> files;
|
||||
|
||||
/**
|
||||
* Create a new {@link FolderSnapshot} for the given folder.
|
||||
* @param folder the source folder
|
||||
*/
|
||||
public FolderSnapshot(File folder) {
|
||||
Assert.notNull(folder, "Folder must not be null");
|
||||
Assert.isTrue(folder.isDirectory(), "Folder must not be a file");
|
||||
this.folder = folder;
|
||||
this.time = new Date();
|
||||
Set<FileSnapshot> files = new LinkedHashSet<FileSnapshot>();
|
||||
collectFiles(folder, files);
|
||||
this.files = Collections.unmodifiableSet(files);
|
||||
}
|
||||
|
||||
private void collectFiles(File source, Set<FileSnapshot> result) {
|
||||
File[] children = source.listFiles();
|
||||
if (children != null) {
|
||||
for (File child : children) {
|
||||
if (child.isDirectory() && !DOT_FOLDERS.contains(child.getName())) {
|
||||
collectFiles(child, result);
|
||||
}
|
||||
else if (child.isFile()) {
|
||||
result.add(new FileSnapshot(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ChangedFiles getChangedFiles(FolderSnapshot snapshot) {
|
||||
Assert.notNull(snapshot, "Snapshot must not be null");
|
||||
File folder = this.folder;
|
||||
Assert.isTrue(snapshot.folder.equals(folder), "Snapshot source folder must be '"
|
||||
+ folder + "'");
|
||||
Set<ChangedFile> changes = new LinkedHashSet<ChangedFile>();
|
||||
Map<File, FileSnapshot> previousFiles = getFilesMap();
|
||||
for (FileSnapshot currentFile : snapshot.files) {
|
||||
FileSnapshot previousFile = previousFiles.remove(currentFile.getFile());
|
||||
if (previousFile == null) {
|
||||
changes.add(new ChangedFile(folder, currentFile.getFile(), Type.ADD));
|
||||
}
|
||||
else if (!previousFile.equals(currentFile)) {
|
||||
changes.add(new ChangedFile(folder, currentFile.getFile(), Type.MODIFY));
|
||||
}
|
||||
}
|
||||
for (FileSnapshot previousFile : previousFiles.values()) {
|
||||
changes.add(new ChangedFile(folder, previousFile.getFile(), Type.DELETE));
|
||||
}
|
||||
return new ChangedFiles(folder, changes);
|
||||
}
|
||||
|
||||
private Map<File, FileSnapshot> getFilesMap() {
|
||||
Map<File, FileSnapshot> files = new LinkedHashMap<File, FileSnapshot>();
|
||||
for (FileSnapshot file : this.files) {
|
||||
files.put(file.getFile(), file);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof FolderSnapshot) {
|
||||
FolderSnapshot other = (FolderSnapshot) obj;
|
||||
return this.folder.equals(other.folder) && this.files.equals(other.files);
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = this.folder.hashCode();
|
||||
hashCode = 31 * hashCode + this.files.hashCode();
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the source folder of this snapshot.
|
||||
* @return the source folder
|
||||
*/
|
||||
public File getFolder() {
|
||||
return this.folder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.folder + " snaphost at " + this.time;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to watch the local filesystem for changes.
|
||||
*/
|
||||
package org.springframework.boot.developertools.filewatch;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFile.Type;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ChangedFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ChangedFileTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void sourceFolderMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("SourceFolder must not be null");
|
||||
new ChangedFile(null, this.temp.newFile(), Type.ADD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("File must not be null");
|
||||
new ChangedFile(this.temp.newFolder(), null, Type.ADD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Type must not be null");
|
||||
new ChangedFile(this.temp.newFile(), this.temp.newFolder(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFile() throws Exception {
|
||||
File file = this.temp.newFile();
|
||||
ChangedFile changedFile = new ChangedFile(this.temp.newFolder(), file, Type.ADD);
|
||||
assertThat(changedFile.getFile(), equalTo(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getType() throws Exception {
|
||||
ChangedFile changedFile = new ChangedFile(this.temp.newFolder(),
|
||||
this.temp.newFile(), Type.DELETE);
|
||||
assertThat(changedFile.getType(), equalTo(Type.DELETE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRelativeName() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
File subFolder = new File(folder, "A");
|
||||
File file = new File(subFolder, "B.txt");
|
||||
ChangedFile changedFile = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFile.getRelativeName(), equalTo("A/B.txt"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileSnapshot}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FileSnapshotTests {
|
||||
|
||||
private static final long TWO_MINS = TimeUnit.MINUTES.toMillis(2);
|
||||
|
||||
private static final long MODIFIED = new Date().getTime()
|
||||
- TimeUnit.DAYS.toMillis(10);
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void fileMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("File must not be null");
|
||||
new FileSnapshot(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileMustNotBeAFolder() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("File must not be a folder");
|
||||
new FileSnapshot(this.temporaryFolder.newFolder());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsIfTheSame() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
File fileCopy = new File(file, "x").getParentFile();
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
FileSnapshot snapshot2 = new FileSnapshot(fileCopy);
|
||||
assertThat(snapshot1, equalTo(snapshot2));
|
||||
assertThat(snapshot1.hashCode(), equalTo(snapshot2.hashCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsIfDeleted() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
file.delete();
|
||||
assertThat(snapshot1, not(equalTo(new FileSnapshot(file))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsIfLengthChanges() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
setupFile(file, "abcd", MODIFIED);
|
||||
assertThat(snapshot1, not(equalTo(new FileSnapshot(file))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsIfLastModifiedChanges() throws Exception {
|
||||
File file = createNewFile("abc", MODIFIED);
|
||||
FileSnapshot snapshot1 = new FileSnapshot(file);
|
||||
setupFile(file, "abc", MODIFIED + TWO_MINS);
|
||||
assertThat(snapshot1, not(equalTo(new FileSnapshot(file))));
|
||||
}
|
||||
|
||||
private File createNewFile(String content, long lastModified) throws IOException {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
setupFile(file, content, lastModified);
|
||||
return file;
|
||||
}
|
||||
|
||||
private void setupFile(File file, String content, long lastModified)
|
||||
throws IOException {
|
||||
FileCopyUtils.copy(content.getBytes(), file);
|
||||
file.setLastModified(lastModified);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFile.Type;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileSystemWatcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FileSystemWatcherTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private FileSystemWatcher watcher;
|
||||
|
||||
private List<Set<ChangedFiles>> changes = new ArrayList<Set<ChangedFiles>>();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
setupWatcher(20, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listenerMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("FileChangeListener must not be null");
|
||||
this.watcher.addListener(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotAddListenerToStartedListener() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("FileSystemWatcher already started");
|
||||
this.watcher.start();
|
||||
this.watcher.addListener(mock(FileChangeListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceFolderMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Folder must not be null");
|
||||
this.watcher.addSourceFolder(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotAddSourceFolderToStartedListener() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("FileSystemWatcher already started");
|
||||
this.watcher.start();
|
||||
this.watcher.addSourceFolder(this.temp.newFolder());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFile() throws Exception {
|
||||
File folder = startWithNewFolder();
|
||||
File file = touch(new File(folder, "test.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addNestedFile() throws Exception {
|
||||
File folder = startWithNewFolder();
|
||||
File file = touch(new File(new File(folder, "sub"), "text.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waitsForIdleTime() throws Exception {
|
||||
this.changes.clear();
|
||||
setupWatcher(100, 0);
|
||||
File folder = startWithNewFolder();
|
||||
touch(new File(folder, "test1.txt"));
|
||||
Thread.sleep(200);
|
||||
touch(new File(folder, "test2.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
assertThat(this.changes.size(), equalTo(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waitsForQuietTime() throws Exception {
|
||||
setupWatcher(300, 200);
|
||||
File folder = startWithNewFolder();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
touch(new File(folder, i + "test.txt"));
|
||||
Thread.sleep(100);
|
||||
}
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
assertThat(changedFiles.getFiles().size(), equalTo(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withExistingFiles() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
touch(new File(folder, "test.txt"));
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.start();
|
||||
File file = touch(new File(folder, "test2.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleSources() throws Exception {
|
||||
File folder1 = this.temp.newFolder();
|
||||
File folder2 = this.temp.newFolder();
|
||||
this.watcher.addSourceFolder(folder1);
|
||||
this.watcher.addSourceFolder(folder2);
|
||||
this.watcher.start();
|
||||
File file1 = touch(new File(folder1, "test.txt"));
|
||||
File file2 = touch(new File(folder2, "test.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
Set<ChangedFiles> change = getSingleOnChange();
|
||||
assertThat(change.size(), equalTo(2));
|
||||
for (ChangedFiles changedFiles : change) {
|
||||
if (changedFiles.getSourceFolder().equals(folder1)) {
|
||||
ChangedFile file = new ChangedFile(folder1, file1, Type.ADD);
|
||||
assertEquals(new HashSet<ChangedFile>(Arrays.asList(file)),
|
||||
changedFiles.getFiles());
|
||||
}
|
||||
else {
|
||||
ChangedFile file = new ChangedFile(folder2, file2, Type.ADD);
|
||||
assertEquals(new HashSet<ChangedFile>(Arrays.asList(file)),
|
||||
changedFiles.getFiles());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleListeners() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
final Set<ChangedFiles> listener2Changes = new LinkedHashSet<ChangedFiles>();
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.addListener(new FileChangeListener() {
|
||||
@Override
|
||||
public void onChange(Set<ChangedFiles> changeSet) {
|
||||
listener2Changes.addAll(changeSet);
|
||||
}
|
||||
});
|
||||
this.watcher.start();
|
||||
File file = touch(new File(folder, "test.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
ChangedFile expected = new ChangedFile(folder, file, Type.ADD);
|
||||
assertThat(changedFiles.getFiles(), contains(expected));
|
||||
assertEquals(this.changes.get(0), listener2Changes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyDeleteAndAdd() throws Exception {
|
||||
File folder = this.temp.newFolder();
|
||||
File modify = touch(new File(folder, "modify.txt"));
|
||||
File delete = touch(new File(folder, "delete.txt"));
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.start();
|
||||
FileCopyUtils.copy("abc".getBytes(), modify);
|
||||
delete.delete();
|
||||
File add = touch(new File(folder, "add.txt"));
|
||||
this.watcher.stopAfter(1);
|
||||
ChangedFiles changedFiles = getSingleChangedFiles();
|
||||
Set<ChangedFile> actual = changedFiles.getFiles();
|
||||
Set<ChangedFile> expected = new HashSet<ChangedFile>();
|
||||
expected.add(new ChangedFile(folder, modify, Type.MODIFY));
|
||||
expected.add(new ChangedFile(folder, delete, Type.DELETE));
|
||||
expected.add(new ChangedFile(folder, add, Type.ADD));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
private void setupWatcher(long idleTime, long quietTime) {
|
||||
this.watcher = new FileSystemWatcher(false, idleTime, quietTime);
|
||||
this.watcher.addListener(new FileChangeListener() {
|
||||
@Override
|
||||
public void onChange(Set<ChangedFiles> changeSet) {
|
||||
FileSystemWatcherTests.this.changes.add(changeSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private File startWithNewFolder() throws IOException {
|
||||
File folder = this.temp.newFolder();
|
||||
this.watcher.addSourceFolder(folder);
|
||||
this.watcher.start();
|
||||
return folder;
|
||||
}
|
||||
|
||||
private ChangedFiles getSingleChangedFiles() {
|
||||
Set<ChangedFiles> singleChange = getSingleOnChange();
|
||||
assertThat(singleChange.size(), equalTo(1));
|
||||
return singleChange.iterator().next();
|
||||
}
|
||||
|
||||
private Set<ChangedFiles> getSingleOnChange() {
|
||||
assertThat(this.changes.size(), equalTo(1));
|
||||
return this.changes.get(0);
|
||||
}
|
||||
|
||||
private File touch(File file) throws FileNotFoundException, IOException {
|
||||
file.getParentFile().mkdirs();
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
fileOutputStream.close();
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.springframework.boot.developertools.filewatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFile.Type;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FolderSnapshot}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class FolderSnapshotTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File folder;
|
||||
|
||||
private FolderSnapshot initialSnapshot;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.folder = createTestFolderStructure();
|
||||
this.initialSnapshot = new FolderSnapshot(this.folder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void folderMustNotBeNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Folder must not be null");
|
||||
new FolderSnapshot(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void folderMustNotBeFile() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Folder must not be a file");
|
||||
new FolderSnapshot(this.temporaryFolder.newFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNothingHasChanged() throws Exception {
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, equalTo(updatedSnapshot));
|
||||
assertThat(this.initialSnapshot.hashCode(), equalTo(updatedSnapshot.hashCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsWhenAFileIsAdded() throws Exception {
|
||||
new File(new File(this.folder, "folder1"), "newfile").createNewFile();
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, not(equalTo(updatedSnapshot)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsWhenAFileIsDeleted() throws Exception {
|
||||
new File(new File(this.folder, "folder1"), "file1").delete();
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, not(equalTo(updatedSnapshot)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEqualsWhenAFileIsModified() throws Exception {
|
||||
File file1 = new File(new File(this.folder, "folder1"), "file1");
|
||||
FileCopyUtils.copy("updatedcontent".getBytes(), file1);
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
assertThat(this.initialSnapshot, not(equalTo(updatedSnapshot)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesSnapshotMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Snapshot must not be null");
|
||||
this.initialSnapshot.getChangedFiles(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesSnapshotMustBeTheSameSourceFolder() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Snapshot source folder must be '" + this.folder + "'");
|
||||
this.initialSnapshot.getChangedFiles(new FolderSnapshot(
|
||||
createTestFolderStructure()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesWhenNothingHasChanged() throws Exception {
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
this.initialSnapshot.getChangedFiles(updatedSnapshot);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangedFilesWhenAFileIsAddedAndDeletedAndChanged() throws Exception {
|
||||
File folder1 = new File(this.folder, "folder1");
|
||||
File file1 = new File(folder1, "file1");
|
||||
File file2 = new File(folder1, "file2");
|
||||
File newFile = new File(folder1, "newfile");
|
||||
FileCopyUtils.copy("updatedcontent".getBytes(), file1);
|
||||
file2.delete();
|
||||
newFile.createNewFile();
|
||||
FolderSnapshot updatedSnapshot = new FolderSnapshot(this.folder);
|
||||
ChangedFiles changedFiles = this.initialSnapshot.getChangedFiles(updatedSnapshot);
|
||||
assertThat(changedFiles.getSourceFolder(), equalTo(this.folder));
|
||||
assertThat(getChangedFile(changedFiles, file1).getType(), equalTo(Type.MODIFY));
|
||||
assertThat(getChangedFile(changedFiles, file2).getType(), equalTo(Type.DELETE));
|
||||
assertThat(getChangedFile(changedFiles, newFile).getType(), equalTo(Type.ADD));
|
||||
}
|
||||
|
||||
private ChangedFile getChangedFile(ChangedFiles changedFiles, File file) {
|
||||
for (ChangedFile changedFile : changedFiles) {
|
||||
if (changedFile.getFile().equals(file)) {
|
||||
return changedFile;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private File createTestFolderStructure() throws IOException {
|
||||
File root = this.temporaryFolder.newFolder();
|
||||
File folder1 = new File(root, "folder1");
|
||||
folder1.mkdirs();
|
||||
FileCopyUtils.copy("abc".getBytes(), new File(folder1, "file1"));
|
||||
FileCopyUtils.copy("abc".getBytes(), new File(folder1, "file2"));
|
||||
return root;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user