вторник, 11 октября 2011 г.

Как получить mime-type файла в Java

Теоретически есть три способа получить информацию о типе файла. Все эти способы имеют свои преимущества и, конечно же, недостатки.

Способ первый: не заглядываем внутрь
Проще всего определить тип файла по его расширению. Знакомая с детства, понятная, быстрая и весьма неточная схема. Собственно, точность зависит от того, насколько полным и актуальным является используемый нами справочник расширений. Чтобы не хардкодить свой справочник используем, например javax.activation.MimetypesFileTypeMap:
import javax.activation.MimetypesFileTypeMap;
import java.io.File;

class GetMimeType {
 public static void main(String args[]) {
  File f = new File("gumby.gif");
  System.out.println("Mime Type of " + f.getName() + " is " +
             new MimetypesFileTypeMap().getContentType(f));
  // expected output :
  // "Mime Type of gumby.gif is image/gif"
 }
}

Для работы этого кода потребуется подключить библиотеку activation.jar, которую можно взять отсюда.
Библиотека ищет данные о типе файла в нескольких местах системы в следующем порядке:
  1. Programmatically added entries to the MimetypesFileTypeMap instance.
  2. The file .mime.types in the user's home directory.
  3. The file /lib/mime.types.
  4. The file or resources named META-INF/mime.types.
  5. The file or resource named META-INF/mimetypes.default (usually found only in the activation.jar file). 
Способ второй: Чтобы понять что это, соединимся с этим
Способ более надёжный, не требует дополнительных библиотек, но не рекомендуется к использованию в "промышленных масштабах" ввиду крайней медлительности.
import java.net.*;

public class FileUtils{
 public static String getMimeType(String fileUrl)
  throws java.io.IOException, MalformedURLException
 {
  String type = null;
  URL u = new URL(fileUrl);
  URLConnection uc = null;
  uc = u.openConnection();
  type = uc.getContentType();
  return type;
 }

 public static void main(String args[]) throws Exception {
  System.out.println(FileUtils.getMimeType("file://c:/temp/test.TXT"));
  // output : text/plain
 }
}

Тут мы практически открываем соединение с файлом по протоколу file:// и читаем заголовок content-type из полученного соединения.
Чуть более быстрый способ:

import java.net.FileNameMap;
import java.net.URLConnection;

public class FileUtils {

 public static String getMimeType(String fileUrl)
   throws java.io.IOException
  {
   FileNameMap fileNameMap = URLConnection.getFileNameMap();
   String type = fileNameMap.getContentTypeFor(fileUrl);

   return type;
  }

  public static void main(String args[]) throws Exception {
   System.out.println(FileUtils.getMimeType("file://c:/temp/test.TXT"));
   // output : text/plain
  }
 }
Принцип тут тот же.

Способ третий: Посмотрим-таки внутрь!
Самый надёжный способ, определяющий тип файла по его содержимому. Точен настолько, насколько это возможно, достаточно быстр. Но требует использования большого числа  сторонних библиотек. Например, используя Apache Tika:

import java.io.File;
import java.io.FileInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.ContentHandler;

public class Main {

  public static void main(String args[]) throws Exception {

  FileInputStream is = null;
  try {
   File f = new File("C:/Temp/mime/test.docx");
   is = new FileInputStream(f);

   ContentHandler contenthandler = new BodyContentHandler();
   Metadata metadata = new Metadata();
   metadata.set(Metadata.RESOURCE_NAME_KEY, f.getName());
   Parser parser = new AutoDetectParser();
   // OOXMLParser parser = new OOXMLParser();
   parser.parse(is, contenthandler, metadata);
   System.out.println("Mime: " + metadata.get(Metadata.CONTENT_TYPE));
   System.out.println("Title: " + metadata.get(Metadata.TITLE));
   System.out.println("Author: " + metadata.get(Metadata.AUTHOR));
   System.out.println("content: " + contenthandler.toString());
  }
  catch (Exception e) {
   e.printStackTrace();
  }
  finally {
    if (is != null) is.close();
  }
 }
}
Библиотека - часть поискового движка Lucene - подключает в рантайме около 20 (!) зависимостей (в общей сложности мегабайт на 18). Смотрите сами, нужно ли оно вам в проекте.

Более компактный вариант - использовать библиотеку JMimeMagic:

  public static String getMime(String path) {
    MagicMatch match = null;
    try {
      match = Magic.getMagicMatch(new File(path), true);
    } catch (Exception e){
      e.printStackTrace(System.err);
    }
    return match.getMimeType();
  }
Плюсы: всего две зависимости: Apache Common logging и Jakarta Oro (уже не разрабатывается). Вместе с зависимостями размер библиотеки едва превышает 150 Kb.
Минусы: сравнительно невысокая точность - "не узнаёт" несколько популярных форматов файлов, иногда "убивает" наше приложение с ошибкой OutOfMemoryError. На исправление ошибок вряд ли стоит рассчитывать: последняя версия вышла в 2006 году.

...или библиотеку Mime-Util:

import eu.medsea.mimeutil.MimeUtil;

public class Main {
  public static void main(String[] args) {
    MimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
    File f = new File ("c:/temp/mime/test.doc");
    Collection<?> mimeTypes = MimeUtil.getMimeTypes(f);
    System.out.println(mimeTypes);
    // output : application/msword
  }
}
Минусы: несколько больший размер, есть проблема с "узнаванием" текстовых файлов: все они (включая html) воспринимаются как "application/octet-stream". Плюсы: достаточно высокая точность определения типов, всего одна зависимость (SLF4J), более стабильная работа.

Как правило, более "навороченные" библиотеки, распознающие больше форматов соответственно и "тяжелее". Я уверен, что есть ещё достаточно много библиотек, не описанных в статье, вольный перевод которой я тут попытался представить вашему вниманию. Если у кого-нибудь возникнет желание познакомиться с какой-нибудь подробнее, дайте ссылку в комментариях, я с удовольствием посмотрю на неё и опишу результат знакомства.

3 комментария: