воскресенье, 30 октября 2011 г.

Android: Как изменить размер BitmapDrawable

Недавно мне понадобилось получить из внешнего потока (InputStream) картинку и вывести ее на экран, но картинка была в одном размере и ее нужно было под разные экраны и условия уменьшить или увеличить.

Для начала получим из потока Drawable. Например с assets каталога:
  Drawable drawable = Drawable.createFromStream(act.getAssets().open(fileName), null);
И для того, чтобы изменить размер, я использую следующий метод:
  public static Drawable resizeDrawable(Drawable drawable, int newWidth, int newHeight) {
    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    float scaleWidth = ((float) newWidth) / width;
    float scaleHeight = ((float) newHeight) / height;
    Matrix matrix = new Matrix();
    matrix.postScale(scaleWidth, scaleHeight);
    Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
        width, height, matrix, true);
    return new BitmapDrawable(resizedBitmap);
  }
Этот метод я запостил скорей чтобы не забыть, потому как в проекте он уже не нужен, и дабы помочь ищущему. Но я думаю есть другие варианты решения, если кто наткнулся на пост и знает, то плиз в комментарии.


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

Простой инсталлятор для Linux средствами bash

Если вам приходилось ставить JDK на вашу Linux-машину, то вы знакомы с этим способом развёртывания приложения. И, если теперь ваша замечательная новая программа должна отправиться к благодарным пользователям, то почему бы не облегчить им жизнь с помощью простого "инсталлятора"? Вот увидите, клиенты это оценят.

Как это выглядит?
Предельно просто. Пользователь загружает .sh файл и запускает его. Скрипт не просто создаёт все директории и т.п., а ещё и извлекает "из себя" файлы приложения и раскладывает их куда нужно.

Как это сделать?
Сначала создаём скрипт, а потом с помощью команды cat добавляем в него архив с файлами.
Например install.sh:
  1. #!/bin/bash
  2. A_F=`readlink -e "$0"`
  3. DIR=`dirname "$A_F"`
  4. TMP_ARH="tmp.tar.gz"
  5. cd $DIR
  6. echo "Creating temporary arhive $TMP_ARH"
  7. tail -n +15 "$0" > $TMP_ARH
  8. echo "Unpacking temporary arhive $TMP_ARH"
  9. tar xzf $TMP_ARH
  10. echo "Removing temporary arhive $TMP_ARH"
  11. rm -f $TMP_ARH
  12. echo "Installation is complete!"
  13. exit 0
  14. ######
Тут делаем следующее:

  • В строках 2-3 определяем директорию, в которой в данный момент находится скрипт, в строке 5 переходим туда. Все дальнейшие операции выполняем относительно этой директории. 
  • В строке 4 задаём имя временного архива.
  • В строке 7 "отрезаем" бинарную часть нашего скрипта (в данном случае она начинается со строки 15), которую добавим к скрипту позже. Бинарную часть сохраняем как временный архив. 
  • В строке 9 распаковываем архив в текущую директорию и удаляем его в строке 11
  • Завершаем выполнение скрипта в строке 13 (чтобы bash не решил выполнить и наши бинарные данные, расположенные ниже).
Как видно из скрипта в строке 15 начинается архив с нашим приложением. Добавим его:
cat my_app.tar.gz >> install.sh
После команды извлечения данных в вашем "инсталляторе" вы сможете сделать также все необходимые симлинки и выполнить другие операции по настройке системы. 

воскресенье, 16 октября 2011 г.

Расширим DAO c Hibernate Generic D.A.O. Framework

Когда я только начал изучать Hibernate я делал на каждую свою сущность отдельный DAO. Вскоре я наткнулся на популярную в сети статью "Не повторяйте DAO", которая используя Spring и дженерики создает обобщенный типизированный DAO. И через некоторое время данный подход возродился в некий фреймворк, который может работать с любой сущностью или коллекцией сущностей. Но смысла нету показывать мои костыли, если есть Hibernate Generic D.A.O. Framework, про который можно прочитать здесь, а я постараюсь показать пример его использования. Правда DAO нужно будет всеравно создавать для каждой сущности, но зато мы получим общие методы и интересный подход к составлению запросов в БД.

вторник, 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), более стабильная работа.

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

Изменение языка интерфейса в NetBeans 7.0+

После выхода 7.0 версии этой чудесной среды появилась весьма хорошая русская локализация. Но мне как и большинству программистам, ближе английский язык в IDE. Решив сменить язык интерфейса я полез в настройки, но к сожалению не нашел ни одного намека на данную функцию. После я прошелся по просторам интернета и нашел вот такие варианты решения:

1. Добавить в строку запуска параметр --locale en:US (FAQ)
Пример :  С:\Program Files\NetBeans 7.0.1\bin\netbeans.exe --locale en:US

2. Добавить --locale en_US в параметр netbeans_default_options в файл <netbeans folder>\etc\netbeans.conf
Пример (в файле С:\Program Files\NetBeans 7.0.1\etc\netbeans.conf):
netbeans_default_options=".... --locale en_US"

