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