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.openstreetmap.josm.gui.layer.GpxLayer; 031 import org.openstreetmap.josm.gui.layer.Layer; 032 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 033 import org.openstreetmap.josm.gui.layer.TMSLayer; 034 import org.openstreetmap.josm.gui.layer.WMSLayer; 035 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer; 036 import org.openstreetmap.josm.tools.MultiMap; 037 import org.openstreetmap.josm.tools.Utils; 038 import org.w3c.dom.Document; 039 import org.w3c.dom.Element; 040 import org.w3c.dom.Text; 041 042 public class SessionWriter { 043 044 private static Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters = 045 new HashMap<Class<? extends Layer>, Class<? extends SessionLayerExporter>>(); 046 static { 047 registerSessionLayerExporter(OsmDataLayer.class , OsmDataSessionExporter.class); 048 registerSessionLayerExporter(TMSLayer.class , ImagerySessionExporter.class); 049 registerSessionLayerExporter(WMSLayer.class , ImagerySessionExporter.class); 050 registerSessionLayerExporter(GpxLayer.class , GpxTracksSessionExporter.class); 051 registerSessionLayerExporter(GeoImageLayer.class , GeoImageSessionExporter.class); 052 } 053 054 /** 055 * Register a session layer exporter. 056 * 057 * The exporter class must have an one-argument constructor with layerClass as formal parameter type. 058 */ 059 public static void registerSessionLayerExporter(Class<? extends Layer> layerClass, Class<? extends SessionLayerExporter> exporter) { 060 sessionLayerExporters.put(layerClass, exporter); 061 } 062 063 public static SessionLayerExporter getSessionLayerExporter(Layer layer) { 064 Class<? extends Layer> layerClass = layer.getClass(); 065 Class<? extends SessionLayerExporter> exporterClass = sessionLayerExporters.get(layerClass); 066 if (exporterClass == null) return null; 067 try { 068 @SuppressWarnings("unchecked") 069 Constructor<? extends SessionLayerExporter> constructor = (Constructor) exporterClass.getConstructor(layerClass); 070 return constructor.newInstance(layer); 071 } catch (Exception e) { 072 throw new RuntimeException(e); 073 } 074 } 075 076 private List<Layer> layers; 077 private Map<Layer, SessionLayerExporter> exporters; 078 private MultiMap<Layer, Layer> dependencies; 079 private boolean zip; 080 081 private ZipOutputStream zipOut; 082 083 public SessionWriter(List<Layer> layers, Map<Layer, SessionLayerExporter> exporters, 084 MultiMap<Layer, Layer> dependencies, boolean zip) { 085 this.layers = layers; 086 this.exporters = exporters; 087 this.dependencies = dependencies; 088 this.zip = zip; 089 } 090 091 /** 092 * A class that provides some context for the individual {@link SessionLayerExporter} 093 * when doing the export. 094 */ 095 public class ExportSupport { 096 private Document doc; 097 private int layerIndex; 098 099 public ExportSupport(Document doc, int layerIndex) { 100 this.doc = doc; 101 this.layerIndex = layerIndex; 102 } 103 104 public Element createElement(String name) { 105 return doc.createElement(name); 106 } 107 108 public Text createTextNode(String text) { 109 return doc.createTextNode(text); 110 } 111 112 /** 113 * Get the index of the layer that is currently exported. 114 * @return the index of the layer that is currently exported 115 */ 116 public int getLayerIndex() { 117 return layerIndex; 118 } 119 120 /** 121 * Create a file inside the zip archive. 122 * 123 * @param zipPath the path inside the zip archive, e.g. "layers/03/data.xml" 124 * @return the OutputStream you can write to. Never close the returned 125 * output stream, but make sure to flush buffers. 126 */ 127 public OutputStream getOutputStreamZip(String zipPath) throws IOException { 128 if (!isZip()) throw new RuntimeException(); 129 ZipEntry entry = new ZipEntry(zipPath); 130 zipOut.putNextEntry(entry); 131 return zipOut; 132 } 133 134 /** 135 * Check, if the session is exported as a zip archive. 136 * 137 * @return true, if the session is exported as a zip archive (.joz file 138 * extension). It will always return true, if one of the 139 * {@link SessionLayerExporter} returns true for the 140 * {@link SessionLayerExporter#requiresZip()} method. Otherwise, the 141 * user can decide in the file chooser dialog. 142 */ 143 public boolean isZip() { 144 return zip; 145 } 146 } 147 148 public Document createJosDocument() throws IOException { 149 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 150 builderFactory.setValidating(false); 151 builderFactory.setNamespaceAware(true); 152 DocumentBuilder builder = null; 153 try { 154 builder = builderFactory.newDocumentBuilder(); 155 } catch (ParserConfigurationException e) { 156 throw new RuntimeException(e); 157 } 158 Document doc = builder.newDocument(); 159 160 Element root = doc.createElement("josm-session"); 161 root.setAttribute("version", "0.1"); 162 doc.appendChild(root); 163 164 Element layersEl = doc.createElement("layers"); 165 root.appendChild(layersEl); 166 167 for (int index=0; index<layers.size(); ++index) { 168 Layer layer = layers.get(index); 169 SessionLayerExporter exporter = exporters.get(layer); 170 ExportSupport support = new ExportSupport(doc, index+1); 171 Element el = exporter.export(support); 172 el.setAttribute("index", Integer.toString(index+1)); 173 el.setAttribute("name", layer.getName()); 174 Set<Layer> deps = dependencies.get(layer); 175 if (deps.size() > 0) { 176 List<Integer> depsInt = new ArrayList<Integer>(); 177 for (Layer depLayer : deps) { 178 int depIndex = layers.indexOf(depLayer); 179 if (depIndex == -1) throw new AssertionError(); 180 depsInt.add(depIndex+1); 181 } 182 el.setAttribute("depends", Utils.join(",", depsInt)); 183 } 184 layersEl.appendChild(el); 185 } 186 return doc; 187 } 188 189 public void writeJos(Document doc, OutputStream out) throws IOException { 190 try { 191 OutputStreamWriter writer = new OutputStreamWriter(out, "utf-8"); 192 writer.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); 193 TransformerFactory transfac = TransformerFactory.newInstance(); 194 Transformer trans = transfac.newTransformer(); 195 trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 196 trans.setOutputProperty(OutputKeys.INDENT, "yes"); 197 trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); 198 StreamResult result = new StreamResult(writer); 199 DOMSource source = new DOMSource(doc); 200 trans.transform(source, result); 201 } catch (TransformerException e) { 202 throw new RuntimeException(e); 203 } 204 } 205 206 public void write(File f) throws IOException { 207 OutputStream out = null; 208 try { 209 out = new FileOutputStream(f); 210 } catch (FileNotFoundException e) { 211 throw new IOException(e); 212 } 213 write(out); 214 } 215 216 public void write (OutputStream out) throws IOException { 217 if (zip) { 218 zipOut = new ZipOutputStream(new BufferedOutputStream(out)); 219 } 220 Document doc = createJosDocument(); // as side effect, files may be added to zipOut 221 if (zip) { 222 ZipEntry entry = new ZipEntry("session.jos"); 223 zipOut.putNextEntry(entry); 224 writeJos(doc, zipOut); 225 zipOut.close(); 226 } else { 227 writeJos(doc, new BufferedOutputStream(out)); 228 } 229 Utils.close(out); 230 } 231 }