再帰検索+テキストデータの連結

WSDLより、Javaソースを作成すると、web.xmlとかがWSDLファイル数分できました。
で、それらを連結しなくちゃいけないんだけど、ファイル数が20ぐらいあるので、プログラムを組んでみました。

ただ、web.xmlの宣言部やROOT要素は共通なので、不要。
よって、web.xmlの1,2,最終行は連結しない。
以下、ソース。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Concat {

    /** 指定フォルダ用定数 */
    private static final String FOLDER_PATH = "C:\\Users\\Public\\Desktop\\Test";

    /** 指定ファイル名用定数 */
    private static final String FILE_NAME = "web.xml";

    /**
     * メイン処理。
     * 
     * @param args 使用しません
     * @throws Exception 実行時例外
     */
    public static void main(String[] args) throws Exception {
        // 指定フォルダ
        File file = new File(FOLDER_PATH);

        // ファイルリストの取得
        List<File> list = getFile(file, FILE_NAME);

        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter(FILE_NAME));
            for (File f : list) {
                List<String> lines = getLineText(f);

                for (int i = 0; i < lines.size(); i++) {
                    if (i == 0 || i == 1 || i == lines.size() - 1) {
                        // 特定の行のみコピーしない
                        continue;
                    }
                    bw.write(lines.get(i));
                    bw.newLine();
                }
            }
        } finally {
            if (bw != null) {
                bw.close();
            }
        }
    }

    /**
     * 指定ファイル内のテキストを、行単位でリストにし返す。
     * 
     * @param file 指定ファイル
     * @return 行単位のテキストデータ
     * @throws IOException 入出力例外
     */
    private static List<String> getLineText(File file) throws IOException {
        // 返却値
        List<String> lines = new ArrayList<String>();

        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file));
            String line = null;
            // ファイルが存在する間ループ
            while ((line = br.readLine()) != null) {
                lines.add(line);
            }
        } finally {
            if (br != null) {
                br.close();
            }
        }

        return lines;
    }

    /**
     * ファイル名を指定し、指定フォルダ以下のファイルリストを再帰的に取得します。
     * 
     * @param folder 指定フォルダ
     * @param fileName ファイル名
     * @return ファイルリスト
     */
    private static List<File> getFile(File folder, String fileName) {
        // 返却値
        List<File> list = new ArrayList<File>();

        File[] files = folder.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                // 同一ファイル名を再帰検索
                list.addAll(getFile(file, fileName));
                continue;
            }

            if (file.getName().equals(fileName)) {
                // 同一ファイル名のパスを追加
                list.add(file);
            }
        }

        return list;
    }
}

getFileで再帰的にweb.xmlを検索。
mainのループの中でファイルのテキストデータを取得し、
その指定行数以外をReaderに出力。
って流れかな?

再帰って久々に使った・・・。

重複チェック

まずは、簡単なオブジェクトを定義

/**
 * ユーザクラス
 */
class User {
  /** ID */
  private String id;
  /** 名前 */
  private String name;
  /** メールアドレス */
  private String email;

  (Getter, Setterなどが定義されている)
}

で、単純にemailが重複していないかをチェックします。

class Test {
  public static void main(String[] args) {
    // メールアドレスset
    Set<String> set = new HashSet<String>();

    List<User> userList = (いろいろなUserクラスが入っているとする。)

    for (User user : userList) {
      String bufEmail = user.getEmail();
      if (set.contains(bufEmail)) {
        // 存在する場合
        System.out.println(bufEmail + "が重複しています。");
      } else {
        // 存在しない場合、setに追加
        set.add(bufEmail);
      }
    }
  }
}


次、どのidと重複しているかを表示する場合。

class Test2 {
  public static void main(String[] args) {
    // キー:email、値:idとなるmap
    Map<String, String> map = new HashMap<String, String>();

    List<User> userList = (いろいろなUserクラスが入っているとする。)

    for (User user : userList) {
      String bufEmail = user.getEmail();
      String tgtId = map.get(bufEmail);
      if (tgtId != null) {
        // 存在した場合
        System.out.println(bufEmail + "が" + tgtId + "と重複しています。");
      } else {
        // 存在しなかった場合、mapに追加
        map.put(bufEmail, user.getId());
      }
    }
  }
}


