承認これくしょん

my black histories

Oracleで特定のテーブル・ビューに依存するビューの一覧を取得する

ビューを参照するビュー、つまり多重ビューがあるとテーブルのスキーマ変更も一苦労です。そもそもアンチパターンでは…?
とはいえ、既に作られてしまったなら立ち向かうしかない。とにかく依存関係を明らかにしなければ。
そこでテーブル・ビュー名を指定すると、それに依存するビューの一覧を出力するスクリプトを作りました。
Oracle専用です。動作確認には11g XEを使いました。

ソース (viewFinder.js)

var Properties = java.util.Properties;
var OracleDriver = Packages.oracle.jdbc.OracleDriver;
var PreparedStatement = java.sql.PreparedStatement;

var queries = {
  dependencies: "SELECT NAME FROM USER_DEPENDENCIES WHERE TYPE = 'VIEW' AND REFERENCED_NAME = ? ORDER BY NAME",
  views: 'SELECT VIEW_NAME, TEXT FROM USER_VIEWS WHERE VIEW_NAME IN '
};
var viewNames = [];

// JDBC接続設定
var url = 'jdbc:oracle:thin:@localhost:1521:xe';  // 接続文字列
var user = 'scott'; // ユーザーID
var password = 'tiger'; // パスワード

if (arguments[0]) {
  var arg = arguments[0];
  var prop = new Properties();
  prop.setProperty('user', user);
  prop.setProperty('password', password);

  try {
    var conn = new OracleDriver().connect(url, prop);
    print(arg);
    printTree(arg, '');

    if (viewNames.length > 0) {
      queries.views += '(' + repeat('?', viewNames.length, ',') + ') ORDER BY VIEW_NAME';
      query(queries.views, viewNames, function(rs) {
        var viewName = rs.getString('VIEW_NAME');
        var text = rs.getString('TEXT');
        print('\r\n' + viewName + '\r\n' + repeat('-', viewName.length) + '\r\n' + text);
      });
    }

  } finally {
    if (conn) {
      try {
        conn.close();
      } catch(e) {}
    }
  }
}

function printTree(name, prefix) {
  var items = query(queries.dependencies, [name], function(rs) {
    return rs.getString('NAME');
  });

  items.forEach(function(item) {
    if (viewNames.indexOf(item) == -1) {
      viewNames.push(item);
    }
  });

  if (items.length > 0) {
    for (var i = 0, length = items.length; i < length; i++) {
      var item = items[i];
      if (i == length - 1) {
        print(prefix + '└─' + item);
        printTree(item, prefix + '    ');
      } else {
        print(prefix + '├─' + item);
        printTree(item, prefix + '│   ');
      }
    }
  }
}

function query(query, params, callback) {
  try {
    var ps = conn.prepareStatement(query);
    if (params) {
      for (var i = 0, length = params.length; i < length; i++) {
        var param = params[i];
        ps.setObject(i + 1, param);
      }
    }
    var arr = [];
    var rs = ps.executeQuery();
    while (rs.next()) {
      arr.push(callback(rs));
    }
    return arr;
  } finally {
    if (ps) {
      try {
        ps.close();
      } catch(e) {}
    }
  }
}

function repeat(str, times, separator) {
  var newStr = '';
  for (var i = 0; i < times; i++) {
    if (separator && i > 0) newStr += separator;
    newStr += str;
  }
  return newStr;
}

実行方法

まずはojdbc6.jarことOracle JDBC Thin Driverを用意。
そしてJDK付属のjrunscript、JREにも入ってるjjsコマンドのいずれかで実行します。

jrunscript -cp ojdbc6.jar viewFinder.js (テーブル・ビュー名)
jjs -cp ojdbc6.jar viewFinder.js -- (テーブル・ビュー名)

出力イメージ

treeコマンド風にビューの依存関係を、その下に各ビューのソースを出力します。

EMP
└─EMP1
    ├─EMP2
    │   └─EMP4
    └─EMP3

EMP1
----
SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM EMP

EMP2
----
SELECT "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" FROM EMP1

EMP3
----
SELECT EMPNO FROM EMP1

EMP4
----
SELECT JOB FROM EMP2

参考

Nashorn + JavaFXでQRコードを表示する

Java 8から新しいJavaScriptエンジンであるNashornが同梱されています。
実行シェルであるjjsでは、-fxオプションを使うと簡単にJavaFXアプリケーションが作成できます。
これを使って、コマンドライン引数の値でQRコードを生成して表示するスクリプトを書きました。

