メモの日々


2013年09月08日(日) [長年日記]

[java] java.util.PropertiesでWindowsのパス表記をエスケープせずに読み込みたい

問題点

Javaのプログラムで

path = C:\aaa\bbb bbb\あああ\ccc

とWindowsのパス表記で書いたファイルを次のコードのようにしてjava.util.Properties#load()を使って読み込むと、

C:aaabbb bbbあああccc

とバックスラッシュが削除されてしまう。

import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;

public class PropertiesでのWindowsパス表記読込 {
    public static void main(String[] args) throws IOException {
        try (Reader r = Files.newBufferedReader(
                Paths.get("test.properties"), Charset.forName("UTF-8"))) {
            Properties p = new Properties();
            p.load(r);
            System.out.println(p.getProperty("path"));
        }
    }
}

こうなるのは、load()メソッドが

このメソッドは、無効なエスケープ文字の前のバックスラッシュ文字 \ をエラーとして処理しない。バックスラッシュは自動的に削除される。

という仕様だから。

この問題はパス区切りのバックスラッシュを2つ重ねて書くかバックスラッシュの代わりにスラッシュを使うようにすれば解決されるんだけど、この対処は面倒だ。そんなことせずにそのまま読み込みたい。

そもそもPropertiesクラスがバックスラッシュを特別扱いする機能って必要なのかな?なんでこんな仕様なんだっけ…

バックスラッシュを二重化するフィルタを挟んで対処してみる

バックスラッシュを特別扱いする機能は要らない(バックスラッシュは単にバックスラッシュとして解釈すればよい)ので、ファイル内の「\」を「\\」に変換するフィルタを作ってそれを挟んで読み込むようにしたらどうだろうか。

import java.io.FilterReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;

public class PropertiesでのWindowsパス表記読込2 {
    public static void main(String[] args) throws IOException {
        try (Reader r = new Backslash二重化フィルタ(Files.newBufferedReader(
                Paths.get("test.properties"), Charset.forName("UTF-8")))) {
            Properties p = new Properties();
            p.load(r);
            System.out.println(p.getProperty("path"));
        }
    }
}

class Backslash二重化フィルタ extends FilterReader {
    private boolean inInBackslash = false;

    public Backslash二重化フィルタ(Reader r) { super(r); }

    @Override
    public int read() throws IOException {
        synchronized (lock) { return read1(); }
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (len <= 0) return 0;
        int result = 0;
        synchronized (lock) {
            for (int i = 0; i < len; ++i) {
                int c = read1();
                if (c < 0) break;
                cbuf[off + i] = (char)c;
                ++result;
            }
        }
        return result == 0 ? -1 : result;
    }

    @Override
    public long skip(long n) throws IOException {
        throw new IOException("not supported");
    }

    @Override
    public boolean ready() throws IOException { return inInBackslash; }

    @Override
    public boolean markSupported() { return false; }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        throw new IOException("not supported");
    }

    @Override
    public void reset() throws IOException {
        throw new IOException("not supported");
    }

    private int read1() throws IOException {
        if (inInBackslash) {
            inInBackslash = false;
            return '\\';
        }
        int result = super.read();
        if (result == '\\') inInBackslash = true;
        return result;
    }
}

このプログラムを動かすと、

C:\aaa\bbb bbb\あああ\ccc

とファイルに書いた文字列がそのまま表示された。OKOK。

こういう対処は見かけないけれど、何か問題あるかな?


2013年09月10日(火) [長年日記]

[java] javax.swing.JTableの列幅を変更する

JTableで列幅を変更する方法がなかなかわからなかったのでメモ。

と列幅を変えられる。

以下サンプル。

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class TableBuilder {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                showTable();
            }
        });
    }

    public static void showTable() {
        JFrame frame = new JFrame();
        frame.add(new JScrollPane(createTable()));
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(400, 300);
        frame.setVisible(true);
    }

    public static JTable createTable() {
        JTable table = new JTable(2, 3);
        table.getColumn("A").setPreferredWidth(50);
        table.getColumn("B").setPreferredWidth(150);
        table.getColumn("C").setPreferredWidth(200);
        return table;
    }
}

テーブルが表示された様子


2013年09月11日(水) [長年日記]

[java] UTF-8はStandardCharsets.UTF_8で指定できる

JavaのプログラムでUTF-8を指定したいときに、

java.nio.charset.Charset.forName("UTF-8")

の代わりに

java.nio.charset.StandardCharsets.UTF_8

と書けることを知ったのでメモ。StandardCharsetsクラスはJava 7で追加されたもののようだ。Pathクラスのjavadocを見ていて気付いた。

[java] java.util.Properties#store() はコメントのマルチバイト文字をエスケープする

Properties#store()はWriterに正しいCharsetを指定しておけばProperteisに設定した日本語をそのまま出力できるけど、store()に与えるcommentsについては日本語を \u 形式でエスケープして出力してしまう、ということをメモ(Java 1.7.0_25で確認)。なんでこうしてるんだ…

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;

public class PropertiesWriter {
    public static void main(String[] args) throws IOException {
        Properties props = new Properties();
        props.setProperty("キー", "値");
        props.setProperty("パス", "c:\\テスト です");
        try (BufferedWriter writer = Files.newBufferedWriter(
                Paths.get("test.txt"), StandardCharsets.UTF_8)) {
            props.store(writer, "コメント");
        }
    }
}
#\u30B3\u30E1\u30F3\u30C8
#Wed Sep 11 17:27:48 JST 2013
パス=c\:\\テスト です
キー=値

2013年09月19日(木) [長年日記]

[java] Javaの正規表現でマッチした部分を取り出す

Javaの正規表現は毎回たくさん調べないと書けない。例をメモしておく。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Regex {
    public static void main(String[] args) {
        String target = "abc 01.02";
        Matcher m = Pattern.compile("c (\\d\\d).(\\d\\d)").matcher(target);
        System.out.println(m.matches());
        System.out.println(m.find());
        System.out.println(m.group());
        System.out.println(m.group(1));
        System.out.println(m.group(2));
    }
}
false
true
c 01.02
01
02

2013年09月27日(金) [長年日記]

[java] Javaでシステムの改行コードを取得

以前は System.getProperty("line.separator") で取得していたがJava 7ではSystem.lineSeparator()が使える、ということが検索してもあまり出てこないのでメモ。

[java] JOptionPaneが作るダイアログをESC押下で閉じないようにする

JOptionPaneで作ったダイアログは、ESCキーを押下すると閉じてしまう。この挙動はVIM使いにとってはイラつくケースがある。

JOptionPane optionPane = new JOptionPane();
optionPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
        KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "none");

のようにJOptionPaneのインスタンスのWHEN_IN_FOCUSED_WINDOWに対応するInputMapのESCキー押下に対する要素を上書きしたら、ESCキーを押しても閉じないダイアログを生成できた。

でもこうするのはあまりよくない気がしてきた。閉じる前に確認ダイアログを表示する方がいいか。

[java] JDialogのwindowClosedイベントが複数回来る

JDialogに対してwindowClosedイベントが何回も来るんだけどなんで?困る。Javaのバージョンは1.7.0_25。

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class DialogTest {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                test();
            }
        });
    }

    public static void test() {
        JDialog dialog = new JDialog((JFrame)null, true);
        dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("closing");
            }
            @Override
            public void windowClosed(WindowEvent e) {
                System.out.println("closed");
            }
        });
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);
        dialog.dispose();
    }
}
closing
closed
closed