001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.sequence;
020
021import icy.file.FileUtil;
022import icy.file.xml.XMLPersistent;
023import icy.image.lut.LUT;
024import icy.painter.Overlay;
025import icy.roi.ROI;
026import icy.system.IcyExceptionHandler;
027import icy.util.StringUtil;
028import icy.util.XMLUtil;
029
030import java.util.LinkedList;
031import java.util.List;
032
033import org.w3c.dom.Document;
034import org.w3c.dom.Element;
035import org.w3c.dom.Node;
036
037/**
038 * @author Stephane
039 */
040public class SequencePersistent implements XMLPersistent
041{
042    private final static String ID_META = "meta";
043    private final static String ID_ROIS = "rois";
044    private final static String ID_OVERLAYS = "overlays";
045    private final static String ID_LUT = "lut";
046
047    private final Sequence sequence;
048
049    private Document document;
050
051    /**
052     * 
053     */
054    public SequencePersistent(Sequence sequence)
055    {
056        super();
057
058        this.sequence = sequence;
059
060        document = XMLUtil.createDocument(true);
061    }
062
063    /**
064     * Should return <code>null</code> if Sequence is not identified (no file name)
065     */
066    private String getXMLFileName()
067    {
068        final String baseName = sequence.getOutputFilename(false);
069
070        if (StringUtil.isEmpty(baseName))
071            return null;
072
073        return baseName + XMLUtil.FILE_DOT_EXTENSION;
074    }
075
076    /**
077     * Load XML persistent data.<br>
078     * Return true if XML data has been correctly loaded.
079     */
080    public boolean loadXMLData()
081    {
082        final String xmlFilename = getXMLFileName();
083        boolean result;
084        Exception exc = null;
085
086        if ((xmlFilename != null) && FileUtil.exists(xmlFilename))
087        {
088            try
089            {
090                // load xml file into document
091                document = XMLUtil.loadDocument(xmlFilename, true);
092
093                // load data from XML document
094                if (document != null)
095                    result = loadFromXML(getRootNode());
096                else
097                {
098                    document = XMLUtil.createDocument(true);
099                    result = false;
100                }
101            }
102            catch (Exception e)
103            {
104                exc = e;
105                result = false;
106            }
107
108            // an error occurred
109            if (!result)
110            {
111                // backup the problematic file
112                String backupName = FileUtil.backup(xmlFilename);
113
114                System.err.println("Error while loading Sequence XML persistent data.");
115                System.err.println("The faulty file '" + xmlFilename + "' has been backuped as '" + backupName);
116
117                if (exc != null)
118                    IcyExceptionHandler.showErrorMessage(exc, true);
119
120                return false;
121            }
122        }
123
124        return true;
125    }
126
127    /**
128     * Save XML persistent data.<br>
129     * Return true if XML data has been correctly saved.
130     */
131    public boolean saveXMLData() throws Exception
132    {
133        final String xmlFilename = getXMLFileName();
134
135        if (xmlFilename == null)
136            return false;
137
138        // rebuild document
139        refreshXMLData();
140
141        // save xml file
142        return XMLUtil.saveDocument(document, xmlFilename);
143    }
144
145    public void refreshXMLData()
146    {
147        // force the new format when we save the XML
148        saveToXML(getRootNode());
149    }
150
151    @Override
152    public boolean loadFromXML(Node node)
153    {
154        boolean result = true;
155        final String name = XMLUtil.getElementValue(node, Sequence.ID_NAME, "");
156
157        // set name only if not empty
158        if (!StringUtil.isEmpty(name))
159            sequence.setName(name);
160
161        if (!loadMetaDataFromXML(node))
162            result = false;
163        if (!loadROIsFromXML(node))
164            result = false;
165        // some overlays does not support persistence so we can ignore errors...
166        loadOverlaysFromXML(node);
167        if (!loadLUTFromXML(node))
168            result = false;
169
170        return result;
171    }
172
173    private boolean loadMetaDataFromXML(Node node)
174    {
175        final Node nodeMeta = XMLUtil.getElement(node, ID_META);
176
177        // no node --> nothing to load...
178        if (nodeMeta == null)
179            return true;
180
181        double d;
182        long l;
183
184        d = XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_POSITION_X, Double.NaN);
185        if (!Double.isNaN(d))
186            sequence.setPositionX(d);
187        d = XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Y, Double.NaN);
188        if (!Double.isNaN(d))
189            sequence.setPositionY(d);
190        d = XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Z, Double.NaN);
191        if (!Double.isNaN(d))
192            sequence.setPositionZ(d);
193        l = XMLUtil.getElementLongValue(nodeMeta, Sequence.ID_POSITION_T, -1L);
194        if (l != -1L)
195            sequence.setPositionT(l);
196
197        d = XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_X, Double.NaN);
198        if (!Double.isNaN(d))
199            sequence.setPixelSizeX(d);
200        d = XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Y, Double.NaN);
201        if (!Double.isNaN(d))
202            sequence.setPixelSizeY(d);
203        d = XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Z, Double.NaN);
204        if (!Double.isNaN(d))
205            sequence.setPixelSizeZ(d);
206        d = XMLUtil.getElementDoubleValue(nodeMeta, Sequence.ID_TIME_INTERVAL, Double.NaN);
207        if (!Double.isNaN(d))
208            sequence.setTimeInterval(d);
209
210        for (int c = 0; c < sequence.getSizeC(); c++)
211        {
212            final String s = XMLUtil.getElementValue(nodeMeta, Sequence.ID_CHANNEL_NAME + c, "");
213
214            if (!StringUtil.isEmpty(s))
215                sequence.setChannelName(c, s);
216        }
217
218        return true;
219    }
220
221    private boolean loadROIsFromXML(Node node)
222    {
223        final Node roisNode = XMLUtil.getElement(node, ID_ROIS);
224
225        // no node --> nothing to load...
226        if (roisNode == null)
227            return true;
228
229        final int roiCount = ROI.getROICount(roisNode);
230        final List<ROI> rois = ROI.loadROIsFromXML(roisNode);
231
232        // add to sequence
233        for (ROI roi : rois)
234            sequence.addROI(roi);
235
236        // return true if we got the expected number of ROI
237        return (roiCount == rois.size());
238    }
239
240    private boolean loadOverlaysFromXML(Node node)
241    {
242        final Node overlaysNode = XMLUtil.getElement(node, ID_OVERLAYS);
243
244        // no node --> nothing to load...
245        if (overlaysNode == null)
246            return true;
247
248        final int overlayCount = Overlay.getOverlayCount(overlaysNode);
249        final List<Overlay> overlays = Overlay.loadOverlaysFromXML(overlaysNode);
250
251        // add to sequence
252        for (Overlay overlay : overlays)
253            sequence.addOverlay(overlay);
254
255        // return true if we got the expected number of ROI
256        return (overlayCount == overlays.size());
257    }
258
259    private boolean loadLUTFromXML(Node node)
260    {
261        final Node nodeLut = XMLUtil.getElement(node, ID_LUT);
262
263        // no node --> nothing to load...
264        if (nodeLut == null)
265            return true;
266
267        // use the default LUT by default
268        final LUT result = sequence.createCompatibleLUT();
269
270        if (result.loadFromXML(nodeLut))
271            sequence.setUserLUT(result);
272
273        return true;
274    }
275
276    @Override
277    public boolean saveToXML(Node node)
278    {
279        XMLUtil.setElementValue(node, Sequence.ID_NAME, sequence.getName());
280
281        saveMetaDataToXML(node);
282        saveROIsToXML(node);
283        saveOverlaysToXML(node);
284        saveLUTToXML(node);
285
286        return true;
287    }
288
289    private void saveMetaDataToXML(Node node)
290    {
291        final Node nodeMeta = XMLUtil.setElement(node, ID_META);
292
293        if (nodeMeta != null)
294        {
295            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_POSITION_X, sequence.getPositionX());
296            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Y, sequence.getPositionY());
297            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_POSITION_Z, sequence.getPositionZ());
298            XMLUtil.setElementLongValue(nodeMeta, Sequence.ID_POSITION_T, sequence.getPositionT());
299            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_X, sequence.getPixelSizeX());
300            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Y, sequence.getPixelSizeY());
301            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_PIXEL_SIZE_Z, sequence.getPixelSizeZ());
302            XMLUtil.setElementDoubleValue(nodeMeta, Sequence.ID_TIME_INTERVAL, sequence.getTimeInterval());
303
304            for (int c = 0; c < sequence.getSizeC(); c++)
305                XMLUtil.setElementValue(nodeMeta, Sequence.ID_CHANNEL_NAME + c, sequence.getChannelName(c));
306        }
307    }
308
309    private void saveROIsToXML(Node node)
310    {
311        final Node nodeROIs = XMLUtil.setElement(node, ID_ROIS);
312
313        if (nodeROIs != null)
314        {
315            XMLUtil.removeAllChildren(nodeROIs);
316
317            // get sorted ROIs
318            final List<ROI> rois = sequence.getROIs(true);
319
320            // set rois in the XML node
321            ROI.saveROIsToXML(nodeROIs, rois);
322        }
323    }
324
325    private void saveOverlaysToXML(Node node)
326    {
327        final Node nodeOverlays = XMLUtil.setElement(node, ID_OVERLAYS);
328
329        if (nodeOverlays != null)
330        {
331            XMLUtil.removeAllChildren(nodeOverlays);
332
333            // get overlays in linked list for faster remove operation
334            final List<Overlay> overlays = new LinkedList<Overlay>(sequence.getOverlays());
335            // remove overlays from ROI as they are be automatically created from ROI
336            for (ROI roi : sequence.getROIs(false))
337                overlays.remove(roi.getOverlay());
338
339            // set overlays in the XML node
340            Overlay.saveOverlaysToXML(nodeOverlays, overlays);
341        }
342    }
343
344    private void saveLUTToXML(Node node)
345    {
346        // save only if we have a custom LUT
347        if (sequence.hasUserLUT())
348        {
349            final LUT lut = sequence.getUserLUT();
350
351            // something to save ?
352            if (lut != null)
353            {
354                final Node nodeLut = XMLUtil.setElement(node, ID_LUT);
355
356                if (nodeLut != null)
357                {
358                    XMLUtil.removeAllChildren(nodeLut);
359                    lut.saveToXML(nodeLut);
360                }
361            }
362        }
363    }
364
365    /**
366     * Get Sequence XML root node
367     */
368    public Node getRootNode()
369    {
370        return XMLUtil.getRootElement(document);
371    }
372
373    /**
374     * Get XML data node identified by specified name
375     * 
376     * @param name
377     *        name of wanted node
378     */
379    public Node getNode(String name)
380    {
381        return XMLUtil.getChild(getRootNode(), name);
382    }
383
384    /**
385     * Create a new node with specified name and return it.<br>
386     * If the node already exists the existing node is returned.
387     * 
388     * @param name
389     *        name of node to set in attached XML data
390     */
391    public Node setNode(String name)
392    {
393        return XMLUtil.setElement(getRootNode(), name);
394    }
395
396    /**
397     * Returns <code>true</code> if the specified Document represents a valid XML persistence document.
398     */
399    public static boolean isValidXMLPersitence(Document doc)
400    {
401        if (doc == null)
402            return false;
403
404        final Element rootNode = XMLUtil.getRootElement(doc);
405
406        return (XMLUtil.getElement(rootNode, Sequence.ID_NAME) != null)
407                && (XMLUtil.getElement(rootNode, ID_META) != null) && (XMLUtil.getElement(rootNode, ID_ROIS) != null)
408                && (XMLUtil.getElement(rootNode, ID_OVERLAYS) != null);
409    }
410
411    /**
412     * Returns <code>true</code> if the specified path represents a valid XML persistence file.
413     */
414    public static boolean isValidXMLPersitence(String path)
415    {
416        if ((path != null) && FileUtil.exists(path))
417        {
418            try
419            {
420                return isValidXMLPersitence(XMLUtil.loadDocument(path, true));
421            }
422            catch (Exception e)
423            {
424                // ignore
425            }
426        }
427
428        return false;
429    }
430}