実行方法

ZXingライブラリを使用しています。Mavenリポジトリから各jarをダウンロードしてください。

  • core-3.2.0.jar
  • javase-3.2.0.jar

それぞれスクリプトと同じディレクトリに配置し、以下のコマンドで実行します。

jjs -cp core-3.2.0.jar;javase-3.2.0.jar -fx qr.js -- (QRコードの内容)

ソース (qr.js)

var Hashtable = java.util.Hashtable;

var ImageView = javafx.scene.image.ImageView;
var StackPane = javafx.scene.layout.StackPane;
var Scene = javafx.scene.Scene;
var SwingFXUtils = javafx.embed.swing.SwingFXUtils;

var EncodeHintType = com.google.zxing.EncodeHintType;
var ErrorCorrectionLevel = com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
var QRCodeWriter = com.google.zxing.qrcode.QRCodeWriter;
var BarcodeFormat = com.google.zxing.BarcodeFormat;
var MatrixToImageWriter = com.google.zxing.client.j2se.MatrixToImageWriter;

var content = arguments[0]; // コマンドライン引数
var width = 230;
var height = 230;

function start(stage) {
  if (content) {
    // エンコード設定
    var encodeHint = new Hashtable();
    encodeHint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
    // QRコード生成
    var writer = new QRCodeWriter();
    var bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, encodeHint);
    var img = SwingFXUtils.toFXImage(MatrixToImageWriter.toBufferedImage(bitMatrix), null);
    // ウインドウに表示
    stage.title = content;
    var root = new StackPane();
    var imageView = new ImageView(img);
    root.children.add(imageView);
    stage.scene = new Scene(root, width, height);
    stage.show();
  } else {
    exit(); // 引数が無ければ終了
  }
}

javafx.application.Applicationを継承せずにサクッと作れます。
あらかじめ用意されているユーティリティを使えば、昔ながらのBufferedImageも表示できます。

参考

複数画像を一括トリミングするやつ

特定アプリケーションのスクリーンショットとか、余白の位置が固定されているときに。
jrunscriptで実行するとコマンドライン引数で渡された画像ファイルを処理して上書き保存します。
とりあえず形式はpngで。

var File = java.io.File;
var ImageIO = Packages.javax.imageio.ImageIO;

// 余白(ピクセル)
var margin = {top: 111, left: 8, right: 8, bottom: 25};

for each (var arg in arguments) {
  var file = new File(arg);
  var baseImage = ImageIO.read(file);
  var newImage = baseImage.getSubimage(margin.left, margin.top,
    baseImage.getWidth() - (margin.left + margin.right), baseImage.getHeight() - (margin.top + margin.bottom));
  ImageIO.write(newImage, "png", file);
}

年度末ですねー。
おちこんだりもするけれど、私はげんきです。

HTA + JScriptでExcel方眼紙に絵を描こう

f:id:old_horizon:20150302222615p:plain
Excelのセルをドットに見立てて絵を描くマクロです。
似たようなものは沢山あるわけですが、今回はWindows標準のHTA + JScriptExcelを操作して作ります。
HTAといえば、よくワンクリック詐欺のポップアップに使われていることで有名ですよね。
早すぎたElectronやNW.jsという感じです…

HTAHTML5を使うには

デフォルトではIE7互換で動作するので、以下の記述でドキュメントモードを最新バージョンに指定します。

<meta http-equiv="X-UA-Compatible" content="IE=edge">

IE9以降のChakraエンジンでもActiveXObjectは生成できました。
HTML5の表現力と強力なCOMが両方そなわり最強に見える。

ソース

ウインドウにD&Dされた画像をCanvasに描画し、そこから各ピクセルの色を取得して対応するセルの背景色に指定します。(アルファチャンネルは無視していますが…)
処理の進行状況はプログレスバーと%表示で確認できます。

