4/2(土) Kotlin 1.0リリース記念勉強会 in 京都に行ってきた

Kotlinの勉強会にSpringBoot+Kotlinネタでしゃべりにいきました。
発表したスライドはこちらになります。

会場でAndroid以外でKotlinを使ってる人がどれくらい居るか質問したんですが、予想よりも多くて驚きました。
Androidアプリで「Java7までしか使えないからKotlin使うかー。使うしか無いかー。」みたいな使われ方するのがメインだと思ってたんですけど、普通にJavaの置き換えとして使うみたいな人が多いんですかね?

Read More

Workbenchの使い方を紹介

by orekyuu 0 Comments

学校の講義でSpringBoot使ってCIツール的なWebアプリを作ってみました。
1年ほどかけて作ってみたけど、ある程度動く形になったので使い方を紹介。


何が出来るの

機能としてはタスク管理と、サーバーでもビルドやテストを行えます。

Ticketboard
タスク管理機能

PullRequest
プルリクエスト

イメージ的にはJenkinsとBitbucket当たりをくっつけてみました的な感じを想像すると近いかもしれないです。


インストール方法

公式サイトからWorkbenchのjarファイルをダウンロードしてWorkbench.jarにリネームします。
適当なディレクトリに配置して以下のコマンドを実行。
java -jar Workbench.jar

起動を確認したらhttp://localhost:8080/へアクセスすれば動いてます。簡単ですね。

login


セットアップ

このままの状態だとユーザーを作れないのでadminユーザーでログインします。
デフォルトではIDがadmin、パスワードがpasswordに設定されています。

このままの設定だとセキュリティ的にまずいのでadminのパスワードを変更します。

admin_setting
赤丸で囲んだSettingを開いてChange Passwordタブから適当なパスワードに変更します。

次にサインアップを有効化して一般ユーザーを作成できるようにします。
global_setting
赤丸で囲んだGlobalSettingから設定を開いてサインアップを許可にチェックを入れます。

signup
ログアウトすると下に「CREATE AN ACCOUNT」と出てくるので、クリックしてアカウントを作成し、ログインします。

ここまでが初回セットアップです。他のユーザーにアカウントを作られないようにする場合は、改めてadminでログインしてサインアップを許可のチェックを外します。


プロジェクトを作成

今回はGithubを使っている前提でプロジェクトを作ります。
まずはpushを出来るようにGithubのアカウントをアカウントにひも付けます。認証に使えるのは今のところベーシック認証のみです。

git
Settingsを開いてGit AccountタブでGithubの認証用アカウントとパスワードを入力して終了です。

projects
次に左からProjectページを選択して、右上のCreateをクリック

必要な項目を入力してプロジェクトを作成。
projectcreate

プロジェクト名: プロジェクトの名前
Gitリポジトリ: gitのリポジトリのurl
作業ブランチ: 作業ブランチのマージ先。大体はmasterだと思う。
ビルドの有効化: ビルド機能を有効化します。
ビルドコマンド: ビルドを実行する時のコマンド。リポジトリをクローンしたrootで実行されます。
成果物のパス: ビルド後に保存する成果物のパスを正規表現で指定します。複数ファイルマッチした場合はzipにまとめられます。
テストの有効化: JUnitを使ったユニットテスト機能を有効化します。
テストコマンド: テストを実行するときのコマンド。リポジトリをクローンしたrootで実行されます。
テスト結果のパス: JUnitのテスト結果のパスを指定します。


課題の作成

左からTicketboardを選択。
NewカラムからAddボタンをクリック。

ticketboard

タイトルなどの必要な情報を入力してOKで課題を作成できます。
ステータスはD&Dで別のカラムへ移動させることで更新できる。詳細を確認する場合などは課題をクリックすることでダイアログを開くことが出来ます。


プルリクエストを作成

左からPullRequestを選択。
右上のNewボタンをクリック。

new_pr

タイトル説明・ブランチの他に関連するチケット(課題)の選択が必要。
プルリクエストを作ると、自動的に関連チケットのステータスがREVIEWに更新されます。

