edu.harvard.mcz.imagecapture.ImageCaptureProperties.java Source code

Java tutorial

Introduction

Here is the source code for edu.harvard.mcz.imagecapture.ImageCaptureProperties.java

Source

/**
 * ImageCaptureProperties.java
 * edu.harvard.mcz.imagecapture
 * Copyright  2009 President and Fellows of Harvard College
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of Version 2 of the GNU General Public License
 * as published by the Free Software Foundation.
 *
 * 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: Paul J. Morris
 */
package edu.harvard.mcz.imagecapture;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Enumeration;
import java.util.Properties;

import javax.swing.table.AbstractTableModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.harvard.mcz.imagecapture.exceptions.NoSuchTemplateException;

/** Filesystem persistence and retrieval of properties for ImageCapture Application.
 * Includes constants for key names to use in properties file, and definition of default
 * values for properties to go with these keys if they aren't defined in the persistent file.
 * 
 * @author Paul J. Morris
 *
 */
public class ImageCaptureProperties extends AbstractTableModel {

    public static final String COLLECTION_MCZENT = "MCZ-ENT";
    public static final String COLLECTION_ETHZENT = "ETHZ-ENT";

    /**
     * The collection for which this deployment is configured to work with.
     */
    public static final String KEY_COLLECTION = "configuration.collection";
    /**
     * Value to use for the specific collection, empty string will use default for 
     * the configured KEY_COLLECTION. 
     */
    public static final String KEY_SPECIFIC_COLLECTION = "configuration.specificcollection";

    /**
     * The most recent location selected for scanning a barcode.
     */
    public static final String KEY_LASTPATH = "scanonebarcode.lastpath";

    /**
     * The most recent location selected for loading data from a file.
     */
    public static final String KEY_LASTLOADPATH = "fileload.lastpath";

    /**
     * Root of the path of the place where all image files should be stored.
     */
    public static final String KEY_IMAGEBASE = "images.basedirectory";
    /**
     * URI to the root of the path of the place where all image files should be stored, 
     * that is, the URI on the image server that points to the same location as 
     * KEY_IMAGEBASE does on the local filesystem.
     */
    public static final String KEY_IMAGEBASEURI = "images.basedirectoryurimap";
    /**
     * Regular expression that image files to be preprocessed must match.
     */
    public static final String KEY_IMAGEREGEX = "images.filenameregex";
    /**
     * Sizes to which to rescale width of unit tray label barcode to on retry of 
     * barcode read.  Value can be a comma delimited list of integers, or a comma
     * delimited list of integers and the words sharpen, brighter, dimmer, for 
     * example '400' or '400,500' or '400,400brighter,400sharpenbrighter,500dimmer'.
     */
    public static final String KEY_IMAGERESCALE = "images.barcoderescalesize";
    /**
     * If true, after scanning for a barcode in an image with zxing and failing to
     * find one, repeat with the zxing property TryHarder set to true, otherwise 
     * just try once with TryHarder set to false.  Setting to true will slow down 
     * preprocessing, but is more likely to find problematic barcodes.  The configured 
     * sequence is applied for every check for a barcode, including each individual 
     * entry in the images.barcodescalesize list.
     */
    public static final String KEY_IMAGEZXINGALSOTRYHARDER = "images.zxingalsotryharder";
    /**
     * PostitionTemplate to use by default (to try first).
     */
    public static final String KEY_TEMPLATEDEFAULT = "template.default";
    /**
     * The path and name of the tesseract executable for OCR failover.
     */
    public static final String KEY_TESSERACT_EXECUTABLE = "program.tesseract";
    /**
     * Path and executable for the ImageMagick program convert.
     */
    public static final String KEY_CONVERT_EXECUTABLE = "program.convert";
    /**
     * Path and executable for the ImageMagick program mogrify.  If blank, 
     * thumbnails will be generated using Java.
     */
    public static final String KEY_MOGRIFY_EXECUTABLE = "program.mogrify";
    /**
     * Default ImageMagick convert properties used for JPG to TIFF conversion to 
     * prepare a file for tesseract.
     */
    public static final String KEY_CONVERT_PARAMETERS = "convert.parameters";
    /**
     * Should the specimen details view pane have the scroll bars forced to be
     * turned on.   If value is VALUE_DETAILS_SCROLL_FORCE_ON, then they will 
     * be on.
     * 
     * @see #VALUE_DETAILS_SCROLL_FORCE_ON
     */
    public static final String KEY_DETAILS_SCROLL = "details.scroll";
    /**
     * Enable or disable the browse option on the main menu.  It is recommended
     * that browse be disabled in production deployments.
     */
    public static final String KEY_ENABLE_BROWSE = "browse.enabled";
    /**
     * The default value for preparation type (e.g. pinned).
     * 
     * @see edu.harvard.mcz.imagecapture.data.SpecimenPart.preserveMethod
     */
    public static final String KEY_DEFAULT_PREPARATION = "default.preparation";
    /**
     * How many characters need to be typed before a filtering select picklist will
     * start filtering on the entered string.
     */
    public static final String KEY_FILTER_LENGTH_THRESHOLD = "picklist.filterlength";
    /**
     * Should all other number types (select distinct on all values) be shown, or just
     * a controlled vocabulary of number types on the other number type picklist.
     */
    public static final String KEY_SHOW_ALL_NUMBER_TYPES = "numbertypes.showall";
    /**
     * Pixel height for generated thumbnail images.
     */
    public static final String KEY_THUMBNAIL_HEIGHT = "images.thumbnailheight";
    /**
     * Pixel width for generated thumbnail images.
     */
    public static final String KEY_THUMBNAIL_WIDTH = "images.thumbnailwidth";
    /**
     * Regular expression to identify drawer numbers in strings.
     */
    public static final String KEY_REGEX_DRAWERNUMBER = "images.regexdrawernumber";