img2xlsx.hta
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<title>img2xlsx</title>
<body>
<progress style="width:200px;height:10px;" id="progress-bar" max="100" value="0"></progress>
<output id="progress-label">0</output>%
<script type="text/jscript">
(function() {

  window.resizeTo(280, 80);

  var isProcessing;
  var width;
  var height;
  var pixels;

  var progressBar = document.getElementById('progress-bar');
  var progressLabel = document.getElementById('progress-label');

  document.addEventListener("dragover", function(e) {
      e.preventDefault();
  });

  document.addEventListener("drop", function(e) {
    e.preventDefault();
    if (!isProcessing) {
      isProcessing = true;
      var file = e.dataTransfer.files[0];
      var image = new Image();
      image.src = window.URL.createObjectURL(file);
      image.onload = function(e) {
        createExcel(getPixelArray(this));
      }
    }
  });

  function getPixelArray(image) {
    width = image.width;
    height = image.height;
    pixels = width * height;

    var canvas = document.createElement('canvas');
    var context = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = height;

    context.drawImage(image, 0, 0);
    return context.getImageData(0, 0, width, height).data;
  }

  function createExcel(pixelArray) {
    var excel = new ActiveXObject('Excel.Application');
    excel.Application.ScreenUpdating = false;

    var book = excel.Workbooks.Add();
    var sheet = book.WorkSheets(1);
    sheet.Cells.ColumnWidth = 0.77;
    sheet.Cells.RowHeight = 7.5;
    excel.ActiveWindow.Zoom = 10;

    var x = 0;
    var y = 0;
    var counter = 0;

    var worker = new Worker('worker.js');
    worker.addEventListener('message', function(e) {
      sheet.Cells(x + 1, y + 1).Interior.Color = e.data;
      counter++;
      y++;
      if (y == width) {
        x++;
        y = 0;
      }
      updateProgress(Math.floor((counter / pixels) * 100));

      if (counter == pixels) {
        excel.Application.ScreenUpdating = true;
        excel.Visible = true;
        excel = null;
        setTimeout(CollectGarbage, 1);

        isProcessing = false;
        updateProgress(0);
      }
    }, false);

    worker.postMessage(pixelArray);
  }

  function updateProgress(value) {
    progressBar.value = value;
    progressLabel.innerText = value;
  }

})();
</script>
</body>
</html>
worker.js
self.addEventListener('message', function(e) {
  var pixels = e.data;
  for (var i = 0, max = pixels.length - 4; i <= max; i += 4) {
    postMessage(getColorValue(pixels[i], pixels[i + 1], pixels[i + 2]));
  }
  self.close();
}, false);

function getColorValue(r, g, b) {
  return r + g * 256 + b * 256 * 256;
}

現状、IEではWorkerをBlob URLから作成できないため別ファイルに切り出す必要があります。
手間はかかりますがその威力は大きく、シングルスレッドで処理するよりも高速です。長時間回しても警告が出ないし。
なおExcelのようなアウトプロセスサーバを利用すると、nullを代入しても正しく解放されないことがあります。そのためCollectGarbageで強制GCしましょうとMSが言ってました。

参考

出来上がりはこんな感じです

f:id:old_horizon:20150302222639j:plain
絡めた右手がいい感じだなあと思いました。

クラスが含まれるjarを検索するスクリプト

既存のJavaプロジェクトをビルドしようとしたらjarの参照が切れていた。
しかも必要なクラスがどのjarに入ってるかわからず、大量のjarの山から探さなければ…という辛い状況で便利かもしれません。
JDK付属のjrunscriptを使います。

使い方

jrunscript classFinder.js (検索対象クラス) (検索対象ディレクトリ)…

検索対象クラスは完全限定名でもそうじゃなくてもヒットするはず。
検索対象ディレクトリは複数指定可。それぞれのディレクトリとそのサブディレクトリ内のjarについて該当のクラスが含まれるか調べます。

ソース (classFinder.js)

var File = java.io.File;
var JarFile = java.util.jar.JarFile;
var JarEntry = java.util.jar.JarEntry;
var Collections = java.util.Collections;

var matcher;

if (arguments.length != 0) {
  matcher = new RegExp('^(.+/)*' + arguments[0].replace(/\./g,'/') + '\.class$');
  findJars(arguments.slice(1));
}

function findJars(dirPaths) {
  while (dirPaths.length != 0) {
    var dir = new File(dirPaths.pop());
    var items = dir.listFiles();
    for each (var item in items) {
      if (item.isFile() && item.getName().endsWith('.jar')) {
        findClass(item);
      } else if (item.isDirectory()) {
        dirPaths.push(item);
      }
    }
  }
}

function findClass(file) {
  var jarFile = new JarFile(file);
  var entries = Collections.list(jarFile.entries());
  for each (var entry in entries) {
    if (matcher.test(entry)) print(file + ': '+ entry);
  }
}

JarFile.entries()の戻り値がEnumerationでどうすんのこれ…って思ったけどArrayListに変換できてよかった。Collections便利。
なんかfor-each文がAS3(というか幻のES4?)っぽいですね。

IEでフォームの入力内容を保存して自動入力できるようにしたい

