вторник, 25 июня 2013 г.

GWT: генерация XML

Генерировать xml по шаблону на стороне браузера (например, для создания xml-запроса для поиска)  удобно при помощи XTemplates из библиотеки GXT. Список возможностей, которые поддерживает XTemplates:
  • String, primitive and object value variable support.
  • Formatting object values into strings.
  • Iterative object and nested iterative object support.
  • Conditional support.
  • Ability to call other Java methods that return boolean.
  • Basic math and expression support.
  • HTML external source support.
Пример использования:

Файл шаблона get-entity-by-uri-query.xml:
<query>
<select>
<field>*</field>
</select>
<from>
<schema>{entityUri.entityType.schemaCode}</schema>
</from>
<where>
<in>
<field>INSTANCE_ID</field>
<value>{entityUri.schemaLocalId}</value>
</in>
</where>
</query>

Класс рендерера GetEntityByUriQueryTemplate.java:
public interface GetEntityByUriQueryTemplate extends XTemplates{
    GetEntityByUriQueryTemplate INSTANCE =  GWT.create(GetEntityByUriQueryTemplate.class);

    @XTemplate(source="get-entity-by-uri-query.xml")
    SafeHtml getQueryXml(EntityUri entityUri);
}

Вызов рендерера:
String queryXml=GetEntityByUriQueryTemplate.INSTANCE.getQueryXml(entityUri).asString())

пятница, 23 ноября 2012 г.

Excel: Как раскрасить строки через одну в автофильтре


  1. Выделяем строки данных автофильтра
  2. Выбираем пункт 'Условное форматирование/Создать правило'
  3. Выбираем "использовать формулу для определения форматируемых ячеек"
  4. Вводим в формулу =ОСТАТ(ПРОМЕЖУТОЧНЫЕ.ИТОГИ(3,$A$1:$A1),2), где А - столбец фильтруемых данных.
  5. Выбираем нужный формат.

Примечание: для английской версии excel нужно использовать формулу:
=MOD(SUBTOTAL(3,$A$1:$A1),2)

Пример:



четверг, 11 октября 2012 г.

Eclipse: Как скопировать полное имя класса.

Иногда нужно получить полное имя класса. В eclipse это можно сделать несколькими способами:

Способ 1: В окне редактирования поставить курсор на необходимый класс и в контекстном меню вызвать "Copy Qualified Name"

Способ 2: Через контекстное меню в Package Explorer. Но, если выбрать непосредственно java-файл:


то скопируем что-то типа /my-project/src/main/java/mypackage/MyClass.java. Чтобы скопировать именно имя класса, нужно развернуть MyClass.java и вызвать контекстное меню на дочернем элементе:


четверг, 9 августа 2012 г.

Отключение автоматического переключения на консоль в Eclipse

Eclipse автоматически показывает вкладку "Console", если отлаживаемое приложение что-то выводит в stdout. Иногда это не очень удобно. Например, работаешь в данный момент во вкладке "Search" или смотришь как выполняются unit-тесты во вкладке "JUnit", а тебя без твоего ведома переключают на консоль.

Для отключения этого поведения нужно в настройках:
Preferences -> Run/Debug -> Console
снять флажки:
Show when program writes to standard out
Show when program writes to standard error

пятница, 27 июля 2012 г.

Общение с потусторонним миром или динамически-подгружаемая библиотека, невидимая для остального приложения

Приветствую!

Сегодня нас ждет немного мистики :)

Недавно так сложилось, что в одном проекте понадобилось одну библиотеку сделать подгружаемой динамически. И это не было бы проблемой (особенно если вы читали статью про загрузку JAR из другого JAR), но вот беда - надо было сделать так, чтобы ее не "видело" наше приложение. А знал бы про нее только класс, из которого мы загрузили библиотеку ну и те классы, которые подгружались вместе с библиотекой.
И еще одной проблемой было то, что в библиотеке дублировались некоторые классы из других библиотек. И вот эти дублированные классы должны быть доступны библиотеке (причем именно они, а не классы из отдельных библиотек). А приложению доступны были только классы из отдельных библиотек, а не классы динамической библиотеки.
Запутал? :)
Объясню на примере:
Пусть наша динамическая библиотека называется jar_1.jar. Она содержит классы Class_1, Class_2, Class_3, Class_4.
Кроме того если статические библиотеки jar_2.jar (с классами Class_1, Class_11, Class_12) и jar_3.jar (с классами Class_2, Class_20, Class_21, Class_22).
Так вот, jar_1 должна видеть jar_1!/Class_1, а не jar_2!/Class_1; и jar_1!/Class_2, а не jar_3!/Class_2.
И при этом, jar_1 должна видеть jar_2!/Class_11, jar_2!/Class_12, jar_3!/Class_20, jar_3!/Class_21, jar_3!/Class_22.
А приложение должно видеть jar_2!/Class_1 и jar_3!/Class_2. И не видеть все классы из jar_1.

Единственный выход - собственный ClassLoader с измененной логикой загрузки классов.

