読者です 読者をやめる 読者になる 読者になる

タツヤカワゴエ跡地

タツヤカワゴエの跡地です

Semicolonless Java を実現する話

Semicolonless Java を実現する話

デンジャラス!ゾンビ!!!

こんにちは!ゲームマスターこと 檀 黎斗 です!

2000 年問題でバグスターウィルス見つけたの僕ですから!!

からのー

ジュリアナー!!トーキョー!

こんにちわ、ジョン・ロビンソンこと半ズボンの宇宙人です。

どうです?なんていうかアメブロっぽい感じっていうの?ムカつくでしょ??

そうでしょう!そうでしょう!

ところでみなさん、Java 書いてますか?Generics 理解してますか?

無駄にドリコムのスライドに釣られてませんか?

今回は Semicolonless Java について書いてみたいと思います。

Semicolonless Java

Semicolonless Java とはその名の通り、セミコロンを使わずに Java でプログラミングすることです。

ある種のパズル、コードゴルフの類に近いといった方が良いかも知れません。

簡単な例ですが、Hello world を出力するプログラムは以下のように書きます。

public class Hello {
        public static void main(String[] args) {
                if (null == System.out.printf("Hello world")){}
        }
}

まあこのようにセミコロンを回避しつつコードを書いていくのですが、正直遊びの範疇をでません。

では本当の Semicolonless Javaは実現できないのでしょうか?

近年では ES6, Golang などセミコロンを書かなくても良い言語をよく目にするようになりました。

確かにセミコロンを書く必要のない言語の方が煩わしくないし、見た目もスッキリして人気がありそうです。

Java もそのようにセミコロンをなくせばもっと人気がでそうです。

従来の Java のコードをほぼそのままにセミコロンレスを実現することはできないものなのでしょうか?

JSR 199: Java Compiler API

Java には Java Compiler をプログラムから操作する API があります。

JSR 199: Java Compiler API です。javax.tools パッケージに属しています。

簡単な例を挙げます。

App.java

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;

public class App {

  public static void main(String[] args) throws Exception {
    final String sourceFile = "./helloworld.java";
    final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)) {

        List<File> sourceFileList = new ArrayList<File>();
        sourceFileList.add(new File(sourceFile));
        final Iterable<? extends JavaFileObject> compilationUnits =
            fileManager.getJavaFileObjectsFromFiles(sourceFileList);
        final CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits);
        final boolean result = task.call();
        if (result) {
            System.out.println("Compilation was successful");
        } else {
            System.out.println("Compilation failed");
        }
    }
  }
}

ToolProvider.getSystemJavaCompiler() から JavaCompiler を取得、FileManagerコンパイル対象を設定、 CompilerTask を実行してコンパイルするような流れになります。

さてこの API ではどれくらいの事が可能なのでしょうか?

主に操作するとすれば以下の 2 クラスだと思います。

javax.tools.JavaCompiler

javax.tools.JavaCompiler.CompilationTask

見てみるとあの API は AnnotaionProcessor を絡めることは出来るかも知れません。

ですがコンパイラーの挙動を変更するなどといったカスタマイズ要件に対しては貧弱すぎてほぼ何もできないように見えます。

やはりセミコロンレスを実現するには処理系そのものを改修しないといけないのでしょうか??

Real world Java Compiler API

javax.tools.* 公開されている API では何もカスタマイズのしようがないのは事実です。

ですが世の中にはこの Compiler API を実際に使用して様々なことをやっているプロダクトが多数存在します。

ではどうしているのでしょうか?実際に使用している例を元に見ていきたいと思います。

ErrorProneの場合

最近、導入されることが多くなってきてる ErrorProne ですが、この ErrorProne もこの API を使用しています。

ErrorProneコンパイル時に静的解析を行います。そのため、この API でカスタマイズした静的解析付きコンパイラーを提供します。

土台になる部分のコードは以下のようになっています。

BaseErrorProneJavaCompiler.java

  @Override
  public CompilationTask getTask(
      Writer out,
      JavaFileManager fileManager,
      DiagnosticListener<? super JavaFileObject> diagnosticListener,
      Iterable<String> options,
      Iterable<String> classes,
      Iterable<? extends JavaFileObject> compilationUnits) {
    ErrorProneOptions errorProneOptions = ErrorProneOptions.processArgs(options);
    List<String> remainingOptions = Arrays.asList(errorProneOptions.getRemainingArgs());
    ImmutableList<String> javacOpts = ImmutableList.copyOf(remainingOptions);
    javacOpts = defaultToLatestSupportedLanguageLevel(javacOpts);
    javacOpts = setCompilePolicyToByFile(javacOpts);
    final JavacTaskImpl task =
        (JavacTaskImpl)
            javacTool.getTask(
                out, fileManager, diagnosticListener, javacOpts, classes, compilationUnits);
    setupMessageBundle(task.getContext());
    RefactoringCollection[] refactoringCollection = {null};
    task.addTaskListener(
        createAnalyzer(errorProneOptions, task.getContext(), refactoringCollection));
    return new CompilationTask() {
      @Override
      public void setProcessors(Iterable<? extends Processor> processors) {
        task.setProcessors(processors);
      }

      @Override
      public void setLocale(Locale locale) {
        task.setLocale(locale);
      }

      @Override
      public Boolean call() {
        return wrapPotentialRefactoringCall(
            task.call(), new PrintWriter(out, true), refactoringCollection[0]);
      }
    };
  }

注目ポイントは以下の部分です。

    final JavacTaskImpl task =
        (JavacTaskImpl)
            javacTool.getTask(
                out, fileManager, diagnosticListener, javacOpts, classes, compilationUnits);

ハイ!きました! JavacTaskImpl

ということで実際にカスタマイズしようとするとあのAPIでは貧弱すぎなので cast して使用しているというのが現実です。

ErrorProneJavacTaskImplcast することで使える addTaskListener メソッドを使って静的解析を行っているのです。

簡単に addTaskListener メソッドの説明をしておくとコンパイラーは幾つかのステージを踏んでクラスファイルを生成するのですが、このメソッドはその各ステージ、成果物作成毎に呼ばれるコールバックを設定するメソッドです。

  • ステージ情報
  • 対象成果物

ステージ開始、終了時に特定のメソッドが呼ばれるので ErrorProne はその情報を元に解析を行っているのです。

その他にも JavacTask (JavacTaskImpl) は使われています。

ENSIMEJava の型情報の取得、補完情報なども JavacTask を使用して解析されています。

では JavacTaskImpl を使うとどれくらいのことができるのでしょうか?

セミコロンレスは実現できるのでしょうか??

Parser 載せ替え Hack

セミコロンレスを実現するにはシンプルに考えて AST を作成時にセミコロンの有無を無視してくれれば良さそうです。

AST を作成する Parser 実装を差し替えることができればなんとかなりそうですね。

ですが、Parser を載せ替えるには ParserFactory をなんらかの方法でコンパイラに読み込ませる必要があります。

この辺からは com.sun.tools.* の知識が必要となってきますが、みなさんご存知のことでしょうからあまり深くふれないことにします。

Context

コンパイラ部は他のクラスと情報をやりとりする際に Context クラスを使用しています。

このクラスは HashTable のクラスのような構造でコンパイラ部で使用するものが雑多に詰まっています。

この中には ParserFactory なども含まれています。

Contextコンパイラが実行する前に取得し、なんらかの方法で ContextParserFactory を載せることができれば…。

では早速チャレンジしてみましょう。

実装

当たり前ですが、Parser 部はすべて package-private になっており、外部からは修正できないようになっています。

そのため、プロジェクト内で com.sun.tools.javac.parser を定義し差し替えます。

SemicolonlessParser.java

package com.sun.tools.javac.parser;

import static com.sun.tools.javac.parser.Tokens.TokenKind.SEMI;

public class SemicolonlessParser extends JavacParser {

    protected SemicolonlessParser(final SemicolonlessParserFactory parserFactory,
                                  final Lexer lexer,
                                  final boolean b,
                                  final boolean b1,
                                  final boolean b2) {
        super(parserFactory, lexer, b, b1, b2);
    }

    @Override
    public void accept(final Tokens.TokenKind tk) {
        if (token.kind == tk) {
            nextToken();
        } else if (tk == SEMI) {
        } else {
            super.accept(tk);
        }
    }
}

