/** * */ package cz.cuni.mff.peckam.java.origamist.files; import java.util.Hashtable; import java.util.Iterator; import java.util.Locale; import java.util.NoSuchElementException; import java.util.ResourceBundle; import javax.xml.bind.annotation.XmlTransient; import org.apache.log4j.Logger; import cz.cuni.mff.peckam.java.origamist.common.LangString; import cz.cuni.mff.peckam.java.origamist.utils.EmptyIterator; import cz.cuni.mff.peckam.java.origamist.utils.LangStringHashtableObserver; import cz.cuni.mff.peckam.java.origamist.utils.ObservableList; import cz.cuni.mff.peckam.java.origamist.utils.ObservablePropertyEvent; import cz.cuni.mff.peckam.java.origamist.utils.ObservablePropertyListener; /** * A category containing some diagram metadata. * <p> * Provides property: parent * * @author Martin Pecka */ public class Category extends cz.cuni.mff.peckam.java.origamist.files.jaxb.Category implements FilesContainer, CategoriesContainer { /** Parent property. */ public static final String PARENT_PROPERTY = "parent"; /** The category or listing this category is a subcategory of. */ protected transient CategoriesContainer parent = null; /** The hastable for more comfortable search in localized names. */ @XmlTransient protected Hashtable<Locale, String> names = new Hashtable<Locale, String>(); /** * Create a new listing category. */ public Category() { ((ObservableList<LangString>) getName()).addObserver(new LangStringHashtableObserver(names)); addObservablePropertyListener(new ObservablePropertyListener<Category>() { @Override public void changePerformed(ObservablePropertyEvent<? extends Category> evt) { evt.getEvent().getItem().setParent(Category.this); } }, Category.CATEGORIES_PROPERTY, Categories.CATEGORY_PROPERTY); addObservablePropertyListener(new ObservablePropertyListener<File>() { @Override public void changePerformed(ObservablePropertyEvent<? extends File> evt) { evt.getEvent().getItem().setParent(Category.this); } }, Category.FILES_PROPERTY, Files.FILE_PROPERTY); } /** * Iterator that iterates over all files in this categorie's files and subcategories. * * @return Iterator that iterates over all files in this categorie's files 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) { Logger.getLogger(getClass()).warn( "Tried to remove a file from a categorie's recursive iterator twice."); } else if (fileIterator != null) { wasRemoved = true; fileIterator.remove(); } else if (categoriesFileIterator != null) { wasRemoved = true; categoriesFileIterator.remove(); } else { Logger.getLogger(getClass()).warn( "Tried to delete a file from a categorie'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 subcategories in this category. * * @return Iterator that iterates over all subcategories in this category. */ public Iterator<Category> recursiveCategoryIterator() { if (getCategories() == null) return new EmptyIterator<Category>(); return (getCategories()).recursiveCategoryIterator(); } /** * @return The category or listing this category is a subcategory of. */ @XmlTransient public CategoriesContainer getParent() { return parent; } /** * @param parent The category or listing this category is a subcategory of. */ public void setParent(CategoriesContainer parent) { CategoriesContainer oldParent = this.parent; this.parent = parent; if ((oldParent != parent && (oldParent != null || parent != null)) || (oldParent != null && !oldParent.equals(parent))) support.firePropertyChange(PARENT_PROPERTY, oldParent, parent); } /** * 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. * * Category "" is treated as <code>this</code>. Categories starting with a slash are not allowed here. * * @param categoryString The category to create. It is written in the form "cat1id/cat2id/cat3id". * @return The created category. * * @throws IllegalArgumentException If the categoryString is <code>null</code> or if it starts with a slash. */ public CategoriesContainer createSubCategories(String categoryString) { if (categoryString == null || categoryString.startsWith("/")) throw new IllegalArgumentException("Cannot create absolute-path-like subcategories in a raw category."); String[] cats = categoryString.split("/"); Category oldCat = this; for (String cat : cats) { Category newCat = (Category) new ObjectFactory().createCategory(); newCat.setId(cat); newCat.getName().add(new LangString(cat, Locale.getDefault())); newCat.setParent(oldCat); if (oldCat.getCategories() == null) oldCat.setCategories((Categories) new ObjectFactory().createCategories()); oldCat.getCategories().getCategory().add(newCat); oldCat = newCat; } return oldCat; } /** * Returns the id of this category composed of names of it and all of its parent categories connected with * <code>separator</code>, starting with the highest category. * * @param separator The string to connect the categories with. * * @return The hierarchical id of this category. */ public String getHierarchicalId(String separator) { return parent.getHierarchicalId(separator) + separator + this.id; } @Override public String toString() { return "Category [name=" + name + ", id=" + id + ", files=" + files + ", categories=" + categories + "]"; } /** * Return the localized name of the category. * * @param l The locale of the name. If null or not found, returns the content of the first <name> element * defined. * @return The localized name. */ public String getName(Locale l) { if (names.size() == 0) { ResourceBundle b = ResourceBundle.getBundle("cz.cuni.mff.peckam.java.origamist.model.Origami", l); return b.getString("nameNotFound"); } if (l == null || !names.containsKey(l)) return name.get(0).getValue(); return names.get(l); } /** * Add a name in the given locale. * * @param l The locale of the name * @param name The name to add */ public void addName(Locale l, String name) { LangString s = (LangString) new cz.cuni.mff.peckam.java.origamist.common.jaxb.ObjectFactory() .createLangString(); s.setLang(l); s.setValue(name); this.name.add(s); } @Override protected String[] getNonChildProperties() { return new String[] { PARENT_PROPERTY }; } }