Java9 Jigsaw モジュール参照側は結局すべてモジュールを引っ張れないとならない?!

Java9 Jigsaw モジュール使用プログラムの実行 - Oboe吹きプログラマの黙示録

に書いたが、Elipse Maven で、WTP 書いてコンパイルができないのは、

依存する JAR が export を宣言した module-info を用意したJAR を使ってないからだ。

でも、開発するWebアプリで使用する JARなどは、要件によって多岐にわたる。

全て用意されていればそのバージョンを使うことになる。

ログ出力で有名な SL4J は新しくないと module-info が入っていない。

  slf4j-api  1.7.xx ではだめで、1.8.0-beta0 ならmodule-info が入っている。

ひどいのは、javax.servlet-api でまだ入っておらず、Mavenセントラルリポジトリ

さがしても、module-info が入っている JAR が見つからない。

Webアプリなどは、javax.servlet-api を使う場面は結構あるはずだ。

これでは、片手落ちである。。

Java9 Jigsaw が浸透するのは、かなり時間かかりそうだ。

Java7, 8 で Jigsaw が登場するのが見送られて、あれだけ時間かかって、、

あれだけ騒いで、ようやくJava9 で出てもこの状況はあんまりです。

マップの比較、MapDiff

1年近く前に、リストの比較を書いた。
リストの比較、ListDiff - Oboe吹きプログラマの黙示録

それなら2つのマップの比較も普遍的なものが書けるであろう。

import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;

/**
 * MapDiff
 */
public final class MapDiff<K, V>{
   private BiFunction<V, V, Boolean> matchFunction;
   private Consumer<K> leftonly;
   private Consumer<K> rightonly;
   private Consumer<K> diffConsumer;
   private Consumer<K> matchConsumer;

   private MapDiff(BiFunction<V, V, Boolean> matchFunction){
      this.matchFunction = matchFunction;
   }
   /**
    * MapDiffインスタンス生成.
    * @param matchFunction 比較する2つのMapのValue要素を等しいと判定する BiFunction で Boolean(true=等しい) を返す。
    * @return ListDiff<T>{
    */
   public static <K, V> MapDiff<K, V> of(BiFunction<V, V, Boolean> matchFunction){
       return new MapDiff<>(matchFunction);
   }
   /**
    * 左側(1番目指定)Mapだけに所属するkeyの Consumerを登録.
    * @param leftonly Consumer<K>
    */
   public void leftOnly(Consumer<K> leftonly){
      this.leftonly = leftonly;
   }
   /**
    * 右側(2番目指定)Mapだけに所属するkeyの Consumerを登録.
    * @param rightonly Consumer<K>
    */
   public void rightOnly(Consumer<K> rightonly){
      this.rightonly = rightonly;
   }
   /**
    * MapのValue値が異なるkeyの Consumerを登録.
    * @param biConsumer Consumer<K>
    */
   public void unmatch(Consumer<K> diffConsumer){
      this.diffConsumer = diffConsumer;
   }
   /**
    * MapのKeyとValue値が同じkeyの Consumerを登録.
    * @param biConsumer Consumer<K>
    */
   public void match(Consumer<K> matchConsumer){
      this.matchConsumer = matchConsumer;
   }
   /**
    * Mapの比較.
    * @param leftMap
    * @param rightMap
    */
   public void diff(Map<K, V> leftMap, Map<K, V> rightMap){
      leftMap.entrySet().stream().forEach(e->{
         K k = e.getKey();
         if (rightMap.containsKey(k)){
            if (matchFunction.apply(e.getValue(), rightMap.get(k))){
               if (matchConsumer != null) matchConsumer.accept(k);
            }else{
               if (diffConsumer != null) diffConsumer.accept(k);
            }
         }else{
            if (leftonly != null) leftonly.accept(k);
         }
      });
      if (rightonly != null){
         rightMap.keySet().forEach(key->{
            if (!leftMap.containsKey(key)) rightonly.accept(key);
         });
      }
   }
   /**
    * Mapの比較.
    * @param map1 マップ1
    * @param map2 マップ2
    * @param match 値が一致するかを返す BiFunction<V, V, Boolean>
    * @param leftOnly マップ1に対してマップ2で削除されたキーのConsumer<K>
    * @param rightOnly マップ1に対してマップ2で追加されたキーのConsumer<K>
    * @param mc マップ1に対してマップ2で変更されたキーのConsumer<K>
    */
   public static <K, V> void diffMap(Map<K, V> map1, Map<K, V> map2, BiFunction<V, V, Boolean> match
   , Consumer<K> leftOnly, Consumer<K> rightOnly, Consumer<K> mc){
      map1.entrySet().stream().forEach(e->{
         K k = e.getKey();
         if (map2.containsKey(k)){
            if (!match.apply(e.getValue(), map2.get(k))) mc.accept(k);;
         }else{
            leftOnly.accept(k);
         }
      });
      map2.keySet().forEach(key->{
         if (!map1.containsKey(key)) rightOnly.accept(key);
      });
   }
}

だいたいこんなかんじで使う

MapDiff<Integer, String> m = MapDiff.of((v1, v2)->v1.equals(v2));
m.leftOnly(k->leftMap.put(k, map1.get(k)));
m.rightOnly(k->rightMap.put(k, map2.get(k)));
m.unmatch(k->dMap.put(k, map1.get(k) + "→" + map2.get(k)));

m.diff(map1, map2);

Java9 Jigsaw モジュール使用プログラムの実行

モジュール参照のJavaプログラムを実行するにはモジュール exportしている JARの置かれたパスを指定して
読み込めるように指定オプションと参照側のクラスを指定しないとならない。

java  -m {モジュール検索先PATH}  -p {実行するモジュール名}/クラス名

