ZipJarCrawler.java

  1. /* Copyright 2002-2018 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.text.ParseException;
  26. import java.util.Iterator;
  27. import java.util.NoSuchElementException;
  28. import java.util.regex.Pattern;
  29. import java.util.zip.ZipEntry;
  30. import java.util.zip.ZipInputStream;

  31. import org.hipparchus.exception.DummyLocalizable;
  32. import org.hipparchus.exception.LocalizedCoreFormats;
  33. import org.orekit.errors.OrekitException;


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

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

  57.     /** Zip archive on the filesystem. */
  58.     private final File file;

  59.     /** Zip archive in the classpath. */
  60.     private final String resource;

  61.     /** Class loader to use. */
  62.     private final ClassLoader classLoader;

  63.     /** Zip archive on network. */
  64.     private final URL url;

  65.     /** Prefix name of the zip. */
  66.     private final String name;

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

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

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

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

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

  125.         try {

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

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

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

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

  147.     }

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

  162.         OrekitException delayedException = null;
  163.         boolean loaded = false;

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

  166.             try {

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

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

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

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

  172.                     } else {

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

  179.                         // apply all registered filters
  180.                         NamedData data = new NamedData(entryName, () -> entry);
  181.                         data = DataProvidersManager.getInstance().applyAllFilters(data);

  182.                         if (supported.matcher(data.getName()).matches()) {
  183.                             // visit the current file
  184.                             try (InputStream input = data.getStreamOpener().openStream()) {
  185.                                 visitor.loadData(input, fullName);
  186.                                 loaded = true;
  187.                             }
  188.                         }

  189.                     }

  190.                 }

  191.             } catch (OrekitException oe) {
  192.                 delayedException = oe;
  193.             }

  194.             entry.close();

  195.         }

  196.         if (!loaded && delayedException != null) {
  197.             throw delayedException;
  198.         }
  199.         return loaded;

  200.     }

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

  203.         /** Zip stream. */
  204.         private final ZipInputStream zip;

  205.         /** Next entry. */
  206.         private EntryStream next;

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

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

  226.         /** {@inheritDoc} */
  227.         @Override
  228.         public Iterator<Archive.EntryStream> iterator() {
  229.             return new Iterator<EntryStream> () {

  230.                 /** {@inheritDoc} */
  231.                 @Override
  232.                 public boolean hasNext() {
  233.                     return next != null;
  234.                 }

  235.                 /** {@inheritDoc} */
  236.                 @Override
  237.                 public EntryStream next() throws NoSuchElementException {
  238.                     if (next == null) {
  239.                         // this should never happen
  240.                         throw new NoSuchElementException();
  241.                     }
  242.                     return next;
  243.                 }

  244.             };
  245.         }

  246.         /** {@inheritDoc} */
  247.         @Override
  248.         public void close() throws IOException {
  249.             zip.close();
  250.         }

  251.         /** Archive entry. */
  252.         public class EntryStream extends InputStream {

  253.             /** Name of the entry. */
  254.             private final String name;

  255.             /** Directory indicator. */
  256.             private boolean isDirectory;

  257.             /** Indicator for already closed stream. */
  258.             private boolean closed;

  259.             /** Simple constructor.
  260.              * @param name name of the entry
  261.              * @param isDirectory if true, the entry is a directory
  262.              */
  263.             EntryStream(final String name, final boolean isDirectory) {
  264.                 this.name        = name;
  265.                 this.isDirectory = isDirectory;
  266.                 this.closed      = false;
  267.             }

  268.             /** Get the name of the entry.
  269.              * @return name of the entry
  270.              */
  271.             public String getName() {
  272.                 return name;
  273.             }

  274.             /** Check if the entry is a directory.
  275.              * @return true if the entry is a directory
  276.              */
  277.             public boolean isDirectory() {
  278.                 return isDirectory;
  279.             }

  280.             /** {@inheritDoc} */
  281.             @Override
  282.             public int read() throws IOException {
  283.                 // delegate read to global input stream
  284.                 return zip.read();
  285.             }

  286.             /** {@inheritDoc} */
  287.             @Override
  288.             public void close() throws IOException {
  289.                 if (!closed) {
  290.                     zip.closeEntry();
  291.                     goToNext();
  292.                     closed = true;
  293.                 }
  294.             }

  295.         }

  296.     }

  297. }