понедельник, 30 марта 2015 г.

JavaScript charset в браузере

Согласно стандарту HTML5, стандартная кодировка страницы - UTF-8. А вот с JavaScript всё сложнее.

Пусть у нас есть какой-то JSON. Напишем функцию, которая его возвращает:

function getOutlineJson() {
  return {
      "title" : "Элемент1"
  };
}

Сохраняем в отдельный файл, привязываем через <script>. Пытаемся вывести в консоль:


document.addEventListener('DOMContentLoaded', function() {
  console.log(getOutlineJson());
});

В консоли будет JSON с полями на неведомом языке. Хотя, как подтверждает view.encoding() в консоли Sublime, все файлы - в UTF-8.

А если добавить вывод чего-то кириллического прямо на экран?

document.addEventListener('DOMContentLoaded', function() {
  console.log("проверка консоли");
  console.log(getOutlineJson());
});

Внезапно, кодировка починилась. Видимо, кодировка выставляется по первому вызову.

Лечится мета-тегом:

<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

Проверено в Firefox и Chrome.

"case folding collision between" в Mercurial под Windows

У Mercurial под Windows есть неприятная особенность - если переименовать файл проекта, изменив в нём только регистр одного из символов, то рухнет всё. Mercurial, как дитя Unix-а, считает, что разные регистры - это разные файлы, а вот для Windows это параллельно. В результате - ошибка "case folding collision between" и полный обвал репозитория: даже если удалить зловредный файл, чтобы сделать push, надо сначала сделать pull, а pull не проходит из-за конфликта имён.

Можно посмотреть, что советуют на официальном сайте. Но эти советы не всегда помогают (мне не помогли).

В конце концов, нашёлся хакерский вариант. Только не забудьте сделать резервную копию.

hg pull
# ревизия из репозитория, можно посмотреть в Tortoise
hg debugsetparents <bad revision>
hg debugrebuildstate
hg rm -A -f WeatherTimeMachine-Info.plist
hg ci -m "fixed collision-folding issue"
hg debugsetparents tip
hg debugrebuildstate
hg ci -m "fixed head"
hg push

среда, 25 марта 2015 г.

eval в JavaScript

eval в JavaScript с отложенным выполнением:

var JsEvaluator = (function(){
  var errors = {
    WRONG_SYNTAX : "Синтаксическая ошибка в выражении \"%SOURCE%\"",
    NON_SYNTAX : "Ошибка %ERROR_TITLE% при разборе выражения \"%SOURCE%\""
  };

  function null_func() { return null; }

  return {
    checkedEval : function(str_to_eval){
      if(!str_to_eval)
        return null_func;
      var trimmed_str = str_to_eval.trim();
      if(!trimmed_str)
        return null_func;
      try {
        var result = eval(trimmed_str);
        if(result === undefined)
          return null_func;
      } catch(e) {
        var error_text = (!(e instanceof SyntaxError)) ?
                          errors.WRONG_SYNTAX.replace(/%SOURCE%/, trimmed_str) :
                          errors.NON_SYNTAX.replace(/%ERROR_TITLE%/, e.name).replace(/%SOURCE%/, trimmed_str);
        console.error(error_text);
        return null_func;
      }
      return function() { return eval(trimmed_str); };
    }
  };
})();

Возвращать именно функцию может быть полезно, если выражение в eval завязано на какую-то внешнюю переменную. Например, в str_to_eval у нас условие: "Param1 > 10". Понятно, что его надо выполнить в момент проверки, а не инициализации. Поэтому выполнение в примере отложено.

Для eval выполняется правило последней строки, пришедшее в Ruby и R из Fortran - eval возвращает значение, которое вернула последняя выполненная строка переданного ему выражения. Это может быть в т.ч. строка с числом:
eval("1;2;4")
> 4

или даже со строковой константой:

eval("'boo'")
> "boo"

Как вариант, можно отказаться от ошибок и понимать "неправильный" JavaScript как строковую константу:

var JsEvaluator = (function(){
  function null_func() { return null; }
  return {
    checkedEval : function(str_to_eval){
      if(!str_to_eval)
        return null_func;
      var trimmed_str = str_to_eval.trim();
      if(!trimmed_str)
        return null_func;
      try {
        var result = eval(trimmed_str);
        if(result === undefined)
          return null_func;
      } catch(e) {
        return function() { return str_to_eval; };
      }
      return function() { return eval(trimmed_str); };
    }
  };
})();

