Author Archives: susumuis

About susumuis

メイドカフェによく居るWebエンジニア

首都圏でJR線の一筆書きルールを利用してノマド作業をするのに適したコース9選

僕は休日によくMacBookAirを持ち歩いて、外でコーディングをします。そういう時、ルノアールを活用される方が多いようですが、どうも自分はカフェで作業が肌に合わず、そこで、電車コーディングを始めました。

ところで、JRには以下のルールがあります。
http://bizmakoto.jp/makoto/articles/1001/09/news006.html
近郊区間内、つまり東京や大阪では、一筆書きにすれば、隣駅までの切符で区間内いくらでも乗れますというルールです。このルールを利用すれば、初乗り料金でかなり長い時間、電車に乗り続けることができます。また、首都圏の中心部を外れれば、景色も楽しめたり、トイレ付きの列車に乗れたりします。

そうして、僕は東京の一筆書きで行ける全線区制覇をしてしまいました。これまでの経験を踏まえて、電車でコーディングする際の情報をここに発信したいと思います。

必要な道具

  • モバイルデータ回線。
    • NTTドコモ、あるいはb-mobile fairがおすすめです!WiMaxだと、多分都市区間を外れると辛いと思います。山手線限定ならばWiMaxはオススメかも知れません。*1
    • テザリング可能なスマートフォンでも可です。ただし電池残量に注意。
  • ノートPC
    • 電源がありません。バッテリーの持ちが必須です。また、起動・終了が軽快であった方が良いので、Macがおすすめです。自分は所有していませんが、マシン自体がWiMaxや3G機能を持っていると便利かもしれません。

以上です。逆に言えばあまり多くの物は持つべきではありません。乗り換えとかでアタフタするだけです。書類や紙の資料などは持たず、どうしても必要なら、電子版をPCに保存しましょう。

ビギナー向けおすすめルート

これは出発地によって変わるのですが、僕の基準で、秋葉原近辺を発着のすることとして書きます。

  • 山手線一周
    • 例:秋葉原,(品川,池袋),御徒町
    • 乗換回数:0回
    • 気軽です。一周約1時間と短めですが、それが良い場合もあるでしょう。本数が多いのが一番です。また、都心に近いので、WiMaxが確実に使えます!
    • ただし、山手線って意外と混んでいます。新宿や池袋など混雑する駅前後では、PC広げているといやがられたりします。電子書籍を読むくらいがやっとかもしれません。
  • 房総半島一周
    • 例:秋葉原,千葉,成東,大網,安房鴨川,蘇我,東京(京葉線),御茶ノ水
    • 乗換回数:6回
    • 自然の海が見えるルートです。日中は乗車率も高くないのも魅力です。いざとなったら特急も使えます。ボトルネックは東金線です。半日がかりになりますが、おすすめです!お昼は千葉駅の駅弁を是非ご利用ください。ただし最近は、全席ロングシートの電車もたまにいるのでご注意ください。
  • 南武線・武蔵野線・京葉線で東京外周
    • 例:秋葉原,池袋,川崎,府中本町,東京,御茶ノ水
    • 乗換回数:4回
    • 乗車率、運転本数的にオススメです。始発駅を効果的に使えるので座れますし、急に予定が発生した場合は中央線を使ってルートを短縮することもできます。武蔵野線が府中本町付近と東京付近で地下に入るので、ネットが一時的に使えなくなります。武蔵野線を府中本町から東京まで乗るのは結構乗りごたえがあります!
  • 東京北西部一周(中央線・川越線・東北/高崎線)
    • 例:御茶ノ水,八王子,川越,大宮,上野
    • 乗換回数:4回
    • 逆ルートは登山帰りの人と一緒になっちゃったりしますので注意が必要です。乗り換えが比較的しやすいと思います。
  • 横浜線
    • 例:東京,東神奈川,八王子,御茶ノ水
    • 横浜側から回ることはお勧めしません。乗換駅が東神奈川になるので、根岸線直通電車の場合は座れない場合が多いです。また、到着先が八王子と、やや遠め。さらに八王子で中央線が座れなかったりします。やるなら逆ルートをオススメします。