    /**
     * Are images expected to contain the barcode number in exif or xmp metadata?
     */
    public static final String KEY_REDUNDANT_COMMENT_BARCODE = "images.metadatacontainsbarcode";

    /**
     *  Value for KEY_DETAILS_SCROLL that will cause the specimen details view pane to
     *  have scroll bars forced to be turned on.
     *   
     *  @see #KEY_DETAILS_SCROLL
     */
    public static final String VALUE_DETAILS_SCROLL_FORCE_ON = "on";
    /**
     * Show the login dialog with the advanced options open by default (desirable for 
     * developers working with development/test/production databases) if true.
     */
    public static final String KEY_LOGIN_SHOW_ADVANCED = "login.showadvanced";

    private static final Log log = LogFactory.getLog(ImageCaptureProperties.class);

    private static final long serialVersionUID = -8078524277278506689L;
    private Properties properties = null;
    private String propertiesFilename = null;
    private StringBuffer propertiesFilePath = null;

    public ImageCaptureProperties() {
        propertiesFilename = "imagecapture.properties";
        propertiesFilePath = new StringBuffer(System.getProperty("user.dir"));
        propertiesFilePath.append(System.getProperty("file.separator"));
        propertiesFilePath.append(propertiesFilename);
        System.out.println("Opening properties file: " + propertiesFilePath.toString());
        try {
            loadProperties();
        } catch (Exception e) {
            // thrown if properties can't be loaded, create default properties.
            properties = new Properties();
        }
        checkDefaults();
        testDefaultTemplate();
    }

    /** Given a File (which could be a directory path as a File object), return
     * the portion of the path to that file (directory) that is below the path 
     * described by KEY_IMAGEBASE.  
     * 
     * @param aFilename The file or directory (File object) from which to extract the path.
     * @return a string representation of a path from imagebase using the system 
     * path separator character.
     */
    public static String getPathBelowBase(File aFilename) {
        return getPathBelowBase(aFilename, File.separator);
    }

