001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.util;
029
030import org.opencms.i18n.CmsEncoder;
031import org.opencms.main.CmsIllegalArgumentException;
032import org.opencms.main.CmsLog;
033import org.opencms.main.CmsRuntimeException;
034import org.opencms.main.OpenCms;
035
036import java.io.BufferedReader;
037import java.io.ByteArrayOutputStream;
038import java.io.File;
039import java.io.FileInputStream;
040import java.io.FileNotFoundException;
041import java.io.IOException;
042import java.io.InputStreamReader;
043import java.io.LineNumberReader;
044import java.io.OutputStreamWriter;
045import java.nio.charset.Charset;
046import java.nio.charset.IllegalCharsetNameException;
047import java.nio.charset.UnsupportedCharsetException;
048import java.util.ArrayList;
049import java.util.List;
050
051import org.apache.commons.logging.Log;
052
053/**
054 * The representation of a RFS file along with the settings to provide
055 * access to certain portions (amount of lines) of it. <p>
056 *
057 * Most often the underlying file will be the OpenCms logfile. <p>
058 *
059 * The portion of the file that is shown is defined by a "window" of "windowSize" lines of text
060 * at a position "windowPosition" which is an enumeration of windows in ascending order. <p>
061 *
062 * @since 6.0.0
063 */
064public class CmsRfsFileViewer implements Cloneable {
065
066    /** The log object for this class. */
067    protected static final Log LOG = CmsLog.getLog(CmsRfsFileViewer.class);
068
069    /** The path to the underlying file. */
070    protected String m_filePath;
071
072    /** The path to the root for all accessible files. */
073    protected String m_rootPath;
074
075    /** The current window (numbered from zero to amount of possible different windows).  */
076    protected int m_windowPos;
077
078    /** The amount of lines to show. */
079    protected int m_windowSize;
080
081    /** The additional allowed RFS roots for viewing files. */
082    private List<String> m_additionalRoots;
083
084    /** Decides whether the view onto the underlying file via readFilePortion is enabled. */
085    private boolean m_enabled;
086
087    /** The character encoding of the underlying file. */
088    private Charset m_fileEncoding;
089
090    /**
091     * If value is <code>true</code>, all setter methods will throw a
092     * <code>{@link CmsRuntimeException}</code><p>.
093     *
094     * Only the method <code>{@link #clone()}</code> returns a clone that has set this
095     * member to <code>false</code> allowing modification to take place.<p>
096     */
097    private boolean m_frozen;
098
099    /**
100     * If true the represented file is a standard OpenCms log file and may be displayed
101     * in more convenient ways (in future versions) because the format is known.
102     */
103    private boolean m_isLogfile;
104
105    /**
106     * Creates an instance with default settings that tries to use the log file path obtained
107     * from <code>{@link OpenCms}'s {@link org.opencms.main.CmsSystemInfo}</code> instance.<p>
108     *
109     * If the log file path is invalid or not configured correctly a logging is performed and the
110     * path remains empty to allow user-specified file selection.<p>
111     */
112    public CmsRfsFileViewer() {
113
114        m_rootPath = getLogFolderPath();
115        m_isLogfile = true;
116        // system default charset: see http://java.sun.com/j2se/corejava/intl/reference/faqs/index.html#default-encoding
117        m_fileEncoding = Charset.forName(new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding());
118        m_enabled = true;
119        m_windowSize = 1000;
120
121    }
122
123    /**
124     * Returns a clone of this file view settings that is not "frozen" and therefore allows modifications.<p>
125     *
126     * Every instance that plans to modify settings has to obtain a clone first that may be
127     * modified. The original instance returned from
128     * (<code>{@link org.opencms.workplace.CmsWorkplaceManager#getFileViewSettings()}</code>) will throw
129     * a <code>{@link CmsRuntimeException}</code> for each setter invocation. <p>
130     *
131     * @return a clone of this file view settings that is not "frozen" and therefore allows modifications
132     */
133    @Override
134    public Object clone() {
135
136        // first run after installation: filePath & rootPath is null:
137        if (m_filePath == null) {
138            // below that runlevel the following call  will fail (not initialized from config yet):
139            if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
140                m_filePath = OpenCms.getSystemInfo().getLogFileRfsPath();
141            }
142        }
143        if (m_rootPath == null) {
144            m_rootPath = getLogFolderPath();
145        }
146        CmsRfsFileViewer clone = new CmsRfsFileViewer();
147        clone.m_rootPath = m_rootPath;
148        try {
149            // strings are immutable: no outside modification possible.
150            clone.setFilePath(m_filePath);
151        } catch (CmsRfsException e) {
152            // will never happen because m_filePath was verified in setFilePath of this instance.
153        } catch (CmsRuntimeException e) {
154            // will never happen because m_filePath was verified in setFilePath of this instance.
155        }
156        clone.m_fileEncoding = m_fileEncoding;
157        clone.m_isLogfile = m_isLogfile;
158        clone.m_enabled = m_enabled;
159        //clone.m_windowPos = m_windowPos;
160        clone.setWindowSize(m_windowSize);
161        // allow clone-modifications.
162        clone.m_frozen = false;
163        return clone;
164    }
165
166    /**
167     * Returns the canonical name of the character encoding of the underlying file.<p>
168     *
169     * If no special choice is fed into
170     * <code>{@link #setFileEncoding(String)}</code> before this call
171     * always the system default character encoding is returned.<p>
172     *
173     * This value may be ignored outside and will be ignored inside if the
174     * underlying does not contain textual content.<p>
175     *
176     * @return the canonical name of the character encoding of the underlying file
177     */
178    public String getFileEncoding() {
179
180        return m_fileEncoding.name();
181    }
182
183    /**
184     * Returns the path denoting the file that is accessed.<p>
185     *
186     * @return the path denoting the file that is accessed
187     */
188    public String getFilePath() {
189
190        return m_filePath;
191    }
192
193    /**
194     * Returns true if the view's internal file path points to a log file in standard OpenCms format.<p>
195     *
196     * @return true if the view's internal file path points to a log file in standard OpenCms format
197     */
198    public boolean getIsLogfile() {
199
200        // method name is bean-convention of apache.commons.beanutils (unlike eclipse's convention for booleans)
201        return m_isLogfile;
202    }
203
204    /**
205     * Returns the path denoting the root folder for all accessible files.<p>
206     *
207     * @return the path denoting the root folder for all accessible files
208     */
209    public String getRootPath() {
210
211        return m_rootPath;
212    }
213
214    /**
215     * Returns the start position of the current display.<p>
216     *
217     * This is a count of "windows" that
218     * consist of viewable text with "windowSize" lines of text (for a non-standard log file) or
219     * log-entries (for a standard log file).<p>
220     *
221     * @return the start position of the current display
222     */
223    public int getWindowPos() {
224
225        return m_windowPos;
226    }
227
228    /**
229     * Get the amount of lines (or entries depending on whether a standard log file is shown)
230     * to display per page. <p>
231     *
232     * @return the amount of lines to display per page
233     */
234    public int getWindowSize() {
235
236        return m_windowSize;
237    }
238
239    /**
240     * Returns true if this view upon the underlying file via
241     * <code>{@link #readFilePortion()}</code> is enabled.<p>
242     *
243     *
244     * @return true if this view upon the underlying file via
245     * <code>{@link #readFilePortion()}</code> is enabled.<p>
246     */
247    public boolean isEnabled() {
248
249        return m_enabled;
250    }
251
252    /**
253     * Return the view portion of lines of text from the underlying file or an
254     * empty String if <code>{@link #isEnabled()}</code> returns <code>false</code>.<p>
255     *
256     * @return the view portion of lines of text from the underlying file or an
257     *         empty String if <code>{@link #isEnabled()}</code> returns <code>false</code>
258     * @throws CmsRfsException if something goes wrong
259     */
260    public String readFilePortion() throws CmsRfsException {
261
262        if (m_enabled) {
263            // if we want to view the log file we have to set the internal m_windowPos to the last window
264            // to view the end:
265            int lines = -1;
266            int startLine;
267            if (m_isLogfile) {
268                lines = scrollToFileEnd();
269                // for logfile mode we show the last window of window size:
270                // it could be possible that only 4 lines are in the last window
271                // (e.g.: 123 lines with windowsize 10 -> last window has 3 lines)
272                // so we ignore the window semantics and show the n last lines:
273                startLine = lines - m_windowSize;
274            } else {
275                m_windowPos = 0;
276                startLine = m_windowPos * m_windowSize;
277            }
278            LineNumberReader reader = null;
279            try {
280                // don't make the buffer too big, just big enough for windowSize lines (estimation: avg. of 200 characters per line)
281                // to save reading too much (this optimizes to read the first windows, much later windows will be slower...)
282                reader = new LineNumberReader(
283                    new BufferedReader(new InputStreamReader(new FileInputStream(m_filePath), m_fileEncoding)),
284                    m_windowSize * 200);
285                int currentLine = 0;
286                // skip the lines to the current window:
287                while (startLine > currentLine) {
288                    reader.readLine();
289                    currentLine++;
290                }
291                StringBuffer result = new StringBuffer();
292                String read = reader.readLine();
293
294                for (int i = m_windowSize; (i > 0) && (read != null); i--) {
295                    result.append(read);
296                    result.append('\n');
297                    read = reader.readLine();
298                }
299
300                return CmsEncoder.escapeXml(result.toString());
301            } catch (IOException ioex) {
302                CmsRfsException ex = new CmsRfsException(
303                    Messages.get().container(Messages.ERR_FILE_ARG_ACCESS_1, m_filePath),
304                    ioex);
305                throw ex;
306            } finally {
307                if (reader != null) {
308                    try {
309                        reader.close();
310                    } catch (IOException e) {
311                        LOG.error(e.getLocalizedMessage(), e);
312                    }
313                }
314            }
315        } else {
316            return Messages.get().getBundle().key(Messages.GUI_FILE_VIEW_NO_PREVIEW_0);
317        }
318    }
319
320    /**
321     * Sets the additional root folders from which files can be viewed.<p>
322     *
323     * @param roots the list of additional root folders
324     */
325    public void setAdditionalRoots(List<String> roots) {
326
327        List<String> additionalRoots = new ArrayList<>();
328        // making sure all paths end with the path separator CHAR
329        for (String path : roots) {
330            if (path != null) {
331                if (!path.endsWith(File.separator)) {
332                    path += File.separator;
333                }
334            }
335            additionalRoots.add(path);
336        }
337        m_additionalRoots = additionalRoots;
338    }
339
340    /**
341     * Set the boolean that decides if the view to the underlying file via
342     * <code>{@link #readFilePortion()}</code> is enabled.<p>
343     *
344     * @param preview the boolean that decides if the view to the underlying file via
345     *        <code>{@link #readFilePortion()}</code> is enabled
346     */
347    public void setEnabled(boolean preview) {
348
349        m_enabled = preview;
350    }
351
352    /**
353     * Set the character encoding of the underlying file.<p>
354     *
355     * The given String has to match a valid char set name (canonical or alias)
356     * of one of the system's supported <code>{@link Charset}</code> instances
357     * (see <code>{@link Charset#forName(java.lang.String)}</code>).<p>
358     *
359     * This setting will be used for reading the file. This enables to correctly
360     * display files with text in various encodings in UIs.<p>
361     *
362     * @param fileEncoding the character encoding of the underlying file to set
363     */
364    public void setFileEncoding(String fileEncoding) {
365
366        checkFrozen();
367        try {
368            m_fileEncoding = Charset.forName(fileEncoding);
369        } catch (IllegalCharsetNameException icne) {
370            throw new CmsIllegalArgumentException(
371                Messages.get().container(Messages.ERR_CHARSET_ILLEGAL_NAME_1, fileEncoding));
372        } catch (UnsupportedCharsetException ucse) {
373            throw new CmsIllegalArgumentException(
374                Messages.get().container(Messages.ERR_CHARSET_UNSUPPORTED_1, fileEncoding));
375
376        }
377
378    }
379
380    /**
381     * Set the path in the real file system that points to the file
382     * that should be displayed.<p>
383     *
384     * This method will only success if the file specified by the <code>path</code>
385     * argument is valid within the file system, no folder and may be read by the
386     * OpenCms process on the current platform.<p>
387     *
388     * @param path the path in the real file system that points to the file that should be displayed to set
389     *
390     * @throws CmsRuntimeException if the configuration of this instance has been frozen
391     * @throws CmsRfsException if the given path is invalid, does not point to a file or cannot be accessed
392     */
393    public void setFilePath(String path) throws CmsRfsException, CmsRuntimeException {
394
395        checkFrozen();
396
397        if (path != null) {
398            // leading whitespace from CmsComboWidget causes exception
399            path = path.trim();
400        }
401        if (CmsStringUtil.isEmpty(path)) {
402            throw new CmsRfsException(
403                Messages.get().container(Messages.ERR_FILE_ARG_EMPTY_1, new Object[] {String.valueOf(path)}));
404        }
405        try {
406            // just for validation :
407            File file = new File(path);
408            if (file.isDirectory()) {
409                // if wrong configuration perform self healing:
410                if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
411                    // this deletes the illegal entry and will default to the log file path
412                    m_filePath = null;
413                    m_isLogfile = true;
414                } else {
415                    throw new CmsRfsException(
416                        Messages.get().container(
417                            Messages.ERR_FILE_ARG_IS_FOLDER_1,
418                            new Object[] {String.valueOf(path)}));
419                }
420            } else if (!file.isFile()) {
421                // if wrong configuration perform self healing:
422                if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
423                    // this deletes the illegal entry and will default to the log file path
424                    m_filePath = null;
425                    m_isLogfile = true;
426                } else {
427                    throw new CmsRfsException(
428                        Messages.get().container(
429                            Messages.ERR_FILE_ARG_NOT_FOUND_1,
430                            new Object[] {String.valueOf(path)}));
431                }
432
433            } else if (!file.canRead()) {
434                // if wrong configuration perform self healing:
435                if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
436                    // this deletes the illegal entry and will default to the log file path
437                    m_filePath = null;
438                    m_isLogfile = true;
439                } else {
440                    throw new CmsRfsException(
441                        Messages.get().container(
442                            Messages.ERR_FILE_ARG_NOT_READ_1,
443                            new Object[] {String.valueOf(path)}));
444                }
445            } else if ((m_rootPath != null) && !isInRoots(file.getCanonicalPath())) {
446                // if wrong configuration perform self healing:
447                if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
448                    // this deletes the illegal entry and will default to the log file path
449                    m_filePath = null;
450                    m_isLogfile = true;
451                } else {
452                    throw new CmsRfsException(
453                        Messages.get().container(
454                            Messages.ERR_FILE_ARG_NOT_READ_1,
455                            new Object[] {String.valueOf(path)}));
456                }
457            } else {
458                m_filePath = file.getCanonicalPath();
459            }
460        } catch (FileNotFoundException fnfe) {
461            // if wrong configuration perform self healing:
462            if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
463                // this deletes the illegal entry and will default to the log file path
464                m_filePath = null;
465                m_isLogfile = true;
466            } else {
467                throw new CmsRfsException(
468                    Messages.get().container(Messages.ERR_FILE_ARG_NOT_FOUND_1, new Object[] {String.valueOf(path)}),
469                    fnfe);
470            }
471        } catch (IOException ioex) {
472            // if wrong configuration perform self healing:
473            if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
474                // this deletes the illegal entry and will default to the log file path
475                m_filePath = null;
476                m_isLogfile = true;
477            } else {
478                throw new CmsRfsException(
479                    Messages.get().container(Messages.ERR_FILE_ARG_ACCESS_1, new Object[] {String.valueOf(path)}),
480                    ioex);
481            }
482
483        }
484    }
485
486    /**
487     * Package friendly access that allows the <code>{@link org.opencms.workplace.CmsWorkplaceManager}</code>
488     * to "freeze" this instance within the system-wide assignment in it's
489     * <code>{@link org.opencms.workplace.CmsWorkplaceManager#setFileViewSettings(org.opencms.file.CmsObject, CmsRfsFileViewer)}</code> method.<p>
490     *
491     * @param frozen if true this instance will freeze and throw <code>CmsRuntimeExceptions</code> upon setter invocations
492     *
493     * @throws CmsRuntimeException if the configuration of this instance has been frozen
494     *                             ({@link #setFrozen(boolean)})
495     */
496    public void setFrozen(boolean frozen) throws CmsRuntimeException {
497
498        m_frozen = frozen;
499    }
500
501    /**
502     * Set if the internal file is in standard log file format (true) or not (false).<p>
503     *
504     * If set to true the file might be
505     * treated / displayed in a more convenient format than standard files in future.
506     * Currently it is only inverted (last lines appear first) and only the last
507     * 'Window Size' lines of the file are displayed.<p>
508     *
509     * Do not activate this (it is possible from the log file viewer settings in the workplace
510     * administration) if your selected file is no log file: The display will confuse you and
511     * be more expensive (imaging scrolling a 20 MB file to view the last 200 lines). <p>
512     *
513     * @param isLogfile determines if the internal file is in standard log file format (true) or not (false)
514     *
515     * @throws CmsRuntimeException if the configuration of this instance has been frozen
516     *                             ({@link #setFrozen(boolean)})
517     */
518    public void setIsLogfile(boolean isLogfile) throws CmsRuntimeException {
519
520        checkFrozen();
521        m_isLogfile = isLogfile;
522    }
523
524    /**
525     * Set the path in the real file system that points to the folder/tree
526     * containing the log files.<p>
527     *
528     * This method will only success if the folder specified by the <code>path</code>
529     * argument is valid within the file system.<p>
530     *
531     * @param path the path in the real file system that points to the folder containing the log files
532     *
533     * @throws CmsRuntimeException if the configuration of this instance has been frozen
534     * @throws CmsRfsException if the given path is invalid
535     */
536    public void setRootPath(String path) throws CmsRfsException, CmsRuntimeException {
537
538        checkFrozen();
539
540        if (path != null) {
541            // leading whitespace from CmsComboWidget causes exception
542            path = path.trim();
543        }
544        if (CmsStringUtil.isEmpty(path)) {
545            throw new CmsRfsException(
546                Messages.get().container(Messages.ERR_FILE_ARG_EMPTY_1, new Object[] {String.valueOf(path)}));
547        }
548        try {
549            // just for validation :
550            File file = new File(path);
551            if (file.exists()) {
552                m_rootPath = file.getCanonicalPath();
553            } else {
554                // if wrong configuration perform self healing:
555                if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
556                    // this deletes the illegal entry
557                    m_rootPath = new File(OpenCms.getSystemInfo().getLogFileRfsPath()).getParent();
558                } else {
559
560                    throw new CmsRfsException(
561                        Messages.get().container(
562                            Messages.ERR_FILE_ARG_NOT_FOUND_1,
563                            new Object[] {String.valueOf(path)}));
564                }
565            }
566        } catch (IOException ioex) {
567            // if wrong configuration perform self healing:
568            if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
569                // this deletes the illegal entry and will default to the log file path
570                m_rootPath = new File(OpenCms.getSystemInfo().getLogFileRfsPath()).getParent();
571            } else {
572
573                throw new CmsRfsException(
574                    Messages.get().container(Messages.ERR_FILE_ARG_ACCESS_1, new Object[] {String.valueOf(path)}),
575                    ioex);
576            }
577        }
578    }
579
580    /**
581     * Sets the start position of the current display.<p>
582     *
583     * This is a count of "windows" that
584     * consist of viewable text with "windowSize" lines of text (for a non-standard log file) or
585     * log-entries (for a standard log file).<p>
586     *
587     * @param windowPos the start position of the current display to set
588     *
589     * @throws CmsRuntimeException if the configuration of this instance has been frozen
590     *                             ({@link #setFrozen(boolean)})
591     */
592    public void setWindowPos(int windowPos) throws CmsRuntimeException {
593
594        checkFrozen();
595        m_windowPos = windowPos;
596    }
597
598    /**
599     * Set the amount of lines (or entries depending on whether a standard log file is shown)
600     * to display per page.<p>
601     *
602     * @param windowSize the amount of lines to display per page
603     *
604     * @throws CmsRuntimeException if the configuration of this instance has been frozen
605     *                             ({@link #setFrozen(boolean)})
606     */
607    public void setWindowSize(int windowSize) throws CmsRuntimeException {
608
609        checkFrozen();
610        m_windowSize = windowSize;
611    }
612
613    /**
614     * Internal helper that throws a <code>{@link CmsRuntimeException}</code> if the
615     * configuration of this instance has been frozen ({@link #setFrozen(boolean)}).<p>
616     *
617     * @throws CmsRuntimeException if the configuration of this instance has been frozen
618     *                             ({@link #setFrozen(boolean)})
619     */
620    private void checkFrozen() throws CmsRuntimeException {
621
622        if (m_frozen) {
623            throw new CmsRuntimeException(Messages.get().container(Messages.ERR_FILE_VIEW_SETTINGS_FROZEN_0));
624        }
625    }
626
627    /**
628     * Reads the log folder RFS path.<p>
629     *
630     * @return the log folder path
631     */
632    private String getLogFolderPath() {
633
634        String path = null;
635        if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
636            path = new File(OpenCms.getSystemInfo().getLogFileRfsPath()).getParent();
637            // making sure the path ends with the file separator CHAR
638            if ((path != null) && !path.endsWith(File.separator)) {
639                path += File.separator;
640            }
641
642        }
643        return path;
644    }
645
646    /**
647     * Check if the given path is below any of the configured roots.<p>
648     *
649     * @param canonicalPath the path to check
650     * @return true if the path is below any of the configured roots
651     */
652    private boolean isInRoots(String canonicalPath) {
653
654        if (canonicalPath.startsWith(m_rootPath)) {
655            return true;
656        }
657        if (m_additionalRoots != null) {
658            for (String root : m_additionalRoots) {
659                if (canonicalPath.startsWith(root)) {
660                    return true;
661                }
662
663            }
664        }
665        return false;
666    }
667
668    /**
669     * Internally sets the member <code>m_windowPos</code> to the last available
670     * window of <code>m_windowSize</code> windows to let further calls to
671     * <code>{@link #readFilePortion()}</code> display the end of the file. <p>
672     *
673     * This method is triggered when a new file is chosen
674     * (<code>{@link #setFilePath(String)}</code>) because the amount of lines changes.
675     * This method is also triggered when a different window size is chosen
676     * (<code>{@link #setWindowSize(int)}</code>) because the amount of lines to display change.
677     *
678     * @return the amount of lines in the file to view
679     */
680    private int scrollToFileEnd() {
681
682        int lines = 0;
683        if (OpenCms.getRunLevel() < OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
684            // no scrolling if system not yet fully initialized
685        } else {
686            LineNumberReader reader = null;
687            // shift the window position to the end of the file: this is expensive but OK for ocs logfiles as they
688            // are ltd. to 2 MB
689            try {
690                reader = new LineNumberReader(
691                    new BufferedReader(new InputStreamReader(new FileInputStream(m_filePath))));
692                while (reader.readLine() != null) {
693                    lines++;
694                }
695                reader.close();
696                // if 11.75 windows are available, we don't want to end on window nr. 10
697                int availWindows = (int)Math.ceil((double)lines / (double)m_windowSize);
698                // we start with window 0
699                m_windowPos = availWindows - 1;
700            } catch (IOException ioex) {
701                LOG.error("Unable to scroll file " + m_filePath + " to end. Ensure that it exists. ");
702            } finally {
703                if (reader != null) {
704                    try {
705                        reader.close();
706                    } catch (Throwable f) {
707                        LOG.info("Unable to close reader of file " + m_filePath, f);
708                    }
709                }
710            }
711        }
712        return lines;
713    }
714
715}