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で列幅を変更する方法がなかなかわからなかったのでメモ。
- JTable#getColumn() などで得られるTableColumnオブジェクトのsetPreferredWidth()を呼び出す
と列幅を変えられる。
- getColumn()の引数には、デフォルトでは列名を指定すればいいみたい。
- 特定の列に対してだけsetPreferredWidth()を呼び出すと思い通りの幅にならない。これはTableColumnオブジェクトのデフォルトの幅が75だからのようだ。
以下サンプル。
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