@blog.justoneplanet.info

日々勉強

ZipInputStreamから文字列を取り出す

メモ。

■ZipInputStream

Zipはzip化されたデータとヘッダー情報から成る。

public static String readZIPStream(InputStream inputStream) throws IOException {
    String str = "";
    String readed;
    ZipInputStream zis = new ZipInputStream(inputStream);
    InputStreamReader inputStreamReader = new InputStreamReader(zis);
    BufferedReader bufferReader = new BufferedReader(inputStreamReader);
    while (zis.getNextEntry() != null) {
        while ((readed = bufferReader.readLine()) != null) {
            str += readed + "\n";
        }
        zis.closeEntry();
    }
    bufferReader.close();
    inputStreamReader.close();
    zis.close();
    return str;
}

■GZIPInputStream

GZipはzip化されたデータ本体で、GZipの場合は勝手が違う。

public static String readGZIPStream(InputStream inputStream) throws IOException {
    String str = "";
    GZIPInputStream gzis = new GZIPInputStream(inputStream);
    InputStreamReader inputStreamReader = new InputStreamReader(gzis);
    BufferedReader bufferReader = new BufferedReader(inputStreamReader);
    String readed;
    while ((readed = bufferReader.readLine()) != null) {
        str += readed;
    }
    bufferReader.close();
    inputStreamReader.close();
    gzis.close();
    return str;
}

BufferedReaderをインスタンス化するときにサイズを指定しない場合は8192となる。

apkからソースを読んでみる

Proguardを使ったアプリのapkを逆コンパイルしてソースを読んでみる。

unzip Hello.apk
wget https://dex2jar.googlecode.com/files/dex2jar-0.0.9.15.zip
unzip dex2jar-0.0.9.15.zip

classes.dexをjarに変換する。

./dex2jar.sh ~/Hello/classes.dex

面倒なのでJava Decompilerのツールでjarをそのまま開く。

■比較

定数は以下のように展開されて削除されている。

private static final int HELLO_ID = 1;
// nothing

staticでなければ残るが変数名は変えられる。

public final int HELLO_CODE = 1;
public final int rk = 1;

ギャラリーから取得したuriをパス表記にする以下のメソッドは

  protected String convertMediaUriToPath(Uri uri) {
    String [] proj = {MediaStore.Images.Media.DATA};
    Cursor cursor = getContentResolver().query(uri, proj, null, null, null);
    if (cursor != null) {// prevent from NullPointerException of the stack trace
      int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
      cursor.moveToFirst();
      String path = cursor.getString(index); 
      cursor.close();
      return path;
    }
    return null;
  }

以下のようにメソッド名が簡潔になり、変数名も簡略化される。Proguardを使わなくても定数は展開される。

protected String a(Uri paramUri)
  {
    String[] arrayOfString = { "_data" };
    Cursor localCursor = getContentResolver().query(paramUri, arrayOfString, null, null, null);
    String str = null;
    if (localCursor != null)
    {
      int i = localCursor.getColumnIndexOrThrow("_data");
      localCursor.moveToFirst();
      str = localCursor.getString(i);
      localCursor.close();
    }
    return str;
  }

以下のようにActivityのクラス名は変わらない。

