savant.util.MiscUtils.java Source code

Java tutorial

Introduction

Here is the source code for savant.util.MiscUtils.java

Source

/**
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package savant.util;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.util.*;
import javax.swing.*;

import com.jidesoft.docking.DockableFrame;
import com.jidesoft.docking.DockingManager;
import net.sf.samtools.SAMRecord;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Various utility methods and constants of general usefulness.
 *
 * @author mfiume, tarkvara
 */
public class MiscUtils {

    public static final boolean MAC;
    public static final boolean WINDOWS;
    public static final boolean LINUX;
    public static final String UNSAVED_MARK = " *";

    /** OS-specific constant for determining menu-options. Either CTRL_MASK or META_MASK. */
    public static final int MENU_MASK;

    private static final Log LOG = LogFactory.getLog(MiscUtils.class);

    static {
        String os = System.getProperty("os.name").toLowerCase();
        MAC = os.startsWith("mac");
        WINDOWS = os.startsWith("windows");
        LINUX = os.contains("linux");
        MENU_MASK = MAC ? InputEvent.META_MASK : InputEvent.CTRL_MASK;
    }

    /** [[ Miscellaneous Functions ]] */
    /**
     * Format an integer to a string (adding commas)
     * @param num The number to format
     * @return A formatted string
     */
    public static String numToString(double num) {
        return numToString(num, 0);
    }

    public static String numToString(double num, int significantdigits) {
        String formatString = "###,###";

        if (significantdigits > 0) {
            formatString += ".";
            for (int i = 0; i < significantdigits; i++) {
                formatString += "#";
            }
        }

        DecimalFormat df = new DecimalFormat(formatString);
        return df.format(num);
    }

