われプログラミングする、ゆえにバグあり

私だって価値を創造してみたいのです

メソッド参照とジェネリクスの可変長引数があやしい

間違いや補足など、指摘していただければありがたいです。

今回の環境

Product Version: NetBeans IDE 8.0 (Build 201403101706)
Java: 1.8.0_05; Java HotSpot(TM) 64-Bit Server VM 25.5-b02
Runtime: Java(TM) SE Runtime Environment 1.8.0_05-b13
System: Mac OS X version 10.9.2 running on x86_64; UTF-8; ja_JP (nb)

 

java8への期待は過剰だった気がしてきているきょうこのごろです。

メソッド参照とジェネリクスの可変長引数を組み合わせたソースコードで、ビルド時にエラーが出て困ることがありました。
そのメモです。

ビルドでエラーが出て困ったコード

IDEはNetBeans8(日本語)を使っていましたが、ソースコードの編集中はNetBeansは一切警告等をだしてくれませんでした。

public class ExSet<T> {

    Collector<T, ?, Collection<T>> collector = Collectors.toCollection(ExSet::getCollection);

    public static <T> Collection<T> getCollection(T... es) {
        return Arrays.asList(es);
    }
}

 

NetBeansのコンソールに表示されたエラーメッセージ

コンパイラで例外が発生しました(1.8.0_05)。Bug Paradeに同じバグが登録されていないことをご確認の上、Java Developer Connection(http://java.sun.com/webapps/bugreport)でバグの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。
java.lang.AssertionError: arraycode T
at com.sun.tools.javac.jvm.Code.arraycode(Code.java:302)
at com.sun.tools.javac.jvm.Gen.makeNewArray(Gen.java:2028)
at com.sun.tools.javac.jvm.Gen.visitNewArray(Gen.java:2001)
at com.sun.tools.javac.tree.JCTree$JCNewArray.accept(JCTree.java:1556)
at com.sun.tools.javac.jvm.Gen.genExpr(Gen.java:947)
at com.sun.tools.javac.jvm.Gen.genArgs(Gen.java:966)
at com.sun.tools.javac.jvm.Gen.visitApply(Gen.java:1905)
at com.sun.tools.javac.tree.JCTree$JCMethodInvocation.accept(JCTree.java:1459)
at com.sun.tools.javac.jvm.Gen.genExpr(Gen.java:947)
at com.sun.tools.javac.jvm.Gen.visitReturn(Gen.java:1863)
at com.sun.tools.javac.tree.JCTree$JCReturn.accept(JCTree.java:1378)
at com.sun.tools.javac.jvm.Gen.genDef(Gen.java:737)
at com.sun.tools.javac.jvm.Gen.genStat(Gen.java:772)
at com.sun.tools.javac.jvm.Gen.genStat(Gen.java:758)
at com.sun.tools.javac.jvm.Gen.genStats(Gen.java:809)
at com.sun.tools.javac.jvm.Gen.visitBlock(Gen.java:1158)
at com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:903)
at com.sun.tools.javac.jvm.Gen.genDef(Gen.java:737)
at com.sun.tools.javac.jvm.Gen.genStat(Gen.java:772)
at com.sun.tools.javac.jvm.Gen.genMethod(Gen.java:1031)
at com.sun.tools.javac.jvm.Gen.visitMethodDef(Gen.java:994)
at com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:772)
at com.sun.tools.javac.jvm.Gen.genDef(Gen.java:737)
at com.sun.tools.javac.jvm.Gen.genClass(Gen.java:2526)
at com.sun.tools.javac.main.JavaCompiler.genCode(JavaCompiler.java:748)
at com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1570)
at com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1534)
at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:904)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:863)
at com.sun.tools.javac.main.Main.compile(Main.java:523)
at com.sun.tools.javac.main.Main.compile(Main.java:381)
at com.sun.tools.javac.main.Main.compile(Main.java:370)
at com.sun.tools.javac.main.Main.compile(Main.java:361)
at com.sun.tools.javac.Main.compile(Main.java:56)
at sun.reflect.GeneratedMethodAccessor458.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.apache.tools.ant.taskdefs.compilers.Javac13.execute(Javac13.java:56)
at org.apache.tools.ant.taskdefs.Javac.compile(Javac.java:1153)
at org.apache.tools.ant.taskdefs.Javac.execute(Javac.java:930)
at org.netbeans.modules.java.source.ant.JavacTask.execute(JavacTask.java:145)
at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:292)
at sun.reflect.GeneratedMethodAccessor342.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
at org.apache.tools.ant.Task.perform(Task.java:348)
at org.apache.tools.ant.taskdefs.Sequential.execute(Sequential.java:68)
at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:292)
at sun.reflect.GeneratedMethodAccessor342.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
at org.apache.tools.ant.Task.perform(Task.java:348)
at org.apache.tools.ant.taskdefs.MacroInstance.execute(MacroInstance.java:396)
at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:292)
at sun.reflect.GeneratedMethodAccessor342.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
at org.apache.tools.ant.Task.perform(Task.java:348)
at org.apache.tools.ant.Target.execute(Target.java:435)
at org.apache.tools.ant.Target.performTasks(Target.java:456)
at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1393)
at org.apache.tools.ant.Project.executeTarget(Project.java:1364)
at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
at org.apache.tools.ant.Project.executeTargets(Project.java:1248)
at org.apache.tools.ant.module.bridge.impl.BridgeImpl.run(BridgeImpl.java:286)
at org.apache.tools.ant.module.run.TargetExecutor.run(TargetExecutor.java:555)
at org.netbeans.core.execution.RunClassThread.run(RunClassThread.java:153)

 
ググってみると、似たような情報がありました。Java 8 compiler error - how to get more information? - Stack Overflow
 