pr
レビューがおわったら右上からマージをクリック。
マージするとチケットのステータスがRESOLVEDに更新されます。
さらに、ビルドやテストの機能が有効化されていた場合はこの状態でビルドとテストが実行されます。


成果物のダウンロードとテスト結果

成果物はFilesページから、テストの結果はTestsページから確認できます。


おわりに

SpringBootで頑張ってみたけど、作業量がすごい多くて大変だった。
Webデザイナー1人とプログラマ3人で1年頑張ったけど(プログラマうち1人は11月頃に途中参加)もうすこし時間欲しかった感じはある。
最終的に750コミット越えてたし結構頑張ったんじゃないかな。今年はもう少し楽な作品作りたい思う。

ちなみにダウンロードは以下からできます。
http://workbench.orekyuu.net/

Java8 Postfixプラグインが公式に取り込まれた

by orekyuu 0 Comments

以前作っていたJava8のpostfixプラグインがIntelliJ IDEA CEに取り込まれた。

Add a new postfix completion template #303

メール確認した時「マージまじ?」ってなった。
たぶん次のアップデートで使えるようになるのかな?
IDEA16 EAPには入ってました。


おまけ

英語苦手なんや 本当にごめん
https://github.com/JetBrains/intellij-community/commit/8a20eaf86ef12fa1411557b41586350d40221427

AquaFXがいい感じ

by orekyuu 0 Comments

この投稿はJavaFXアドベントカレンダーの22日目の記事です。

JavaFXのライブラリで最近いいなと思ったのがAquaFXというライブラリ。
コンポーネント追加とかではなくて見た目をMac風にしてくれるライブラリです。

使い方

build.gradleに依存関係追加

compile 'com.aquafx-project:aquafx:0.1'

AquaFx#style()をApplication#start(Stage)あたりで呼び出せばとりあえず動きます。

コンポーネントの見た目を変える

AquaFxではButtonやLabelなどのコンポーネントにも複数のデザインが用意されています。
形などを変えたい場合はAquaFx#create○○Styler()からメソッドチェーンで値を設定して、styleメソッドに反映させたいコンポーネントを与えれば変更できます。

サンプルとして以下のコードを実行してみました。

@Override
public void start(Stage primaryStage) {
    AquaFx.style();

    VBox box = new VBox();
    //Buttons
    Arrays.stream(ButtonType.values()).forEach(type -> {
        Button button = new Button(type.getStyleName());
        AquaFx.createButtonStyler().setType(type).style(button);
        box.getChildren().add(button);
    });

    box.getChildren().add(new Separator());
    //TextFields
    Arrays.stream(TextFieldType.values()).forEach(type -> {
        TextField field = new TextField();
        AquaFx.createTextFieldStyler().setType(type).style(field);
        box.getChildren().add(field);
    });

    Scene scene = new Scene(box);
    primaryStage.setScene(scene);
    primaryStage.show();
}

実行結果
スクリーンショット 2015-12-21 0.28.33

AquaFx以外にもWindows風のAeroFx、今風なフラットデザインのflatterがあるみたいです。
結構手軽に使えてかっこいい見た目にできるのでおすすめです。
こういった見た目系ライブラリが今後も増えるといいなー。

明日は@s_kozakeさんです。

IntelliJ IDEAのPostfix補完プラグインを作る

by orekyuu 0 Comments

JetBrains IDE Advent Calendar2日目の記事です。前日はlaco0416氏のWebStormのTypeScript統合機能でした。

ところでPostfix補完使ってますか?当然使ってますよね?
Postfix補完をキメると気持ちいいですよね?Postfix補完でガシガシコードがかけると何でもできちゃいそうな気分になりますよね。

けどPostfix補完標準のテンプレートだけだと足りないですよね・・・。たとえばOptional.ofNullable(obj)とかobj.optから出したい・・・。というので作ったのがJava8Postfixってプラグイン。


