view main/io/inst/chk_spreadsheet_support.m @ 11400:a22b232ac32c octave-forge

Made JVM memory check more robust w.r.t. Java return types
author prnienhuis
date Sun, 20 Jan 2013 18:03:07 +0000
parents 1f1a284a6eb9
children 3b5916d412de
line wrap: on
line source

% Check Octave / Matlab environment for spreadsheet I/O support.
%
%   usage:  [ RETVAL ] = chk_spreadsheet_support ( [/PATH/TO/JARS], [,DEBUG_LEVEL] [,PATH_TO_OOO])
%
% CHK_SPREADSHEET_SUPPORT first checks ActiveX (native MS-Excel); then
% Java JRE presence, then Java support (builtin/activated (Matlab) or
% added tru octave-forge Java package (Octave); then check existing
% javaclasspath for Java class libraries (.jar) needed for various
% Java-based spreadsheet I/O interfaces.
% If desired the relevant classes can be added to the dynamic
% javaclasspath. In that case the path name to the directory 
% containing these classes should be specified as input argument
% with -TAKE NOTICE- /forward/ slashes. In these jars reside in
% different directories, multiple calls to chk_spreadsheet_support
% can be made.
%
%     Input arguments (all are optional, but the order is important):
% /PATH/TO/JARS = (string) path (relative or absolute) to a
%                 subdirectory where java class libraries (.jar)
%                 for spreadsheet I/O reside. Can be [] or ''
% DEBUG_LEVEL   = (integer) between [0 (no output) .. 3 (full output]
% PATH_TO_OOO   = (string) installation directory of Openffice.org,
%                 usually (but not guaranteed):
%                 - Windows: C:\Program Files\OpenOffice.org
%                 - *nix:    /usr/lib/ooo
%                 - Mac OSX: ?????
%                 IMPORTANT: PATH_TO_OOO should be such that both:
%                 1. PATH_TO_OOO/program/       
%                  and
%                 2. PATH_TO_OOO/ure/.../ridl.jar
%                 resolve OK
%     Returns:
% RETVAL        =  0 No spreadsheet I/O support found
%               <> 0 At least one spreadsheet I/O interface found. RETVAL
%                  RETVAL will be set to the sum of values for found interfaces:
%                  ---------- XLS (Excel) interfaces: ----------
%                    1 = COM (ActiveX / Excel)
%                    2 = POI (Java / Apache POI)
%                    4 = POI+OOXML (Java / Apache POI)
%                    8 = JXL (Java / JExcelAPI)
%                   16 = OXS (Java / OpenXLS)
%                  --- ODS (OpenOffice.org Calc) interfaces ----
%                   32 = OTK (Java/ ODF Toolkit)
%                   64 = JOD (Java / jOpenDocument)
%                  ----------------- XLS & ODS: ----------------
%                  128 = UNO (Java / UNO bridge - OpenOffice.org)

function  [ retval ]  = chk_spreadsheet_support (path_to_jars, dbug, path_to_ooo)

% Copyright (C) 2009,2010,2011,2012,2013 Philip Nienhuis <prnienhuis at users.sf.net>
%
% This program is free software; you can redistribute it and/or modify it under
% the terms of the GNU General Public License as published by the Free Software
% Foundation; either version 3 of the License, or (at your option) any later
% version.
%
% This program is distributed in the hope that it will be useful, but WITHOUT
% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
% details.
%
% You should have received a copy of the GNU General Public License along with
% this program; if not, see <http://www.gnu.org/licenses/>.


% Author: Philip Nienhuis
% Created 2010-11-03 for Octave & Matlab
% Updates:
% 2010-12-19 Found that dom4j-1.6.1.jar is needed regardless of ML's dom4j
%            presence in static classpath (ML r2007a)
% 2011-01-04 Adapted for general checks, debugging & set up, both Octave & ML
% 2011-04-04 Rebuilt into general setup/debug tool for spreadsheet I/O support
%            and renamed chk_spreadsheet_support()
% 2011-05-04 Added in UNO support (OpenOffice.org & clones)
%     ''     Improved finding jar names in javaclasspath
% 2011-05-07 Improved help text
% 2011-05-15 Better error msg if OOo instal dir isn't found
% 2011-05-20 Attempt to cope with case variations in subdir names of OOo install dir (_get_dir_)
% 2011-05-27 Fix proper return value (retval); header text improved
% 2011-05-29 Made retval value dependent on detected interfaces & adapted help text
% 2011-06-06 Fix for javaclasspath format in *nix w. octave-java-1.2.8 pkg
%     ''     Fixed wrong return value update when adding UNO classes
% 2011-09-03 Small fix to better detect Basis* subdir when searching unoil.jar
% 2011-09-18 Fixed 'Matlab style short circuit' warning in L. 152
% 2012-12-24 Amended code stanze to find unoil.jar; now works in LibreOffice 3.5b2 as well
% 2012-06-07 Replaced all tabs by double space
% 2012-06-24 Replaced error msg by printf & return
%     ''     Added Java pkg inquiry (Octave) before attempting javaclasspath()
%     ''     Updated check for odfdom version (now supports 0.8.8)
% 2012-10-07 Moved common classpath entry code to private function
% 2012-12-21 POI 3.9 support (w. either xmlbeans.jar or xbeans.jar)
% 2013-01-16 Updated to Octave w built-in Java (3.7.1+)
% 2013-01-20 Made JVM memory detector more robust wrt Java return type

  jcp = []; retval = 0;
  if (nargin < 3); path_to_ooo= ''; end %if
    if (nargin < 2); dbug = 0; end %if
  isOctave = exist ('OCTAVE_VERSION', 'builtin') ~= 0;
    if (dbug); fprintf ('\n'); end %if
  % interfaces = {'COM', 'POI', 'POI+OOXML', 'JXL', 'OXS', 'OTK', 'JOD', 'UNO'}; % Order  = vital

  % Check if MS-Excel COM ActiveX server runs
  if (dbug), fprintf ('Checking Excel/ActiveX/COM... '); end %if
  try
    app = actxserver ('Excel.application');
    % If we get here, the call succeeded & COM works.
    xlsinterfaces.COM = 1;
    % Close Excel to avoid zombie Excel invocation
    app.Quit();
    delete(app);
    if (dbug), fprintf ('OK.\n\n'); end %if
    retval = retval + 1;
  catch
    % COM not supported
    if (dbug), fprintf ('not working.\n\n'); end %if
  end %try_catch

    % Check Java
  if (dbug), fprintf ('Checking Java support...\n'); end %if
  if (dbug > 1), fprintf ('  1. Checking Java JRE presence.... '); end %if
  % Try if Java is installed at all
  if (isOctave)
    oct_vsn = str2double (strsplit (OCTAVE_VERSION, '.'){1}) + ...
              0.1 * str2double (strsplit (OCTAVE_VERSION, '.'){2});
    if (ispc)str2double (strsplit (OCTAVE_VERSION, '.'){2});
      jtst = (system ('java -version 2> nul'));
    else
      jtst = (system ('java -version 2> /dev/null'));
    end %if
  else
    tst1 = version ('-java');
    jtst = isempty (strfind (tst1, 'Java'));
  end %if
  if (jtst)
    printf ('Apparently no Java JRE installed.\n');
    return;
  else
    if (dbug > 1), fprintf ('OK, found one.\n'); end %if
  end %if
  if (dbug > 1 && isOctave), fprintf ('  2. Checking Octave Java support... '); end %if
  try
    % The following stanza is meant for older Octave w/o built-in Java
    if (isOctave && oct_vsn < 3.7)
      % Check Java package
      [~, b] = pkg ('describe', 'java');
      if    (strcmpi (b{:}, 'Not loaded'))
        if (dbug > 1); printf ('Java package not loaded. First do: "pkg load java"\n'); end %if
      elseif strcmpi (b{:}, 'Not installed')
        if (dbug > 1); printf ('Java package is not installed.\n'); end %if
      endif
    end %if
    jcp = javaclasspath ('-all');
    % If we get here, at least Java works.
    if (dbug > 1 && isOctave), fprintf ('Java seems to work OK.\n'); end %if
    % Now check for proper version (> 1.6.x.x)
    jver = char (javaMethod ('getProperty', 'java.lang.System', 'java.version'));
    cjver = strsplit (jver, '.');
    if (sscanf (cjver{2}, '%d') < 6)
      if (dbug)
        fprintf ('  Java version (%s) too old - you need at least Java 6 (v. 1.6.x.x)\n', jver);
        if (isOctave)
          warning ('    At Octave prompt, try "!system ("java -version")"'); 
        else
          warning ('    At Matlab prompt, try "version -java"');
        end %if
      end %if
      return
    else
      if (dbug > 2), fprintf ('  Java (version %s) seems OK.\n', jver); end %if
    end %if
    % Under *nix the classpath must first be split up.
    % Matlab is braindead here. For ML we need a replacement for Octave's builtin strsplit()
    % This is found on ML Central (BSD license so this is allowed) & adapted for input arg order
    if (isunix && ~iscell (jcp)); jcp = strsplit (char (jcp), ':'); end %if
    if (dbug > 1)
      % Check JVM virtual memory settings
      jrt = javaMethod ('getRuntime', 'java.lang.Runtime');
      jmem = jrt.maxMemory ()
	  % Some Java versions return jmem as octave_value => convert to double
	  if (! isnumeric (jmem)); jmem = jmem.doubleValue(); end %if
      jmem = int16 (jmem/1024/1024);
      fprintf ('  Maximum JVM memory: %5d MiB; ', jmem);
      if (jmem < 400)
        fprintf ('should better be at least 400 MB!\n');
        fprintf ('    Hint: adapt setting -Xmx in file "java.opts" (supposed to be here:)\n');
        if (isOctave)
          fprintf ('    %s\n', [matlabroot filesep 'share' filesep 'octave' filesep 'packages' filesep 'java-<version>' filesep 'java.opts']);
        else
          fprintf ('    $matlabroot/bin/<arch>]\n');
        end %if
      else
        fprintf ('sufficient.\n');
      end %if
    end %if
    if (dbug), fprintf ('Java support OK\n'); end %if
  catch
    printf ('No Java support found.\n');
    return
  end %try_catch

  if (dbug), fprintf ('\nChecking javaclasspath for .jar class libraries needed for spreadsheet I/O...:\n'); end %if

  % Try Java & Apache POI. First Check basic .xls (BIFF8) support
  if (dbug > 1), fprintf ('\nBasic POI (.xls) <poi-3> <poi-ooxml>:\n'); end %if
  entries1 = {'poi-3', 'poi-ooxml-3'}; missing1 = zeros (1, numel (entries1));
  % Only under *nix we might use brute force: e.g., strfind (javaclasspath, classname)
  % as javaclasspath is one long string. Under Windows however classpath is a cell array
  % so we need the following more subtle, platform-independent approach:
  [jpchk1, missing1] = chk_jar_entries (jcp, entries1, dbug);
  if (jpchk1 >= numel (entries1)), retval = retval + 2; end %if
  if (dbug > 1)
    if (jpchk1 >= numel (entries1))
      fprintf ('  => Apache (POI) OK\n');
    else
      fprintf ('  => Not all classes (.jar) required for POI in classpath\n');
    end %if
  end %if
  % Next, check OOXML support
  if (dbug > 1), fprintf ('\nPOI OOXML (.xlsx) <xbean/xmlbean> <poi-ooxml-schemas> <dom4j>:\n'); end %if
  entries2 = {{'xbean', 'xmlbean'}, 'poi-ooxml-schemas', 'dom4j'}; 
  % Only update retval if all classes for basic POI have been found in javaclasspath
  [jpchk2, missing2] = chk_jar_entries (jcp, entries2, dbug);
  if (jpchk1 >= numel (entries1) && jpchk2 >= numel (entries2)), retval = retval + 4; end %if
  if (dbug > 1)
    if (jpchk2 >= numel (entries2)) 
      fprintf ('  => POI OOXML OK\n');
    else
      fprintf ('  => Some classes for POI OOXML support missing\n'); 
    end %if
  end %if

  % Try Java & JExcelAPI
  if (dbug > 1), fprintf ('\nJExcelAPI (.xls (incl. BIFF5 read)) <jxl>:\n'); end %if
  entries3 = {'jxl'}; missing3 = zeros (1, numel (entries3));
  [jpchk, missing3] = chk_jar_entries (jcp, entries3, dbug);
  if (jpchk >= numel (entries3)), retval = retval + 8; end %if
  if (dbug > 1)
    if (jpchk >= numel (entries3))
      fprintf ('  => Java/JExcelAPI (JXL) OK.\n');
    else
      fprintf ('  => Not all classes (.jar) required for JXL in classpath\n');
    end %if
  end %if

  % Try Java & OpenXLS
  if (dbug > 1), fprintf ('\nOpenXLS (.xls (BIFF8)) <OpenXLS>:\n'); end %if
  entries4 = {'OpenXLS'}; missing4 = zeros (1, numel (entries4));
  [jpchk, missing4] = chk_jar_entries (jcp, entries4, dbug);
  if (jpchk >= numel (entries4)), retval = retval + 16; end %if
  if (dbug > 1)
    if (jpchk >= numel (entries4))
      fprintf ('  => Java/OpenXLS (OXS) OK.\n');
    else
      fprintf ('  => Not all classes (.jar) required for OXS in classpath\n');
    end %if
  end %if

  % Try Java & ODF toolkit
  if (dbug > 1), fprintf ('\nODF Toolkit (.ods) <odfdom> <xercesImpl>:\n'); end %if
  entries5 = {'odfdom', 'xercesImpl'}; missing5 = zeros (1, numel (entries5));
  [jpchk, missing5] = chk_jar_entries (jcp, entries5, dbug);
  if (jpchk >= numel (entries5))    % Apparently all requested classes present.
    % Only now we can check for proper odfdom version (only 0.7.5 & 0.8.6 work OK).
    % The odfdom team deemed it necessary to change the version call so we need this:
    odfvsn = ' ';
    try
      % New in 0.8.6
      odfvsn = javaMethod ('getOdfdomVersion', 'org.odftoolkit.odfdom.JarManifest');
    catch
      % Worked in 0.7.5
      odfvsn = javaMethod ('getApplicationVersion', 'org.odftoolkit.odfdom.Version');
    end %try_catch
    if ~(strcmp (odfvsn, '0.7.5') || strcmp (odfvsn, '0.8.6') || strcmp (odfvsn, '0.8.7')
      || ~isempty (strfind (odfvsn, '0.8.8')))
      warning ('  *** odfdom version (%s) is not supported - use v. 0.8.6 or newer\n', odfvsn);
    else  
      if (dbug > 1), fprintf ('  => ODFtoolkit (OTK) OK.\n'); end %if
      retval = retval + 32;
    end %if
    elseif (dbug > 1)
    fprintf ('  => Not all required classes (.jar) in classpath for OTK\n');
  end %if

  % Try Java & jOpenDocument
  if (dbug > 1), fprintf ('\njOpenDocument (.ods + experimental .sxc readonly) <jOpendocument>:\n'); end %if
  entries6 = {'jOpenDocument'}; missing6 = zeros (1, numel (entries6));
  [jpchk, missing6] = chk_jar_entries (jcp, entries6, dbug);
  if (jpchk >= numel (entries6)), retval = retval + 64; end %if
  if (dbug > 1)
    if (jpchk >= numel(entries6))
      fprintf ('  => jOpenDocument (JOD) OK.\n');
    else
      fprintf ('  => Not all required classes (.jar) in classpath for JOD\n');
    end %if
  end %if

  % Try Java & UNO
  if (dbug > 1), fprintf ('\nUNO/Java (.ods, .xls, .xlsx, .sxc) <OpenOffice.org>:\n'); end %if
  % entries0(1) = not a jar but a directory (<000_install_dir/program/>)
  entries0 = {'program', 'unoil', 'jurt', 'juh', 'unoloader', 'ridl'};
  [jpchk, missing0] = chk_jar_entries (jcp, entries0, dbug);
  if (jpchk >= numel (entries0)), retval = retval + 128; end %if
  if (dbug > 1)
    if (jpchk >= numel (entries0))
      fprintf ('  => UNO (OOo) OK\n');
    else
      fprintf ('  => One or more UNO classes (.jar) missing in javaclasspath\n');
    end %if
  end %if

  % If requested, try to add UNO stuff to javaclasspath
  ujars_complete = isempty (find (missing0, 1));

  if (~ujars_complete && nargin > 0 && ~isempty (path_to_ooo))
    if (dbug), fprintf ('\nTrying to add missing UNO java class libs to javaclasspath...\n'); end %if
    if (~ischar (path_to_jars)), printf ('Path expected for arg # 1\n'); return; end %if
    % Add missing jars to javaclasspath. First combine all entries
    targt = sum (missing0);
    if (missing0(1))
      % Add program dir (= where soffice or soffice.exe or ooffice resides)
      programdir = [path_to_ooo filesep entries0{1}];
      if (exist (programdir, 'dir'))
        if (dbug > 2), fprintf ('  Found %s, adding it to javaclasspath ... ', programdir); end %if
        try
          javaaddpath (programdir);
          targt = targt - 1;
          if (dbug > 2), fprintf ('OK\n'); end %if
        catch
          if (dbug > 2), fprintf ('FAILED\n'); end %if
        end %try_catch
      else
        if (dbug > 2)
          printf ('Suggested OpenOffice.org install directory: %s not found!\n', path_to_ooo); 
          return
        end %if
      end %if
    end %if
    % Rest of missing entries. Find where URE is located. Watch out because case of ./ure is unknown
    uredir = get_dir_ (path_to_ooo, 'ure');
    if (isempty (uredir)), return; end %if
    % Now search for UNO jars
    for ii=2:length (entries0)
      if (missing0(ii))
        if (ii == 2)
          % Special case as unoil.jar usually resides in ./Basis<something>/program/classes
          % Find out the exact name of Basis.....
          basisdirlst = dir ([path_to_ooo filesep '?asis' '*']);
          jj = 1;
          if (numel (basisdirlst) > 0) 
            while (jj <= size (basisdirlst, 1) && jj > 0)
              basisdir = basisdirlst(jj).name;
              if (basisdirlst(jj).isdir)
                basisdir = basisdirlst(jj).name;
                jj = 0;
              else
                jj = jj + 1;
              end %if
            end %while
            basisdir = [path_to_ooo filesep basisdir ];
          else
            basisdir = path_to_ooo;
          endif
          basisdirentries = {'program', 'classes'};
          tmp = basisdir; jj=1;
          while (~isempty (tmp) && jj <= numel (basisdirentries))
            tmp = get_dir_ (tmp, basisdirentries{jj});
            jj = jj + 1;
          end %if
          unojarpath = tmp;
          file = dir ([ unojarpath filesep entries0{2} '*' ]);
        else
          % Rest of jars in ./ure/share/java or ./ure/java
          unojardir = get_dir_ (uredir, 'share');
          if (isempty (unojardir))
            tmp = uredir;
          else
            tmp = unojardir;
          end %if
          unojarpath = get_dir_ (tmp, 'java');
          file = dir ([unojarpath filesep entries0{ii} '*']);
        end %if
        % Path found, now try to add jar
        if (isempty (file))
          if (dbug > 2), fprintf ('  ? %s<...>.jar ?\n', entries0{ii}); end %if
        else
          if (dbug > 2), fprintf ('  Found %s, adding it to javaclasspath ... ', file.name); end %if
          try
            javaaddpath ([unojarpath filesep file.name]);
            targt = targt - 1;
            if (dbug > 2), fprintf ('OK\n'); end %if
          catch
            if (dbug > 2), fprintf ('FAILED\n'); end %if
                    end %try_catch
        end %if
      end %if
    end %for
    if (~targt); retval = retval + 128; end %if
    if (dbug)
      if (targt)
        fprintf ('Some UNO class libs still lacking...\n\n'); 
      else
        fprintf ('UNO interface supported now.\n\n');
      end %if
    end %f
  end %if

% ----------Rest of Java interfaces----------------------------------

  missing = [missing1 missing2 missing3 missing4 missing5 missing6];
  jars_complete = isempty (find (missing, 1));
  if (dbug)
    if (jars_complete)
      fprintf ('All Java-based interfaces (save UNO) fully supported.\n\n');
    else
      fprintf ('Some class libs lacking yet...\n\n'); 
    end %if
  end %if

  if (~jars_complete && nargin > 0 && ~isempty (path_to_jars))
    % Add missing jars to javaclasspath. Assume they're all in the same place
    if (dbug), fprintf ('Trying to add missing java class libs to javaclasspath...\n'); end %if
    if (~ischar (path_to_jars)), printf ('Path expected for arg # 1\n'); return; end %if
    % First combine all entries
    targt = sum (missing);
    % For each interface, search tru list of missing entries
    for ii=1:6   % Adapt number in case of future new interfaces
      tmpe = eval ([ 'entries' char(ii + '0') ]);
      tmpm = eval ([ 'missing' char(ii + '0') ]);
      if (sum (tmpm))
        for jj=1:numel (tmpe)
          if (tmpm(jj))
            if (iscellstr (tmpe{jj}))
              rtval = 0; kk = 1;
              while (kk <= numel (tmpe{jj}) && ! rtval)
                jtmpe = tmpe{jj}{kk};
                rtval = add_jars_to_jcp (path_to_jars, jtmpe, dbug);
                ++kk;
              end %while
            else
              rtval = add_jars_to_jcp (path_to_jars, tmpe{jj}, dbug);
            end %if
            if (rtval)
              targt = targt - rtval;
              tmpm(jj) = 0;
            end %if
          end %if
        end %for
        if (~sum (tmpm))
          retval = retval + 2^ii;
        end %if
      end %if
    end %for
    if (dbug)
      if (targt)
        fprintf ('Some other class libs still lacking...\n\n');
      else
        fprintf ('All Java-based interfaces fully supported.now.\n\n');
      end %if
    end %f
  end %if

end %function


function [ ret_dir ] = get_dir_ (base_dir, req_dir)

% Construct path to subdirectory req_dir in a subdir tree, aimed
% at taking care of proper case (esp. for *nix) of existing subdir
% in the result. Case of input var req_dir is ignored on purpose.

  ret_dir = '';
  % Get list of directory entries
  ret_dir_list = dir (base_dir);
  % Find matching entries
  idx = find (strcmpi ({ret_dir_list.name}, req_dir));
  % On *nix, several files and subdirs in one dir may have the same name as long as case differs
  if (~isempty (idx))
    ii = 1;
    while (~ret_dir_list(idx(ii)).isdir)
      ii = ii + 1;
      if (ii > numel (idx)); return; end %if
    end %while
    % If we get here, a dir with proper name has been found. Construct path
    ret_dir = [ base_dir filesep  ret_dir_list(idx(ii)).name ];
  end %if

end %function


function [ retval ] = add_jars_to_jcp (path_to_jars, jarname, dbug)

% Given a subdirectory path and a (sufficiently unique part of a) Java class
% lib file (.jar), checks if it can find the file in the subdir and tries to
% add it to the javaclasspath

  retval = 0;
  file = dir ([path_to_jars filesep jarname '*']);  %%% FIXME mult_jar
  if (isempty (file))
    if (dbug > 2), fprintf ('  ? %s<...>.jar ?\n', jarname); end %if
  else
    if (dbug > 2), fprintf ('  Found %s, adding it to javaclasspath ... ', file(1).name); end %if
    try
      javaaddpath ([path_to_jars filesep file(1).name]);
      if (dbug > 2), fprintf ('OK\n'); end %if
      retval = 1;
    catch
      if (dbug > 2), fprintf ('FAILED\n'); end %if
    end %try_catch
  end %if

end %function