可変長引数を使っているあたりに原因がありそうなので、そのあたりのソースコードを書き換えて回避してみます。
 

回避策1

メソッド参照を諦める

Collector<T, ?, Collection<T>> collector = Collectors.toCollection(() -> {
    return ExSet.getCollection();
});

public static <T> Collection<T> getCollection(T... es) {
    return Arrays.asList(es);
}

これはシンタックスシュガーを無くしただけです。

 

回避策2

ジェネリクスを諦める

Collector<T, ?, Collection<T>> collector = Collectors.toCollection(() -> {
    return ExSet.getCollection();
});

public static <T> Collection<T> getCollection(Object... es) {
    // return Arrays.asList(es); // もちろん無理!!
    return Arrays.asList();
}

これではわけがわかりません。

 

回避策3

Collectors.toCollectionが引数にとるのはSupplierなので、引数なしでgetCollection()が呼ばれます。
なので、この場合は可変長引数はとらなくてもよいわけです。

Collector<T, ?, Collection<T>> collector = Collectors.toCollection(ExSet::getCollection);

public static <T> Collection<T> getCollection() {
    return Arrays.asList();
}

個人的には却下です。

 

まとめ

これと同じ事で困った人、いるのかなぁ…

CarrierWave のアップロード先を S3 にする

fog の gem を追加すれば、簡単に CarrierWave のアップロード先を AWS S3 にできます。


前提とする環境はCarrierWave でファイルアップロード - われプログラミングする、ゆえにバグありを参照してください。

fog の gem を追加

$ vim Gemfile

unf の gem がないと警告が出るので、それも追加しておきます。

# 最終行に追加
gem 'fog'
gem 'unf'

bundle install を実行しておきます

$ bundle install

 

S3 の設定を記述

$ vim config/initializers/carrierwave.rb

CarrierWave.configure do |config|
  config.fog_credentials = {
    provider:              'AWS',
    aws_access_key_id:     'access-key-xx', # 自分の環境に合わせる
    aws_secret_access_key: 'sec-ac-key-yy', # 自分の環境に合わせる
    region:                'us-west-2'      # 自分の環境に合わせる
  }
  config.fog_directory = 'bucket-name-zz'   # 自分の環境に合わせる
end

同ファイルを CarrierWave + RMagick で画像を切り抜く - われプログラミングする、ゆえにバグあり などで作成済みの場合は、それに追記します。どちらを先にしても問題ありません。
region は必須ではありませんが、警告がでるので指定しておきます。

 

Uploader クラスを編集

$ vim app/uploader/image_uploader.rb

11行目あたり

- storage :file
+ storage :fog

 

以上でアップロード先が S3 になります。
もちろん、参照先の URL もS3 になっています。
 

まとめ

すごく簡単でした。

CarrierWave + RMagick で画像を切り抜く

CarrierWave でファイルアップロード機能を追加したアプリに画像の切り抜きを実装します。


前提とする環境はCarrierWave でファイルアップロード - われプログラミングする、ゆえにバグありを参照してください。

ImageMagick-devel をインストール

$ sudo yum -y install ImageMagick-devel

 

RMagick の gem を追加

$ vim Gemfile

# 最終行に追加
gem 'rmagick'

bundle install を実行しておきます

$ bundle install

 

まずはリサイズ機能を使ってみる

アップロード時に別の version の画像を生成し、それをサムネイルとして扱います

Uploader を編集

$ vim app/uploader/image_uploader.rb

アンコメントするだけです

- #version :thumb do
- #  process :resize_to_fit => [50, 50]
- #end
+ version :thumb do
+   process :resize_to_fit => [50, 50]
+ end

オリジナルのファイルが
public/uploads/photo/image/[model.id]/xxxxxxxxxxx.png
の場合、同時に
public/uploads/photo/image/[model.id]/thumb_xxxxxxxxxxx.png
というファイルが生成されます。こちらがサムネイルです。

 

参照方法
photo = Photo.first
photo.image.class       # => ImageUploader
photo.image.url         # => "/uploads/photo/image/1/xxxxxxxxxxx.png"
photo.image.thumb.class # => ImageUploader
photo.image.thumb.url   # => "/uploads/photo/image/1/thumb_xxxxxxxxxxx.png"

 

画像を切り抜いてみる

CarrierWave で用意されているメソッドでは、アスペクト比を維持してリサイズした後に、その結果としてはみ出た部分を切り抜く事はやってくれます。
ですが、単純に切り抜く事を目的としたメソッドがないので、それを作成します。

ライブラリを拡張

参考carrierwave/lib/carrierwave/processing/rmagick.rb at master · carrierwaveuploader/carrierwave · GitHub
※ 汎用させないなら、メソッドは Uploader クラスに直に定義してもいいです。

$ vim config/initializers/carrierwave.rb

module CarrierWave::RMagick
  module ClassMethods
    def crop(rate_w, rate_h, min_w, min_h)
      process :crop => [rate_w, rate_h, min_w, min_h]
    end
  end
  def crop(rate_w, rate_h, min_w, min_h, gravity=Magick::CenterGravity)
    manipulate! do |img|
      _w = img.columns
      _h = img.rows
      width  = [[[_w * rate_w, _w].min, min_w].max, _w].min.to_i
      height = [[[_h * rate_h, _h].min, min_h].max, _h].min.to_i
      img.crop!(gravity, width, height)
      img = yield(img) if block_given?
      img
    end
  end
end

 

Uploader クラスを編集

$ vim app/uploader/image_uploader.rb

  version :thumb do
-   process :resize_to_fit => [50, 50]
+   process :crop => [0.5, 0.5, 50, 50]
  end

元の画像の中心から縦横50%のサイズ(ただし、50px以下にはしない)で切り取ります。

 
以上で画像の切り抜きが可能になります。