2013年7月7日日曜日

TimeMachineのバックアップの信頼性を修復する方法

QNAP TurboNAS TS-421のTimeMachineに突然やってきた悲劇のダイアログ

信頼性を向上するために、新規バックアップを作成する必要があります

提示された選択肢は「後でやる」か「今やる」か
結局どちらも新規バックアップをつくることになる

過去に戻れないTimeMachineなんて
なさけなくて涙があふれそうになりながらぐぐってみたら
修復することができて嬉しかったのでメモっておくことにする

TimeMachineのバックアップはディスクイメージに保存する仕組みになっていて
ディスクイメージ内に構築したHFSファイルシステムが壊れたので
「信頼性が低くなった」と表示したようだ
(fsckしてみたらhard linkの数が合わないって言ってた気がする)

やるべきことは2つ
  1. ファイルシステムを修復すること
  2. TimeMachineの検証状態をリセットすること

面倒だからといって[2]を実施しないと
TimeMachineは新規バックアップ作成を強要してくるので注意

それと、修復作業は時間がかかるのでMacを放置することになる
修復作業中にMacがスリープしないように設定を変更するべきだろう


ファイルシステムを修復する手順

QNAPにadminでログイン

File Stationを開く

共有フォルダの新規作成を開始する

詳細設定をクリックし、手動でパスを入力する
ドロップダウンから.timemachineを選択する
残りのステップはデフォルトのままだったと思う
※普段使っているアカウントでアクセスできるよう権限を設定すること

QNAP(AFP)の「TM」フォルダをクリックしてマウントする

「TM」フォルダがマウントされた様子

「マシン名.sparsebundle」をディスクユーティリティで開く
(ここから次の画像のようになるまで5分くらいかかったかも)

「マシン名.sparsebundle」を開く

「Time Machine Backups」を選択して「ディスクを修復」をクリック

修復している様子

修復完了すると「修復を中止」ボタンが「ディスクを修復」ボタンに変わる
(この画像は作業中のものではないので実際とはことなるかも)

「マシン名.sparsebundle」の中の「com.apple.TimeMachine.MachineID.plist」を書き換える

テキストエディタで開いて

VerificationStateの値を0(ゼロ)に変更する

これで修復作業は完了したので
コンピュータにマウントされている共有フォルダ「TM」を取り出す
QNAPのTM共有フォルダの解除はFile Stationではなくてコントロールパネルで操作するのだが、次回に備えて共有したままにしておいた

メニューバーのTimeMachineで「今すぐバックアップを作成」を始めると...

検証に5分くらいかかったと思う

しばらくしてバックアップが正常に完了

幸運を祈る!

2013年5月27日月曜日

cocos2d-xで画像データを表示する方法

メモリ上で作成したRAWデータの画像を表示したい場合、
CCTexture2Dでテクスチャを作成して、CCSpriteで表示する。

例えばこんな感じ。

CCSprite* createSpriteWithRawData(const void* data, unsigned int width, unsigned int height)
{
    CCTexture2D* texture = new CCTexture2D();
    if (texture && texture->initWithData(data, kCCTexture2DPixelFormat_RGBA8888, width, height, CCSizeMake(width, height))) {
        texture->autorelease();
        return CCSprite::createWithTexture(texture);
    }
    CC_SAFE_DELETE(texture);
    return NULL;
}
※ CCTexture2D::initWithDataは、dataをglTexture2Dに渡してグラフィックスハードウェアに転送している。
従ってdataをメモリ上に確保しておく必要はない。

ネットワーク経由で取得したPNGやJPG画像を表示したい場合などは、
CCImageで画像フォーマットを解析してから、CCTexture2DとCCSpriteで表示する。

例えばこんな感じ。

CCSprite* createSpriteWithPngData(void* data, unsigned long data_length)
{
    CCImage* image = new CCImage();
    if (image && image->initWithImageData(data, data_length, CCImage::kFmtPng)) {
        image->autorelease();
        CCTexture2D* texture = new CCTexture2D();
        if (texture && texture->initWithImage(image)) {
            texture->autorelease();
            return CCSprite::createWithTexture(texture);
        }
        CC_SAFE_DELETE(texture);
        return NULL;
    }
    CC_SAFE_DELETE(image);
    return NULL;
}
※ CCImage::initWithImageDataは、dataを必要に応じてコピーしている。
従ってdataをメモリ上に確保しておく必要はない。

