マルチプラットフォームなJavaでプラットフォームに依存するバグにぶちあたりちょっと困った事になった。

プログラム自体はなんてことないFileInputStreamを使ったファイルの読み取り処理。
開発機であるWindowsではもちろん正常に動作していたんですが
環境が変わったときに問題が発生してしまいました。

[環境]

// コードはサンプルです
FileInputStream fis = null;
try {
	fis = new FileInputStream(path);

	/*** 中略 ***/

	Closeable closeable = fis;
	Channel channel = ((FileInputStream) closeable).getChannel();
	if (channel.isOpen()) {
		closeable.close();
	}
} catch (Exception e) {
	throw new Exception(e);
}

これを実行するとFileInputStreamのclose処理で落ちてしまいます。

Caused by: java.io.IOException: Bad file number
at sun.nio.ch.FileDispatcher.preClose0(Native Method)
at sun.nio.ch.FileDispatcher.preClose(Unknown Source)
at sun.nio.ch.FileChannelImpl.implCloseChannel(Unknown Source)
at java.nio.channels.spi.AbstractInterruptibleChannel.close(Unknown Source)
at java.io.FileInputStream.close(Unknown Source)

RHELで動かしてみても以下のエラーが発生。

[環境]

  • RHEL5.5(2.6.18-194.el5)
  • jre1.6.0_20

Caused by: java.io.IOException: Bad file descriptor
at sun.nio.ch.FileDispatcher.preClose0(Native Method)
at sun.nio.ch.FileDispatcher.preClose(Unknown Source)
at sun.nio.ch.FileChannelImpl.implCloseChannel(Unknown Source)
at java.nio.channels.spi.AbstractInterruptibleChannel.close(Unknown Source)
at java.io.FileInputStream.close(Unknown Source)

sun bug databaseからそれらしきものを拾ってくると、此処らへんになるのかなぁ…
Bug ID: JDK-6322678 FileInputStream(FileDescriptor) throws IOException when reading a file if FD is invalid
Bug ID: JDK-6359397 FileInputStream.read() and FileOutputStream.write() throw IOException: No such file or directory

FileInputStream(FileDescriptor) throws IOException when reading a file if FD is invalid辺りはそれっぽいですが、これはこれでまた別の事象なような。
ちなみに、この問題はjre1.6.0_25で一応解決してるとのことでしたので、早速jreを入れ替えて試してみると

………動いた。

はて、これでいいのかな… と思いつつも今回はこれ以上調査する時間もないのでまた時間があるときに調べてみよう。


【追記】
http://256.com/gray/docs/misc/java_bad_file_descriptor_close_bug.shtml
上記サイトにはRandomAccessFile.seek()、FileInputStream.readBytes()で落ちると例で書かれていたので試してみましたが正常に動きますねぇ…まあ、バージョン違うんですけど。
今のところFileInputStream.close()以外では問題は発生していないみたい。

ExcelVBAでExcelのページ数を取得する(2)

前回の方法よりもっと確実な方法を見つけた
どうやらPageSetupから直接ページ数が取得出来るみたい。

Sub getPageCount()
    Dim strFile As String
    Dim intPageCount As Integer

    'ファイル指定
    strFile = "C:\test\test.xlsx"
    '関数からExcel WorkBookのページ数を取得
    intPageCount = ExcelPrintPageCount(strFile)

    MsgBox intPageCount & "ページだよ!"
End Sub

'Excelブックのページ数をカウントする
Function ExcelPrintPageCount(ByVal strFile As String) As Integer
    Dim pageCount As Integer
    Dim xlApp As Excel.Application
    Dim objBooks As Excel.workbooks
    Dim objBook As Excel.Workbook
    Dim sht As Excel.Worksheet

    Set xlApp = New Excel.Application
    'xlApp.Visible = True 'デバッグ時に使用する

    'エラー処理
    On Error Resume Next
    Set objBooks = xlApp.workbooks

    If Dir(CStr(strFile)) <> "" Then
        'Excelファイルを読み取り専用で開く
        Set objBook = objBooks.Open( _
                        Filename:=strFile, _
                        UpdateLinks:=False, _
                        ReadOnly:=True, _
                        IgnoreReadOnlyRecommended:=True)

        If Err.Number = 0 Then
            For Each sht In objBook.Worksheets
                'シートをアクティブに変更
                sht.Activate
                'ウィンドウを改ページプレビューで表示する
                xlApp.windows(Dir(strFile)).View = xlPageBreakPreview
                '印刷プレビューのページカウントを取得する
                pageCount = pageCount + CInt(sht.PageSetup.Pages.Count)
            Next sht
        Else
            'ファイルが読み取れない場合は-1をセット
            pageCount = -1
        End If
    Else
        'ファイルが存在しない場合は-1をセット
        pageCount = -1
    End If

    If Err.Number <> 0 Then
        'その他例外が発生した場合は-1をセット
        pageCount = -1
    End If

    If Not objBook Is Nothing Then
        'Workbookを閉じる
        objBook.Saved = True
        objBook.Close
    End If
    If Not objBooks Is Nothing Then
        'Workbooksを閉じる
        objBooks.Close
    End If
    If Not xlApp Is Nothing Then
        'Excelを閉じる
        xlApp.Quit
    End If

	'戻り値をセット
	ExcelPrintPageCount = pageCount

	'メモリ解放
    Set sht = Nothing
    Set objBook = Nothing
    Set objBooks = Nothing
    Set xlApp = Nothing