とても雑な実装ですが、とりあえずセミコロン判定を無視していきます。

そしてこの Parser を適用するための ParserFactory を実装します。

SemicolonlessParserFactory.java

package com.sun.tools.javac.parser;

import com.sun.tools.javac.util.Context;

public class SemicolonlessParserFactory extends ParserFactory {

    protected SemicolonlessParserFactory(final Context context) {
        super(context);
    }

    public static SemicolonlessParserFactory instance(final Context context) {
        SemicolonlessParserFactory instance = (SemicolonlessParserFactory) context.get(parserFactoryKey);
        if (instance == null) {
            instance = new SemicolonlessParserFactory(context);
        }
        return instance;
    }

    @Override
    public JavacParser newParser(final CharSequence input,
                                 final boolean keepDocComments,
                                 final boolean keepEndPos,
                                 final boolean keepLineMap) {
        final Lexer lexer = scannerFactory.newScanner(input, keepDocComments);
        return new SemicolonlessParser(this, lexer, keepDocComments, keepLineMap, keepEndPos);
    }

}

重要な実装はこれぐらいです。

どうでしょうか?とてもコード量が少なくて楽勝な感じがしてきましたね。

ではこれを Context に設定して使用してもらうようにコンパイラ部を書いてみましょう。

BaseSemicolonlessCompiler.java

import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.parser.SemicolonlessParserFactory;
import com.sun.tools.javac.util.Context;

import javax.annotation.processing.Processor;
import javax.lang.model.SourceVersion;
import javax.tools.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Set;

public class BaseSemicolonlessCompiler implements JavaCompiler{
    private final JavaCompiler javaCompiler;

    public BaseSemicolonlessCompiler(){
        this.javaCompiler = ToolProvider.getSystemJavaCompiler();
    }

    @Override
    public CompilationTask getTask(Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits) {
        final JavacTaskImpl task = (JavacTaskImpl) javaCompiler.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits);
        replaceParser(task);
        return new CompilationTask() {
            @Override
            public void setProcessors(Iterable<? extends Processor> processors) {
                task.setProcessors(processors);
            }

            @Override
            public void setLocale(Locale locale) {
                task.setLocale(locale);
            }

            @Override
            public Boolean call() {
                return task.call();
            }
        };
    }

    @Override
    public StandardJavaFileManager getStandardFileManager(DiagnosticListener<? super JavaFileObject> diagnosticListener, Locale locale, Charset charset) {
        return this.javaCompiler.getStandardFileManager(diagnosticListener, locale, charset);
    }

    @Override
    public int isSupportedOption(String option) {
        return 0;
    }

    @Override
    public int run(InputStream in, OutputStream out, OutputStream err, String... arguments) {
        return this.javaCompiler.run(in, out, err, arguments);
    }

    @Override
    public Set<SourceVersion> getSourceVersions() {
        return this.javaCompiler.getSourceVersions();
    }

    private void replaceParser(final JavacTaskImpl compilerTask) {
        final Context context = compilerTask.getContext();
        SemicolonlessParserFactory.instance(context);
    }

}

重要なのは replaceParser メソッドのみです。

JavacTaskImplcast すると getContext メソッドが見えるようになり、Context を操作することができます。

ParserFactory.instance にセットしてしまえば準備完了です。

では簡単に呼び出せるようにさらに実装をしていきましょう。

SemicolonlessCompiler.java

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class SemicolonlessCompiler {

    public CompileResult compile(final List<File> sourceFileList, final File output) throws IOException {
        final JavaCompiler compiler = new BaseSemicolonlessCompiler();
        try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)) {
            final Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFileList);
            final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
            final List<String> compileOptions = Arrays.asList(
                    "-g", "-deprecation",
                    "-d", output.getCanonicalPath(),
                    "-source", "1.8",
                    "-target", "1.8",
                    "-encoding", "UTF-8"
            );

            final JavaCompiler.CompilationTask task = compiler.getTask(null,
                    fileManager,
                    diagnosticCollector,
                    compileOptions,
                    null,
                    compilationUnits);
            final boolean result = task.call();
            return new CompileResult(result, diagnosticCollector.getDiagnostics());
        }
    }

    public class CompileResult {

        private final boolean success;
        private List<Diagnostic<? extends JavaFileObject>> diagnostics = new ArrayList<>();

        CompileResult(final boolean success,
                      final List<Diagnostic<? extends JavaFileObject>> diagnostics) {
            this.success = success;
            this.diagnostics = new ArrayList<>(diagnostics);
        }

        public boolean isSuccess() {
            return success;
        }

        public List<Diagnostic<? extends JavaFileObject>> getDiagnostics() {
            return diagnostics;
        }
    }
}

動作確認にための簡単なテストを書いて実行してみましょう。

SemicolonlessCompilerTest.java

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

public class SemicolonlessCompilerTest {


    @Test
    public void testCompile() throws IOException {

        final File out = new File(System.getProperty("java.io.tmpdir"), "out");
        out.mkdirs();
        final SemicolonlessCompiler compiler = new SemicolonlessCompiler();
        final List<File> targets = new ArrayList<>();
        final File file = new File("./src/test/resources/Hello.java").getCanonicalFile();
        assertTrue(file.exists());
        targets.add(file);
        final SemicolonlessCompiler.CompileResult compileResult = compiler.compile(targets, out);
        assertEquals(true, compileResult.isSuccess());
    }
}

コンパイル対象は以下です。もちろんセミコロンレスです。

Hello.java

public class Hello {

    public void greeting() {
        System.out.println("Hello world")
    }

    public static void main(String[] args) throws Exception {
        new Hello().greeting()
    }

}

テストを実行すると tmpdiroutHello.class ができているはずです。

そのディレクトリへ移動し、実行してみると…。

$ java Hello
Hello world

成功しました!

これでいくらでもセミコロンレスにできそうです!!!

とざっくり Java コンパイラーの挙動を変える方法を紹介してみました。

今回、作成したものは以下から参照できます。興味のある方は見て動かしてみて下さい。

github.com

Abby 社員募集について

最後に現在 株式会社Abby では社員を募集しています。

現在進行系のプロジェクトでは以下のような技術を使用しています。(主なもの)

  • Java + libGDX でのクライアント開発
  • Golang でのサーバーサイド開発
  • Vue.js を使った SPA 開発
  • React Native を使ったユーティリティツールの開発(予定)

興味がある方は info@abby.co.jp までメールを下さい。

もしくはTwitterFacebookで@mopemope宛まで連絡してください。

そして一度弊社まで遊びにきてください。皆様からの応募お待ちしております。

(あのeseharaでさえ一度遊びに来てるので多くの場合問題ないと思います)

あと EmacsJava 書きたい狂人のコントリビューターも募集中です!!

なお、東方神起のメンバーは募集していませんのでご了承ください。

2015 振り返り

2015 振り返り

2015 年はアウトプットはあまりありませんでした。ただ今後への投資という意味でなかなか成果があった年ではないかと思います。

後半はほぼ Go を書いていた気がしますが、やはり、軽量プロセス、速度、配布の容易さという点を重視したものに投資してきたかなあという印象です。

後半、開発環境系を見直しました。

Clojure

Clojure は以前より使っていたのですが、やっと ClojureScript の実戦投入をしました。

UI は Reagent (React) を使用した SPA です。

やはり少数精鋭で Clojure を使うと生産性の桁が違うというところを実感しました。

Java 案件は全て Clojure でも構わないくらいでしょう。

Javascript

やっと少しずつですが最近の流行りっぽいものに追随しはじめました。

ES6 も導入し、よりモダンな環境を使うように移行してきています。

最近は vuejs + webpack でフロントエンドを構築しています。

React については素で使う気がしないので Reagent などを使わない場合には無理に使用しないという方針です。

Go

某ゲームサーバーなどで導入開始しました。Go 1.4 → Go 1.5 へのアップグレードも行いました。

小さな問題はありましたがほぼ問題ないといっていいぐらいの安定っぷりです。

Erlang(LFE)

重い腰をあげてやっと Erlang を少し勉強しはじめました。

普通に Erlang を書いても仕方がないので LFE で書いています。

結論としてはよほどのことがない限りうちでは 使うことはない です。

Haskell