{モジュール検索先PATH}複数の場合、Windowsでは、";" セミコロン区切り、Linux なら ":"コロン区切り
Eclipse で実行する時は、実行構成の VM引数にこれを書かなくてはならず、めんどくさい。

実際、Maven プロジェクトで実行テストするケースが多いので、参照実行側の module-info を見つけられるように、
モジュール検索先PATH には、target/classes も付けないとならない。

-p target/classes;C:\Users\yipuran\.m2\repository\org\wa\model\models\1.0 -m org.wa.note/org.wa.note.NodeTest

これはPCのローカルリポジトリに models-1.0.jar という参照されるモジュールJARが存在して
org.wa.note パッケージで作成した module-info.class が、models-1.0.jarの モジュールに依存している場合だ。

手数がかかる。。

java -jar で実行する為に1つのJAR に纏めてしまえば、こんな手間は要らないが、Jigsawの目的である
依存によるプログラムの肥大化を防ぐということには反することになる。
でも、プログラム開発時、module-info 定義で、見せたくない~直接使用させたくない public を制限抑止するのには
モジュールの使用は有効だ。

pom.xml の記述

<build>

  <plugins>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-shade-plugin</artifactId>
         <version>1.2.1</version>
         <executions>
            <execution>
               <phase>package</phase>
               <goals>
                  <goal>shade</goal>
               </goals>
               <configuration>
                  <finalName>note</finalName>
                  <transformers>
                     <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.wa.note.NodeTest</mainClass>
                     </transformer>
                     </transformers>
                  </configuration>
            </execution>
         </executions>
      </plugin>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <version>3.7.0</version>
         <configuration>
            <descriptorRefs>
               <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
            <source>9</source>
            <target>9</target>
         </configuration>
      </plugin>
   </plugins>

</build>

これで、

java -jar note.jar

で実行できる。

しかし、Webアプリとしてはどうだろうか?
Eclipse WTP で用意して実行してみるがダメだった。

参照側ーWebアプリ側で、module-info.java を書かなければ、参照するものはきちんと参照もできて
module-info export 記述されてないパッケージへは使用できないようにコンパイルエラーにはなる。
半分目的は達成されているのだが、参照側で module-info を Webアプリではどこにどう書けばよいのか?
まだ、明確に理解していない。

Java9 待望の Optionalのメソッド

(1)Java8 まで、Optional の、肯定と否定を、以前書いたように実行していたが、
Optional のifPresent の否定形 - Oboe吹きプログラマの黙示録

ifPresentOrElse で書けるようになった。さほど文が短くなるわけではないけど、型を書かなくてもいい。

Optional.ofNullable(object).ifPresentOrElse(e->{
   // NULLでない時の処理、Consumer
}, ()->{
   // NULL の時の Runnableメソッド
});

(2)Optional に追加された public Stream stream​()
JavaDoc のヒントを見て、気がついた。NULL を含むリスト、コレクションから、Stream の filter を使わずに
NULLをスキップした Stream を作れるではないですか。

サンプル

Stream<Optional<String>> nullableStream
= Arrays.asList("a", "b", null, "c").stream().<Optional<String>>map(Optional::ofNullable);

Stream<String> stringstream = nullableStream.flatMap(Optional::stream);

この stringstream を forEach で確かめます。

stringstream.forEach(System.out::println);

=== 結果は、、====
a
b
c

Java9 Jigsaw と Maven

Java9 も一般公開リリースされて日数も経ったが、まだ仕事の現場で使用されたというのは聞こえてこない。
Jigsaw モジュール定義で公開したいものだけを module-info.java に書いて、参照側でモジュール定義名を参照限定する機能、
ソース管理と伴に隠蔽化したいものを隠蔽できて魅力的だと思っていた。
いざ、Maven で使用するとどうも、安定しない。
→ そんなことない。pom.xml で descriptorRef jar-with-dependencies の記述が不足していた。

例)fooService というモジュールを用意

module fooService{
   exports org.app.foo;
   exports org.app.salon;
}

参照側の pom.xml に書くプラグイン、compilerArgs で、 --add-modules として追加するものを指定する。

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.7.0</version>
   <configuration>
      <descriptorRefs>
         <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs>
      <source>9</source>
      <target>9</target>
   </configuration>
</plugin>

参照側の module-info.javaEclipseではエラーになる。

module org.app.web{
   requires fooService;
}

参照側の module-info.java を作らないことにすると、
参照されるモジュールの JAR の中で、exports を書いてないものは参照不可=アクセス不可になって、
目的は果たして良いのだけど、参照側で、module-info.java を記述すると、Maven がどうも良く動かない。

Maven でうまく動かないとなると、ライブラリ管理をMaven に頼るのもつらくなってくる。
Eclipse プロジェクト参照や外部JARの読み込みを併用するか。。。

FooLib プロジェクトに module を用意する。

package org.foo を公開モジュールとして、

module foo{
   exports org.foo;
}

を、FooLib/src に置く。

<classpathentry combineaccessrules="false" kind="src" path="/FooLib">
   <attributes>
      <attribute name="module" value="true"/>
   </attributes>
</classpathentry>

使用側で、

module sample{
   requires foo;
}

oboe2uran.hatenablog.com

Wicket が自動的に組み込む jQuery のバージョンを変更

Wicket が自動的に組み込む jQuery のバージョンを変更したい時は、
WebApplocation の init() メソッドで以下のように実行する。

getJavaScriptLibrarySettings()
.setJQueryReference(new UrlResourceReference(Url.parse("js/jquery-2.1.4.min.js")));

これは、URLで参照するWebコンテキスト上のPATH、"js" の下に置いた jQuery に切り替える書き方。