З.Ы. Данные решения можно применять как в Windows так и в Linux.

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

JSch: Работаем с удалённой машиной по ssh из java-приложения

Хочу рассказать об одной интересной находке: библиотеке JSch. Это реализация ssh соединения на java.  Не уникальная, само собой, но из полудесятка пересмотренных мной, именно она понравилась больше всего.
Возможности весьма впечатляют: аворизация по ключам, порт-форвардинг, соединение через прокси и ещё много чего. Также обрадовала простота работы с библиотекой. Вот, к примеру, реализация удалённой консоли в три десятка строк:

import com.jcraft.jsch.*;
import javax.swing.*;

public class Shell {

  public static void main(String[] arg) {
    try {
      JSch jsch = new JSch();
      String connstr = null;
      if (arg.length > 0) {
        connstr = arg[0];
      } else {
        connstr = JOptionPane.showInputDialog("Enter username:password@hostname", System.getProperty("user.name")+":<pass>"+"@localhost");
      }
      String user = connstr.substring(0, connstr.indexOf(':'));
      String pass = connstr.substring(connstr.indexOf(':')+1, connstr.indexOf('@'));
      String host = connstr.substring(connstr.indexOf('@') + 1);
      Session session = jsch.getSession(user, host, 22);
      session.setPassword(pass);
      session.setConfig("StrictHostKeyChecking", "no");
      session.connect();
      Channel channel = session.openChannel("shell");
      channel.setInputStream(System.in);
      channel.setOutputStream(System.out);
      channel.connect();
    } catch (Exception e) {
      System.out.println(e);
    }
  }
}

Тут мы делаем следующее:
  • Получаем от пользователя настройки подключения (как параметр вызова или диалоговым окном)
  • Создаём сессию и устанавливаем соединение
  • Создаём Channel (в данном случае для интерактивного исполнения команд, хотя есть и другие варианты)
  • "Заворачиваем" системные потоки ввода и вывода в полученный канал
  • наслаждаемся :)

понедельник, 3 октября 2011 г.

Упаковка и распаковка архива на Java

Java без посторонних библиотек поддерживает работу с несколькими видами архивов, в том числе и с самым популярным - Zip. Для этого используются классы из пакета java.util.zip. Давайте посмотрим, как с помощью этих инструментов можно упаковать и распаковать набор каталог с файлами и каталогами.
Упаковка:

  public static void pack(File directory, String to) throws IOException {
    URI base = directory.toURI();
    Deque<File> queue = new LinkedList<File>();
    queue.push(directory);
    OutputStream out = new FileOutputStream(new File(to));
    Closeable res = out;

    try {
      ZipOutputStream zout = new ZipOutputStream(out);
      res = zout;
      while (!queue.isEmpty()) {
        directory = queue.pop();
        for (File child : directory.listFiles()) {
          String name = base.relativize(child.toURI()).getPath();
          if (child.isDirectory()) {
            queue.push(child);
            name = name.endsWith("/") ? name : name + "/";
            zout.putNextEntry(new ZipEntry(name));
          } else {
            zout.putNextEntry(new ZipEntry(name));
            InputStream in = new FileInputStream(child);
            try {
              byte[] buffer = new byte[1024];
              while (true) {
                int readCount = in.read(buffer);
                if (readCount < 0) {
                  break;
                }
                zout.write(buffer, 0, readCount);
              }
            } finally {
              in.close();
            }
            zout.closeEntry();
          }
        }
      }
    } finally {
      res.close();
    }
  }



Этот метод интересен тем, что, в отличие от большинства методов для манипуляции с файлами, он нерекурсивный. Тут для обработки вложенных директорий используется очередь. Кстати, метод не мой, взят отсюда.
Обратный процесс также реализуем нерекурсивным методом.Проходим по всем entry в архиве, директории сразу создаём, файлы добавляем в очередь. Потом проходим по очереди и создаём файлы, копируя их из ZipInputStream в FileOutputStream:

  public static void unpack(String path, String dir_to) throws IOException {
    ZipFile zip = new
ZipFile(path);
    Enumeration entries = zip.entries();
    LinkedList<ZipEntry> zfiles = new LinkedList<ZipEntry>();
    while (entries.hasMoreElements()) {
      ZipEntry entry = (ZipEntry) entries.nextElement();
      if (entry.isDirectory()) {
        new File(dir_to+"/"+entry.getName()).mkdir();
      } else {
        zfiles.add(entry);
      }
    }
    for (ZipEntry entry : zfiles) {
      InputStream in = zip.getInputStream(entry);
      OutputStream out = new FileOutputStream(dir_to+"/"+entry.getName());
      byte[] buffer = new byte[1024];
      int len;
      while ((len = in.read(buffer)) >= 0)
        out.write(buffer, 0, len);
      in.close();
      out.close();
      }
    zip.close();
  }