/** * */ package cz.cuni.mff.peckam.java.origamist.files; import java.io.IOException; import java.util.Hashtable; import java.util.Locale; import java.util.ResourceBundle; import javax.xml.bind.annotation.XmlTransient; 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.LangStringHashtableObserver; import cz.cuni.mff.peckam.java.origamist.utils.ObservableList; /** * Metadata of a model. * * Provides the following bound properties: * <ul> * <li>isOrigamiLoaded - fired when the origami this file references to gets loaded</li> * <li>origami</li> * <li>parent</li> * <li>loading</li> * <li>invalid</li> * <li>author</li> * <li>license</li> * <li>year</li> * <li>thumbnail</li> * <li>src</li> * <li>original</li> * </ul> * * @author Martin Pecka */ public class File extends cz.cuni.mff.peckam.java.origamist.files.jaxb.File implements HierarchicalComponent { /** The origami property. */ public static final String ORIGAMI_PROPERTY = "origami"; /** The parent property. */ public static final String PARENT_PROPERTY = "parent"; /** The loading property. */ public static final String LOADING_PROPERTY = "loading"; /** The invalid property. */ public static final String INVALID_PROPERTY = "invalid"; /** The loaded property. */ public static final String LOADED_PROPERTY = "loaded"; /** * The hastable for more comfortable search in localized names. */ @XmlTransient protected Hashtable<Locale, String> names = new Hashtable<Locale, String>(); /** * The hastable for more comfortable search in localized short descriptions. */ @XmlTransient protected Hashtable<Locale, String> shortDescs = new Hashtable<Locale, String>(); /** The origami model corresponding to this file. */ @XmlTransient protected Origami origami = null; /** The category or listing this file is contained in. */ protected transient FilesContainer parent = null; /** True if the origami is being loaded right now. */ @XmlTransient protected volatile boolean origamiLoading = false; /** True if this file doesn't point to a valid origami. */ @XmlTransient protected volatile boolean invalid = false; /** * Create a new origami metadata. */ public File() { ((ObservableList<LangString>) getName()).addObserver(new LangStringHashtableObserver(names)); ((ObservableList<LangString>) getShortdesc()).addObserver(new LangStringHashtableObserver(shortDescs)); } /** * Return the localized name of the model. * * @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) { String[] parts = getSrc().toString().split("/"); return parts[parts.length - 1]; } if (l == null || !names.containsKey(l)) return names.elements().nextElement(); 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); } /** * Return the localized short description of the model. * * @param l The locale of the short descripton. If null or not found, * returns the content of the first <shortdesc> element * defined * @return The localized note */ public String getShortDesc(Locale l) { if (shortDescs.size() == 0) { ResourceBundle b = ResourceBundle.getBundle("cz.cuni.mff.peckam.java.origamist.model.Origami", l); return b.getString("shortDescNotFound"); } if (l == null || !shortDescs.containsKey(l)) return shortDescs.elements().nextElement(); return shortDescs.get(l); } /** * Add a short description in the given locale. * * @param l The locale of the short description * @param desc The short description to add to add */ public void addShortDesc(Locale l, String desc) { LangString s = (LangString) new cz.cuni.mff.peckam.java.origamist.common.jaxb.ObjectFactory() .createLangString(); s.setLang(l); s.setValue(desc); this.shortdesc.add(s); } /** * (Potentionally load) and return the model corresponding to this listing entry. * * @return The model corresponding to this listing entry. * * @throws IOException If the source could not be read. * @throws UnsupportedDataFormatException If the given source does not contain a valid model. */ @XmlTransient public Origami getOrigami() throws UnsupportedDataFormatException, IOException { return getOrigami(false); } /** * (Potentionally load) and return the model corresponding to this listing entry. * * If the referenced file does not contain a valid model, remove this file from its parent. * * @param onlyMetadata If true, load only metadata of the model if it has not yet been loaded. * * @return The model corresponding to this listing entry. * * @throws IOException If the source could not be read. * @throws UnsupportedDataFormatException If the given source does not contain a valid model. */ public Origami getOrigami(boolean onlyMetadata) throws UnsupportedDataFormatException, IOException { return getOrigami(onlyMetadata, true); } /** * (Potentionally load) and return the model corresponding to this listing entry. * * @param onlyMetadata If true, load only metadata of the model if it has not yet been loaded. * @param autoRemoveBad If the referenced file does not contain a valid model and <code>autoRemoveBad</code> is * <code>true</code>, remove this file from its parent. * * @return The model corresponding to this listing entry. * * @throws IOException If the source could not be read. * @throws UnsupportedDataFormatException If the given source does not contain a valid model. */ public Origami getOrigami(boolean onlyMetadata, boolean autoRemoveBad) throws UnsupportedDataFormatException, IOException { if (origami == null) { setOrigamiLoading(true); try { Origami origami = ServiceLocator.get(OrigamiHandler.class).loadModel(getSrc(), onlyMetadata); setOrigami(origami); setInvalid(false); } catch (UnsupportedDataFormatException e) { if (autoRemoveBad && this.parent != null) { this.parent.getFiles().getFile().remove(this); } setInvalid(true); setOrigamiLoading(false); throw e; } catch (IOException e) { if (autoRemoveBad && this.parent != null) { this.parent.getFiles().getFile().remove(this); } setInvalid(true); setOrigamiLoading(false); throw e; } } setOrigamiLoading(false); return origami; } /** * Sets the model this file represents (particulary this doesn't update the file's metadata from the origami's * metadata). * * @param origami The origami to set. */ public void setOrigami(Origami origami) { Origami oldOrigami = this.origami; this.origami = origami; if (origami != null) { origami.setFile(this); fillFromOrigami(); } if (oldOrigami == null && origami != null) support.firePropertyChange(LOADED_PROPERTY, null, origami); if ((oldOrigami != origami && (oldOrigami == null || origami == null)) || (oldOrigami != null && !oldOrigami.equals(origami))) { support.firePropertyChange(ORIGAMI_PROPERTY, oldOrigami, origami); } if (origami != null) setInvalid(false); } /** * Fills this file's metadata from the metadata of this file's origami. * * @throws IllegalStateException If the origami has not yet been loaded. */ public void fillFromOrigami() throws IllegalStateException { if (origami == null) throw new IllegalStateException( "Tried to fill a File's metadata from its origami, but the origami has not been loaded yet."); setAuthor(origami.getAuthor()); setLicense(origami.getLicense()); this.name.clear(); this.name.addAll(origami.getName()); setOriginal(origami.getOriginal()); this.shortdesc.clear(); this.shortdesc.addAll(origami.getShortdesc()); setThumbnail(origami.getThumbnail()); setCreationDate(origami.getCreationDate()); } /** * @return The category or listing this file is contained in. */ @XmlTransient public FilesContainer getParent() { return parent; } /** * @param parent The category or listing this file is contained in. */ public void setParent(FilesContainer parent) { FilesContainer oldParent = this.parent; this.parent = parent; if ((oldParent != parent && (oldParent == null || parent == null)) || (oldParent != null && !oldParent.equals(parent))) support.firePropertyChange(PARENT_PROPERTY, oldParent, parent); } @Override public String toString() { return "File [names=" + names + ", shortDescs=" + shortDescs + ", author=" + author + ", name=" + name + ", creationDate=" + creationDate + ", shortdesc=" + shortdesc + ", license=" + license + ", original=" + original + ", thumbnail=" + thumbnail + ", src=" + src + "]"; } @Override public String getHierarchicalId(String separator) { return parent.getHierarchicalId(separator) + separator + this.getName(Locale.getDefault()); } /** * @return True if the origami belonging to this file has been loaded. */ public boolean isOrigamiLoaded() { return origami != null; } /** * @return True if the origami is being loaded right now. */ @XmlTransient public boolean isOrigamiLoading() { return origamiLoading; } /** * @param origamiLoading True if the origami is being loaded right now. */ protected void setOrigamiLoading(boolean origamiLoading) { boolean oldLoading = this.origamiLoading; this.origamiLoading = origamiLoading; if (oldLoading != origamiLoading) support.firePropertyChange(LOADING_PROPERTY, oldLoading, origamiLoading); } /** * @return False if this file points to a valid origami or the origami isn't loaded. */ @XmlTransient public boolean isInvalid() { return invalid; } /** * @param invalid Set to true to indicate this file doesn't point to a valid origami. */ public void setInvalid(boolean invalid) { boolean oldInvalid = this.invalid; this.invalid = invalid; if (oldInvalid != invalid) support.firePropertyChange(INVALID_PROPERTY, oldInvalid, invalid); } @Override protected String[] getNonChildProperties() { return new String[] { File.PARENT_PROPERTY, File.INVALID_PROPERTY, File.LOADED_PROPERTY, File.LOADING_PROPERTY, File.ORIGAMI_PROPERTY }; } }