Java tutorial
/* * JStock - Free Stock Market Software * Copyright (C) 2015 Yan Cheng Cheok <yccheok@yahoo.com> * * 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 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.yccheok.jstock.gui; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.http.FileContent; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpResponse; import com.google.api.services.drive.Drive; import com.google.api.services.drive.Drive.Files; import com.google.api.services.drive.model.FileList; import com.google.api.services.drive.model.ParentReference; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.InstanceCreator; import com.google.gson.reflect.TypeToken; import com.thoughtworks.xstream.XStream; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Container; import java.awt.Desktop; import java.awt.Dimension; import java.awt.Font; import java.awt.FontFormatException; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.Image; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.PixelGrabber; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.MessageFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.EnumMap; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineListener; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JList; import javax.swing.JRadioButton; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.HyperlinkEvent; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.plaf.basic.BasicComboPopup; import net.sourceforge.pinyin4j.PinyinHelper; import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.lang.CharUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdesktop.jxlayer.JXLayer; import org.yccheok.jstock.analysis.Connection; import org.yccheok.jstock.analysis.DoubleConstantOperator; import org.yccheok.jstock.analysis.EqualityOperator; import org.yccheok.jstock.analysis.Indicator; import org.yccheok.jstock.analysis.OperatorIndicator; import org.yccheok.jstock.analysis.SinkOperator; import org.yccheok.jstock.analysis.StockOperator; import org.yccheok.jstock.engine.*; import org.yccheok.jstock.internationalization.MessagesBundle; import org.yccheok.jstock.network.Utils.Type; /** * * @author yccheok */ public class Utils { /** Creates a new instance of Utils */ private Utils() { } public static boolean isNullOrEmpty(String string) { return string == null || string.trim().isEmpty(); } public static void updateFactoriesPriceSource() { for (Country country : Country.values()) { final PriceSource priceSource = JStock.instance().getJStockOptions().getPriceSource(country); Factories.INSTANCE.updatePriceSource(country, priceSource); } } /** * Returns true if there are specified language files designed for this * locale. As in Java, when there are no specified language files for a * locale, a default language file will be used. * * @param locale the locale * @return true if there are specified language files designed for this * locale */ public static boolean hasSpecifiedLanguageFile(Locale locale) { // Please revise Statement's construct code, when adding in new language. // So that its language guessing algorithm will work as it is. if (Utils.isTraditionalChinese(locale)) { return true; } else if (Utils.isSimplifiedChinese(locale)) { return true; } else if (locale.getLanguage().equals(Locale.GERMAN.getLanguage())) { return true; } else if (locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { return true; } else if (locale.getLanguage().equals(Locale.ITALIAN.getLanguage())) { return true; } else if (locale.getLanguage().equals(Locale.FRENCH.getLanguage())) { return true; } return false; } /** * Adjust popup for combo box, so that horizontal scrollbar will not display. * http://forums.oracle.com/forums/thread.jspa?messageID=8037483� * http://www.camick.com/java/source/BoundsPopupMenuListener.java * * Update : According to https://forums.oracle.com/forums/thread.jspa?messageID=9789603#9789603 * , the above techniques is longer workable. * ========================================================================= * 6u25 changed when popupMenuWillBecomeVisible is called: it is now called * before the list is created so you can add items in that method and still * have the list size correctly. * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4743225 * So for your workaround: either it isn't needed anymore or you need to add * an extra hierarchy listener to check when the list is actually added. * ========================================================================= * * I use a quick hack from * http://javabyexample.wisdomplug.com/java-concepts/34-core-java/59-tips-and-tricks-for-jtree-jlist-and-jcombobox-part-i.html * * @param comboBox The combo box */ public static void adjustPopupWidth(JComboBox comboBox) { if (comboBox.getItemCount() == 0) return; Object comp = comboBox.getAccessibleContext().getAccessibleChild(0); if (!(comp instanceof BasicComboPopup)) { return; } BasicComboPopup popup = (BasicComboPopup) comp; JList list = popup.getList(); JScrollPane scrollPane = getScrollPane(popup); // Just to be paranoid enough. if (list == null || scrollPane == null) { return; } // Determine the maximimum width to use: // a) determine the popup preferred width // b) ensure width is not less than the scroll pane width int popupWidth = list.getPreferredSize().width + 5 // make sure horizontal scrollbar doesn't appear + getScrollBarWidth(comboBox, scrollPane); Dimension scrollPaneSize = scrollPane.getPreferredSize(); //popupWidth = Math.max(popupWidth, scrollPaneSize.width); // Use comboBox.getSize(), since we realize under Linux's Java 6u25, // After expanding, scrollPane.getPreferredSize() will return expanded // size in the 2nd round, although no expand is required. popupWidth = Math.max(popupWidth, comboBox.getSize().width); // Adjust the width scrollPaneSize.width = popupWidth; scrollPane.setPreferredSize(scrollPaneSize); scrollPane.setMaximumSize(scrollPaneSize); // The above workaround is no longer working. Use the below hack code! if (comboBox instanceof JComboBoxPopupAdjustable) { ((JComboBoxPopupAdjustable) comboBox).setPopupWidth(popupWidth); } } /* * I can't find any property on the scrollBar to determine if it will be * displayed or not so use brute force to determine this. */ private static int getScrollBarWidth(JComboBox comboBox, JScrollPane scrollPane) { int scrollBarWidth = 0; if (comboBox.getItemCount() > comboBox.getMaximumRowCount()) { JScrollBar vertical = scrollPane.getVerticalScrollBar(); scrollBarWidth = vertical.getPreferredSize().width; } return scrollBarWidth; } /* * Get the scroll pane used by the popup so its bounds can be adjusted */ private static JScrollPane getScrollPane(BasicComboPopup popup) { JList list = popup.getList(); Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, list); return (JScrollPane) c; } /** * Restart the application. * * There are some important aspects to have in mind for this code: * + The application's main class must be in a jar file. mainFrame * must be an instance of any class inside the same jar file (could be the * main class too). * + The called java VM will be the same that the application is currently * running on. * + There is no special error checking: the java VM may return an error like * class not found or jar not found, and it will not be caught by the code * posted above. * * The function will never return if it doesn't catch an error. It would be * a good practice to close all the handlers that could conflict with the * 'duplicate' new application before calling restartApplication(). There * will be a small time (which depends on many factors) where both * applications will be running at the same time. * * @param mainFrame One and only one mainFrame * @return true if restart success */ public static boolean restartApplication(JStock mainFrame) { String javaBin = System.getProperty("java.home") + "/bin/javaw"; File jarFile; try { jarFile = new File(mainFrame.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); } catch (Exception e) { log.error(null, e); return false; } /* is it a jar file or exe file? */ if (!jarFile.getName().endsWith(".jar") && !jarFile.getName().endsWith(".exe")) { //no, it's a .class probably return false; } String toExec[] = null; if (jarFile.getName().endsWith(".exe")) { toExec = new String[] { jarFile.getPath() }; } else { toExec = new String[] { javaBin, "-jar", jarFile.getPath() }; } // Before launching new JStock, save all the application settings. mainFrame.save(); // Before launching new JStock, remember to remove app lock. AppLock.unlock(); try { Process p = Runtime.getRuntime().exec(toExec); } catch (Exception e) { log.error(null, e); return false; } // And close the old's if new JStock launched successfully. mainFrame.setVisible(false); mainFrame.dispose(); System.exit(0); return true; } // Get timestamp (in ms) information from Google server. Returns 0 if // invalid. public static long getGoogleServerTimestamp() { final String _time = org.yccheok.jstock.gui.Utils .getUUIDValue(org.yccheok.jstock.network.Utils.getURL(Type.GET_TIME), "time"); if (_time == null) { return 0; } try { final long time = Long.parseLong(_time); return time; } catch (NumberFormatException exp) { log.error(null, exp); } return 0; } public static String toEndWithFileSeperator(String string) { if (string.endsWith(File.separator)) { return string; } return string + File.separator; } public static boolean extractZipFile(String zipFilePath, boolean overwrite) { return extractZipFile(new File(zipFilePath), overwrite); } public static boolean extractZipFile(File zipFilePath, boolean overwrite) { return extractZipFile(zipFilePath, Utils.getUserDataDirectory(), overwrite); } public static boolean extractZipFile(File zipFilePath, String destDirectory, boolean overwrite) { InputStream inputStream = null; ZipInputStream zipInputStream = null; boolean status = true; try { inputStream = new FileInputStream(zipFilePath); zipInputStream = new ZipInputStream(inputStream); final byte[] data = new byte[1024]; while (true) { ZipEntry zipEntry = null; FileOutputStream outputStream = null; try { zipEntry = zipInputStream.getNextEntry(); if (zipEntry == null) { break; } final String destination; if (destDirectory.endsWith(File.separator)) { destination = destDirectory + zipEntry.getName(); } else { destination = destDirectory + File.separator + zipEntry.getName(); } if (overwrite == false) { if (Utils.isFileOrDirectoryExist(destination)) { continue; } } if (zipEntry.isDirectory()) { Utils.createCompleteDirectoryHierarchyIfDoesNotExist(destination); } else { final File file = new File(destination); // Ensure directory is there before we write the file. Utils.createCompleteDirectoryHierarchyIfDoesNotExist(file.getParentFile()); int size = zipInputStream.read(data); if (size > 0) { outputStream = new FileOutputStream(destination); do { outputStream.write(data, 0, size); size = zipInputStream.read(data); } while (size >= 0); } } } catch (IOException exp) { log.error(null, exp); status = false; break; } finally { org.yccheok.jstock.file.Utils.close(outputStream); org.yccheok.jstock.file.Utils.closeEntry(zipInputStream); } } // while(true) } catch (IOException exp) { log.error(null, exp); status = false; } finally { org.yccheok.jstock.file.Utils.close(zipInputStream); org.yccheok.jstock.file.Utils.close(inputStream); } return status; } // A value obtained from server, to ensure all JStock's users are getting // same value for same key. // Note that, key "id" is a reserved word. public static String getUUIDValue(String url, String key) { final org.yccheok.jstock.gui.Utils.InputStreamAndMethod inputStreamAndMethod = org.yccheok.jstock.gui.Utils .getResponseBodyAsStreamBasedOnProxyAuthOption(url); if (inputStreamAndMethod.inputStream == null) { inputStreamAndMethod.method.releaseConnection(); return null; } Properties properties = new Properties(); try { properties.load(inputStreamAndMethod.inputStream); } catch (IOException exp) { log.error(null, exp); return null; } catch (IllegalArgumentException exp) { log.error(null, exp); return null; } finally { org.yccheok.jstock.file.Utils.close(inputStreamAndMethod.inputStream); inputStreamAndMethod.method.releaseConnection(); } final String _id = properties.getProperty("id"); if (_id == null) { log.info("UUID not found"); return null; } final String id = org.yccheok.jstock.gui.Utils.decrypt(_id); if (id.equals(org.yccheok.jstock.gui.Utils.getJStockUUID()) == false) { log.info("UUID doesn't match"); return null; } final String value = properties.getProperty(key); if (value == null) { log.info("Value not found"); return null; } return value; } public static Map<String, String> getUUIDValue(String url) { Map<String, String> map = new HashMap<String, String>(); final org.yccheok.jstock.gui.Utils.InputStreamAndMethod inputStreamAndMethod = org.yccheok.jstock.gui.Utils .getResponseBodyAsStreamBasedOnProxyAuthOption(url); if (inputStreamAndMethod.inputStream == null) { inputStreamAndMethod.method.releaseConnection(); return java.util.Collections.emptyMap(); } Properties properties = new Properties(); try { properties.load(inputStreamAndMethod.inputStream); } catch (IOException exp) { log.error(null, exp); return java.util.Collections.emptyMap(); } catch (IllegalArgumentException exp) { log.error(null, exp); return java.util.Collections.emptyMap(); } finally { org.yccheok.jstock.file.Utils.close(inputStreamAndMethod.inputStream); inputStreamAndMethod.method.releaseConnection(); } final String _id = properties.getProperty("id"); if (_id == null) { log.info("UUID not found"); return java.util.Collections.emptyMap(); } final String id = org.yccheok.jstock.gui.Utils.decrypt(_id); if (id.equals(org.yccheok.jstock.gui.Utils.getJStockUUID()) == false) { log.info("UUID doesn't match"); return java.util.Collections.emptyMap(); } for (Object key : properties.keySet()) { // "id" is a reserved word. Ignore it! if (key != null && !key.equals("id")) { map.put(key.toString(), properties.getProperty(key.toString())); } } return map; } /** * Returns <code>ZipEntry</code> which is usable in both Linux and Windows. * * @param zipEntryName zip entry name * @return <code>ZipEntry</code> which is usable in both Linux and Windows */ public static ZipEntry getZipEntry(String zipEntryName) { // Linux will not able to recognized File.seperator from Windows. // Change all to "/", which will be recognized by both Linux and Windows. return new ZipEntry(zipEntryName.replace(File.separator, "/")); } private static List<String> getNTPServers() { // The list is obtained from Windows Vista, Internet Time Server List itself. // The complete server list can be obtained from http://tf.nist.gov/tf-cgi/servers.cgi final List<String> defaultServer = java.util.Collections.unmodifiableList( java.util.Arrays.asList("time-a.nist.gov", "time-b.nist.gov", "time-nw.nist.gov")); List<String> servers = Utils.NTPServers; if (servers != null) { // We already have the server list. return servers; } final String server = getUUIDValue(org.yccheok.jstock.network.Utils.getURL(Type.NTP_SERVER_TXT), "server"); if (server != null) { String[] s = server.split(","); if (s.length > 0) { List<String> me = java.util.Collections.unmodifiableList(java.util.Arrays.asList(s)); // Save it! So that we need not to ask for server list again next time. Utils.NTPServers = me; return me; } } // Use default servers, so that we need not to ask for server list again next time. Utils.NTPServers = defaultServer; return defaultServer; } public static void launchWebBrowser(String address) { if (Desktop.isDesktopSupported()) { final Desktop desktop = Desktop.getDesktop(); if (desktop.isSupported(Desktop.Action.BROWSE)) { URL url = null; String string = address; try { url = new URL(string); } catch (MalformedURLException ex) { return; } try { desktop.browse(url.toURI()); } catch (URISyntaxException ex) { } catch (IOException ex) { } } } } public static void launchWebBrowser(javax.swing.event.HyperlinkEvent evt) { if (HyperlinkEvent.EventType.ACTIVATED.equals(evt.getEventType())) { URL url = evt.getURL(); if (Desktop.isDesktopSupported()) { final Desktop desktop = Desktop.getDesktop(); if (desktop.isSupported(Desktop.Action.BROWSE)) { if (url == null) { // www.yahoo.com considered an invalid URL. Hence, evt.getURL() returns null. String string = "http://" + evt.getDescription(); try { url = new URL(string); } catch (MalformedURLException ex) { return; } } try { desktop.browse(url.toURI()); } catch (URISyntaxException ex) { } catch (IOException ex) { } } } } } public static java.awt.Image getScaledImage(Image image, int maxWidth, int maxHeight) { // This code ensures that all the pixels in the image are loaded image = new ImageIcon(image).getImage(); final int imgWidth = image.getWidth(null); final int imgHeight = image.getHeight(null); final int preferredWidth = Math.min(imgWidth, maxWidth); final int preferredHeight = Math.min(imgHeight, maxHeight); final double scaleX = (double) preferredWidth / (double) imgWidth; final double scaleY = (double) preferredHeight / (double) imgHeight; final double bestScale = Math.min(scaleX, scaleY); return image.getScaledInstance((int) ((double) imgWidth * bestScale), (int) ((double) imgHeight * bestScale), Image.SCALE_SMOOTH); } // This method returns true if the specified image has transparent pixels private static boolean hasAlpha(Image image) { // If buffered image, the color model is readily available if (image instanceof BufferedImage) { BufferedImage bimage = (BufferedImage) image; return bimage.getColorModel().hasAlpha(); } // Use a pixel grabber to retrieve the image's color model; // grabbing a single pixel is usually sufficient PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); try { pg.grabPixels(); } catch (InterruptedException e) { } // Get the image's color model ColorModel cm = pg.getColorModel(); return cm.hasAlpha(); } // This method returns a buffered image with the contents of an image public static BufferedImage toBufferedImage(Image image) { if (image instanceof BufferedImage) { return (BufferedImage) image; } // This code ensures that all the pixels in the image are loaded image = new ImageIcon(image).getImage(); // Determine if the image has transparent pixels; for this method's // implementation, see e661 Determining If an Image Has Transparent Pixels boolean hasAlpha = hasAlpha(image); // Create a buffered image with a format that's compatible with the screen BufferedImage bimage = null; GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); try { // Determine the type of transparency of the new buffered image int transparency = Transparency.OPAQUE; if (hasAlpha) { transparency = Transparency.BITMASK; } // Create the buffered image GraphicsDevice gs = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gs.getDefaultConfiguration(); bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency); } catch (HeadlessException e) { // The system does not have a screen } if (bimage == null) { // Create a buffered image using the default color model int type = BufferedImage.TYPE_INT_RGB; if (hasAlpha) { type = BufferedImage.TYPE_INT_ARGB; } bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type); } // Copy image to buffered image Graphics g = bimage.createGraphics(); // Paint the image onto the buffered image g.drawImage(image, 0, 0, null); g.dispose(); return bimage; } // This code is being picked from Google Guava Library. // http://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/io/Files.java /** * Atomically creates a new directory somewhere beneath the system's * temporary directory (as defined by the {@code java.io.tmpdir} system * property), and returns its name. * * <p>Use this method instead of {@link File#createTempFile(String, String)} * when you wish to create a directory, not a regular file. A common pitfall * is to call {@code createTempFile}, delete the file and create a * directory in its place, but this leads a race condition which can be * exploited to create security vulnerabilities, especially when executable * files are to be written into the directory. * * <p>This method assumes that the temporary volume is writable, has free * inodes and free blocks, and that it will not be called thousands of times * per second. * * @return the newly-created directory * @throws IllegalStateException if the directory could not be created */ public static File createTempDir() { File baseDir = new File(System.getProperty("java.io.tmpdir")); String baseName = System.currentTimeMillis() + "-"; for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { File tempDir = new File(baseDir, baseName + counter); if (tempDir.mkdir()) { return tempDir; } } throw new IllegalStateException("Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')'); } // Deletes all files and subdirectories under dir. // Returns true if all deletions were successful. // If a deletion fails, the method stops attempting to delete and returns false. public static boolean deleteDir(File dir, boolean deleteRoot) { if (dir.isDirectory()) { String[] children = dir.list(); for (int i = 0; i < children.length; i++) { boolean success = deleteDir(new File(dir, children[i]), true); if (!success) { return false; } } } // The directory is now empty so delete it if (deleteRoot) { return dir.delete(); } return true; } public static boolean deleteDir(String dir, boolean deleteRoot) { return deleteDir(new File(dir), deleteRoot); } public static boolean createCompleteDirectoryHierarchyIfDoesNotExist(String directory) { return createCompleteDirectoryHierarchyIfDoesNotExist(new File(directory)); } private static boolean createCompleteDirectoryHierarchyIfDoesNotExist(File f) { if (f == null) return true; if (false == createCompleteDirectoryHierarchyIfDoesNotExist(f.getParentFile())) { return false; } final String path = f.getAbsolutePath(); return createDirectoryIfDoesNotExist(path); } private static boolean createDirectoryIfDoesNotExist(String directory) { java.io.File f = new java.io.File(directory); if (f.exists() == false) { if (f.mkdir()) { return true; } else { return false; } } return true; } public static boolean isFileOrDirectoryExist(String fileOrDirectory) { java.io.File f = new java.io.File(fileOrDirectory); return f.exists(); } /** * Returns user data directory. * @return user data directory */ public static String getUserDataDirectory() { return System.getProperty("user.home") + File.separator + ".jstock" + File.separator + getApplicationVersionString() + File.separator; } /** * Returns cached history files directory. * @return cached history files directory */ public static String getHistoryDirectory() { return getHistoryDirectory(JStock.instance().getJStockOptions().getCountry()); } public static String getHistoryDirectory(Country country) { return Utils.getUserDataDirectory() + country + File.separator + "history" + File.separator; } public static AlphaComposite makeComposite(float alpha) { int type = AlphaComposite.SRC_OVER; return (AlphaComposite.getInstance(type, alpha)); } public static Color getColor(double price, double referencePrice) { final boolean reverse = org.yccheok.jstock.engine.Utils.isFallBelowAndRiseAboveColorReverse(); if (price < referencePrice) { if (reverse) { return JStockOptions.DEFAULT_HIGHER_NUMERICAL_VALUE_FOREGROUND_COLOR; } else { return JStockOptions.DEFAULT_LOWER_NUMERICAL_VALUE_FOREGROUND_COLOR; } } if (price > referencePrice) { if (reverse) { return JStockOptions.DEFAULT_LOWER_NUMERICAL_VALUE_FOREGROUND_COLOR; } else { return JStockOptions.DEFAULT_HIGHER_NUMERICAL_VALUE_FOREGROUND_COLOR; } } return JStockOptions.DEFAULT_NORMAL_TEXT_FOREGROUND_COLOR; } // Deletes all files and subdirectories under dir. // Returns true if all deletions were successful. // If a deletion fails, the method stops attempting to delete and returns false. public static boolean deleteDir(File dir) { if (dir.isDirectory()) { String[] children = dir.list(); for (int i = 0; i < children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } // The directory is now empty so delete it return dir.delete(); } public static void deleteAllOldFiles(File dir, int days) { if (dir.isDirectory()) { String[] children = dir.list(); for (int i = 0; i < children.length; i++) { deleteAllOldFiles(new File(dir, children[i]), days); } final String[] list = dir.list(); if (list == null) { // For unknown reason, list will be null although it shouldn't be, as dir is a // directory. "The result is null if this file is not a directory.". We just return // early, as we aren't sure how are we going to deal with it. return; } // Delete empty directory if (list.length == 0) { dir.delete(); } } else { final long today = System.currentTimeMillis(); final long timeStamp = dir.lastModified(); final long difMil = today - timeStamp; final long milPerDay = 1000 * 60 * 60 * 24; final long d = difMil / milPerDay; if (d >= days) { dir.delete(); } } } public static String getAboutBoxVersionString() { return ABOUT_BOX_VERSION_STRING; } public static String getApplicationVersionString() { return APPLICATION_VERSION_STRING; } // If you try to call this method at different time with same source, the // resultant encrypted string will be different. The best part is, it is still // able to be decrypted back to the original source. public static String encrypt(String source) { if (source.length() <= 0) return ""; org.jasypt.encryption.pbe.PBEStringEncryptor pbeStringEncryptor = new org.jasypt.encryption.pbe.StandardPBEStringEncryptor(); pbeStringEncryptor.setPassword(getJStockUUID()); return pbeStringEncryptor.encrypt(source); } public static String decrypt(String source) { if (source.length() <= 0) { return ""; } org.jasypt.encryption.pbe.PBEStringEncryptor pbeStringEncryptor = new org.jasypt.encryption.pbe.StandardPBEStringEncryptor(); pbeStringEncryptor.setPassword(getJStockUUID()); try { return pbeStringEncryptor.decrypt(source); } catch (org.jasypt.exceptions.EncryptionOperationNotPossibleException exp) { log.error(null, exp); } return ""; } public static String getJStockUUID() { return "fe78440e-e0fe-4efb-881d-264a01be483c"; } // Returns application name, used by Google Doc service. private static String getCloudApplicationName() { return "JStock-" + CLOUD_FILE_VERSION_ID; } // Remember to revise googleDocTitlePattern if we change the definition // of this method. private static String getGoogleDriveTitle(long checksum, long date, int version) { return "jstock-" + getJStockUUID() + "-checksum=" + checksum + "-date=" + date + "-version=" + version + ".zip"; } /** * Returns true if the given locale is simplified chinese. * * @param locale the locale * @return true if the given locale is simplified chinese */ public static boolean isSimplifiedChinese(Locale locale) { // I assume every country in this world is using simplified chinese, // except Taiwan (Locale.TRADITIONAL_CHINESE.getCountry). But, how // about Hong Kong? Note that, we cannot just simply compare by using // Locale.getLanguage, as both Locale.SIMPLIFIED_CHINESE.getLanguage // and Locale.TRADITIONAL_CHINESE.getLanguage are having same value. return locale.getLanguage().equals(Locale.SIMPLIFIED_CHINESE.getLanguage()) && !locale.getCountry().equals(Locale.TRADITIONAL_CHINESE.getCountry()); } /** * Returns true if given locale is traditional chinese. * * @param locale the locale * @return true if given locale is traditional chinese */ public static boolean isTraditionalChinese(Locale locale) { // I assume every country in this world is using simplified chinese, // except Taiwan (Locale.TRADITIONAL_CHINESE.getCountry). But, how // about Hong Kong? Note that, we cannot just simply compare by using // Locale.getLanguage, as both Locale.SIMPLIFIED_CHINESE.getLanguage // and Locale.TRADITIONAL_CHINESE.getLanguage are having same value. return locale.getLanguage().equals(Locale.TRADITIONAL_CHINESE.getLanguage()) && locale.getCountry().equals(Locale.TRADITIONAL_CHINESE.getCountry()); } public static boolean isMacOSX() { return org.jdesktop.swingx.util.OS.isMacOSX(); } public static boolean isWindows7() { String osName = System.getProperty("os.name"); String osVersion = System.getProperty("os.version"); return "Windows 7".equals(osName) && "6.1".equals(osVersion); } public static boolean isWindows8() { String osVersion = System.getProperty("os.version"); return "6.2".equals(osVersion); } public static boolean isWindows() { String windowsString = "Windows"; String osName = System.getProperty("os.name"); if (osName == null) return false; return osName.regionMatches(true, 0, windowsString, 0, windowsString.length()); } public static Executor getZoombiePool() { return zombiePool; } public static Indicator getLastPriceRiseAboveIndicator(double lastPrice) { final StockOperator stockOperator = new StockOperator(); stockOperator.setType(StockOperator.Type.LastPrice); final DoubleConstantOperator doubleConstantOperator = new DoubleConstantOperator(); doubleConstantOperator.setConstant(lastPrice); final EqualityOperator equalityOperator = new EqualityOperator(); equalityOperator.setEquality(EqualityOperator.Equality.GreaterOrEqual); final SinkOperator sinkOperator = new SinkOperator(); final Connection stockToEqualityConnection = new Connection(); final Connection doubleConstantToEqualityConnection = new Connection(); final Connection equalityToSinkConnection = new Connection(); stockOperator.addOutputConnection(stockToEqualityConnection, 0); equalityOperator.addInputConnection(stockToEqualityConnection, 0); doubleConstantOperator.addOutputConnection(doubleConstantToEqualityConnection, 0); equalityOperator.addInputConnection(doubleConstantToEqualityConnection, 1); equalityOperator.addOutputConnection(equalityToSinkConnection, 0); sinkOperator.addInputConnection(equalityToSinkConnection, 0); final OperatorIndicator operatorIndicator = new OperatorIndicator(); operatorIndicator.setName("RiseAbove"); operatorIndicator.add(stockOperator); operatorIndicator.add(doubleConstantOperator); operatorIndicator.add(equalityOperator); operatorIndicator.add(sinkOperator); assert (operatorIndicator.getType() == OperatorIndicator.Type.AlertIndicator); operatorIndicator.preCalculate(); return operatorIndicator; } public static Indicator getLastPriceFallBelowIndicator(double lastPrice) { final StockOperator stockOperator = new StockOperator(); stockOperator.setType(StockOperator.Type.LastPrice); final DoubleConstantOperator doubleConstantOperator = new DoubleConstantOperator(); doubleConstantOperator.setConstant(lastPrice); final EqualityOperator equalityOperator = new EqualityOperator(); equalityOperator.setEquality(EqualityOperator.Equality.LesserOrEqual); final SinkOperator sinkOperator = new SinkOperator(); final Connection stockToEqualityConnection = new Connection(); final Connection doubleConstantToEqualityConnection = new Connection(); final Connection equalityToSinkConnection = new Connection(); stockOperator.addOutputConnection(stockToEqualityConnection, 0); equalityOperator.addInputConnection(stockToEqualityConnection, 0); doubleConstantOperator.addOutputConnection(doubleConstantToEqualityConnection, 0); equalityOperator.addInputConnection(doubleConstantToEqualityConnection, 1); equalityOperator.addOutputConnection(equalityToSinkConnection, 0); sinkOperator.addInputConnection(equalityToSinkConnection, 0); final OperatorIndicator operatorIndicator = new OperatorIndicator(); operatorIndicator.setName("FallBelow"); operatorIndicator.add(stockOperator); operatorIndicator.add(doubleConstantOperator); operatorIndicator.add(equalityOperator); operatorIndicator.add(sinkOperator); assert (operatorIndicator.getType() == OperatorIndicator.Type.AlertIndicator); operatorIndicator.preCalculate(); return operatorIndicator; } public static String setDefaultLookAndFeel() { try { String className = UIManager.getSystemLookAndFeelClassName(); UIManager.setLookAndFeel(className); return className; } catch (java.lang.ClassNotFoundException | java.lang.InstantiationException | java.lang.IllegalAccessException | javax.swing.UnsupportedLookAndFeelException exp) { log.error(null, exp); } return null; } public static class CloudFile { public final File file; public final long checksum; public final long date; public final int version; private CloudFile(File file, long checksum, long date, int version) { this.file = file; this.checksum = checksum; this.date = date; this.version = version; } public static CloudFile newInstance(File file, long checksum, long date, int version) { return new CloudFile(file, checksum, date, version); } } private static class GoogleCloudFile { public final com.google.api.services.drive.model.File file; public final long checksum; public final long date; public final int version; private GoogleCloudFile(com.google.api.services.drive.model.File file, long checksum, long date, int version) { this.file = file; this.checksum = checksum; this.date = date; this.version = version; } public static GoogleCloudFile newInstance(com.google.api.services.drive.model.File file, long checksum, long date, int version) { return new GoogleCloudFile(file, checksum, date, version); } } private static GoogleCloudFile searchFromGoogleDrive(Drive drive, String qString) { try { Files.List request = drive.files().list().setQ(qString); do { FileList fileList = request.execute(); long checksum = 0; long date = 0; int version = 0; com.google.api.services.drive.model.File file = null; for (com.google.api.services.drive.model.File f : fileList.getItems()) { final String title = f.getTitle(); if (title == null || f.getDownloadUrl() == null || f.getDownloadUrl().length() <= 0) { continue; } // Retrieve checksum, date and version information from filename. final Matcher matcher = googleDocTitlePattern.matcher(title); String _checksum = null; String _date = null; String _version = null; if (matcher.find()) { if (matcher.groupCount() == 3) { _checksum = matcher.group(1); _date = matcher.group(2); _version = matcher.group(3); } } if (_checksum == null || _date == null || _version == null) { continue; } try { checksum = Long.parseLong(_checksum); date = Long.parseLong(_date); version = Integer.parseInt(_version); } catch (NumberFormatException ex) { log.error(null, ex); continue; } file = f; break; } if (file != null) { return GoogleCloudFile.newInstance(file, checksum, date, version); } request.setPageToken(fileList.getNextPageToken()); } while (request.getPageToken() != null && request.getPageToken().length() > 0); } catch (IOException ex) { log.error(null, ex); return null; } return null; } private static CloudFile _loadFromGoogleDrive(Credential credential, String qString) { Drive drive = org.yccheok.jstock.google.Utils.getDrive(credential); GoogleCloudFile googleCloudFile = searchFromGoogleDrive(drive, qString); if (googleCloudFile == null) { return null; } final com.google.api.services.drive.model.File file = googleCloudFile.file; final long checksum = googleCloudFile.checksum; final long date = googleCloudFile.date; final int version = googleCloudFile.version; HttpResponse resp = null; InputStream inputStream = null; java.io.File outputFile = null; OutputStream outputStream = null; try { resp = drive.getRequestFactory().buildGetRequest(new GenericUrl(file.getDownloadUrl())).execute(); inputStream = resp.getContent(); outputFile = java.io.File.createTempFile(Utils.getJStockUUID(), ".zip"); outputFile.deleteOnExit(); outputStream = new FileOutputStream(outputFile); int read = 0; byte[] bytes = new byte[1024]; while ((read = inputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, read); } } catch (IOException ex) { log.error(null, ex); } finally { org.yccheok.jstock.file.Utils.close(outputStream); org.yccheok.jstock.file.Utils.close(inputStream); if (resp != null) { try { resp.disconnect(); } catch (IOException ex) { log.error(null, ex); } } } if (outputFile == null) { return null; } return CloudFile.newInstance(outputFile, checksum, date, version); } public static CloudFile loadFromGoogleDrive(Credential credential) { // 25 is based on experiment. Might changed by Google in the future. final String titleName = ("jstock-" + Utils.getJStockUUID() + "-checksum=").substring(0, 25); final String qString = "title contains '" + titleName + "' and trashed = false and 'appdata' in parents and 'me' in owners"; return _loadFromGoogleDrive(credential, qString); } // Legacy. Shall be removed after a while... public static CloudFile loadFromLegacyGoogleDrive(Credential credential) { // 25 is based on experiment. Might changed by Google in the future. final String titleName = ("jstock-" + Utils.getJStockUUID() + "-checksum=").substring(0, 25); final String qString = "title contains '" + titleName + "' and trashed = false and not 'appdata' in parents and 'me' in owners"; return _loadFromGoogleDrive(credential, qString); } public static boolean saveToGoogleDrive(Credential credential, File file) { // 25 is based on experiment. Might changed by Google in the future. final String titleName = ("jstock-" + Utils.getJStockUUID() + "-checksum=").substring(0, 25); final String qString = "title contains '" + titleName + "' and trashed = false and 'appdata' in parents and 'me' in owners"; return _saveToGoogleDrive(credential, file, qString, "appdata"); } public static boolean saveToLegacyGoogleDrive(Credential credential, File file) { // 25 is based on experiment. Might changed by Google in the future. final String titleName = ("jstock-" + Utils.getJStockUUID() + "-checksum=").substring(0, 25); final String qString = "title contains '" + titleName + "' and trashed = false and not 'appdata' in parents and 'me' in owners"; return _saveToGoogleDrive(credential, file, qString, null); } private static boolean _saveToGoogleDrive(Credential credential, File file, String qString, String folder) { Drive drive = org.yccheok.jstock.google.Utils.getDrive(credential); // Should we new or replace? GoogleCloudFile googleCloudFile = searchFromGoogleDrive(drive, qString); final long checksum = org.yccheok.jstock.analysis.Utils.getChecksum(file); final long date = new Date().getTime(); final int version = org.yccheok.jstock.gui.Utils.getCloudFileVersionID(); final String title = getGoogleDriveTitle(checksum, date, version); if (googleCloudFile == null) { String id = null; if (folder != null) { com.google.api.services.drive.model.File appData; try { appData = drive.files().get(folder).execute(); id = appData.getId(); } catch (IOException ex) { log.error(null, ex); return false; } } return null != insertFile(drive, title, id, file); } else { final com.google.api.services.drive.model.File oldFile = googleCloudFile.file; return null != updateFile(drive, oldFile.getId(), title, file); } } /** * Insert new file. * * @param service Drive API service instance. * @param title Title of the file to insert, including the extension. * @param parentId Optional parent folder's ID. * @param mimeType MIME type of the file to insert. * @param filename Filename of the file to insert. * @return Inserted file metadata if successful, {@code null} otherwise. */ private static com.google.api.services.drive.model.File insertFile(Drive service, String title, String parentId, java.io.File fileContent) { // File's metadata. com.google.api.services.drive.model.File body = new com.google.api.services.drive.model.File(); body.setTitle(title); // Set the parent folder. if (parentId != null && parentId.length() > 0) { body.setParents(Arrays.asList(new ParentReference().setId(parentId))); } // File's content. FileContent mediaContent = new FileContent("", fileContent); try { com.google.api.services.drive.model.File file = service.files().insert(body, mediaContent).execute(); return file; } catch (IOException e) { log.error(null, e); return null; } } /** * Update an existing file's metadata and content. * * @param service Drive API service instance. * @param fileId ID of the file to update. * @param newTitle New title for the file. * @param newFilename Filename of the new content to upload. * @return Updated file metadata if successful, {@code null} otherwise. */ private static com.google.api.services.drive.model.File updateFile(Drive service, String fileId, String newTitle, java.io.File fileContent) { try { // First retrieve the file from the API. com.google.api.services.drive.model.File file = service.files().get(fileId).execute(); // File's new metadata. file.setTitle(newTitle); FileContent mediaContent = new FileContent("", fileContent); // http://stackoverflow.com/questions/23707388/unable-update-file-store-in-appdata-scope-500-internal-server-error // Send the request to the API. com.google.api.services.drive.model.File updatedFile = service.files() .update(fileId, file, mediaContent).setNewRevision(false).execute(); return updatedFile; } catch (IOException e) { log.error(null, e); return null; } } public static boolean isCloudFileCompatible(int cloudFileVersionId) { if (cloudFileVersionId == CLOUD_FILE_VERSION_ID) { return true; } else if (cloudFileVersionId >= 1051 && cloudFileVersionId <= (CLOUD_FILE_VERSION_ID - 1)) { return true; } return false; } /** * Get response body through non-standard POST method. * Please refer to <url>http://stackoverflow.com/questions/1473255/is-jakarta-httpclient-sutitable-for-the-following-task/1473305#1473305</url> * * @param uri For example, http://X/%5bvUpJYKw4QvGRMBmhATUxRwv4JrU9aDnwNEuangVyy6OuHxi2YiY=%5dImage? * @param formData For example, [SORT]=0,1,0,10,5,0,KL,0&[FIELD]=33,38,51 * @return the response body. null if fail. */ public static String getPOSTResponseBodyAsStringBasedOnProxyAuthOption(String uri, String formData) { org.yccheok.jstock.engine.Utils.setHttpClientProxyFromSystemProperties(httpClient); org.yccheok.jstock.gui.Utils.setHttpClientProxyCredentialsFromJStockOptions(httpClient); final PostMethod method = new PostMethod(uri); final RequestEntity entity; try { entity = new StringRequestEntity(formData, "application/x-www-form-urlencoded", "UTF-8"); } catch (UnsupportedEncodingException exp) { log.error(null, exp); return null; } method.setRequestEntity(entity); method.setContentChunked(false); final JStockOptions jStockOptions = JStock.instance().getJStockOptions(); String respond = null; try { if (jStockOptions.isProxyAuthEnabled()) { /* WARNING : This chunck of code block is not tested! */ method.setFollowRedirects(false); httpClient.executeMethod(method); int statuscode = method.getStatusCode(); if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) || (statuscode == HttpStatus.SC_MOVED_PERMANENTLY) || (statuscode == HttpStatus.SC_SEE_OTHER) || (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) { //Make new Request with new URL Header header = method.getResponseHeader("location"); /* WARNING : Correct method to redirect? Shall we use POST? How about form data? */ HttpMethod RedirectMethod = new GetMethod(header.getValue()); // I assume it is OK to release method for twice. (The second // release will happen in finally block). We shouldn't have an // unreleased method, before executing another new method. method.releaseConnection(); // Do RedirectMethod within try-catch-finally, so that we can have a // exception free way to release RedirectMethod connection. // #2836422 try { httpClient.executeMethod(RedirectMethod); respond = RedirectMethod.getResponseBodyAsString(); } catch (HttpException exp) { log.error(null, exp); return null; } catch (IOException exp) { log.error(null, exp); return null; } finally { RedirectMethod.releaseConnection(); } } else { respond = method.getResponseBodyAsString(); } // if statuscode = Redirect } else { httpClient.executeMethod(method); respond = method.getResponseBodyAsString(); } // if jStockOptions.isProxyAuthEnabled() } catch (HttpException exp) { log.error(null, exp); return null; } catch (IOException exp) { log.error(null, exp); return null; } finally { method.releaseConnection(); } return respond; } /** * Request server response by sending request together without agent info. * * @param request the request * @return server response. null if fail. */ public static String getResponseBodyAsStringBasedOnProxyAuthOption(String request) { return _getResponseBodyAsStringBasedOnProxyAuthOption(httpClient, request); } /** * Request server response by sending request together with agent info. * * @param request the request * @return server response. null if fail. */ public static String getResponseBodyAsStringBasedOnProxyAuthOptionWithAgentInfo(String request) { return _getResponseBodyAsStringBasedOnProxyAuthOption(httpClientWithAgentInfo, request); } // We prefer to have this method in gui package instead of engine. This is because it requires // access to JStockOptions. // Returns null if fail. private static String _getResponseBodyAsStringBasedOnProxyAuthOption(HttpClient client, String request) { org.yccheok.jstock.engine.Utils.setHttpClientProxyFromSystemProperties(client); org.yccheok.jstock.gui.Utils.setHttpClientProxyCredentialsFromJStockOptions(client); final HttpMethod method = new GetMethod(request); final JStockOptions jStockOptions = JStock.instance().getJStockOptions(); String respond = null; try { if (jStockOptions.isProxyAuthEnabled()) { method.setFollowRedirects(false); client.executeMethod(method); int statuscode = method.getStatusCode(); if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) || (statuscode == HttpStatus.SC_MOVED_PERMANENTLY) || (statuscode == HttpStatus.SC_SEE_OTHER) || (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) { //Make new Request with new URL Header header = method.getResponseHeader("location"); HttpMethod RedirectMethod = new GetMethod(header.getValue()); // I assume it is OK to release method for twice. (The second // release will happen in finally block). We shouldn't have an // unreleased method, before executing another new method. method.releaseConnection(); // Do RedirectMethod within try-catch-finally, so that we can have a // exception free way to release RedirectMethod connection. // #2836422 try { client.executeMethod(RedirectMethod); respond = RedirectMethod.getResponseBodyAsString(); } catch (HttpException exp) { log.error(null, exp); return null; } catch (IOException exp) { log.error(null, exp); return null; } finally { RedirectMethod.releaseConnection(); } } else { respond = method.getResponseBodyAsString(); } // if statuscode = Redirect } else { client.executeMethod(method); respond = method.getResponseBodyAsString(); } // if jStockOptions.isProxyAuthEnabled() } catch (HttpException exp) { log.error(null, exp); return null; } catch (IOException exp) { log.error(null, exp); return null; } finally { method.releaseConnection(); } return respond; } public static class InputStreamAndMethod { public final InputStream inputStream; public final HttpMethod method; public InputStreamAndMethod(InputStream inputStream, HttpMethod method) { this.inputStream = inputStream; this.method = method; } } // Unlike getResponseBodyAsStringBasedOnProxyAuthOption, method must be closed // explicitly by caller. If not, the returned input stream will not be valid. // The returned InputStreamAndMethod and InputStreamAndMethod.method will always be non-null. // // InputStreamAndMethod.inputStream will be null if we fail to get any respond. // // We must always remember to close InputStreamAndMethod.method, after finish // reading InputStreamAndMethod.inputStream. public static InputStreamAndMethod getResponseBodyAsStreamBasedOnProxyAuthOption(String request) { org.yccheok.jstock.engine.Utils.setHttpClientProxyFromSystemProperties(httpClient); org.yccheok.jstock.gui.Utils.setHttpClientProxyCredentialsFromJStockOptions(httpClient); final GetMethod method = new GetMethod(request); final JStockOptions jStockOptions = JStock.instance().getJStockOptions(); InputStreamAndMethod inputStreamAndMethod = null; InputStream respond = null; HttpMethod methodToClosed = method; try { if (jStockOptions.isProxyAuthEnabled()) { method.setFollowRedirects(false); httpClient.executeMethod(method); int statuscode = method.getStatusCode(); if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) || (statuscode == HttpStatus.SC_MOVED_PERMANENTLY) || (statuscode == HttpStatus.SC_SEE_OTHER) || (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) { //Make new Request with new URL Header header = method.getResponseHeader("location"); GetMethod RedirectMethod = new GetMethod(header.getValue()); methodToClosed = RedirectMethod; method.releaseConnection(); // Do RedirectMethod within try-catch-finally, so that we can have a // exception free way to release RedirectMethod connection. // #2836422 try { httpClient.executeMethod(RedirectMethod); respond = RedirectMethod.getResponseBodyAsStream(); } catch (HttpException exp) { log.error(null, exp); } catch (IOException exp) { log.error(null, exp); } } else { methodToClosed = method; respond = method.getResponseBodyAsStream(); } // if statuscode = Redirect } else { methodToClosed = method; httpClient.executeMethod(method); respond = method.getResponseBodyAsStream(); } // if jStockOptions.isProxyAuthEnabled() } catch (HttpException exp) { log.error(null, exp); } catch (IOException exp) { log.error(null, exp); } finally { inputStreamAndMethod = new InputStreamAndMethod(respond, methodToClosed); } return inputStreamAndMethod; } // We prefer to have this method in gui package instead of engine. This is because it requires // access to JStockOptions. private static void setHttpClientProxyCredentialsFromJStockOptions(HttpClient httpClient) { final JStockOptions jStockOptions = JStock.instance().getJStockOptions(); if (jStockOptions.isProxyAuthEnabled() == false) { httpClient.getState().clearCredentials(); } else { httpClient.getState().setProxyCredentials(AuthScope.ANY, jStockOptions.getCredentials()); } } /* * Get the extension of a file. */ public static String getFileExtension(String s) { String ext = ""; int i = s.lastIndexOf('.'); if (i > 0 && i < s.length() - 1) { ext = s.substring(i + 1).toLowerCase(); } return ext; } /* * Get the extension of a file. */ public static String getFileExtension(File f) { return getFileExtension(f.getName()); } public static ApplicationInfo getLatestApplicationInfo() { final String request = org.yccheok.jstock.network.Utils.getURL(Type.VERSION_INFORMATION_TXT); final org.yccheok.jstock.gui.Utils.InputStreamAndMethod inputStreamAndMethod = org.yccheok.jstock.gui.Utils .getResponseBodyAsStreamBasedOnProxyAuthOption(request); if (inputStreamAndMethod.inputStream == null) { inputStreamAndMethod.method.releaseConnection(); return null; } Properties properties = new Properties(); try { properties.load(inputStreamAndMethod.inputStream); } catch (IOException exp) { log.error(null, exp); return null; } catch (IllegalArgumentException exp) { log.error(null, exp); return null; } finally { org.yccheok.jstock.file.Utils.close(inputStreamAndMethod.inputStream); inputStreamAndMethod.method.releaseConnection(); } final String applicationVersionID = properties.getProperty("applicationVersionID"); final String windowsDownloadLink = properties.getProperty("windowsDownloadLink"); final String linuxDownloadLink = properties.getProperty("linuxDownloadLink"); final String macDownloadLink = properties.getProperty("macDownloadLink"); final String solarisDownloadLink = properties.getProperty("solarisDownloadLink"); if (applicationVersionID == null || windowsDownloadLink == null || linuxDownloadLink == null || macDownloadLink == null || solarisDownloadLink == null) { return null; } final int version; try { version = Integer.parseInt(applicationVersionID); } catch (NumberFormatException exp) { log.error(null, exp); return null; } return new ApplicationInfo(version, windowsDownloadLink, linuxDownloadLink, macDownloadLink, solarisDownloadLink); } public static int getApplicationVersionID() { return Utils.APPLICATION_VERSION_ID; } public static int getCloudFileVersionID() { return Utils.CLOUD_FILE_VERSION_ID; } public static String toHTML(String plainText) { plainText = plainText.replace(System.getProperty("line.separator"), "<br>"); return "<html><head></head><body>" + plainText + "</body></html>"; } @SuppressWarnings("unchecked") public static <A> A fromXML(Class c, Reader reader) { // Don't ever try to use DomDriver. They are VERY slow. XStream xStream = new XStream(); try { Object object = xStream.fromXML(reader); if (c.isInstance(object)) { return (A) object; } } catch (Exception exp) { log.error(null, exp); } finally { /* The caller shall close reader explicitly. */ } return null; } @SuppressWarnings("unchecked") public static <A> A fromXML(Class<A> c, File file) { // Don't ever try to use DomDriver. They are VERY slow. XStream xStream = new XStream(); InputStream inputStream = null; Reader reader = null; try { inputStream = new java.io.FileInputStream(file); reader = new InputStreamReader(inputStream, Charset.forName("UTF-8")); Object object = xStream.fromXML(reader); if (c.isInstance(object)) { return (A) object; } } catch (Exception exp) { log.error(null, exp); } catch (java.lang.Error err) { log.error(null, err); } finally { org.yccheok.jstock.file.Utils.close(reader); org.yccheok.jstock.file.Utils.close(inputStream); } return null; } @SuppressWarnings("unchecked") public static <A> A fromXML(Class<A> c, String filePath) { return fromXML(c, new File(filePath)); } public static boolean toXML(Object object, File file) { XStream xStream = new XStream(); OutputStream outputStream = null; Writer writer = null; try { outputStream = new FileOutputStream(file); writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8")); xStream.toXML(object, writer); } catch (Exception exp) { log.error(null, exp); return false; } finally { org.yccheok.jstock.file.Utils.close(writer); org.yccheok.jstock.file.Utils.close(outputStream); } return true; } public static boolean toXML(Object object, String filePath) { return toXML(object, new File(filePath)); } public static File getStockInfoDatabaseMetaFile() { return new File(org.yccheok.jstock.gui.Utils.getUserDataDirectory() + "stock-info-database-meta.json"); } public static String getExtraDataDirectory() { return org.yccheok.jstock.gui.Utils.getUserDataDirectory() + "extra" + File.separator; } public static String toHTMLFileSrcFormat(String filename) { try { return new File(filename).toURI().toURL().toString(); } catch (MalformedURLException ex) { log.error(null, ex); } // http://www.exampledepot.com/egs/javax.swing/checkbox_AddIcon.html return "file:" + filename; } public static class FileEx { public final File file; public final org.yccheok.jstock.file.Statement.Type type; public FileEx(File file, org.yccheok.jstock.file.Statement.Type type) { this.file = file; this.type = type; } } // Calling to this method will affect state of JStockOptions. // Returns null if no file being selected. public static FileEx promptSavePortfolioCSVAndExcelJFileChooser(final String suggestedFileName) { final JStockOptions jStockOptions = JStock.instance().getJStockOptions(); final JFileChooser chooser = new JFileChooser(jStockOptions.getLastFileIODirectory()); final FileNameExtensionFilter csvFilter = new FileNameExtensionFilter("CSV Documents (*.csv)", "csv"); final FileNameExtensionFilter xlsFilter = new FileNameExtensionFilter("Microsoft Excel (*.xls)", "xls"); chooser.setAcceptAllFileFilterUsed(false); chooser.addChoosableFileFilter(csvFilter); chooser.addChoosableFileFilter(xlsFilter); final org.yccheok.jstock.gui.file.PortfolioSelectionJPanel portfolioSelectionJPanel = new org.yccheok.jstock.gui.file.PortfolioSelectionJPanel(); chooser.setAccessory(portfolioSelectionJPanel); chooser.addPropertyChangeListener(JFileChooser.FILE_FILTER_CHANGED_PROPERTY, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { final boolean flag = ((FileNameExtensionFilter) evt.getNewValue()).equals(csvFilter); portfolioSelectionJPanel.setEnabled(flag); chooser.setSelectedFile(chooser.getFileFilter().getDescription().equals(csvFilter.getDescription()) ? new File(portfolioSelectionJPanel.getSuggestedFileName()) : new File(suggestedFileName)); } }); portfolioSelectionJPanel.addJRadioButtonsActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { chooser.setSelectedFile(new File(portfolioSelectionJPanel.getSuggestedFileName())); } }); final java.util.Map<String, FileNameExtensionFilter> map = new HashMap<String, FileNameExtensionFilter>(); map.put(csvFilter.getDescription(), csvFilter); map.put(xlsFilter.getDescription(), xlsFilter); final FileNameExtensionFilter filter = map.get(jStockOptions.getLastSavedFileNameExtensionDescription()); if (filter != null) { chooser.setFileFilter(filter); } // Only enable portfolioSelectionJPanel, if CSV is being selected. portfolioSelectionJPanel .setEnabled(chooser.getFileFilter().getDescription().equals(csvFilter.getDescription())); chooser.setSelectedFile(chooser.getFileFilter().getDescription().equals(csvFilter.getDescription()) ? new File(portfolioSelectionJPanel.getSuggestedFileName()) : new File(suggestedFileName)); while (true) { final int returnVal = chooser.showSaveDialog(JStock.instance()); if (returnVal != JFileChooser.APPROVE_OPTION) { return null; } File file = chooser.getSelectedFile(); if (file == null) { return null; } // Ensure the saved file is in correct extension. If user provide correct // file extension explicitly, leave it as is. If not, mutate the filename. final String extension = Utils.getFileExtension(file); if (extension.equals("csv") == false && extension.equals("xls") == false) { if (chooser.getFileFilter().getDescription().equals(csvFilter.getDescription())) { file = new File(file.getAbsolutePath() + ".csv"); } else if (chooser.getFileFilter().getDescription().equals(xlsFilter.getDescription())) { file = new File(file.getAbsolutePath() + ".xls"); } else { // Impossible. return null; } } if (file.exists()) { final String output = MessageFormat .format(MessagesBundle.getString("question_message_replace_old_template"), file.getName()); final int result = javax.swing.JOptionPane.showConfirmDialog(JStock.instance(), output, MessagesBundle.getString("question_title_replace_old"), javax.swing.JOptionPane.YES_NO_OPTION, javax.swing.JOptionPane.QUESTION_MESSAGE); if (result != javax.swing.JOptionPane.YES_OPTION) { continue; } } final String parent = chooser.getSelectedFile().getParent(); if (parent != null) { jStockOptions.setLastFileIODirectory(parent); } if (Utils.getFileExtension(file).equals("csv")) { jStockOptions.setLastFileNameExtensionDescription(csvFilter.getDescription()); } else if (Utils.getFileExtension(file).equals("xls")) { jStockOptions.setLastFileNameExtensionDescription(xlsFilter.getDescription()); } else { // Impossible. return null; } return new FileEx(file, portfolioSelectionJPanel.getType()); } } // This method returns the selected radio button in a button group public static JRadioButton getSelection(ButtonGroup group) { for (Enumeration e = group.getElements(); e.hasMoreElements();) { JRadioButton b = (JRadioButton) e.nextElement(); if (b.getModel() == group.getSelection()) { return b; } } return null; } private static File promptOpenJFileChooser(FileNameExtensionFilter... fileNameExtensionFilters) { final JStockOptions jStockOptions = JStock.instance().getJStockOptions(); final JFileChooser chooser = new JFileChooser(jStockOptions.getLastFileIODirectory()); chooser.setAcceptAllFileFilterUsed(false); for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { chooser.addChoosableFileFilter(fileNameExtensionFilter); } final java.util.Map<String, FileNameExtensionFilter> map = new HashMap<String, FileNameExtensionFilter>(); for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { map.put(fileNameExtensionFilter.getDescription(), fileNameExtensionFilter); } final FileNameExtensionFilter filter = map.get(jStockOptions.getLastSavedFileNameExtensionDescription()); if (filter != null) { chooser.setFileFilter(filter); } int returnVal = chooser.showOpenDialog(JStock.instance()); if (returnVal != JFileChooser.APPROVE_OPTION) { return null; } File file = chooser.getSelectedFile(); if (file == null || !file.exists()) { return null; } final String parent = chooser.getSelectedFile().getParent(); if (parent != null) { jStockOptions.setLastFileIODirectory(parent); } final String extension = Utils.getFileExtension(file); for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { final String[] extensions = fileNameExtensionFilter.getExtensions(); if (extensions.length <= 0) { continue; } if (extension.equals(extensions[0])) { jStockOptions.setLastFileNameExtensionDescription(fileNameExtensionFilter.getDescription()); return file; } } return null; } public static void playAlertSound() { new Thread(new Runnable() { @Override public void run() { try { Clip clip = AudioSystem.getClip(); clip.addLineListener(new LineListener() { @Override public void update(LineEvent event) { if (event.getType() == LineEvent.Type.STOP) { event.getLine().close(); } } }); final InputStream audioSrc = Utils.class.getResourceAsStream("/assets/sounds/doorbell.wav"); // http://stackoverflow.com/questions/5529754/java-io-ioexception-mark-reset-not-supported // Add buffer for mark/reset support. final InputStream bufferedIn = new BufferedInputStream(audioSrc); AudioInputStream inputStream = AudioSystem.getAudioInputStream(bufferedIn); clip.open(inputStream); clip.start(); } catch (Exception e) { log.error(null, e); } } }).start(); } // Calling to this method will affect state of JStockOptions. // Returns null if no file being selected. public static File promptOpenCSVAndExcelJFileChooser() { final FileNameExtensionFilter csvFilter = new FileNameExtensionFilter("CSV Documents (*.csv)", "csv"); final FileNameExtensionFilter xlsFilter = new FileNameExtensionFilter("Microsoft Excel (*.xls)", "xls"); return promptOpenJFileChooser(csvFilter, xlsFilter); } public static File promptOpenZippedJFileChooser() { final FileNameExtensionFilter zippedFilter = new FileNameExtensionFilter("Zipped Files (*.zip)", "zip"); return promptOpenJFileChooser(zippedFilter); } public static String stockPriceDecimalFormat(Object value) { // 0.1 -> "0.10" // 0.01 -> "0.01" // 0.001 -> "0.001" final DecimalFormat decimalFormat = new DecimalFormat("0.00#"); return decimalFormat.format(value); } public static String stockPriceDecimalFormat(double value) { // 0.1 -> "0.10" // 0.01 -> "0.01" // 0.001 -> "0.001" DecimalFormat decimalFormat = new DecimalFormat("0.00#"); return decimalFormat.format(value); } private static File promptSaveJFileChooser(String suggestedFileName, FileNameExtensionFilter... fileNameExtensionFilters) { final JStockOptions jStockOptions = JStock.instance().getJStockOptions(); final JFileChooser chooser = new JFileChooser(jStockOptions.getLastFileIODirectory()); chooser.setAcceptAllFileFilterUsed(false); for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { chooser.addChoosableFileFilter(fileNameExtensionFilter); } chooser.setSelectedFile(new File(suggestedFileName)); final java.util.Map<String, FileNameExtensionFilter> map = new HashMap<String, FileNameExtensionFilter>(); for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { map.put(fileNameExtensionFilter.getDescription(), fileNameExtensionFilter); } final FileNameExtensionFilter filter = map.get(jStockOptions.getLastSavedFileNameExtensionDescription()); if (filter != null) { chooser.setFileFilter(filter); } while (true) { final int returnVal = chooser.showSaveDialog(JStock.instance()); if (returnVal != JFileChooser.APPROVE_OPTION) { return null; } File file = chooser.getSelectedFile(); if (file == null) { return null; } // Ensure the saved file is in correct extension. If user provide correct // file extension explicitly, leave it as is. If not, mutate the filename. final String extension = Utils.getFileExtension(file); boolean found = false; root: for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { String[] extensions = fileNameExtensionFilter.getExtensions(); for (String e : extensions) { if (e.equals(extension)) { found = true; break root; } } } if (!found) { for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { String[] extensions = fileNameExtensionFilter.getExtensions(); if (extensions.length <= 0) { continue; } final String e = extensions[0]; if (chooser.getFileFilter().getDescription().equals(fileNameExtensionFilter.getDescription())) { if (e.startsWith(".")) { file = new File(file.getAbsolutePath() + e); } else { file = new File(file.getAbsolutePath() + "." + e); } break; } } } if (file.exists()) { final String output = MessageFormat .format(MessagesBundle.getString("question_message_replace_old_template"), file.getName()); final int result = javax.swing.JOptionPane.showConfirmDialog(JStock.instance(), output, MessagesBundle.getString("question_title_replace_old"), javax.swing.JOptionPane.YES_NO_OPTION, javax.swing.JOptionPane.QUESTION_MESSAGE); if (result != javax.swing.JOptionPane.YES_OPTION) { continue; } } final String parent = chooser.getSelectedFile().getParent(); if (parent != null) { jStockOptions.setLastFileIODirectory(parent); } final String e = Utils.getFileExtension(file); for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) { String[] extensions = fileNameExtensionFilter.getExtensions(); if (extensions.length <= 0) { continue; } if (e.equals(extensions[0])) { jStockOptions.setLastFileNameExtensionDescription(fileNameExtensionFilter.getDescription()); break; } } return file; } } public static File promptSaveZippedJFileChooser(String suggestedFileName) { final FileNameExtensionFilter zippedFilter = new FileNameExtensionFilter("Zipped Files (*.zip)", "zip"); return promptSaveJFileChooser(suggestedFileName, zippedFilter); } /** * Get a new bold version of specified font, with rest of specified font * attributes remained the same. * * @param font specified font * @return a new bold version of specified font */ public static Font getBoldFont(Font font) { return font.deriveFont(font.getStyle() | Font.BOLD); } // Calling to this method will affect state of JStockOptions. // Returns null if no file being selected. public static File promptSaveCSVAndExcelJFileChooser(String suggestedFileName) { final FileNameExtensionFilter csvFilter = new FileNameExtensionFilter("CSV Documents (*.csv)", "csv"); final FileNameExtensionFilter xlsFilter = new FileNameExtensionFilter("Microsoft Excel (*.xls)", "xls"); return promptSaveJFileChooser(suggestedFileName, csvFilter, xlsFilter); } /** * Returns a JXLayer with busy indicator, which wraps around the auto * complete combo box. * * @param autoCompleteJComboBox the auto complete combo box * @return a JXLayer with busy indicator, which wraps around the auto * complete combo box */ public static JXLayer<JComboBox> getBusyJXLayer(AutoCompleteJComboBox autoCompleteJComboBox) { // Wrap combo box. final JXLayer<JComboBox> layer = new JXLayer<JComboBox>(autoCompleteJComboBox); // Set our LayerUI. JComboBoxLayerUI jComboBoxLayerUI = new JComboBoxLayerUI(); layer.setUI(jComboBoxLayerUI); autoCompleteJComboBox.attachBusyObserver(jComboBoxLayerUI); return layer; } /** * Returns username in email format if possible. The default used email is * GMail. Returns null if conversion is not possible. * * @param username the username * @return username in email format if possible. The default used email is * GMail. Returns null if conversion is not possible */ public static String toEmailIfPossible(String username) { if (false == org.apache.commons.validator.EmailValidator.getInstance().isValid(username)) { // The default email is gmail. username = username + "@gmail.com"; if (false == org.apache.commons.validator.EmailValidator.getInstance().isValid(username)) { return null; } } return username; } /** * Returns number of lines in a file. * * @param file The file * @return number of lines in a file */ public static int numOfLines(File file, boolean skipMetadata) { int line = 0; int metaLineNumber = 0; LineNumberReader lnr = null; try { lnr = new LineNumberReader(new FileReader(file)); if (skipMetadata) { String nextLine = lnr.readLine(); // Metadata handling. while (nextLine != null) { String[] tokens = nextLine.split("=", 2); if (tokens.length == 2) { String key = tokens[0].trim(); if (key.length() > 0) { // Is OK for value to be empty. metaLineNumber++; nextLine = lnr.readLine(); } else { break; } } else { break; } } } lnr.skip(Long.MAX_VALUE); line = lnr.getLineNumber(); } catch (IOException ex) { log.error(null, ex); } finally { org.yccheok.jstock.file.Utils.close(lnr); } return line - metaLineNumber; } public static String downloadAsString(String location) { final Utils.InputStreamAndMethod inputStreamAndMethod = Utils .getResponseBodyAsStreamBasedOnProxyAuthOption(location); if (inputStreamAndMethod.inputStream == null) { inputStreamAndMethod.method.releaseConnection(); return null; } try { java.util.Scanner s = new java.util.Scanner(inputStreamAndMethod.inputStream, "UTF-8") .useDelimiter("\\A"); return s.hasNext() ? s.next() : null; } finally { org.yccheok.jstock.file.Utils.close(inputStreamAndMethod.inputStream); inputStreamAndMethod.method.releaseConnection(); } } /** * Performs download and save the download as temporary file. * * @param location Download URL location * @return The saved temporary file if download success. <code>null</code> * if failed. */ public static File downloadAsTempFile(String location) { final Utils.InputStreamAndMethod inputStreamAndMethod = Utils .getResponseBodyAsStreamBasedOnProxyAuthOption(location); if (inputStreamAndMethod.inputStream == null) { inputStreamAndMethod.method.releaseConnection(); return null; } // Write to temp file. OutputStream out = null; File temp = null; try { // Create temp file. temp = File.createTempFile(Utils.getJStockUUID(), null); // Delete temp file when program exits. temp.deleteOnExit(); out = new FileOutputStream(temp); // Transfer bytes from the ZIP file to the output file byte[] buf = new byte[1024]; int len; while ((len = inputStreamAndMethod.inputStream.read(buf)) > 0) { out.write(buf, 0, len); } // Success! return temp; } catch (IOException ex) { log.error(null, ex); } finally { org.yccheok.jstock.file.Utils.close(out); org.yccheok.jstock.file.Utils.close(inputStreamAndMethod.inputStream); inputStreamAndMethod.method.releaseConnection(); } return null; } /** * Returns list of Han Yu Pin Yin's prefix of every characters. If the * character is an alphabet or numerical, the original character will be * used. If there is any error occur during conversion, that particular * character will be ignored. * * @param chinese String to be converted * @return List of Han Yu Pin Yin's prefix of every characters. */ public static List<String> toHanyuPinyin(String chinese) { // Is this an empty string? if (chinese.isEmpty()) { return new ArrayList<String>(); } // Use StringBuilder instead of String during processing for speed // optimization. List<StringBuilder> stringBuilders = null; for (int i = 0, length = chinese.length(); i < length; i++) { final char c = chinese.charAt(i); String[] pinyins = null; final java.util.Set<Character> set = new java.util.HashSet<Character>(); // Is this Chinese character? if (CharUtils.isAscii(c)) { if (CharUtils.isAsciiAlphanumeric(c)) { // We are only interested in 'abc' and '123'. set.add(c); } } else { // This is possible a Chinese character. try { pinyins = PinyinHelper.toHanyuPinyinStringArray(c, DEFAULT_HANYU_PINYIN_OUTPUT_FORMAT); if (pinyins != null) { for (String pinyin : pinyins) { set.add(pinyin.charAt(0)); } } } catch (BadHanyuPinyinOutputFormatCombination ex) { log.error(null, ex); // No. This is not Chinese character. // Just ignore the error. Continue for the rest of characters. // return new ArrayList<String>(); } } final List<StringBuilder> tmps = stringBuilders; stringBuilders = new ArrayList<StringBuilder>(); if (tmps == null) { // This will be the first converted character. for (Character character : set) { final StringBuilder me = new StringBuilder(); me.append(character); stringBuilders.add(me); } } else { for (Character character : set) { for (StringBuilder tmp : tmps) { final StringBuilder me = new StringBuilder(); me.append(tmp); me.append(character); stringBuilders.add(me); } } } } List<String> result = new ArrayList<String>(); // Do we have any converted characters? if (stringBuilders != null) { for (StringBuilder stringBuilder : stringBuilders) { result.add(stringBuilder.toString()); } } return result; } /** * Returns default currency symbol, regardless what country we are in right * now. * * @return Default currency symbol, regardless what country we are in right * now. */ public static String getDefaultCurrencySymbol() { return "$"; } public static String commonDateFormat(long time) { return commonDateFormat.get().format(time); } public static String commonDateFormat(Date date) { return commonDateFormat.get().format(date); } public static Date commonDateParse(String string) { try { return commonDateFormat.get().parse(string); } catch (ParseException e) { log.error(null, e); try { return legacyCommonDateFormat.get().parse(string); } catch (ParseException e1) { log.error(null, e1); } } return null; } public static boolean isToday(long timestamp) { Calendar calendar = Calendar.getInstance(); int date = calendar.get(Calendar.DATE); int month = calendar.get(Calendar.MONTH); int year = calendar.get(Calendar.YEAR); calendar.setTimeInMillis(timestamp); int _date = calendar.get(Calendar.DATE); int _month = calendar.get(Calendar.MONTH); int _year = calendar.get(Calendar.YEAR); return date == _date && month == _month && year == _year; } private static Gson getGsonForStockInfoDatabaseMeta() { final Gson gson = new GsonBuilder().registerTypeAdapter(new TypeToken<EnumMap<Country, Long>>() { }.getType(), new EnumMapInstanceCreator<Country, Long>(Country.class)).create(); return gson; } public static List<Country> getSupportedStockMarketCountries() { java.util.List<Country> countries = new ArrayList<>(Arrays.asList(Country.values())); // Czech and Hungary are only for currency exchange purpose. countries.remove(Country.Czech); countries.remove(Country.Hungary); // Spain are no longer supported. countries.remove(Country.Spain); return countries; } public static Map<Country, Long> loadStockInfoDatabaseMeta(String json) { final Gson gson = getGsonForStockInfoDatabaseMeta(); Map<Country, Long> stockInfoDatabaseMeta = null; try { stockInfoDatabaseMeta = gson.fromJson(json, new TypeToken<EnumMap<Country, Long>>() { }.getType()); } catch (Exception ex) { log.error(null, ex); } if (stockInfoDatabaseMeta == null) { return java.util.Collections.emptyMap(); } return stockInfoDatabaseMeta; } public static Map<Country, Long> loadStockInfoDatabaseMeta(File stockInfoDatabaseMetaFile) { final Gson gson = getGsonForStockInfoDatabaseMeta(); Map<Country, Long> stockInfoDatabaseMeta = null; try { //If the constructor throws an exception, the finally block will NOT execute //BufferedReader reader = new BufferedReader(new FileReader(file)); BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(stockInfoDatabaseMetaFile), "UTF-8")); try { stockInfoDatabaseMeta = gson.fromJson(reader, new TypeToken<EnumMap<Country, Long>>() { }.getType()); } finally { //no need to check for null //any exceptions thrown here will be caught by //the outer catch block reader.close(); } } catch (IOException ex) { log.error(null, ex); } catch (com.google.gson.JsonSyntaxException ex) { log.error(null, ex); } if (stockInfoDatabaseMeta == null) { return java.util.Collections.emptyMap(); } return stockInfoDatabaseMeta; } public static Font getRobotoLightFont() { if (ROBOTO_LIGHT_FONT == null) { ROBOTO_LIGHT_FONT = _getRobotoLightFont(); } return ROBOTO_LIGHT_FONT; } private static Font _getRobotoLightFont() { InputStream inputStream = Utils.class.getResourceAsStream("/assets/fonts/Roboto-Light.ttf"); try { Font font = Font.createFont(Font.TRUETYPE_FONT, inputStream); if (font != null) { return font; } } catch (FontFormatException ex) { log.error(null, ex); } catch (IOException ex) { log.error(null, ex); } finally { org.yccheok.jstock.file.Utils.close(inputStream); } Font oldLabelFont = UIManager.getFont("Label.font"); return oldLabelFont; } public static boolean saveStockInfoDatabaseMeta(File stockInfoDatabaseMetaFile, Map<Country, Long> stockInfoDatabaseMeta) { final Gson gson = getGsonForStockInfoDatabaseMeta(); String string = gson.toJson(stockInfoDatabaseMeta); try { //If the constructor throws an exception, the finally block will NOT execute BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(stockInfoDatabaseMetaFile), "UTF-8")); try { writer.write(string); } finally { writer.close(); } } catch (IOException ex) { log.error(null, ex); return false; } return true; } /** * Returns time format used in status bar. * * @return time format used in status bar */ public static DateFormat getTodayLastUpdateTimeFormat() { return todayLastUpdateTimeFormat.get(); } public static DateFormat getOtherDayLastUpdateTimeFormat() { return otherDayLastUpdateTimeFormat.get(); } private static class EnumMapInstanceCreator<K extends Enum<K>, V> implements InstanceCreator<EnumMap<K, V>> { private final Class<K> enumClazz; public EnumMapInstanceCreator(final Class<K> enumClazz) { super(); this.enumClazz = enumClazz; } @Override public EnumMap<K, V> createInstance(final java.lang.reflect.Type type) { return new EnumMap<K, V>(enumClazz); } } /** * Represents latest application information. This is being used for * application upgrading. */ public static class ApplicationInfo { /** * ID to represent application version. */ public final int applicationVersionID; /** * URL link to download Windows application version <code>applicationVersionID</code> */ public final String windowsDownloadLink; /** * URL link to download Linux application version <code>applicationVersionID</code> */ public final String linuxDownloadLink; /** * URL link to download Mac application version <code>applicationVersionID</code> */ public final String macDownloadLink; /** * URL link to download Solaris application version <code>applicationVersionID</code> */ public final String solarisDownloadLink; /** * Constructs application information object. * * @param applicationVersionID ID to represent application version * @param windowsDownloadLink URL link to download Windows application * @param linuxDownloadLink URL link to download Linux application * @param macDownloadLink URL link to download Mac application * @param solarisDownloadLink URL link to download Solaris application */ public ApplicationInfo(int applicationVersionID, String windowsDownloadLink, String linuxDownloadLink, String macDownloadLink, String solarisDownloadLink) { this.applicationVersionID = applicationVersionID; this.windowsDownloadLink = windowsDownloadLink; this.linuxDownloadLink = linuxDownloadLink; this.macDownloadLink = macDownloadLink; this.solarisDownloadLink = solarisDownloadLink; } } // Use ThreadLocal to ensure thread safety. @Deprecated private static final ThreadLocal<DateFormat> legacyCommonDateFormat = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { // We will use a fixed date format (Locale.English), so that it will be // easier for Android to process. // // "Sep 5, 2011" - Locale.ENGLISH // "2011-9-5" - Locale.SIMPLIFIED_CHINESE // "2011/9/5" - Locale.TRADITIONAL_CHINESE // 05.09.2011 - Locale.GERMAN DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale.ENGLISH); return dateFormat; } }; private static final ThreadLocal<DateFormat> commonDateFormat = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH); } }; private static final ThreadLocal<DateFormat> todayLastUpdateTimeFormat = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("h:mm a"); } }; private static final ThreadLocal<DateFormat> otherDayLastUpdateTimeFormat = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return DateFormat.getDateInstance(DateFormat.SHORT); } }; public static Font ROBOTO_LIGHT_FONT = null; private static final HanyuPinyinOutputFormat DEFAULT_HANYU_PINYIN_OUTPUT_FORMAT = new HanyuPinyinOutputFormat(); static { DEFAULT_HANYU_PINYIN_OUTPUT_FORMAT.setCaseType(HanyuPinyinCaseType.LOWERCASE); DEFAULT_HANYU_PINYIN_OUTPUT_FORMAT.setToneType(HanyuPinyinToneType.WITHOUT_TONE); DEFAULT_HANYU_PINYIN_OUTPUT_FORMAT.setVCharType(HanyuPinyinVCharType.WITH_V); } private static volatile List<String> NTPServers = null; // We will use this as directory name. Do not have space or special characters. private static final String APPLICATION_VERSION_STRING = "1.0.7"; // 1.0.7e // Remember to update isCloudFileCompatible method. private static final int CLOUD_FILE_VERSION_ID = 1107; //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// private static final String ABOUT_BOX_VERSION_STRING = "1.0.7.17"; // 1.0.7.17 // For About box comparision on latest version purpose. private static final int APPLICATION_VERSION_ID = 1146; //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// private static final Executor zombiePool = Executors.newFixedThreadPool(Utils.NUM_OF_THREADS_ZOMBIE_POOL); private static final int NUM_OF_THREADS_ZOMBIE_POOL = 4; private static final HttpClient httpClient; private static final HttpClient httpClientWithAgentInfo; /** Maximum loop count when creating temp directories. */ private static final int TEMP_DIR_ATTEMPTS = 10000; static { MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager(); multiThreadedHttpConnectionManager.getParams().setMaxTotalConnections(128); multiThreadedHttpConnectionManager.getParams().setDefaultMaxConnectionsPerHost(128); httpClient = new HttpClient(multiThreadedHttpConnectionManager); // To prevent cookie warnings. httpClient.getParams().setParameter("http.protocol.single-cookie-header", true); httpClient.getParams() .setCookiePolicy(org.apache.commons.httpclient.cookie.CookiePolicy.BROWSER_COMPATIBILITY); multiThreadedHttpConnectionManager.getParams().setMaxConnectionsPerHost(httpClient.getHostConfiguration(), 128); } static { MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager(); multiThreadedHttpConnectionManager.getParams().setMaxTotalConnections(128); multiThreadedHttpConnectionManager.getParams().setDefaultMaxConnectionsPerHost(128); httpClientWithAgentInfo = new HttpClient(multiThreadedHttpConnectionManager); // Provide agent information, as requested by KLSEInfo owner. httpClientWithAgentInfo.getParams().setParameter(HttpMethodParams.USER_AGENT, "JStock-1.0.6o"); // To prevent cookie warnings. httpClientWithAgentInfo.getParams().setParameter("http.protocol.single-cookie-header", true); httpClientWithAgentInfo.getParams() .setCookiePolicy(org.apache.commons.httpclient.cookie.CookiePolicy.BROWSER_COMPATIBILITY); multiThreadedHttpConnectionManager.getParams() .setMaxConnectionsPerHost(httpClientWithAgentInfo.getHostConfiguration(), 128); } // http://stackoverflow.com/questions/1360113/is-java-regex-thread-safe private static final Pattern googleDocTitlePattern = Pattern.compile( "jstock-" + getJStockUUID() + "-checksum=([0-9]+)-date=([0-9]+)-version=([0-9]+)\\.zip", Pattern.CASE_INSENSITIVE); private static final Log log = LogFactory.getLog(Utils.class); }