За основу мы взяли URLClassLoader и через него мы и подгружаем нашу библиотеку, которая, чтобы быть невидимой для всех остальных ClassLoader-ов находилась в приложении в виде ресурса.

Теперь оставалось только сделать верную логику загрузки классов, чтобы классы из обычных библиотек были прекрасно видны нашей библиотекой. А классы нашей библиотеки не становились бы доступны остальному приложению.

Результат:


@Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> c = null;

        try {
            c = findLoadedClass(name);
        } catch (Exception e) {
            // swallow
        }
        if (c == null) {
            try {
                c = findClass(name);
            } catch (Exception e) {
                // swallow
            }
        }      
        if ((c == null) && (getParent() != null)) {
            try {
                c = getParent().loadClass(name);
            } catch (Exception e) {
                // swallow
            }
        }
        if (c == null) {
            c = findSystemClass(name);
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

При этом надо не забыть передать в конструктор текущий ClassLoader как родителя:

new MagicURLClassLoader(new URL[] {  getInsideJarURL(getClass().getClassLoader(), "magic.jar")}, getClass().getClassLoader());

Загрузка JAR из другого JAR. Часть 2

Приветствую!

Как и обещал - вторая часть рассказа про загрузку jar, находящихся внутри другого jar.
Сегодня мы рассмотрим случай, когда у нас есть статически прилинкованный jar, внутри которого если несколько других библиотек, и нам нужно загрузить их все.

Здесь ключевым моментом является получения URL на "внешний" jar. Проще всего это сделать из класса, находящегося в этой библиотеке. Для этого вызываем (предполагаем, что класс называется ThisClass):
ThisClass.class.getProtectionDomain().getCodeSource().getLocation();

А далее по URL получаем путь к файлу (а это, напомню, настоящий файл на файловой системе) через вызов метода getPath().
После чего нам ничто не мешает создать File по пути и JarFile по файлу.

Далее все совсем просто - бегаем по JarEntry нашего "внешнего" jar-файла и если находим в нем вложенный jar-файл, то загружаем его как было описано в первой части статьи - выгрузка во временный файл и передача URLClassLoader URL этого файла.
Надо только не забыть правильно сформировать имя для временного файла, т.е. сделать что-то типа jarEntry.getName().replace('/''_').

вторник, 24 июля 2012 г.

Загрузка JAR из другого JAR. Часть 1

Приветствую!
Сегодня хочется поговорить о вопросе загрузки jar, находящегося внутри другого jar. При этом, мы предполагаем, что этот "большой" jar-файл был статически прилинкован к приложению и уже загружен.
К сожалению совсем легко это не сделать, но и усилий надо приложить совсем немного.
Единственный возможный выход - это сохранить эти файлы на файловой системе (лучше всего во временной папке) и передать URLClassLoader-у пути к файлам.

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

Итак, приступим. Наша задача достаточно проста:

  1. Находим внутренний jar через вызов getResource текущего ClassLoader
  2. Получаем у него InputStream
  3. Создаем временный файл
  4. Заливаем наш jar-ник во временный файл
  5. Закрываем все потоки
  6. URL временного файла скармливаем URLClassLoader
  7. Здесь на выбор - либо через ClassLoader вызываем loadClass, либо через Class.forName - загружаем нужный класс
  8. Повторяем все шаги для остальных jar-ков
А вот и код
private URL getInsideJarURL(ClassLoader cl, String resourceName) throws IOException {
        debug("Get inside JAR URL for resource {}", resourceName);
        try {
            URL resourceUrl = cl.getResource(resourceName);
            InputStream is = resourceUrl.openStream();
            File tempFile = File.createTempFile(resourceName, ".jar");
            tempFile.deleteOnExit();
            FileOutputStream fos = new FileOutputStream(tempFile);

            byte[] buf = new byte[1024];
            int len;
            while (0 < (len = is.read(buf, 0, 1024))) {
                fos.write(buf, 0, len);
            }

            is.close();
            fos.close();
            info("Saved inside JAR resource {} as {}", resourceName, tempFile.getAbsolutePath());
            return tempFile.toURI().toURL();
        } catch (IOException e) {
            error("Error loading resource jar {}", resourceName);
            throw e;
        }
    }
и его использование:
ClassLoader cl = getClass().getClassLoader();
classLoader = new URLClassLoader(new URL[] { getInsideJarURL(cl, "jar-1.jar"),
                    getInsideJarURL(cl, "jar-2.jar"), getInsideJarURL(cl, "jar-3.jar") }, cl);
Class<?> myClass = Class.forName("MyClass", true, classLoader);

Если есть желание подгрузить сразу все классы - это тоже не проблема.
Мы знаем наш файл, можем создать JarFile и пробежаться по его JarEntry.

Если мы хотим "скормить" URLClassLoader не всю нашу библиотеку, а лишь отдельные классы, то здесь тоже нет проблемы. Мы выгружаем библиотеку как и раньше, а вот дальше мы создаем объект JarFile на основе нашего файла и достаем те JarEntry, которые нам нужны и тоже сохраняем их в файлы. А затем отдаем URLClassLoader ссылки на выгруженные файлы классов.