Apache HttpClientのソースをScalaで書き換えてみる
社内でやっているScala勉強会で、テストがあるJavaの実装をScalaで書き換えてみようということになり、Apache HttpClientのソースをScalaで書き換えることになりました。
作業の流れは以下のとおりです。
- 対象とするソースを全員でひとつ選ぶ
- そのソースのファイルを削除する(コンパイルエラーになる)
- 各々がScalaで同じクラスを実装し、テストを通す
- ソースレビュー
今回書き換える対象のソースはorg.apache.http.impl.client.CloseableHttpResponseProxyです。
CloseableHttpResponseProxy
これが実際のJavaの実装です。
/* * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * */ package org.apache.http.impl.client; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.annotation.NotThreadSafe; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.util.EntityUtils; /** * @since 4.3 */ @NotThreadSafe class CloseableHttpResponseProxy implements InvocationHandler { private final HttpResponse original; CloseableHttpResponseProxy(final HttpResponse original) { super(); this.original = original; } public void close() throws IOException { final HttpEntity entity = this.original.getEntity(); EntityUtils.consume(entity); } public Object invoke( final Object proxy, final Method method, final Object[] args) throws Throwable { final String mname = method.getName(); if (mname.equals("close")) { close(); return null; } else { try { return method.invoke(original, args); } catch (final InvocationTargetException ex) { final Throwable cause = ex.getCause(); if (cause != null) { throw cause; } else { throw ex; } } } } public static CloseableHttpResponse newProxy(final HttpResponse original) { return (CloseableHttpResponse) Proxy.newProxyInstance( CloseableHttpResponseProxy.class.getClassLoader(), new Class<?>[] { CloseableHttpResponse.class }, new CloseableHttpResponseProxy(original)); } }
で、これが私が書き換えたScalaのソースです。
package org.apache.http.impl.client import java.io.IOException import java.lang.reflect.{InvocationTargetException, InvocationHandler, Method, Proxy} import org.apache.http.HttpResponse import org.apache.http.util.EntityUtils import org.apache.http.client.methods.CloseableHttpResponse import org.apache.http.annotation.NotThreadSafe @NotThreadSafe class CloseableHttpResponseProxy(val original: HttpResponse) extends InvocationHandler { @throws(classOf[IOException]) def close = EntityUtils.consume(original.getEntity) @throws(classOf[Throwable]) override def invoke(proxy: Object, method: Method, args: Array[Object]): Object = { method.getName match { case "close" => { close return null } case _ => { try { method.invoke(original, args :_*) } catch { case e: InvocationTargetException => { e.getCause match { case cause: Throwable => throw cause case _ => throw e } } } } } } } object CloseableHttpResponseProxy { def newProxy(original: HttpResponse): CloseableHttpResponse = { Proxy.newProxyInstance( classOf[CloseableHttpResponseProxy].getClassLoader, Array[Class[_]](classOf[CloseableHttpResponse]), new CloseableHttpResponseProxy(original)).asInstanceOf[CloseableHttpResponse] } }
これがScala的に完璧かはわからないですが、今回勉強になったことをひとつずつ書いていきます。
@throws
Scalaではすべての例外をメソッドの外の投げることができる。
明示的に記載する場合は@throwsアノテーションを使う。
Java
public void close() throws IOException {/** ... */}
Scala
@throws(classOf[IOException])
def close = EntityUtils.consume(original.getEntity)
classOf[T]
JavaのCloseableHttpResponse.classをScalaで書く場合、classOf[T]を使う。
Java
CloseableHttpResponse.class
Scala
classOf[CloseableHttpResponse]
: _*
Method#invokeのところで、Array[Object]を渡しているが、args :_*としないと以下の様な警告がでる。
I assume that the elements of this array should be passed as individual arguments to the vararg. Therefore I follow the array with a `: _*', to mark it as a vararg argument. If that's not what you want, compile this file with option -Xno-varargs-conversion.
Javaの可変長引数をとるメソッドにArrayを渡す場合、: _*を付けないと警告となる。
Scalaの可変長引数をとるメソッドに、変数で値を渡す場合には同様の対応をしなければいけないが、Scalaの場合はコンパイルエラーとなる。
例
Stringの可変長引数を取り、出力するメソッド
scala> def foo(args: String*) {args.foreach(println)} foo: (args: String*)Unit scala> foo("foo", "bar", "boo") foo bar boo
これに対して変数で値を渡そうとすると、そのままでは下記のようにエラーになる。
scala> val list = List("foo", "bar", "boo") list: List[String] = List(foo, bar, boo) scala> foo(list) <console>:10: error: type mismatch; found : List[String] required: String foo(list) ^
なので、: _*をつける。
scala> foo(list: _*) foo bar boo
ちょっとめんどい。
Scalaのバージョンによる違い?
error: cannot find symbol class CloseableHttpResponseProxy
最初、pomで定義しているscala-libraryのバージョンの指定が、2.7.2でやっていて、このクラスの呼び出し元でこんなエラーが出ててよくわかんなかったんですが、バージョンを2.10.0にしたら解決できました。
error: unreachable code
e.getCause のmatchのところで、nullのところを_(プレースホルダー)にすると到達不能コードと言われ、コンパイルエラーとなりました。
詳しく調べてないですが、これも2.10.0にすることで解決できました。