Illegal group reference

自前のサイトで今までにない例外(Illegal group reference)が発生していたので調査していたら、どうも「replaceAll」によるものみたい。

文字列の置換は「replace」と「replaceAll」の二つがあるのだけど、replaceだと最初の一文字だけ置換かと思って、いままではすべて変換は「replaceAll」を使っていた。
でもどうもどちらもすべて変換してくれるようです。

違いは、replaceは正規表現ではなく、replaceAllは正規表現であるということみたいです。

では、なぜ「replaceAll」は上記例外が発生しているか?
それは置換後文字列に「$」が入っているためでした。
正規表現では、「(」と「)」で結ばれた文字を、置換後文字列の「$1」「$2」で置き換えるということができるのでそのために「$」を検出したら置き換えをしようとしている模様。
「$」の後ろが数字じゃなかったら無視とかできないのだろうか・・・
または「(」や「)」がなかったら無視とか・・・。とjava内部のことに文句を言ってもしかたがないので「replace」を利用するか、replaceで置換元を「\\$」に置換してから「replaceAll」するしか方法がないもよう。

参考ページ
http://d.hatena.ne.jp/srkzhr/20090529/1243614146
http://app.dimage.co.jp/atblog/trackback/4d2e7bd33c4757841199381a64e43e50922f

各キャリアの絵文字

ソース上、DB上にimode絵文字があるとmobyletで各キャリア用の絵文字になるのはfirefoxで確認したのだが、
formからimode絵文字を入力し、DBに保存し、そのデータを再び呼び出しformで修正しようとすると
3キャリアともに以下のようなhtmlタグが出てしまう。

<img src="chrome://msim/content/emoji/e/51.gif" alt="" border="0" width="15" height="15" />

 ※firefoxのシュミレーションで確認

なんか表現が難しいけどこんな感じ
formで絵文字入力 → java → DB保存
formから絵文字のあるデータを修正依頼 → java → DBから呼び出し → 表示

表示自体は問題ないけど、textboxでは絵文字が表現できていないっぽい。

追記:
 firefoxでは完全なシュミレーションは無理みたい。

時間関数の備忘録

import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
 * 時間関数の備忘録
 */
public class Timesample {
	public static void main(String[] args) throws ParseException {
		Timestamp      ts;
		java.util.Date utildate;
		java.sql.Date  sqldate;
		String sDate;

		// 文字列(yyyymmddhhmmss) → Timestamp
		DateFormat dateformat = new SimpleDateFormat("yyyyMMddhhmmss");
		ts = new Timestamp(dateformat.parse("20090716010203").getTime());

		// 文字列(yyyymddhhmmss) → Date
		utildate = new java.util.Date(dateformat.parse("20090716010203").getTime());
		sqldate  = new java.sql.Date(dateformat.parse("20090716010203").getTime());

		// 文字列(yyyymmddhhmmss) → Calendar
		java.util.Date date = new java.util.Date( dateformat.parse("20090716010203").getTime());
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);

		// Timestamp → Date
		utildate = new java.util.Date(ts.getTime());
		sqldate  = new java.sql.Date(ts.getTime());

		// Date → Timestamp
		ts = new Timestamp(utildate.getTime());
		ts = new Timestamp(sqldate.getTime());

		// Date → 文字列
		SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		sDate = sdf1.format(utildate);
		System.out.println("utildate="+sDate);
		sDate = sdf1.format(sqldate);
		System.out.println("sqldate ="+sDate);

		// Timestamp → 文字列
		sDate = sdf1.format(ts);
		System.out.println("timestamp="+sDate);

