20191101のJavaに関する記事は12件です。

SolrJ で JSON をインポートする

課題

SolrJ ではドキュメントをインポートするのに「SolrInputDocument」クラスを使うことになっていますが、JSONからSolrInputDocumentに変換する機能は提供されていないようなので作りました。
フィールドのタイプをプロパティで指定できるようにしてあります。

Code

import java.io.IOException;
import java.util.Properties;

import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrInputDocument;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

public class SolrJsonImporter {

    SolrClient client;
    Properties props;

    public SolrJsonImporter(String endPoint) {
        client = new HttpSolrClient.Builder(endPoint).build();
    }

    public UpdateResponse add(String json) throws SolrServerException, IOException {

        SolrInputDocument document = new SolrInputDocument();
        JsonObject jsonObj = (JsonObject) new Gson().fromJson(json, JsonObject.class);
        if (this.props == null) {
            for (String key : jsonObj.keySet()) {
                String value = jsonObj.get(key).getAsString();
                document.addField(key, value);
            }

        } else {
            processFields(jsonObj, document, this.props);

        }

        UpdateResponse response = client.add(document); // throws SolrServerException,IOException
        return response;

    }

    private void processFields(JsonObject jsonObj, SolrInputDocument document, Properties props) {
        for (int n = 0; true; n++) {
            String propKey = String.format("field[%d]", n);
            String fieldName = props.getProperty(propKey + ".name");
            if (fieldName == null) {
                break;
            }

            String fieldType = props.getProperty(propKey + ".type");

            System.err.println(fieldName + "," + fieldType);

            String fieldTarget = props.getProperty(propKey + ".target", fieldName);

            Object oValue = null;

            if (fieldType.equals("String")) {
                oValue = jsonObj.get(fieldName).getAsString();
            } //
            else if (fieldType.equals("String[]")) {
                JsonArray arr = jsonObj.get(fieldName).getAsJsonArray();
                Object[] oo = new Object[arr.size()];
                for (int x = 0; x < arr.size(); x++) {
                    oo[x] = arr.get(x).getAsString();
                }
                oValue = oo;
            } //
            else if (fieldType.equals("Long")) {
                oValue = jsonObj.get(fieldName).getAsLong();
            } //
            else if (fieldType.equals("Long[]")) {
                JsonArray arr = jsonObj.get(fieldName).getAsJsonArray();
                Object[] oo = new Object[arr.size()];
                for (int x = 0; x < arr.size(); x++) {
                    oo[x] = arr.get(x).getAsLong();
                }
                oValue = oo;
            } //
            else if (fieldType.equals("Double")) {
                oValue = jsonObj.get(fieldName).getAsDouble();
            } //
            else if (fieldType.equals("Double[]")) {
                JsonArray arr = jsonObj.get(fieldName).getAsJsonArray();
                Object[] oo = new Object[arr.size()];
                for (int x = 0; x < arr.size(); x++) {
                    oo[x] = arr.get(x).getAsDouble();
                }
                oValue = oo;
            } //
            document.addField(fieldTarget, oValue);
        }

    }

    public UpdateResponse commit() throws IOException, SolrServerException {
        return client.commit();
    }

    private void setProperties(Properties props) {
        this.props = props;

    }

}

使い方

String urlString = "http://localhost:8983/solr/sandbox";
Properties props = new Properties();
props.setProperty("debug", "false");
props.setProperty("field[0].name", "id");
props.setProperty("field[0].type", "String");
props.setProperty("field[1].name", "text");
props.setProperty("field[1].type", "String");
props.setProperty("field[2].name", "field1");
props.setProperty("field[2].type", "String");
props.setProperty("field[3].name", "field2");
props.setProperty("field[3].type", "String[]");
props.setProperty("field[4].name", "field3");
props.setProperty("field[4].type", "Long[]");
props.setProperty("field[5].name", "field4");
props.setProperty("field[5].type", "Double[]");
props.setProperty("field[6].name", "field5");
props.setProperty("field[6].type", "Long");
props.setProperty("field[7].name", "field6");
props.setProperty("field[7].type", "Double");