寝かせていた Haskell をまた勉強しはじめました。Lens など放置してた部分を少しずつ勉強し始めています。

今年は stackage, stack などの登場により更に開発のしきいが下がったと思います。

勉強がてら過去に書いていたツールなどもライブラリ更新、 stack 対応などもしました。

開発環境

Arch Linux を相変わらずメインに使っています。

ibus-mozc の点が気になったので fcitx へ乗り換え、ついでに ibus を使わないので Gnome3 をやめ xmonad を再度導入しました。

xmonad も真面目に設定を書いたので快適度があがりました。

zsh → fish への乗り換えもしました。人のコピペで作られた zsh を捨てることにより本当に必要な環境を構築できたと思います。

emacs 環境も見直し、ac -> company に乗り換えたりなどもしました。

rkt → docker に出戻りしました。rkt は新しいカーネルでハングするなどの問題がありつらみしかありません。

CI には drone を導入しています。

まとめ

バンドやろうぜ!

TGludXgg44Gu44Ot44Kw44Kk44Oz44KS44GL44Gj44GT6Imv44GP44GX44KI44GG44Gu5be7Cg==

この記事はPyspa Advent Calendar 2015のエントリとして書かれました。

IyBMaW51eCDjga7jg63jgrDjgqTjg7PjgpLjgYvjgaPjgZPoia/jgY/jgZfjgojjgYbjga7lt7sK
CuOBk+OCk+OBq+OBoeOBr++8geWkp+ixqumZoumCquawl+OBp+OBme+8gQoK5LuK5Zue44Gv44Ov
44Kk44Or44OJ44KI44Gt44Gh44KD44KT44Gu6Kmx44KS44GX44KI44GG44Go5oCd44Gj44Gf44Gu
44Gn44GZ44GM44CB5b2T5bGA44GL44KJ5Zyn5Yqb44GM44GL44GL44Gj44Gm44GX44G+44GECuOB
iuiUteWFpeOCiuOBq+OBquOBo+OBpuOBl+OBvuOBhOOBvuOBl+OBn+OAggoK6Z2e5bi444Gr5q6L
5b+144Gn44GZ44CCCgrjgZXjgabjgIHjgb/jgarjgZXjgpPjgZTlrZjnn6Xjga7jgojjgYbjgavj
g5fjg63jgrDjg6njg57jga/ml6XjgIXjgrXjgqTjgrHjg4fjg6rjg4Pjgq/jgpLpo5/jgaPjgabj
govjga7jgafjgZnjgYzjgIHjgZ3jga4K5LqL44Gr44Gk44GE44Gm44Gv6Kqw44KC6Kem44KM44G+
44Gb44KT44CCCgrjgarjgZzjgafjgZfjgofjgYbjgYvvvJ8KCuKApuOAggoK4oCm44CCCgrlg5Xj
goLop6bjgozjgarjgYTjgZPjgajjgavjgZfjgb7jgZnjgILjgILjgIIKClB5U3BhIOmWouOBl+OB
puipseOBmeOBqOOBhuOBo+OBi+OCiuOBl+OBn+OBk+OBqOOCkuOBl+OCg+OBueOCiuOBi+OBreOB
quOBhOOBl+OAgeS7luOBruOCiOOBreOCgOOCieOBruipseOCkuOBmeOCi+OBqArljbPjgIHntKDo
oYzoqr/mn7vljqgg4oaSIOeCjuS4iuOBmeOCi+OBruOBp+OChOOCgeOBpuOBiuOBjeOBvuOBmeOA
ggoK44G+44Gf56eB44Gu6Kmx44GX44Gm44GE44Gq44GE6YGO5Y6744Gu6Kmx44Gv44OX44Oz44Kz
44Os5paw5bm05Lya44Gn5qmf5Lya44GM44Ki44Os44Gw6Kmx44GX44G+44GZ44CCCgrjgafjgIHj
g43jgr/jgYzjgarjgYTjga7jgafjganjgYbjgZfjgojjgYbjgYvjgajmgJ3jgaPjgabjgYTjgZ/j
gokgYGxpZ2h0ZG0td2Via2l0LWdyZWV0ZXJgIOOCkuS9v+OBo+OBpuOBnwrjgZPjgajjgpLmgJ3j
gYTlh7rjgZfjgZ/jga7jgafjgZ3jgozjgavjgaTjgYTjgaYgMiDjgJwgMyDmlofmm7jjgY3jgZ/j
gYTjgajmgJ3jgYTjgb7jgZnjgIIKCiMjIExpZ2h0RE0KCkxpZ2h0RE0g44Gv44Kv44Ot44K544OH
44K544Kv44OI44OD44OX44Gu44OH44Kj44K544OX44Os44Kk44Oe44ON44O844K444Oj44Gn44GZ
44CCCgrlibLjgajlpJrjgY/jga7jg4fjgqPjgrnjg4jjg6rjg5Pjg6Xjg7zjgrfjg6fjg7Pjgafk
vb/jgYjjgb7jgZnjgIIKCui7vemHj+OBp+WfuuacrOeahOOBquapn+iDveOBq+a6luaLoOOBl+OB
puOBiuOCiuOAgVVJ6YOo44Go44Ot44K444OD44Kv6YOo44GM5oq96LGh5YyW44GV44KM44Gm44GK
44KK44Kr44K544K/44Oe44Kk44K644GMCuWPr+iDveOBq+OBquOBo+OBpuOBhOOBvuOBmeOAggoK
44Gd44Gu44Gf44KB44CBTGlnaHRETSDjga/jg63jgrDjgqTjg7MoVUkp6YOo44KS44Kr44K544K/
44Oe44Kk44K644GV44KM44KL44GT44Go44GM5aSa44GP44Gd44Gu6YOo5YiG44GvIGBncmVldGVy
YArjgajjgZfjgabjg5Hjg4PjgrHjg7zjgrjjg7PjgrDjgZXjgozjgabjgYTjgb7jgZnjgIIKCu+8
iFVidW50dSDjgafjga8gdW5pdHktZ3JlZXRlciDjgYzkvb/nlKjjgZXjgozjgabjgYTjgb7jgZnv
vIkKCiMjIEdyZWV0ZXIKCkdyZWV0ZXIg44GvIGBsaWdodGRtLWd0ay1ncmVldGVyYCwgYGxpZ2h0
ZG0ta2RlLWdyZWV0ZXJgIOOBquOBqeOBhOOCjeOBhOOCjeOBguOCi+OBruOBp+OBmeOBjOOAgQrj
gqvjgrnjgr/jg57jgqTjgrrlj6/og73jgajjgYTjgaPjgabjgoLmlbflsYXjgYzpq5jjgZ3jgYbj
gavopovjgYjjgb7jgZnjgIIKCuOCguOBo+OBqOewoeWNmOOBq+OCq+OCueOCv+ODnuOCpOOCuuOB
p+OBjeOBquOBhOOBp+OBl+OCh+OBhuOBi++8nwoK44CM44Gv44GE77yBU1RBUOe0sOiDnuOBr+OB
guOCiuOBvuOBme+8geOAjQoK44Go6KiA44KP44KT44Gw44GL44KK57Ch5Y2Y44Gr44Kr44K544K/
44Oe44Kk44K644GZ44KL5pa55rOV44GM44GC44KK44G+44GZ44CC44Gd44KM44GvIGBsaWdodGRt
LXdlYmtpdC1ncmVldGVyYArjgpLkvb/jgYbmlrnms5XjgafjgZnjgIIKCu+8iOOBiuOBvOOAnO+8
iQoKIyMgV2Via2l0LUdyZWV0ZXIKCmBsaWdodGRtLXdlYmtpdC1ncmVldGVyYCDjga/jgZ3jga7l
kI3jga7pgJrjgorjg4bjg7zjg57jgasgYHdlYmtpdGAg44GM5L2/55So44Gn44GN44G+44GZ44CC
CgpIVE1MLCBDU1Mg44OG44O844Oe44KS5L2c44KK44CB44Ot44Kw44Kk44Oz44Ot44K444OD44Kv
6YOo44KSIEphdmFzY3JpcHQg44Gn44Gh44KH44Gh44KH44Gj44Go5pu444GR44Gw5a6M5oiQ44Gn
44GZ44CCCgrjgarjgpPjgabjgYrmiYvou73jgarjgpPjgaDvvIHvvIEKCuOBvuOBmuOBryBgbGln
aHRkbS13ZWJraXQtZ3JlZXRlcmAg44KS44Kk44Oz44K544OI44O844Or44GX44CB5pyJ5Yq544Gr
44GX44G+44GZ44CCCgpgL2V0Yy9saWdodGRtL2xpZ2h0ZG0uY29uZmAg44Gn6Kit5a6a44Gn44GN
44G+44GZ44CCCgpgYGAKW1NlYXQ6Kl0KLi4uCmdyZWV0ZXItc2Vzc2lvbj1saWdodGRtLXdlYmtp
dC1ncmVldGVyCi4uLgpgYGAKCuasoeOBqyBgbGlnaHRkbS13ZWJraXQtZ3JlZXRlcmAg44Gu6Kit
5a6a44KSIGAvZXRjL2xpZ2h0ZG0vbGlnaHRkbS13ZWJraXQtZ3JlZXRlci5jb25mYArjgavmm7jj
gY3jgb7jgZnjgIIKCmBgYApbZ3JlZXRlcl0KYmFja2dyb3VuZD0KdGhlbWUtbmFtZT1DbGVhcmxv
b2tzCndlYmtpdC10aGVtZT1tYWMKZm9udC1uYW1lPVJpY3R5IDE0CnhmdC1hbnRpYWxpYXM9dHJ1
ZQp4ZnQtZHBpPTk2CnhmdC1oaW50c3R5bGU9c2xpZ2h0CnhmdC1yZ2JhPXJnYgpgYGAKCumHjeim
geOBquOBruOBryBgd2Via2l0LXRoZW1lYCDjgafjgZnjgILjgZPjga7jg4bjg7zjg57lkI3jgafl
j4LnhafjgZfjgavooYzjgY/jg4fjgqPjg6zjgq/jg4jjg6rjgYzmsbrjgb7jgorjgb7jgZnjgIIK
CuWPgueFp+OBmeOCiyBgL3Vzci9zaGFyZS9saWdodGRtLXdlYmtpdC90aGVtZXMvPHdlYmtpdC10
aGVtZT4vYCDjgafjgZnjgIIKCuS4iuiomOioreWumuOBquOCiSBgbWFjYCDjgajjgarjgaPjgabj
gYTjgovjga7jgacgYC91c3Ivc2hhcmUvbGlnaHRkbS13ZWJraXQvdGhlbWVzL21hYy9gIOOCkuWP
gueFpwrjgZfjgb7jgZnjgIIg44OG44O844Oe44Gv44OH44Kj44Os44Kv44OI44Oq44GU44Go44Gr
5YiG44GR44KJ44KM44KL44Gu44Gn6KSH5pWw44OG44O844Oe44KS44Kk44Oz44K544OI44O844Or
44GX44CB5YiH44KKCuabv+OBiOOCi+OBk+OBqOOBjOOBp+OBjeOBvuOBmeOAggoK5a6f6Zqb44Gr
44Ot44Kw44Kk44Oz5pmC44Gr6Kqt44G/6L6844G+44KM44KL44Gu44Gv44OG44O844Oe5YaFIGBp
bmRleC50aGVtZWAg44Gn44GZ44CCCgrku6XkuIvjgavkvovjgpLmm7jjgY3jgb7jgZnjgIIKCmBg
YApbdGhlbWVdCm5hbWU9VGVzdCBUaGVtZQpkZXNjcmlwdGlvbj1UZXN0IFdlYmtpdCBUaGVtZQpl
bmdpbmU9bGlnaHRkbS13ZWJraXQtZ3JlZXRlcgp1cmw9aW5kZXguaHRtbApzZXNzaW9uPXhtb25h
ZApgYGAKCmB1cmxgIOOBjOWun+mam+OBq+iqreOBv+i+vOOBvuOCjOOCi+ODleOCoeOCpOODq+OB
q+OBquOCiuOBvuOBmeOAggrkuIroqJjjgafjga9VSeOAgeODreOCuOODg+OCr+OBquOBqeOBryBg
aW5kZXguaHRtbGAg44Gr6KiY6L+w44GX44Gm44GE44GN44G+44GZ44CCCgojIyDlrp/ot7UKCuOB
p+OBr+Wun+ijheS+i+OCkuimi+OBpuOBv+OBvuOBl+OCh+OBhuOAggoK6Kmz44GX44GP44Gv6Kqs
5piO44GX44G+44Gb44KT44GM44GK44GK44KI44Gd44Gu6Zuw5Zuy5rCX44Gv6Kej44KL44Go5oCd
44GE44G+44GZ44CCCgpgaW5kZXguaHRtbGAKCmBgYGh0bWwKPGh0bWw+CjxoZWFkPgo8bGluayBy
ZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiBocmVmPSJzdHlsZS5jc3MiIC8+CjxzY3Jp
cHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBzcmM9InNjcmlwdC5qcyI+PC9zY3JpcHQ+CjwvaGVh
ZD4KCjxib2R5IG9ubG9hZD0iaW5pdGlhbGl6ZSgpIiBvbmNvbnRleHRtZW51PSJyZXR1cm4gZmFs
c2U7Ij4KICA8ZGl2IGNsYXNzPSJoZWFkZXIiPjxzcGFuIGlkPSJjdXJyZW50X3RpbWUiIGNsYXNz
PSJ0aW1lIj48L3NwYW4+PC9kaXY+CiAgPGRpdiBjbGFzcz0ibG9naW5fY29udGVudCI+CiAgICA8
ZGl2IGNsYXNzPSJsb2dpbl9jb250YWluZXIiPgogICAgICA8ZGl2IGNsYXNzPSJjZW50ZXIiPgog
ICAgICAgIDxkaXYgaWQ9InVzZXJfdGVtcGxhdGUiIGNsYXNzPSJ1c2VyIGhpZGRlbiBzbW9vdGgg
YnV0dG9uIj4KICAgICAgICAgIDxkaXYgY2xhc3M9InVzZXJfaW1hZ2Vfd3JhcHBlciI+IDxpbWcg
Y2xhc3M9InVzZXJfaW1hZ2UiIHNyYz0iIi8+IDwvZGl2PgogICAgICAgICAgPHNwYW4gY2xhc3M9
InVzZXJfbmFtZSI+PC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgICA8L2Rpdj4KICAgICAgPGRp
diBpZD0icGFzc3dvcmRfY29udGFpbmVyIiBjbGFzcz0iY2VudGVyIGhpZGRlbiBzbW9vdGgiPgog
ICAgICAgIDxmb3JtIGFjdGlvbj0iamF2YXNjcmlwdDogcHJvdmlkZV9zZWNyZXQoKSIgY2xhc3M9
InBhc3N3b3JkX3Byb21wdCI+CiAgICAgICAgICA8aW5wdXQgaWQ9InBhc3N3b3JkX2VudHJ5IiB0
eXBlPSJwYXNzd29yZCIgcGxhY2Vob2xkZXI9IlBhc3N3b3JkIiAvPgogICAgICAgIDwvZm9ybT4K
ICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KICA8L2Rpdj4KICA8ZGl2IGlkPSJtZXNzYWdlIiBjbGFz
cz0ic21vb3RoIj4KICAgIDxkaXYgaWQ9Im1lc3NhZ2VfY29udGVudCI+PC9kaXY+CiAgPC9kaXY+
CiAgPGRpdiBjbGFzcz0iZm9vdGVyIj4KICAgICAgPGRpdiBpZD0iYWN0aW9uX3RlbXBsYXRlIiBj
bGFzcz0iYWN0aW9uIGJ1dHRvbiI+CiAgICAgICAgPGRpdiBjbGFzcz0iYWN0aW9uX2ltYWdlX3dy
YXBwZXIiPjxpbWcgY2xhc3M9ImFjdGlvbl9pbWFnZSIgc3JjPSIiLz48L2Rpdj4KICAgICAgICA8
c3BhbiBjbGFzcz0iYWN0aW9uX2xhYmVsIj48L3NwYW4+CiAgICAgIDwvZGl2PgogIDwvZGl2Pgo8
L2JvZHk+CjwvaHRtbD4KCmBgYAoKYHNjcmlwdC5qc2AKCmBgYGphdmFzY3JpcHQKdmFyIHRpbWVf
cmVtYWluaW5nID0gMDsKdmFyIHNlbGVjdGVkX3VzZXIgPSBudWxsOwp2YXIgdmFsaWRfaW1hZ2Ug
PSAvLipcLihwbmd8c3ZnfGpwZ3xqcGVnfGJtcCkkL2k7CgovLy8vLy8vLy8vLy8vLy8vLy8vLy8v
Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLwovLyBDQUxMQkFDSyBBUEkuIENhbGxlZCBieSB0aGUg
d2Via2l0IGdyZWVldGVyCi8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v
Ly8vLy8vCgovLyBjYWxsZWQgd2hlbiB0aGUgZ3JlZXRlciBhc2tzIHRvIHNob3cgYSBsb2dpbiBw
cm9tcHQgZm9yIGEgdXNlcgpmdW5jdGlvbiBzaG93X3Byb21wdCh0ZXh0KSB7CiAgdmFyIHBhc3N3
b3JkX2NvbnRhaW5lcj0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInBhc3N3b3JkX2NvbnRhaW5l
ciIpOwogIHZhciBwYXNzd29yZF9lbnRyeT0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInBhc3N3
b3JkX2VudHJ5Iik7CgogIGlmICghaXNWaXNpYmxlKHBhc3N3b3JkX2NvbnRhaW5lcikpIHsKICAg
IHZhciB1c2Vycz0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLnVzZXIiKTsKICAgIHZhciB1
c2VyX25vZGU9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoIiMiICsgc2VsZWN0ZWRfdXNlcik7CiAg
ICB2YXIgcmVjdD0gdXNlcl9ub2RlLmdldENsaWVudFJlY3RzKClbMF07CiAgICB2YXIgcGFyZW50
UmVjdD0gdXNlcl9ub2RlLnBhcmVudEVsZW1lbnQuZ2V0Q2xpZW50UmVjdHMoKVswXTsKICAgIHZh
ciBjZW50ZXI9IHBhcmVudFJlY3Qud2lkdGgvMjsKICAgIHZhciBsZWZ0PSBjZW50ZXIgLSByZWN0
LndpZHRoLzIgLSByZWN0LmxlZnQ7CiAgICB2YXIgaSA9IDA7CiAgICBpZiAobGVmdCA8IDUgJiYg
bGVmdCA+IC01KSB7CiAgICAgIGxlZnQ9IDA7CiAgICB9CiAgICBmb3IgKGk9IDA7IGkgPCB1c2Vy
cy5sZW5ndGg7IGkrKykgewogICAgICB2YXIgbm9kZT0gdXNlcnNbaV07CiAgICAgIHNldFZpc2li
bGUobm9kZSwgbm9kZS5pZCA9PSBzZWxlY3RlZF91c2VyKTsKICAgICAgbm9kZS5zdHlsZS5sZWZ0
PSBsZWZ0OwogICAgfQoKICAgIHNldFZpc2libGUocGFzc3dvcmRfY29udGFpbmVyLCB0cnVlKTsK
ICAgIHBhc3N3b3JkX2VudHJ5LnBsYWNlaG9sZGVyPSB0ZXh0LnJlcGxhY2UoIjoiLCAiIik7CiAg
fQogIHBhc3N3b3JkX2VudHJ5LnZhbHVlPSAiIjsKICBwYXNzd29yZF9lbnRyeS5mb2N1cygpOwp9
CgovLyBjYWxsZWQgd2hlbiB0aGUgZ3JlZXRlciBhc2tzIHRvIHNob3cgYSBtZXNzYWdlCmZ1bmN0
aW9uIHNob3dfbWVzc2FnZSh0ZXh0KSB7CiAgdmFyIG1lc3NhZ2U9IGRvY3VtZW50LnF1ZXJ5U2Vs
ZWN0b3IoIiNtZXNzYWdlX2NvbnRlbnQiKTsKICBtZXNzYWdlLmlubmVySFRNTD0gdGV4dDsKICBp
ZiAodGV4dCkgewogICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvcigiI21lc3NhZ2UiKS5jbGFzc0xp
c3QucmVtb3ZlKCJoaWRkZW4iKTsKICB9IGVsc2UgewogICAgZG9jdW1lbnQucXVlcnlTZWxlY3Rv
cigiI21lc3NhZ2UiKS5jbGFzc0xpc3QuYWRkKCJoaWRkZW4iKTsKICB9CiAgbWVzc2FnZS5jbGFz
c0xpc3QucmVtb3ZlKCJlcnJvciIpOwp9CgovLyBjYWxsZWQgd2hlbiB0aGUgZ3JlZXRlciBhc2tz
IHRvIHNob3cgYW4gZXJyb3IKZnVuY3Rpb24gc2hvd19lcnJvcih0ZXh0KSB7CiAgIHNob3dfbWVz
c2FnZSh0ZXh0KTsKICAgdmFyIG1lc3NhZ2U9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJtZXNz
YWdlX2NvbnRlbnQiKTsKICAgbWVzc2FnZS5jbGFzc0xpc3QuYWRkKCJlcnJvciIpOwp9CgovLyBj
YWxsZWQgd2hlbiB0aGUgZ3JlZXRlciBpcyBmaW5pc2hlZCB0aGUgYXV0aGVudGljYXRpb24gcmVx
dWVzdApmdW5jdGlvbiBhdXRoZW50aWNhdGlvbl9jb21wbGV0ZSgpIHsKICBpZiAobGlnaHRkbS5p
c19hdXRoZW50aWNhdGVkKSB7CiAgICBsaWdodGRtLmxvZ2luKGxpZ2h0ZG0uYXV0aGVudGljYXRp
b25fdXNlciwgbGlnaHRkbS5kZWZhdWx0X3Nlc3Npb24pOwogIH0gZWxzZSB7CiAgICBzaG93X2Vy
cm9yKCJBdXRoZW50aWNhdGlvbiBGYWlsZWQiKTsKICAgIHN0YXJ0X2F1dGhlbnRpY2F0aW9uKHNl
bGVjdGVkX3VzZXIpOwogIH0KfQoKLy8gY2FsbGVkIHdoZW4gdGhlIGdyZWV0ZXIgd2FudHMgdXMg
dG8gcGVyZm9ybSBhIHRpbWVkIGxvZ2luCmZ1bmN0aW9uIHRpbWVkX2xvZ2luKHVzZXIpIHsKICAg
bGlnaHRkbS5sb2dpbiAobGlnaHRkbS50aW1lZF9sb2dpbl91c2VyKTsKICAgLy8gc2V0VGltZW91
dCgndGhyb2JiZXIoKScsIDEwMDApOwp9CgovLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8K
Ly8gSW1wbGVtZW50YXRpb24KLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vCmZ1bmN0aW9u
IHN0YXJ0X2F1dGhlbnRpY2F0aW9uKHVzZXJuYW1lKSB7CiAgIGxpZ2h0ZG0uY2FuY2VsX3RpbWVk
X2xvZ2luKCk7CiAgIHNlbGVjdGVkX3VzZXI9IHVzZXJuYW1lOwogICBsaWdodGRtLnN0YXJ0X2F1
dGhlbnRpY2F0aW9uKHVzZXJuYW1lKTsKfQoKZnVuY3Rpb24gcHJvdmlkZV9zZWNyZXQoKSB7CiAg
IHNob3dfbWVzc2FnZSgiTG9nZ2luZyBpbi4uLiIpOwogICB2YXIgZW50cnkgPSBkb2N1bWVudC5x
dWVyeVNlbGVjdG9yKCcjcGFzc3dvcmRfZW50cnknKTsKICAgbGlnaHRkbS5wcm92aWRlX3NlY3Jl
dChlbnRyeS52YWx1ZSk7Cn0KCmZ1bmN0aW9uIHNob3dfdXNlcnMoKSB7CiAgdmFyIHVzZXJzID0g
ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLnVzZXIiKTsKICBmb3IgKHZhciBpPSAwOyBpIDwg
dXNlcnMubGVuZ3RoOyBpKyspIHsKICAgIHNldFZpc2libGUodXNlcnNbaV0sIHRydWUpOwogICAg
dXNlcnNbaV0uc3R5bGUubGVmdD0gMDsKICB9CiAgc2V0VmlzaWJsZShkb2N1bWVudC5xdWVyeVNl
bGVjdG9yKCIjcGFzc3dvcmRfY29udGFpbmVyIiksIGZhbHNlKTsKICBzZWxlY3RlZF91c2VyPSBu
dWxsOwp9CgpmdW5jdGlvbiB1c2VyX2NsaWNrZWQoZXZlbnQpIHsKICBpZiAoc2VsZWN0ZWRfdXNl
ciAhPSBudWxsKSB7CiAgICBzZWxlY3RlZF91c2VyPSBudWxsOwogICAgbGlnaHRkbS5jYW5jZWxf
YXV0aGVudGljYXRpb24oKTsKICAgIHNob3dfdXNlcnMoKTsKICB9IGVsc2UgewogICAgc2VsZWN0
ZWRfdXNlcj0gZXZlbnQuY3VycmVudFRhcmdldC5pZDsKICAgIHN0YXJ0X2F1dGhlbnRpY2F0aW9u
KGV2ZW50LmN1cnJlbnRUYXJnZXQuaWQpOwogIH0KICBzaG93X21lc3NhZ2UoIiIpOwogIGV2ZW50
LnN0b3BQcm9wYWdhdGlvbigpOwogIHJldHVybiBmYWxzZTsKfQoKZnVuY3Rpb24gc2V0VmlzaWJs
ZShlbGVtZW50LCB2aXNpYmxlKSB7CiAgaWYgKHZpc2libGUpIHsKICAgIGVsZW1lbnQuY2xhc3NM
aXN0LnJlbW92ZSgiaGlkZGVuIik7CiAgfSBlbHNlIHsKICAgIGVsZW1lbnQuY2xhc3NMaXN0LmFk
ZCgiaGlkZGVuIik7CiAgfQp9CgpmdW5jdGlvbiBpc1Zpc2libGUoZWxlbWVudCkgewogIHJldHVy
biAhZWxlbWVudC5jbGFzc0xpc3QuY29udGFpbnMoImhpZGRlbiIpOwp9CgpmdW5jdGlvbiB1cGRh
dGVfdGltZSgpIHsKICB2YXIgdGltZT0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImN1cnJlbnRf
dGltZSIpOwogIHZhciBkYXRlPSBuZXcgRGF0ZSgpOwoKICB2YXIgaGggPSBkYXRlLmdldEhvdXJz
KCk7CiAgdmFyIG1tID0gZGF0ZS5nZXRNaW51dGVzKCk7CiAgdmFyIHNzID0gZGF0ZS5nZXRTZWNv
bmRzKCk7CiAgdmFyIHN1ZmZpeD0gIkFNIjsKICBpZiAoaGggPiAxMikgewogICAgaGg9IGhoIC0g
MTI7CiAgICBzdWZmaXg9ICJQTSI7CiAgfQogIGlmIChoaCA8IDEwKSB7aGggPSAiMCIraGg7fQog
IGlmIChtbSA8IDEwKSB7bW0gPSAiMCIrbW07fQogIGlmIChzcyA8IDEwKSB7c3MgPSAiMCIrc3M7
fQogIHRpbWUuaW5uZXJIVE1MPSBoaCsiOiIrbW0gKyAiICIgKyBzdWZmaXg7Cn0KCi8vLy8vLy8v
Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8KLy8gSW5pdGlhbGl6YXRpb24KLy8vLy8vLy8vLy8v
Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLwoKZnVuY3Rpb24gaW5pdGlhbGl6ZSgpIHsKICBzaG93X21l
c3NhZ2UoIiIpOwogIGluaXRpYWxpemVfdXNlcnMoKTsKICBpbml0aWFsaXplX2FjdGlvbnMoKTsK
ICBpbml0aWFsaXplX3RpbWVyKCk7Cn0KCmZ1bmN0aW9uIGluaXRpYWxpemVfdXNlcnMoKSB7CiAg
dmFyIHRlbXBsYXRlID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcigiI3VzZXJfdGVtcGxhdGUiKTsK
ICB2YXIgcGFyZW50ID0gdGVtcGxhdGUucGFyZW50RWxlbWVudDsKICBwYXJlbnQucmVtb3ZlQ2hp
bGQodGVtcGxhdGUpOwoKICBmb3IgKHZhciBpID0gMDsgaSA8IGxpZ2h0ZG0udXNlcnMubGVuZ3Ro
OyBpKyspIHsKICAgIHZhciB1c2VyPSBsaWdodGRtLnVzZXJzW2ldOwogICAgdmFyIHVzZXJOb2Rl
PSB0ZW1wbGF0ZS5jbG9uZU5vZGUodHJ1ZSk7CgogICAgdmFyIGltYWdlID0gdXNlck5vZGUuZ2V0
RWxlbWVudHNCeUNsYXNzTmFtZSgidXNlcl9pbWFnZSIpWzBdOwogICAgdmFyIG5hbWUgPSB1c2Vy
Tm9kZS5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCJ1c2VyX25hbWUiKVswXTsKICAgIG5hbWUuaW5u
ZXJIVE1MPSB1c2VyLmRpc3BsYXlfbmFtZTsKCiAgICBpZiAodXNlci5pbWFnZSkgewogICAgICBp
bWFnZS5zcmMgPSB1c2VyLmltYWdlOwogICAgICBpbWFnZS5vbmVycm9yPSBmdW5jdGlvbihlKSB7
CiAgICAgICAgZS5jdXJyZW50VGFyZ2V0LnNyYz0gImltZy9hdmF0YXIuc3ZnIjsKICAgICAgfTsK
ICAgIH0gZWxzZSB7CiAgICAgIGltYWdlLnNyYyA9ICJpbWcvYXZhdGFyLnN2ZyI7CiAgICB9Cgog
ICAgdXNlck5vZGUuaWQ9IHVzZXIubmFtZTsKICAgIHVzZXJOb2RlLm9uY2xpY2s9IHVzZXJfY2xp
Y2tlZDsKICAgIHBhcmVudC5hcHBlbmRDaGlsZCh1c2VyTm9kZSk7CiAgfQogIHNldFRpbWVvdXQo
c2hvd191c2VycywgNDAwKTsKfQoKZnVuY3Rpb24gaW5pdGlhbGl6ZV9hY3Rpb25zKCkgewogIHZh
ciB0ZW1wbGF0ZSA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoIiNhY3Rpb25fdGVtcGxhdGUiKTsK
ICB2YXIgcGFyZW50ID0gdGVtcGxhdGUucGFyZW50RWxlbWVudDsKICBwYXJlbnQucmVtb3ZlQ2hp
bGQodGVtcGxhdGUpOwoKICBpZiAobGlnaHRkbS5jYW5fc3VzcGVuZCkgewogICAgYWRkX2FjdGlv
bigic2xlZXAiLCJTbGVlcCIsICJpbWcvc2xlZXAuc3ZnIiwgZnVuY3Rpb24oZSkge2xpZ2h0ZG0u
c3VzcGVuZCgpOyBlLnN0b3BQcm9wYWdhdGlvbigpO30sIHRlbXBsYXRlLCBwYXJlbnQpOwogIH0K
ICBpZiAobGlnaHRkbS5jYW5fcmVzdGFydCkgewogICAgYWRkX2FjdGlvbigicmVzdGFydCIsICJS
ZXN0YXJ0IiwgImltZy9yZXN0YXJ0LnN2ZyIsIGZ1bmN0aW9uKGUpIHtsaWdodGRtLnJlc3RhcnQo
KTsgZS5zdG9wUHJvcGFnYXRpb24oKTt9LCB0ZW1wbGF0ZSwgcGFyZW50KTsKICB9CiAgaWYgKGxp
Z2h0ZG0uY2FuX3NodXRkb3duKSB7CiAgICBhZGRfYWN0aW9uKCJzaHV0ZG93biIsICJTaHV0ZG93
biIsICJpbWcvc2h1dGRvd24uc3ZnIiwgZnVuY3Rpb24oZSkge2xpZ2h0ZG0uc2h1dGRvd24oKTsg
ZS5zdG9wUHJvcGFnYXRpb24oKTt9LCB0ZW1wbGF0ZSwgcGFyZW50KTsKICB9Cn0KCmZ1bmN0aW9u
IGluaXRpYWxpemVfdGltZXIoKSB7CiAgdXBkYXRlX3RpbWUoKTsKICBzZXRJbnRlcnZhbCh1cGRh
dGVfdGltZSwgMTAwMCk7Cn0KCmZ1bmN0aW9uIGFkZF9hY3Rpb24oaWQsIG5hbWUsIGltYWdlLCBj
bGlja2hhbmRsZXIsIHRlbXBsYXRlLCBwYXJlbnQpIHsKICB2YXIgYWN0aW9uX25vZGUgPSB0ZW1w
bGF0ZS5jbG9uZU5vZGUodHJ1ZSk7CiAgYWN0aW9uX25vZGUuaWQ9ICJhY3Rpb25fIiArIGlkOwog
IHZhciBpbWdfbm9kZSA9IGFjdGlvbl9ub2RlLnF1ZXJ5U2VsZWN0b3JBbGwoIi5hY3Rpb25faW1h
Z2UiKVswXTsKICB2YXIgbGFiZWxfbm9kZT0gYWN0aW9uX25vZGUucXVlcnlTZWxlY3RvckFsbCgi
LmFjdGlvbl9sYWJlbCIpWzBdOwogIGxhYmVsX25vZGUuaW5uZXJIVE1MPSBuYW1lOwogIGltZ19u
b2RlLnNyYz0gaW1hZ2U7CiAgYWN0aW9uX25vZGUub25jbGljaz0gY2xpY2toYW5kbGVyOwogIHBh
cmVudC5hcHBlbmRDaGlsZChhY3Rpb25fbm9kZSk7Cn0KYGBgCgpgc3R5bGUuY3NzYAoKYGBgY3Nz
CmJvZHkgewogICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKCdiZy5qcGcnKTsKICAgIGRpc3BsYXk6
IHRhYmxlOwogICAgaGVpZ2h0OiAxMDAlOwogICAgd2lkdGg6IDEwMCU7CiAgICBtYXJnaW46IDA7
CgogICAgZm9udC1zaXplOiAxNHB0OwogICAgdGV4dC1zaGFkb3c6IDFweCAxcHggM3B4IGJsYWNr
OwogICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKfQoKaW5wdXQgewogIGJvcmRlcjogMXB4
IHNvbGlkIHdoaXRlOwogIGJvcmRlci1yYWRpdXM6IDRweDsKICBwYWRkaW5nOiA0cHg7CiAgYm94
LXNoYWRvdzogMCAwIDJweCAxcHggd2hpdGU7CgogIC13ZWJraXQtdHJhbnNpdGlvbjogYm94LXNo
YWRvdyAwLjNzIGVhc2UtaW4tb3V0Owp9CgppbnB1dDpmb2N1cyB7CiAgb3V0bGluZTogbm9uZTsK
ICBib3gtc2hhZG93OiAwIDAgNXB4IDJweCAjN0RCRUYxOwp9CgphIHsKICB0ZXh0LWRlY29yYXRp
b246IG5vbmU7Cn0KCi5zbW9vdGggewogIC13ZWJraXQtdHJhbnNpdGlvbjogdmlzaWJpbGl0eSAw
cywgb3BhY2l0eSAwLjNzLCBsZWZ0IDAuNHM7Cn0KCi5oaWRkZW4gewogIG9wYWNpdHk6IDA7CiAg
dmlzaWJpbGl0eTogaGlkZGVuOwogIC13ZWJraXQtdHJhbnNpdGlvbi1kZWxheTogMC4zcywgMHMs
IDBzOwp9CgouY2VudGVyIHsKICB0ZXh0LWFsaWduOiBjZW50ZXI7Cn0KCi5idXR0b24gewogIGN1
cnNvcjogcG9pbnRlcjsKfQoKLmhlYWRlciB7CiAgaGVpZ2h0OiAzMCU7Cn0KCi5mb290ZXIgewog
IHRleHQtYWxpZ246IGNlbnRlcjsKICBoZWlnaHQ6IDEwMHB4Owp9CgouZm9vdGVyLCAuaGVhZGVy
IHsKICBkaXNwbGF5OiB0YWJsZS1yb3c7CiAgd2lkdGg6IDEwMCU7CiAgY29sb3I6ICNCMkIyQjI7
Cn0KCi50aW1lIHsKICBmbG9hdDogcmlnaHQ7CiAgbGluZS1oZWlnaHQ6IDI1cHg7CiAgZm9udC1z
aXplOiAxMXB0OwogIG1hcmdpbi1yaWdodDogMTBweDsKICBtYXJnaW4tdG9wOiAycHg7Cn0KCi5s
b2dpbl9jb250ZW50IHsKICBkaXNwbGF5OiB0YWJsZS1yb3c7Cn0KCi5sb2dpbl9jb250YWluZXIg
ewogIGRpc3BsYXk6IHRhYmxlLWNlbGw7CiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKfQoKI21l
c3NhZ2UgewogIGRpc3BsYXk6IHRhYmxlLXJvdzsKICBoZWlnaHQ6IDEyMHB4OwogIC13ZWJraXQt
dHJhbnNpdGlvbjogdmlzaWJpbGl0eSAwcywgb3BhY2l0eSAwLjNzLCBoZWlnaHQgMC4zczsKfQoK
I21lc3NhZ2UuaGlkZGVuIHsKICBoZWlnaHQ6IDBweDsKfQoKI21lc3NhZ2VfY29udGVudCB7CiAg
ZGlzcGxheTogdGFibGUtY2VsbDsKICB2ZXJ0aWNhbC1hbGlnbjogdG9wOwogIHRleHQtYWxpZ246
IGNlbnRlcjsKICBjb2xvcjogI0VGRUZFRjsKfQoKI21lc3NhZ2VfY29udGVudC5lcnJvciB7CiAg
Y29sb3I6ICNGNTU7Cn0KCi51c2VyIHsKICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgbWFyZ2lu
LWxlZnQ6IDIwcHg7CiAgbWFyZ2luLXJpZ2h0OiAyMHB4OwogIG1hcmdpbi1ib3R0b206IDIwcHg7
CiAgcG9zaXRpb246IHJlbGF0aXZlOwp9CgoudXNlcjphY3RpdmUgewogIG9wYWNpdHk6IDAuNTsK
fQoKLnVzZXJfaW1hZ2Vfd3JhcHBlciB7CiAgd2lkdGg6IDgwcHg7CiAgaGVpZ2h0OiA4MHB4OwoK
ICBib3JkZXItcmFkaXVzOiA2MHB4OwogIGJveC1zaGFkb3c6IDAgMCAxcHggMXB4ICMyMjIsIGlu
c2V0IDAgMCAycHggMXB4ICMyMjI7CiAgYm9yZGVyOiAycHggc29saWQgI0VFRTsKICBiYWNrZ3Jv
dW5kOiAtd2Via2l0LXJhZGlhbC1ncmFkaWVudChjaXJjbGUsICNGRkYsICM5OTkpOwp9CgoudXNl
cl9pbWFnZV93cmFwcGVyOmhvdmVyIHsKICBib3gtc2hhZG93OiAgMCAwIDFweCAxcHggIzIyMiwg
aW5zZXQgMCAwIDJweCAxcHggIzIyMiwgMCAwIDVweCAycHggIzdEQkVGMTsKfQoKLnVzZXJfaW1h
Z2UgewogIHdpZHRoOiA4MHB4OwogIGhlaWdodDogODBweDsKICBib3JkZXItcmFkaXVzOiA2MHB4
Owp9CgoudXNlcl9uYW1lIHsKICBkaXNwbGF5OiBibG9jazsKICBtYXJnaW4tdG9wOiAxNXB4Owog
IGNvbG9yOiAjRUZFRkVGOwp9CgouYWN0aW9uIHsKICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAg
d2lkdGg6IDgwcHg7CiAgbWFyZ2luLWxlZnQ6IDQwcHg7CiAgbWFyZ2luLXJpZ2h0OiA0MHB4Owp9
CgouYWN0aW9uX2ltYWdlIHsKICBoZWlnaHQ6IDUwcHg7CiAgd2lkdGg6IDUwcHg7CgogIG1hcmdp
bi1ib3R0b206IDVweDsKfQoKLmFjdGlvbl9sYWJlbCB7CiAgY29sb3I6ICNCMkIyQjI7Cn0KYGBg
Cg==