		// 1日前の日付
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DATE, -1);
		utildate = cal.getTime();
		sqldate  = new java.sql.Date(utildate.getTime());
		sDate = sdf1.format(utildate);
		System.out.println("1日前util:" + sDate);
		sDate = sdf1.format(sqldate);
		System.out.println("1日前sql :" + sDate);

		// 1年前の日付
		cal.add(Calendar.YEAR, -1);
		// 1ヶ月前の日付
		cal.add(Calendar.MONDAY, -1);
		// 1時間前
		cal.add(Calendar.HOUR_OF_DAY, -1);
		// 1分前
		cal.add(Calendar.MINUTE, -1);
		// 1秒前
		cal.add(Calendar.SECOND, -1);

		// 月末の日付
		cal.set(Calendar.DATE, 1);   // 日付を1日にする
		cal.add(Calendar.MONTH, 1);  // 月を1月後にする
		cal.add(Calendar.DATE, -1);  // 1日戻して月末に
		System.out.println("月末日:" + cal.get(Calendar.DATE));
		System.out.println("月末日:" + cal.getActualMaximum(Calendar.DAY_OF_MONTH));
	}
}

UTF-8からJIS,SJIS,EUCJPに文字コード変換すると文字化けするの巻き

UTF-8で「〜」や「‖」などが文字コード変換すると「?」に文字化けするのです。
かなり嵌ってしまいました・・・
いろいろ調べているとほかにも困っている人が多いようでした。
なので、sourceforgeで化けないように変換するソースが公開されていました。
http://sourceforge.jp/cvs/view/nagaraichat/iCC/ome/ome-core/OME_JavaProject/old-src/Cp932.java?revision=1.1.1.1&view=markup

リンク切れ防止にソースを貼り付けておく
toJisの引数の文字列(UTF-8)の文字化けするものを化けない文字に変換して返してくれる。

String jis = new String( Cp932.toJIS("〜").getBytes( "JIS" ));
package OME;

/*
 * The Cp932 class contains a utility method for converting Microsoft's
 * Cp 932 into JIS.
 * 
 * http://java-house.etl.go.jp/ml/archive/j-h-b/014452.html
 *
 * @author Kazuhiro Kazama
 * @version 1.0 01/06/97
 */
import java.util.Locale;

public class Cp932 {

    private static boolean isCp932 = false;

    static {
        String p = System.getProperty("iscp932");
        String os = System.getProperty("os.name");
        if (Locale.getDefault().getLanguage().equals("ja")) {
            isCp932 = true;
            /*	    if (p != null && Boolean.getBoolean(p))
             isCp932 = true;
             else if (os != null && (os.equals("Windows 95") || os.equals("Windows NT")))
             isCp932 = true;
             */}
        //        Logging.writeMessage("Cp932 : p = "+p);
        //        Logging.writeMessage("Cp932 : os = "+os);
        //        Logging.writeMessage("Cp932 : isCp932 = "+isCp932);
    }

    /*
     * You can't use this constructor.
     */
    private Cp932() {}

    /*
     * This method converts Cp932 to JIS.
     */
    public static String toJIS(String s) {
        if (!isCp932) return s;
        StringBuffer sb = new StringBuffer();
        char c;
        for (int i = 0; i < s.length(); i++) {
            c = s.charAt(i);
            switch (c) {
            case 0xff3c:
                // FULLWIDTH REVERSE SOLIDUS ->
                c = 0x005c; // REVERSE SOLIDUS
                break;
            case 0xff5e:
                // FULLWIDTH TILDE ->
                c = 0x301c; // WAVE DASH
                break;
            case 0x2225:
                // PARALLEL TO ->
                c = 0x2016; // DOUBLE VERTICAL LINE
                break;
            case 0xff0d:
                // FULLWIDTH HYPHEN-MINUS ->
                c = 0x2212; // MINUS SIGN
                break;
            case 0xffe0:
                // FULLWIDTH CENT SIGN ->
                c = 0x00a2; // CENT SIGN
                break;
            case 0xffe1:
                // FULLWIDTH POUND SIGN ->
                c = 0x00a3; // POUND SIGN
                break;
            case 0xffe2:
                // FULLWIDTH NOT SIGN ->
                c = 0x00ac; // NOT SIGN
                break;
            }
            sb.append(c);
        }
        return new String(sb);
    }

