Keep it simple, stupid!!

IT系で調べたこととか考えたこととか書くつもりです。

BigDecimalがゼロかどうか?

BigDecimalがゼロかどうかの判定が意外とめんどくさいのと、
やりかたがいろいろあるので記事書くことにしました。

ダメなやり方

BigDecimal value = new BigDecimal("0.0");
if ( value.equals(BigDecimal.ZERO) )
    System.out.println("true");
else
    System.out.println("false");

scaleが違うと一致とみなさないのでうまくいかない。
(scaleまで比較してほしいなら正しい)

よく見るやり方※1

BigDecimal value = new BigDecimal("0.0");
if ( value.compareTo(BigDecimal.ZERO)==0 )
    System.out.println("true");
else
    System.out.println("false");

これだとscale関係なく0かどうか判定で来てハッピー!

さらに、

今日見つけたやり方※2

BigDecimal value = new BigDecimal("0.0");
if ( value.signum()==0 )
    System.out.println("true");
else
    System.out.println("false");

※1と結果は同じ。
速度的にはcompareTo:signum⇒10:6ぐらいの感じだった。

ゼロかどうか?正かどうか?負かどうか?を判定するならsignum()使った方が速いっぽい。
ただ職場で使うなら何やってるかわかりやすいcompareTo使う*1かなー。

*1:日本語にしたらそれぞれcompareToは「ZEROと比べて差がないなら0」signumは「符号がなければ0」だから前者の方がまだマシかと。

javaで円グラフの描画(awtで描画)

参考はこちら
Javaサンプルソース【アンチエイリアスで円グラフをきれいに表示】『愛のJava256本ノック』
JAVAについて質問です☆JFrameを使って円グラフを作... - Yahoo!知恵袋

import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Random;

public class PieChartPainter {
	private final Random r = new Random();

	/**
	 * 円グラフ描画
	 * 
	 * @param graphics
	 * @param pieSize
	 *            円グラフ直径
	 * @param width
	 *            描画範囲幅
	 * @param height
	 *            描画範囲高さ
	 * @param data
	 *            グラフデータ
	 */
	public void paint(Graphics graphics, int pieSize, int width, int height,
			int[] data) {
		if (graphics instanceof Graphics2D) {
			// アンチエイリアス
			((Graphics2D) graphics).setRenderingHint(KEY_ANTIALIASING,
					VALUE_ANTIALIAS_ON);
		}

		// 配列の和
		int sum = sum(data);
		// 割合計算
		int tmp[] = new int[data.length];
		for (int i = 0; i < data.length; i++) {
			tmp[i] = (int) (((double) data[i] / sum) * 360.0);
		}
		// 誤差調整
		int cnt = 0;
		while (sum(tmp) != 360) {
			try {
				tmp[cnt++] += 1;
			} catch (Exception e) {
				break;
			}
		}
		// グラフ描画
		sum = 90;
		for (int i = 0; i < tmp.length; i++) {
			graphics.setColor(getColor(i));
			// 描画
			graphics.fillArc((width / 2) - (pieSize / 2), (height / 2)
					- (pieSize / 2), pieSize, pieSize, sum, -tmp[i]);
			sum -= tmp[i];
		}
	}

	private int sum(int data[]) {
		int sum = 0;
		for (int element : data) {
			sum += element;
		}
		return sum;
	}

	protected Color getColor(int index) {
		return new Color(r.nextInt(0xff), r.nextInt(0xff), r.nextInt(0xff));
	}
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Arrays;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Sample extends JFrame {
	class SamplePanel extends JPanel {
		public static final int WIDTH = 400;
		public static final int HEIGHT = 400;

		private final Random r = new Random();

		private final int SIZE = 300;

		SamplePanel() {
			setPreferredSize(new Dimension(WIDTH, HEIGHT));
			setBackground(Color.WHITE);
		}

		/**
		 * paintComponent()
		 */
		@Override
		public void paintComponent(Graphics g) {
			super.paintComponent(g);

			int[] data;
			{
				int cnt = r.nextInt(10) + 2;
				int[] tmp = new int[cnt];
				for (int i = 0; i < tmp.length; i++) {
					tmp[i] = r.nextInt(100);
				}

				Arrays.sort(tmp);

				data = new int[cnt];
				for (int i = 0; i < data.length; i++) {
					data[i] = tmp[data.length - i - 1];
				}
			}

			PieChartPainter painter = new PieChartPainter();
			painter.paint(g, SIZE, WIDTH, HEIGHT, data);
		}
	}

	Sample() {
		add(new SamplePanel());
		pack();
		setTitle("円グラフ");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
	}

	/**
	 * 実行
	 */
	public static void main(String[] args) {
		new Sample();
	}
}

特定の扇だけ切り離したい。
こういうのf:id:order_is_water:20140924102141j:plain:medium