株式会社Abby 社員募集のお知らせ

株式会社Abby 社員募集のお知らせ

ジュリアナー!!トーキョー!

こんにちわ、ジョン・ロビンソンこと半ズボンの宇宙人です。

株式会社Abbyでは業務拡大に伴い、共に働いてくれる仲間を募集しています。

以前事務所は恵比寿にあったのですが、クックパッドがくるということで逃げるように中目黒に事務所を移転しました。

事務所などは会社のサイトを参照して下さい。

株式会社 Abby エビイ

今まではシステム開発メインでやっていましたが、現在はアプリの開発にシフトしています。

http://www.abby.co.jp/service.html

社内には優秀なメンバーも揃っています。いろいろな技術的な話もできるので技術者にとっては刺激の多い環境だと思います。

社内は明るく@ が最新ギャグを教えてくれたりします。お笑い好きにもたまらない現場になっています。

@ のギャグの例(あくまで一例です):

  • 「ようかーいにんげーん!しーっとるけー!ヒャーッホホッ!!」
  • ジュンスでーす!」「ユチョンでーす!」「三波春夫でございます!」(バシッ!)
  • 「こん平でーす!!!」(コンパイルが通る度絶叫するギャグ)

最近では@の一発ギャグ「聞いてるよー!」(聞いてないよの口調で)も流行っています。