定型業務とかテストとかで、Webアプリのフォームに何度も同じ内容を入力する機会があると思う。これを保存しておいて自動入力できるようにしたい。
IEのアドオン開発は敷居が高そうだけど、右クリック時のコンテキストメニューは簡単に編集できるらしい。これを使ってみよう。

コンテキストメニューの編集

こんな感じでregファイルを作ってレジストリに登録する。

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt\フォームの内容をブックマークレットに保存(&B)]
"Contexts"=dword:00000001
@="D:\\Works\\MenuExt\\main.html"

規定値に実行するhtmlのパスを、Contextsは規定の0x1にしておく。
画像や文字列と言った選択中のオブジェクトの種類を指定して表示条件を設定できるそうだ。

htmlの作成

このhtmlは画面上には表示されないし、新規タブにも現れない。
しかしscriptタグ内の処理は実行されるので、ここにロジックを書けばOK。

<script language="JScript">
var shell = new ActiveXObject('WScript.Shell');
var fso = new ActiveXObject('Scripting.FileSystemObject');
// ブックマークレットの保存先(ユーザーのお気に入りフォルダ)
var path = shell.SpecialFolders('Favorites') + '\\';

var oDocument = window.external.menuArguments.document;
var script = getBookmarklet(oDocument);
if (script) {
   var name = oDocument.title || oDocument.location.href.replace(/.+\//, '');
   saveBookmarklet(script, name, path);
   alert('ブックマークレットを保存しました。');
} else {
   alert('名称つきフォームが存在しないか、保存可能な値が入力された項目が存在しません。');
}

shell = null;
fso = null;

function getBookmarklet(document) {
   var script = 'javascript:(function (a){for(var b in a)for(var c in a[b])document.forms[b].elements[c].value=a[b][c]})(';
   var outerScript = '';
   for (var i = 0; i < document.forms.length; i++) {
       if (document.forms[i].name != '[object HTMLInputElement]' && document.forms[i].name != '') {
           outerScript && (outerScript += ',');
           var innerScript = '';
           for (var j = 0; j < document.forms[i].elements.length; j++) {
               if (document.forms[i].elements[j].name
                   && document.forms[i].elements[j].type != 'hidden'
                   && document.forms[i].elements[j].type != 'file'
                   && document.forms[i].elements[j].type != 'submit'
                   && document.forms[i].elements[j].type != 'image'
                   && document.forms[i].elements[j].type != 'reset'
                   && document.forms[i].elements[j].type != 'button'
                   && document.forms[i].elements[j].value != '') {
                       innerScript && (innerScript += ',');
                       innerScript += document.forms[i].elements[j].name + ':"' + document.forms[i].elements[j].value + '"';
                   }
           }
           outerScript += innerScript ? '{' + document.forms[i].name + ':{' + innerScript + '}' : '';
       }
   }
   script += outerScript + '});'
   return outerScript ? script : '';
}

function saveBookmarklet(script, name, path) {
   fso.FolderExists(path) || fso.CreateFolder(path);
   if (!fso.FileExists(path + name + '.url')) {
       var sc = shell.CreateShortcut(path + name + '.url');
   } else {
       var i = 2;
       while(fso.FileExists(path + name + ' (' + i + ').url')) {
           i++;
       }
       var sc = shell.CreateShortcut(path + name + ' (' + i + ').url');
   }
   sc.TargetPath = script;
   sc.Save();
}
</script>

JScriptから呼び出し元のDOMを取得し、フォームに入力された値を自動入力するためのブックマークレットを生成する。
ブックマークレットはpathで指定したフォルダ内にショートカットファイルとして作成されます。
name属性のないフォームおよびその子要素や、文字列として保持できないものなどは記録対象外です。

参考

Internet Exploreにコンテキストメニューを追加して機能を追加する

Chromeの新規タブの「よくアクセスするページ」を非表示にする

新規タブをリダイレクトさせるんじゃなくて、単に非表示にしたい時に。
簡単な拡張機能を作ってインストールする。

manifest.json
{
  "manifest_version":2,
  "version":"1.0",
  "name": "Hide Most Visited",
  "content_scripts": [
    {
      "js": ["hidemv.js"],
      "matches": ["https://www.google.co.jp/_/chrome/newtab*"]
    }
  ]
}
hidemv.js
document.getElementById('mv-tiles').style.display = 'none';

上記2ファイルを適当なフォルダに入れて「パッケージ化されていない拡張機能を読み込む…」からインストールする。
なおデベロッパーモードを有効にしないとこのボタンは出ない。