Browse Source
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-3084pull/3077/merge
11 changed files with 1320 additions and 0 deletions
@ -0,0 +1,128 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue