ZipJarCrawler.java

  1. /* Copyright 2002-2013 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.data;

  18. import java.io.Closeable;
  19. import java.io.File;
  20. import java.io.FileInputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.net.URISyntaxException;
  24. import java.net.URL;
  25. import java.nio.channels.UnsupportedAddressTypeException;
  26. import java.text.ParseException;
  27. import java.util.Iterator;
  28. import java.util.regex.Matcher;
  29. import java.util.regex.Pattern;
  30. import java.util.zip.GZIPInputStream;
  31. import java.util.zip.ZipEntry;
  32. import java.util.zip.ZipInputStream;

  33. import org.apache.commons.math3.exception.util.DummyLocalizable;
  34. import org.apache.commons.math3.exception.util.LocalizedFormats;
  35. import org.orekit.errors.OrekitException;


  36. /** Helper class for loading data files from a zip/jar archive.

  37.  * <p>
  38.  * This class browses all entries in a zip/jar archive in filesystem or in classpath.
  39.  * </p>
  40.  * <p>
  41.  * The organization of entries within the archive is unspecified. All entries are
  42.  * checked in turn. If several entries of the archive are supported by the data
  43.  * loader, all of them will be loaded.
  44.  * </p>
  45.  * <p>
  46.  * Gzip-compressed files are supported.
  47.  * </p>
  48.  * <p>
  49.  * Zip archives entries are supported recursively.
  50.  * </p>
  51.  * <p>
  52.  * This is a simple application of the <code>visitor</code> design pattern for
  53.  * zip entries browsing.
  54.  * </p>
  55.  * @see DataProvidersManager
  56.  * @author Luc Maisonobe
  57.  */
  58. public class ZipJarCrawler implements DataProvider {

  59.     /** Zip archive on the filesystem. */
  60.     private final File file;

  61.     /** Zip archive in the classpath. */
  62.     private final String resource;

  63.     /** Class loader to use. */
  64.     private final ClassLoader classLoader;

  65.     /** Zip archive on network. */
  66.     private final URL url;

  67.     /** Prefix name of the zip. */
  68.     private final String name;

  69.     /** Build a zip crawler for an archive file on filesystem.
  70.      * @param file zip file to browse
  71.      */
  72.     public ZipJarCrawler(final File file) {
  73.         this.file        = file;
  74.         this.resource    = null;
  75.         this.classLoader = null;
  76.         this.url         = null;
  77.         this.name        = file.getAbsolutePath();
  78.     }

  79.     /** Build a zip crawler for an archive file in classpath.
  80.      * <p>
  81.      * Calling this constructor has the same effect as calling
  82.      * {@link #ZipJarCrawler(ClassLoader, String)} with
  83.      * {@code ZipJarCrawler.class.getClassLoader()} as first
  84.      * argument.
  85.      * </p>
  86.      * @param resource name of the zip file to browse
  87.      * @exception OrekitException if resource name is malformed
  88.      */
  89.     public ZipJarCrawler(final String resource) throws OrekitException {
  90.         this(ZipJarCrawler.class.getClassLoader(), resource);
  91.     }

  92.     /** Build a zip crawler for an archive file in classpath.
  93.      * @param classLoader class loader to use to retrieve the resources
  94.      * @param resource name of the zip file to browse
  95.      * @exception OrekitException if resource name is malformed
  96.      */
  97.     public ZipJarCrawler(final ClassLoader classLoader, final String resource)
  98.         throws OrekitException {
  99.         try {
  100.             this.file        = null;
  101.             this.resource    = resource;
  102.             this.classLoader = classLoader;
  103.             this.url         = null;
  104.             this.name        = classLoader.getResource(resource).toURI().toString();
  105.         } catch (URISyntaxException use) {
  106.             throw new OrekitException(use, LocalizedFormats.SIMPLE_MESSAGE, use.getMessage());
  107.         }
  108.     }

  109.     /** Build a zip crawler for an archive file on network.
  110.      * @param url URL of the zip file on network
  111.      * @exception OrekitException if url syntax is malformed
  112.      */
  113.     public ZipJarCrawler(final URL url) throws OrekitException {
  114.         try {
  115.             this.file        = null;
  116.             this.resource    = null;
  117.             this.classLoader = null;
  118.             this.url         = url;
  119.             this.name        = url.toURI().toString();
  120.         } catch (URISyntaxException use) {
  121.             throw new OrekitException(use, LocalizedFormats.SIMPLE_MESSAGE, use.getMessage());
  122.         }
  123.     }

  124.     /** {@inheritDoc} */
  125.     public boolean feed(final Pattern supported, final DataLoader visitor)
  126.         throws OrekitException {

  127.         try {

  128.             // open the raw data stream
  129.             Archive archive = null;;
  130.             try {
  131.                 if (file != null) {
  132.                     archive = new Archive(new FileInputStream(file));
  133.                 } else if (resource != null) {
  134.                     archive = new Archive(classLoader.getResourceAsStream(resource));
  135.                 } else {
  136.                     archive = new Archive(url.openConnection().getInputStream());
  137.                 }

  138.                 return feed(name, supported, visitor, archive);

  139.             } finally {
  140.                 if (archive != null) {
  141.                     archive.close();
  142.                 }
  143.             }

  144.         } catch (IOException ioe) {
  145.             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
  146.         } catch (ParseException pe) {
  147.             throw new OrekitException(pe, new DummyLocalizable(pe.getMessage()));
  148.         }

  149.     }

  150.     /** Feed a data file loader by browsing the entries in a zip/jar.
  151.      * @param prefix prefix to use for name
  152.      * @param supported pattern for file names supported by the visitor
  153.      * @param visitor data file visitor to use
  154.      * @param archive archive to read
  155.      * @exception OrekitException if some data is missing, duplicated
  156.      * or can't be read
  157.      * @return true if something has been loaded
  158.      * @exception IOException if data cannot be read
  159.      * @exception ParseException if data cannot be read
  160.      */
  161.     private boolean feed(final String prefix, final Pattern supported,
  162.                          final DataLoader visitor, final Archive archive)
  163.         throws OrekitException, IOException, ParseException {

  164.         OrekitException delayedException = null;
  165.         boolean loaded = false;

  166.         // loop over all entries
  167.         for (final Archive.EntryStream entry : archive) {

  168.             try {

  169.                 if (visitor.stillAcceptsData() && !entry.isDirectory()) {

  170.                     final String fullName = prefix + "!" + entry.getName();

  171.                     if (ZIP_ARCHIVE_PATTERN.matcher(entry.getName()).matches()) {

  172.                         // recurse inside the archive entry
  173.                         loaded = feed(fullName, supported, visitor, new Archive(entry)) || loaded;

  174.                     } else {

  175.                         // remove leading directories
  176.                         String entryName = entry.getName();
  177.                         final int lastSlash = entryName.lastIndexOf('/');
  178.                         if (lastSlash >= 0) {
  179.                             entryName = entryName.substring(lastSlash + 1);
  180.                         }

  181.                         // remove suffix from gzip entries
  182.                         final Matcher gzipMatcher = GZIP_FILE_PATTERN.matcher(entryName);
  183.                         final String baseName = gzipMatcher.matches() ? gzipMatcher.group(1) : entryName;

  184.                         if (supported.matcher(baseName).matches()) {

  185.                             // visit the current entry
  186.                             final InputStream stream =
  187.                                 gzipMatcher.matches() ? new GZIPInputStream(entry) : entry;
  188.                             visitor.loadData(stream, fullName);
  189.                             stream.close();
  190.                             loaded = true;

  191.                         }

  192.                     }

  193.                 }

  194.             } catch (OrekitException oe) {
  195.                 delayedException = oe;
  196.             }

  197.             entry.close();

  198.         }

  199.         if (!loaded && delayedException != null) {
  200.             throw delayedException;
  201.         }
  202.         return loaded;

  203.     }

  204.     /** Local class wrapping a zip archive. */
  205.     private static final class Archive implements Closeable, Iterable<Archive.EntryStream> {

  206.         /** Zip stream. */
  207.         private final ZipInputStream zip;

  208.         /** Next entry. */
  209.         private EntryStream next;

  210.         /** Simple constructor.
  211.          * @param rawStream raw stream
  212.          * @exception IOException if first entry cannot be retrieved
  213.          */
  214.         public Archive(final InputStream rawStream) throws IOException {
  215.             zip = new ZipInputStream(rawStream);
  216.             goToNext();
  217.         }

  218.         /** Go to next entry.
  219.         * @exception IOException if next entry cannot be retrieved
  220.          */
  221.         private void goToNext() throws IOException {
  222.             final ZipEntry ze = zip.getNextEntry();
  223.             if (ze == null) {
  224.                 next = null;
  225.             } else {
  226.                 next = new EntryStream(ze.getName(), ze.isDirectory());
  227.             }
  228.         }

  229.         /** {@inheritDoc} */
  230.         @Override
  231.         public Iterator<Archive.EntryStream> iterator() {
  232.             return new Iterator<EntryStream> () {

  233.                 /** {@inheritDoc} */
  234.                 @Override
  235.                 public boolean hasNext() {
  236.                     return next != null;
  237.                 }

  238.                 /** {@inheritDoc} */
  239.                 @Override
  240.                 public EntryStream next() {
  241.                     return next;
  242.                 }

  243.                 /** {@inheritDoc} */
  244.                 @Override
  245.                 public void remove() {
  246.                     // this part is never called
  247.                     throw new UnsupportedAddressTypeException();
  248.                 }

  249.             };
  250.         }

  251.         /** {@inheritDoc} */
  252.         @Override
  253.         public void close() throws IOException {
  254.             zip.close();
  255.         }

  256.         /** Archive entry. */
  257.         public class EntryStream extends InputStream {

  258.             /** Name of the entry. */
  259.             private final String name;

  260.             /** Directory indicator. */
  261.             private boolean isDirectory;

  262.             /** Indicator for already closed stream. */
  263.             private boolean closed;

  264.             /** Simple constructor.
  265.              * @param name name of the entry
  266.              * @param isDirectory if true, the entry is a directory
  267.              */
  268.             public EntryStream(final String name, final boolean isDirectory) {
  269.                 this.name        = name;
  270.                 this.isDirectory = isDirectory;
  271.                 this.closed      = false;
  272.             }

  273.             /** Get the name of the entry.
  274.              * @return name of the entry
  275.              */
  276.             public String getName() {
  277.                 return name;
  278.             }

  279.             /** Check if the entry is a directory.
  280.              * @return true if the entry is a directory
  281.              */
  282.             public boolean isDirectory() {
  283.                 return isDirectory;
  284.             }

  285.             /** {@inheritDoc} */
  286.             @Override
  287.             public int read() throws IOException {
  288.                 // delegate read to global input stream
  289.                 return zip.read();
  290.             }

  291.             /** {@inheritDoc} */
  292.             @Override
  293.             public void close() throws IOException {
  294.                 if (!closed) {
  295.                     zip.closeEntry();
  296.                     goToNext();
  297.                     closed = true;
  298.                 }
  299.             }

  300.         }

  301.     }

  302. }