package cologne.eck.all_peas.files;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Map;
import java.util.TreeMap;
import cologne.eck.all_peas.control.PeaControl;
import cologne.eck.all_peas.data.Attachments;
import cologne.eck.all_peas.data.PeaProperties;
import cologne.eck.tools.FileTools;
import cologne.eck.tools.UnexpectedValueException;
/**
* This class contains methods for file, that are specific
* for all PEAs of the PeaFactory
*/
/**
*
* @author Axel von dem Bruch
*
*/
/*
* NOTES:
* TreeMap:
* log(n) time cost for containsKey, get, put and remove
* put overwrites the existing value,
* so containsKey must be called before, if this behavior is unsuitable.
*
* get key/value by index (this is expensive):
* String key = map.keySet().toArray()[***INDEX***];
* String value = map.get(key);
* to get only the value:
* map.values().toArray()[***INDEX***];
* or iterate over the map
*/
public class FileModel {
/**
* TreeMap of File and String:
* the key is the original file,
* the value is the annotations for the file.
* Annotations may contain
* invalid markers,
* short descriptions of the invalidity,
* directory markers,
* space to indicate the file hierarchy,
*/
TreeMap<File, String> map = new TreeMap<File, String>();
private int fileNumber = 0; // number of displayed valid files
private long allSize = 0; // size of displayed valid files
// these are helper values to check if number of files and size
// is greater than limits
private long newFileNumber = 0;
private long newFileSize = 0;
private static final String INVALID_MARKER = ": ~";
/**
* Checks a file if it is a valid to encrypt/decrypt or not.
* Checks existence, read and write access, if empty or too large.
* If the file is invalid, the invalid marker and a comment is set
* as value.
*
* @param file the file to check
* @param addFile true: add the file to the map
* @param printErrors true: print error messages (test mode)
*
* @return true if the file is valid, false if invalid
*/
private final boolean checkAccess(File file,
boolean addFile, boolean printErrors) {
// check if exists:
if ( ! file.exists() ) {
if (addFile == true){
map.put(file, PeaProperties.getBundle().getString("can_not_find") + INVALID_MARKER);
}
if (printErrors == true){
System.err.println("File notfound: " + file.getAbsolutePath());
}
return false;
}
// check access:
if ( ! file.canRead() ) {
if (addFile == true){
map.put(file, PeaProperties.getBundle().getString("can_not_read") + INVALID_MARKER);
}
if (printErrors == true){
System.err.println("No read access: " + file.getAbsolutePath());
}
return false;
}
if ( ! file.canWrite() ) {
if (addFile == true){
map.put(file, PeaProperties.getBundle().getString("can_not_write") + INVALID_MARKER);
}
if (printErrors == true){
System.err.println("No write access: " + file.getAbsolutePath());
}
return false;
}
if ( file.length() == 0 && ! file.isDirectory() ) {
if (addFile == true){
map.put(file, PeaProperties.getBundle().getString("empty_file") + INVALID_MARKER);
}
if (printErrors == true){
System.err.println("Empty file: " + file.getAbsolutePath());
}
return false;
}
if ( file.length() > Long.MAX_VALUE || file.length() < 0) {
if (addFile == true){
map.put(file, PeaProperties.getBundle().getString("file_too_large") + INVALID_MARKER);
}
if (printErrors == true){
System.err.println("Invalid file size: " + file.getAbsolutePath());
}
return false;
}
if (addFile == true){
map.put(file, "");
}
return true;
}
/**
* Checks if a file contains the file identifier of the given PEA.
* If not, an invalid marker and a comment is set as value.
* Note: The file must be already listed in the map. Invalid
* files are not checked.
*
* @param file the file to check
* @param printErrors print error messages to stderr (true) or not (false)
* @param addFile true = add file and set annotations
*
* @return true if the file is valid and contains the fileIdentifier, false otherwise
*/
public final boolean checkFileIdentifier(
File file, boolean printErrors, boolean addFile) {
boolean result = false;
RandomAccessFile f;
String fileName = file.getAbsolutePath();
if (! file.isFile()) {
if ( printErrors == true) {
new UnexpectedValueException("file", "File", "is not a file: " + file.getAbsolutePath()).printDescription();
}
return false;
}
if (addFile == true && ! map.containsKey(file)){
// the file must have been checked for access before...
if ( printErrors == true) {
new UnexpectedValueException("file", "File", "is not listed in map").printDescription();
}
return false;
}
if (addFile == true && map.get(file).contains(INVALID_MARKER)){
// do not check...
return false;
}
try {
f = new RandomAccessFile(fileName, "r" );
if(Attachments.checkFileIdentifier(f, false) == true) {
if (addFile== true) {
map.put(file, "");
}
result = true;
} else {
if (addFile == true) {
map.put(file, PeaProperties.getBundle().getString("not_encrypted_with_this_archive")
+ INVALID_MARKER);
}
if (printErrors == true) {
System.err.println("File was not encrypted with this PEA: " + fileName);
}
result = false;
}
f.close();
return result;
} catch (FileNotFoundException e) {
if (addFile== true) {
map.put(file, PeaProperties.getBundle().getString("can_not_find") + INVALID_MARKER);
}
if (printErrors == true) {
System.err.println("File not found: " + fileName);
}
return false;
} catch (IOException e) {
if (addFile== true) {
map.put(file, PeaProperties.getBundle().getString("can_not_read") + INVALID_MARKER);
}
if (printErrors == true) {
System.err.println("No access to file: " + fileName);
}
return false;
}
}
/**
* Checks a file and sets possibly an invalid marker as value.
*
* @param file the file to check
* @param plainModus the modus: encrypted file = false
* decrypted files = true
* @param printErrors print error messages to stderr (true) or not (false)
* @param checkAgain true if already listed files should be checked again
*
* @return true if the file was added, false if file
* was already listed
*/
public final boolean checkAndAddFile( File file,
boolean plainModus, boolean printErrors, boolean checkAgain) {
boolean alreadyInList = map.containsKey(file);
if (alreadyInList == false) {
// add the file to the map:
map.put(file, "");
}
if (alreadyInList == false || checkAgain == true) {
boolean access = checkAccess(file, true, printErrors);
if (file.isFile() ) {
if (plainModus == true) {
if (PeaControl.getDialog().checkFormat(file, true, true, false) == true) {
return true;
} else {
map.put(file, PeaProperties.getBundle().getString("unsuitable_format") + INVALID_MARKER);
//return false;
}
} else { // plainModus == false
if( access == true ){
if (checkFileIdentifier(file, printErrors, true) == true){ // add file
return true;
} else {
//return false;
}
} else {
// don't check
}
}
} else if (file.isDirectory()) {
if (file.list().length == 0){
map.put(file,
PeaProperties.getBundle().getString("invalid_empty_directory")
+ INVALID_MARKER);
}
} else {
map.put(file, PeaProperties.getBundle().getString("unknown_file_type")
+ INVALID_MARKER);
//return false;
}
return true;
} else {
return false;
}
}
/**
* Checks if the TreeMap contains at least
* one valid file to encrypt/decrypt
*
* @return true if at least one valid file was found
*/
public boolean checkForValidFile(){
boolean validFileFound = false;
int size = map.size();
if (size == 0) {
return false;
}
File[] files = map.keySet().toArray(new File[map.size()]);
for (int i = 0; i < size; i++) {
if (files[i].isDirectory()){
continue;
}
if (map.get(files[i]).contains(INVALID_MARKER)){
continue;
} else {
validFileFound = true;
break;
}
}
return validFileFound;
}
/**
* Checks if at least one annotation
* contains the given String
*
* @param containingString the String to check
*
* @return true if the map contains the String
*/
public boolean checkAnnotations(String containingString){
for(Map.Entry<File, String> entry : map.entrySet()) {
String annotation = entry.getValue();
if (annotation.contains(containingString)){
return true;
}
}
return false;
}
/**
* Checks if a directory contains at least
* one valid file to encrypt/decrypt. This function does
* not set annotation or add files to the map
*
* @param directory the directory to check
* @param printErrors print error messages to stderr (true) or not (false)
* @param plainModus true: plain text files,false: encrypted files
* @param window the component for dialogs
*
* @return 0 if one valid file was found, 1 no valid file found,
* -1 if directory should not be added (execution time break)
*/
public final int checkAndAddIncludedChildren(File directory,
boolean printErrors, boolean plainModus, Object window) {
if (directory == null) {
if (printErrors == true) {
new UnexpectedValueException("directory", "File", "is null").printDescription();
}
return 1;
}
if (directory.isDirectory() == false) {
if (printErrors == true) {
new UnexpectedValueException("directory", "File", "is not directory").printDescription();
}
return 1;
}
// update helper values
newFileNumber = 0;
newFileSize = 0;
if (checkNumberAndSizeOfFolder(directory, window, printErrors) == false){
return -1;
}
File[] files = FileTools.getAllFilesOfFolder(directory, false);// list also directories
if (files == null || files.length == 0){
return 1;
}
int len = files.length;
for (int i = 0; i < len; i++) {
File file = files[i];
if (file.isDirectory()){
int checkChildren = checkAndAddIncludedChildren(file, printErrors,plainModus, window);
if (checkChildren == 0){
return 0;
} else if (checkChildren == -1){
// adding process broken because of execution time warning
return -1;
} else {
continue;
}
} else if (file.isFile()){
if (checkAccess(file, false, printErrors) == true) {
if (plainModus == false) {
if (checkFileIdentifier(file, printErrors, false) == true) {// do not add file
return 0;
}
} else {
// format check for PEA: only simple check
if (PeaControl.getDialog().checkFormat(file, true, false, false) == true) {
return 0;
}
}
}
}
}
return 1;// no valid file found
}
/**
* Check number and size before checking and
* adding the files
*
* @param files the files to check
* @param window the component for warning dialog
* @param printErrors print error messages in test mode
*
* @return true: files should be added
* false: do not add files (process canceled or closed)
*/
public boolean checkNumberAndSize(File[] files, Object window, boolean printErrors) {
// reset helper values:
newFileNumber = 0;
newFileSize = 0;
if (files == null) {
new UnexpectedValueException("files", "File[]", "is null").printDescription();
return false;
}
int len = files.length;
for (int i= 0; i < len; i++) {
File file = files[i];
if (file.isFile()) {
// check size limits:
newFileSize = FileTools.getFileSize(files[i].getAbsolutePath());
newFileNumber ++;
if (ExecutionTimeObserver.warnExecutionTime(
newFileNumber, newFileSize, this, window) == false){
// remove if in map
map.remove(file);
return false;
}
} else if (file.isDirectory()){
if (checkNumberAndSizeOfFolder(file, window,printErrors) == false){
return false;
}
} else {
// nothing to do: invalid file
}
}
return true; // no warning or warnings ignored
}
/**
* Check if the number of files and overall size of a folder
* is greater than limits. This function is rather fast
* than accurate and checks only to give a warning message
* for long file number or large size.
*
* @param directory the directory
* @param window the parent frame or component for the
* warning dialog
* @param printErrors print error messages in test mode
*
* @return true: files should be added, false: do not add files
*/
public boolean checkNumberAndSizeOfFolder(File directory, Object window, boolean printErrors) {
if (directory == null) {
new UnexpectedValueException("directory", "File", "is null").printDescription();
//return false;
}
if (! directory.isDirectory()) {
new UnexpectedValueException("directory", "File", "is not directory: " + directory.getAbsolutePath()).printDescription();
//return false;
}
// get direct children: files and directories
File[] fileList = directory.listFiles();
if (fileList == null) {
if (printErrors == true){
new UnexpectedValueException("fileList", "File[]", "is null: " + directory.getAbsolutePath()).printDescription();
}
return false;
}
int len = fileList.length;
newFileNumber += len;
// check number limits:
if (ExecutionTimeObserver.warnExecutionTime(newFileNumber, newFileSize, this, window) == false){
// remove from map if it was added
map.remove(directory);
return false;
}
for (int i = 0; i < len; i++) {
File file = fileList[i];
if (file.isFile()) {
newFileSize += FileTools.getFileSize(file.getAbsolutePath());
// check size limits:
if (ExecutionTimeObserver.warnExecutionTime(
newFileNumber, newFileSize, this, window) == false){
// remove from map if it was added
map.remove(file);
return false;
}
} else if (file.isDirectory()) {
if (checkNumberAndSizeOfFolder(file, window, printErrors) == false){
return false;
}
}
}
return true;
}
/**
* Remove all invalid and deselected files from map
*/
public void cleanupMap(){
File[] files = map.keySet().toArray(new File[map.size()]);
int len = files.length;
if (len == 0){
return;
}
for (int i = 0; i < len; i++) {
String annotation = map.get(files[i]);
if (annotation.contains(INVALID_MARKER) // invalid
|| annotation.startsWith("-")){ // deselected
map.remove(files[i]);
}
}
}
/**
* Calculate and set the file number and overall size
* of the files of the current map.
*/
public void calculateNumberAndSize(){
//reset:
allSize = 0;
fileNumber = 0;
// get all files:
File[] files = map.keySet().toArray(new File[map.size()]);
int len = files.length;
// set values:
for (int i = 0; i < len; i++) {
File file = files[i];
if (file.isFile()){ // only valid files
if (! (map.get(file).contains(INVALID_MARKER) // invalid
|| map.get(file).startsWith("-"))){ // deselected
try {
allSize += FileTools.getFileSize(file);
fileNumber++;
} catch (Exception e) {
map.put(file,
PeaProperties.getBundle().getString("unexpected_error")
+ " -size- "
+ INVALID_MARKER);
}
}
}
}
}
/**
* Get valid file names: only files, not directories,
* not invalid or not selected files
*
* @return the valid file names
*/
/* public String[] getValidFileNames() {
File[] files = map.keySet().toArray(new File[map.size()]);
int len = files.length;
int index = 0;
String[] validFileNames = new String[len];
// set values:
for (int i = 0; i < len; i++) {
File file = files[i];
if (file.isFile()){ // only valid files
if (! (map.get(file).contains(INVALID_MARKER) // invalid
|| map.get(file).startsWith("-"))){ // deselected
validFileNames[index] = file.getAbsolutePath();
index++;
}
}
}
String[] result = new String[index];
System.arraycopy(validFileNames, 0, result, 0, index);
return result;
}
*/
/**
* Removes a key from the map.
*
* @param file the file to remove
*/
public void remove(File file){
map.remove(file);
}
/**
* Checks if a file is already listed in the map
*
* @param file the file to check
* @return true if the file is already listed
* false otherwise
*/
public boolean containsFile(File file) {
if (map.containsKey(file)){
return true;
} else {
return false;
}
}
//TODO: performance
/**
* Checks if a directory is has a child
* in the current map
*
* @param file the directory to check
* @return true if there is a child in the map
*/
public boolean hasValidChild(File file) {
File[] files = map.keySet().toArray(new File[map.size()]);
int len = files.length;
for (int i = 0; i < len; i++) {
if (files[i].getParentFile().equals(file)) {
return true;
}
}
return false;
}
/**
* Get the annotation of the given file
*
* @param file the file
* @return the annotation for this file
*/
public String getAnnotation(File file) {
return map.get(file);
}
/**
* The the annotation for a file
*
* @param file the file
* @param annotation the annotation for the file
*/
public void setAnnotation(File file, String annotation) {
map.put(file, annotation);
}
/*
* Get the index of a file in the TreeMap.
* Note: This method is expensive!
* Don't use inside a loop.
*
* @param file the file
*
* @return the index in the map
*/
/* public int getIndexInMap(File file) {
untested
if (! map.containsKey(file)) {
return -1;
} else {
return map.headMap(file).size();
}
} */
/**
* Removes all of the mappings from this map
*/
public void resetTreeMap(){
map.clear();
map = new TreeMap<File,String>();
}
public File[] getAllFiles(){
return map.keySet().toArray(new File[map.size()]);
}
/**
* Get the TreeMap containing the files
* and annotations
*
* @return the current TreeMap
*/
public TreeMap<File, String> getTreeMap() {
return map;
}
/**
* Set a new TreeMap for this model. Existing
* entries will be deleted.
*
* @param newMap the new map to set
*/
/* public void setTreeMap(TreeMap<File, String> newMap){
map.clear();
map = newMap;
calculateNumberAndSize();
} */
/**
* Get the number of all selected files
*
* @return the fileNumber
*/
public int getFileNumber() {
return fileNumber;
}
/**
* Set the number of all selected files
*
* @param fileNumber
*/
public void setFileNumber(int fileNumber) {
this.fileNumber = fileNumber;
}
/**
* Get the size of all selected files
*
* @return the allSize
*/
public long getAllSize() {
return allSize;
}
/**
* Set the size of all selected files
*
* @param allSize
*/
public void setAllSize(long allSize) {
this.allSize = allSize;
}
/**
* Get the String that indicates that a file is invalid.
* This String indicates also the end of the annotation and
* the beginning of the file name.
*
* @return the INVALID_MARKER
*/
public static String getInvalidMarker() {
return INVALID_MARKER;
}
}