IntelliJのSettings→PluginsでPostfixとかで検索してインストールできるので、Java8使ってる方は良ければ入れてください。


宣伝はここまでにして、本題に入ってPostfixプラグインの作り方。
まず開発環境としてIntelliJのプラグイン開発用プラグインを入れておきます。その後NewProjectすればこんな画面が出てくるはず。
plugindev1

あとはプロジェクト名とか決めて終了。
プラグインのプロジェクト構成はこんな感じ。
dev2

よく見かけるやつなので特にコメントもないかな。
plugin.xmlがプラグインの情報を記述する所。識別用のIDとか説明文とか。
この辺は調べるといくつか資料出てくるので飛ばします。

ここからPostfix補完のプラグインの話。
新しいPostfixのテンプレートを登録するためにJavaPostfixTemplateProviderを継承した新しいクラスを作成します。
JavaPostfixTemplateProvider#getTemplatesの戻り値には新しくプラグインで追加するPostfixTemplateのSetを返すようにしておきます。
Java8Postfixではこんな感じ。

public class Java8Postfix
        extends JavaPostfixTemplateProvider {
    private final HashSet<PostfixTemplate> templates;

    public Java8Postfix() {
        this.templates = ContainerUtil.newHashSet(new PostfixTemplate[] { new NullableOptionalPostfixTemplate(), new OptionalPostfixTemplate(), new ArrayToStreamPostfixTemplate(), new MethodToLambdaPostfixTemplate() });
    }

    @NotNull
    public Set<PostfixTemplate> getTemplates()
    {
        return templates;
    }
}

このままでは当然何も動作しないので、plugin.xmlにProviderを読み込んでもらうように指定します。

<extensions defaultExtensionNs="com.intellij">
  <codeInsight.template.postfixTemplateProvider language="JAVA" implementationClass="net.orekyuu.postfix.Java8Postfix"/>
</extensions>

次はPostfixTemplateを実装する。
Java8PostfixではPostfixTemplateWithExpressionSelectorを継承しています。
今回の例では.optのコードを例に出します。

public class OptionalPostfixTemplate
        extends PostfixTemplateBase
{
    public OptionalPostfixTemplate()
    {
        //(1)
        super("opt", "Optional.of(Object)", ConditionMerger.or(JavaPostfixTemplatesUtils.IS_NOT_PRIMITIVE, new Condition[] { MyConditions.IS_DOUBLE, MyConditions.IS_INT, MyConditions.IS_LONG }));
    }

    //(2)
    protected void expandForChooseExpression(@NotNull PsiElement context, @NotNull Editor editor)
    {
        //(3)
        PsiExpression expression = JavaPostfixTemplatesUtils.getTopmostExpression(context);
        if (expression == null) {
            return;
        }
        PsiType type = expression.getType();
        String optionalClass = "Optional";
        if (PsiType.DOUBLE.equals(type)) {
            optionalClass = "OptionalDouble";
        } else if (PsiType.LONG.equals(type)) {
            optionalClass = "OptionalLong";
        } else if (PsiType.INT.equals(type)) {
            optionalClass = "OptionalInt";
        }
        Project project = context.getProject();
        Document document = editor.getDocument();

        TextRange range = expression.getTextRange();
        document.deleteString(range.getStartOffset(), range.getEndOffset());

        TemplateManager manager = TemplateManager.getInstance(project);
        //(4)
        Template template = manager.createTemplate("", "");
        template.setToReformat(true);
        template.addTextSegment(optionalClass + ".of(");
        template.addTextSegment(expression.getText());
        template.addTextSegment(")");

        manager.startTemplate(editor, template);
    }
}

abstract class PostfixTemplateBase
        extends PostfixTemplateWithExpressionSelector
{
    public PostfixTemplateBase(String postfix, String desc, PostfixTemplateExpressionSelector selector)
    {
        super(postfix, desc, selector);
    }

    public PostfixTemplateBase(String postfix, String desc, Condition<PsiElement> condition)
    {
        this(postfix, desc, JavaPostfixTemplatesUtils.selectorAllExpressionsWithCurrentOffset(condition));
    }
}