一度は乗っておきたい、難易度高めルートルート

  • 水戸線
    • 例:上野,友部,小山,尾久(あるいは湘南新宿ライン経由新宿,御茶ノ水)
    • 筑波山が綺麗で一度はおすすめ、常磐線の結構遠くまで行ってしまうので、いざとなったときに返ってくるのがつらいです。またどちら向きとっても友部は始発乗り換えでないので、座れる保証がありません。
  • 成田線(成田-松岸)
    • 例:上野,我孫子,成田,松岸,成東,千葉,錦糸町,秋葉原
    • 本数が少なく、我孫子、成田、松岸などで待ち時間が多く発生します。銚子の隣の松岸まで行っても海は見えません。銚子まで行けない歯がゆさがあります。佐原、水郷あたりの町並みを見たことがないなら、一度はおすすめです。
  • 南武支線、鶴見線
    • 例:東京,川崎,尻手,浜川崎,鶴見,横浜,武蔵小杉,新宿,御茶ノ水
    • 「鉄」の人の大好きなルートw本数の少なさは筋金入り。浜川崎での乗り換え時の駅の快適度、鶴見での改札通過など難所があるのでこの用途ではおすすめしません。
    • 南武線から横須賀線への武蔵小杉乗り換えの距離が長いです。
  • 八高線
    • 高崎線を利用する場合、倉賀野乗り換えが難点です。時間が許すなら、宇都宮線で小山に行き、両毛線で高崎に入ると、乗り換えもスムーズです。が、両毛線、八高線両方を大回りすると、一日がかりになります。時刻表を読んで事前計画が必要でしょう。しかしながら、八高線は首都圏で唯一気動車に乗れる線区であり、まだ乗ったことがなければ新鮮な味わいがあるはずです。
  • 両毛線
    • 群馬・栃木の山の中を走って行く大変風情のある路線ですが、東京方面から最短で回ると、どうしても大宮を通過してしまうので、大宮までの往復切符が必要になってしまいます。そのため、前述の水戸線か八高線を合わせなければならないため、非常に大掛かりになります。

書いてみて、「鉄」として楽しむのか、「オフィス」として利用するかでまったく評価が変わることに気づきました。鶴見線や八高線は、「不便だから楽しい!」線区なのですが、まともに作業をしようと考えたら「不便は不便」です。

また、鉄道本来の利用方法ではないため、他のお客さんに迷惑をかけることがないよう、混雑をしてきたら作業をやめたり席を譲るなどしましょう。お金はかかりますが、グリーン車が走っているエリアではグリーン車に乗りましょう。

2014/05/05

昔の記事を整理していて、良い情報だと思ったので、タイトル含め大幅に編集しました。初稿が2011年なので少し情報が古い部分もありますが今でもそのまま通用すると思います。

この記事を書いた当時は、僕の小遣い的に節約志向だったので、なんとしてでも初乗りで済ませようとしていましたが、最近は、正規の鉄道料金(割引きっぷも使用せず)を終点から終点まで大人買いしちゃって鉄道会社を応援したりします。そういう観点で今後また記事を書くかもしれません。

また、僕自身カフェでのコーディングに慣れて来たこともあって、あまり電車コーディングはしなくなってしまいました。

*1:記事執筆2011年当時の情報でしたので書き直しました。

TreeSetのComparatorではまったのでメモ(初心者向け)

今更感のある話題ですが、初心者=僕が、ドハマリして、いろいろ面白い現象に遭遇しました。
内容はいたって基本中の基本のため、仕事でプログラミングしている者としては恥ずかしい限りですが、後に同じようにハマる人がいたときのために、メモを残します。

やろうとしたこと:

もともとこんなコードがあって

set = new TreeSet();
for (DBの取得結果) {
set.add(取得した値);
}

この後、setをつかって、UIを生成していたのだけど、順番が違うよって指摘を受けた。DBには、よくある、「表示順」のカラムがある。SQLならorder byで取れる。TreeSetの場合はCompatatorを渡せば良い。ということで、こんなHashMapをつくって

Map<String, Integer> idToSortOrderMap = new HashMap<String, Integer>();
for (DBの取得結果) {
idToSortOrderMap.put(id, order);
}

みたいにして。こんなstatic内部クラスを作る

private static class MyComparator implements Comparator, Serializable {
private HashMap<String, Integer> idToOrderMap;
public MyComparator(HashMap<String, Integer> idToOrderMap) {
this.idToOrderMap= idToOrderMap;
}
public int compare(String o1, String o2) {
Integer i1 = idToOrderMap.get(o1);
Integer i2 = idToOrderMap.get(o2);
if (i1 == null) { return 1; }
if (i2 == null) { return -1; }
return i1 - i2;
}
}

