Jython を学ぶ(6)───JAR ファイルを動的にロードする

前回の記事で、 batik.jar をクラスパスに入れる時に環境変数を使わなければならなかったのが良くなかったので、JAR ファイルを動的ロードすることにします。

Java では煩雑なリフレクション API を使わなければならないのですが、動的型付けかつ動的言語である Python では全てがリフレクションのようなものです。
実際、 Jython では普通に import したクラスのメソッドもリフレクション API で呼び出されていると思います。

例:

#! jython
# dload.py
from java.io import File
from java.net import URL
from java.net import URLClassLoader
import jarray
urlClassLoader=URLClassLoader(jarray.array([File("batik.jar").toURL()], URL))
JSVGCanvas=urlClassLoader.loadClass("org.apache.batik.swing.JSVGCanvas")
#以下、JSVGCanvas を普通に import した場合と同じように使える
print JSVGCanvas
jsvgCanvas=JSVGCanvas()
print jsvgCanvas.toString()
# ... 

カレントディレクトリに batik.jar を置いて、

jython dload.py


六行目が少しわかりにくいかもしれません。
URLClassLoader のコンストラクタは JAR ファイルまたはディレクトリの URL の配列を引数にとります。
このクラスローダは引数に指定された URL からクラスとリソースを検索します。


ClassLoader#loadClass で指定した名前のクラスオブジェクトを得ることができます。
ここで Java ならリフレクション API でこのクラスオブジェクトを使う羽目になるのですが、Jython では普通に import した場合と同じように使えます。


今後この日記に載せる Jython プログラムでは標準ライブラリ以外の JAR ファイルは全て動的にロードしようと思います。

Jython を学ぶ(5)───Apache Batik の SVGCanvas で SVG を簡単に表示する

しばらく中断といっておきながら一日しか中断しないのであった。

どうも I/O 関連は考えが定まらないので、先に Apache Batik で SVG を表示する例を紹介します。

http://xmlgraphics.apache.org/batik/using/swing.html に載っている例の Jython 版です。

#! jython
from java.lang import Runnable
class PyCallableRunnable(Runnable):
	def __init__(self, pyCallable):
		self.pyCallable=pyCallable
		return
	def run(self):
		self.pyCallable()
		return
def initializeAWTUI():
	from javax.swing import JPanel
	from java.awt import BorderLayout
	panel=JPanel(BorderLayout())
	from java.awt import FlowLayout
	p=JPanel(FlowLayout(FlowLayout.LEFT))
	from javax.swing import JButton
	button=JButton("Choose SVG File")
	p.add(button)
	from org.apache.batik.swing import JSVGCanvas
	svgCanvas=JSVGCanvas()
	panel.add("North", p)
	panel.add("Center", svgCanvas)
	def buttonActionPerformed(ae):
		from javax.swing import JFileChooser
		fc=JFileChooser(".")
		choice=fc.showOpenDialog(panel)
		if choice==JFileChooser.APPROVE_OPTION:
			f=fc.selectedFile
			svgCanvas.setURI(f.toURL().toString())
	button.actionPerformed=buttonActionPerformed
	from javax.swing import JFrame
	from java.awt import Rectangle
	frame=JFrame(
		title="Batik Test", 
		bounds=Rectangle(800, 600), 
		visible=False
		)
	frame.contentPane.add(panel)
	def windowClosing(e):
		window=e.window
		window.visible=False
		window.dispose()
	frame.windowClosing=windowClosing
	frame.visible=True
if __name__=="__main__":
	from java.awt import EventQueue
	EventQueue.invokeLater(PyCallableRunnable(initializeAWTUI))


環境変数 CLASSPATH に ./batik.jar を追記して、

jython svgviewer.py


なんかだらだら解説を書くのに疲れたので、各自解読してください。


Jython に慣れればあとは JavaDoc を調べれば分かるはずです。きっと分かるに違いない。

I/O に悩む(2)

昨日の記事があまりにも酷い。


よく考えたら欲しいのは新しいアーカイブファイルフォーマットではなく既存のリソースに今より楽にアクセスするインターフェースでした。

If all you have is a hammer, everything looks like a nail!

仮想ファイルシステムを構築して、それにユーザーモードファイルシステムからアクセス
ZIPもTARもFTPもISOもUDFもWebDAVSQLもいわゆる"Recent Files"も単一のファイルシステムにマウント