募集要項

弊社では現在、東京本社でプログラマを募集しています。

仕事内容はモバイルアプリ開発がメインになります。

詳細は他の人も書いているのでそちらを参照して下さい。

株式会社Abby 社員募集 - よねのはてな

AbbyでJava技術者募集(急募) - しんさんの出張所 はてな編

上記でも書いてあるとおりiOS向けアプリも全てJavaで開発しているのでメインはJavaでの開発になります。

そのためJavaでの開発経験がある方を募集しています。

Javaという点では@がエグいほどのエキスパートで彼ほどJavaを書ける人を私は見たことがありません。そのためJavaプログラマであれば彼に学ぶことをすごく多いと思います。

Javaの経験が無くても、他のプログラミング言語で得意なものがある人も一度遊びにきてみてください。

OS、開発環境などは個人の好きなように構築しています。NetBeansを使う人も入れば私のようにArch Linux + Emacsを使っている人もいます。

私個人としてはスキルも大事ですが、積極性や今後ののびしろがある方を優先に採用したいと思っています。

この業界は多岐に渡り様々な知識が必要です。現状出来なくても当たり前の事は多いので現状のスキルとよりも向上心があり、伸びてくれる人材を探しています。

(もちろん一緒に働く現メンバーは上記に当てはまる素晴らしいメンバーですよ:))