String json = "{" //
        + "'id':'001'" //
        + ",'text':'This is test.'" //
        + ",'field1':'fieldvalue001'" //
        + ",'field2':['fieldvalue001']" //
        + ",'field3':[1,2,3]" //
        + ",'field4':[0.1,0.2,0.3]" //
        + ",'field5':123" //
        + ",'field6':0.123" //
        + "}";

SolrJsonImporter util = new SolrJsonImporter(urlString);
util.setProperties(props);

System.err.println(util.add(json));

util.commit();

まとめ

このクラスを使うと簡単にJSONをSolrJ経由でインポートすることができます。
既存のJSONがあり、適当に変換してインポートしたい場合に便利なのではないかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SumとSummingIntくらべ

実験してみました

比較対象は主にstreamの操作です.

以下では数値リストの足し算に
mapToInt(v -> v).sum()を使うか
collect(Collectors.summingInt(Integer::intValue)))を使うか

加えて、

中間操作と終端操作の処理内容を別関数に分けるか
すべて一つの関数でまとめるか

について比較しています.

ProcessingTest.java
package mk42;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

public class ProcessingTest {
    public static void main(String[] args) {
        long startTime = System.nanoTime();
        run(generate.get());
        long endTime = System.nanoTime();
        Mk42.echo.accept("Processing Time : " + (endTime - startTime) + " ns");
    }
    /**
     * Processing speed test.
     *
     * @param list
     */
    private static void run(List<String> list) {
        try {
            // fastest
            Mk42.echo.accept(list.stream().filter(Objects::nonNull).map(add3.andThen(convertInt)).mapToInt(v -> v).sum());
            // normal
            Mk42.echo.accept(list.stream().filter(Objects::nonNull).map(add3.andThen(convertInt))
                    .collect(Collectors.summingInt(Integer::intValue)));
            // bad
            all.accept(list);
        } catch (NumberFormatException e) {
            Mk42.echo.accept(join.apply(list));
        }
    }
    /** A series of processing */
    static Consumer<List<String>> all = v -> {
        Mk42.echo.accept(
                v.stream().filter(Objects::nonNull).map(x -> x + "3").map(Integer::parseInt).mapToInt(z -> z).sum());
    };
    /** Add 3 */
    static UnaryOperator<String> add3 = v -> v + "3";
    /** Convert to Integer */
    static Function<Object, Integer> convertInt = v -> Integer.parseInt(String.valueOf(v));
    /** Join with comma */
    static Function<List<String>, String> join = v -> v.stream().collect(Collectors.joining(","));
    /** Generate List which can convert to Integer */
    static Supplier<List<String>> generate = () -> Arrays.asList(null, "1", "2", "3", "4", "5");
}

ちなみにMk42とは

Mk42.java
package mk42;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Answer to the Ultimate Question of Life, the Universe, and Everything.
 */
public class Mk42 {
    /** echo */
    public static Consumer<Object> echo = v -> System.out.println(v);
    /** not null */
    public static Predicate<Object> notNull = v -> Optional.ofNullable(v).isPresent();
    /** sum list */
    public static Function<List<Integer>, Integer> sum = v -> v.stream().filter(notNull).mapToInt(i -> i).sum();
    /** generate String list */
    public static Supplier<List<String>> strList = () -> Arrays.asList(null, "", " ", "a", "0");
    /** generate Integer list */
    public static Supplier<List<Integer>> intList = () -> Arrays.asList(null, 1, 2, 3, 4, 5);
}

結果

  • 操作分割 終端操作mapToIntからのsum
    処理時間:2261200 ns
  • 操作分割 終端操作collect
    処理時間:3573700 ns
  • 操作結合 終端操作mapToInt
    処理時間:16587300 ns

で?

操作の関数を分けたほうが早いみたいです.

今回のメインは終端操作なんですが
mapToIntよりcollectでのCollectors呼び出しのほうが遅いですね
オブジェクト呼び出してるから当たり前っぽいですね