Подключаем JSON к Java-проекту

Очень долго JSON в Java читали и генерировали сторонними библиотеками вроде Jackson. Но прогресс не стоит на месте и начиная с Java 7 вроде как появилась стандартная сборка javax.json.

Но, к сожалению, если вставить банальное import javax.json.*, то работать ничего не будет. Java скажет, что не знает этого пространства имён.

Пользователи maven (вроде меня), идёт в Гугл и читают там, что надо подключить зависимость в pom.xml. Вот такую:

<dependency>
   <groupId>org.glassfish</groupId>
   <artifactId>javax.json</artifactId>
   <version>1.0.4</version>
</dependency>

С ней проект действительно компилируется, запускается... и торжественно вылетает с ошибкой Provider org.glassfish.json.JsonProviderImpl not found.

Правильное Dependency выглядит так:

<dependency>
   <groupId>org.glassfish</groupId>
   <artifactId>javax.json</artifactId>
   <version>1.0.4</version>
</dependency>

И теперь всё заработает.

И т.к. namespace - часть Glassfish, то чтобы он заработал в проекте без Maven, надо скачать и добавить к проекту JSON Processing RI jar.

вторник, 24 марта 2015 г.

Разница между isInstance() и instanceof в Java

В чём разница между instanceof из class.isInstance(item) в Java? На самом деле разницы почти нет, просто instanceof требует, чтобы класс, с которым сравнивают, был известен ещё на этапе компиляции.

А вот isInstance можно смело вызывать и от экземпляра: item1.getClass().isInstance(item2)

воскресенье, 22 марта 2015 г.

JavaFX Color в CSS

Внешний вид компонент JavaFX настраивается в CSS. Соответственно, должен быть какой-то конвертер стандартного javafx.scene.paint.Color в CSS-friendly формат.

Возможно, он и правда есть - но я его не нашёл. К тому же, внутри класс Color устроен немного по-другому: насыщенность цвета в свойствах getRed, getBlue и getGreen задаётся double-числом от 0.0 до 1.0.

И вот что получилось:

public static String colorToJson(Color color){
   return String.format("#%02X%02X%02X",
                 (int)(color.getRed() * 0xFF),
                 (int)(color.getBlue() * 0xFF),
                 (int)(color.getGreen() * 0xFF));
}

На выходе из COLOR.Red получается правильный #FF0000.

Выглядит симпатично и немного напоминает чистый C :).Правда, прозрачность потерялась.

А если заглянуть в стандарт CSS, то выясняется: уже давно можно задавать цвет как rgba(0, 0, 255, 1.0).

А значит, наш код можно переписать вот так (formatter нужен, чтобы вместо точки в десятичной дроби не завелась запятая):

public static String colorToJson(Color color){
  StringBuilder sb = new StringBuilder();
  Formatter formatter = new Formatter(sb, Locale.US);
  formatter.format("rgba(%.0f, %.0f, %.0f, %.1f)", color.getRed() * 0xFF, color.getBlue() * 0xFF, color.getGreen() * 0xFF, color.getOpacity());
  return sb.toString();
}

Callable в Java, который возвращает void

К сожалению, в Java пока не появилась аналога шаблонных Func<> и Action<> из C#. Приходится обходиться Callable, в котором указывать тип возвращаемого значения - обязательно.

А если нужно просто выполнить функцию, которая возвращает void, то пишут Callable<Void> и (для асинхронных) Future<Void>. В учебниках обычно не упоминают, что для void есть класс-обёртка.

четверг, 19 марта 2015 г.

Annotation type expected для @Entity или @Test

Иногда в ответ на @Entity у Hibernate или @Test у JUnit компилятор Java заявляет: Annotation type expected.

Это означает, что выставили не тот Import

У @Entity - вместо org.hibernate.metamodel.domain.Entry надо  java.persistance.*
У @Test - вместо junit.framework.Test надо org.junit.Test

Всплывающие подсказки в JavaFX

Накопилась куча материала (кое-что годится на полноценную статью, а что-то просто может забиться).

Буду понемногу выкладывать, чтобы не хранить на бумаге.

Итак, всплывающие подсказки в JavaFx.

В давние времена, когда приложения оставались десктопными, окошечки под Windows было удобней всего рисовать с помощью библиотеки VCL, той самой, которая жила внутри Delphi. Конечно, кто-то писал на MFC в тогда ещё доисторической Visual Studio, кто-то был вынужден поддерживать проекты на OWL, а отдельные маньяки - даже на голом WinAPI, но их мы трогать не будем. И ещё не будем трогать тех, кто писал на VCL для Unix-ов (помните Kylix? А он был!)

