001/*
002 *   XGSSGeomReader -- A class that can read and write an XGSS formatted geometry file.
003 *
004 *   Copyright (C) 2013-2016, Joseph A. Huwaldt
005 *   All rights reserved.
006 *   
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *   
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom.reader;
023
024import geomss.geom.GeomElement;
025import geomss.geom.GeomList;
026import geomss.geom.GeomXMLBinding;
027import geomss.geom.GeometryList;
028import java.io.File;
029import java.io.FileInputStream;
030import java.io.FileOutputStream;
031import java.io.IOException;
032import java.text.MessageFormat;
033import java.util.Locale;
034import java.util.Map;
035import static java.util.Objects.nonNull;
036import static java.util.Objects.requireNonNull;
037import java.util.zip.GZIPInputStream;
038import java.util.zip.GZIPOutputStream;
039import java.util.zip.ZipException;
040import javolution.context.StackContext;
041import javolution.util.FastMap;
042import javolution.xml.XMLObjectReader;
043import javolution.xml.XMLObjectWriter;
044import javolution.xml.XMLReferenceResolver;
045import javolution.xml.stream.XMLStreamException;
046
047/**
048 * A {@link GeomReader} for reading geometry from an XGSS formatted, GZIP compressed, XML
049 * geometry file. XGSS is the native file format for GeomSS.
050 *
051 * <p> Modified by: Joseph A. Huwaldt </p>
052 *
053 * @author Joseph A. Huwaldt, Date: June 20, 2013
054 * @version September 9, 2016
055 */
056public class XGSSGeomReader extends AbstractGeomReader {
057
058    //  A brief description of the data read by this reaader.
059    private static final String DESCRIPTION = RESOURCES.getString("xgssDescription");
060
061    /**
062     * The preferred file extension for files of this reader's type.
063     */
064    public static final String EXTENSION = "xgss";
065
066    /**
067     * The XML file tag used for all GeomElement objects.
068     */
069    private static final String GEOMETRYTAG = "Geometry";
070
071    /**
072     * The XML file tag used for all non-geometry related workspace items.
073     */
074    private static final String WORKSPACETAG = "Workspace";
075
076    /**
077     * The user data key used to store the variable name for geometry stored in the
078     * workspace.
079     */
080    private static final String VARNAMEKEY = "GeomSSVarName";
081
082    /**
083     * The XML binding to use in reading/writing an XGSS formatted file.
084     */
085    private static final GeomXMLBinding BINDING = new GeomXMLBinding();
086
087    /**
088     * Returns a string representation of the object. This will return a brief description
089     * of the format read by this reader.
090     *
091     * @return A brief description of the format read by this reader.
092     */
093    @Override
094    public String toString() {
095        return DESCRIPTION;
096    }
097
098    /**
099     * Returns the preferred file extension (not including the ".") for files of this
100     * GeomReader's type.
101     *
102     * @return The preferred file extension for files of this readers type.
103     */
104    @Override
105    public String getExtension() {
106        return EXTENSION;
107    }
108
109    /**
110     * This method always returns <code>true</code> as XGSS files do encode the units that
111     * are being used for each geometry element.
112     *
113     * @return This method always returns true.
114     */
115    @Override
116    public boolean isUnitAware() {
117        return true;
118    }
119
120    /**
121     * Method that determines if this reader can read geometry from the specified input
122     * file.
123     *
124     * @param inputFile The input file containing the geometry to be read in.
125     * @return GeomReader.NO if the file format is not recognized by this reader.
126     *         GeomReader.YES if the file format is definitely recognized by this reader.
127     *         GeomReader.MAYBE if the file format might be readable by this reader, but
128     *         that can't easily be determined without actually reading the file.
129     * @throws java.io.IOException If there is a problem reading from the specified
130     * file.
131     */
132    @Override
133    public int canReadData(File inputFile) throws IOException {
134
135        int response = NO;
136        if (inputFile.isFile() && inputFile.exists()) {
137            String name = inputFile.getName();
138            name = name.toLowerCase().trim();
139            if (name.endsWith(".xgss"))
140                response = YES;
141        }
142
143        return response;
144    }
145
146    /**
147     * Reads in an XGSS geometry file from the specified input file and returns a
148     * {@link GeometryList} object that contains the geometry from the file.
149     *
150     * @param inputFile The input file containing the geometry to be read in. May not be
151     *                  null.
152     * @return A {@link GeometryList} object containing the geometry read in from the
153     *         file. If the file has no readable geometry in it, then this list will have
154     *         no elements in it (will have a size() of 0).
155     * @throws IOException If there is a problem reading the specified file.
156     */
157    @Override
158    public GeometryList read(File inputFile) throws IOException {
159        requireNonNull(inputFile);
160
161        //  XGSS XML files are required to be in ASCII with U.S. style number formatting.
162        //  Get the default locale and save it.  Then change the default locale to US.
163        Locale defLocale = Locale.getDefault();
164        Locale.setDefault(Locale.US);
165
166        try (GZIPInputStream is = new GZIPInputStream(new FileInputStream(inputFile))) {
167            
168            //  Create an XML object reader.
169            XMLObjectReader reader = XMLObjectReader.newInstance(is);
170
171            //  Set the GeomSS XML binding to get consistant formatting.
172            reader.setBinding(BINDING);
173
174            //  Use a reference resolver.
175            reader.setReferenceResolver(new XMLReferenceResolver());
176
177            //  Read in the XML file and return a top level Geometry List.
178            GeometryList<?, GeomElement> geom = reader.read(GEOMETRYTAG);
179
180            //  For this version of "read" strip out any workspace variable name references
181            //  in the geometry.
182            for (GeomElement elem : geom) {
183                elem.removeUserData(VARNAMEKEY);
184            }
185
186            return geom;
187
188        } catch (ZipException e) {
189            throw new IOException(MessageFormat.format(
190                    RESOURCES.getString("xgssWrongFormat"), inputFile.getName()), e);
191
192        } catch (XMLStreamException e) {
193            throw new IOException(e);
194            
195        } finally {
196            //  Restore the default locale.
197            Locale.setDefault(defLocale);
198        }
199    }
200
201    /**
202     * Returns <code>true</code>. This reader can write all geometry element types to an
203     * XGSS file.
204     *
205     * @return This method always returns true.
206     */
207    @Override
208    public boolean canWriteData() {
209        return true;
210    }
211
212    /**
213     * Writes out a geometry file for the geometry contained in the supplied
214     * {@link GeometryList} object.
215     *
216     * @param outputFile The output File to which the geometry is to be written. May not
217     *                   be null.
218     * @param geometry   The {@link GeometryList} object containing the geometry to be
219     *                   written out. May not be null.
220     * @throws IOException If there is a problem writing to the specified file.
221     */
222    @Override
223    public void write(File outputFile, GeometryList geometry) throws IOException {
224        requireNonNull(outputFile);
225        _warnings.clear();
226        if (!geometry.containsGeometry()) {
227            _warnings.add(RESOURCES.getString("noGeometryWarning"));
228            return;
229        }
230
231        //  XGSS XML files are required to be in ASCII with U.S. style number formatting.
232        //  Get the default locale and save it.  Then change the default locale to US.
233        Locale defLocale = Locale.getDefault();
234        Locale.setDefault(Locale.US);
235
236        StackContext.enter();
237        try (GZIPOutputStream os = new GZIPOutputStream(new FileOutputStream(outputFile))) {
238            //  Create an XML object writer.
239            XMLObjectWriter writer = XMLObjectWriter.newInstance(os);
240
241            // Set the GeomSS XML binding to get consistant formatting.  Use a tabs for spacing.
242            writer.setIndentation("\t");
243            writer.setBinding(BINDING);
244
245            //  Use a reference resolver to prevent duplicate objects in the file.
246            writer.setReferenceResolver(new XMLReferenceResolver());
247
248            //  Write out the top level geometry list to the file.
249            writer.write(geometry, GEOMETRYTAG);
250
251            writer.close();
252
253        } catch (XMLStreamException e) {
254            throw new IOException(e);
255
256        } finally {
257            StackContext.exit();
258            
259            //  Restore the default locale.
260            Locale.setDefault(defLocale);
261        }
262    }
263
264    /**
265     * Writes out an XGSS geometry file for the and non-geometry workspace variables
266     * contained in the supplied maps.
267     *
268     * @param outputFile The output File to which the geometry is to be written. May not
269     *                   be null.
270     * @param geometry   A map identifying geometry elements by their associated variable
271     *                   names. May not be null.
272     * @param vars       A map identifying non-geometry related workspace variables by
273     *                   their variable names. May not be null.
274     * @throws IOException If there is a problem writing to the specified file.
275     */
276    public void write(File outputFile, Map<String, GeomElement> geometry, Map<String, Object> vars) throws IOException {
277        requireNonNull(outputFile);
278        requireNonNull(vars);
279        _warnings.clear();
280        if (geometry.isEmpty()) {
281            _warnings.add(RESOURCES.getString("noGeometryWarning"));
282        }
283
284        StackContext.enter();
285        try (GZIPOutputStream os = new GZIPOutputStream(new FileOutputStream(outputFile))) {
286            //  Convert the geometry map into a list of geometry with the variable names stored
287            //  in the user data.
288            GeomList geom = GeomList.newInstance();
289            for (String varName : geometry.keySet()) {
290                GeomElement elem = geometry.get(varName);
291                elem.putUserData(VARNAMEKEY, varName);
292                geom.add(elem);
293            }
294
295            //  Create an XML object writer.
296            XMLObjectWriter writer = XMLObjectWriter.newInstance(os);
297
298            // Set the GeomSS XML binding to get consistant formatting.  Use a tabs for spacing.
299            writer.setIndentation("\t");
300            writer.setBinding(BINDING);
301
302            //  Use a reference resolver to prevent duplicate objects in the file.
303            writer.setReferenceResolver(new XMLReferenceResolver());
304
305            //  Write out the top level geometry list to the file.
306            writer.write(geom, GEOMETRYTAG);
307
308            //  Write the Map of workspace variables to the file.
309            if (!vars.isEmpty())
310                writer.write(vars, WORKSPACETAG);
311
312            writer.close();
313
314        } catch (XMLStreamException e) {
315            throw new IOException(e);
316
317        } finally {
318            //  Remove the variable names added to the user data above.
319            for (String varName : geometry.keySet()) {
320                GeomElement elem = geometry.get(varName);
321                elem.removeUserData(VARNAMEKEY);
322            }
323            StackContext.exit();
324        }
325    }
326
327    /**
328     * Reads in an XGSS geometry + workspace file from the specified input file and
329     * returns a Map object that contains the geometry and workspace variables from the
330     * file with the variable names as the keys.
331     *
332     * @param inputFile The input file containing the geometry + workspace to be read in.
333     * @return A Map object containing the geometry and workspace variables read in from
334     *         the file with the variable names as the keys. If the file has no readable
335     *         geometry in it, then this map will have no contents.
336     * @throws IOException If there is a problem reading the specified file.
337     */
338    public Map<String, Object> readWorkspace(File inputFile) throws IOException {
339
340        try (GZIPInputStream is = new GZIPInputStream(new FileInputStream(inputFile))) {
341            //  Create an XML object reader.
342            XMLObjectReader reader = XMLObjectReader.newInstance(is);
343
344            //  Set the GeomSS XML binding to get consistant formatting.
345            reader.setBinding(BINDING);
346
347            //  Use a reference resolver.
348            reader.setReferenceResolver(new XMLReferenceResolver());
349
350            //  Read in the geometry and return a top level Geometry List.
351            GeometryList<?, GeomElement> geom = reader.read(GEOMETRYTAG);
352
353            //  Read in the optional non-geometry workspace variables.
354            Map<String, Object> vars = null;
355            try {
356                vars = reader.read(WORKSPACETAG);
357            } catch (XMLStreamException e) { /* ignore (means workspace is not present) */ }
358
359            //  Close the reader.
360            reader.close();
361
362            //  For this version of "read" add named elements to the workspace by their
363            //  variable name.
364            FastMap<String, Object> workspace = FastMap.newInstance();
365            for (GeomElement elem : geom) {
366                String varName = (String)elem.getUserData(VARNAMEKEY);
367                if (nonNull(varName))
368                    workspace.put(varName, elem);
369            }
370
371            //  Add any non-geometry variables to the output workspace.
372            if (nonNull(vars) && !vars.isEmpty())
373                workspace.putAll(vars);
374
375            //  If there were no named variable names defined, put in the entire geometry
376            //  list with the name "geom".
377            if (workspace.isEmpty())
378                workspace.put("geom", geom);
379
380            return (Map<String, Object>)workspace;
381
382        } catch (ZipException e) {
383            throw new IOException(MessageFormat.format(
384                    RESOURCES.getString("xgssWrongFormat"), inputFile.getName()), e);
385
386        } catch (XMLStreamException e) {
387            throw new IOException(e);
388        }
389    }
390
391}