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

以前作っていたJava8のpostfixプラグインIntelliJ IDEA CEに取り込まれた。 Add a new postfix completion template #303

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


おまけ

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

AquaFXがいい感じ

この投稿は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補完プラグインを作る

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ビーム工房でテキストを読み上げるプラグインを作った話

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

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

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

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