2013年5月19日日曜日

cocos2d-xでハイブリッドプロジェクトを作る方法

cocos2d-xの新規プロジェクト、どうやって作ってる?

ぐぐってみると、

  1. Xcodeのプロジェクトテンプレートを使ってiOS向けの新規プロジェクトを作成する
  2. create-android-project.shを使ってAndroid向けのプロジェクトを作成する
  3. これら2つのディレクトリをマージする
  4. cocos2d-xのソースをマージする
  5. Android向けのビルド関連ファイルを修正する

という手順が紹介されていることが多い。

cocos2d-x.orgのニュースに掲載されている本「Cocos2d-X By Example Beginner's Guide」でも同様の手順が紹介されている。

しかし、この手順でプロジェクトを作ると3つのことが気に入らない。

  1. めんどくさい
  2. ディレクトリ構成がめちゃくちゃ
  3. iOSやAndroid以外のプラットフォームのビルドはどうするんだ?

個人的には(3)は無視できるけれど、(2)は無視できないし、(1)は致命的だ。

この状況を解決してくれるのが project-creator だ。
cocos2d-2.1rc0-x-2.1.3.zipを展開すると、tools/project-creatorディレクトリに"create_project.py"というファイル名のPythonスクリプトがある。

このスクリプトを使うとsamplesと同じ構成のハイブリッドなプロジェクトを、ものすごく簡単に作れるうえに、iOSやAndroidだけでなく、たとえばwin32向けのビルドもできるようになるのだ。

たまたま、Windows8(x64)の環境で遊んでいたので、win32向けのビルドを試してみた。

Microsoft Visual C++ 2010 Express
Windows SDK
Python 2.7.5 Windows X86-64 Installer

これらをインストール(再起動が必要だったし、ついでにMicrosoft Updateしたり)した後、
コマンドプロンプトでcreate_project.pyを実行した。

D:\> cd cocos2d-2.1rc0-x-2.1.3\tools\project-creator
D:\cocos2d-2.1rc0-x-2.1.3\tools\project-creator> create_project.py -project Praise002 -package net.xpraise.praise002 -language cpp

proj.win32のslnファイルをVC++で開いてデバッグ実行した時のキャプチャ。



VC++でデバッグするのはありかもしれないな。

2013年3月2日土曜日

String interpolation in Scala 2.10

小さいけど影響力大きい。
val name = "K"
println(s"hello ${name} !")
参考: String interpolation

2013年3月1日金曜日

最近のお気に入りはIterator.continually

for (line <- Iterator.continually(readLine()).takeWhile(_ != "quit")) {
  println(line)
}

2012年11月23日金曜日

gimliを使ってみた

MarkdownをPDFに変換したくなることがよくある。

いまだにベストだと思えるツールに出会えていない。

今日はHow-to generate PDF from Markdownを読んでgimliを試してみた。
gimli -stylesheet sample.css -outputfilename sample -file sample.md
cssを指定することもできるしよい感じだ。
h1,h2,h3,h4,h5,h6,li,pre,p,a,div {
  font-family: IPAexMincho;
}
pre {
  border: solid 1px #088;
  border-radius: 8px;
  padding: 18px 5px 5px 5px;
}
a {
  color: #000;
}

だけど、コードブロックの先頭が、段落下げされてしまうのがものすごく残念。
MarkdownをPDFに変換したくなることがよくある。

    いまだにベストだと思えるツールに出会えていない。
    今日はHow-to generate PDF from Markdownを読んでgimliを試してみた。
    cssを指定することもできるしよい感じだ。

コードブロックの先頭が、段落下げされてしまうのがものすごく残念。

2012年11月21日水曜日

build.sbtでclassifierを使ってみた

sbtでperf4j-0.9.16-slf4jonly.jarを使いたくなったので調べてみた。
build.sbtから抜粋。
resolvers += "Codehaus Repository" at "http://repository.codehaus.org/"

libraryDependencies +=  "org.perf4j" % "perf4j" % "0.9.16" classifier "slf4jonly"

