/**
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.commons.file.util;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileFactory;
import com.mucommander.commons.file.PermissionAccess;
import com.mucommander.commons.file.PermissionType;
import com.mucommander.commons.io.RandomAccessOutputStream;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import java.io.IOException;
/**
* A test case for the {@link com.mucommander.commons.file.util.FileMonitor} class.
*
* @author Maxence Bernard
*/
public class FileMonitorTest implements FileMonitorConstants {
/** Temporary file used by the current test */
private AbstractFile file;
/** FileMonitor used by the current test */
private FileMonitor fileMonitor;
/** FileChangeTracker used by the current test */
private FileChangeTracker fileChangeTracker;
/** Poll period used by the FileMonitor (in milliseconds) */
private final static int POLL_PERIOD = 10;
/** Number of milliseconds to wait for an attribute change before timing out */
private final static int TIMEOUT = 5000;
/**
* Validates that FileMonitor properly reports {@link FileMonitor#DATE_ATTRIBUTE} changes when a file's date changes.
*
* @throws IOException should not normally happen
*/
@Test
public void testDateAttribute() throws IOException {
setUp(DATE_ATTRIBUTE);
file.changeDate(file.getDate()-2000);
assert hasAttributeChanged(DATE_ATTRIBUTE);
file.changeDate(file.getDate()+2000);
assert hasAttributeChanged(DATE_ATTRIBUTE);
}
/**
* Validates that FileMonitor properly reports {@link FileMonitor#SIZE_ATTRIBUTE} changes when a file's size changes.
*
* @throws IOException should not normally happen
*/
@Test
public void testSizeAttribute() throws IOException {
setUp(SIZE_ATTRIBUTE);
RandomAccessOutputStream raos = file.getRandomAccessOutputStream();
try {
raos.setLength(10);
assert hasAttributeChanged(SIZE_ATTRIBUTE);
raos.setLength(0);
}
finally {
if(raos!=null)
raos.close();
}
}
/**
* Validates that FileMonitor properly reports {@link FileMonitor#PERMISSIONS_ATTRIBUTE} changes when a file's
* permissions change.
*
* @throws IOException should not normally happen
*/
@Test
public void testPermissionsAttribute() throws IOException {
setUp(PERMISSIONS_ATTRIBUTE);
file.changePermission(PermissionAccess.USER, PermissionType.WRITE, !file.getPermissions().getBitValue(PermissionAccess.USER, PermissionType.WRITE));
assert hasAttributeChanged(PERMISSIONS_ATTRIBUTE);
}
/**
* Validates that FileMonitor properly reports {@link FileMonitor#IS_DIRECTORY_ATTRIBUTE} changes when a file
* becomes a directory and vice-versa.
*
* @throws IOException should not normally happen
*/
@Test
public void testIsDirectoryAttribute() throws IOException {
setUp(IS_DIRECTORY_ATTRIBUTE);
file.delete();
file.mkdir();
assert hasAttributeChanged(IS_DIRECTORY_ATTRIBUTE);
file.delete();
file.mkfile();
assert hasAttributeChanged(IS_DIRECTORY_ATTRIBUTE);
}
/**
* Validates that FileMonitor properly reports {@link FileMonitor#EXISTS_ATTRIBUTE} changes when an existing file or
* directory is deleted, or when a non-existing file or directory is created.
*
* @throws IOException should not normally happen
*/
@Test
public void testExistsAttribute() throws IOException {
setUp(EXISTS_ATTRIBUTE);
file.delete();
assert hasAttributeChanged(EXISTS_ATTRIBUTE);
file.mkdir();
assert hasAttributeChanged(EXISTS_ATTRIBUTE);
file.delete();
assert hasAttributeChanged(EXISTS_ATTRIBUTE);
file.mkfile();
assert hasAttributeChanged(EXISTS_ATTRIBUTE);
}
/**
* Called after each test, stops monitoring file changes.
*/
@AfterMethod
protected void tearDown() {
fileMonitor.stopMonitoring();
}
/////////////////////////////////
// Support methods and classes //
/////////////////////////////////
/**
* Sets everything up for a test: retrieves a temporary file instance, create the file, waits until the file exists,
* create a <code>FileMonitor</code>, register a {@link FileChangeTracker} and finally start monitoring file changes.
*
* @param attribute the attribute to monitor
* @throws IOException should not normally happen
*/
private void setUp(int attribute) throws IOException {
// Retrieve a temporary AbstractFile instance that will be deleted on VM shutdown
file = FileFactory.getTemporaryFile(getClass().getName(), true);
// Create the file
file.mkfile();
// Waits until the file truly exists (I/O are usually asynchroneous)
while(!file.exists()) {
try { Thread.sleep(POLL_PERIOD); }
catch(InterruptedException e) {}
}
// Create the monitor, change listener and start monitoring file changes
fileMonitor = new FileMonitor(file, attribute, POLL_PERIOD);
fileChangeTracker = new FileChangeTracker();
fileMonitor.addFileChangeListener(fileChangeTracker);
fileMonitor.startMonitoring();
}
/**
* Returns <code>true</code> if the current <code>FileMonitor</code> reported a change on the specified attribute.
* This method will wait up to {@link #TIMEOUT} milliseconds for the attribute to change and if it hasn't changed,
* will return <code>false</code>.
*
* @param attribute the attribute to test against
* @return true if the current <code>FileMonitor</code> reported a change on the specified attribute
*/
private boolean hasAttributeChanged(int attribute) {
boolean hasAttributeChanged = false;
try {
synchronized(fileChangeTracker) {
hasAttributeChanged = (attribute&fileChangeTracker.getChangedAttributes())!=0;
if(!hasAttributeChanged) {
// Waits until FileChangeTracker calls notify to report an attribute change; give up after
// TIMEOUT milliseconds
fileChangeTracker.wait(TIMEOUT);
}
hasAttributeChanged = (attribute&fileChangeTracker.getChangedAttributes())!=0;
}
}
catch(InterruptedException e) {}
// Resets FileChangeTracker to be ready to detect the next attribute change
fileChangeTracker.reset();
return hasAttributeChanged;
}
/**
* This {@link FileChangeListener} keeps track of the attributes that changed, as reported by
* {@link #fileChanged(com.mucommander.commons.file.AbstractFile, int)}.
*/
private static class FileChangeTracker implements FileChangeListener {
/** Bit mask that describes the attributes that have changed */
private int changedAttributes;
/**
* Returns a bit mask that describes the attributes that have changed.
*
* @return a bit mask that describes the attributes that have changed
*/
private int getChangedAttributes() {
return changedAttributes;
}
/**
* Resets the changed attributes bit mask to zero.
*/
private void reset() {
this.changedAttributes = 0;
}
///////////////////////////////////////
// FileChangeListener implementation //
///////////////////////////////////////
public void fileChanged(AbstractFile file, int changedAttributes) {
synchronized(this) {
this.changedAttributes |= changedAttributes;
notify(); // Notify that hasAttributeChanged(int) method that an attribute has changed
}
}
}
}