    /** 
     * Given a file, is that file inside the path described by ImageCaptureProperties.KEY_IMAGEBASE
     * 
     * @param aFile
     * @return true if aFile is inside imagebase, false otherwise.
     */
    public static boolean isInPathBelowBase(File aFile) {
        boolean result = false;
        String base = Singleton.getSingletonInstance().getProperties().getProperties()
                .getProperty(ImageCaptureProperties.KEY_IMAGEBASE);
        String filePath = aFile.getPath();

        if (aFile.isFile()) {
            filePath = aFile.getParent();
        }
        log.debug("Provided path to test: " + filePath);
        if (File.separator.equals("\\")) {
            if (!base.endsWith("\\")) {
                base = base + "\\";
            }
            // the separator "\" is represented in java as "\\" and in a java regular expression as "\\\\"
            base = base.replaceAll("\\\\", "\\\\\\\\");
            filePath = filePath.replaceAll("\\\\", "\\\\\\\\");
        } else {
            if (!base.endsWith("/")) {
                base = base + "/";
            }
            if (!filePath.endsWith("/")) {
                filePath = filePath + "/";
            }
        }
        log.debug("Base path for test: " + base);
        if (filePath.startsWith(base)) {
            result = true;
        }
        return result;
    }

    /** Warning: For unit testing only.  Do not invoke this method.  Always use getPathBelowBase(File aFilename) instead.
     *  
     * @see edu.harvard.mcz.imagecapture.ImageCaptureProperties#getPathBelowBase(File)
     */
    public static String getPathBelowBase(File aFilename, String fileSeparator) {
        String result = "";
        String base = Singleton.getSingletonInstance().getProperties().getProperties()
                .getProperty(ImageCaptureProperties.KEY_IMAGEBASE); // this is what we are going to strip off aFilename
        //String filename = "";  // name of file if aFilename is a file rather than a directory

        result = aFilename.getPath();
        log.debug("Local path to file: " + result);
        if (aFilename.isFile()) {
            result = aFilename.getParent();
        }

        if (fileSeparator.equals("\\")) {
            if (!base.endsWith("\\")) {
                base = base + "\\";
            }
            // the separator "\" is represented in java as "\\" and in a java regular expression as "\\\\"
            base = base.replaceAll("\\\\", "\\\\\\\\");
        } else {
            if (!base.endsWith("/")) {
                base = base + "/";
            }
            if (!result.endsWith("/")) {
                result = result + "/";
            }
        }
        log.debug("Base path to remove: " + base);
        // strip base out of canonical form of aFilename
        if (base.equals(result)) {
            result = "";
        } else {
            result = result.replaceFirst(base, "");
        }
        // make sure that path ends with fileSeparator
        if (!result.endsWith(fileSeparator)) {
            result = result + fileSeparator;
        }

        // if result is only a separator set it to an empty string
        if (fileSeparator.equals("\\")) {
            if (result.equals("\\")) {
                result = "";
            }
        } else {
            if (result.equals("/")) {
                result = "";
            }
        }

        log.debug("Path below basepath: " + result);

        return result;
    }

    /**
     * Given a path from the image base (property ImageCaptureProperties.KEY_IMAGEBASE)
     * and a filename, returns the full path to that file, including the image base
     * using the file separators for the current system.  For "images/testimages/" and 
     * "imagefile.jpg" returns a string like "/mount/lepidoptera/images/testimages/imagefile.jpg"
     * or "Z:\\lepidoptera\images\testimages\imagefile.jpg"
     * 
     * @param aDirectoryPath 
     * @param aFileName
     * @return String containing the full path to the file
     */
    public static String assemblePathWithBase(String aDirectoryPath, String aFileName) {
        return assemblePathWithBase(aDirectoryPath, aFileName, File.separator);
    }

