DATE : 2006/11/07 (Tue)
Maven と maven-jetty-plugin(6.0.0rc4)とで JSF を使ったページを開こうとすると、次のようなエラーが発生する場合があります。
PWC6188: The absolute uri: http://java.sun.com/jsf/html cannot be resolved in either web.xml or the jar files deployed with this application
これは、JSP のコンパイル時に、「http://java.sun.com/jsf/html」という名前空間が web.xml や配備されている JAR ファイルの中に見つからなかったことを示します。
通常、JSF の名前空間やタグを定義した TLD ファイルは JSF の実装に同梱されており、その JAR ファイルを配備すれば上記の名前空間も JAR ファイルから自動的に見つけ出されます。
しかし、プロジェクトが依存するライブラリとして JSF の実装を登録しておいても、上記のエラーは発生することがあります。
どのようにすればエラーを解決できるのでしょうか。ウェブ上を検索した結果、「Maven 2 novamente -> mvn jetty:run」(Urubatan`s Weblog)という記事が見つかりました。しかし、この記事はポルトガル語で書かれているようです。そこで、以下に記事からなんとなく分かった内容をメモしておきます。
プロジェクトが依存するライブラリとして JSF の実装を登録するには、POM ファイルに次のように記述します。(ここでは、JSF の実装として Apache MyFaces を取り上げます。 「...」は省略を表します)
... <dependencies> <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-api</artifactId> <version>1.1.4</version> </dependency> <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-impl</artifactId> <version>1.1.4</version> </dependency> </dependencies> ...
JSP のコンパイル時に JSF の名前空間が見つからないエラーが出る場合は、上の記述を maven-jetty-plugin の設定にコピーします。
例えば、以下がコピー前の maven-jetty-plugin の設定だとすると
... <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <scanIntervalSeconds>10</scanIntervalSeconds> </configuration> </plugin> ...
maven-jetty-plugin が依存するライブラリとして、JSF の実装を登録します。
... <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <dependencies> <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-api</artifactId> <version>1.1.4</version> </dependency> <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-impl</artifactId> <version>1.1.4</version> </dependency> </dependencies> <configuration> <scanIntervalSeconds>10</scanIntervalSeconds> </configuration> </plugin> ...
このようにすることで、maven-jetty-plugin で JSP のコンパイル時に JSF の名前空間が見つからないというエラーを防ぐことができます。
2006-11-22 追記
この現象は、maven-jetty-plugin で起動される Jetty ウェブサーバのバグと分かりました。詳しくは、「maven-jetty-plugin で JSF の名前空間が見つからないのはバグ」をご覧ください。
現状では、まだこのバグは直っていません。
DATE : 2006/10/30 (Mon)
java.lang.CharSequence というインタフェースがあります。
このインタフェースは、Java で文字列を表す String クラスや、可変の文字列データの StringBuilder、char 型のデータ列を表す CharBuffer などが実装しており、文字列データを統一して扱えるという点で便利です。
例えば、正規表現を扱う java.util.regex パッケージでは、操作対象として CharSequence を要求します。つまり、String であっても CharBuffer であっても正規表現を使った操作が可能です。
しかし、CharSequence では equals メソッドが定義されていません。つまり、CharSequence 同士で同じ文字列データを表すかどうかを確かめることはできません。
例えば、あるバイト列が特定の文字列を表すかどうかをテストするコードを書いたとします。
// ・string は比較対象の文字列 // ・byteData は、バイト列を表す ByteBuffer オブジェクト // ・encoding は、文字エンコーディングを表す Charset オブジェクト CharBuffer stringData = encoding.decode(byteData); Assert.assertEquals(string, stringData);
「Assert#assertEquals(Object, Object)」は、JUnit の API で、引数に指定されたオブジェクトが同じものかどうか、Object#equals メソッドを使ってテストします。
しかし、例え stringData に正しく string の文字列データが復元できたとしても、このテストは失敗します。
それでは、テストを成功させるためにはどうすれば良いでしょうか。CharSequence には toString メソッドが定義されています。toString メソッドは CharSequence で表される文字列データを String オブジェクトとして返します。すると、以下のようにすればテストは成功します。
CharBuffer stringData = encoding.decode(byteData); Assert.assertEquals(string, stringData.toString());
そのため、CharSequence 同士を比較する際には、toString メソッドを使って String オブジェクトに変換してから比較することになります。
DATE : 2006/09/16 (Sat)
(前回の記事)
メソッド抽出の結果
これまでに行ってきたリファクタリングの結果を以下に掲載します。
リファクタリング前のコード :
/** * 指定の画像にアルファチャンネルを加えます。 * * @param image アルファチャンネルを加える画像 * @return アルファチャンネルを加えた画画像 */ private static RenderedImage addAlphaChannel(RenderedImage image) { // 対象画像の大きさ int width = image.getWidth(); int height = image.getHeight(); // 対象画像のカラースペース ColorSpace sourceColorSpace = image.getColorModel(). getColorSpace(); // アルファチャンネルのあるカラーモデル ColorModel destColorModel = new ComponentColorModel( sourceColorSpace, true, false, Transparency.BITMASK, DataBuffer.TYPE_BYTE); WritableRaster destRaster = destColorModel. createCompatibleWritableRaster(width, height); // 色成分のバンド番号の配列 int[] colorBandList = getColorBandList(destRaster.getNumBands()); // 色成分のみの WritableRaster WritableRaster colorRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, colorBandList); WritableRaster alphaRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); colorRaster.setRect(image.getData()); alphaRaster.setRect(visibleMaskRaster); return new BufferedImage(destColorModel, destRaster, false, null); } /** * 可視部分のアルファ成分のみを持つ WritableRaster を生成します。 * * @param source 生成対象の画像 * @return 可視部分のアルファ成分のみを持つ WritableRaster */ private static WritableRaster createVisibleAlphaRaster( RenderedImage source) { // 画像の大きさ int width = source.getWidth(); int height = source.getHeight(); // SampleModel SampleModel sampleModel = new BandedSampleModel( DataBuffer.TYPE_BYTE, width, height, 1); WritableRaster raster = Raster.createWritableRaster( sampleModel, new Point(0, 0)); int[] alphaSample = new int[width * height]; // データの最大値を設定(アルファ成分を最大にする) Arrays.fill(alphaSample, 0xff); raster.setSamples(0, 0, width, height, 0, alphaSample); return raster; } /** * 指定のバンド数から、アルファ成分を除いたバンド番号の配列を * 返します。 * * @param numBands バンド数 * @return アルファ成分を除いたバンド番号の配列 */ private static int[] getColorBandList(int numBands) { int[] bands = new int[numBands - 1]; for (int i = 0; i < bands.length; i++) { bands[i] = i; } return bands; }
リファクタリング後 :
private static int ALPHA_VALUE = Integer.MAX_VALUE;
/** * 指定の画像にアルファチャンネルを加えます。 * * @param image アルファチャンネルを加える画像 * @return アルファチャンネルを加えた画画像 */ private static RenderedImage addAlphaChannel(RenderedImage image) { ColorModel destColorModel = createColorModelWithAlpha(image.getColorModel()); WritableRaster destRaster = destColorModel. createCompatibleWritableRaster( image.getWidth(), image.getHeight()); setColorSamples(destRaster, image.getData()); setAlphaSamples(destRaster); return new BufferedImage(destColorModel, destRaster, destColorModel.isAlphaPremultiplied(), null); } /** * アルファチャンネルを追加したカラーモデルを生成します。 * * @param src アルファチャンネルを追加する元となるカラーモデル * @return アルファチャンネルを追加したカラーモデル * @throws NullPointerException src が null の場合 */ private static ColorModel createColorModelWithAlpha(ColorModel src) { return new ComponentColorModel(src.getColorSpace(), true, src.isAlphaPremultiplied(), Transparency.BITMASK, src.getTransferType()); } /** * 指定の WritableRaster の色成分を設定します。 * * @param dest 色成分を設定する WritableRaster * @param src 色成分を表す Raster */ private static void setColorSamples(WritableRaster dest, Raster src) { WritableRaster colorRaster = dest.createWritableChild( 0, 0, dest.getWidth(), dest.getHeight(), 0, 0, getBandListExceptAlpha(dest)); colorRaster.setRect(src); } /** * 指定のバンド数から、アルファ成分を除いたバンド番号の配列を * 返します。 * * @param raster アルファ成分を除いたバンド番号の配列を取得する Raster * @return アルファ成分を除いたバンド番号の配列 */ private static int[] getBandListExceptAlpha(Raster raster) { int[] bandList = new int[raster.getNumBands() - 1]; for (int i = 0; i < bandList.length; i++) { bandList[i] = i; } return bandList; } /** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param raster アルファ成分を設定する WritableRaster */ private static void setAlphaSamples(WritableRaster raster) { int alphaBand = raster.getNumBands() - 1; for (int y = 0; y < raster.getHeight(); y++) { for (int x = 0; x < raster.getWidth(); x++) { raster.setSample(x, y, alphaBand, ALPHA_VALUE); } } }
(つづきます)
DATE : 2006/09/15 (Fri)
(前回の記事)
メソッドの抽出 (3)
前回に続いてさらに、「4 : 2の WritableRaster のアルファ成分を設定」の部分
WritableRaster alphaRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); alphaRaster.setRect(visibleMaskRaster);
を setAlphaSamplesメソッドに抽出します。
/** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param image アルファ成分を追加する元となる画像 * @param dest アルファ成分を追加する WritableRaster */ private static void setAlphaSamples( RenderedImage image, WritableRaster dest) { WritableRaster alphaRaster = dest. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); alphaRaster.setRect(visibleMaskRaster); }
しかし、メソッドを抽出していてふと思いました。上のメソッドでは、アルファ成分のみの Raster を生成して、WritableRaster のアルファ成分として設定しています。ところが、ここで設定するアルファ値は全画素で共通のはずです。すると、わざわざアルファ成分のみの Raster を生成するよりも、直接アルファ成分を設定した方がコードは簡単になります。
そこで、上のコードを、直接アルファ成分を設定するように変更します。
/** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param raster アルファ成分を設定する WritableRaster */ private static void setAlphaSamples(WritableRaster raster) { int alphaBand = raster.getNumBands() - 1; for (int y = 0; y < raster.getHeight(); y++) { for (int x = 0; x < raster.getWidth(); x++) { raster.setSample(x, y, alphaBand, 0xff); } } }
ここでは、最大のアルファ値として 0xff を設定しています。ところが、1バンクの幅が1バイトを超えると、0xff は最大のアルファ値とはなりません。例えば、WritableRaster(Raster)の転送型が java.awt.image.DataBuffer#TYPE_INT の場合は、符号付き4バイト値(int型)でアルファ値が表されることになります。そのため、0xff を int 型の最大値に変更します。(バンク幅を超えた値は、バンク幅に合わせてビットが切り捨てられます)
raster.setSample(x, y, alphaBand, Integer.MAX_VALUE);
また、Integer.MAX_VALUE に「アルファ値」としての意味を持たせるため、定数として抽出します。
結果、setAlphaSamples メソッドは次のようになります。
private static final int ALPHA_VALUE = Integer.MAX_VALUE;
/** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param raster アルファ成分を設定する WritableRaster */ private static void setAlphaSamples(WritableRaster raster) { int alphaBand = raster.getNumBands() - 1; for (int y = 0; y < raster.getHeight(); y++) { for (int x = 0; x < raster.getWidth(); x++) { raster.setSample( x, y, alphaBand, ALPHA_VALUE); } } }
初めに抽出したメソッドと比べて、引数が1つ減りました。
また、createVisibleAlphaRaster メソッドを使う必要がなくなりました。そのため、このメソッドは消去しておきます。
なお、「4 : 2の WritableRaster のアルファ成分を設定」の部分は次のように変わります。
setAlphaSamples(destRaster);
最後に、「5 : BufferedImage の生成」の部分です。
return new BufferedImage(destColorModel, destRaster, false, null);
しかし、この部分はコメントの通り BufferedImage を生成しているだけなので、メソッドには抽出しません。
ただ、アルファの乗算状態を false と決め打ちにしているところは気になります。そこでこの部分を、処理対象の画像のカラーモデルと同じ状態になるように変更します。
return new BufferedImage(destColorModel, destRaster, destColorModel.isAlphaPremultiplied(), null);
(つづきます)
DATE : 2006/09/14 (Thu)
(前回の記事)
メソッドの抽出 (2)
前回に続いて、「3 : 2の WritableRaster の色成分を設定」の部分
// 色成分のバンド番号の配列 int[] colorBandList = getColorBandList(destRaster.getNumBands()); // 色成分のみの WritableRaster WritableRaster colorRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, colorBandList); colorRaster.setRect(image.getData());
を、setColorSamples メソッドに抽出します。
/** * 指定の WritableRaster の色成分を設定します。 * * @param dest 色成分を設定する WritableRaster * @param src 色成分を表す Raster */ private static void setColorSamples(WritableRaster dest, Raster src) { WritableRaster colorRaster = dest.createWritableChild( 0, 0, dest.getWidth(), dest.getHeight(), 0, 0, getColorBandList(dest.getNumBands())); colorRaster.setRect(src); }
colorBandList に格納する配列を生成するメソッド getColorBandList を、dest.createWritableChild メソッドの引数に埋め込みました。
ここで、 getColorBandList というメソッド名は分かりにくいので、分かりやすい名前に変更します。このメソッドの中身は、次の通りでした。
private static int[] getColorBandList(int numBands) { int[] bands = new int[numBands - 1]; for (int i = 0; i < bands.length; i++) { bands[i] = i; } return bands; }
アルファ成分を格納するバンドはバンド列中の最後となっています。そのため、アルファ成分のバンドを除いたバンド列の配列を返します。「アルファ成分のバンドを除く」という部分を強調して、getColorBandList から getBandListExceptAlpha にメソッド名を変更します。
また、バンド列を扱うのは Raster(WritableRaster)のみなので、getBandListExceptAlpha メソッドの引数は Raster にして、メソッドの中でバンド数を取得することにします。
/** * 指定のバンド数から、アルファ成分を除いたバンド番号の配列を * 返します。 * * @param raster アルファ成分を除いたバンド番号の配列を取得する Raster * @return アルファ成分を除いたバンド番号の配列 */ private static int[] getBandListExceptAlpha(Raster raster) { int[] bandList = new int[raster.getNumBands() - 1]; for (int i = 0; i < bandList.length; i++) { bandList[i] = i; } return bandList; }
すると、setColorSamples メソッドは次のようになります。
/** * 指定の WritableRaster の色成分を設定します。 * * @param dest 色成分を設定する WritableRaster * @param src 色成分を表す Raster */ private static void setColorSamples(WritableRaster dest, Raster src) { WritableRaster colorRaster = dest.createWritableChild( 0, 0, dest.getWidth(), dest.getHeight(), 0, 0, getBandListExceptAlpha(dest)); colorRaster.setRect(src); }
そして、「3 : 2の WritableRaster の色成分を設定」の部分は次のように変わります。
setColorSamples(destRaster, image.getData());
(つづきます)