/** * */ package cz.cuni.mff.peckam.java.origamist.files; import java.io.FileFilter; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import org.apache.log4j.Level; import org.apache.log4j.Logger; import cz.cuni.mff.peckam.java.origamist.common.LangString; import cz.cuni.mff.peckam.java.origamist.exceptions.UnsupportedDataFormatException; import cz.cuni.mff.peckam.java.origamist.model.Origami; import cz.cuni.mff.peckam.java.origamist.services.ServiceLocator; import cz.cuni.mff.peckam.java.origamist.services.interfaces.OrigamiHandler; import cz.cuni.mff.peckam.java.origamist.utils.EmptyIterator; import cz.cuni.mff.peckam.java.origamist.utils.ObservablePropertyEvent; import cz.cuni.mff.peckam.java.origamist.utils.ObservablePropertyListener; /** * Additional functionality for the JAXB generated listing element. * * @author Martin Pecka */ public class Listing extends cz.cuni.mff.peckam.java.origamist.files.jaxb.Listing implements FilesContainer, CategoriesContainer { /** * */ public Listing() { addObservablePropertyListener(new ObservablePropertyListener<Category>() { @Override public void changePerformed(ObservablePropertyEvent<? extends Category> evt) { evt.getEvent().getItem().setParent(Listing.this); } }, Listing.CATEGORIES_PROPERTY, Categories.CATEGORY_PROPERTY); addObservablePropertyListener(new ObservablePropertyListener<File>() { @Override public void changePerformed(ObservablePropertyEvent<? extends File> evt) { evt.getEvent().getItem().setParent(Listing.this); } }, Listing.FILES_PROPERTY, Files.FILE_PROPERTY); } /** * Adds the <code>java.net.URI</code>s to this listing. If recursive is non-<code>null</code> and greater than 0, * add files from subdirectories and create a category for each subdirectory of depth <code>recurseDepth</code> and * less. If recursive is <code>null</code>, recurse all subdirectories. The recursion is done only for URIs with the * "file" scheme. * * @param uris The URIs to add. * @param recurseDepth If <code>null</code>, recurse infinitely, else recurse subdirectories of the depth * <code>recurseDepth</code> and less. * @param category The category or listing to add the files to. */ public void addFiles(List<URI> uris, final Integer recurseDepth, FilesContainer category) { if (category == null) throw new IllegalArgumentException("Cannot add files to null category."); if (recurseDepth != null && recurseDepth < 0) return; final FileFilter fileFilter = new FileFilter() { @Override public boolean accept(java.io.File pathname) { if (recurseDepth != null && recurseDepth == 0 && pathname.isDirectory()) return false; if (pathname.isDirectory()) return true; return pathname.getName().toLowerCase().endsWith("xml"); } }; final ObjectFactory of = new ObjectFactory(); for (URI uri : uris) { if ("file".equals(uri.getScheme())) { java.io.File ioFile = new java.io.File(uri); if (!ioFile.isDirectory()) { if (!fileFilter.accept(ioFile)) continue; File file = (File) of.createFile(); file.setSrc(uri); file.setParent(category); if (category.getFiles() == null) category.setFiles((Files) of.createFiles()); category.getFiles().getFile().add(file); } else { List<java.io.File> ioFilesToAdd = Arrays.asList(ioFile.listFiles(fileFilter)); Category newCategory = (Category) of.createCategory(); String name = ioFile.getName(); newCategory.setId(name); newCategory.getName().add(new LangString(name, Locale.getDefault())); CategoriesContainer cCategory = (CategoriesContainer) category; newCategory.setParent(cCategory); if (cCategory.getCategories() == null) cCategory.setCategories((Categories) of.createCategories()); cCategory.getCategories().getCategory().add(newCategory); this.addFiles(ioFilesToAdd, recurseDepth == null ? null : recurseDepth - 1, newCategory, false); } } else { if (!uri.toString().toLowerCase().endsWith(".xml")) continue; File file = (File) of.createFile(); file.setSrc(uri); file.setParent(category); if (category.getFiles() == null) category.setFiles((Files) of.createFiles()); category.getFiles().getFile().add(file); } } } /** * Adds the <code>java.io.File</code>s to this listing. If recursive non-null and greater than 0, add files from * subdirectories and create a category for each subdirectory of depth <code>recurseDepth</code> and less. If * recursive is <code>null</code>, recurse all subdirectories. * * @param ioFiles The files to add. * @param recurseDepth If <code>null</code>, recurse infinitely, else recurse subdirectories of the depth * <code>recurseDepth</code> and less. * @param category The category or listing to add the files to. */ public void addFiles(List<java.io.File> ioFiles, final Integer recurseDepth, FilesContainer category, boolean unused) { if (category == null) throw new IllegalArgumentException("Cannot add files to null category."); if (recurseDepth != null && recurseDepth < 0) return; final FileFilter fileFilter = new FileFilter() { @Override public boolean accept(java.io.File pathname) { if (recurseDepth != null && recurseDepth == 0 && pathname.isDirectory()) return false; if (pathname.isDirectory()) return true; return pathname.getName().toLowerCase().endsWith("xml"); } }; final ObjectFactory of = new ObjectFactory(); for (java.io.File ioFile : ioFiles) { if (!ioFile.isDirectory()) { if (!fileFilter.accept(ioFile)) continue; File file = (File) of.createFile(); file.setSrc(ioFile.toURI()); file.setParent(category); if (category.getFiles() == null) category.setFiles((Files) of.createFiles()); category.getFiles().getFile().add(file); } else { List<java.io.File> ioFilesToAdd = Arrays.asList(ioFile.listFiles(fileFilter)); Category newCategory = (Category) of.createCategory(); String name = ioFile.getName(); newCategory.setId(name); newCategory.getName().add(new LangString(name, Locale.getDefault())); CategoriesContainer cCategory = (CategoriesContainer) category; newCategory.setParent(cCategory); if (cCategory.getCategories() == null) cCategory.setCategories((Categories) of.createCategories()); cCategory.getCategories().getCategory().add(newCategory); this.addFiles(ioFilesToAdd, recurseDepth == null ? null : recurseDepth - 1, newCategory, false); } } } /** * Create a category with the given id. If the id contains slashes ("/"), it is concerned as a category id hierarchy * and all missing categories are created. * * Categories "" and "/" are concerned as the listing. * * @param categoryString The category to create. It is written in the form "cat1id/cat2id/cat3id". * @return The created category. */ public CategoriesContainer createSubCategories(String categoryString) { if (categoryString == null || categoryString.equals("") || categoryString.equals("/")) return this; String[] cats = categoryString.split("/"); Category firstCat = (this.getCategories()).getHashtable().get(cats[0]); if (firstCat == null) { firstCat = (Category) new ObjectFactory().createCategory(); firstCat.setId(cats[0]); firstCat.getName().add(new LangString(cats[0], Locale.getDefault())); firstCat.setParent(this); if (this.getCategories() == null) this.setCategories((Categories) new ObjectFactory().createCategories()); this.getCategories().getCategory().add(firstCat); } if (cats.length == 1) { return firstCat; } return firstCat.createSubCategories(categoryString.substring(categoryString.indexOf("/") + 1)); } /** * Adds the given origami to the file listing. * * @param origami The origami to add. It must have its src property non-<code>null</code>. * @param categoryString The category to add the origami to, or <code>""</code> if it has to be added directly under * the listing. The category is written in the form "cat1id/cat2id/cat3id". * * @return The File object created when adding the origami. */ public File addOrigami(Origami origami, String categoryString) { FilesContainer category = (FilesContainer) createSubCategories(categoryString); return addOrigami(origami, category); } /** * Loads origami from the given path and adds it to the file listing. * * @param path The path where the origami is to be loaded from. * @param category The category to add the origami to, or <code>null</code> if it has to be added directly under the * listing. * * @return The File object created when adding the origami. * * @throws UnsupportedDataFormatException If the origami could not be loaded. * @throws IOException If an IO error occured while loading the origami. */ public File addOrigami(URI path, FilesContainer category) throws UnsupportedDataFormatException, IOException { Origami o = ServiceLocator.get(OrigamiHandler.class).loadModel(path, true); return addOrigami(o, category); } /** * Loads origami from the given path and adds it to the file listing. * * @param path The path where the origami is to be loaded from. * @param category The category or listing to add the origami to. * * @return The File object created when adding the origami. * * @throws UnsupportedDataFormatException If the origami could not be loaded. * @throws IOException If an IO error occured while loading the origami. */ public File addOrigami(URL path, FilesContainer category) throws UnsupportedDataFormatException, IOException { Origami o = ServiceLocator.get(OrigamiHandler.class).loadModel(path, true); return addOrigami(o, category); } /** * Loads origami from the given path and adds it to the file listing. * * @param path The path where the origami is to be loaded from. * @param category The category to add the origami to, or <code>""</code> if it has to be added directly under * the listing. The category is written in the form "cat1id/cat2id/cat3id". * * @return The File object created when adding the origami. * * @throws UnsupportedDataFormatException If the origami could not be loaded. * @throws IOException If an IO error occured while loading the origami. */ public File addOrigami(URI path, String category) throws UnsupportedDataFormatException, IOException { Origami o = ServiceLocator.get(OrigamiHandler.class).loadModel(path, true); return addOrigami(o, category); } /** * Loads origami from the given path and adds it to the file listing. * * @param path The path where the origami is to be loaded from. * @param category The category to add the origami to, or <code>""</code> if it has to be added directly under * the listing. The category is written in the form "cat1id/cat2id/cat3id". * * @return The File object created when adding the origami. * * @throws UnsupportedDataFormatException If the origami could not be loaded. * @throws IOException If an IO error occured while loading the origami. */ public File addOrigami(URL path, String category) throws UnsupportedDataFormatException, IOException { Origami o = ServiceLocator.get(OrigamiHandler.class).loadModel(path, true); return addOrigami(o, category); } /** * Adds the given origami to the file listing. * * @param origami The origami to add. It must have its src property non-<code>null</code>. * @param category The category or listing to add the origami to. * * @return The File object created when adding the origami. */ public File addOrigami(Origami origami, FilesContainer category) { File file = (File) new ObjectFactory().createFile(); if (origami.getSrc() == null) { Logger.getLogger("application").l7dlog(Level.ERROR, "listingAddOrigamiInvalidOrigamiSource", new NullPointerException()); return null; } try { file.setSrc(origami.getSrc().toURI()); } catch (URISyntaxException e) { Logger.getLogger("application").l7dlog(Level.ERROR, "listingAddOrigamiInvalidOrigamiSource", e); return null; } file.setParent(category); file.setOrigami(origami); file.fillFromOrigami(); if (category.getFiles() == null) category.setFiles((Files) new ObjectFactory().createFiles()); category.getFiles().getFile().add(file); return file; } /** * Iterator that iterates over all files in this listing and the categories and subcategories. * * @return Iterator that iterates over all files in this listing and the categories and subcategories. */ public Iterator<File> recursiveFileIterator() { return new Iterator<File>() { Iterator<File> fileIterator = null; Iterator<File> categoriesFileIterator = null; boolean wasRemoved = false; { if (getFiles() != null) { fileIterator = getFiles().getFile().iterator(); } if (getCategories() != null) { categoriesFileIterator = (getCategories()).recursiveFileIterator(); } } @Override public void remove() { if (wasRemoved) { throw new IllegalStateException( "Tried to remove a file from a listings's recursive iterator twice."); } else if (fileIterator != null) { wasRemoved = true; fileIterator.remove(); } else if (categoriesFileIterator != null) { wasRemoved = true; categoriesFileIterator.remove(); } else { throw new IllegalStateException( "Tried to delete a file from a listing's recursive iterator before a call to next()."); } } @Override public File next() { wasRemoved = false; if (fileIterator != null && fileIterator.hasNext()) { return fileIterator.next(); } else if (categoriesFileIterator != null && categoriesFileIterator.hasNext()) { fileIterator = null; return categoriesFileIterator.next(); } throw new NoSuchElementException("No more elements in recursive file iterator."); } @Override public boolean hasNext() { if (fileIterator != null && fileIterator.hasNext()) { return true; } else if (categoriesFileIterator != null && categoriesFileIterator.hasNext()) { return true; } return false; } }; } /** * Iterator that iterates over all files in this listing and the categories and subcategories. * * @param beginWithThis If true, the first value returned by the iterator will be <code>this</code>. * @return Iterator that iterates over all files in this listing and the categories and subcategories. */ public Iterator<? extends CategoriesContainer> recursiveCategoryIterator(boolean beginWithThis) { if (!beginWithThis) { return recursiveCategoryIterator(); } else { return new Iterator<CategoriesContainer>() { boolean wasThis = false; Iterator<Category> categoryIterator = recursiveCategoryIterator(); @Override public boolean hasNext() { if (!wasThis) { return true; } else if (categoryIterator != null && categoryIterator.hasNext()) { return true; } else { return false; } } @Override public CategoriesContainer next() { if (!wasThis) { wasThis = true; return Listing.this; } else if (categoryIterator != null) { return categoryIterator.next(); } throw new NoSuchElementException("There are no more categories in the listing."); } @Override public void remove() { if (!wasThis) { throw new IllegalStateException( "Tried to remove a category from a listings's recursive iterator before a call to next()."); } else { if (categoryIterator != null && categoryIterator.hasNext()) { try { categoryIterator.remove(); } catch (IllegalStateException e) { // if wasThis == true and we get the exception here, it is the case when this was // returned last time and the categoryIterator.next() has not been called yet // we don't allow to remove the whole listing throw new IllegalStateException( "Tried to remove listing from a listing's category iterator. Not allowed."); } } else if (categoryIterator != null) { categoryIterator.remove(); } else { throw new IllegalStateException( "There are no categories to remove in the listing iterator."); } } } }; } } /** * Iterator that iterates over all subcategories in this listing. * * @return Iterator that iterates over all subcategories in this listing. */ public Iterator<Category> recursiveCategoryIterator() { if (getCategories() == null) { return new EmptyIterator<Category>(); } return (getCategories()).recursiveCategoryIterator(); } @Override public String toString() { return "Listing [files=" + files + ", categories=" + categories + "]"; } @Override public String getHierarchicalId(String separator) { return ""; } @Override public Category getParent() { return null; } }