    /**
     * Warning: For unit testing only.  Do not invoke this method.  Use assemblePathWithBase(String aDirectoryPath, String aFileName) instead.
     * @see edu.harvard.mcz.imagecapture.ImageCaptureProperties#assemblePathWithBase(String, String)
     */
    public static String assemblePathWithBase(String aDirectoryPath, String aFileName, String fileSeparator) {
        String result = "";
        String base = Singleton.getSingletonInstance().getProperties().getProperties()
                .getProperty(ImageCaptureProperties.KEY_IMAGEBASE);
        String path = aDirectoryPath;
        // First, correct the aDirectoryPath to the local file separator.
        //log.debug("File separator = '" + fileSeparator + "'");
        //log.debug(path);
        //log.debug(base);
        if (fileSeparator.equals("/")) {
            // unix filesystem
            path = path.replaceAll("\\\\", "/");
        } else {
            // windows filesystem
            path = path.replaceAll("/", "\\\\");
        }
        // Second, if base path doesn't end with a file separator, add one.
        if (!base.endsWith(fileSeparator)) {
            base = base + fileSeparator;
        }
        // Third, assemble the components. 
        if (path.endsWith(fileSeparator)) {
            result = base + path + aFileName;
        } else {
            result = base + path + fileSeparator + aFileName;
        }
        log.debug(result);
        return result;
    }

    /* (non-Javadoc)
     * @see javax.swing.table.AbstractTableModel#getColumnClass(int)
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    /* (non-Javadoc)
     * @see javax.swing.table.AbstractTableModel#getColumnName(int)
     */
    @Override
    public String getColumnName(int column) {
        String returnValue = "";
        if (column == 0) {
            returnValue = "Key";
        }
        if (column == 1) {
            returnValue = "Property value";
        }
        return returnValue;
    }