    /**
     * Get an integer from a string
     * @param str The string representing an integer (possibly with commas)
     * @return An integer
     */
    public static int stringToInt(String str) {
        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException e) {
            LOG.info(MiscUtils.getMessage(e));
            return -1;
        }
    }

    /**
     * Get a string representation of the the current time
     * @return A string representing the current time
     */
    public static String now() {
        Calendar cal = Calendar.getInstance();
        return DateFormat.getTimeInstance().format(cal.getTime());
    }

    /**
     * Remove the specified character from the given string.
     * @param s The string from which to remove the character
     * @param c The character to remove from the string
     * @return The string with the character removed
     */
    public static String removeChar(String s, char c) {
        String r = "";
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != c) {
                r += s.charAt(i);
            }
        }
        return r;
    }

    public static String getFilenameFromPath(String path) {
        int lastSlashIndex = path.lastIndexOf(System.getProperty("file.separator"));
        if (lastSlashIndex == -1) {
            lastSlashIndex = path.lastIndexOf("/");
        }
        return path.substring(lastSlashIndex + 1, path.length());
    }

    /**
     * Extract the extension from the given path.
     *
     * @param path The path from which to extract the extension
     * @return The extension of the file at the given path
     */
    public static String getExtension(String path) {
        int dotIndex = path.lastIndexOf(".");

        if (dotIndex == -1 || dotIndex == path.length() - 1) {
            return "";
        } else {
            return path.substring(dotIndex + 1);
        }
    }

    public static String getTemporaryDirectory() {
        String tmpDir;
        if (MAC || LINUX) {
            tmpDir = System.getenv("TMPDIR");
            if (tmpDir != null) {
                return tmpDir;
            } else {
                return "/tmp/savant";
            }
        } else {
            if ((tmpDir = System.getenv("TEMP")) != null) {
                return tmpDir;
            } else if ((tmpDir = System.getenv("TMP")) != null) {
                return tmpDir;
            } else {
                return System.getProperty("user.dir");
            }
        }
    }

    /**
     * Translate a pixel to a (genome) position
     * @param pixel The pixel to transform
     * @param widthOfComponent The width of the component in which the pixel occurs
     * @param positionalRange The (genome) range the component applies to
     * @return The position represented by the pixel
     */
    public static int transformPixelToPosition(int pixel, int widthOfComponent, Range positionalRange) {
        double positionsperpixel = ((double) positionalRange.getLength()) / widthOfComponent;
        return positionalRange.getFrom() + (int) Math.floor(positionsperpixel * pixel);
    }

    /**
     * Translate a (genome) position to a pixel
     * @param position genome position, first base is 1
     * @param widthOfComponent The width of the component in which the pixel occurs
     * @param positionalRange The (genome) range the component applies to
     * @return The pixel represented by the position
     */
    public static int transformPositionToPixel(int position, int widthOfComponent, Range positionalRange) {
        double pixelsperposition = ((double) widthOfComponent) / positionalRange.getLength();
        return (int) Math.round((position - positionalRange.getFrom()) * pixelsperposition);
    }

    public static List<String> set2List(Set<String> set) {
        List<String> l = new ArrayList<String>();
        for (String s : set) {
            l.add(s);
        }
        Collections.sort(l);
        return l;
    }

    public static String posToShortString(int genomepos) {
        return posToShortString(genomepos, 0);
    }

    public static String posToShortStringWithSeparation(int pos, int separation) {

        if (separation > 10) {
            int backdigits = (int) Math.floor(Math.log10(separation));
            int significantDigits = 0;
            if (pos > 1000000000) {
                significantDigits = 9 - backdigits;
            } else if (pos > 1000000) {
                significantDigits = 6 - backdigits;
            } else if (pos > 1000) {
                significantDigits = 3 - backdigits;
            }

            return posToShortString(pos, significantDigits);
        } else {
            // For separation of 10 or less, there's no point in using the G/M/k forms.
            return MiscUtils.numToString(pos);
        }
    }

    public static String posToShortString(int pos, int significantDigits) {
        String result;

        if (pos > 1000000000) {
            result = MiscUtils.numToString(pos / 1.0E9, significantDigits) + " G";
        } else if (pos > 1000000) {
            result = MiscUtils.numToString(pos / 1.0E6, significantDigits) + " M";
        } else if (pos > 1000) {
            result = MiscUtils.numToString(pos / 1000.0, significantDigits) + " k";
        } else {
            result = MiscUtils.numToString(pos, significantDigits);
        }

        return result;
    }

    public static int[] getTickPositions(double min, double max) {
        // The 0.35 is a factor which gives us roughly the right density of ticks at all magnifications.
        int log = (int) Math.floor(Math.log10(max - min) - 0.35);
        int step = log > 0 ? (int) Math.pow(10, log) : 1;
        int[] result = new int[(int) (max - min) / step + 1];
        int p0 = ((((int) min - 1) / step) + 1) * step;
        for (int i = 0; i < result.length; i++) {
            result[i] = p0;
            p0 += step;
        }
        return result;
    }

    /**
     * Given a range, return a reasonable set of tick positions for that range.
     */
    public static int[] getTickPositions(Range r) {
        return getTickPositions(r.getFrom(), r.getTo() + 1);
    }

    /**
     * Sometimes Throwable.getMessage() returns a useless string (e.g. "null" for a NullPointerException).
     * Return a string which is more meaningful to the end-user.
     */
    public static String getMessage(Throwable t) {
        if (t instanceof NullPointerException) {
            return "Null pointer exception";
        } else if (t instanceof FileNotFoundException) {
            return String.format("File %s not found", t.getMessage());
        } else if (t instanceof ArrayIndexOutOfBoundsException) {
            return "Array index out of bounds: " + t.getMessage();
        } else if (t instanceof OutOfMemoryError) {
            return "Out of memory: " + t.getMessage();
        } else if (t instanceof NumberFormatException) {
            String msg = t.getMessage();
            int quotePos = msg.indexOf('\"');
            if (quotePos > 0) {
                // Exception message is of form "For input string: \"foo\"".
                return String.format("Unable to interpret %s as a number", msg.substring(quotePos));
            }
            return msg;
        } else {
            if (t.getMessage() != null) {
                return t.getMessage();
            } else {
                return t.toString();
            }
        }
    }

    public static String getStackTrace(Throwable t) {
        if (t == null) {
            return "";
        }
        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        t.printStackTrace(printWriter);
        return result.toString();
    }

    /*
     * Return string without sequence title (chr, contig)
     */
    public static String homogenizeSequence(String s) {
        String result = s;
        if (result.contains("chr")) {
            result = result.replaceAll("chr", "");
        }
        if (result.contains("Chr")) {
            result = result.replaceAll("Chr", "");
        }
        if (result.contains("contig")) {
            result = result.replaceAll("contig", "");
        }
        if (result.contains("Contig")) {
            result = result.replaceAll("Contig", "");
        }
        return result;
    }

    public static void setFrameVisibility(String frameKey, boolean isVisible, DockingManager m) {
        DockableFrame f = m.getFrame(frameKey);
        if (isVisible) {
            m.showFrame(frameKey);
        } else {
            m.hideFrame(frameKey);
        }
    }

    public static double roundToSignificantDigits(double num, int n) {
        if (num == 0) {
            return 0;
        } else if (n == 0) {
            return Math.round(num);
        }

        String s = num + "";
        int index = s.indexOf(".");
        while (n >= s.length() - index) {
            s = s + "0";
        }
        return Double.parseDouble(s.substring(0, index + n + 1));
    }

    public static String getSophisticatedByteString(long bytes) {
        if (bytes < 1000) {
            return bytes + " KB";
        } else if (bytes < 1000000000) {
            return roundToSignificantDigits(((double) bytes / 1000000), 1) + " MB";
        } else {
            return roundToSignificantDigits(((double) bytes / 1000000000), 2) + " GB";
        }
    }

    /**
     * Set the title of a window to reflect whether it is saved or not.  On Windows
     * and Linux, this appends an asterisk to the title of an unsaved document; on
     * Mac, it puts a dot inside the close-button.
     * @param f
     * @param unsaved
     */
    public static void setUnsavedTitle(JFrame f, String title, boolean unsaved) {
        f.getRootPane().putClientProperty("Window.documentModified", unsaved);
        if (!MAC && unsaved) {
            f.setTitle(title + UNSAVED_MARK);
        } else {
            f.setTitle(title);
        }
    }

    /**
     * Register the escape key so that it can be used to cancel the associated JDialog.
     */
    public static void registerCancelButton(final JButton cancelButton) {
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JDialog dialog = (JDialog) SwingUtilities.getWindowAncestor(cancelButton);
        dialog.getRootPane().registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                cancelButton.doClick();
            }
        }, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
    }

    /**
     * Invoke the given runnable on the AWT event thread.
     *
     * @param r the action to be invoked
     */
    public static void invokeLaterIfNecessary(Runnable r) {
        if (EventQueue.isDispatchThread()) {
            r.run();
        } else {
            EventQueue.invokeLater(r);
        }
    }

    public static String reverseString(String str) {
        int strlen = str.length();
        char[] result = new char[strlen];
        for (int i = 1; i <= strlen; i++) {
            result[strlen - i] = str.charAt(i - 1);
        }
        return new String(result);
    }

    /**
     * If rec1 is likely a mate of rec2, return true.
     *
     * @param rec1 first record
     * @param rec2 second record
     * @param extraCheck if true, equality check is insufficient to avoid self-mating; check positions as well
     */
    public static boolean isMate(SAMRecord rec1, SAMRecord rec2, boolean extraCheck) {

        // If rec1 and rec2 came from the same source (e.g. the same call to getRecords),
        // an equality test is sufficient to avoid mating with ourselves.
        if (rec1 == rec2) {
            return false;
        }
        String name1 = rec1.getReadName();
        String name2 = rec2.getReadName();
        int len1 = name1.length();
        int len2 = name2.length();

        if (extraCheck) {
            // Check if names equal and coordinates match as expected.
            if (name1.equals(name2) && rec1.getMateAlignmentStart() == rec2.getAlignmentStart()
                    && rec1.getAlignmentStart() == rec2.getMateAlignmentStart()) {
                return true;
            }
        } else {
            // Check if names equal.
            if (name1.equals(name2)) {
                return true;
            }
        }

        //list of possible suffices...may grow over time.
        String[][] suffices = { { "\\1", "\\2" }, { "_F", "_R" }, { "_F3", "_R3" } };

        //check suffices
        for (String[] pair : suffices) {
            int len = pair[0].length(); //assumes both suffices of same length
            if (name1.substring(0, len1 - len).equals(name2.substring(0, len2 - len))
                    && ((name1.substring(len1 - len).equals(pair[0]) && name2.substring(len2 - len).equals(pair[1]))
                            || (name1.substring(len1 - len).equals(pair[1])
                                    && name2.substring(len2 - len).equals(pair[0]))))
                return true;
        }

        //not mates
        return false;
    }

    /**
     * Blend two colours, in the given proportions.  Resulting alpha is always 1.0.
     * @param col1 the first colour
     * @param col2 the second colour
     * @param weight1 the weight given to col1 (from 0.0-1.0)
     */
    public static Color blend(Color col1, Color col2, float weight1) {

        float weight2 = (1.0F - weight1) / 255;
        weight1 /= 255;

        // This constructor expects values from 0.0F to 1.0F, so weights have to be scaled appropriately.
        return new Color(col1.getRed() * weight1 + col2.getRed() * weight2,
                col1.getGreen() * weight1 + col2.getGreen() * weight2,
                col1.getBlue() * weight1 + col2.getBlue() * weight2);
    }

    /**
     * Utility method to create a polygonal path from a list of coordinates
     * @param coords a sequence of x,y coordinates (should be an even number and at least 4)
     */
    public static Path2D.Double createPolygon(double... coords) {
        if (coords.length < 4 || (coords.length & 1) != 0)
            throw new IllegalArgumentException("Invalid coordinates for createPolygon");

        Path2D.Double result = new Path2D.Double(Path2D.WIND_NON_ZERO, coords.length / 2);
        result.moveTo(coords[0], coords[1]);
        for (int i = 2; i < coords.length; i += 2) {
            result.lineTo(coords[i], coords[i + 1]);
        }
        result.closePath();
        return result;
    }

    /**
     * Patterned off GraphPane.drawMessageHelper, draws a string centred in the given box.
     */
    public static void drawMessage(Graphics2D g2, String message, Rectangle2D box) {
        FontMetrics metrics = g2.getFontMetrics();
        Rectangle2D stringBounds = g2.getFont().getStringBounds(message, g2.getFontRenderContext());
        float x = (float) (box.getX() + (box.getWidth() - stringBounds.getWidth()) / 2.0);
        float y = (float) (box.getY() + (box.getHeight() + metrics.getAscent() - metrics.getDescent()) / 2.0);

        g2.drawString(message, x, y);
    }
}