興味がある方は一度会社に遊びに来て下さい。

一緒に働きたい方は、info@abby.co.jp までメールを下さい。

もしくはTwitterFacebookで@宛まで連絡してください。

皆様からの応募お待ちしております。

なお、東方神起のメンバーは募集していませんのでご了承ください。

Docker本を書いたという話

お久しぶりです。僕です。

今回、いろいろとご縁がありまして、Docker本を書かせて頂きました。

一応どんな人が書いてるかというと以下を見てもらうといいかも知れません。ストック数 1位なのが私です。

dockerに関する164件の投稿 - Qiita

今回執筆の機会を与えてくださった技術評論社様および共著者の@yone098に感謝致します。

(95%私が書いてますけど)

夜な夜な原稿見直しをしてるとDockerのサイトがリニューアルして焦ったり、いろいろとあったのですがなんとか出す事ができました。

タイトルは入門となっていますが、ひと通りの事は書いてあるのでリファレンスに近い部分もあるかも知れません。

また、内容ですが、最新の Docker 0.10 を元に記述しています。

興味のある方、Dockerを触ってみたいという方は是非読んでみて下さい。

gihyo

https://gihyo.jp/dp/ebook/2014/978-4-7741-6504-2

Amazon Kindle

http://www.amazon.co.jp/o/asin/B00JWM4W2E/