"最近使ったファイル"にCreateFileで(つまり既存のあらゆるアプリケーションで)アクセスできたら嬉しいじゃないですか。


これで私がディスクに関してやりたいことは大体できるんじゃないか?と思いました。
もちろん問題はパフォーマンスと信頼性。

I/O で悩む

ディスクアクセスの効率化についていろいろ調べたりしているのですが、複雑すぎてよく分からないというのが正直な感想です。

解決策

下に行くほど根本的だが非現実的。

  • 今使っているマシンの搭載メモリ量が32bitの壁に達していないならば、搭載メモリを増やす。
  • 64bit化して搭載メモリを増やす。
  • 今使っているハードディスクの容量に匹敵する容量ののRAMディスクを用意する。

いや、全部非現実的かな……
単にメモリは多ければ多いほど良いですよ、ってだけですね。

構想

64bit環境で数百GBのイメージファイルを丸ごとメモリにマップ。
→そのメモリ上にヒープを構築すれば、比較的単純なコードでファイルシステムを作れる。
→デバイスに直接アクセスするコードを書くのは怖いけれど、これなら野心的なファイルシステムをどんどん自作可能。

既存のインターフェースから使うにはFUSE http://fuse.sourceforge.net/Dokan http://dokan-dev.net/ユーザーモードファイルシステムを作りイメージファイルをマウントできるようにする。
ドライバ開発はハードウェアに近いので怖い。


ちなみに完成しても無茶苦茶俺専用仕様になるでしょうから、公開も紹介もしないでしょう。

Jython を学ぶ(4)───Jython で Java の配列を使う(2)、 Jython で Java の I/O 関連 API を使う(1)

Jython を学ぶ(4) part1───JythonJava の配列を使う(2)

前回の続きです。
例を再掲します。

#!jython
import jarray
buffer=jarray.zeros(3, "b")
for i in range(0, len(buffer)):
	buffer[i]=i
for b in buffer:
	print b
buffer1=jarray.zeros(5, "b")
from java.lang import System
System.arraycopy(buffer, 0, buffer1, 0, 3)
print buffer1
pyList=[5, 4, 3, 2, 1]
buffer2=jarray.array(pyList, "b")
print buffer2


この Jython コードにおおよそ相当する Java のコードは以下の通りです。

class ArrayTest{
	public static void main(String args[]){
		byte[] buffer=new byte[3];
		for(byte i=0; i<buffer.length; i++){
			buffer[i]=i;
		}
		for(byte b:buffer){
			System.out.println(b);
		}
		byte[] buffer1=new byte[5];
		System.arraycopy(buffer, 0, buffer1, 0, 3);
		for(byte b:buffer1){
			System.out.print(b);
		}
		System.out.print("\n");
		byte[] buffer2={5, 4, 3, 2, 1};
		for(byte b:buffer2){
			System.out.print(b);
		}
		System.out.print("\n");
	}
}

javac ArrayTest.java
java ArrayTest


この例の要点は以下の通りです。

jarray.zeros 関数

zeros 関数は第一引数に長さ、第二引数に配列に格納される値型をあらわす文字または格納される参照の型をとり、そのような配列をゼロフィルして返します。
値型を表す文字は Python 標準ライブラリの array モジュールで使われている文字と同じで、以下の通りです。

文字
"z" boolean
"c" char
"b" byte
"h" short
"i" int
"l" long
"f" float
"d" double
jarray.array 関数

array 関数は第一引数に Python のシーケンス、第二引数に zeros と同様の型の指定をとり、シーケンスの内容が格納されたシーケンスと同じ長さの配列を返します。

配列はシーケンス

Java の配列を Python のシーケンスとして使うことができます。例えば for 文、 len 関数、 range 関数と共に使うことができます。

java.lang.System#arraycopy メソッド

System#arraycopy メソッドは配列を効率的にコピーする静的メソッドです。
http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/System.html#arraycopy(java.lang.Object, int, java.lang.Object, int, int)


arraycopy は Java の for 文でコピーする場合はもちろん、 JNI を使ってコピーするよりはるかに高速です。これは arraycopy が JVM に組み込まれているからです。
arraycopy を使えば、( Java プラットフォームが正しく実装されていれば)型安全に高速な配列のコピーができます。

Jython を学ぶ(4) part2───JythonJava の I/O 関連 API を使う(1)

Java の配列を扱えるので、バイト配列を多用する Java の I/O を Jython だけでも効率よく使うことができます。