以上、変数名などテキトー。
また、Eclipseがやる気を起こしてくれないため、手書き+未コンパイル。
動作確認なし。
間違ってたら、そのうち直そうと思います。

SimpleDateFormatについて

文字列strがあり、

SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
sdf.parse(str);

とした場合、
"2010/03/18"を与えると正常に動作しますが、
"2010/03/1a"を与えた場合も例外が発生しません。
取得できるデータは2020年3月1日となります。


エラーチェックとしてparseしてみる場合も、上記のような場合はエラーとならないので、ご注意ください。

Google Web Toolkit覚書(GAEプラグイン)

流れ

Eclipseにて「Webアプリケーション・プロジェクト」を作成したら、基本的なファイルは作られる。
プロジェクトにて右クリック、実行、Webアプリケーションを選択すると、実行できる。


プロジェクト作成時に(プロジェクト名を"Sample"とする。)
war/Sample.html
が作成される。
tdタグなどにid属性をつけておく。(Javaよりそのid値を利用しアクセスする)


src/パッケージ/Sample.gwt.xml
が何者かによって呼び出される(はず)
entry-pointタグのclass属性クラスがロードされる。
そのクラスがEntryPointインターフェースを実装しており、onModuleLoadが呼び出される。

final TextBox nameField = new TextBox();
RootPanel.get("nameFieldContainer").add(nameField);

htmlのid値が"nameFieldContainer"となっているため、そのエレメント内部にTextBoxが追加される。

サーバとの通信

clientパッケージに、
GreetingServiceインターフェース
GreetingServiceAsyncインターフェース
が必要。(名前は*Service, *ServiceAsyncが妥当?)
命名規則や、シグネチャが間違っている場合は書いてる途中に、
「No asynchronous version of method メソッド名 exists in type GreetingServiceAsync」
とか言われるけど、インターフェースx2、実装クラスをちゃんと記述したらエラーは無くなる。


serverパッケージに
GreetingServiceImplクラス
が必要。(名前は*ServiceImplが妥当?)
client.GreetingServiceを実装する。(Asyncが無い方)


サーバー側は通常のJavaが利用できる。(好きに書いて良い)
クライント側はjava.lang以下+java.util以下のパッケージ以外は、googleパッケージや自作クラスのみ利用可能(?)
sharedパッケージ以下にはサーバ・クライアント両方で利用するクラスを定義する。
POJOなBeanやバリデータなどを定義するためのパッケージ?
→ただし、
・バリデータでも基本以外のクラスも利用できないので、簡単なチェックのみを記述し、実際のチェックはサーバサイドで行うべき(?)
・サーバ・クライアント間のデータを、POJOなBeanに詰めてやりとりする事は可能。ただし、それをそのまま永続化(DBに登録)はできない。(アノテーションを付けると、コンパイル時に怒られる→org.datanucleus.metadata.InvalidMetaDataException: Class com.noboru.shared.SampleModel has application-identity and no objectid-class specified yet has 0 primary key fields. Unable to use SingleFieldIdentity.)


どのパッケージをクライアント側とする(JavaScriptに変換する?)かは、Sample.gwt.xmlのsourceタグによる?

永続化(JDOの場合)

package com.sample.server;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.sample.shared.TransModel;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class StoreModel {

	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Long id;
	@Persistent
	private Date date;

	public StoreModel() {
	}

	public StoreModel(Date date, Integer amount, String store,
			String note) {
		this.date = date;
	}

	public StoreKakeiboModel(TransModel model) {
		SimpleDateFormat sdf = new SimpleDateFormat();
		sdf.applyPattern("yyyy/MM/dd");
		try {
			this.date = sdf.parse(model.getDate());
		} catch (ParseException e) {
			e.printStackTrace();
			this.date = null;
		}
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}
}

上記のように、いくつかのアノテーションが必要。
@PersistenceCapable
@PrimaryKey
@Persistent
が必要っぽい。


また、お約束クラス(?)が必要。(無くても毎回書けば動く)

package com.sample.server;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public class PMF {

	private static final PersistenceManagerFactory pmfInstance = JDOHelper
			.getPersistenceManagerFactory("transactions-optional");

	private PMF() {
	}

	public static PersistenceManagerFactory get() {
		return pmfInstance;
	}
}

transactions-optionalは、META-INFのjdoconfig.xmlによる?
いろいろ設定できるみたいだけど、不明。
デフォルトのままで動くので放置。