2011年12月1日木曜日

データベース + MyBatis + Guice のサンプル


MyBatisだけでも十分に素敵だけど、Guiceと組み合わせるともう最強って感じ。

せっかくなのでMyBatis+Guiceの魅力をwebappではない、スタンドアローンのプログラムを書いて堪能してみる。

mybatis: ver 3.0.6
mybatis-guice: ver 3.2
guice: ver 1.0

[1] データベースにテーブルを作る

MySQLで。
CREATE TABLE `Friends` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `memo` varchar(140),
  PRIMARY KEY (`id`),
  UNIQUE INDEX `name_index` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


[2] プロジェクトを作る

Mavenで。
(依存ライブラリを探してダウンロード+解凍+パス設定+などなどを、手動でやってた頃が懐かしい。)

MyBatis-GuiceはMyBatisとGuiceをつなぐ架け橋。これがあるおかげで、ものすごく便利な組み合わせになってる。

MyBatisは主要なロギングフレームワークに対応しているので、logback-classicを入れてる。
ロギングフレームワークは自動認識されるので、ライブラリをパスに追加するだけでログを吐くようになる。

<project
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.takumakei.study</groupId>
  <artifactId>mysql-mybatis-guice</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mysql-mybatis-guice</name>
  <url>http://takumakei.blogspot.com/</url>

  <properties>
    <project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.6.4</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.0.6</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-guice</artifactId>
      <version>3.2</version>
    </dependency>
    <dependency>
      <groupId>com.google.code.guice</groupId>
      <artifactId>guice</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.0.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.18</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>6</source>
          <target>6</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>


[3] レコードのモデルクラスを作る

POJOで書いてるけれど、BeanでもOK。
package com.takumakei.study.model;

public class Friend {
 public int id;
 public String name;
 public String memo;

 public Friend() { // 引数なしのコンストラクタがないとエラーになる
 }

 public Friend(String name, String memo) {
  this.name = name;
  this.memo = memo;
 }

 @Override
 public String toString() {
  return String.format("%s[%d/%s/%s]", super.toString(), id, name, memo);
 }
}


[4] マッパーインターフェースを作る

このインターフェースを、MyBatis-Friends.xmlでmapperにマッピングする。
package com.takumakei.study.mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.RowBounds;

import com.takumakei.study.model.Friend;

public interface FriendsMapper {
 public void insert(Friend friend);

 // DELETEしたレコードの数を返す仕様みたい
 public int deleteById(int id);

 // UPDATEしたレコードの数を返す仕様みたい
 // 複数のパラメータを指定している。mybatis-friends.xmlでは#{0}とか#{1}で参照することになる
 // こういう指定方法も可能ではあるけれど、保守性低下するから使わない方が良さそう
 public int updateMemo(int id, String memo);

 public Friend findById(int id);

 // org.apache.ibatis.session.RowBoundsのパラメータの扱いは特殊。
 // mybatis-friends.xmlではこのパラメータを明示していないけれど
 // MyBatisは期待通りにコード数を制限してくれる。
 public List<Friend> list(RowBounds bounds);

 // Mapで取り出す例。
 // 複数のパラメータを指定する際に、それぞれのパラメータに名前を付けて参照できるようにする例でもある。
 // (ただし、offsetとlimitはRowBoundsを使うべきところ)
 @MapKey("id")
 public Map<Integer, Friend> map(@Param("offset") int offset, @Param("limit") int limit);

 public int count(); 
}


[5] データベース操作用のクラスを作る

@Injectを指定して、friendsフィールドをGuiceに生成してもらう。

無駄に@Transactionalを使っているが、トランザクションの扱いも非常に簡単。

package com.takumakei.study.db;

import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.guice.transactional.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.takumakei.study.mapper.FriendsMapper;
import com.takumakei.study.model.Friend;

public class Friends {
 static final Logger logger = LoggerFactory.getLogger(Friends.class);

 // Guiceにインスタンスを生成してもらう
 @Inject
 protected FriendsMapper friends;