本当は Java7 で導入されるNIO2(JSR 203: More New I/O APIs for the JavaTM Platform ("NIO.2"), http://www.jcp.org/en/jsr/detail?id=203)を使いたいのですが、 Java7 のリリース予定は2009年の夏です。
正式リリース以前のライブラリを使ったコードは例として良くないので、Early Access 版は使いません。

NIO2 では OS の非同期 I/O 機能を使う API が提供されるようです。この機能を使えば OS によって最適化された効率の良い非同期 I/O が実現できるでしょう。

しかし今はまだこれを使えませんので、非常に単純な非同期 I/O 機能を Jython で書くことにします。

以下はカレントディレクトリの source という名前のファイルの内容を destination という名前のファイルにコピーする Jython スクリプトです。
ファイルが存在しなかったりすると例外を送出して停止します。

#!jython
import jarray
from java.lang import Runnable
from java.lang import ThreadLocal
from java.io import FileInputStream
from java.io import FileOutputStream
from java.util.concurrent import Callable
from java.util.concurrent import Executors
from java.util.concurrent import FutureTask

class PyCallableRunnable(Runnable):
	def __init__(self, pyCallable):
		self.pyCallable=pyCallable
		return
	def run(self):
		self.pyCallable()
		return
class PyCallableCallable(Callable):
	def __init__(self, pyCallable):
		self.pyCallable=pyCallable
		return
	def call(self):
		return self.pyCallable()
asyncTranslateThreadLocal=ThreadLocal()
asyncTranslateExecutorService=Executors.newSingleThreadExecutor()
#newCachedThreadPool(), newFixedThreadPool(n), ...
def asyncTranslate(inputStream, outputStream):
	def translate():
		amountTranslated=0
		buffer=asyncTranslateThreadLocal.get()
		if buffer is None:
			buffer=jarray.zeros(4048, "b")
			asyncTranslateThreadLocal.set(buffer)
		while True:
			amountRead=inputStream.read(buffer)
			if amountRead==-1:
				break
			amountTranslated+=amountRead
			outputStream.write(buffer, 0, amountRead)
		return amountTranslated
	futureTask=FutureTask(PyCallableCallable(translate))
	asyncTranslateExecutorService.execute(futureTask)
	return futureTask
def asyncCopy(sourcePath, destinationPath):
	inputStream=FileInputStream(sourcePath)
	outputStream=FileOutputStream(destinationPath)
	future=asyncTranslate(inputStream, outputStream)
	return future
if __name__=="__main__":
	future=asyncCopy("source", "destination")
	print future.get()
	asyncTranslateExecutorService.shutdown()

この場合は非同期である意味がほとんどありませんが、複雑なプログラムでは非同期 I/O が役に立つでしょう。
内容の解説は次回に回します。

Jython を学ぶ(3)───Jython で Java の配列を使う(1)

Apache Batik を使う例を書いていたら、色々と解決すべき問題があったため、まずはそちらについて書くことにします。

JythonJava の配列を使う

Java の配列は固定長配列です。


値型(boolean, char, byte, short, int, long, float, double)を扱う JavaAPI を呼び出したときは、それに相当するクラスのインスタンス(Boolean, Char, Byte, Short, Integer, Long, Float, Double)との間で Boxing ・ Unboxsing が行われます。


しかし長大な配列を扱うには Java の固定長配列を直接扱えた方がいいので、 Jython では Java の固定長配列を扱うためのモジュール jarray が提供されています。
http://www.jython.org/docs/jarray.html


Python の標準ライブラリでも値型配列を扱うための array モジュールが提供されています。
http://docs.python.org/lib/module-array.html
これは Jython でも提供されています。
Numerical Python と呼ばれるライブラリではさらに効率的な配列が使えます。


jarray の簡単な使い方の例は以下の通りです。

#!jython
import jarray
buffer=jarray.zeros(3, "b")
for i in range(0, len(buffer)):
	buffer[i]=i
for b in buffer:
	print b
buffer1=jarray.zeros(5, "b")
from java.lang import System
System.arraycopy(buffer, 0, buffer1, 0, 3)
print buffer1
pyList=[5, 4, 3, 2, 1]
buffer2=jarray.array(pyList, "b")
print buffer2

結果は

0
1
2
array('b',[0, 1, 2, 0, 0])
array('b',[5, 4, 3, 2, 1])

どうにも時間がないので

例の解説はまた次回