また弊社ではエンジニアを募集しています。

気になる方は、TwitterなりFacebookなりで私、@yone098に声をかけてください。

さて、次は CoreOS か ProjectAtomic いくか…

Clojure (Java) はやはり速かったという話

Clojure (Java) はやはり速かったという話

あまりにも遅すぎなのでは?と思ったので調べたらやはり計測方法に問題があっ たみたい。

Java がこんなに遅いわけない。

遅かった原因

いつも通り nrepl 経由で適当に実行していたのが原因。

leiningen から nrepl を立ち上げていたのだが、以下を見れば遅い理由がわかる。

java -client -Xbootclasspath/a:/home/ma2/.lein/self-installs/leiningen-2.3.3-standalone.jar -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Dfile.encoding=UTF-8 -Dmaven.wagon.http.ssl.easy=false -Dleiningen.original.pwd=/home/ma2 -Dleiningen.script=/home/ma2/bin/lein -classpath /home/ma2/.clojure:/home/ma2/.lein/self-installs/leiningen-2.3.3-standalone.jar clojure.main -m leiningen.core.main repl :headless :port 60000

-client オプション久々に見た!

leiningen は前から起動がクソ遅いと文句を言われていたので jvm のオプションを指定している。

起動速度をあげるために以下のオプションで起動している。

  • -client

  • -XX:+TieredCompilation

  • -XX:TieredStopAtLevel=1