これで一見すると、表示順は正常で、あたいもちゃんと追加されているので、完了…そう思っていました。

問題発生

ところが、例えば

idToOrderMap.put("hoge", 1);
idToOrderMap.put("hoge2", 2);
idToOrderMap.put("fuga", 1);

のとき、

set.add("hoge");
set.add("hoge2");
System.out.println(set.contains("fuga")); // falseのはず

ところが、結果はtrueになってしまいます。

問題の原因

初歩的なことですが、Java APIリファレンスに、
http://java.sun.com/javase/ja/6/docs/ja/api/java/util/TreeSet.html

あるセットが Set インタフェースを正しく実装するには、明示的なコンパレータが提供されているかどうかにかかわらず、そのセットによって維持される順序付けが「equals との一貫性」のあるものでなければいけないことに注意してください。

http://java.sun.com/javase/ja/6/docs/ja/api/java/util/Comparator.html

たとえば、(a.equals(b) && c.compare(a, b) != 0) である 2 つの要素 a および b をコンパレータ c で空の TreeSet に追加すると仮定します。a と b はツリーセットの点から見て等価ではないため、2 番目の add オペレーションは、Set.add メソッドの仕様とは異なる場合でも、true を返し、ツリーセットのサイズは大きくなります。

と記述されています。

噛み砕いていうと「compareの結果が0と、equalsが同値でないと、TreeSetは異常な動きをするよ」というところです。

Effective Java

を持っている人は、Effective Javaの第2版 「項目12 Comparableの実装を検討する」に関連する記述があります。

はまりどころは、compareとequalsの一貫性は、必須ではないというところです。しかし、TreeSetやTreeMapのようにこの一貫性を要求する実装クラスがあるということです。

完成コード

上記クラスの修正版は以下のとおりです。

private static class MyComparator implements Comparator, Serializable {
private HashMap<String, Integer> idToOrderMap;
public MyComparator(HashMap<String, Integer> idToOrderMap) {
this.idToOrderMap= idToOrderMap;
}
public int compare(String o1, String o2) {
if (o1 == null) {
return o2 == null ? 0 : -1;
}
Integer i1 = idToOrderMap.get(o1);
Integer i2 = idToOrderMap.get(o2);
if (i1 == null) {
if (i2 != null) {
return -1;
}
return o1.compareTo(o2);
}
if (i2 == null) {
return 1;
}
if (i1.equals(i2)) {
return o1.compareTo(o2);
}
return i1.compareTo(i2);
}
}

2011/06/24 修正

ソースを修正しました。修正した箇所は、

