/* TagDocument.java created 2007-09-12
*
*/
package org.signalml.app.document;
import static org.signalml.app.util.i18n.SvarogI18n._;
import java.beans.IntrospectionException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.log4j.Logger;
import org.signalml.app.document.signal.SignalDocument;
import org.signalml.app.model.components.LabelledPropertyDescriptor;
import org.signalml.app.util.XMLUtils;
import org.signalml.domain.signal.space.SignalSpaceConstraints;
import org.signalml.domain.tag.StyledTagSet;
import org.signalml.domain.tag.TagStyles;
import org.signalml.plugin.export.SignalMLException;
import org.signalml.plugin.export.signal.ExportedTag;
import org.signalml.plugin.export.signal.ExportedTagDocument;
import org.signalml.plugin.export.signal.ExportedTagStyle;
import org.signalml.plugin.export.signal.Tag;
import org.signalml.plugin.export.signal.TagStyle;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.Annotations;
import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
import com.thoughtworks.xstream.converters.reflection.NativeFieldKeySorter;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
/**
* The document with {@link Tag tags} and {@link TagStyle tag styles}.
* Contains two static methods, which create documents with:
* <ul>
* <li>the styles from the given file,</li>
* <li>the default styles of sleeping stages.</li>
* </ul>
* Tags and styles are stored in the {@link StyledTagSet set}, which can be
* obtained from this document.
* Moreover every instance has methods that allow to:
* <ul>
* <li>get the name,</li>
* <li>get the size (in seconds) of the block and the page,</li>
* <li>get the {@link SignalDocument document} with a signal with which the
* tags in this document are associated,</li>
* <li>get the number of tags and tag styles.</li>
* </ul>
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class TagDocument extends AbstractMutableFileDocument implements ExportedTagDocument {
/**
* Logger to save history of execution at
*/
protected static final Logger logger = Logger.getLogger(TagDocument.class);
/**
* Charset used to save this document. Probably shouldn't be changed.
*/
public static final String CHAR_SET = "UTF-8";
/**
* the {@link StyledTagSet set} in which the {@link Tag tags} and
* {@link TagStyle styles} are stored
*/
private StyledTagSet tagSet;
/**
* the {@link SignalDocument document} with a signal with which the tags
* in this document are associated
*/
private SignalDocument parent;
/**
* the fallback name
*/
private String fallbackName = "";
/**
* Empty constructor.
* @throws SignalMLException never thrown (???)
*/
public TagDocument() throws SignalMLException {
}
/**
* Constructor. Sets the size of the page and the block.
* Creates a new {@link StyledTagSet set} with these parameters.
* @param pageSize the size of the page
* @param blocksPerPage the number of blocks in the single page
* @throws SignalMLException never thrown
*/
public TagDocument(float pageSize, int blocksPerPage) throws SignalMLException {
tagSet = new StyledTagSet(pageSize, blocksPerPage);
saved = true;
}
/**
* Constructor. Reads the {@link Tag tags} and their {@link TagStyle
* styles} from file.
* @param file the file to be read
* @throws SignalMLException if there is no backing file or
* the document stored in the file has invalid format or other
* non I/O error occurs while reading a file
* @throws IOException if I/O error occurs while reading the file
*/
public TagDocument(File file) throws SignalMLException, IOException {
super(file);
}
/**
* Constructor. Sets the {@link StyledTagSet set}.
* @param tagSet the set in which the {@link Tag tags} and {@link TagStyle
* styles} are stored
* @throws SignalMLException never thrown (???)
*/
public TagDocument(StyledTagSet tagSet) throws SignalMLException {
this.tagSet = tagSet;
saved = true;
}
@Override
public void newDocument() throws SignalMLException {
if (tagSet == null) {
tagSet = new StyledTagSet();
} else {
tagSet = new StyledTagSet(tagSet.getPageSize(), tagSet.getBlocksPerPage());
}
saved = true;
}
/**
* Creates the new tag document with the {@link TagStyle styles} to mark
* sleep stages.
* @param pageSize the size of the page
* @param blocksPerPage the number of blocks in the page
* @return the created tag document
* @throws SignalMLException never thrown (??)
* @throws IOException if the stream to the file with styles could not
* be opened
*/
public static TagDocument getNewSleepDefaultDocument(float pageSize, int blocksPerPage) throws SignalMLException, IOException {
Resource r = new ClassPathResource("org/signalml/domain/tag/sample/default_sleep_styles.xml");
TagDocument templateDocument = new TagDocument();
templateDocument.readDocument(r.getInputStream());
TagStyles styles = templateDocument.getTagSet().getTagStyles().clone();
StyledTagSet tagSet = new StyledTagSet(styles, pageSize, blocksPerPage);
TagDocument tagDocument = new TagDocument(tagSet);
return tagDocument;
}
/**
* Creates the new {@link TagDocument tag document} with the
* {@link TagStyle styles} taken from another tag document stored
* in the given file.
* @param file the file with the tag document from which the styles are
* to be taken
* @param pageSize the size of the page
* @param blocksPerPage the number of blocks in the page
* @return the created tag document
* @throws SignalMLException if the document stored in the file has
* invalid format or other non I/O error occurs while reading a file
* @throws IOException if I/O error occurs while reading the file
*/
public static TagDocument getStylesFromFileDocument(File file, float pageSize, int blocksPerPage) throws SignalMLException, IOException {
TagDocument templateDocument = new TagDocument(file);
TagStyles styles = templateDocument.getTagSet().getTagStyles().clone();
StyledTagSet tagSet = new StyledTagSet(styles, pageSize, blocksPerPage);
TagDocument tagDocument = new TagDocument(tagSet);
return tagDocument;
}
@Override
public void closeDocument() throws SignalMLException {
tagSet = null;
super.closeDocument();
}
@Override
public void readDocument(InputStream is) {
XStream streamer = getTagStreamer();
try {
tagSet = (StyledTagSet) streamer.fromXML(is);
} catch (CannotResolveClassException e) {
throw new RuntimeException("failed to read XML, element "
+ e.getMessage());
}
}
@Override
protected void writeDocument(OutputStream os) {
XStream streamer = getTagStreamer();
try {
XMLUtils.writeXMLHeader(os);
} catch (IOException ex) {
logger.error("Failed to save tag - i/o exception", ex);
}
streamer.toXML(tagSet, os);
}
/**
* Returns the {@link StyledTagSet set} in which the {@link Tag tags} and
* {@link TagStyle styles} are stored.
* @return the set in which the tags and styles are stored
*/
public StyledTagSet getTagSet() {
return tagSet;
}
@Override
public float getBlockSize() {
return tagSet.getBlockSize();
}
@Override
public int getBlocksPerPage() {
return tagSet.getBlocksPerPage();
}
@Override
public float getPageSize() {
return tagSet.getPageSize();
}
@Override
public SignalDocument getParent() {
return parent;
}
/**
* Sets the {@link SignalDocument document} with a signal with which the
* {@link Tag tags} in this document are associated.
* @param parent the document with a signal with which the tags
* in this document are associated
*/
public void setParent(SignalDocument parent) {
if (this.parent != parent) {
if (this.parent != null) {
this.parent.removeTagDocument(this);
}
this.parent = parent;
if (parent != null) {
parent.addTagDocument(this);
}
}
}
/**
* Returns the streamer which reads {@link Tag tags} and {@link TagStyle
* tag styles} from file.
* @return the streamer which reads tags and styles from file
*/
private XStream getTagStreamer() {
XStream streamer = new XStream(
new PureJavaReflectionProvider(new FieldDictionary(new NativeFieldKeySorter())),
new DomDriver(CHAR_SET, new XmlFriendlyReplacer() {
// the classes in question don't have $'s in their names and the
// format specifies single underscores, so disregard replacing
@Override
public String escapeName(String name) {
return name;
}
@Override
public String unescapeName(String name) {
return name;
}
}
));
Annotations.configureAliases(streamer,
StyledTagSet.class
);
XMLUtils.configureStreamerForMontage(streamer);
return streamer;
}
@Override
public String getName() {
return (backingFile != null ? backingFile.getName() : fallbackName);
}
@Override
public String getFallbackName() {
return fallbackName;
}
/**
* Sets the fallback name.
* @param fallbackName the fallback name to set
*/
public void setFallbackName(String fallbackName) {
this.fallbackName = fallbackName;
}
@Override
public Object[] getArguments() {
SignalDocument document = getParent();
String parentName = document.getName();
return new Object[] {
getName(),
parentName
};
}
@Override
public String[] getCodes() {
if (getBackingFile() == null) {
return new String[] { "newTagDocument" };
} else {
return new String[] { "tagDocument" };
}
}
@Override
public String getDefaultMessage() {
return toString();
}
/**
* Returns the number of {@link TagStyle tag styles} in this document.
* @return the number of tag styles in this document
*/
public int getTagStyleCount() {
return tagSet.getTagStyleCount();
}
@Override
public int getTagCount() {
return tagSet.getTagCount();
}
/**
* Sets the {@link TagStyle#isMarker() marker} styles from this document
* in the {@link SignalSpaceConstraints signal space constraints}.
* @param constraints the constraints in which the marker styles are
* to be set
*/
public void updateSignalSpaceConstraints(SignalSpaceConstraints constraints) {
List<TagStyle> markerStyles = new ArrayList<TagStyle>();
for (TagStyle style: getTagSet().getChannelStyles()) {
markerStyles.add(style);
}
constraints.setMarkerStyles(markerStyles.toArray(new TagStyle[0]));
}
@Override
public List<LabelledPropertyDescriptor> getPropertyList() throws IntrospectionException {
List<LabelledPropertyDescriptor> list = super.getPropertyList();
list.add(new LabelledPropertyDescriptor(_("tag style count"), "tagStyleCount", TagDocument.class, "getTagStyleCount", null));
list.add(new LabelledPropertyDescriptor(_("tag count"), "tagCount", TagDocument.class, "getTagCount", null));
return list;
}
/* (non-Javadoc)
* @see org.signalml.plugin.export.signal.ExportedTagDocument#getSetOfTags()
*/
@Override
public SortedSet<ExportedTag> getSetOfTags() {
return new TreeSet<ExportedTag>(getTagSet().getTags());
}
/* (non-Javadoc)
* @see org.signalml.plugin.export.signal.ExportedTagDocument#getTagStyles()
*/
@Override
public Set<ExportedTagStyle> getTagStyles() {
StyledTagSet tagSet = getTagSet();
List<TagStyle> styles = tagSet.getListOfStyles();
Set<ExportedTagStyle> exportedStyles = new LinkedHashSet<ExportedTagStyle>();
for (TagStyle style : styles) {
exportedStyles.add(style);
}
return exportedStyles;
}
}