package uc.files.filelist; import helpers.GH; import helpers.LevensteinDistance; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Pattern; import javax.xml.transform.sax.TransformerHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import uc.IUser; import uc.crypto.HashValue; import uc.files.AbstractDownloadable.AbstractDownloadableFolder; import uc.files.IDownloadable.IDownloadableFolder; import uc.files.downloadqueue.AbstractDownloadQueueEntry; /** * a folder in a FileList * containing other FilelistFiles/folders * * @author Quicksilver * */ public class FileListFolder extends AbstractDownloadableFolder implements Iterable<FileListFile> , IDownloadableFolder, IFileListItem { private final FileList fileList; private long containedSize = 0; private int containedFiles = 0; private final FileListFolder parent; private final String foldername; private final List<FileListFolder> subfolders = new CopyOnWriteArrayList<FileListFolder>(); private final List<FileListFile> files = new CopyOnWriteArrayList<FileListFile>(); /** * * constructor only used by root element */ FileListFolder(FileList rootElementConstructor) { this(rootElementConstructor,null,""); } /** * normal constructor * @param parent - parent folder * @param foldername - name of folder */ public FileListFolder(FileListFolder parent, String foldername ) { this(parent.getFilelist(),parent,foldername); } private FileListFolder(FileList f, FileListFolder parent, String foldername ) { this.fileList = f; this.parent = parent; this.foldername = foldername; if (parent != null) { parent.addChild(this); } } public FileListFolder getChildPerName(String foldernameOfTheChild) { for (FileListFolder f:subfolders) { if (f.foldername.equals(foldernameOfTheChild)) { return f; } } return null; } public FileListFolder getClosestChildPerName(String foldernameOfTheChild) { FileListFolder ff = getChildPerName(foldernameOfTheChild); if (ff == null) { ArrayList<String> foldernames = new ArrayList<String>(); for (FileListFolder f:subfolders) { foldernames.add(f.foldername); } String closestName = LevensteinDistance.getClosest(foldernames, foldernameOfTheChild); return getChildPerName(closestName); } return ff; } public FileListFile getFilePerName(String filenameOfTheChild){ for (FileListFile f:files) { if (f.getName().equals(filenameOfTheChild)) { return f; } } return null; } /** * * @param size * @param add false for removeal */ private void addSizeToFolderparent(long size,boolean add){ containedSize += add? size:-size; containedFiles+= add?1:-1; if (parent != null) { parent.addSizeToFolderparent(size,add); } } @Override public boolean isOriginal() { return true; } /** * adds a new filelistfile to this folder.. * only called by filelistfile * @param a - the file that should be added to this folder */ void addChild(FileListFile a) { int found = Collections.binarySearch(files, a); if (found < 0) { files.add(-(found+1) , a); addSizeToFolderparent(a.getSize(),true); fileList.addedOrRemoved(true, a); } } /** * * @param a - a Folder that should eb added to thisfolder */ void addChild(FileListFolder a) { int found = Collections.binarySearch(subfolders, a); if (found < 0) { subfolders.add(-(found+1) , a); fileList.addedOrRemoved(true, a); } } void removeChild(FileListFile a) { int found = Collections.binarySearch(files, a); if (found >= 0) { files.remove(found); addSizeToFolderparent(a.getSize(),false); fileList.addedOrRemoved(false, a); } } void removeChild(FileListFolder a) { int found = Collections.binarySearch(subfolders, a); if (found >= 0) { a.clear(); subfolders.remove(found); fileList.addedOrRemoved(false, a); } } /** * removes all children from the folder -> clears it */ private void clear() { for (FileListFile f:files) { removeChild(f); } for (FileListFolder ff:subfolders) { removeChild(ff); } } public void removeChild(String filename) { for (FileListFile f: files) { if (f.getName().equals(filename)) { removeChild(f); } } } public void removeFolder(String foldername) { for (FileListFolder f: subfolders) { if (f.getName().equals(foldername)) { removeChild(f); } } } public void remove(String fileOrFoldername){ removeChild(fileOrFoldername); removeFolder(fileOrFoldername); } public String getPath() { if (parent != null) { return parent.getPath() +foldername+File.separator; } else { return ""; } } /** * the user for IDownloadable .. * in this case the filelist owner */ public IUser getUser() { return fileList.getUsr(); } /** * uses the pattern to find results * @param onSearch * @param results */ public void search(Pattern onSearch, List<IFileListItem> results) { for (FileListFolder folder: subfolders) { if (onSearch.matcher(folder.getName()).find()) { results.add(folder); } folder.search(onSearch, results); } for (FileListFile file : files) { if (onSearch.matcher(file.getName()).find()) { results.add(file); } } } @Override public HashValue getTTHRoot() { throw new UnsupportedOperationException("Folders do not have hashValues"); } public List<IFileListItem> getChildren() { List<IFileListItem> children = new ArrayList<IFileListItem>(); children.addAll(subfolders); children.addAll(files); return children; } public boolean hasSubfolders() { return !subfolders.isEmpty() ; } /** * recursively writes this FilelistFolder and its children * to a writer... * used to build XML FileLists * * @param out - where to write to * @param recursive - if lower levels should be written out * @param writeout - if this is not written out only the name of the folder should be printed * but no contents * * @throws IOException - errors that occur on writing to out are thrown */ public void writeToXML(TransformerHandler hd,AttributesImpl atts,boolean recursive,boolean isBase,boolean writeout) throws SAXException { atts.clear(); //root doesn't write itself.. and any empty directory doesn't write itself boolean writeSelf = !isBase && !(subfolders.isEmpty() && files.isEmpty()); if (writeSelf) { atts.addAttribute("", "", "Name", "CDATA", foldername); if (!recursive && !writeout && !(subfolders.isEmpty() && files.isEmpty() ) ) { atts.addAttribute("", "", "Incomplete", "CDATA", "1"); } hd.startElement("", "", "Directory", atts); } if (writeout) { for (FileListFolder f: subfolders) { f.writeToXML(hd,atts,recursive,false,recursive?true:false); } for (FileListFile f: files) { f.writeToXML( hd,atts ); } } if (writeSelf) { hd.endElement("", "", "Directory"); } } /** * @return the foldername */ public String getName() { return foldername; } /** * @return the parent */ public FileListFolder getParent() { return parent; } /** * @return the filelist */ public FileList getFilelist() { return fileList; } /** * @return the files */ public List<FileListFile> getFiles() { return files; } /** * @return the subfolders */ public List<FileListFolder> getSubfolders() { return subfolders; } /** * @return the containedFiles */ public int getContainedFiles() { return containedFiles; } /** * @return the containedSize */ public long getSize() { return containedSize; } @Override public AbstractDownloadQueueEntry download(File target) { for (FileListFolder folder : subfolders) { folder.download(new File(target,folder.getName())); } for (FileListFile file:files) { file.download(new File(target,file.getName())); } return null; } public FileListFolder getByPath(String path) { return getByPath(path, false); } /** * FileList folder by path .. * File.separator char is used to separate paths.. * * @param path * @param allowApproximation / take smallest levensteinsdistance if not found * @return */ public FileListFolder getByPath(String path,boolean allowApproximation) { int i = path.indexOf(File.separatorChar); if (i == 0) { path= path.substring(1); i = path.indexOf(File.separatorChar); } if (GH.isEmpty(path)) { return this; } String name; if (i == -1) { name = path; } else { name = path.substring(0, i); } FileListFolder next = allowApproximation ? getClosestChildPerName(name) :getChildPerName(name); if (next != null) { return next.getByPath(path.substring(name.length()),allowApproximation); } else { return null; } } public boolean deepEquals(FileListFolder obj) { if (this == obj) return true; if (obj == null) return false; final FileListFolder other = obj; if (containedFiles != other.containedFiles) return false; if (containedSize != other.containedSize) return false; if (files == null) { if (other.files != null) return false; } else if (!files.equals(other.files)) return false; if (foldername == null) { if (other.foldername != null) return false; } else if (!foldername.equals(other.foldername)) return false; if (parent == null) { if (other.parent != null) return false; } else if (!parent.equals(other.parent)) return false; if (subfolders == null) { if (other.subfolders != null) return false; } else if (!subfolders.equals(other.subfolders)) return false; return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((foldername == null) ? 0 : foldername.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FileListFolder other = (FileListFolder) obj; if (foldername == null) { if (other.foldername != null) return false; } else if (!foldername.equals(other.foldername)) return false; if (parent == null) { if (other.parent != null) return false; } else if (!parent.equals(other.parent)) return false; return true; } // -----------------------Start Enumerator .. .. private class FLIterator implements Iterator<FileListFile> { private Iterator<FileListFolder> e1; private Iterator<FileListFile> e2; private Iterator<FileListFile> current; public FLIterator() { e1 = subfolders.iterator(); e2 = files.iterator(); if (e1.hasNext()) { current = e1.next().iterator(); } } public boolean hasNext() { if (e2.hasNext()) { return true; } else if (current == null) { return false; } else if (current.hasNext()) { return true; } else if (e1.hasNext()) { current = e1.next().iterator(); return hasNext(); } else { current = null; return false; } } public FileListFile next() { if (e2.hasNext()) { return e2.next(); } else if (current == null) { return null; } else if (current.hasNext()) { return current.next(); } else if(e1.hasNext()) { current= e1.next().iterator(); return next(); } else { current = null; return null; } } public void remove(){ throw new UnsupportedOperationException(); } } /** * iterates over all FileListFiles contained * in this Folder or its subfolders recursively.. */ public Iterator<FileListFile> iterator() { return new FLIterator(); } private class FolderIterator implements Iterator<FileListFolder> { private Iterator<FileListFolder> first = subfolders.iterator(); private Iterator<FileListFolder> second = subfolders.iterator(); private Iterator<FileListFolder> current; public FolderIterator() { if (second.hasNext()) { current = second.next().iterator2(); } } public boolean hasNext() { if (current == null) { return false; } else if (first.hasNext()) { return true; } else if (current.hasNext()) { return true; } else if (second.hasNext()) { current = second.next().iterator2(); return hasNext(); } else { current = null; return false; } } public FileListFolder next() { if (current == null) { return null; } else if (first.hasNext()) { return first.next(); } else if(current.hasNext()) { return current.next(); } else if (second.hasNext()) { current = second.next().iterator2(); return next(); } else { current = null; return null; } } public void remove() { throw new UnsupportedOperationException(); } } /** * gives an iterator that iterates over all contained * subfolders recursively * @return */ public Iterator<FileListFolder> iterator2() { return new FolderIterator(); } //-----------------------End Enumerator .. }