 // 無駄に@Transactionalを指定してみた。
 // org.mybatis.guiceのログレベルをDEBUGにすると、
 // insertの前後でTransaction開始/終了したようなログが出力される
 @Transactional
 public Friend insert(String name, String memo) {
  Friend friend = new Friend(name, memo);
  try {
   // friendのidはAUTO_INCREMENT
   // insertに成功するとfriendのidを更新して返してくれる
   // mybatis-friends.xmlのuseGeneratedKeysとkeyPropertyのおかげかな?
   logger.info("before:{}", friend);
   friends.insert(friend);
   logger.info(" after:{}", friend);
   return friend;
  } catch (PersistenceException e) {
   Throwable cause = e.getCause();
   if (cause instanceof SQLIntegrityConstraintViolationException) {
    // 制約に違反してinsert失敗した時にはnullを返すようにしてみた。
    // ここでわざわざ例外を握りつぶす必要性は全くない。例示のための実装。
    logger.warn("INSERT FAILED:{}", cause.getMessage());
    return null;
   }
   throw e;
  }
 }

 public boolean deleteById(int id) {
  return 1 == friends.deleteById(id);
 }

 public boolean updateMemo(int id, String memo) {
  return 1 == friends.updateMemo(id, memo);
 }

 public Friend findById(int id) {
  return friends.findById(id);
 }

 public List<Friend> list(int offset, int limit) {
  return friends.list(new RowBounds(offset, limit));
 }

 public Map<Integer, Friend> map(int offset, int limit) {
  return friends.map(offset, limit);
 }

 public int count() {
  return friends.count();
 }
}


