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 }