public final class ConditionMerger
{
    private ConditionMerger()
    {
        throw new UnsupportedOperationException();
    }

    public static Condition<PsiElement> or(final Condition<PsiElement> first, final Condition<PsiElement>... option)
    {
        if (first == null) {
            throw new NullPointerException("first condition is null");
        }
        return new Condition<PsiElement>() {
            public boolean value(PsiElement element) {
                if (first.value(element)) {
                    return true;
                }
                for (Condition<PsiElement> condition : option) {
                    if (condition.value(element)) {
                        return true;
                    }
                }
                return false;
            }
        };
    }
}

public enum MyConditions
        implements Condition<PsiElement>
{
    IS_ARRAY {
        @Override
        public boolean value(PsiElement element) {
            if(!(element instanceof PsiExpression)) {
                return false;
            } else {
                PsiType type = ((PsiExpression)element).getType();
                return JavaPostfixTemplatesUtils.isArray(type);
            }
        }
    },
    IS_LAMBDA {
        @Override
        public boolean value(PsiElement element) {
            return element instanceof PsiLambdaExpression;
        }
    },
    IS_METHOD_CALL {
        @Override
        public boolean value(PsiElement element) {
            return element instanceof PsiMethodCallExpression;
        }
    },
    IS_DOUBLE {
        @Override
        public boolean value(PsiElement element) {
            if(!(element instanceof PsiExpression)) {
                return false;
            } else {
                PsiType type = ((PsiExpression)element).getType();
                return PsiType.DOUBLE.equals(type);
            }
        }
    },
    IS_LONG {
        @Override
        public boolean value(PsiElement element) {
            if(!(element instanceof PsiExpression)) {
                return false;
            } else {
                PsiType type = ((PsiExpression)element).getType();
                return PsiType.LONG.equals(type);
            }
        }
    },
    IS_INT {
        @Override
        public boolean value(PsiElement element) {
            if(!(element instanceof PsiExpression)) {
                return false;
            } else {
                PsiType type = ((PsiExpression)element).getType();
                return PsiType.INT.equals(type);
            }
        }
    };
}

(1)
第一引数に補完を書ける時に使用する文字列。optを渡しているので.optで補完できるようになる。
第二引数は説明文。
第三引数は補完をかけるための条件。今回の場合はプリミティブでないオブジェクト || double || int || longのみ補完できる。

(2)
補完をかける時のイベント。ここでテンプレートを展開する。

(3)
PsiExpressionを取得してくる。これで対象の式の戻り値とか解析しながら補完をかけれる。

(4)
Templateを使って変換後の文字列を作る。文字列の操作が楽だったりsetToReformat(true)としておけばフォーマット整えてくれたりするので基本はこれを使うんじゃないかなー?
最後にTemplateManager#startTemplateを呼び出さないといけないので注意。

Postfix補完プラグインではPsiHogehogeの扱いが難しいので色々調べないといけない感じある。
調べるとIntelliJ IDEA PSI Cookbookってサイトがあったので参考になると思う。

最後にEditor>General>Postfix Completionに説明文を入れる必要がある。書かないとエラーになった記憶。
dev3

この画面の説明文は”postfixTemplates/テンプレートのクラス名”パッケージに配置する。
dev4
before.java.templateにはBeforeに表示されるコードをそのまま記述。
after.java.templateにはAfterに表示されるコードをそのまま記述。
description.htmlにはDescriptionに表示する説明文をhtmlで記述する。
これでおしまい。動作確認後ビルドしてPluginRepositoryで配布しようね。

明日はhiromikai_green氏がなんか書いてくれると思います。期待。

Javaビーム工房でテキストを読み上げるプラグインを作った話

by orekyuu 0 Comments

通っている学校の学祭でJavatterブースをやった時にデモ用に作ったプラグインの話。
内容は/say [読み上げさせたい文章]ってコマンドを追加してその文章を読み上げさせるだけ。