[6] SQLを書く
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.takumakei.study.mapper.FriendsMapper">
  <sql id="friend">id, name, memo</sql>
  <sql id="#memo">#{memo, javaType=String, jdbcType=VARCHAR}</sql>

  <insert id="insert" parameterType="Friend" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO Friends (name, memo)
    VALUES(#{name}, <include refid="#memo"/>);
  </insert>

  <delete id="deleteById" parameterType="int">
    DELETE FROM Friends WHERE id = #{id}
  </delete>

  <update id="updateMemo">
    UPDATE Friends
    SET memo = #{1, javaType=string, jdbcType=VARCHAR}
    WHERE id = #{0, javaType=int, jdbcType=INTEGER}
  </update>

  <select id="findById" parameterType="int" resultType="Friend">
    SELECT <include refid="friend"/>
    FROM Friends
    WHERE id = #{id}
  </select>

  <select id="list" resultType="Friend">
    SELECT <include refid="friend"/>
    FROM Friends
    ORDER BY id
  </select>

  <select id="map" resultType="Map">
    SELECT <include refid="friend"/>
    FROM Friends
    ORDER BY id DESC
    LIMIT #{offset}, #{limit}
  </select>

  <select id="count" resultType="int">
    SELECT COUNT(*) FROM Friends
  </select>
</mapper>


[7] mybatisの設定ファイルを作る
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <properties>
    <property name="driver" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/test" />
    <property name="username" value="change*me" />
    <property name="password" value="change*me" />
  </properties>
  <typeAliases>
    <typeAlias alias="Friend" type="com.takumakei.study.model.Friend" />
    <typeAlias alias="FriendsMapper" type="com.takumakei.study.mapper.FriendsMapper" />
  </typeAliases>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
        <property name="driver" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${username}" />
        <property name="password" value="${password}" />
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="mybatis-friends.xml" />
  </mappers>
</configuration>


[8] logbackの設定ファイルを作る

設定ファイルは後で書いた方が、大量のDEBUGログを見れるので良いかもしれない。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{136} - %msg%n</pattern>
    </encoder>
  </appender>
  <logger name="org.apache.ibatis" level="INFO" />
  <logger name="org.mybatis.guice" level="INFO" />
  <logger name="java.sql.Connection" level="INFO" />
  <logger name="java.sql.Statement" level="INFO" />
  <logger name="java.sql.PreparedStatement" level="INFO" />
  <logger name="java.sql.ResultSet" level="INFO" />
  <root level="DEBUG">
    <appender-ref ref="stdout"/>
  </root>
</configuration>


[9] 動かす
package com.takumakei.study;

import java.sql.SQLRecoverableException;
import java.util.Date;
import java.util.Map;

import org.apache.ibatis.exceptions.PersistenceException;
import org.mybatis.guice.XMLMyBatisModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.takumakei.study.db.Friends;
import com.takumakei.study.model.Friend;

public class App {
 static final Logger logger = LoggerFactory.getLogger(App.class);

 public static void main(String[] args) {
  try {
   logger.info("<start>");
   run();
  } catch (PersistenceException e) {
   Throwable cause = e.getCause();
   if (cause instanceof SQLRecoverableException)
   logger.error("database dead? [{}] {}", cause.getClass().getSimpleName(), cause.getMessage());
  } catch (Throwable t) {
   logger.error("something bad", t);
  } finally {
   logger.info("<exit>");
  }
 }

 public static void run() throws Exception {
  Injector injector = Guice.createInjector(new XMLMyBatisModule() {
   @Override
   protected void initialize() {
    setEnvironmentId("development");
    setClassPathResource("mybatis-config.xml");
   }
  });

  Friends db = injector.getInstance(Friends.class);
  
  logger.info("[list up]");
  for (Friend friend : db.list(0, 3)) {
   logger.info("select:{}", friend);
  }

  logger.info("[insert]");
  logger.info("insert:{}", db.insert("(1)TAKUMA KEI", "(1)hello world"));
  logger.info("insert:{}", db.insert("(1)TAKUMA KEI", "(1)same name!!"));
  logger.info("insert:{}", db.insert("(2)TAKUMA KEI", "(2)hello world"));
  logger.info("insert:{}", db.insert("(3)TAKUMA KEI", "(3)hello world"));
  logger.info("insert:{}", db.insert("(4)TAKUMA KEI", "(4)hello world"));

  Friend insertedFriend = db.insert(new Date().toString(), null);
  logger.info("insert:{}", insertedFriend);

  logger.info("[count]");
  logger.info("count:{}", db.count());

  logger.info("[findById]");
  logger.info("findById({}):{}", insertedFriend.id, db.findById(insertedFriend.id));
  logger.info("findById(0):{}", db.findById(0));
  
  logger.info("[update]");
  logger.info("update:{}", db.updateMemo(insertedFriend.id, "HELLO HELLO HELLO"));
  logger.info("update:{}", db.updateMemo(1, new Date().toString()));

  logger.info("findById({}):{}", insertedFriend.id, db.findById(insertedFriend.id));
  
  logger.info("[delete]");
  logger.info("delete:{}", db.deleteById(insertedFriend.id));
  
  logger.info("[map]");
  Map<Integer, Friend> map = db.map(0,  3);
  for (Integer id : map.keySet()) {
   logger.info("map:{} => {}", id, map.get(id));
  }
 }
}

2011年11月5日土曜日

mavenでjavacのオプションを指定してみた


mavenのデフォルト設定では、アノテーションを使うとエラーになる。

デフォルト設定が、javacのオプションが-source 1.4と-target 1.4になっているのが原因。

pom.xmlで次のように-source 1.6と-target 1.6にすれば解決する。

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>

参考情報



-source release
Specifies the version of source code accepted. The following values for release are allowed:
1.3
The compiler does not support assertions, generics, or other language features introduced after JDK 1.3.
1.4
The compiler accepts code containing assertions, which were introduced in JDK 1.4.
1.5
The compiler accepts code containing generics and other language features introduced in JDK 5.
5
Synonym for 1.5.
1.6
This is the default value. No language changes were introduced in Java SE 6. However, encoding errors in source files are now reported as errors, instead of warnings, as previously.
6
Synonym for 1.6.

-target version
Generate class files that target a specified version of the VM. Class files will run on the specified target and on later versions, but not on earlier versions of the VM. Valid targets are 1.1 1.2 1.3 1.4 1.5 (also 5) and 1.6 (also 6).
The default for -target depends on the value of -source:

If -source is not specified, the value of -target is 1.6
If -source is 1.2, the value of -target is 1.4
If -source is 1.3, the value of -target is 1.4
For all other values of -source, the value of -target is the value of -source.

2011年7月10日日曜日

mavenで遊んでみた

いままでちゃんと使ってこなかったことを後悔するくらいmavenって便利かも。

mvnで依存関係解決すると、eclipseで-sources.jarとか-javadoc.jarもちゃんと解決してくれるのってすごく便利。

mvn packageとかmvn deployしたら、-sources.jarと-javadoc.jarも一緒に更新してくれるようにできるのって便利。

今日は、こんなpom.xmlを作って満足してるところ。

<project>
  ...

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <repositories>
    <repository>
      <id>local.file.system</id>
      <url>file:///c:/Users/Kei/Devel/Java/MavenRepos/</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

  <distributionManagement>
    <repository>
      <id>deploy-repository</id>
      <name>deployRepository</name>
      <url>file:///c:/Users/Kei/Devel/Java/MavenRepos/</url>
    </repository>
  </distributionManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <configuration>
          <excludeResources>true</excludeResources>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
              <goals>
                <goal>jar</goal>
              </goals>
          </execution>
        </executions>
      </plugin>
      
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
        <configuration>
          <author>true</author>
          <source>1.6</source>
          <locale>en</locale>
          <!-- <locale>ja</locale> -->
          <showPackage>true</showPackage>
          <showPrivate>true</showPrivate>
          <encoding>utf-8</encoding>
          <charset>utf-8</charset>
          <decoding>utf-8</decoding>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
              <goals>
                <goal>jar</goal>
              </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

androidのプロジェクトもmavenでビルドできたらいいのに。
android library projectの参照解決がしっかりできなくちゃダメ。
もしかして、それもできるのかな?知らないだけ?

2011年5月19日木曜日

AndroidのWebViewがhttpsでloadUrlすると真っ白になる件

Froyo以前のバージョンでは信頼できない証明書を受け入れるかどうかを選択する方法が提供されていないらしい。

しかも、警告も何もなしに読み込みを停止しちゃうので真っ白になる。

その解決方法がプライベートAPIを使ったものなのだけど、プライベートAPIの使い方が、「え?それでいいの?」って感じ。

Android WebView with https loadUrl shows blank/empty page

2011年4月11日月曜日

Apache HttpClientでJSONをgzipしてPOSTする


サーバ側はSinatraのview (erb)で書いたらこんな感じ。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>post /</title>
</head>
<body>
<div>post /</div>
<div>Content-Type: <%= request.content_type%></div>
<div>Content-Encoding: <%= request.env["HTTP_CONTENT_ENCODING"]%></div>
<textarea cols="80" rows="20" readonly><%=
  if "gzip" == request.env["HTTP_CONTENT_ENCODING"]
    begin
      Zlib::Inflate.inflate(request.body.read)
    rescue => e
      e.pretty_inspect
    end
  else
    request.body.read
  end
%></textarea>
</body>
</html>

2011年4月8日金曜日

AndroidでSystem.outとSystem.errをLogに転送する

Androidでは、System.outとSystem.errがLogに転送されているけれど、
任意のプライオリティと任意のタグでLogに転送するコードを作ってみた。

ついでに、はじめてGistを使ってみた。

Gistで保存するコードの先頭には、コピーライトとか書かない方がいいのかもなぁ。



おもいっきりプライベートなAPI使ってるから良くない子に分類されるだろう。

2011年3月31日木曜日

JavaでJSONしたい

JavaでJSONを扱うためのライブラリはたくさんあるなぁ

どれを使ったらいいんだろう?

ちょっと古い記事だけど、A Review of 5 Java JSON Libraries が参考になりそう。

この記事で取り上げているのは次の5つ。

  1. org.json
  2. Jackson
  3. XStream
  4. JsonMarshaller
  5. JSON.simple

www.json.orgに18ものライブラリが掲載されていると書いてあるが、
その中からどういう基準で5つを選んだのか気になるところ。

それでも、これらの中のどれかを状況に合わせて選択すれば今を生きていくには十分だろう。

「とりあえずJSON使っておく?」
みたいな状況だったら JSON.simple を使えば良さそう。

ところで、AndroidのSDKにはorg.json.JSONObjectなどが用意されている。
パッケージ名から類推して org.json のコードが使われているのかと思っていたら間違いだった。

[platform/libcore.git] / json / src / main / java / org / json / JSONObject.java
ここのソースのコメントに、

// Note: this class was written without inspecting the non-free org.json sourcecode.

と書いてある。
オリジナルってことかな?

2011年3月1日火曜日

JavaでXPathを使ったことがあった

<?xml version="1.0" encoding="UTF-8"?>
<hello>
    <item>
        <world attr="value1"/>
    </item>
    <item>
        <world attr="value2"/>
    </item>
</hello>
import java.io.File;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XPathSample {
    public static void main(String[] args) throws Exception {
        XPathExpression xpath =
            XPathFactory.newInstance()
            .newXPath()
            .compile("//world[@attr='value2']");

        Document document =
            DocumentBuilderFactory.newInstance()
            .newDocumentBuilder()
            .parse(new File("hello.xml"));

        NodeList nodeList = (NodeList) xpath.evaluate(document, XPathConstants.NODESET);
        for (int i = 0; i < nodeList.getLength(); ++i) {
            Node node = nodeList.item(i);
            NamedNodeMap attributes = node.getAttributes();
            String value = attributes.getNamedItem("attr").getNodeValue();
            System.out.println(value);
        }
    }
}

2011年2月28日月曜日

ついでにAESで暗号化・復号化するサンプルコード書いてみた

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Sample {
    public static class EncryptedData {
        public byte[] iv;
        public byte[] data;
    }

    public static void main(String[] args) throws Exception {
        // 鍵を生成する。
        // この鍵を保存して暗号文を交換する2者間で安全に共有する。
        final byte[] key = generateKey();
        print("[Key]", key);

        // 暗号化対象の平文
        final byte[] input = "hello world".getBytes();
        print("[Input]", input);

        // 鍵で平文を暗号化する。
        // 復号化するためには、「鍵」と「IV(initial vector)」と「暗号文」が必要。
        // 「鍵」は事前に安全に共有されていると仮定して、ここでは「IV」と「暗号文」を求める。
        final EncryptedData encrypted = encrypt(key, input);

        // 試しに暗号化したデータを表示してみる。
        print("[IV]", encrypted.iv);
        print("[Encrypted]", encrypted.data);

        // 「鍵」と「IV」と「暗号文」を渡して復号化する。 
        final byte[] decrypted = decrypt(key, encrypted.iv, encrypted.data);

        // 試しに復号化したデータを表示してみる。
        print("[Decrypted]", decrypted);

        // さらに、同じ鍵で同じ平文を暗号化・復号化してみる。
        final EncryptedData encrypted2 = encrypt(key, input);
        print("[IV(2)]", encrypted2.iv);
        print("[Encrypted(2)]", encrypted2.data);
        final byte[] decrypted2 = decrypt(key, encrypted.iv, encrypted.data);
        print("[Decrypted(2)]", decrypted2);
    }

    public static byte[] generateKey()
            throws NoSuchAlgorithmException
    {
        final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        //random.setSeed(getRandomSeedBytes()); // ここで本物の乱数で初期化する
        final KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256, random);
        return keyGen.generateKey().getEncoded();
    }

    public static EncryptedData encrypt(byte[] key, byte[] input)
            throws
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        //random.setSeed(getRandomSeedBytes()); // ここで本物の乱数で初期化する
        final SecretKey secretKey = new SecretKeySpec(key, "AES");
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
        final EncryptedData result = new EncryptedData();
        result.iv = cipher.getIV();
        result.data = cipher.doFinal(input);
        return result;
    }

    public static byte[] decrypt(byte[] key, byte[] iv, byte[] input)
            throws
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                InvalidAlgorithmParameterException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final SecretKey secretKey = new SecretKeySpec(key, "AES");
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
        return cipher.doFinal(input);
    }

    public static void print(String tag, byte[] bs) {
        System.out.print(tag);
        for (int i = 0; i < bs.length; ++i) {
            if (i % 16 == 0) {
                System.out.println();
            }
            System.out.print(String.format(" %02X", bs[i]));
        }
        System.out.println();
    }
}