    /*
     * This method convert JIS to Cp932.
     */
    public static String toCp932(String s) {
        if (!isCp932) return s;
        StringBuffer sb = new StringBuffer();
        char c;
        for (int i = 0; i < s.length(); i++) {
            c = s.charAt(i);
            switch (c) {
            case 0x005c:
                // REVERSE SOLIDUS ->
                //		c = 0xff3c;	// FULLWIDTH REVERSE SOLIDUS
                c = 0x00a5; //Yen Mark ( by msyk )
                break;
            /*	不要みたい、これ
             case 0x301c:	// WAVE DASH ->
             c = 0xff5e;	// FULLWIDTH TILDE
             break;
             */
            case 0x2016:
                // DOUBLE VERTICAL LINE ->
                c = 0x2225; // PARALLEL TO
                break;
            /*	これも不要みたい
             case 0x2212:	// MINUS SIGN ->
             c = 0xff0d;	// FULLWIDTH HYPHEN-MINUS
             break;
             */case 0x00a2:
                // CENT SIGN ->
                c = 0xffe0; // FULLWIDTH CENT SIGN
                break;
            case 0x00a3:
                // POUND SIGN ->
                c = 0xffe1; // FULLWIDTH POUND SIGN
                break;
            case 0x00ac:
                // NOT SIGN ->
                c = 0xffe2; // FULLWIDTH NOT SIGN
                break;
            }
            sb.append(c);
        }
        return new String(sb);
    }
}

SimpleWhereには次のメソッドがあります。

メソッド 説明
eq(CharSequence propertyName, Object value) propertyName = ? の条件を追加します。valueがnullの時は追加されません。
ne(CharSequence propertyName, Object value) propertyName <> ? の条件を追加します。valueがnullの時は追加されません。
lt(CharSequence propertyName, Object value) propertyName < ? の条件を追加します。valueがnullの時は追加されません。
le(CharSequence propertyName, Object value) propertyName <= ? の条件を追加します。valueがnullの時は追加されません。
gt(CharSequence propertyName, Object value) propertyName > ? の条件を追加します。valueがnullの時は追加されません。
ge(CharSequence propertyName, Object value) propertyName >= ? の条件を追加します。valueがnullの時は追加されません。
in(CharSequence propertyName, Object... values) propertyName in (?, ...) の条件を追加します。 valuesがnullの時または配列の長さが0の時は追加されません。
in(CharSequence propertyName, Collection values) propertyName in (?, ...) の条件を追加します。 valuesがnullの時またはリストの長さが0の時は追加されません。
notIn(CharSequence propertyName, Object... values) propertyName not in (?, ...) の条件を追加します。 valuesがnullの時または配列の長さが0の時は追加されません。
notIn(CharSequence propertyName, Collection values) propertyName not in (?, ...) の条件を追加します。 valuesがnullの時またはリストの長さが0の時は追加されません。
like(CharSequence propertyName, String value) propertyName like ? の条件を追加します。 valueがnullの時は追加されません。
like(CharSequence propertyName, String value, char escape) propertyName like ? escape ? の条件を追加します。 valueがnullの時は追加されません。
notLike(CharSequence propertyName, String value) propertyName not like ? の条件を追加します。 valueがnullの時は追加されません。
notLike(CharSequence propertyName, String value, char escape) propertyName not like ? escape ? の条件を追加します。 valueがnullの時は追加されません。
starts(CharSequence propertyName, String value) propertyName like ? の条件を追加します。 valueがnullの時は追加されません。 valueの最後に自動的に % が追加されます。 valueに '%', '_' が含まれる場合はエスケープされます。
notStarts(CharSequence propertyName, String value) propertyName not like ? の条件を追加します。 valueがnullの時は追加されません。 valueの最後に自動的に % が追加されます。 valueに '%', '_' が含まれる場合はエスケープされます。
ends(CharSequence propertyName, String value) propertyName like ? の条件を追加します。 valueがnullの時は追加されません。 valueの最初に自動的に % が追加されます。 valueに '%', '_' が含まれる場合はエスケープされます。
notEnds(CharSequence propertyName, String value) propertyName not like ? の条件を追加します。 valueがnullの時は追加されません。 valueの最初に自動的に % が追加されます。 valueに '%', '_' が含まれる場合はエスケープされます。
contains(CharSequence propertyName, String value) propertyName like ? の条件を追加します。 valueがnullの時は追加されません。 valueの最初と最後に自動的に % が追加されます。 valueに '%', '_' が含まれる場合はエスケープされます。
notContains(CharSequence propertyName, String value) propertyName not like ? の条件を追加します。 valueがnullの時は追加されません。 valueの最初と最後に自動的に % が追加されます。 valueに '%', '_' が含まれる場合はエスケープされます。
isNull(CharSequence propertyName, Boolean value) propertyName is null の条件を追加します。 valueがnullあるいはBoolean.FALSEの時は追加されません。
isNotNull(CharSequence propertyName, Boolean value) propertyName is not null の条件を追加します。 valueがnullあるいはBoolean.FALSEの時は追加されません。
excludesWhitespace() valueが空白文字列の時は条件に追加されません。