インサート(登録)は以下の通り

package com.sample.server;

import javax.jdo.PersistenceManager;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.sample.client.GreetingService;
import com.sample.shared.KakeiboModel;

public class GreetingServiceImpl extends RemoteServiceServlet implements
		GreetingService {

	public void insertNew(TransModel model) {

		StoreModel newModel = new StoreModel(model);
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			pm.makePersistent(newModel);
		} finally {
			pm.close();
		}
	}
}

PersistenceManager#makePersistentの引数に永続化対象クラスを渡すだけ。
更新も同様にできる。(らしい)


SELECT(照会)は以下の通り。

	@SuppressWarnings("unchecked")
	public List<TransModel> listAll() {
		List<TransModel> ret = new ArrayList<TransModel>();

		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(StoreModel.class);
		query.setOrdering("date asc");

		List<StoreModel> list = (List<StoreModel>) query
				.execute();
		for (StoreModel storedData : list) {
			TransModel model = new TransModel();
			model.setId(Long.toString(storedData.getId()));
			model.setDate(Converter.dateToString(storedData.getDate()));

			ret.add(model);
		}
		return ret;
	}

ただ、サーバ側→クライアント側では、TransModelのコンストラクタにStoreModelを渡したりはできない。(serverパッケージをインポートできないため。)
サーバ側に、ファクトリークラスを作ったりしたらよい?

まとめ

GWTをやる気が薄れてきたので、文字として残しておく。
後で参考になるといいな。

日付をフォーマットする

DBより取得した値を、MM月dd日に変換する。
ただし、一桁の場合はスペース埋めを行う。
(ゼロ埋めなら簡単なのに・・・)

// DB取得値のつもり
String dateStr = "2010-02-19 23:44:40";
// Dateに変換
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = sdf.parse(dateStr);

// Calendarを取得
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

// フォーマット
int month = calendar.get(Calendar.MONTH) + 1;
int day = calendar.get(Calendar.DAY_OF_MONTH);
String formatDate = String.format("%2d月%2d日", month, day);

// 出力
System.out.println(formatDate);

出力結果
2月19日
↑スペースがちゃんと出力されています。
もっと簡単にできると思うんだけど・・・


ちなみに、ゼロ埋めで良ければ

// フォーマット
SimpleDateFormat sdf2 = new SimpleDateFormat("MM月dd日");
String formatDate2 = sdf2.format(date);

// 出力
System.out.println(formatDate2);

出力結果
02月19日

ゼロ埋めならこんなに簡単。
仕様変更してくれんかなー。(たぶん無理)

google calendarに終日イベントを追加する

基本的に他の人のソースのパクリ。

package sample.calendar;

import java.net.URL;

import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.extensions.When;

public class AllDayEventCreator {

	// Google アカウント
	private static String GOOGLE_ACCOUNT = "xxxxxxxxxx@gmail.com";
	// Google アカウントのパスワード
	private static String GOOGLE_PASSWORD = "xxxxxxxxxx";
	// 送信URL
	private static String GOOGLE_CAL_URL = "http://www.google.com/calendar/feeds/default/private/full";