oh …

実行速度が遅くて当たり前である。

lein スクリプト内

...

export LEIN_JVM_OPTS="${LEIN_JVM_OPTS-"-XX:+TieredCompilation -XX:TieredStopAtLevel=1"}"

...

export TRAMPOLINE_FILE
"$LEIN_JAVA_CMD" -client \
    "${BOOTCLASSPATH[@]}" \
    $LEIN_JVM_OPTS \
    -Dfile.encoding=UTF-8 \
    -Dmaven.wagon.http.ssl.easy=false \
    -Dleiningen.original.pwd="$ORIGINAL_PWD" \
    -Dleiningen.script="$SCRIPT" \
    -classpath "$CLASSPATH" \

がっつり固定で書かれている…

grench から起動すればこんなの気にしなくていいので -server モードで起動するようにすべきである。

server モード で計測

lein スクリプトを修正して以下のオプションで立ちあげてみる。

java -server -Xbootclasspath/a:/home/ma2/.lein/self-installs/leiningen-2.3.3-standalone.jar -Xms512m -Xmx1024m -Dfile.encoding=UTF-8 -Dmaven.wagon.http.ssl.easy=false -Dleiningen.original.pwd=/home/ma2 -Dleiningen.script=/home/ma2/bin/lein -classpath /home/ma2/.clojure:/home/ma2/.lein/self-installs/leiningen-2.3.3-standalone.jar clojure.main -m leiningen.core.main repl :headless :port 60000

計測してみよう。

λ grench eval '(time (reduce unchecked-add (map unchecked-add (range 1000000) (range 1000000))))'
"Elapsed time: 366.023913 msecs"
999999000000

ほぼ Python と変わらないくらいになった。

僕のおんぼろ X200s だとこれぐらいですが、会社の i7 搭載マシンだと Python より速くなります。

まとめ

パフォーマンスを計測する場合、leiningen 使うとダメです! nrepl 経由で楽せずちゃんと測りましょう!

Clojure が遅いという話

Clojure が遅いという話

遅いケースもあるという認識も持っておいてもらおう。 比較のために今回はPythonと比べてみる。

Clojure

(dotimes [i 5]
  (time
   (reduce unchecked-add (map unchecked-add (range 1000000) (range 1000000)))))

Python

import timeit

def f (x):
    return reduce(lambda x, y: x + y, (map (lambda x, y: x + y, xrange(1, x), xrange(1, x))))

r = timeit.timeit("f(1000000)", "from __main__ import f",  number = 1)

print(r*1000)

結果

両者、かなり似たコードになっているが結果はどうか?

Clojure は初回どうしても遅くなるので 5 回計測。

Clojure

"Elapsed time: 936.786616 msecs"
"Elapsed time: 922.82911 msecs"
"Elapsed time: 923.171342 msecs"
"Elapsed time: 931.944958 msecs"
"Elapsed time: 935.293865 msecs"

Python

352.871894836

よく unchecked-xxx 使えみたいな話がありますが、使ってもなお遅いということを覚えておいた方がよさそうですね。