DATE : 2013/03/14 (Thu)
Javaソースコードのカバレッジ計測ツールのEMMAには、HTMLレポートの生成時にJavaソースコードの文字エンコーディングを指定する機能がありません。そこで、HTMLレポート生成時に、ソースコードの文字エンコーディングを指定できるようにしたパッチを作成しました。パッチは本家に投稿済みであり、Android SDK版についてはGitHubにもアップロードしています。
現在最新バージョンのEMMA(2.0.5312)では、HTMLレポートを生成する際に、ソースコードの文字エンコーディングを指定することができません。プラットフォームのデフォルト文字エンコーディングがソースコードの文字エンコーディングとして使用されるため、両者が一致していない場合はHTMLレポートが文字化けしてしまいます。例えば日本語版Windowsのデフォルト文字エンコーディングはShift JISのため、それ以外の文字エンコーディングで書かれたソースコードからHTMLレポートを生成すると、文字化けしてしまいます。
そこで、ソースコードの文字エンコーディングを指定できるようにしたパッチを作りました。本パッチを適用したEMMAでは、以下のいずれかの方法でソースコードの文字エンコーディングを指定することができます。
- 「EMMA report generation property」としてreport.source.encodingキーを追加したため、その値として文字エンコーディングを指定する。
- Ant向けに、html要素にsourceencoding属性を追加したため、その値として文字エンコーディングを指定する。
本パッチは、本家に投稿済みです。また、Android SDKに含まれているEMMAについても本パッチを適用し、結果をGitHubにアップロードしました。EMMAプロジェクトは現在休止中であるため、マージが待てない方は本パッチをご自由に(ただし自己責任で)お使いください。
DATE : 2013/01/26 (Sat)
ビルド時にJDK 1.4が必要なライブラリをビルドしようと64ビット版XubuntuにJDK 1.4をインストールしようとしたのですが、「./install.sfx.8648: not found」というエラーメッセージとともにインストーラが停止してしまいました。x64用Linux版JDK 1.4はOracleのサイトのアーカイブにはなく、x32用しかありません。そのためx32用JDKをダウンロードしてインストーラを実行したのですが、「./install.sfx.8648: not found」というエラーメッセージが出力され、インストールできなかったのです。
そこで調べてみると、g++-multilibパッケージをインストールすると32ビット用JDKを64ビット版Linuxにインストールできることがわかりました。g++-mutilibとは、g++に標準で備わっているアーキテクチャのサポートをさらに追加するためのパッケージで、このパッケージをインストールすることで64ビット版Linuxでも32ビット向けプログラムをビルドできるようになります。
実際にg++-multilibをインストールし、再びJDK 1.4のインストーラを起動したところ、無事にJDK 1.4をインストールできました。前述のライブラリのビルドにも成功しました。
参考文献
DATE : 2012/02/23 (Thu)
Javaでは、メソッドやクラスなどをパッケージスコープとする際にアクセス修飾子を省略します。しかしソースコードの作者以外の人から見ると、アクセス修飾子を省略したのかそれとも書き忘れたのか、判断できません。そこでパッケージスコープであると明示的に示そうと、「/* package */」のようなコメントをアクセス修飾子がない代わりに入れることがあります。例えば、パッケージスコープなメソッドは次のように書きます。
/* package */ void method() { ... }
Eclipseの一部やAndroidに標準搭載されているブラウザアプリケーションや設定アプリケーションなどがそのようなコーディングルールで記述されています。
Javaで書かれたソースコードがコーディング規約に従っているかどうかをチェックする際には、Checkstyleがよく使用されます。しかし、本記事の執筆時点の最新バージョンであるCheckstyle 5.5では、パッケージスコープを表すコメントの有無をチェックできません。
そこで今回、パッケージスコープを表すコメントの有無をチェックするCheckstyleプラグインを作ってみました。GitHubで公開しています。ライセンスはCheckstyleと同じLGPLです。Checkstyle 5.5以上に対応しています。
本プラグインでは、アクセス修飾子のないクラスなどの定義にパッケージスコープを表すコメントが書かれていることをチェックし、書かれていなければ警告します。加えて、アクセス修飾子が書かれているにもかかわらず以下のようにパッケージスコープを表すコメントがある場合に警告を行います。
/* package */ public void method() { ... }
使い方は次の通りです。まず、Checkstyleの設定ファイルに本プラグインのモジュールを追加します。本プラグインのモジュールは、TreeWalkerモジュールのサブモジュールです。例えば、次のようになります。
<module name="TreeWalker"> <module name="CommentedPackageVisibilityCheck" /> </module>
クラスパスに本プラグインのJARファイルを追加して、Checkstyleを実行してください。
JARファイルは、「commented-package-visibility-check-0.1.zip」をダウンロードして展開すると手に入ります。
CommentedPackageVisibilityCheckモジュールには、以下のプロパティを用意しています。必要に応じて設定してください。
プロパティ名 | 説明 | 省略時の値 |
---|---|---|
format | パッケージスコープを表すコメントにマッチする正規表現。 | /\* package \*/ |
requireLatterWhiteSpace | パッケージスコープを表すコメントの直後に、少なくとも1文字以上の空白が必要か。 | true |
DATE : 2010/01/13 (Wed)
ある日、大文字が含まれているかもしれない文字列を全て小文字に統一したいという目的からJava SE 6のAPIドキュメントをなんとなく眺めていました。すると、String.toLowerCase()の解説に次のような記述がありました。
注: このメソッドはロケールに依存するため、ロケールが単独で解釈されるような文字列に使用すると、予期せぬ結果を引き起こすことがあります。例として、プログラミング言語識別子、プロトコルキー、HTML タグがあります。たとえば、トルコ語ロケールの "TITLE".toLowerCase() は "t?tle" を返します。ここで、「?」は点のないラテン小文字の I の文字です。ロケールに依存しない文字列の正しい結果を取得するには、toLowerCase(Locale.ENGLISH) を使用します。
つまりトルコ語ロケールの環境下では、小文字変換で「TITLE」は「title」にならず、大文字変換でも「title」は「TITLE」になりません。トルコ語(Wikipedia)に掲載されているトルコ語でのアルファベッドによると、トルコ語では「i」が「İ」(上に点のあるI)、「I」が「ı」(点のないi)に変換されます。
するとロケールによって変換結果が異なっては困る処理でString.toLowerCase()およびString.toUpperCase()を使用すると、ロケールによっては正しく動作しないという現象が発生します。この問題を解決するには、APIドキュメントにもあるとおり、引数にLocale.ENGLISHを指定して、変換対象のロケールを指定できるString.toLowerCase(Locale)やString.toUpperCase(Locale)を使用します。
それでは大文字・小文字を区別せずに比較するString.equalsIgnoreCase(String)はどうなのでしょうか。結論から言えば、String.equalsIgnoreCase(String)はどのロケールであっても結果が同じとなります。
String.equalsIgnoreCase(String)のAPIドキュメントには以下のような記述があります。
この String とほかの String を比較します。大文字と小文字は区別されません。長さが同じで、2 つの文字列内の対応する文字が大文字と小文字の区別なしで等しい場合、2 つの文字列は大文字と小文字の区別なしで等しいと見なされます。
次のどれかに該当する場合に、c1 と c2 という 2 つの文字は大文字小文字の区別なしで等しいと見なされます。
- 2 つの文字が等しい (== 演算子による比較)
- Character.toUpperCase(char) メソッドをそれぞれの文字に適用すると同じ結果になる
- Character.toLowerCase(char) メソッドをそれぞれの文字に適用すると同じ結果になる
String.equalsIgnoreCase(String)で用いられているのは、String.toLowerCase(), String.toUpperCase()ではなくCharacter.toLowerCase(char), Character.toUpperCase(char)です。
Character.toLowerCase(char)のAPIドキュメントには以下のように書かれています。
UnicodeData ファイル内のケースマッピング情報を使用して、文字引数を小文字に変換します。
(中略)
通常、String.toLowerCase() は、文字を小文字にマップするときに使用する必要があります。String ケースマッピングメソッドは、Character ケースマッピングメソッドと比べて複数の利点があります。String ケースマッピングメソッドは、ロケール依存のマッピング、コンテキスト依存のマッピング、および 1:M 文字マッピングを実行できるのに対し、Character ケースマッピングメソッドはそのようなマッピングを実行できません。
ここで重要なのは「通常、String.toLowerCase() は」で始まる部分です。ここにString.toLowerCase()とCharacter.toLowerCase(char)の違いが明記されています。
つまりCharacter.toLowerCase(char), Character.toUpperCase(char)はロケールに依存せず、どのようなロケールでも同じ結果となります。そのためString.equalsIgnoreCase(String)も同様にロケールに依存せず、どのようなロケールでも同じ結果となると言えます。
初めはメソッド名が同じなので、String.toLowerCase(), String.toUpperCase()とCharacter.toLowerCase(char), Character.toUpperCase(char)とが同じと勘違いしてしまいました。
(;^ω^)クラス名やメソッド名が似ているからと言って処理が同じだと判断せずに、ドキュメントはきちんと読むべきでした。
DATE : 2009/06/21 (Sun)
Mapから値を取り出すと同時にオートアンボクシングを使用する場合は、nullが出てくる可能性を考えなければなりません。
Mapからboolean値を取り出してif文で条件分岐をさせるコードを書いたら、NullPointerExceptionが発生してしまいました。NullPointerExceptionが発生した箇所は下記の通りです。
private void function(String key, Map<String, Boolean> map) { ... if (map.get(key)) { // NullPointerException発生 .... } ... }
初めは、MapがnullだったためNullPointerExceptionが発生したのかと思いました。しかし動作を調べてみても、Mapにはきちんとインスタンスが割り当てられていました。
実は、NullPointerExceptionが発生していたのは、ラッパークラスのBooleanオブジェクトからプリミティブ値のboolean型へと変換を行うオートアンボクシングの部分でした。NullPointerExceptionが発生した箇所の処理を細かく見ていくと、以下のようになります。
- 「map.get(key)」でBooleanオブジェクトを取り出す。
- if文の評価にはプリミティブ値のboolean値が必要。
- そのため、取り出されたBooleanオブジェクトをプリミティブ値のboolean値にオートアンボクシングする。
問題だったのは、Mapから取り出したBooleanオブジェクトがnullになっていたことでした。キーに対応づく値がMapに存在しなかったためです。nullオブジェクトをアンボクシングしようとすると、NullPointerExceptionが発生します。だから上記の箇所でNullPointerExceptionが発生していたのです。
こういう場合、オートアンボクシングでNullPointerExceptionが発生しないようにするには、Mapから値を取り出す際にはオートアンボクシングせず、取り出した値がnullではないか確かめるほうが安全です。
(;^ω^)問題が発生した書き方だとコードがやけにすっきりするので、nullのことをつい忘れてしまいました。