AndroidでRSAを使ってみた

RSAでの暗号化と復号化のシンプルなサンプルコードを書いてみた。

    public static byte[] encrypt(byte[] data)
            throws
                InvalidKeySpecException,
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final Key publicKey =
            KeyFactory
            .getInstance("RSA")
            .generatePublic(new X509EncodedKeySpec(PUBLIC_KEY_BYTES));
        final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }
    public static byte[] decrypt(byte[] data)
            throws
                InvalidKeySpecException,
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final Key privateKey =
            KeyFactory
            .getInstance("RSA")
            .generatePrivate(new PKCS8EncodedKeySpec(PRIVATE_KEY_BYTES));
        final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

ポイントは、Cipherのインスタンスを取得するときに、
フィードバックモードとパディングをちゃんと指定すること。

Cipher.getInstance("RSA") // これは悪い例!

同じJavaVM環境なら、こうやって書いても期待通りに動いてしまう。

フィードバックモードおよびパディングを省略すると、
プロバイダ固有のデフォルト値が使われると書いてある。

同じJavaVM環境ならプロバイダーが同じだからデフォルト値も同じなので問題がないというわけ。

ところが、
Sun(Oracle)のJREやJDKにバンドルされているJCEプロバイダのデフォルトはSunJCEで、
Androidの場合はBouncyCastleのようだ。