if (i1 == i2) {

の箇所で、i1,i2がintではなく、Integerだったので、値の比較ではなく、オブジェクトの比較がされていました。equalsを使うべきでしたが、そうすると、nullの場合にヌルポになるので、上記の対応になります。やりたいことは最終行の

return i1.compareTo(i2);

なんですよね。Javaが冗長とか言われるのも、こういったNull対策があったりとかするところなんでしょうね……。

まあ、SQLでもこう言うのありますけどね。

JavaでPDF生成する方法(LibreOffice, jodConverterによる方法)

JavaでPDFを生成する場合、こういった方法や、http://allabout.co.jp/gm/gc/80691/ こういった方法http://www.atmarkit.co.jp/fjava/javatips/121jspservlet41.html がある。しかし、どちらも低レベル過ぎて、美しいビジネス文書や帳票を出力するにはワープロを作るくらいの気合が必要となる。

手っ取り早いのは、ExcelやWordの文書をApache POI経由で編集し、そいつをPDFにして出力出来れば、美しいPDFが任意のテンプレートで作れるというプランである。

最近のOfficeならPDF出力をできるので、もしMSマンセーな組織なら、Windowsサーバ上でExcelを常駐させて、COMなんちゃらを利用して実現するのが良いと思う。しかし僕はJava屋だ。サーバはLinuxが好きだ。というわけで、今回は、以下の組み合わせで実現したので、ポイントを抜粋して紹介します。

なお、POIのことについては割愛します。
まずはPOIなので、HSSFWorkbookを生成します。

final HSSFWorkbook book = new HSSFWorkbook(templateStream);

そして、bookを使って色々操作します(w
最後にoutputStreamをbook#writeに渡せばExcelファイルが出力されます。(ここまでは普通のPOIです)

book.write(out);

次はPDF化です。LibreOfficeをインストールして、インストールディレクトリの
program/soffice (Windowsの場合はsoffice.exe)に、次のオプションを付けて起動します。

 -accept="socket,port=8100;urp;"

これでソケット通信を受け付けるようになります。

次に、jodConverterを用意しましょう。http://sourceforge.net/projects/jodconverter/files/JODConverter/2.2.2/ から jodconverter-webapp-2.2.2.zip をダウンロードして解凍します。zip中身にwarが入っているので、そいつをtomcatのwebappの下に配置したらすぐに使用できるようになります。

次に、アプリとjodConterverの連携です。幸い、jodConverterにはWebサービス機能があるのでそれを使うのが良さそうです。
ぐぐっても見つからないと思ったら本家に書いてありました(笑)
http://www.artofsolving.com/node/15

ということで、ざっくりこんな感じでユーティリティメソッドを作ります。

public int convertExcepToPdf(final InputStream in, OutputStream out) throws IOException {
    HttpClient httpClient = new HttpClient();
    PostMethod post = new PostMethod("http://localhost:8080/jodconverter-webapp-2.2.2/service");
    post.setRequestHeader("Accept", "application/pdf");
    post.setRequestHeader("Content-Type", "application/vnd.ms-excel");
    post.setRequestBody(in);
    try {
        int status = httpClient.executeMethod(post);
        if (status != HttpStatus.SC_OK) {
            return status;
        }
        InputStream response = post.getResponseBodyAsStream();
        byte[] buf = new byte[256];
        for (int len; (len = response.read(buf)) != -1;) {
            out.write(buf, 0, len);
        }
        return status;
    } finally {
        post.releaseConnection();
    }
}

これで接続ができますが、POIのwriteはOutputStream、今回作った関数のパラメータはInputStreamという問題が残ります。
ByteArrayInputやOutputStreamを使ってメモリに全部記憶すればできますが、Excelファイルも大きめのファイルなので、メモリは節約したいです。かと言って、一旦ファイルに保存するのはなんともダサい感じです。HDDガリガリとかナンセンスです!

悩んだ結果、僕はPipedOutputStream, PipedInputStreamを使用しました。

final PipedOutputStream pipeout = new PipedOutputStream();
PipedInputStream pipeIn = new PipedInputStream(pipeout);
Thread t = new Thread(new Runnable() {
    public void run() {
        try {
            try {
                book.write(pipeout);
            } finally {
                pipeout.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
t.start();
PDFConverterConnector pdfC = new PDFConverterConnector();
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
try {
    pdfC.convertDocumentFileToPdf(pipeIn, out);
} catch (Exception e) {
    e.printStackTrace();
}

無事PDFが出力されたのを見ると感激しますね!

2011/05/26 追記

本番環境にインストールするに当たって、CentOS5に入れたのですが、バージョンが古くてLibreOfficeではなく、OpenOffice.orgになってしまったのと、コマンドラインから実行すると、

Set DISPLAY environment variable, use -display option or check permissions of your X-Server (See "man X" resp. "man xhost" for details)

というようなエラーが出てしまいました。これは、

yum install openoffice.org-headless

とすればOKでした。
参考:http://stackoverflow.com/questions/4004456/centos-server-openoffice-headless

追記(2014/04/26)

ここではJodConverter2を使っていますが、OpenOffice, LibreOffice側にメモリリーク問題があり、デーモンとして起動したOOo(LibreOffice)がクラッシュしてしまう問題があります。実際に私の現場でも、容量の大きいExcelファイルを連続して扱うとクラッシュしてしまう現象を観測し、現在は、JodConverter3に移行しています。

JodConverter3については、下記の記事を参照してください
https://code.google.com/p/jodconverter/wiki/WhatsNewInVersion3

JodConverter3はずっとbetaで開発が止まっていて作者によると

I started this project back in 2003, but I am no longer maintaining it. I moved the code here at GitHub in the hope that a well-maintained fork will emerge.

という立場のようです。すでに多数のforkされてpull requestもあるようです。
https://github.com/mirkonasato/jodconverter

私の現場では最新betaを使用していますが、とりたてて問題は発生していません。ただ、jodConverter3には便利なWebサービス機能がありませんので、自前でJodConnver2のものを元にWevServiceを作成しました。
その辺りを後日紹介したいと思います。

この記事は以前多くのブックマーク・コメントを頂きました。

これからブックマークされる方はこちら↓