InterceptorのHttpServletRequestの取得

InterceptorはHotDeployではなくSingletonなのでHttpServletRequestがDIがされないみたいです。いくらdiconとかいらっても無駄みたいです・・・
seasar2の公式ページにdiconファイルを設定してという内容があるので一生懸命弄ったけど無駄だったみたいです・・・
http://www.seasar.org/wiki/index.php?FAQ%2FS2AOP#q69751d4
いくらやっても無駄だったのでスマートではないけれども以下のようにして対応した。

	private S2Container container;

	public void setS2Container(S2Container container){
		this.container = container;
	}

	private HttpServletRequest getRequestFromContainer() {
		HttpServletRequest request = (HttpServletRequest)container.getComponent(HttpServletRequest.class);
		return request;
	}

	public final Object handleThrowable(final Exception e, final MethodInvocation invocation) throws Throwable {
		HttpServletRequest request = getRequestFromContainer();
		if (request == null) {
			e.printStackTrace();
		} else {
			Utils.procException(request, e);

			return "/error.jsp";
		}
		return invocation.proceed();
	}

追記
seasar2のMLに問い合わせてみたところ、以下のようにするといいらしいです。
基本的に「http://www.seasar.org/wiki/index.php?FAQ%2FS2AOP#q69751d4」のやり方なのです。

<components>
  <include path="default-customizer.dicon"/>
  <component name="formCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain"/>
  <component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
    <!-- ここから -->
    <initMethod name="addCustomizer">
      <arg>
        <component class="org.seasar.framework.container.customizer.AspectCustomizer">
          <property name="useLookupAdapter">true</property>
          <property name="interceptorName">"myThrowableInterceptor"</property>
        </component>

      </arg>
    </initMethod>
    <!-- ここまで -->
    <initMethod name="addAspectCustomizer">
      <arg>"actionMessagesThrowsInterceptor"</arg>
    </initMethod>
public class MyThrowableInterceptor extends ThrowsInterceptor {
	private static final long serialVersionUID = 5306143265122341227L;

	@Resource private  HttpServletRequest request;

	public final String handleThrowable(final Exception e, final MethodInvocation invocation) throws Throwable {

		Utils.procException(request, e);

		return "/error.jsp";
	}
}
package miraque.utils;