	/**
	 * 円グラフ描画
	 * 
	 * @param graphics
	 * @param pieSize
	 *            円グラフ直径
	 * @param width
	 *            描画範囲幅
	 * @param height
	 *            描画範囲高さ
	 * @param data
	 *            グラフデータ
	 * @param highlightIndex
	 *            indexの扇だけ離す
	 */
	public void paint(Graphics graphics, double pieSize, double width,
			double height, int[] data, int highlightIndex) {
		if (graphics instanceof Graphics2D) {
			// アンチエイリアス
			((Graphics2D) graphics).setRenderingHint(KEY_ANTIALIASING,
					VALUE_ANTIALIAS_ON);
		}

		// 配列の和
		int sum = sum(data);
		// 割合計算
		int tmp[] = new int[data.length];
		for (int i = 0; i < data.length; i++) {
			tmp[i] = (int) (((double) data[i] / sum) * 360.0);
		}
		// 誤差調整
		int cnt = 0;
		while (sum(tmp) != 360) {
			try {
				tmp[cnt++] += 1;
			} catch (Exception e) {
				break;
			}
		}
		// グラフ描画
		sum = 90;
		for (int i = 0; i < tmp.length; i++) {
			graphics.setColor(getColor(i));

			if (i == highlightIndex) {
				// highlight
				int c = sum - (tmp[i] / 2);
				double r = Math.toRadians(c);
				double addX = (Math.cos(r) * pieSize) / 20;// 1/20離す
				double addY = (Math.sin(r) * pieSize) / 20;// 1/20離す
				// 描画
				graphics.fillArc((int) (((width / 2) - (pieSize / 2)) + addX),
						(int) (((height / 2) - (pieSize / 2)) - addY),
						(int) pieSize, (int) pieSize, sum, -tmp[i]);
			} else {
				// 描画
				graphics.fillArc((int) ((width / 2) - (pieSize / 2)),
						(int) ((height / 2) - (pieSize / 2)), (int) pieSize,
						(int) pieSize, sum, -tmp[i]);
			}
			sum -= tmp[i];
		}
	}

Enumのabstract method

javaEnumってabstract methodが書けるね。

こんなのだね。

public class Otameshi {
	public enum E {
		A {
			@Override
			void method() {
				System.out.println("A method");
			}
		},
		B {
			@Override
			void method() {
				System.out.println("B method");
			}
		};

		abstract void method();
	}

	public static void main(String[] args) {
		for (E e : E.values()) {
			e.method();
		}
	}
}

で、なんとなく気になってクラスファイルを覗いたら案の定クラスがEnum宣言分増えてたよ。
つまり、

public class Otameshi {
	public enum E {
		A {
			@Override
			void method() {
				System.out.println("A method");
			}
		},
		B {
			@Override
			void method() {
				System.out.println("B method");
			}
		};

		abstract void method();
	}

