Consumer を Inject

Google guice で、List をインジェクションする時によく使うのが、com.google.inject.TypeLiteral 

Java8 lambda の Consumer をインジェクションすることをやれば、条件分岐によるメソッド実行を
一括管理の記述ができると思った。

まず、分岐ではなく、ただの Consumer をインジェクションする例。

Injector injector =
Guice.createInjector(new AbstractModule(){
   @Override
   protected void configure(){
      binder().bind(new TypeLiteral<Consumer<?>>(){})
      .toInstance(e->{
         System.out.println(e);
      });
   }
});

Injector で生成されるクラスのメソッドで、Consumer を実行

@Inject Consumer<String> consumer;
public void exec(){
   consumer.accept("accept! on Foo::exec()");
}


AbstractModule の configure の中で if 文を書くか、if 文による ModuleインスタンスGuice.createInjector
に渡す方法を考えるかもしれないが、
あまり整理できると思えない。

今のところ、Mapを使用することぐらいしか思いつかない。

Map<String, Consumer<String>> map = new HashMap<>();
map.put("1", e->{
   System.out.println("Test 1 : " + e);
});
map.put("2", e->{
   System.out.println("Test 2 : " + e);
});

String key = "2";

Injector injector = Guice.createInjector(new AbstractModule(){
   @Override
   protected void configure(){
      binder().bind(new TypeLiteral<Consumer<String>>(){}).toInstance(map.get(key));
   }
});

条件毎の実装クラス定義を作らなくて済むだけでもありがたいのかも知れない。

NULLインジェクション

いつも、Google guice 使用を書いていて思うのが、NULLインジェクションが発生するとき、
@Nullable をつけて、Module の configure で、toProvider を書かなくてはならないのが
煩わしい。

   binder().bind(Integer.class).toProvider(Providers.of(null));
   @Inject @Nullable private Integer i;

com.google.inject.Inject のオプション @Inject(optional=true) だけではダメで、
@Nullable が必要なのだ。
明示的であり、NULL 発生で落ちるのが解るから、toProviderと@Nullable を使うのだと言えば、それで納得なんだけど。。。

結局、optional がない javax.inject.Inject の方をいつも使っている。

JavaFX CSS で @CHARSET記述は不要である

JavaFXCSS を書いていると、ついHTMLのCSS を書いていた時の癖で @CHARSET "UTF-8"; を書いてしまう。

でも、これを書いてしまうと、com.sun.javafx.css.parser.CSSParser は、CSS parseエラーになり思いとおりの
スタイルが適用されなくなる。

docs.oracle.com

@Rule の章をよく読むべし。

JavaFX の ウィンドウフレームを無くす

JavaFX の ウィンドウフレームを無くしたい場合、Application の start(Stage) で渡される javafx.stage.Stage を

javafx.stage.StageStyle の StageStyle.TRANSPARENT を指定すれば、ウィンドウフレーム無しの透過
StageStyle.UNDECORATED なら、ウィンドウフレーム無しで背景はし白色で塗りつぶしになる。

public class MainApp extends Application{
   /* @see javafx.application.Application#start(javafx.stage.Stage) */
   @Override
   public void start(Stage primaryStage) throws Exception{
      primaryStage.initStyle(StageStyle.TRANSPARENT);

JavaFX で、外部フォント

JavaFX で、外部フォントを指定する場合の書き方

CSS に書く場合 @font-faceで fontファミリ-ttfを定義、→ -fx-font-family で指定

@font-face{
    font-family: meiryo;
  /** ttf ファイル参照 **/
    src: url("file://c:/home/test/meiryo.ttf");
}

Pane{
   -fx-font-family: meiryo;
}


ttc ファイルがあって ttf が存在しない場合、

UniteTTC というツールを使えば、ttc を分割して ttf を抽出できる。

java.time.format.FormatStyle を確認

java.time.format.FormatStyle で Locale に従った日付表記を試してみる。

    LocalDate.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(new Locale("ja", "JP")));
    LocalDate.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(new Locale("ja", "JP")));
    LocalDate.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(new Locale("ja", "JP")));
    LocalDate.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(new Locale("ja", "JP")));

4通りある。
これを、 Locale( 言語、国 ) を指定して標準出力してみると、、

--- FormatStyle.FULL ---
ja JP → 2017年3月8日
en US → Wednesday, March 8, 2017
en UK → Wednesday, March 8, 2017
zh CN → 2017年3月8日 星期三
zh TW → 2017年3月8日 星期三
ko KR → 2017년 3월 8일 수요일
ru RU → 8 марта 2017 г.
--- FormatStyle.LONG ---
ja JP → 2017/03/08
en US → March 8, 2017
en UK → March 8, 2017
zh CN → 2017年3月8日
zh TW → 2017年3月8日
ko KR → 2017년 3월 8일 (수)
ru RU → 8 марта 2017 г.
--- FormatStyle.MEDIUM ---
ja JP → 2017/03/08
en US → Mar 8, 2017
en UK → Mar 8, 2017
zh CN → 2017-3-8
zh TW → 2017/3/8
ko KR → 2017. 3. 8
ru RU → 08.03.2017
--- FormatStyle.SHORT ---
ja JP → 17/03/08
en US → 3/8/17
en UK → 3/8/17
zh CN → 17-3-8
zh TW → 2017/3/8
ko KR → 17. 3. 8
ru RU → 08.03.17

