/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.data; import static com.google.common.base.Preconditions.checkNotNull; import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger; import static fr.ens.biologie.genomique.eoulsan.EoulsanRuntime.getSettings; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import fr.ens.biologie.genomique.eoulsan.EoulsanException; import fr.ens.biologie.genomique.eoulsan.Globals; import fr.ens.biologie.genomique.eoulsan.Main; import fr.ens.biologie.genomique.eoulsan.design.Design; import fr.ens.biologie.genomique.eoulsan.design.Sample; import fr.ens.biologie.genomique.eoulsan.util.ClassPathResourceLoader; import fr.ens.biologie.genomique.eoulsan.util.FileResourceLoader; import fr.ens.biologie.genomique.eoulsan.util.StringUtils; import fr.ens.biologie.genomique.eoulsan.util.Utils; /** * this class register DataFormat to allow get the DataFormat of a file from its * filename. * @since 1.0 * @author Laurent Jourdren */ public class DataFormatRegistry { private static final String RESOURCE_PREFIX = "META-INF/services/xmldataformats/"; private static final String FORMAT_SUBDIR = "formats"; private final Set<DataFormat> formats = new HashSet<>(); private final Map<String, DataFormat> mapFormats = new HashMap<>(); private boolean xmlServicesCurrentlyLoading; private final Map<String, DataFormat> mapDesignMetadataKeyDataFormat = new HashMap<>(); private final Map<String, DataFormat> mapSampleMetadataKeyDataFormat = new HashMap<>(); private static DataFormatRegistry instance; // // Inner classes // /** * This class define a resource loader for resource defined in the file * system. */ private static final class DataFormatFileResourceLoader extends FileResourceLoader<XMLDataFormat> { @Override protected String getExtension() { return ".xml"; } @Override protected String getResourceName(final XMLDataFormat resource) { checkNotNull(resource, "resource argument cannot be null"); return resource.getName(); } @Override protected XMLDataFormat load(final InputStream in, final String source) throws IOException, EoulsanException { return new XMLDataFormat(in); } /** * Get the default format directory. * @return the default format directory */ private static DataFile getDefaultFormatDirectory() { final Main main = Main.getInstance(); if (main == null) { return new DataFile(FORMAT_SUBDIR); } return new DataFile(new File(main.getEoulsanDirectory(), FORMAT_SUBDIR)); } // // Constructors // /** * Constructor. * @param resourcePaths paths where searching for the resources. */ public DataFormatFileResourceLoader(final String resourcePaths) { super(XMLDataFormat.class, getDefaultFormatDirectory()); if (resourcePaths != null) { addResourcePaths(resourcePaths); } } } /** * This class define a resource loader for resource defined in the class path. */ private static final class DataFormatClassPathLoader extends ClassPathResourceLoader<XMLDataFormat> { @Override protected String getResourceName(final XMLDataFormat resource) { checkNotNull(resource, "resource argument cannot be null"); return resource.getName(); } @Override protected XMLDataFormat load(final InputStream in, final String source) throws IOException, EoulsanException { if (in == null) { throw new NullPointerException( "The input stream of the XML DataFormat source is null: " + source); } return new XMLDataFormat(in); } public DataFormatClassPathLoader() { super(XMLDataFormat.class, RESOURCE_PREFIX); } } /** * Register a DataFormat. * @param df the DataFormat to register * @throws EoulsanException if the DataFormat is not valid */ public void register(final DataFormat df) throws EoulsanException { register(df, false); } /** * Register a DataFormat. * @param df the DataFormat to register * @param callFromConstructor true if method is call from constructor * @throws EoulsanException if the DataFormat is not valid */ private void register(final DataFormat df, final boolean callFromConstructor) throws EoulsanException { if (df == null || this.formats.contains(df)) { return; } check(df, callFromConstructor); for (String suffix : df.getExtensions()) { final String prefix = df.getPrefix(); final String key = prefix + "\t" + suffix; this.formats.add(df); this.mapFormats.put(key, df); } } /** * Register DataFormats. * @param array Array with DataFormats to register * @throws EoulsanException if the DataFormat is not valid */ public void register(final DataFormat[] array) throws EoulsanException { if (array == null) { return; } for (DataFormat df : array) { register(df); } } private void check(final DataFormat df, final boolean callFromConstructor) throws EoulsanException { if (df.getName() == null) { throw new EoulsanException( "The DataFormat " + df.getClass().getName() + " as no name."); } if (!df.getName().toLowerCase().trim().equals(df.getName())) { throw new EoulsanException( "The DataFormat name can't contains upper case character" + df.getClass().getName() + " as no name."); } for (DataFormat format : this.formats) { if (format.getName().equals(df.getName())) { throw new EoulsanException( "A DataFormat named " + df.getName() + " is already registered."); } } final String prefix = df.getPrefix(); if (prefix == null || "".equals(prefix)) { throw new EoulsanException( "The prefix of a DataFormat can't be null or empty (" + df.getName() + ")"); } if (prefix.indexOf('\t') != -1) { throw new EoulsanException( "The prefix of a DataFormat can't contains tab character: " + prefix); } final List<String> extensions = df.getExtensions(); if (extensions == null || extensions.size() == 0) { throw new EoulsanException( "The extensions of a DataFormat can't be null or empty."); } if (df.getDefaultExtension() == null) { throw new EoulsanException( "The no default extension is provided for DataFormat: " + df.getName()); } boolean defaultExtensionFound = false; for (String suffix : df.getExtensions()) { if (suffix == null) { throw new EoulsanException( "The extension of a DataFormat can't be null"); } if (suffix.indexOf('\t') != -1) { throw new EoulsanException( "The extension of a DataFormat can't contains tab character: " + suffix); } if (suffix.equals(df.getDefaultExtension())) { defaultExtensionFound = true; } final String key = prefix + "\t" + suffix; if (this.mapFormats.containsKey(key)) { throw new EoulsanException( "The DataFormat registry already contains entry for prefix \"" + prefix + "\" and extension \"" + suffix + "\""); } if (!callFromConstructor) { throw new EoulsanException("This DataFormat " + df.getName() + " is not registered as a spi service. Cannot register it."); } // Register DataFormat for design fields is necessary if (df.getSampleMetadataKeyName() != null) { this.mapSampleMetadataKeyDataFormat.put(df.getSampleMetadataKeyName(), df); } // Register DataFormat for design fields is necessary if (df.getDesignMetadataKeyName() != null) { this.mapDesignMetadataKeyDataFormat.put(df.getDesignMetadataKeyName(), df); } this.formats.add(df); this.mapFormats.put(key, df); } if (!defaultExtensionFound) { throw new EoulsanException("The default extension of DataFormat \"" + df.getName() + "\" is not in the list of extensions."); } } /** * Get a DataFormat From a file prefix and extension * @param prefix the prefix of the file * @param extension the extension of the file without compression extension * @return a DataFormat or null if the DataFormat was not found */ public DataFormat getDataFormatFromFilename(final String prefix, final String extension) { if (prefix == null) { throw new NullPointerException("The prefix is null"); } if (extension == null) { throw new NullPointerException("The extension is null"); } final String key = prefix + "\t" + extension; return this.mapFormats.get(key); } /** * Get the DataFormat of a file from its filename * @param filename the filename of the file * @return a DataFormat or null if the DataFormat was not found */ public DataFormat getDataFormatFromFilename(final String filename) { if (filename == null) { throw new NullPointerException("The filename is null"); } final String f = StringUtils.filenameWithoutCompressionExtension(filename.trim()); final int dotPos = f.lastIndexOf('.'); if (dotPos == -1) { return null; } final String ext = f.substring(dotPos); final int underscorePos = f.lastIndexOf('_', dotPos); if (underscorePos != -1) { final String prefix = f.substring(0, underscorePos + 1); final DataFormat df = getDataFormatFromFilename(prefix, ext); if (df != null) { return df; } } for (DataFormat df : this.formats) { if (df.isDataFormatFromDesignFile()) { for (String dfExt : df.getExtensions()) { if (dfExt.equals(ext)) { return df; } } } } return null; } /** * Get a DataFormat from its name. * @param dataFormatName the name of the DataFormat to get * @return a DataFormat if found or null */ public DataFormat getDataFormatFromName(final String dataFormatName) { if (dataFormatName == null) { return null; } for (DataFormat df : this.formats) { if (df.getName().equals(dataFormatName)) { return df; } } return null; } /** * Get a DataFormat from its alias. * @param dataFormatAlias the name of the DataFormat to get * @return a DataFormat if found or null */ public DataFormat getDataFormatFromAlias(final String dataFormatAlias) { if (dataFormatAlias == null) { return null; } for (DataFormat df : this.formats) { final String alias = df.getAlias(); if (dataFormatAlias.toLowerCase().equals(alias)) { return df; } } return null; } /** * Get DataFormats from an toolshed Galaxy name extension. * @param name Galaxy name extension. * @return DataFormat */ public DataFormat getDataFormatFromToolshedExtension(final String name) { if (name == null || name.isEmpty()) { return null; } // Search with DataType for (DataFormat df : this.formats) { // Parse Galaxy tool extension for (String ext : df.getGalaxyToolExtensions()) { if (name.toLowerCase(Globals.DEFAULT_LOCALE).equals(ext)) { return df; } } } return null; } /** * Get a DataFormat from its alias. * @param name the name of the DataFormat to get * @return a DataFormat if found or null */ public DataFormat getDataFormatFromNameOrAlias(final String name) { DataFormat result = getDataFormatFromName(name); return result != null ? result : getDataFormatFromAlias(name); } /** * Get a DataFormat from its alias. * @param name the name of the DataFormat to get * @return a DataFormat if found or null */ public DataFormat getDataFormatFromToolshedExtensionOrNameOrAlias(final String name) { DataFormat result = getDataFormatFromToolshedExtension(name); return result != null ? result : getDataFormatFromNameOrAlias(name); } /** * Get DataFormats from an extension. * @param extension the extension of the file without compression extension * @return a set of DataFormat */ public Set<DataFormat> getDataFormatsFromExtension(final String extension) { if (extension == null) { return Collections.emptySet(); } final Set<DataFormat> result = new HashSet<>(); // Search with DataType for (DataFormat df : this.formats) { for (String ext : df.getExtensions()) { if (extension.equals(ext)) { result.add(df); } } } return Collections.unmodifiableSet(result); } /** * Get all the registered formats. * @return a set with all the registered formats */ public Set<DataFormat> getAllFormats() { return Collections.unmodifiableSet(this.formats); } /** * Get the DataFormat that define a metadata entry in the design file. * @param key the name of the metadata key * @return a DataFormat */ public DataFormat getDataFormatForDesignMetadata(final String key) { if (key == null) { return null; } return this.mapDesignMetadataKeyDataFormat.get(key); } /** * Get the DataFormat that define a metadata entry of a sample in the design * file. * @param key the name of the metadata key * @return a DataFormat */ public DataFormat getDataFormatForSampleMetadata(final String key) { if (key == null) { return null; } return this.mapSampleMetadataKeyDataFormat.get(key); } /** * Get the field name in a Design object that correspond to a dataformat. * @param design design object * @param dataformat dataformat to search * @return the field name if found or null */ public String getDesignMetadataKeyForDataFormat(final Design design, final DataFormat dataformat) { if (design == null || dataformat == null) { return null; } for (String fieldname : design.getMetadata().keySet()) { final DataFormat df = getDataFormatForDesignMetadata(fieldname); if (dataformat.equals(df)) { return fieldname; } } return null; } /** * Get the field name in a Sample object that correspond to a dataformat. * @param sample sample object * @param dataformat dataformat to search * @return the field name if found or null */ public String getSampleMetadataKeyForDataFormat(final Sample sample, final DataFormat dataformat) { if (sample == null || dataformat == null) { return null; } for (String fieldname : sample.getMetadata().keySet()) { final DataFormat df = getDataFormatForSampleMetadata(fieldname); if (dataformat.equals(df)) { return fieldname; } } return null; } /** * Register all type defines by classes. */ private void registerAllClassServices() { final Iterator<DataFormat> it = ServiceLoader.load(DataFormat.class).iterator(); for (final DataFormat df : Utils.newIterable(it)) { try { getLogger().fine("try to register format: " + df); register(df, true); } catch (EoulsanException e) { getLogger() .warning("Cannot register " + df.getName() + ": " + e.getMessage()); } } } /** * Register all type defines by XML files. */ private void registerAllXMLServices() { try { final List<DataFormat> formats = new ArrayList<>(); // Load XML formats from the Jar DataFormatClassPathLoader formatClassLoader = new DataFormatClassPathLoader(); formatClassLoader.reload(); formats.addAll(formatClassLoader.loadAllResources()); // Load XML formats from external resources (files...) DataFormatFileResourceLoader formatFileLoader = new DataFormatFileResourceLoader(getSettings().getDataFormatPath()); formatFileLoader.reload(); formats.addAll(formatFileLoader.loadAllResources()); // Register formats for (DataFormat format : formats) { register(format, true); } } catch (EoulsanException e) { getLogger().severe("Cannot register XML data format: " + e.getMessage()); } } /** * Reload the list of the available data types. */ public void reload() { registerAllClassServices(); // Avoid to load XML formats if XML formats are currently loading if (!this.xmlServicesCurrentlyLoading) { this.xmlServicesCurrentlyLoading = true; registerAllXMLServices(); this.xmlServicesCurrentlyLoading = false; } } // // Static method // /** * Get the singleton instance of DataFormatRegistry * @return the DataFormatRegistry singleton */ public static synchronized DataFormatRegistry getInstance() { if (instance == null) { instance = new DataFormatRegistry(); // Initial loading of the formats instance.reload(); } return instance; } // // Constructor // /** * Private constructor. */ private DataFormatRegistry() { } }