	/**
	 * 終日のイベントを追加します。
	 * 
	 * @throws Exception
	 */
	public void insertAlldayData() throws Exception {

		URL postURL = new URL(GOOGLE_CAL_URL);

		// イベント登録クラス
		CalendarEventEntry calEntry = new CalendarEventEntry();
		// タイトルを設定
		calEntry.setTitle(new PlainTextConstruct("終日のテスト"));
		// 詳細を設定
		calEntry.setContent(new PlainTextConstruct("詳細のサンプル"));

		DateTime startTime = new DateTime();
		startTime.setTzShift(9);
		startTime = DateTime.parseDate("2009-11-27");

		// 開始終了日時をWhen型オブジェクトに代入し、イベントクラスに追加
		When eventTimes = new When();
		eventTimes.setStartTime(startTime);
		calEntry.addTime(eventTimes);

		// Google Calendarサービスに接続
		CalendarService calService = new CalendarService(
				"chaos-alldaySample-0.1");
		calService.setUserCredentials(GOOGLE_ACCOUNT, GOOGLE_PASSWORD);

		// スケジュールを追加する
		calService.insert(postURL, calEntry);
		System.out.println("終日の予定:「" + calEntry.getTitle().getPlainText()
				+ "」を追加しました。");
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		try {
			// インスタンス生成
			AllDayEventCreator main = new AllDayEventCreator();
			// 終日イベントを追加
			main.insertAlldayData();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

終日イベントはsetStartTimeに渡すDateTimeを作成するときに、
DateTime.parseDate("2009-11-27");
のように、parseDateを使い、時分秒を指定しないとできる(みたい)。

google calendarでカレンダーを作成する

基本的にはsampleについてきたCalendarFeedDemo.javaから、
不要だと思われる箇所を削っただけ。

package sample.calendar;

import java.net.URL;

import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarEntry;
import com.google.gdata.data.calendar.ColorProperty;
import com.google.gdata.data.calendar.HiddenProperty;
import com.google.gdata.data.calendar.TimeZoneProperty;
import com.google.gdata.data.extensions.Where;

public class CreateCalendar {

	/** google calendar 基準URL */
	private static final String METAFEED_URL_BASE = "http://www.google.com/calendar/feeds/";

	/** マイカレンダー用アドレス */
	private static final String OWNCALENDARS_FEED_URL_SUFFIX = "/owncalendars/full";

	/** マイカレンダーフィード用URL */
	private static URL owncalendarsFeedUrl = null;

	private static final String BLUE = "#2952A3";

	/** ユーザー名 */
	static final String USER_NAME = "xxxxxxxx@gmail.com";
	/** パスワード */
	static final String USER_PASSWORD = "xxxxxxxx";


	/**
	 * カレンダーを作成します。
	 * 
	 * @param service
	 *            CalendarServiceオブジェクト
	 * @return 作成されたCalendarEntryオブジェクト
	 * @throws Exception
	 */
	private static CalendarEntry createCalendar(CalendarService service)
			throws Exception {
		System.out.println("カレンダーを作成します。");

		// カレンダーオブジェクトを初期化
		CalendarEntry calendar = new CalendarEntry();
		calendar.setTitle(new PlainTextConstruct("マイカレンダー1"));
		calendar.setSummary(new PlainTextConstruct("サンプルの説明"));
		calendar.setTimeZone(new TimeZoneProperty("Asia/Tokyo"));
		calendar.setHidden(HiddenProperty.FALSE);
		calendar.setColor(new ColorProperty(BLUE));
		calendar.addLocation(new Where("", "", "Toyama"));

		// カレンダーを追加します。
		CalendarEntry newEntry = service.insert(owncalendarsFeedUrl, calendar);

		return newEntry;
	}

	public static void main(String[] args) {

		try {
			// URLオブジェクトを作成
			owncalendarsFeedUrl = new URL(METAFEED_URL_BASE + USER_NAME
					+ OWNCALENDARS_FEED_URL_SUFFIX);

			// カレンダーサービスに接続
			CalendarService service = new CalendarService(
					"chaos-CreateCalendarSample-1");
			service.setUserCredentials(USER_NAME, USER_PASSWORD);

			// カレンダーを作成
			createCalendar(service);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

コンストラクタ:CalendarServiceの引数は文字列で
[company-id]-[app-name]-[app-version]
らしいので、テキトーに変更。
(例外処理とかはテキトーです。)

MacBookでEclipseをインストール

EclipseJavaEE(MacOS用)をDLしてきました。
で、ターミナルを開いて

cd Downloads/
tar -xvf eclipse-jee-ganymede-SR1-macosx-carbon.tar

で、解凍。
作成されたeclipseディレクトリを/Applications以下に移動。
で、Eclipseを起動

jsfのinputタグなどをJavaScriptで制御する。

<h:view>
  <h:form id="form1">
    <h:inputText id="userId" />
  </h:form>
</h:view>

と、すると、inputタグは"form1:userId"というid(nameだったかも)になる。
ここで、

document.form1.form1:userId.value = "hoge";

としてもJavaScriptエラーとなる。


ので、

document.getElementById("form1:userId").value = "hoge";

や、

document.all['form1:userId'].value = 'hoge';

とする。

プロパティファイルを編集するeclipseプラグイン

忘れないようにメモ。


JSFのメッセージファイル(?)のようなプロパティファイルは、直接日本語を編集できないので、
PropertiesEditorプラグインを入れます。
http://sourceforge.jp/projects/propedit/files/


以上。