Итак, внутри Delphi жил VCL. Если вы писали на C++Builder, он там тоже был. И дополнял C++ своей уникальной String, а также списками, которые начинались с 1.

И почти у всех визуальных компонент были свойства ShowHint и Hint. В Hint писался текст подсказки, а ShowHint мог её отключить. А более прокачанные даже знали, что можно сделать расширенный вариант подсказки. Если написать "Нажми меня|Кнопка просит, чтобы вы её нажали", то левая часть всплывёт, а правая будет передана сообщением, которое можно перехватить и вывести, например, в Status Bar.

Но пришла новая эра, VCL ушёл в историю, а у нас теперь, к примеру, кроссплатформенный JavaFX. И никакого свойства Hint у его компонентов нет. И status bar-а среди компонентов тоже нет. Такие дела.

Что же делать?

Для подсказок всплывающих есть невидимый для SceneBuilder компонент Tooltip, который отвечает за всплывающие подсказки. Если его создать, а потом привязать через setTooltip, то при наведении курсора мы и правда увидим подсказку (на чёрном фоне, но так надо).

Но при этом свойство Tooltip (и соответствующие методы) есть только у наследников класса javafx.scene.control.Control. А все панели и прочие области наследуются от javafx.scene.layout.Region. И никаких подсказок на них всплывать не может.

К тому же, разумеется, мы по прежнему не приблизились к StatusBar-у. Придётся писать всё своими руками.

Сначала класс привязки, который хранит ссылку на элемент управления и текст, который к нему относится:

ATooltipHintItem.java
import javafx.scene.Node;

/**
 * Базовый класс для элемента подсказки. Связывает вместе компонент и
 * текст подсказки.
 *
 * Created by a.teut on 18.03.15.
 */
public abstract class ATooltipHintItem<N> {
    private N attachedNode;
    protected void setAttachedNode(N node) {
        attachedNode = node;
    }
    public N getAttachedNode() {
        return attachedNode;
    }

    private String statusBarHint;
    protected void setStatusBarHint(String hint){
        statusBarHint = hint;
    }
    public String getStatusBarHint(){
        return statusBarHint;
    }

    private ITooltipHintController tooltipHintController;
    public ITooltipHintController getTooltipHintController() {
        return tooltipHintController;
    }

    public void showStatusBarHint(){
        tooltipHintController.setStatusBarText(statusBarHint);
    }

    public ATooltipHintItem(N attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this.attachedNode = attachedNode;
        this.tooltipHintController = tooltipHintController;

        if(statusBarHint != null && statusBarHint != ""){
            initStatusBar();
            this.setStatusBarHint(statusBarHint);
        }
    }

    private void initStatusBar() {
        getAttachedNode().setOnMouseEntered(observableValue -> {
            this.showStatusBarHint();
        });

        getAttachedNode().setOnMouseExited(observableValue -> {
            getTooltipHintController().setDefaultStatusBarText();
        });
    }
}

Теперь реализация для Region:

TooltipHintRegionItem.java
import javafx.scene.layout.Region;

/**
 * Реализация для компонент, унаследованных от Region. Всплывающие подсказки
 * прицепить нельзя.
 *
 * Created by a.teut on 18.03.15.
 */
public final class TooltipHintRegionItem extends ATooltipHintItem<Region>{
    public TooltipHintRegionItem(Region attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        super(attachedNode, tooltipHintController, statusBarHint);
    }
}

А у Control могут быть Tooltip-ы:


TooltipHintRegionItem.java

import javafx.scene.control.Control;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

/**
 * Реализация для компонент, унаследованных от Control. Можно делать и всплывающие подсказки.
 *
 * Created by a.teut on 18.03.15.
 */

/**
 * Реализация для компонент, унаследованных от Control. Можно делать и всплывающие подсказки.
 *
 * Created by a.teut on 18.03.15.
 */
public final class TooltipHintControlItem extends ATooltipHintItem<Control> {
    private Tooltip tooltip;
    public Tooltip getTooltip() {
        return tooltip;
    }

    private String tooltipHint;
    public TooltipHintControlItem setTooltipHint(String hint){
        tooltipHint = hint;
        if(tooltip == null) {
            initTooltip();
        }
        tooltip.setText(hint);
        return this;
    }
    public String getTooltipHint(){
        return tooltipHint;
    }