SunJCEの場合
"RSA"は"RSA/ECB/PKCS1Padding"
がデフォルトみたい。

BouncyCastleの場合は
"RSA"は"RSA/None/NoPadding"
がデフォルトみたい。

これらはたまたま手元の環境で動かしたらそうなっただけで、
デフォルト値が未来永劫変わらない保証はない。

というわけで、フィードバックモードとパディングを省略すると
サーバとAndroid端末の間で渡したデータを復号化できなくなると思った方がよい。

2011年2月2日水曜日

ChromeでDesktop Notificationしてみた

Chromeのみ。
GmailがChrome Extensionなしで新着通知してくれるようになったのでやってみた。
  • 許可を得ないと機能しない。
  • 許可を得るためにはユーザのアクションが必要。(自動的に許可を求めることはできない)
ということで、初めてボタンをクリックした時は許可を与えてやるべし。
それから、もう一回ボタンをクリックするべし。
<input id="showNotification" type="button" value="show notification" onclick="showNotification()">
<script>
if (!window.webkitNotifications) {
  document.getElementById('showNotification').disabled = "disabled";
}
function showNotification() {
  if (window.webkitNotifications.checkPermission() == 0) {
    var icon = 'https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzc7Z4tgITpt9OABl3LvK8zrzmRQPhszoIVx67rc6XsNGSnK3S3ZT9BS-FpJFK8fjNFKprTax2o0-lRRMz6f1nqdlsQ5Lb50aDVQSAc5tW_eIa45kTAggLtD2-2mrkEZDrJDvzT2LSAg/';
    var title = 'Chrome Notification Sample';
    var message = 'This works only with Google Chrome Web Browser.';
    var n = window.webkitNotifications.createNotification(icon, title, message);
    n.ondisplay = function() {
      setTimeout(function() { n.cancel(); }, 5000);
    };
    n.show();
  } else {
    window.webkitNotifications.requestPermission();
  }
}
</script>