	public static void main(String[] args) {
		for (E e : E.values()) {
			System.out.println(e.getClass());
		}
	}
}

ってやると

class Otameshi$E$1
class Otameshi$E$2

ってなる。

try-with-resourcesと等価コード

try-with-resourcesの使い方を昨日初めて知りましたorz
残念なあまりバイトコードまで読んで等価のコードを読み取ってみたよ*1

とりあえず

try (InputStream input = new FileInputStream("ファイル.txt")) {
    // 処理
}

な書き方ができるのがtry-with-resources
いままでは

InputStream input = new FileInputStream("ファイル.txt");
try {
    // 処理
} finally {
    input.close();
}

って書いてたね*2

でもこの二つは等価じゃないみたい。

バイトコードから読み取った感じだと、

Throwable e1 = null;
Throwable e2 = null;
try {
	InputStream input = new FileInputStream("ファイル.txt");
	try {
		// 処理
	} catch (Throwable e) {
		e1 = e;
		throw e1;
	} finally {
		if (input != null) {
			input.close();
		}
	}
} catch (Throwable e) {
	e2 = e;
	if (e1 == null) {
		e1 = e2;
	} else {
		if (e1 == e2) {
			e1.addSuppressed(e2);
		}
	}
	throw e1;
}

のような感じ。

#超厳密にいうと、変数宣言*3とtry catch finallyの範囲の指定*4javaコードから書くのはたぶん不可能だったりする。
基本的にバイトコードのほうが効率的になっている

*1:間違ってる可能性大

*2:ええ僕は一昨日まで書いてましたよorz

*3:Throwable eという変数はバイトコードには存在しない。
次の行でe1、e2に代入しているがバイトコードではcatchで直接代入している。

} catch (e2) {
っとか書きたい。e1の扱いはなかなか難しい。

*4:input.close()前のfinallyは本当はfinallyではないし、catchの書き方も厳密には違う(どういうコード書いたらそういうバイトコードになるのかわからない。)
記載したコードは残念ながらgotoとfinally扱いがいくつか多くなる。

org.eclipse.jface.text.rules.IRule

EclipseプラグインのEditor作るとき色つけたくて
ScannerでIRuleをポイポイぶっこんだので、
使って覚えたことを書きます。*1

標準クラス

NumberRule 数字文字列のルール
WordRule 指定文字列のルール
使い方はこっち見るといい
Eclipseプラグイン DMDLEditor キーワード(Hishidama's Eclipse Plugin DMDLEditor Memo)
MultiLineRule 開始文字と終了文字を指定したルール。行を跨いでscanする
SingleLineRule 開始文字と終了文字を指定したルール。こっちは行を跨がない
WhitespaceRule 空白のルール。スキップとかに使うのかな?

自力で作った場合

evaluateで対象文字数分scanner.read()して、
対象だったらそのままITokenを返す。
そうするとevaluateに入ってきた文字列indexから
index+(scanner.read()回数分)までの文字をITokenで装飾してくれる。

対象でなければ、scanner.unread();で開始文字まで戻して、
Token.UNDEFINEDでも返しておく。(たぶん何でもいい)
scanner.unread()で正しく戻さないと、次のルールの開始位置がずれたりしちゃうので注意。


一文字(?マーク)だけ判断するクラスを張ってみる。

public class QuestionRule implements IRule {

	protected IToken fToken;

	public QuestionRule(IToken token) {
		Assert.isNotNull(token);
		fToken = token;
	}

	/*
	 * @see IRule#evaluate(ICharacterScanner)
	 */
	@Override
	public IToken evaluate(ICharacterScanner scanner) {
		int c = scanner.read();
		if (c == '?') {
			return fToken;
		}
		scanner.unread();
		return Token.UNDEFINED;
	}
}

*1:大体はhishidamaとか見たほうが早い

Eclipseプラグインで作ったEditorで外部ファイルを開きたい。

普通に「エディター付きプラグイン」で作ったエディタだとプロジェクト外のファイル開くとエラーになる。


TextEditorから継承されたクラスのでsetDocumentProvider()してるところを

setDocumentProvider(new TextFileDocumentProvider());

にしたら行けた。

IDocumentPartitionerが設定できなくなるけど、
僕は今、IDocumentPartitionerの使い道がわからないので別に困らない。

Eclipseプラグイン作成

最近、Eclipseプラグインを作った。
とりあえず初歩の初歩だけど、jarにするまでの手順を書く。
(使ってるのはEclipse3.7の日本語化です。)

プロジェクトを作る

Eclipseから、
「ファイル」→「新規」→「プラグイン・プロジェクト」
「プロジェクト名」を決めて「次へ」。

「ID」に適当にいれる。(アクティベーターのパッケージ名とかにするといい感じかも。)
「名前」に何プラグインなのかいれる。
「アクティベーター*1」にプラグインのアクティベータークラス名をいれる。
「次へ」。


次に選ぶのはテンプレート。
テンプレートを選んで作成すればかなり簡単に始められる。
いくつか試した奴だけ下記に説明する。

インクリメンタル・プロジェクト・ビルダー付きプラグイン Eclipseのビルドに乗っけて何かやれるプラグインを作るならここから始めるのがいい。
エディター付きプラグイン 何かのファイルのエディターを作るならここから始めるのがいい。
ビュー付きプラグイン Eclipseの「ウィンドウ」→「ビューの表示」で出てくるビューを作るならここから始めるのがいい。
Hello,Worldコマンド Eclipseのメニュー・ツールバーにアクションを乗っけたいならここから始めるのがいい。
カスタム・プラグイン・ウィザード 複合的にいろんなテンプレートをいっぺんに使いたい場合はここから始める。
普通のテンプレートには無い「設定ページ」があったりする。
(実は後でどうにでもなるので、作る範囲が決まってなければメイン部分のテンプレートで始めるといい)


この次は、どのテンプレートを選んだかで入力項目が違うので勘で入れてく。
どうしてもわからなければ、一旦捨てプロジェクト作って試すといいと思う。


「完了」するとプロジェクトができてる。

javaじゃないファイル

いくつかよくわからないファイルができてると思う。
基本的にはEclipseさんがUIで入力できるようにしてくれているので
気にせず突き進んでもいいけど、
一応なんとなくわかることだけ書く。

MANIFEST.MF プラグインのIDとか名前とか持ってる。
他には、入ってなきゃいけないプラグイン(依存関係)とか
jar使ったりするならここに書かれる。
plugin.xml どんなプラグイン拡張があるか、それぞれどのクラスで動くか、ここに書いてある。
エディターがあって、このクラスですよ。とか、
ビューがあって、このクラスですよ。とか。
build.properties たぶんエクスポートの時に必要。
「javacDefaultEncoding.. = UTF-8」って追記しないと
プラグインのエクスポートでエラー*2になることがある。

デバッグ

plugin.xml(MANIFEST.MF)をダブルクリックすると、
プラグイン情報用のUIがエディタ領域に出てくる。
「概要」タブの右チョイ下に
Eclipseアプリケーションをデバッグモードで起動」をクリックするとデバッグできる。

プラグインをjarファイルにする(エクスポート)

デバッグと同じく「概要」タブの右下の
「エクスポート・ウィザード」をクリックして、
「宛先」(出力先)を適当に選んで「完了」するとjarができる。

*1:Activator。AbstractUIPluginを継承して作成されるクラス。プラグインの中心クラス

*2:Eclipseの環境によるっぽい。MS932で記載してたら要らないのかな?