public class Utils {
	public static void procException(final HttpServletRequest request, final Exception e) {

		// 例外詳細内容を取得
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		e.printStackTrace(pw);

		// 呼び出し関数を取得
		String[] methods = request.getParameterValues("SAStruts.method");
		String method = "";
		if (methods != null) {
			method = "/" + methods[0];
		}

		String body = "";
		body += "リクエスト元\n";
		body += request.getRequestURI() + method + "\n\n";
		body += "リクエスト内容\n";
		try {
			body += Utils.getAllParameter(request);
		} catch (Exception e1) {
			e.printStackTrace();
		}
		body += "=====================================================================\n";
		try {
			body += Utils.getAllHeader(request);
		} catch (Exception e1) {
			e.printStackTrace();
		}
		body += "=====================================================================\n";
		body += sw.toString();

		// 例外を標準出力へ
		e.printStackTrace();

		MailTransfer.send(body);
	}
}

それでもって@Aspectをなくするといいらしい。@Aspectでインターセプターを設定すると2回呼び出されてしまい。@Aspectで呼び出されたインターセプターにはHttpServletRequestがNULLになるようです。

例外のインターセプト

ログ出力インターセプトの続きとして例外発生時のインターセプトの実装。
これもdiconは一切弄る必要なくできる。

インターセプト

/**
 * 例外発生時にメール送信するクラス.
 *
 * @author zoi
 *
 */
public class MyThrowableInterceptor extends ThrowsInterceptor {
	/** シリアルバージョン. */
	private static final long serialVersionUID = 5306143265122341227L;

	/** サーブレットへのリクエスト内容. */
	public HttpServletRequest request;

	/**
	 * 例外インターセプター.
	 *
	 * @param e           スローされた例外内容
	 * @param invocation  スロー元のメソッド名
	 * @return            エラーページのファイル名
	 * @throws Throwable  スローされる例外
	 */
	public final String handleThrowable(final Exception e, final MethodInvocation invocation) throws Throwable {

		Utils.procException(request, e);

		return "/error.jsp";
	}
}

Utils.java
	/**
	 * 例外発生時にエラーページを表示してメール送信.
	 *
	 * @param request リクエスト内容
	 * @param e       スローされた例外内容
	 */
	public static void procException(final HttpServletRequest request, final Exception e) {
		ActionMessages errors = new ActionMessages();

		errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("errors.exception"));
		ActionMessagesUtil.addErrors(request, errors);

		// 例外詳細内容を取得
		StringWriter sw = new StringWriter();
	    PrintWriter pw = new PrintWriter(sw);
	    e.printStackTrace(pw);

	    // 呼び出し関数を取得
	    String[] methods = request.getParameterValues("SAStruts.method");
		String method = "";
		if (methods != null) {
			method = "/" + methods[0];
		}

	    String body = "";
	    body += "リクエスト元\n";
	    body += request.getRequestURI() + method + "\n\n";
	    body += "リクエスト内容\n";
		try {
			body += Utils.getAllParameter(request);
		} catch (Exception e1) {
			e.printStackTrace();
		}
	    body += "=====================================================================\n";
	    try {
			body += Utils.getAllHeader(request);
		} catch (Exception e1) {
			e.printStackTrace();
		}
	    body += "=====================================================================\n";
	    body += sw.toString();

	    // 例外を標準出力へ
	    e.printStackTrace();

		MailTransfer.send(body);
	}

error.jsp
<font color="red">
	<ul>
		<li>想定外のエラーが発生しました。エラー内容を管理人にメールしました。解決までしばらくお待ちください。</li>
	</ul>
</font>

後はポイントカットするクラスに

@Aspect("myThrowableInterceptor")
public class IndexAction {

とすれば出来上がり。設定ファイルがなくなることでかなり楽になるけれども、初めの第一歩はやはり大変です^^;

追記:
 サーバにcooldeployで更新したら、NULLPOINTER例外が・・・多分requestがDIされていない?
そのようなことがNETにはいろいろあるんで試行錯誤してみたけれども、解決できなかった・・・
とりあえずhotdeployでサーバを更新して対応・・・