2011年1月26日水曜日

Android Screenshots, Screen Capture, Screen Cast

これは便利だ。

USB接続したAndroidの画面をどんどんキャプチャ―してPNGに保存してくれる。

Android Screenshots, Screen Capture, Screen Cast



AndroidScreenCapture-1.1.zipをダウンロードして実行。

android-sdk_r08-windows.zipでは動かない。
android-sdk_r07-windows.zipを使ったら動いた!
sdkのディレクトリ構造が変わったのが原因だろうな。

ダウンロードはsourceforgeから。

2011年1月13日木曜日

Google APIsとGALAXY Tabのエミュレータ

android-sdkでSamsung GALAXY Tabのエミュレータを簡単に作成できる。

でも、残念ながらGoogle APIs、たとえばGoogle Maps APIを使ったapkを実行できない。

しかたがないので600x1024サイズのAVDを使うことにする。

Google APIs(Google Inc.) - API Level 8をTargetにしたAVDを作成してから、

~/.android/avd/AVDNAME.avd/config.iniのskin.pathを

add-ons/addon_galaxy_tab_samsung_electronics_8/skins/GALAXY Tab

にすると、GALAXY Tabの画面サイズにできる。

動作に影響はしないみたいだけれどskin.nameもGALAXY Tabにしておいた。