    /* (non-Javadoc)
     * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int)
     */
    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        boolean returnValue = false;
        if (columnIndex == 1) {
            returnValue = true;
        }
        return returnValue;
    }

    /* (non-Javadoc)
     * @see javax.swing.table.AbstractTableModel#setValueAt(java.lang.Object, int, int)
     */
    @Override
    public void setValueAt(Object value, int rowIndex, int columnIndex) {
        if (columnIndex == 1) {
            Enumeration<Object> p = properties.keys();
            int element = 0;
            while (p.hasMoreElements()) {
                String e = (String) p.nextElement();
                if (element == rowIndex) {
                    properties.setProperty(e, (String) value);
                }
                element++;
            }
        }
    }

    /**
     * Test to see if the KEY_TEMPLATEDEFAULT matches a valid PositionTemplate.
     * Change to PositionTemplate.TEMPLATE_DEFAULT and Log error if it does not.
     * Note: if the KEY_TEMPLATEDEFAULT property does not match a hardcoded 
     * PositionTemplate, a database lookup will be triggered and, if the request
     * is being made at application launch, a login dialog will be launched.
     * 
     * Take no action if there is no match to the KEY_TEMPLATEDEFAULT
     * 
     * @return true if template in properties exists, false if no match to key or 
     * if template was changed.    
     */
    private boolean testDefaultTemplate() {
        boolean result = false;
        if (properties.containsKey(KEY_TEMPLATEDEFAULT)) {
            String templateId = properties.getProperty(KEY_TEMPLATEDEFAULT);
            try {
                PositionTemplate template = new PositionTemplate(templateId);
                template.getClass(); // added to suppress findbugs DLS_DEAD_LOCAL_STORE
                // no exception thrown, this template is OK.
                result = true;
            } catch (NoSuchTemplateException e) {
                // Template isn't recognized, set property to default template.
                properties.setProperty(KEY_TEMPLATEDEFAULT, PositionTemplate.TEMPLATE_DEFAULT);
            }
        }
        return result;
    }

    /** Make sure required properties are present as keys, if they aren't add
     * them with default values.  This is where the default properties are defined.
     * 
     */
    private void checkDefaults() {
        if (!properties.containsKey(KEY_SPECIFIC_COLLECTION)) {
            // location in collection to use, if not default provided from KEY_COLLECTION
            // in LocationInCollection.
            properties.setProperty(KEY_SPECIFIC_COLLECTION, "");
        }
        if (!properties.containsKey(KEY_COLLECTION)) {
            // Root of the path of the place where all image files should be stored.
            properties.setProperty(KEY_COLLECTION, ImageCaptureProperties.COLLECTION_MCZENT);
        } else {
            switch (properties.get(KEY_COLLECTION).toString().trim()) {
            case (ImageCaptureProperties.COLLECTION_ETHZENT):
                log.debug("Configured for " + ImageCaptureProperties.COLLECTION_ETHZENT);
                break;
            case (ImageCaptureProperties.COLLECTION_MCZENT):
                log.debug("Configured for " + ImageCaptureProperties.COLLECTION_MCZENT);
                break;
            default:
                log.error("Unrecognized collection: " + properties.get(KEY_COLLECTION).toString());
                log.error("Allowed values for " + ImageCaptureProperties.KEY_COLLECTION + " are "
                        + ImageCaptureProperties.COLLECTION_MCZENT + " or "
                        + ImageCaptureProperties.COLLECTION_ETHZENT);
            }
        }
        if (!properties.containsKey(KEY_IMAGEBASE)) {
            // Root of the path of the place where all image files should be stored.
            properties.setProperty(KEY_IMAGEBASE, "/mount/lepidopteraimages");
        }
        if (!properties.containsKey(KEY_IMAGEBASEURI)) {
            // URI to the root of the path of the place where all image files should be stored.
            properties.setProperty(KEY_IMAGEBASEURI, "http://mczbase.mcz.harvard.edu/specimen_images/");
        }
        if (!properties.containsKey(KEY_IMAGEREGEX)) {
            // Regular expression to identify image filenames for processing.
            properties.setProperty(KEY_IMAGEREGEX, ImageCaptureApp.REGEX_IMAGEFILE);
        }
        if (!properties.containsKey(KEY_IMAGERESCALE)) {
            // Sizes to which to rescale width of unit tray label barcode to on retry.
            properties.setProperty(KEY_IMAGERESCALE, "400,600sharpen,600brighter,600dimmer,400sharpenbrighter");
        }
        if (!properties.containsKey(KEY_IMAGEZXINGALSOTRYHARDER)) {
            // Default value for choosing whether or not to also try harder with xzing.
            properties.setProperty(KEY_IMAGEZXINGALSOTRYHARDER, "true");
        }
        if (!properties.containsKey(KEY_TEMPLATEDEFAULT)) {
            // PostitionTemplate to use by default
            properties.setProperty(KEY_TEMPLATEDEFAULT, PositionTemplate.TEMPLATE_DEFAULT);
        }
        if (!properties.containsKey(KEY_TESSERACT_EXECUTABLE)) {
            // name of the tesseract executable, probably tesseract on unix, tesseract.exe on windows
            properties.setProperty(KEY_TESSERACT_EXECUTABLE, "tesseract ");
        }
        if (!properties.containsKey(KEY_CONVERT_EXECUTABLE)) {
            properties.setProperty(KEY_CONVERT_EXECUTABLE, "convert ");
        }
        if (!properties.containsKey(KEY_MOGRIFY_EXECUTABLE)) {
            properties.setProperty(KEY_MOGRIFY_EXECUTABLE, "mogrify ");
        }
        if (!properties.containsKey(KEY_CONVERT_PARAMETERS)) {
            // default ImageMagick convert properties used for JPG to TIFF conversion to 
            // prepare a file for tesseract.
            properties.setProperty(KEY_CONVERT_PARAMETERS, " -depth 8 -compress None -type Grayscale ");
        }
        if (!properties.containsKey(KEY_DETAILS_SCROLL)) {
            // default value is no scroll bars for SpecimenDetailsViewPane.
            properties.setProperty(KEY_DETAILS_SCROLL, "none");
        }
        if (!properties.containsKey(KEY_ENABLE_BROWSE)) {
            // default value is disabled browse on main menu.
            properties.setProperty(KEY_ENABLE_BROWSE, "false");
        }
        if (!properties.containsKey(KEY_DEFAULT_PREPARATION)) {
            // default preparation type
            properties.setProperty(KEY_DEFAULT_PREPARATION, "pinned");
        }
        if (!properties.containsKey(KEY_FILTER_LENGTH_THRESHOLD)) {
            // default value is disabled browse on main menu.
            properties.setProperty(KEY_FILTER_LENGTH_THRESHOLD, "3");
        }
        if (!properties.containsKey(KEY_SHOW_ALL_NUMBER_TYPES)) {
            // default value is disabled browse on main menu.
            properties.setProperty(KEY_SHOW_ALL_NUMBER_TYPES, "false");
        }
        if (!properties.containsKey(KEY_THUMBNAIL_HEIGHT)) {
            // default value is 120 pixels.
            properties.setProperty(KEY_THUMBNAIL_HEIGHT, "120");
        }
        if (!properties.containsKey(KEY_THUMBNAIL_WIDTH)) {
            // default value is 120 pixels.
            properties.setProperty(KEY_THUMBNAIL_WIDTH, "80");
        }
        if (!properties.containsKey(KEY_REGEX_DRAWERNUMBER)) {
            // default value is 120 pixels.
            properties.setProperty(KEY_REGEX_DRAWERNUMBER, ImageCaptureApp.REGEX_DRAWERNUMBER);
        }
        if (!properties.containsKey(KEY_REDUNDANT_COMMENT_BARCODE)) {
            // default value is that images are expected to contain the barcode number
            // in both the image and in its metadata.  
            properties.setProperty(KEY_REDUNDANT_COMMENT_BARCODE, "true");
        }
        if (!properties.containsKey(KEY_LOGIN_SHOW_ADVANCED)) {
            // default value is closed advanced options (server) on login dialog.
            properties.setProperty(KEY_LOGIN_SHOW_ADVANCED, "false");
        }

    }

    /* Place where properties in this instance are persisted.
     *  
     * @returns a text string representing the storage location from which this instance of
     * properties was loaded such ast the path and filename of the file from which the values for
     * this instance of properties was retrieved.
     */
    public String getPropertiesSource() {
        return propertiesFilePath.toString();
    }

    protected void loadProperties() throws Exception {
        properties = new Properties();
        FileInputStream propertiesStream = null;
        try {
            propertiesStream = new FileInputStream(propertiesFilePath.toString());
            properties.load(propertiesStream);
            // Test to see if all properties are set in the loaded file
            checkDefaults();
        } catch (FileNotFoundException e) {
            System.out.println("Error: Properties file not found.");
            throw e;
        } catch (Exception ex) {
            System.out.println("Error loading properties.");
            System.out.println(ex.getMessage());
            throw ex;
        } finally {
            if (propertiesStream != null) {
                propertiesStream.close();
            }
        }
    }

    public void saveProperties() throws Exception {
        FileOutputStream propertiesStream = null;
        try {
            System.out.println("Saving properties file: " + propertiesFilePath.toString());
            propertiesStream = new FileOutputStream(propertiesFilePath.toString());
            properties.store(propertiesStream,
                    ImageCaptureApp.APP_NAME + " " + ImageCaptureApp.APP_VERSION + " Properties");
            propertiesStream.close();
        } catch (Exception e) {
            System.out.println("Error saving properties.");
            System.out.println(e.getMessage());
            e.printStackTrace();
            throw e;
        } finally {
            if (propertiesStream != null) {
                propertiesStream.close();
            }
        }
    }

    public Properties getProperties() {
        return properties;
    }

    @Override
    public int getColumnCount() {
        return 2;
    }

    @Override
    public int getRowCount() {
        return properties.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        String value = "";
        Enumeration<Object> p = properties.keys();
        int element = 0;
        while (p.hasMoreElements()) {
            String e = (String) p.nextElement();
            if (element == rowIndex) {
                if (columnIndex == 0) {
                    value = e;
                } else {
                    value = properties.getProperty(e);
                }
            }
            element++;
        }
        return value;
    }

}