001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.io.session;
003    
004    import java.io.BufferedOutputStream;
005    import java.io.File;
006    import java.io.FileNotFoundException;
007    import java.io.FileOutputStream;
008    import java.io.IOException;
009    import java.io.OutputStream;
010    import java.io.OutputStreamWriter;
011    import java.lang.reflect.Constructor;
012    import java.util.ArrayList;
013    import java.util.HashMap;
014    import java.util.List;
015    import java.util.Map;
016    import java.util.Set;
017    import java.util.zip.ZipEntry;
018    import java.util.zip.ZipOutputStream;
019    
020    import javax.xml.parsers.DocumentBuilder;
021    import javax.xml.parsers.DocumentBuilderFactory;
022    import javax.xml.parsers.ParserConfigurationException;
023    import javax.xml.transform.OutputKeys;
024    import javax.xml.transform.Transformer;
025    import javax.xml.transform.TransformerException;
026    import javax.xml.transform.TransformerFactory;
027    import javax.xml.transform.dom.DOMSource;
028    import javax.xml.transform.stream.StreamResult;
029    
030    import org.w3c.dom.Document;
031    import org.w3c.dom.Element;
032    import org.w3c.dom.Text;
033    
034    import org.openstreetmap.josm.gui.layer.Layer;
035    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
036    import org.openstreetmap.josm.gui.layer.TMSLayer;
037    import org.openstreetmap.josm.gui.layer.WMSLayer;
038    import org.openstreetmap.josm.tools.MultiMap;
039    import org.openstreetmap.josm.tools.Utils;
040    
041    public class SessionWriter {
042    
043        private static Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters =
044                new HashMap<Class<? extends Layer>, Class<? extends SessionLayerExporter>>();
045        static {
046            registerSessionLayerExporter(OsmDataLayer.class , OsmDataSessionExporter.class);
047            registerSessionLayerExporter(TMSLayer.class , ImagerySessionExporter.class);
048            registerSessionLayerExporter(WMSLayer.class , ImagerySessionExporter.class);
049        }
050    
051        /**
052         * Register a session layer exporter.
053         *
054         * The exporter class must have an one-argument constructor with layerClass as formal parameter type.
055         */
056        public static void registerSessionLayerExporter(Class<? extends Layer> layerClass, Class<? extends SessionLayerExporter> exporter) {
057            sessionLayerExporters.put(layerClass, exporter);
058        }
059    
060        public static SessionLayerExporter getSessionLayerExporter(Layer layer) {
061            Class<? extends Layer> layerClass = layer.getClass();
062            Class<? extends SessionLayerExporter> exporterClass = sessionLayerExporters.get(layerClass);
063            if (exporterClass == null) return null;
064            try {
065                @SuppressWarnings("unchecked")
066                Constructor<? extends SessionLayerExporter> constructor = (Constructor) exporterClass.getConstructor(layerClass);
067                return constructor.newInstance(layer);
068            } catch (Exception e) {
069                throw new RuntimeException(e);
070            }
071        }
072    
073        private List<Layer> layers;
074        private Map<Layer, SessionLayerExporter> exporters;
075        private MultiMap<Layer, Layer> dependencies;
076        private boolean zip;
077    
078        private ZipOutputStream zipOut;
079    
080        public SessionWriter(List<Layer> layers, Map<Layer, SessionLayerExporter> exporters,
081                    MultiMap<Layer, Layer> dependencies, boolean zip) {
082            this.layers = layers;
083            this.exporters = exporters;
084            this.dependencies = dependencies;
085            this.zip = zip;
086        }
087    
088        public class ExportSupport {
089            private Document doc;
090            private int layerIndex;
091    
092            public ExportSupport(Document doc, int layerIndex) {
093                this.doc = doc;
094                this.layerIndex = layerIndex;
095            }
096    
097            public Element createElement(String name) {
098                return doc.createElement(name);
099            }
100    
101            public Text createTextNode(String text) {
102                return doc.createTextNode(text);
103            }
104    
105            public int getLayerIndex() {
106                return layerIndex;
107            }
108    
109            /**
110             * Create a file in the zip archive.
111             *
112             * @return never close the output stream, but make sure to flush buffers
113             */
114            public OutputStream getOutputStreamZip(String zipPath) throws IOException {
115                if (!isZip()) throw new RuntimeException();
116                ZipEntry entry = new ZipEntry(zipPath);
117                zipOut.putNextEntry(entry);
118                return zipOut;
119            }
120    
121            public boolean isZip() {
122                return zip;
123            }
124        }
125    
126        public Document createJosDocument() throws IOException {
127            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
128            builderFactory.setValidating(false);
129            builderFactory.setNamespaceAware(true);
130            DocumentBuilder builder = null;
131            try {
132                builder = builderFactory.newDocumentBuilder();
133            } catch (ParserConfigurationException e) {
134                throw new RuntimeException(e);
135            }
136            Document doc = builder.newDocument();
137    
138            Element root = doc.createElement("josm-session");
139            root.setAttribute("version", "0.1");
140            doc.appendChild(root);
141    
142            Element layersEl = doc.createElement("layers");
143            root.appendChild(layersEl);
144    
145            for (int index=0; index<layers.size(); ++index) {
146                Layer layer = layers.get(index);
147                SessionLayerExporter exporter = exporters.get(layer);
148                ExportSupport support = new ExportSupport(doc, index+1);
149                Element el = exporter.export(support);
150                el.setAttribute("index", Integer.toString(index+1));
151                el.setAttribute("name", layer.getName());
152                Set<Layer> deps = dependencies.get(layer);
153                if (deps.size() > 0) {
154                    List<Integer> depsInt = new ArrayList<Integer>();
155                    for (Layer depLayer : deps) {
156                        int depIndex = layers.indexOf(depLayer);
157                        if (depIndex == -1) throw new AssertionError();
158                        depsInt.add(depIndex+1);
159                    }
160                    el.setAttribute("depends", Utils.join(",", depsInt));
161                }
162                layersEl.appendChild(el);
163            }
164            return doc;
165        }
166    
167        public void writeJos(Document doc, OutputStream out) throws IOException {
168            try {
169                OutputStreamWriter writer = new OutputStreamWriter(out, "utf-8");
170                writer.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
171                TransformerFactory transfac = TransformerFactory.newInstance();
172                Transformer trans = transfac.newTransformer();
173                trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
174                trans.setOutputProperty(OutputKeys.INDENT, "yes");
175                trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
176                StreamResult result = new StreamResult(writer);
177                DOMSource source = new DOMSource(doc);
178                trans.transform(source, result);
179            } catch (TransformerException e) {
180                throw new RuntimeException(e);
181            }
182        }
183    
184        public void write(File f) throws IOException {
185            OutputStream out = null;
186            try {
187                out = new FileOutputStream(f);
188            } catch (FileNotFoundException e) {
189                throw new IOException(e);
190            }
191            write(out);
192        }
193    
194        public void write (OutputStream out) throws IOException {
195            if (zip) {
196                zipOut = new ZipOutputStream(new BufferedOutputStream(out));
197            }
198            Document doc = createJosDocument(); // as side effect, files may be added to zipOut
199            if (zip) {
200                ZipEntry entry = new ZipEntry("session.jos");
201                zipOut.putNextEntry(entry);
202                writeJos(doc, zipOut);
203                zipOut.close();
204            } else {
205                writeJos(doc, new BufferedOutputStream(out));
206            }
207            Utils.close(out);
208        }
209    }