読み上げの音声を作る方法は色々あると思うけど、今回はVoiceText Web APIのJavaラッパーでVoiceText4jというライブラリを見つけたので使うことにしました。

VoiceTextの読み上げはかなり優秀で、適当にWikipediaの文章を読み上げさせてみましたが全く違和感なく漢字や英単語を発音していました。面白いので使ってみてください。
読み上げプラグインはプラグインリポジトリからダウンロードできます。

最後に読み上げプラグインのソースコードを貼っておしまい。

IntelliJのAnalyze機能が便利

by orekyuu 0 Comments

Case1

クラッシュの報告でスタックトレースを含むログファイルが送られてきた。
テキストエディタでスタックトレースを開きながらコードを読む?

Analyze>Analyze Stacktraceを選択し、ログをコピペ
スクリーンショット 2015-10-30 12.12.00

これでOKを押すと、Runウィンドウでログが見れる!
もちろん自分の書いたコードにはファイル名クリックでジャンプできます。
スクリーンショット 2015-10-30 12.12.29


Case2

あるメソッドの引数のデータの流れを追いたい。
メソッドの呼び出し元を順に見ていく?

今回は例として引数のdirectoryのデータの流れを追います
スクリーンショット 2015-10-30 12.17.17

directoryにカーソルを合わせてAnalyze>Analyze Data Flow to Here
スクリーンショット 2015-10-30 12.19.18

データの流れがすぐに分かって、プレビューでコードが見れるのでかなり良い!


Case3

似たようなコードを見つけてメソッドの抽出をしたい。

Analyze>Locate Duplicates
Projectスコープにして次のウィンドウでJavaにチェックを入れる。

スクリーンショット 2015-10-30 12.25.53
似たようなコードの一覧が見れるようになりました。


他にも探せば使えそうな機能出てきそう。

java.lang.reflect.Proxyが面白い

by orekyuu 0 Comments

最近Retrofitというライブラリを教えてもらったのでこれを使って遊んでました。

interface Hoge {
  @GET("/users")
  List<User> findUsers();
}
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("...")
    .build();
Hoge hoge = retrofit.create(Hoge.class);
List<User> users = hoge.findUsers();

Hogeインターフェイスを作っておいて、RetrofitにHoge.classを渡してインスタンスを返してもらうと、findUsersが/usersにGETで情報を取得してくるメソッドとして振る舞ってくれるといった動きをします。

どういった実装になっているかRetrofitのコードを読んでみましょう。
Retrofitのリポジトリ: https://github.com/square/retrofit

Retrofit#createを見てみます。

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadMethodHandler(method).invoke(args);
          }
        });
  }

どうやらProxy#newProxyInstanceでインスタンスを作っているみたいです。
Proxy#newProxyInstanceは第二引数のClass[]を実装するプロキシインスタンスを作るメソッドです。第三引数のInvocationHandlerはプロキシインスタンスのメソッドが呼び出されるとinvokeメソッドが呼び出されるので、プロキシインスタンスの振る舞いはこのInvocationHandlerに書きます。
Retrofit#createは引数のインターフェイスを実装したプロキシインスタンスを返して、振る舞いはInvocationHandler#invoke内でやってるって感じみたいですね。
ここまで分かれば後は色々と遊べそうです。

というわけで講義中に遊びで作ったProxyを使ったコードをのせておしまいです。

Java8に対応したPostfix補完プラグイン作ったよ

by orekyuu 0 Comments

Java8に対応したPostfix補完を追加するプラグインつくった。

テンプレート 変換前 変換後
.stream array.stream Arrays.stream(array)
.opt obj.opt Optional.of(opt)
.opt intValue.opt OptionalInt.of(opt)
.opt longValue.opt OptionalLong.of(opt)
.opt doubleValue.opt OptionalDouble.of(opt)
.optnull obj.optnull Optional.ofNullable(opt)
.lambda System.out.println(“Hello”).lambda () -> System.out.println(“Hello”)

Java8 Postfixとかで検索すると出てくるので使ってみてください。