End Function

しかし、この方法他のExcelVBAサイトにまだ載ってないのね・・・

ExcelVBAでExcelのページ数を取得する(1)

こまめに修正が入るドキュメント類のページ数を、わざわざ手動で数えるのが面倒なので手っ取り早くExcelVBAマクロで取得する方法がないか調べてみました。

ちなみに、当方の環境は以下の通りです。

  • OS:Windows7 Ultimate x32
  • Office:Office Professional 2010
  • Office:Office Professional 2007

Officeは一応2007と2010で動作確認してます。

まずGoogle先生に質問して出てくるのはここら辺ですね。

    H_Break = Sheet1.HPageBreaks.Count    '横の改ページ数取得
    V_Break = Sheet1.VPageBreaks.Count    '縦の改ページ数取得

VPageBreaks.Count、HPageBreaks.Countで縦横の改ページ数を取得出来るみたいですが、このサンプルでは
こんな感じ

な場合に上手く取得出来なかったりします。
他にも何個かのファイルで試してみましたが『VPageBreaks.Count』の数値自体、改ページプレビューになってないファイルのページ数を上手く取得出来なかったりして、これでマクロ組むのはちょいと面倒そうなのでパス。

お次はこれ

Sub PrintPage()
    Dim PPage As Integer
    Sheets("sheet1").Select
    PPage = Application.ExecuteExcel4Macro("get.document(50)")
    MsgBox PPage
End Sub

なるほど、Excel4Macroを使ってページ数を取得するんですね。
という訳でちょっと組んでみました。

'Excelファイルのページ数を取得する。(メイン)
Sub getPageCount()
    Dim xlApp As Excel.Application
    Dim objFileNameList As Variant
    Dim strFile As Variant
    Dim intPageCount As Integer
    Dim strOut As String

    '制御用Excelアプリケーションインスタンス生成
    Set xlApp = ActiveWindow.Application
    'ファイルを選択
    objFileNameList = selectFileProp(xlApp)

    For Each strFile In objFileNameList
        'Excel WorkBookのページ数を取得
        intPageCount = ExcelPrintPageCount(CStr(strFile))
        strOut = strOut & CStr(intPageCount) & "ページだよ!" & vbCrLf
    Next

    Set xlApp = Nothing
    
    MsgBox strOut
End Sub


'ファイル選択用のウィンドウを立ち上げる
Function selectFileProp(ByRef xlApp As Excel.Application) As Variant
    Dim objFileNameList As Variant

    objFileNameList = xlApp.GetOpenFilename( _
                        FileFilter:="Microsoft Excelブック,*.xls*", _
                        MultiSelect:=True, _
                        Title:="ページ数をカウントするファイルを選択(複数選択可)")
    
    'ファイル名読込キャンセル判定
    If Not IsArray(objFileNameList) Then
        Set xlApp = Nothing
        End 'キャンセルした場合は処理終了
    End If

    '戻り値設定
    selectFileProp = objFileNameList
End Function

'Excelブックのページ数をカウントする
Function ExcelPrintPageCount(ByVal strFile As String) As Integer
    Dim pageCount As Integer
    Dim xlApp As Excel.Application: Set xlApp = New Excel.Application
    Dim objBooks As Excel.Workbooks: Set objBooks = xlApp.Workbooks
    Dim objBook As Excel.Workbook
    Dim sht As Excel.Worksheet

    'xlApp.Visible = True 'デバッグ用

    On Error Resume Next
    'Excelファイルを読み取り専用で開く
    Set objBook = objBooks.Open( _
                        Filename:=strFile, _
                        UpdateLinks:=False, _
                        ReadOnly:=True, _
                        IgnoreReadOnlyRecommended:=True)
                
    For Each sht In objBook.Worksheets
        'シート選択
        sht.Select
        'ページ数を取得にゃー
        pageCount = pageCount + xlApp.ExecuteExcel4Macro("get.document(50)")
    Next sht
    
    'ファイルが読み取れない場合はページ数に-1を指定
    If Err.Number <> 0 Then pageCount = -1
    
    '戻り値設定
    ExcelPrintPageCount = pageCount

    'Workbookを閉じる
    If Not objBook Is Nothing Then objBook.Saved = True
    If Not objBook Is Nothing Then objBook.Close
    If Not objBooks Is Nothing Then objBooks.Close
    'Excelを閉じる
    If Not xlApp Is Nothing Then xlApp.Quit

    Set sht = Nothing
    Set objBook = Nothing
    Set objBooks = Nothing
    Set xlApp = Nothing
End Function


おお!いいんじゃないですか?

ただ、get.document(50)は改ページプレビューしていないファイルや、ページレイアウトで「縦1ページ×横1ページ」を指定している場合上手く所得できないみたい?
もう少し改良が必要ですね。