/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.inputoutput.filetype; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import javax.activation.FileTypeMap; import javax.activation.MimeType; import javax.activation.MimeTypeParseException; import org.arakhne.afc.inputoutput.mime.MimeName; import org.arakhne.afc.vmutil.asserts.AssertMessages; /** An utility class that permits to detect the type of a file. * This class determines the file type according to the file content * (Unix approach) and not according to * the filename extension (Windows® approach). A MIME constant * is replied for the detected type. * * <p>This class implements equivalent functionalities as * the {@code file} Linux command. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 14.0 */ public final class FileType { private static final String IMAGE = "image"; //$NON-NLS-1$ private FileType() { // } /** Be sure that the default FileTypeMap is a * {@link ContentFileTypeMap}. * * @return the default content type manager. */ public static ContentFileTypeMap ensureContentTypeManager() { FileTypeMap defaultMap = FileTypeMap.getDefaultFileTypeMap(); if (!(defaultMap instanceof ContentFileTypeMap)) { defaultMap = new ContentFileTypeMap(defaultMap); FileTypeMap.setDefaultFileTypeMap(defaultMap); } return (ContentFileTypeMap) defaultMap; } /** Register a MIME type of the given file. * * @param magicNumber is the string that identify the type of the content. */ public static void addContentType(MagicNumber magicNumber) { final ContentFileTypeMap map = ensureContentTypeManager(); map.addContentType(magicNumber); } /** Replies the MIME type of the given file. * * @param filename is the name of the file to test. * @return the MIME type of the given file. */ public static String getContentType(File filename) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.getContentType(filename); } /** Replies the MIME type of the given file. * * @param filename is the name of the file to test. * @return the MIME type of the given file. */ public static String getContentType(String filename) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.getContentType(filename); } /** Replies the MIME type of the given file. * * @param filename is the name of the file to test. * @return the MIME type of the given file. */ public static String getContentType(URL filename) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.getContentType(filename); } /** Replies the version of the format of the given file. * * @param filename is the name of the file to test. * @return the format version for the given file. */ public static String getFormatVersion(File filename) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.getFormatVersion(filename); } /** Replies the version of the format of the given file. * * @param filename is the name of the file to test. * @return the format version for the given file. */ public static String getFormatVersion(String filename) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.getFormatVersion(filename); } /** Replies the version of the format of the given file. * * @param filename is the name of the file to test. * @return the format version for the given file. */ public static String getFormatVersion(URL filename) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.getFormatVersion(filename); } /** Replies if the specified MIME type corresponds to an image. * * @param mime is the MIME type to test. * @return <code>true</code> if the given MIME type is corresponding to an image, * otherwise <code>false</code> */ public static boolean isImage(String mime) { try { final MimeType type = new MimeType(mime); return IMAGE.equalsIgnoreCase(type.getPrimaryType()); } catch (MimeTypeParseException e) { // } return false; } /** Replies if the given file is compatible with the given MIME type. * * @param filename is the name of the file to test. * @param desiredMimeType is the desired MIME type. * @return <code>true</code> if the given file has type equal to the given MIME type, * otherwise <code>false</code>. */ public static boolean isContentType(File filename, String desiredMimeType) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.isContentType(filename, desiredMimeType); } /** Replies if the given file is compatible with the given MIME type. * * @param filename is the name of the file to test. * @param desiredMimeType is the desired MIME type. * @return <code>true</code> if the given file has type equal to the given MIME type, * otherwise <code>false</code>. */ public static boolean isContentType(String filename, String desiredMimeType) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.isContentType(filename, desiredMimeType); } /** Replies if the given file is compatible with the given MIME type. * * @param filename is the name of the file to test. * @param desiredMimeType is the desired MIME type. * @return <code>true</code> if the given file has type equal to the given MIME type, * otherwise <code>false</code>. */ public static boolean isContentType(URL filename, String desiredMimeType) { final ContentFileTypeMap map = ensureContentTypeManager(); return map.isContentType(filename, desiredMimeType); } /** Map that contains the file content type definitions. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public static class ContentFileTypeMap extends FileTypeMap { private final SoftReference<FileTypeMap> parent; private final Map<String, Collection<MagicNumber>> typedNumbers = new TreeMap<>(); private final Collection<MagicNumber> defaultNumbers = new ArrayList<>(); /** * @param parent is the parent of this content type map, which will be invoked if this map does not know the answer. */ public ContentFileTypeMap(FileTypeMap parent) { this.parent = new SoftReference<>(parent); } /** Constructor. */ public ContentFileTypeMap() { this.parent = new SoftReference<>(FileTypeMap.getDefaultFileTypeMap()); } /** {@inheritDoc} */ @Override public String getContentType(File filename) { if (!filename.exists()) { return null; } try { final MimeType type = getMimeType(filename.toURI().toURL()); if (type != null) { return type.toString(); } } catch (Exception e) { // } final FileTypeMap lparent = this.parent.get(); if (lparent != null) { return lparent.getContentType(filename); } return MimeName.MIME_OCTET_STREAM.getMimeConstant(); } @Override public String getContentType(String filename) { return getContentType(new File(filename)); } /** Replies the mime type of the specified url. * * @param url is the location of the file to test. * @return the MIME type of the given file. */ public String getContentType(URL url) { try { final MimeType type = getMimeType(url); if (type != null) { return type.toString(); } } catch (Exception e) { // } return MimeName.MIME_OCTET_STREAM.getMimeConstant(); } /** Replies if the given file has the given MIME type. * * @param filename is the filename of the file to read. * @param mimeType is the restriction type. * @return <code>true</code> if the file has the given MIME type, * otherwise <code>false</code> */ public boolean isContentType(File filename, String mimeType) { assert filename != null : AssertMessages.notNullParameter(0); assert mimeType != null && !mimeType.isEmpty() : AssertMessages.notNullParameter(1); if (!filename.exists()) { return false; } try { final MimeType mtype = new MimeType(mimeType); if (isMimeType(filename.toURI().toURL(), mtype)) { return true; } } catch (Exception e) { // silently ignore errors } final FileTypeMap lparent = this.parent.get(); if (lparent != null) { final String mime = lparent.getContentType(filename); return mimeType.equalsIgnoreCase(mime); } return false; } /** Replies if the given file has the given MIME type. * * @param filename is the filename of the file to read. * @param mimeType is the restriction type. * @return <code>true</code> if the file has the given MIME type, * otherwise <code>false</code> */ public boolean isContentType(String filename, String mimeType) { return isContentType(new File(filename), mimeType); } /** Replies if the given file has the given MIME type. * * @param filename is the filename of the file to read. * @param mimeType is the restriction type. * @return <code>true</code> if the file has the given MIME type, * otherwise <code>false</code> */ public boolean isContentType(URL filename, String mimeType) { assert filename != null : AssertMessages.notNullParameter(0); assert mimeType != null && !mimeType.isEmpty() : AssertMessages.notNullParameter(1); try { final MimeType mType = new MimeType(mimeType); if (isMimeType(filename, mType)) { return true; } } catch (Exception e) { // } return false; } private Set<MagicNumber> buildDecoderSet(String lowType) { final Set<MagicNumber> allDecoders = new TreeSet<>(); final Collection<MagicNumber> magicNumbers = this.typedNumbers.get(lowType); if (magicNumbers != null) { allDecoders.addAll(magicNumbers); } allDecoders.addAll(this.defaultNumbers); return allDecoders; } /** Replies the mime type of the specified url. * * @see #isMimeType(URL, MimeType) */ @SuppressWarnings("resource") private MimeType getMimeType(URL url) throws Exception { assert url != null : AssertMessages.notNullParameter(0); final URLConnection connection = url.openConnection(); String lowType = connection.getContentType(); if (MimeName.MIME_UNKNOW.isMimeConstant(lowType)) { lowType = MimeName.MIME_OCTET_STREAM.getMimeConstant(); } boolean found; InputStream stream = connection.getInputStream(); for (final MagicNumber magicNumber : buildDecoderSet(lowType)) { try { final MagicNumberStream mns; if (stream == null) { try (InputStream localstream = url.openStream()) { mns = new MagicNumberStream(url, localstream); } } else { mns = new MagicNumberStream(url, stream); stream = null; } magicNumber.doStreamEncoding(mns); found = magicNumber.isContentType(mns); magicNumber.undoStreamEncoding(mns); mns.close(); if (found) { return magicNumber.getMimeType(); } } catch (IOException e) { // } } try { if (lowType != null) { return new MimeType(lowType); } } catch (MimeTypeParseException e) { // } return null; } /** Replies if the given URL is of the given MIME type. * * @see #getMimeType(URL) */ @SuppressWarnings("resource") private boolean isMimeType(URL url, MimeType mimeType) throws Exception { assert url != null : AssertMessages.notNullParameter(0); assert mimeType != null : AssertMessages.notNullParameter(1); final String baseType = mimeType.getBaseType(); assert baseType != null && !baseType.isEmpty(); final URLConnection connection = url.openConnection(); String lowType = connection.getContentType(); if (MimeName.MIME_UNKNOW.isMimeConstant(lowType)) { lowType = MimeName.MIME_OCTET_STREAM.getMimeConstant(); } if (baseType.equalsIgnoreCase(lowType)) { return true; } InputStream stream = connection.getInputStream(); boolean found; for (final MagicNumber magicNumber : buildDecoderSet(lowType)) { if (baseType.equalsIgnoreCase(magicNumber.getMimeType().getBaseType())) { try { final MagicNumberStream mns; if (stream == null) { try (InputStream localstream = url.openStream()) { mns = new MagicNumberStream(url, localstream); } } else { mns = new MagicNumberStream(url, stream); stream = null; } magicNumber.doStreamEncoding(mns); found = magicNumber.isContentType(mns); magicNumber.undoStreamEncoding(mns); mns.close(); if (found) { return true; } } catch (IOException e) { // } } } return false; } /** Replies the format version of the specified url. * * @param url is the name of the file to test. * @return the MIME type of the given file. */ @SuppressWarnings("resource") public String getFormatVersion(URL url) { assert url != null : AssertMessages.notNullParameter(); String lowType; InputStream stream; try { final URLConnection connection = url.openConnection(); lowType = connection.getContentType(); stream = connection.getInputStream(); } catch (IOException e) { return null; } if (MimeName.MIME_UNKNOW.isMimeConstant(lowType)) { lowType = MimeName.MIME_OCTET_STREAM.getMimeConstant(); } boolean found; for (final MagicNumber magicNumber : buildDecoderSet(lowType)) { try { final MagicNumberStream mns; if (stream == null) { try (InputStream localstream = url.openStream()) { mns = new MagicNumberStream(url, localstream); } } else { mns = new MagicNumberStream(url, stream); stream = null; } magicNumber.doStreamEncoding(mns); found = magicNumber.isContentType(mns); magicNumber.undoStreamEncoding(mns); mns.close(); if (found) { return magicNumber.getFormatVersion(); } } catch (IOException e) { // } } return null; } /** Replies the version of the format of the given file. * * @param filename is the name of the file to test. * @return the format version for the given file. */ public String getFormatVersion(File filename) { assert filename != null : AssertMessages.notNullParameter(); if (!filename.exists()) { return null; } try { return getFormatVersion(filename.toURI().toURL()); } catch (Exception e) { // } return null; } /** Replies the version of the format of the given file. * * @param filename is the name of the file to test. * @return the format version for the given file. */ public String getFormatVersion(String filename) { assert filename != null && !filename.isEmpty() : AssertMessages.notNullParameter(); return getFormatVersion(new File(filename)); } /** Register a MIME type of the given file. * * @param magicNumber is the string that identify the type of the content. */ public void addContentType(MagicNumber magicNumber) { assert magicNumber != null : AssertMessages.notNullParameter(); boolean isTyped = false; final MimeType[] types = magicNumber.getHostMimeTypes(); if (types != null) { for (final MimeType type : types) { if (type != null) { final String m = type.getBaseType(); Collection<MagicNumber> col = this.typedNumbers.get(m); if (col == null) { col = new ArrayList<>(); this.typedNumbers.put(m, col); } col.add(magicNumber); isTyped = true; } } } if (!isTyped) { this.defaultNumbers.add(magicNumber); } } } }