    private Image tooltipImage;
    public TooltipHintControlItem setTooltipImage(Image image){
        tooltipImage = image;
        tooltip.setGraphic((image != null) ? new ImageView(image) : null);
        return this;
    }
    public Image getTooltipImage(){
        return tooltipImage;
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint, Image imageHint) {
        super(attachedNode, tooltipHintController, statusBarHint);
        if(tooltipHint != null && tooltipHint != ""){
            initTooltip();
        }
        setTooltipHint(tooltipHint);

        if(imageHint == null) {
            setTooltipImage(imageHint);
        }
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint) {
        this(attachedNode, tooltipHintController, statusBarHint, tooltipHint, null);
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this(attachedNode, tooltipHintController, statusBarHint, null, null);
    }

    private void initTooltip() {
        tooltip = new Tooltip();
        getAttachedNode().setTooltip(tooltip);
    }
    public Image getTooltipImage(){
        return tooltipImage;
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint) {
        super(attachedNode, tooltipHintController, statusBarHint);
        if(tooltipHint != null && tooltipHint != ""){
            initTooltip();
        }
        setTooltipHint(tooltipHint);
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this(attachedNode, tooltipHintController, statusBarHint, null);
    }

    private void initTooltip() {
        tooltip = new Tooltip();
        getAttachedNode().setTooltip(tooltip);
    }
}

Теперь распишем интерфейс, который их вызывает:

ITooltipHintController.java

/**
 * Функции контроллера, которые вызывают сами элементы привязок.
 *
 * Created by a.teut on 18.03.15.
 */
public interface ITooltipHintController {
    void setStatusBarText(String text);
    String getStatusBarText();
    void setDefaultStatusBarText();
}

И, наконец, контроллер:


TooltipHintController.java

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Labeled;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * Контроллер подсказок. Хранит привязки подсказок к компонентом и, если нужно,
 * всплывающие подсказки.
 *
 * Как правило, один на всё приложение.
 *
 * Created by a.teut on 18.03.15.
 */
public final class TooltipHintController implements ITooltipHintController {
    private final String DefaultStatusBarText = "";
    private final Labeled statusBarControl;
    private final ObservableList<ATooltipHintItem> tooltipHintItems;

    private boolean isStatusBarLocked = false;
    public boolean getIsStatusBarLocked() {
        return isStatusBarLocked;
    }
    public void setIsStatusBarLocked(boolean isStatusBarLocked) {
        this.isStatusBarLocked = isStatusBarLocked;
    }

    public Labeled getStatusBarControl() {
        return this.statusBarControl;
    }

    public void setStatusBarTextForce(String text) {
        statusBarControl.setText(text);
    }
    @Override
    public void setStatusBarText(String text) {
        if(!isStatusBarLocked){
            setStatusBarTextForce(text);
        }
    }
    @Override
    public String getStatusBarText() {
        return statusBarControl.getText();
    }
    @Override
    public void setDefaultStatusBarText(){
        setStatusBarTextForce(DefaultStatusBarText);
    }

    //тут есть дублирование кода, но пока ничего серьёзного
    public void addTooltipHint(Region region, String statusBarHint){
        // Tooltip нас не интересует - у регионов в JavaFX не бывает всплывающих подсказок
        ATooltipHintItem tooltipHintItem = findTooltipHint(region);
        if(tooltipHintItem == null) {
            tooltipHintItem = new TooltipHintRegionItem(region, this, statusBarHint);
            tooltipHintItems.add(tooltipHintItem);
        } else {
            TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem;
            if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
                tooltipHintControlItem.setStatusBarHint(statusBarHint);
        }
    }

    public void addTooltipHint(Control control, String statusBarHint){
        addTooltipHint(control, statusBarHint, null, null);
    }


    public void addTooltipHint(Control control, String statusBarHint, String tooltipHint){
        addTooltipHint(control, statusBarHint, tooltipHint, null);
    }

    public void addTooltipHint(Control control, String statusBarHint, String tooltipHint, Image image){
        ATooltipHintItem tooltipHintItem = findTooltipHint(control);
        if(tooltipHintItem == null) {
            tooltipHintItem = new TooltipHintControlItem(control, this, statusBarHint, tooltipHint, image);
            tooltipHintItems.add(tooltipHintItem);
        } else {
            TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem;
            if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
                tooltipHintControlItem.setStatusBarHint(statusBarHint);

            if(tooltipHint != null && tooltipHintControlItem.getTooltipHint() == null)
                tooltipHintControlItem.setTooltipHint(tooltipHint);

            if(image != null && tooltipHintControlItem.getTooltipImage() == null)
                tooltipHintControlItem.setTooltipImage(image);
        }
    }

