/*
* Copyright 2004-2011 the Seasar Foundation and the Others.
*
* Licensed 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.
*/
package org.seasar.mayaa.impl.ex.rhino;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import org.mozilla.javascript.Undefined;
import org.seasar.mayaa.impl.cycle.script.ScriptUtil;
/**
* Mayaaの簡易デバッガ。
*
*
* Packages.org.seasar.mayaa.impl.ex.rhino.MayaaDebugger.debug()
*
* と実行した位置でJavaScriptの入力ウィンドウが開き、executeボタンまたはCtrl+Enterで実行。
* 実行結果は{@link java.lang.System#out(java.lang.Object)}で出力される。
* 内部を変更するスクリプトなどもそのまま実行できる。
*
*
* このウィンドウが開いた状態ではTomcatが終了しないのでウィンドウをちゃんと閉じること。
*
*/
public class MayaaDebugger {
private static final String EXIT_COMMAND = "";
private static final int DEFAULT_WIDTH = 400;
private static final int DEFAULT_HEIGHT = 300;
private static boolean isNotLocalhost(final String remoteAddr) {
return (remoteAddr.equals("127.0.0.1") || remoteAddr.equals("0:0:0:0:0:0:0:1")) == false;
}
private static Object executeScript(final String script) {
return ScriptUtil.compile(script, Object.class).execute(null);
}
/**
* デバッグ
*/
public static void debug() {
try {
// REMOTE_ADDR が localhost でなければ何もしない
Object remoteAddr = executeScript("${ request.underlyingContext.remoteAddr }");
if (isNotLocalhost(String.valueOf(remoteAddr))) {
return;
}
// page.skipdebugger が true の場合は起動しない
Object skipResult = executeScript("${ page['skipdebugger'] }");
if (skipResult != null && skipResult instanceof Boolean && ((Boolean) skipResult).booleanValue()) {
return;
}
} catch (Throwable t) {
t.printStackTrace();
return;
}
openDebugWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
private static void openDebugWindow(final int width, final int height) {
// 実行するスクリプトのキュー
final BlockingQueue scriptQueue = new ArrayBlockingQueue(1);
// 実行結果を格納するキュー (null不可なのでUndefinedを使う)
final BlockingQueue resultQueue = new ArrayBlockingQueue(1);
final AtomicBoolean dontShowAgain = new AtomicBoolean(false);
String title = String.valueOf(executeScript("${ injectedNode.getSystemID() + ':' + injectedNode.getLineNumber() }"));
// デバッグウィンドウ作成
final JFrame frame = new JFrame("debug");
frame.setTitle(title);
frame.setLayout(new BorderLayout());
final JCheckBox pageCloseCheck = new JCheckBox("Don't show again on this page scope");
frame.getContentPane().add(pageCloseCheck, BorderLayout.NORTH);
pageCloseCheck.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dontShowAgain.set(pageCloseCheck.isSelected());
}
});
final JTextArea textArea = new JTextArea();
textArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16));
frame.getContentPane().add(textArea, BorderLayout.CENTER);
final JButton execButton = new JButton("execute (CTRL+ENTER)");
execButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
try {
// mayaaの実装をそのまま使うため${}で囲む。このため{@link #EXIT_COMMAND}を投げることはできない。
scriptQueue.put("${" + textArea.getText() + "}");
Object result = resultQueue.take();
if (result == Undefined.instance) {
result = null;
}
// 結果オブジェクトをSystem.outに出力
System.out.println(result);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
frame.getContentPane().add(execButton, BorderLayout.SOUTH);
textArea.addKeyListener(new KeyListener() {
@Override
public void keyTyped(final KeyEvent e) {
// do nothing.
}
@Override
public void keyReleased(final KeyEvent e) {
// do nothing.
}
@Override
public void keyPressed(final KeyEvent e) {
if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_ENTER) {
execButton.doClick();
}
}
});
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(final WindowEvent e) {
// do nothing.
}
@Override
public void windowClosing(final WindowEvent e) {
// 閉じるときには空文字列をスクリプトキューに追加
try {
if (dontShowAgain.get()) {
scriptQueue.put("${ page['skipdebugger'] = true; }");
resultQueue.take();
}
scriptQueue.put(EXIT_COMMAND);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
});
frame.setSize(width, height);
frame.setVisible(true);
try {
// スクリプトキューに空文字列が置かれるまでループ
String text;
while ((text = scriptQueue.take()).equals(EXIT_COMMAND) == false) {
Object result = executeScript(text);
if (result == null) {
// resultQueueはnull不可
result = Undefined.instance;
}
resultQueue.put(result);
}
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (Throwable t) {
t.printStackTrace();
} finally {
if (resultQueue.size() == 0) {
resultQueue.add(Undefined.instance);
}
frame.dispose();
}
}
}