細やかなことかもですが、似ている処理ならどちらのほうが処理速度が速いか
覚えていきたいものです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GCPのjavaクライアントライブラリからStackdriver loggingにエラーを投げてもStackdriver ErrorReportingには入らない

超概要

stackdriver loggingのJavaクライアントライブラリからLogbackを使って利用する場合にはErrorをLogに送っても、Stackdriver ErrorReportingの方にはそのまま入らない。(題名通り
通常はStackdriver loggingにError以上のLogを入れるとErrorReportingの方にも入るのだが(それから通知などに使える)
Javaクライントライブラリ(Logback使用)の場合はどうやっても入らなかった。
これにハマって色々と調べるとなかなかつらいことが判明。

経緯

時代にさかのぼってる感ありだが、最近Java(正確にはKotlin)で動くデスクトップアプリを制作している。
各ユーザーさんの環境で動くアプリケーションがどのようなエラーを吐いているかを見たいので、クラウドのロギングサービスを導入しようと思った。
ちょうどGCPを色々と試していた&Stackdriver ErrorReportingはJavaで使ったことあったので、Stackdriver loggingを使うことにした。

Stackdriver ErrorReportingを使ったとき

→Official
https://cloud.google.com/error-reporting/docs/reference/libraries?hl=ja
このままのコードでやると直接ErrorReportingには飛ばせるのだが、ロガーに追加して投げる処理を追加しないといけない。
これはなんか同じコードが各所で出てきて美しくない。(気がする。
なんとかしてlogger.error("hoge error", exception)
みたいな感じでファイルログと同時にErrorReportingできないものだろうか。

Stackdriver logging with logback

→official
https://cloud.google.com/logging/docs/setup/java?hl=ja
LogbackのAppenderを作れば、logger.なんたらでそのままStackdriver loggingにも入れてくれる。
ここまではOK、ただし
https://cloud.google.com/error-reporting/docs/setup/java?hl=ja
にある通り、

Stackdriver Logging を使用してエラーデータを Error Reporting に送信することもできます。データのフォーマット要件については、Stackdriver Logging でのエラー メッセージのフォーマットをご覧ください。

とあるんだが、何度やってもLoggingにエラー送ってもErrorReportingには何も入らない。
もうちょっと詳しく見てみると

データのフォーマット要件については、Stackdriver Logging でのエラー メッセージのフォーマットをご覧ください。

エラーメッセージのフォーマット?
リンク先を見ると

{
  "eventTime": string,
  "serviceContext": {
    "service": string,     // Required.
    "version": string
  },
  "message": string,       // Required. Should contain the full exception
                           // message, including the stack trace.
  "context": {
    "httpRequest": {
      "method": string,
      "url": string,
      "userAgent": string,
      "referrer": string,
      "responseStatusCode": number,
      "remoteIp": string
    },
    "user": string,
    "reportLocation": {    // Required if no stack trace in 'message'.
      "filePath": string,
      "lineNumber": number,
      "functionName": string
    }
  }
}

いくつかrequiredのフィールドになっているが、Logbackからのloggingには該当するフィールドは入っていない。
(serviceContext > serviceが無いようだった)
たぶんこれかな、ということはわかったのでじゃあ設定でなんとかなるか、と思いきや。
Logbackのxml設定ではserviceContextとかもろもろを設定する項目はない!
ナンテコッタイ

やろうとしたこと

https://cloud.google.com/logging/docs/setup/java?hl=ja#additional_fields_and_labels
この通り、LoggingEnhancerを作成してやればラベルとかは追加できるようだ。
ただこれもGCPのコンソールでの絞り込みで使えるだけでErrorReportingにはいかない。
かといってEnhacerにそれ以上カスタマイズできるようなコードは見当たらない…

結局は

さらに色々と調べるとこんなことも
https://github.com/googleapis/google-cloud-java/issues/4179
デフォではできないんかい!
で、このやりとりの中の別のIssueをたどっていくと
https://github.com/logstash/logstash-logback-encoder
こんなのも。
つまり、logbackのencoderのlayoutを直接いじるクラスを作ればErrorReportingに入るLoggingができるそう。
こちらもありました。
https://github.com/kurochan/logback-stackdriver-logging

結論

クライアントアプリケーションは難しい
javaクライアントライブラリがうまく対応してくれると嬉しい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【java】if文でString型の条件文が無視される

javaでif(string == "test_string")を使っているあなた.

String string = "test_string";

if(string=="test_string"){
 System.out.println("SUCCESS");
}

このようなString型の条件文にうまく入れないことがあります.

そんなあなたに朗報です.

String string = "test_string";

if(string.equals("test_string")){
 System.out.println("SUCCESS");
}

こちらを使えば入れます.

equalsメソッドの詳しい解説はこちらを参照してみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

位置情報サービスを利用したAndroidアプリのかくれんぼゲーム

私は現在学生で、卒業研究で位置情報サービスを利用を利用したかくれんぼのゲームを制作をしています。開発環境はAndroid Studioで、GoogleMaps APIを使用しています。自分の位置情報や他人の位置情報はFireBaseで管理しようと思っています。
現在は自分の位置情報を表示する段階まで進んだのですが、その位置情報をどのようにFireBeseと連携すれば良いのか、また他人の位置情報をどのようにして取得すればよいのかわからず、作業が停滞しています。
最近では、ほんとにFireBeseを扱うのが適切なのか、悩んでいます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SolrJ でドキュメントをインポートする

Code

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrInputDocument;

public class HelloDocumentAdd2 {

    public static void main(String[] args) throws SolrServerException, IOException {

        String urlString = "http://localhost:8983/solr/sandbox";
        SolrClient solr = new HttpSolrClient.Builder(urlString).build();

        SolrInputDocument document = new SolrInputDocument();
        document.addField("id", "5");
        document.addField("field_text_en", "This is test. for Date formatting ");
        document.addField("field_location_string", "ja");

        {
            String dateNow = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")).format(new Date());
            System.err.println(dateNow);
            document.addField("field_date", dateNow);
        }

        UpdateResponse response = solr.add(document); // throws SolrServerException,IOException

        System.err.println(response);

        // Remember to commit your changes!

        solr.commit();

    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】音量変更(=音量ボタン押下)の検出方法

昔からあるレガシーな方法ですが、ググって出てくる情報が少し実用性には欠けるため、書いておきます。

手法

音量変更もしくは音量ボタンが押された事を検出するIntentとして、AudioManager.VOLUME_CHANGED_ACTION (android.media.VOLUME_CHANGED_ACTION)があります。これをBroadcastReceiverで受ければ良いだけです。

ここまではググってすぐに出てくる情報ですが、AudioManager.VOLUME_CHANGED_ACTIONは以下の点に注意する必要があります。

  • 一度の音量変更操作で複数のイベントが通知される
    • AudioManager.STREAM_MUSICなどSTREAM毎に通知され、しかも音量変わって無くても通知される
  • 画面ON中の初回ボタン操作時は音量UI表示され、実際には音量が変わらないためにイベント通知されない
  • 画面OFF中は音楽アプリ等で音楽再生していないと、ボタン押してもイベント通知されない
    • ボリュームが変わらないため

ソースコード

AndroidManifest.xml
<receiver android:name=".VolumeChangedActionReceiver">
    <intent-filter>
        <actionandroid:name="android.media.VOLUME_CHANGED_ACTION" />
    </intent-filter>
</receiver>
VolumeChangedActionReceiver.java
public static class VolumeChangedActionReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
            int newVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", 0);
            int oldVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", 0);
            int streamType = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_TYPE", 0);

            if (streamType == AudioManager.STREAM_MUSIC) {
                // TODO: write your code
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JScrollBarで自動スクロールしようとしたらイベントハンドラの件

詰まって数時間が吹き飛ばされたので、メモ代わりに。Swingを使っている人ならだれでも一度は通る失敗(らしい)。

どんなのをつくっていたか

Javaで自作シェルっぽいものを作ろうとしていて、JScrollPaneを自動スクロールさせようとしていた。調べながら書いたソースコードがこれ。

MyShell.java
class MyShell extends JFrame implements ActionListener{
    MyShell{
//(中略)   
    String now = new File(".").getAbsoluteFile().getParent().toLowerCase();//カレントディレクトリ

    JTextField text = new JTextField();//入力エリア
    text.addActionListener( this );
    JTextArea area = new JTextArea(now+"->");//表示エリア
    JScrollPane scrollpane = new JScrollPane(area);

    JPanel p = new JPanel()
    p.add(text,BorderLayout.SOUTH);
    p.add(scrollpane);
//(中略)
    }
    public void actionPerformed(ActionEvent e){
        String input = text.getText().toLowerCase();
        if(input.equals("exit")){System.exit(0);}//強制終了用
        area.append(text.getText()+"\nPC->"+mani(input.split(" "))+"\n\n"+now+"->");//描画エリアに追記
        text.setText("");//入力エリアの内容を消去

        JScrollBar scrollBar = scrollpane.getVerticalScrollBar();
        scrollBar.setValue(scrollBar.getMaximum());
    }
    public String mani(String[] input){
        //コマンド解釈用関数
    }
//(中略)
}

※分かりやすいように、コンストラクタの中で宣言しているように書いていますが、実際はインスタンスフィールドとして各種変数を宣言しています。

一応、解説しておくとactionPerformedは入力用テキストフィールド内でエンターキーが押されたときに呼び出されます(参考リンク)。そして、エンターキーを押した直後にコマンドの解釈が行われ、描画エリアに追記。その後、スクロールバーの高さの分だけスクロールされて、JTextAreaの一番下が表示されている......はずでした。

なにがいけなかったのか

結論から言うと、イベントハンドラとして登録された処理は、そのすべての処理が終わってから描画されます。つまり、scrollBar.getMaximum()で取得できる大きさは再描画前の大きさなので、再描画前のJTextAreaの一番下の場所にしか行けないのです。actionPerformedの最後の行にThread.sleep(1000)でも挿入してみると、挙動がよく分かると思います。
まあ、よくよく考えれば、描画処理はできるだけ少ない回数で済んだほうが良いですし、当然といえば当然なんですね。

解決法

この記事より。

単一のイベントハンドラーでは状態の変更とただ一回のrepaintの呼び出しのみが許されると思ってください。複数回repaintを呼び出してもpaintComponentメソッドは一度しか呼び出されません。

更に調べていると、こんな記事が......。同じことで詰まっている人いましたね......。この記事にあるコードをactionPerformedに入れてやると無事動きました。

また、Swingはシングルスレッドでの設計のようでした。

invokeLater() メソッドは(中略)保留中のすべての AWT イベントが処理されたあとに発生します

引用元:http://wisdom.sakura.ne.jp/system/java/swing/swing4.html

まとめ

  1. なんでもかんでもイベントハンドラで行おうとしない
  2. イベントハンドラでは終了時に1度だけ描画処理を行うのが基本
  3. Swingでイベントハンドラのスレッド以外から何らかの描画処理をしたい場合はinvokeLater()を使う

余談

余談ですが、paintImmediatelyも使ってみました。どうやら、描画処理だけで、それ以外の数値等の更新はやはりイベントハンドラ終了後に行われるようです。

MyShell.java
actionPerformed(ActionEvent e){
    System.out.println(scrollbar.getValue()+"#"+scrollbar.getMaximum()+"#"+area.getHeight());
    p.paintImmediately(0,0,5000,5000);
    System.out.println(scrollbar.getValue()+"#"+scrollbar.getMaximum()+"#"+area.getHeight());
}



以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JScrollBarで自動スクロールしようとしたらイベントハンドラの描画が1度だけだった件

詰まって数時間が吹き飛ばされたので、メモ代わりに。Swingを使っている人ならだれでも一度は通る失敗(らしい)。

どんなのをつくっていたか

Javaで自作シェルっぽいものを作ろうとしていて、JScrollPaneを自動スクロールさせようとしていた。調べながら書いたソースコードがこれ。

MyShell.java
class MyShell extends JFrame implements ActionListener{
    MyShell{
//(中略)   
    String now = new File(".").getAbsoluteFile().getParent().toLowerCase();//カレントディレクトリ

    JTextField text = new JTextField();//入力エリア
    text.addActionListener( this );
    JTextArea area = new JTextArea(now+"->");//表示エリア
    JScrollPane scrollpane = new JScrollPane(area);

    JPanel p = new JPanel()
    p.add(text,BorderLayout.SOUTH);
    p.add(scrollpane);
//(中略)
    }
    public void actionPerformed(ActionEvent e){
        String input = text.getText().toLowerCase();
        if(input.equals("exit")){System.exit(0);}//強制終了用
        area.append(text.getText()+"\nPC->"+mani(input.split(" "))+"\n\n"+now+"->");//描画エリアに追記
        text.setText("");//入力エリアの内容を消去

        JScrollBar scrollBar = scrollpane.getVerticalScrollBar();
        scrollBar.setValue(scrollBar.getMaximum());
    }
    public String mani(String[] input){
        //コマンド解釈用関数
    }
//(中略)
}

※分かりやすいように、コンストラクタの中で宣言しているように書いていますが、実際はインスタンスフィールドとして各種変数を宣言しています。

一応、解説しておくとactionPerformedは入力用テキストフィールド内でエンターキーが押されたときに呼び出されます(参考リンク)。そして、エンターキーを押した直後にコマンドの解釈が行われ、描画エリアに追記。その後、スクロールバーの高さの分だけスクロールされて、JTextAreaの一番下が表示されている......はずでした。

なにがいけなかったのか

結論から言うと、イベントハンドラとして登録された処理は、そのすべての処理が終わってから描画されます。つまり、scrollBar.getMaximum()で取得できる大きさは再描画前の大きさなので、再描画前のJTextAreaの一番下の場所にしか行けないのです。actionPerformedの最後の行にThread.sleep(1000)でも挿入してみると、挙動がよく分かると思います。
まあ、よくよく考えれば、描画処理はできるだけ少ない回数で済んだほうが良いですし、当然といえば当然なんですね。

解決法

(ネットはちゃんと調べると、いくらでも情報が出てきますね......。)

単一のイベントハンドラーでは状態の変更とただ一回のrepaintの呼び出しのみが許されると思ってください。複数回repaintを呼び出してもpaintComponentメソッドは一度しか呼び出されません。
引用元:https://teratail.com/questions/75183

更に調べていると、こんな記事が......。同じことで詰まっている人いましたね......。この記事にあるコードをactionPerformedに入れてやると無事動きました。

また、Swingはシングルスレッドでの設計のようでした。

invokeLater() メソッドは(中略)保留中のすべての AWT イベントが処理されたあとに発生します

引用元:http://wisdom.sakura.ne.jp/system/java/swing/swing4.html

まとめ

  1. なんでもかんでもイベントハンドラで行おうとしない
  2. イベントハンドラでは終了時に1度だけ描画処理を行うのが基本
  3. Swingでイベントハンドラのスレッド以外から何らかの描画処理をしたい場合はinvokeLater()を使う

余談

余談ですが、paintImmediatelyも使ってみました。どうやら、描画処理だけで、それ以外の数値等の更新はやはりイベントハンドラ終了後に行われるようです。

MyShell.java
actionPerformed(ActionEvent e){
    System.out.println(scrollbar.getValue()+"#"+scrollbar.getMaximum()+"#"+area.getHeight());
    p.paintImmediately(0,0,5000,5000);
    System.out.println(scrollbar.getValue()+"#"+scrollbar.getMaximum()+"#"+area.getHeight());
}



以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

astah*プラグイン開発スニペット

現在開いている図取得
IDiagramViewManager diagramViewManager = api.getViewManager().getDiagramViewManager();
IDiagram currentDiagram = diagramViewManager.getCurrentDiagram();
選択されている図要素取得
IDiagramViewManager diagramViewManager = api.getViewManager().getDiagramViewManager();
List<IPresentation> iPresentationList = Arrays.asList(diagramViewManager.getSelectedPresentations());
文字の色を変更

例:inpはINodePresentationです。IPresentation派生なら行けるかと。

inp.setProperty(Key.FONT_COLOR, "#FF0000");
簡易テキストダイアログ
package xyz.astah.example;

import java.awt.Frame;
import java.awt.Insets;

import javax.swing.JDialog;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class TextDialog extends JDialog {
    public TextDialog(String text, Frame frame, int width, int height, boolean modal) {
        super(frame, "Text Dialog", modal);

        JTextArea jTextArea = new JTextArea(text);
        jTextArea.setMargin(new Insets(8, 8, 8, 8));

        JScrollPane jScrollPane = new JScrollPane(jTextArea);

        this.getContentPane().add(jScrollPane);
        this.setBounds(frame.getX() + 100, frame.getY() + 100, width, height);
        this.setVisible(true);
    }
}

使用例

TextDialog textDialog = new TextDialog(outputText.toString(), api.getViewManager().getMainFrame(), 300, 300, false);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Framework 5.2.0 - CORSハンドリング変更によるVary header重複

5.2.0.RELEASEにてSpring Securityを使用する場合、以下のVaryレスポンスヘッダーが重複することがある。

  • Origin
  • Access-Control-Request-Method
  • Access-Control-Request-Headers

特にこんな感じで@CrossOriginなControllerのテストを実行する際に重複してしまう。

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .apply(SecurityMockMvcConfigurers.springSecurity()).build();
    }

対象

Spring Framework 5.2.0.RELEASE (spring-web)
+ Spring Security使用

原因

エンドポイントに対し、Spring SecurityのCORSの設定と@CrossOriginアノテーションの両方を設定していると発生する。
これはDefaultCorsProcessor.javahandleInternalメソッドからprocessRequestメソッドにてVary headerを追加するように変更となったため。
Spring SecurityでのCORSとWebMVCでのCORSのFilterがかぶるようで2回実行されてしまうことがある。

DefaultCorsProcessor.java
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
        HttpServletResponse response) throws IOException {

    response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
    response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
    response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
    ...

差分はこちら

回避方法

Spring Security configで以下のように設定しているcorsの設定をやめる。

SpringSecurityConfig.java
@Override
public void configure(HttpSecurity http) throws Exception {
    http...
        .cors()
            .configurationSource(this.corsConfigurationSource());
}

private CorsConfigurationSource corsConfigurationSource() {
    ...
}

これまではCorsFilterが2回走っても問題なかったが、ヘッダー付加の場所が変わってしまったため2回走ると重複するようになってしまった。

Spring SecurityのCORSの設定と@CrossOriginの2つ設定しちゃだめなんですかね。。
なにか他に良い解決方法があれば。。。。。

ちなみに、その他変更点も結構ある。5.2.0怖
https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-5.x

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユークリッドの互除法(Java)

ユークリッドの互除法

2数の最大公約数を求める手法
同処理を繰り返すので、再帰関数を使うと短く書けます

考え方
1. 余りが0なら終わり(その時の割る数が答)
2. 余りが0でなければ、「割る数→割られる数」「余り→割る数」として繰り返し

Euclid
    static long gcd(long x, long y) {
        return x%y==0? y : gcd(y,x%y);
    }
  • gcd : Greatest common divisor(最大公約数)の略
  • 2入力の順序は考慮不要(xの方が小さくても大丈夫)

(追記)コメントでご指摘いただきました。
上記のメソッドでは、x%yの計算を2回行ってしまうので、以下の書き方の方が計算量が少ないです。

Euclid
    static long gcd(long x, long y) {
        return y==0? x : gcd(y,x%y);
    }
  • この書き方の時、余りが0になったら、次のメソッドを呼んでその時に処理します。したがって、メソッドの呼び出し回数は上の書き方よりも1回増えます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む