public class HelloActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
public class HelloActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {

これはProguardの設定に以下のような記述があるからである。

vim ~/android-sdk-mac_x86/tools/proguard/proguard-android.txt
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

その他にもproguard-android.txtには、manifestファイルに記述するクラスはクラス名が変更されないような記述が書いてある。

Mozcの記号辞書に全角チルダを追加する

全角波ダッシュが一部の環境で文字化けするために全角チルダも使えるようにする。

// JavaScript
unescape('%uff5e');// 全角チルダ:~
unescape('%u301c');// 波ダッシュ:〜

以下のファイルを開き

vim data/symbol/symbol.tsv

波ダッシュについては以下のようになっているので

記号    〜  にょろ なみ から ー - ~ きごう より 波ダッシュ      GENERAL

以下のように追記すればよい。

記号    ~  にょろ ちるだ から ー - ~ きごう より   全角チルダ      GENERAL

macでは見栄えに全く差が無いので、一応vimで検索して確かめておく。

:/\%u301c
:/\%uff5e

ちなみにこのtsvの形式は以下のようになっている。

POS CHAR    Reading (space separated)   description additional description  category    memo    unicode

Xcode 5.1にアップデートするとビルドできなくなる

arm64に対応していないものが原因である。

■Build Settings>Architectures

CocoaPodsに対応するため以下の部分を

$(ARCHS_STANDARD)

以下のように修正する。

$(ARCHS_STANDARD_32_BIT)

変更後、以下のコマンドを実行する。

pod update

■JSONKit

isa is deprecatedと表示されてビルドできないので、Podfileに以下を追記する。ちなみにcompiler flagに-ferror-limit=0を設定してもビルドできない。

post_install do |installer_representation|
    installer_representation.project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['CLANG_WARN_DIRECT_OBJC_ISA_USAGE'] = 'YES'
        end
    end
end

JSONKitをやめてNSJSONSerializationにしたいのだが某SDKが使用しているので諦める。

■OpenSSL

OpenSSL-for-iPhoneを再ビルド

cd OpenSSL-for-iPhone/
vim ./build-libssl.sh

以下のように修正する。というかmasterからpullすると7.1になっている。

VERSION="1.0.1e"                                                          #
SDKVERSION="7.1"           

以下のコマンドでビルドしてできるlibcrypto.aとlibssl.aで上書きする。

./build-libssl.sh

参考

Mozc for Androidのテストを読んでみる

EasyMockを使っている部分があるのでそこに関する自分用のメモ

public class MozcViewTest extends InstrumentationTestCaseWithMock

以下のようにしてモックを作る。

@SmallTest
public void testStartAnimation() {
    MozcView mozcView = createViewMockBuilder(MozcView.class)
        .addMockedMethods("getCandidateView", "getSymbolInputView", "expandDropShadowAndBackground", "collapseDropShadowAndBackground")
        .createMock();
    CandidateView candidateView = createViewMockBuilder(CandidateView.class)
        .addMockedMethods("getVisibility")
        .addMockedMethod("setVisibility", int.class)
        .createMock();
    SymbolInputView symbolInputView = createViewMockBuilder(SymbolInputView.class)
        .addMockedMethods("getVisibility")
        .addMockedMethod("setVisibility", int.class)
        .createMock();

以下のようにテストする。

    {
      resetAll();// 今までのexpectで登録されたものをリセット

      expect(mozcView.getCandidateView()).andReturn(candidateView);
      // (a)を実行した時、モックmozcViewのgetCandidateViewが呼ばれ返り値がcandidateViewでとなる事を想定

      candidateView.setVisibility(View.GONE);
      expect(mozcView.getSymbolInputView()).andReturn(symbolInputView);
      // (a)を実行した時、モックmozcViewのgetSymbolInputViewが呼ばれ返り値がsymbolInputViewでとなる事を想定

      expect(symbolInputView.getVisibility()).andReturn(View.VISIBLE);
      // (a)を実行した時、モックsymbolInputViewのgetVisibilityが呼ばれ返り値がView.VISIBLEでとなる事を想定

      replayAll();// 登録したモックをリプレイモードにする
      mozcView.startCandidateViewOut();// (a)…startCandidateViewOutを実行する
      verifyAll();// 登録したモックを全て検証する(想定通りになったかどうか検証)
    }

startCandidateViewOutは以下のようになっているとする。

  void startCandidateViewOut() {
    getCandidateView().setVisibility(View.GONE);
    if (getSymbolInputView().getVisibility() != VISIBLE && isDropShadowExpanded) {
      collapseDropShadowAndBackground();
    }
  }

この時にstartCandidateViewOutで、expectしていないメソッドが呼ばれたり、定義した回数以上呼ばれたりなどするとテストが失敗する。

参考

mozcで「かお」の変換時に顔文字が表示されないようにする

現状、かおの変換候補表示にタイムラグがある。

rewriter/emoticon_rewriter.cc

bool RewriteCandidate(Segments *segments)

以下の部分を削除する。

      // "かお"
    } else if (key == "\xE3\x81\x8B\xE3\x81\x8A") {
      // When key is "かお", expand all candidates in conservative way.
      const EmbeddedDictionary::Token *token
          = Singleton<EmoticonDictionary>::get()->GetDictionary()->AllToken();
      CHECK(token);
      // first 6 candidates are inserted at 4 th position.
      // Other candidates are pushed to the buttom.
      value = token->value;
      value_size = token->value_size;
      initial_insert_pos = 4;
      initial_insert_size = 6;

rewriter/emoticon_rewriter_test.ccに2箇所ほど「かお」の変換をテストしている箇所があるので削除する。

TizenでUUIDを取得する

void
UUIDManager::Get(String &uuid) {
	result r = E_SUCCESS;
	String keyUUID("key_uuid");
	AppRegistry* appRegistry = Application::GetInstance()->GetAppRegistry();
	r = appRegistry->Get(keyUUID, uuid);
	if (r == E_KEY_NOT_FOUND) {
		String u;
		Tizen::Base::UuId* pUUID = Tizen::Base::UuId::GenerateN();
		u.Append(pUUID->ToString().GetPointer());
		appRegistry->Add(keyUUID, u);
		delete pUUID, pUUID = NULL;
	}
	r = appRegistry->Get(keyUUID, uuid);
}

Tizenでデータを永続化する

SQLiteを使うまでのデータでない場合に、SharedPreferencesやNSUserDefaultのようなものが欲しくなる。Tizenでは以下のようにする。

#include <FApp.h>
using namespace Tizen::App;
result r = E_SUCCESS;
String hogeKey("tmp_hoge_key");
String hogeValue("tmp_hoge_value");
String value("");
AppRegistry* appRegistry = Application::GetInstance()->GetAppRegistry();
r = appRegistry->Get(hogeKey, value);
if (r == E_KEY_NOT_FOUND) {
	AppLog("value:%ls", value.GetPointer());
	appRegistry->Add(hogeKey, hogeValue);
}
r = appRegistry->Get(hogeKey, value);
AppLog("value:%ls", value.GetPointer());

以下のようなログが表示される。

virtual result HogeHoge::OnInitializing()(45) > value:
virtual result HogeHoge::OnInitializing()(50) > value:tmp_hoge_value
virtual result HogeHoge::OnInitializing()(50) > value:tmp_hoge_value

TizenアプリでSQLiteを使う

SDKに同梱されているコードがいまいちわかりにくかったので、オフィシャルサイトにあるdocumentationからサンプルコードを拝借する。

■DBManager.cpp

とりあえず上から読んでいってSQLもしくはそれに準ずるものを探す。

result DBManager::CreateTable()

CREATE TABLE文がそのまま見つかったので読んでみる。

String sql;
result r = E_SUCCESS;
sql.Append(L"CREATE TABLE IF NOT EXISTS  member(seq INTEGER PRIMARY KEY,id varchar(20),name varchar(20),age INTEGER)");
r = Execute(sql);
AppLog("create : %s",GetErrorMessage(r));

return r;

SQL文を作ってそのままprivateメソッドに投げてるようだ。

result DBManager::Execute(String sql)

投げられたSQLをそのまま処理しているようだ。

__pDatabase->BeginTransaction();
r = __pDatabase->ExecuteSql(sql, true);
__pDatabase->CommitTransaction();

__pDatabaseを操作している。

result DBManager::Open(String dbName)

__pDatabaseをインスタンス化している。use `dbname`;的なところでDBをopenしているところのようだ。

AppLog("Open");
__dbName = dbName;
result r = E_SUCCESS;
__pDatabase = new Database;
__pDatabase->Construct(__dbName,true);
return r;

bool DBManager::IsMember(String id)

以下のとおり取り出したデータはAndroidのCursorのようになっているようだ。

bool isMember = false;
String sql;
sql.Format(100,L"SELECT id FROM member where id = '%ls'",id.GetPointer());

 // select a query using the Database::QueryN() wrapper AP
DbEnumerator* pEnum = __pDatabase->QueryN(sql);
if (pEnum)
{
	while (pEnum->MoveNext() == E_SUCCESS)
	{
		isMember = true;
	}
	delete pEnum;
}

return isMember;

result DBManager::GetAllMember(ArrayList& list)

もう少しちゃんと取り出しているところを見る。

AppLog("GetAllMember");
result r = E_SUCCESS;

String sql;
sql.Format(50,L"select seq, id, name,age from member");

MEMBERINFO* pInfo = null;
 // select a query using the Database::QueryN() wrapper AP
DbEnumerator* pEnum = __pDatabase->QueryN(sql);
if (pEnum)
{
	while (pEnum->MoveNext() == E_SUCCESS)
	{
		pInfo = new MEMBERINFO();
		r = pEnum->GetIntAt(0, pInfo->seq);
		r = pEnum->GetStringAt(1,pInfo->id);
		r = pEnum->GetStringAt(2,pInfo->name);
		r = pEnum->GetIntAt(3,pInfo->age);


		list.Add(*pInfo);
	}
	delete pEnum;
}

return r;

サンプルコードが簡略化されていて理解しやすい。

■DBManager.h

このクラスはSingletonパターンになっていて以下のメソッドでインスタンスを取得する。

static DBManager* GetDB()
{
	if(__pInstance == null)
	{
		__pInstance = new DBManager();
	}

	return __pInstance;
}

使い終わった時のメソッドも用意されている。

static void ReleaseDB()
{
	if(__pInstance)
	{
		delete __pInstance;
		__pInstance = null;
	}
}

コンストラクタ

DBManager::DBManager()
:__pDatabase(null)
{
	// TODO Auto-generated constructor stub
	Init();
}
void DBManager::Init()
Open(App::GetInstance()->GetAppRootPath() + L"data/mdb.db");
CreateTable();

次の2つのメソッドを呼び出している。

result DBManager::Open(String dbName)

引き数で与えられたデータベースファイルを開く。

AppLog("Open");
__dbName = dbName;
result r = E_SUCCESS;
__pDatabase = new Database;
__pDatabase->Construct(__dbName,true);

return r;
result DBManager::CreateTable()

CREATE TABLE文を実行する。

String sql;
result r = E_SUCCESS;
sql.Append(L"CREATE TABLE IF NOT EXISTS  member(seq INTEGER PRIMARY KEY,id varchar(20),name varchar(20),age INTEGER)");
r = Execute(sql);
AppLog("create : %s",GetErrorMessage(r));

return r;

デストラクタ

DBManager::~DBManager()
{
	Close();
	// TODO Auto-generated destructor stub
}
result DBManager::Close()
AppLog("Close");
result r = E_SUCCESS;
if(__pDatabase)
{
	delete __pDatabase;
	__pDatabase = null;
}

return r;

■Form1.cpp

DBManagerの使い方は以下のようになっている。

GetDB()->GetAllMember(list);
result
Form1::OnTerminating(void)
{
	result r = E_SUCCESS;

	if(__pInfoPopup)
	{
		delete __pInfoPopup;
		__pInfoPopup = null;
	}
	ReleaseDB();

	// TODO: Add your termination code here

	return r;
}