en US(米国)も en UK も 変わらない。

FormatStyle.MEDIUM と FormatStyle.SHORT → 中国と 微妙な扱いの台湾、違う!!
(この差にあまり触れたくない)

JavaFX のイニシャライズ処理を Google guice で整理

前回に引き続き、JavaFX のイニシャライズ処理を Google guice で整理してみる。
起動のメインクラス・・・共通イニシャライズとして、guice の Module を配置、
最初に起動されるコントローラ・・・次画面のイニシャライズとして Module を配置、
  →画面遷移のパラメータ転送に相当する。

メインクラス

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.name.Names;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Callback;

public class MainApp extends Application{
   static String WINDOW_TITLE  = "fxtest";
   private Stage stage;

   public static void main(String[] args){
      launch(args);
   }
   private Module commonModule = new AbstractModule(){
      @Override
      protected void configure(){
         binder().bind(String.class).annotatedWith(Names.named("TITLE")).toInstance("fxtest");
      }
   };
   /* @see javafx.application.Application#start(javafx.stage.Stage) */
   @Override
   public void start(Stage primaryStage) throws Exception{
      stage = primaryStage;
      setPage(StartPage.class, "start.fxml");
   }

   public void setPage(final Class<?> cls, String fxml, Module...modules) throws Exception{
      Injector injector = modules.length==0 ? Guice.createInjector(commonModule)
             : Guice.createInjector(commonModule).createChildInjector(modules);
      FXMLLoader loader = new FXMLLoader(cls.getResource(fxml));
      loader.setControllerFactory(p->injector.getInstance(cls));
      Scene scene = new Scene((Parent)loader.load());
      BasePage page = (BasePage)loader.getController();
      page.setApp(this);
      stage.setTitle(WINDOW_TITLE);
      stage.setScene(scene);
      stage.show();
   }
}

javafx.application.Application の start で、画面遷移としての共通メソッド setPage を呼ぶ。
メインクラスで実行する場合や、Module を指定しない画面遷移を判定して→Module...modules の length  
Guice の createInjector 、さらに、createChildInjector で生成する Injector で
FXMLLoader のファクトリを用意する。
このファクトリの指定は、ラムダで書ける。

loader.setControllerFactory(new Callback<Class<?>, Object>(){
   @Override
   public Object call(Class<?> type){
      return injector.getInstance(cls);
   }
});

と書くところを、javafx.util.Callback が、@FunctionalInterfaceであるので、

loader.setControllerFactory(p->injector.getInstance(cls));

と書ける。



start.fxml の コントローラ

import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
import javafx.fxml.FXML;

public class StartPage extends BasePage{
   @FXML
   @Override
   public void initialize(){
      super.initialize();
   }
   public void nextPage(){
      setPage(AlphaPage.class, "alpha.fxml", new AbstractModule() {
         @Override
         protected void configure() {
            binder().bind(String.class).annotatedWith(Names.named("STATUS")).toInstance("あいう");
            binder().bind(String.class).annotatedWith(Names.named("BUTTON")).toInstance("送信");
         }
      });
   }
}

共通イニシャライズとメインクラスインスタンスを参照するための BasePage を継承している。
次画面 alpha.fxml を

import javax.inject.Inject;
import javax.inject.Named;
import com.google.inject.Module;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public abstract class BasePage{
   @Inject @Named("TITLE") private String titlename;
   private MainApp application;

   @FXML private Label title;

   @FXML
   public void initialize(){
      title.setText(titlename);
   }
   public void setApp(MainApp application){
      this.application = application;
   }
   public void setPage(Class<? extends BasePage> cls, String fxml, Module...modules){
      try{
         application.setPage(cls, fxml, modules);
      }catch(Exception e){
         e.printStackTrace();
         throw new RuntimeException(e);
      }
   }
}

共通イニシャライズ  @FXML public void initialize() が存在する。
画面遷移の setPage メソッドが存在する。

import javax.inject.Inject;
import javax.inject.Named;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class AlphaPage extends BasePage{
   @Inject @Named("STATUS") String statusValue;
   @Inject @Named("BUTTON") String buttonName;

   @FXML private Label status;
   @FXML private Button btn1;

   @FXML
   @Override
   public void initialize(){
      super.initialize();
      status.setText(statusValue);
      btn1.setText(buttonName);
   }
   public void backPage(){
      setPage(StartPage.class, "start.fxml");
   }
}

initialize() をオーバライドして、共通とこの画面専用のイニシャライズを行う。
backPage() メソッドで元の画面を呼び出すように設定する。