001/**
002 * GeomReaderFactory -- Factory that returns appropriate GeomReader objects.
003 *
004 * Copyright (C) 2000-2024, by Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package geomss.geom.reader;
019
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.net.URL;
024import java.text.MessageFormat;
025import java.util.Arrays;
026import java.util.Enumeration;
027import static java.util.Objects.isNull;
028import static java.util.Objects.nonNull;
029import static java.util.Objects.requireNonNull;
030import java.util.Properties;
031import java.util.ResourceBundle;
032import java.util.logging.Level;
033import java.util.logging.Logger;
034import javax.swing.JOptionPane;
035import javolution.util.FastComparator;
036import javolution.util.FastSet;
037import javolution.util.FastTable;
038
039/**
040 * This class returns a specific {@link GeomReader} object that can read in the specified
041 * file. This class implements a pluggable architecture. A new {@link GeomReader} class
042 * can be added by simply creating a subclass of <code>GeomReader</code>, creating a
043 * "GeomReader.properties" file that refers to it, and putting that properties file
044 * somewhere in the Java search path. All "GeomReader.properties" files that are found are
045 * merged together to create a global list of reader/handler mappings.
046 * 
047 * <p> Modified by: Joseph A. Huwaldt </p>
048 * 
049 * @author Joseph A. Huwaldt, Date: April 14, 2000
050 * @version January 1, 2024
051 */
052public final class GeomReaderFactory {
053
054    /**
055     * The resource bundle for this package.
056    *
057     */
058    private static final ResourceBundle RESOURCES
059            = ResourceBundle.getBundle("geomss.geom.reader.GeomReaderFactoryResources", java.util.Locale.getDefault());
060
061    /**
062     * All class loader resources with this name ("GeomReader.properties") are loaded as
063     * .properties definitions and merged together to create a global list of reader
064     * handler mappings.
065     */
066    private static final String MAPPING_RES_NAME = "GeomReader.properties";
067
068    //  An array containing a reference to all the readers that have been found.
069    private static final GeomReader[] _allReaders;
070
071    //  Locate and load all the readers we can find.
072    static {
073        GeomReader[] temp = null;
074        try {
075            temp = loadResourceList(MAPPING_RES_NAME, getClassLoader());
076
077        } catch (Exception e) {
078            Logger.getLogger(GeomReaderFactory.class.getName()).log(Level.SEVERE,
079                    MessageFormat.format(RESOURCES.getString("couldntLoadMappings"), MAPPING_RES_NAME), e);
080        }
081
082        _allReaders = temp;
083    }
084
085    // this class is not extendible
086    private GeomReaderFactory() { }
087
088    /**
089     * Method that attempts to find an {@link GeomReader} object that might be able to
090     * read the data in the specified file.
091     *
092     * @param theFile The file to find a reader for. May not be null.
093     * @return A reader that is appropriate for the specified file, or <code>null</code>
094     *         if the user cancels the multiple reader selection dialog.
095     * @throws IOException if an appropriate reader for the file could not be found.
096     */
097    public static GeomReader getReader(File theFile) throws IOException {
098
099        if (!theFile.exists())
100            throw new IOException(MessageFormat.format(
101                    RESOURCES.getString("missingFileErr"), theFile.getName()));
102
103        //  Get the list of data readers that are available.
104        GeomReader[] allReaders = getAllReaders();
105        if (isNull(allReaders) || allReaders.length < 1)
106            throw new IOException(RESOURCES.getString("noReadersErr"));
107
108        GeomReader selectedReader;
109        FastTable<GeomReader> list = FastTable.newInstance();
110
111        try {
112            try {
113
114                //  Loop through all the available readers and see if any of them will work.
115                for (GeomReader reader : allReaders) {
116
117                    //  Can this reader read the specified input stream?
118                    int canReadFile = reader.canReadData(theFile);
119
120                    //  If the reader is certain it can read the data, use it.
121                    if (canReadFile == GeomReader.YES) {
122                        return reader;
123                    }
124
125                    //  Otherwise, build a list of maybes.
126                    if (canReadFile == GeomReader.MAYBE)
127                        list.add(reader);
128
129                }
130
131            } catch (Exception e) {
132                e.printStackTrace();
133                throw new IOException(RESOURCES.getString("detFileTypeErr"));
134            }
135
136            if (list.size() < 1) {
137                throw new IOException(RESOURCES.getString("fileTypeErr"));
138            }
139
140            //  If there is only one reader in the list, try and use it.
141            if (list.size() == 1)
142                selectedReader = list.get(0);
143
144            else {
145                //  Ask the user to select which reader they want to try and use.
146                GeomReader[] possibleValues = list.toArray(new GeomReader[list.size()]);
147                selectedReader = (GeomReader)JOptionPane.showInputDialog(null,
148                        MessageFormat.format(RESOURCES.getString("chooseFormatMsg"), theFile.getName()),
149                        RESOURCES.getString("chooseFormatTitle"),
150                        JOptionPane.INFORMATION_MESSAGE, null, possibleValues, possibleValues[0]);
151            }
152
153        } finally {
154            //  Clean up before leaving.
155            FastTable.recycle(list);
156        }
157
158        return selectedReader;
159    }
160
161    /**
162     * Method that returns a list of all the {@link GeomReader} objects found by this
163     * factory.
164     *
165     * @return An array of GeomReader objects (can be <code>null</code> if static
166     *         initialization failed).
167     */
168    public static GeomReader[] getAllReaders() {
169        return _allReaders;
170    }
171
172    /*
173     * Loads a reader list that is a union of *all* resources named
174     * 'resourceName' as seen by 'loader'. Null 'loader' is equivalent to the
175     * application loader.
176     */
177    private static GeomReader[] loadResourceList(final String resourceName, ClassLoader loader) {
178        if (isNull(loader))
179            loader = ClassLoader.getSystemClassLoader();
180
181        final FastSet<GeomReader> result = FastSet.newInstance();
182
183        try {
184            final Enumeration<URL> resources = loader.getResources(resourceName);
185
186            if (nonNull(resources)) {
187                // merge all mappings in 'resources':
188
189                while (resources.hasMoreElements()) {
190                    final URL url = resources.nextElement();
191                    final Properties mapping;
192
193                    try (InputStream urlIn = url.openStream()) {
194
195                        mapping = new Properties();
196                        mapping.load(urlIn);            // load in .properties format
197
198                    } catch (IOException ioe) {
199                        // ignore this resource and go to the next one
200                        continue;
201                    }
202
203                    // load all readers specified in 'mapping':
204                    for (Enumeration keys = mapping.propertyNames(); keys.hasMoreElements();) {
205                        final String format = (String)keys.nextElement();
206                        final String implClassName = mapping.getProperty(format);
207
208                        result.add(loadResource(implClassName, loader));
209                    }
210                }
211            }
212
213        } catch (IOException ignore) {
214            // ignore: an empty result will be returned
215        }
216
217        //  Convert result Set to an array.
218        GeomReader[] resultArr = result.toArray(new GeomReader[result.size()]);
219
220        //  Sort the array using the specified comparator.
221        Arrays.sort(resultArr, FastComparator.DEFAULT);
222
223        //  Clean up before leaving.
224        FastSet.recycle(result);
225
226        //  Output the sorted array.
227        return resultArr;
228    }
229
230    /*
231     * Loads and initializes a single resource for a given format name via a
232     * given classloader. For simplicity, all errors are converted to
233     * RuntimeExceptions.
234     */
235    private static GeomReader loadResource(final String className, final ClassLoader loader) {
236        requireNonNull(className, "null input: className");
237        requireNonNull(loader, "null input: loader");
238
239        final Class cls;
240        final Object reader;
241        try {
242            cls = Class.forName(className, true, loader);
243            reader = cls.getDeclaredConstructor().newInstance();
244
245        } catch (Exception e) {
246            throw new RuntimeException(MessageFormat.format(
247                    RESOURCES.getString("couldntLoadClass"), className) + " " + e.getMessage());
248        }
249
250        if (!(reader instanceof GeomReader))
251            throw new RuntimeException(
252                    MessageFormat.format(RESOURCES.getString("notGeomReader"), cls.getName()));
253        
254        return (GeomReader)reader;
255    }
256
257    /**
258     * This method decides on which class loader is to be used by all resource/class
259     * loading in this class. At the very least you should use the current thread's
260     * context loader. A better strategy would be to use techniques shown in
261     * http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html
262     */
263    private static ClassLoader getClassLoader() {
264        return Thread.currentThread().getContextClassLoader();
265    }
266
267}   //  end of class
268