    public void removeTooltipHint(Node control){
        ATooltipHintItem tooltipHintItem = null;
        Iterator<ATooltipHintItem> iteratorTooltipHintItems = tooltipHintItems.iterator();
        while(iteratorTooltipHintItems.hasNext()){
            tooltipHintItem = iteratorTooltipHintItems.next();
            if(tooltipHintItem.getAttachedNode() == control){
                tooltipHintItems.remove(tooltipHintItem);
                break;
            }
        }
    }

    public ATooltipHintItem findTooltipHint(Node control){
        for(ATooltipHintItem tooltipHintItem : tooltipHintItems)
            if(tooltipHintItem.getAttachedNode() == control)
                return tooltipHintItem;
        return null;
    }

    /**
     * При создании нужно привязать контроллер к компоненту, который будет
     * показывать подсказки.
     *
     * @param statusBarControl Компонент для подсказок
     */

    public TooltipHintController(Labeled statusBarControl){
        if(statusBarControl == null) {
            throw new NullPointerException("Unable to create TooltipHintController. statusBarControl can't be null.");
        }

        this.statusBarControl = statusBarControl;
        tooltipHintItems = FXCollections.observableList(new ArrayList<>());
    }

    private static TooltipHintController mainInstance;
    public static TooltipHintController getMainInstance() {
        if(mainInstance == null){
            throw new NullPointerException("Main instance of TooltipHintController isn't initialised.");
        }

        return mainInstance;
    }
    public static void setMainInstance(TooltipHintController tooltipHintController) {
        mainInstance = tooltipHintController;
    }
}

Чтобы заработало, надо инициализировать контроллер тем самым элементом, в который надо отображать подсказки:

TooltipHintController.setMainInstance(new TooltipHintController(labelStatusBar));

И привязываем к компонентам:

TooltipHintController.getMainInstance().addTooltipHint(buttonStart, "Нажми меня", "Нажми эту кнопку");
TooltipHintController.getMainInstance().addTooltipHint(paneButtons, "Здесь нажимают");


вторник, 17 марта 2015 г.

undefined в JavaScript и receiving GET params of url - 3

В чём разница между typeof boo == "undefined" и boo === undefined в JavaScript?

Они делают почти одно и то же. И у boo === undefined есть плюсы и минусы:

Плюсы

+ И в "undefined", и в undefined можно допустить опечатку. Но во втором случае браузер немедленно заругается.
+ Такое сравнение подходит и для "пустых" параметров функции, и для несуществующих свойств объектов.

Минусы

- Если переменная boo просто нигде не объявлена, то будет ошибка. А вот typeof boo == "undefined" сработает.

Аналогично boo === undefined срабатывает и || для неопределённого свойства или аргумента функции.

А это значит, что старый код можно улучшить ещё:

$.extend({
 getUrlVars : function(){
  var vars = {};
  var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
  var i = hashes.length;
  while(i--) {
   var hash = hashes[i].split('=');
   vars[hash[0]] = hash[1];
    }
  return vars;
 },
 urlVars : null,
 getUrlVar : function(key) {
  if(!key) return null;
  if(!this.urlVars) this.urlVars = this.urlVars();
  return (this.urlVars[key] !== undefined) ? this.urlVars[key] : null;
 },
 toUrlVar : function(url, params) {
  var url_val = url || window.location.href.slice(0, window.location.href.indexOf('?'));
  var dict_params = params || this.urlVars;
  if(dict_params === undefined || !dict_params)
   return url_val;
  var params_str = "";
  for(var param_key in dict_params) {
   var param = param_key + "=" + dict_params[param_key];
   params_str += (!params_str) ? param : ("&" + param);
  }
  return params_str ? (url_val + "?" + params_str) : url_val;
 }
});

вторник, 10 марта 2015 г.

FontFamily и FontName

Как получить список шрифтов в JavaFX? Ведь функций там две - Font.getFamilies() и Font.getFontNames()

И в Windows, и в Unix-ах, и на Mac-ах рисуют отдельные шрифты для основного, полужирного и курсивного начертания. У некоторых шрифтов есть только основное. Начертания для одного шрифта группируются в FontFamily, а отдельные шрифты - FontName.