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.file; import au.com.bytecode.opencsv.CSVReader; import au.com.bytecode.opencsv.CSVWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.swing.table.TableModel; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRichTextString; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.yccheok.jstock.engine.Board; import org.yccheok.jstock.engine.Code; import org.yccheok.jstock.engine.Stock; import org.yccheok.jstock.engine.Industry; import org.yccheok.jstock.engine.StockHistoryServer; import org.yccheok.jstock.engine.StockInfo; import org.yccheok.jstock.engine.StockInfoDatabase; import org.yccheok.jstock.engine.StockNameDatabase; import org.yccheok.jstock.engine.Symbol; import org.yccheok.jstock.file.GUIBundleWrapper.Language; import org.yccheok.jstock.gui.BackwardCompatible; import org.yccheok.jstock.gui.POIUtils; import org.yccheok.jstock.engine.Pair; import org.yccheok.jstock.gui.StockTableModel; import org.yccheok.jstock.gui.portfolio.CommentableContainer; import org.yccheok.jstock.gui.treetable.BuyPortfolioTreeTableModelEx; import org.yccheok.jstock.gui.treetable.SellPortfolioTreeTableModelEx; import org.yccheok.jstock.portfolio.Portfolio; import org.yccheok.jstock.portfolio.PortfolioInfo; import org.yccheok.jstock.portfolio.PortfolioRealTimeInfo; import org.yccheok.jstock.portfolio.Transaction; import org.yccheok.jstock.portfolio.TransactionSummary; import org.yccheok.jstock.watchlist.WatchlistInfo; /** * * @author yccheok */ public class Statements { public static final Statements UNKNOWN_STATEMENTS = new Statements(Statement.Type.Unknown, GUIBundleWrapper.newInstance(Language.DEFAULT)); public static class StatementsEx { public final Statements statements; public final String title; public StatementsEx(Statements statements, String title) { if (statements == null || title == null) { throw new java.lang.IllegalArgumentException(); } this.statements = statements; this.title = title; } } /** * Prevent client from constructing Statements other than static factory * method. */ private Statements(Statement.Type type, GUIBundleWrapper guiBundleWrapper) { this.type = type; this.guiBundleWrapper = guiBundleWrapper; } public static Statements newInstanceFromBuyPortfolioTreeTableModel( BuyPortfolioTreeTableModelEx buyPortfolioTreeTableModel, PortfolioRealTimeInfo portfolioRealTimeInfo, boolean languageIndependent) { final GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance( languageIndependent ? GUIBundleWrapper.Language.INDEPENDENT : GUIBundleWrapper.Language.DEFAULT); final String[] tmp = { guiBundleWrapper.getString("MainFrame_Code"), guiBundleWrapper.getString("MainFrame_Symbol"), guiBundleWrapper.getString("PortfolioManagementJPanel_Date"), guiBundleWrapper.getString("PortfolioManagementJPanel_Units"), guiBundleWrapper.getString("PortfolioManagementJPanel_PurchasePrice"), guiBundleWrapper.getString("PortfolioManagementJPanel_CurrentPrice"), guiBundleWrapper.getString("PortfolioManagementJPanel_PurchaseValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_CurrentValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_GainLossPrice"), guiBundleWrapper.getString("PortfolioManagementJPanel_GainLossValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_GainLossPercentage"), guiBundleWrapper.getString("PortfolioManagementJPanel_Broker"), guiBundleWrapper.getString("PortfolioManagementJPanel_ClearingFee"), guiBundleWrapper.getString("PortfolioManagementJPanel_StampDuty"), guiBundleWrapper.getString("PortfolioManagementJPanel_NetPurchaseValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_NetGainLossValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_NetGainLossPercentage"), guiBundleWrapper.getString("PortfolioManagementJPanel_Comment") }; Statement.What what = Statement.what(Arrays.asList(tmp)); final Statements statements = new Statements(what.type, what.guiBundleWrapper); final Portfolio portfolio = (Portfolio) buyPortfolioTreeTableModel.getRoot(); final int summaryCount = portfolio.getChildCount(); for (int i = 0; i < summaryCount; i++) { Object o = portfolio.getChildAt(i); final TransactionSummary transactionSummary = (TransactionSummary) o; // Metadatas will be used to store TransactionSummary's comment. final String comment = transactionSummary.getComment().trim(); if (comment.isEmpty() == false) { final Stock stock = ((Transaction) transactionSummary.getChildAt(0)).getStock(); statements.metadatas.put(stock.code.toString(), comment); } final int transactionCount = transactionSummary.getChildCount(); for (int j = 0; j < transactionCount; j++) { final Transaction transaction = (Transaction) transactionSummary.getChildAt(j); final Stock stock = transaction.getStock(); final boolean shouldConvertPenceToPound = org.yccheok.jstock.portfolio.Utils .shouldConvertPenceToPound(portfolioRealTimeInfo, stock.code); final List<Atom> atoms = new ArrayList<>(); atoms.add(new Atom(stock.code.toString(), tmp[0])); atoms.add(new Atom(stock.symbol.toString(), tmp[1])); final String dateString = transaction.getDate() != null ? org.yccheok.jstock.gui.Utils.commonDateFormat(transaction.getDate().getTime()) : ""; atoms.add(new Atom(dateString, tmp[2])); atoms.add(new Atom(transaction.getQuantity(), tmp[3])); atoms.add(new Atom(transaction.getPrice(), tmp[4])); atoms.add(new Atom(buyPortfolioTreeTableModel.getCurrentPrice(transaction), tmp[5])); if (shouldConvertPenceToPound == false) { atoms.add(new Atom(transaction.getTotal(), tmp[6])); } else { atoms.add(new Atom(transaction.getTotal() / 100.0, tmp[6])); } atoms.add(new Atom(buyPortfolioTreeTableModel.getCurrentValue(transaction), tmp[7])); atoms.add(new Atom(buyPortfolioTreeTableModel.getGainLossPrice(transaction), tmp[8])); if (shouldConvertPenceToPound == false) { atoms.add(new Atom(buyPortfolioTreeTableModel.getGainLossValue(transaction), tmp[9])); } else { atoms.add(new Atom(buyPortfolioTreeTableModel.getGainLossValue(transaction) / 100.0, tmp[9])); } atoms.add(new Atom(buyPortfolioTreeTableModel.getGainLossPercentage(transaction), tmp[10])); atoms.add(new Atom(transaction.getBroker(), tmp[11])); atoms.add(new Atom(transaction.getClearingFee(), tmp[12])); atoms.add(new Atom(transaction.getStampDuty(), tmp[13])); if (shouldConvertPenceToPound == false) { atoms.add(new Atom(transaction.getNetTotal(), tmp[14])); atoms.add(new Atom(buyPortfolioTreeTableModel.getNetGainLossValue(transaction), tmp[15])); } else { atoms.add(new Atom(transaction.getNetTotal() / 100.0, tmp[14])); atoms.add( new Atom(buyPortfolioTreeTableModel.getNetGainLossValue(transaction) / 100.0, tmp[15])); } atoms.add(new Atom(buyPortfolioTreeTableModel.getNetGainLossPercentage(transaction), tmp[16])); atoms.add(new Atom(transaction.getComment(), tmp[17])); final Statement statement = new Statement(atoms); if (statements.getType() != statement.getType()) { return UNKNOWN_STATEMENTS; } statements.statements.add(statement); } } return statements; } /** * Construct Statements based on given stock history server. * * @param server stock history server * @param languageIndependent should the returned statements be language * independent? * @return the constructed Statements. UNKNOWN_STATEMENTS if fail */ public static Statements newInstanceFromStockHistoryServer(StockHistoryServer server, boolean languageIndependent) { final GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance( languageIndependent ? GUIBundleWrapper.Language.INDEPENDENT : GUIBundleWrapper.Language.DEFAULT); final Statements s = new Statements(Statement.Type.StockHistory, guiBundleWrapper); final int size = server.size(); Stock stock = null; for (int i = 0; i < size; i++) { final long timestamp = server.getTimestamp(i); stock = server.getStock(timestamp); assert (timestamp != 0 && stock != null); final List<Atom> atoms = new ArrayList<Atom>(); final Atom atom0 = new Atom(org.yccheok.jstock.gui.Utils.commonDateFormat(timestamp), guiBundleWrapper.getString("StockHistory_Date")); final Atom atom1 = new Atom(Double.valueOf(stock.getOpenPrice()), guiBundleWrapper.getString("StockHistory_Open")); final Atom atom2 = new Atom(Double.valueOf(stock.getHighPrice()), guiBundleWrapper.getString("StockHistory_High")); final Atom atom3 = new Atom(Double.valueOf(stock.getLowPrice()), guiBundleWrapper.getString("StockHistory_Low")); final Atom atom4 = new Atom(Double.valueOf(stock.getLastPrice()), guiBundleWrapper.getString("StockHistory_Close")); // TODO: CRITICAL LONG BUG REVISED NEEDED. final Atom atom5 = new Atom(Long.valueOf(stock.getVolume()), guiBundleWrapper.getString("StockHistory_Volume")); atoms.add(atom0); atoms.add(atom1); atoms.add(atom2); atoms.add(atom3); atoms.add(atom4); atoms.add(atom5); Statement statement = new Statement(atoms); // They should be the same type. The checking just act as paranoid. if (s.getType() != statement.getType()) { throw new java.lang.RuntimeException("" + statement.getType()); } s.statements.add(statement); } if (stock != null) { // Metadata. Oh yeah... s.metadatas.put("code", stock.code.toString()); s.metadatas.put("symbol", stock.symbol.toString()); s.metadatas.put("name", stock.getName()); s.metadatas.put("board", stock.getBoard().name()); s.metadatas.put("industry", stock.getIndustry().name()); } return s; } /** * Construct Statements based on given Excel File. * * @param file Given Excel File * @return the List of constructed Statements. Empty list if fail. */ public static List<Statements> newInstanceFromExcelFile(File file) { FileInputStream fileInputStream = null; final List<Statements> statementsList = new ArrayList<>(); try { fileInputStream = new FileInputStream(file); final POIFSFileSystem fs = new POIFSFileSystem(fileInputStream); final HSSFWorkbook wb = new HSSFWorkbook(fs); final int numberOfSheets = wb.getNumberOfSheets(); for (int k = 0; k < numberOfSheets; k++) { final HSSFSheet sheet = wb.getSheetAt(k); final int startRow = sheet.getFirstRowNum(); final int endRow = sheet.getLastRowNum(); // If there are 3 rows, endRow will be 2. // We must have at least 2 rows. (endRow = 1) if (startRow != 0 || endRow <= startRow) { continue; } final HSSFRow row = sheet.getRow(startRow); if (row == null) { continue; } final int startCell = row.getFirstCellNum(); final int endCell = row.getLastCellNum(); // If there are 2 cols, endCell will be 2. // We must have at least 1 col. (endCell = 1) if (startCell != 0 || endCell <= startCell) { continue; } final List<String> types = new ArrayList<String>(); for (int i = startCell; i < endCell; i++) { final HSSFCell cell = row.getCell(i); if (cell == null) { continue; } // Exception may be thrown here, as cell may be numerical value. final String type = cell.getRichStringCellValue().getString(); if (type != null) { types.add(type); } } if (types.isEmpty()) { continue; } if (types.size() != (endCell - startCell)) { continue; } final Statement.What what = Statement.what(types); Statements s = new Statements(what.type, what.guiBundleWrapper); for (int i = startRow + 1; i <= endRow; i++) { final HSSFRow r = sheet.getRow(i); if (r == null) { continue; } final List<Atom> atoms = new ArrayList<Atom>(); for (int j = startCell; j < endCell; j++) { final HSSFCell cell = r.getCell(j); if (cell == null) { continue; } Object value = null; if (cell.getCellType() == HSSFCell.CELL_TYPE_STRING) { final HSSFRichTextString richString = cell.getRichStringCellValue(); if (richString != null) { value = richString.getString(); } else { value = ""; } } else if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC) { try { value = new Double(cell.getNumericCellValue()); } catch (NumberFormatException ex) { log.error(null, ex); value = new Double(0.0); } } else { } if (null == value) { continue; } atoms.add(new Atom(value, types.get(j - startCell))); } final Statement statement = new Statement(atoms); if (s.getType() != statement.getType()) { // Give up. s = null; break; } s.statements.add(statement); } // for (int i = startRow + 1; i <= endRow; i++) if (s != null) { statementsList.add(s); } } /* for(int k = 0; k < numberOfSheets; k++) */ } catch (Exception ex) { log.error(null, ex); } finally { Utils.close(fileInputStream); } return statementsList; } /** * Construct Statements based on given CSV File. * * @param file Given CSV File * @return the constructed Statements. UNKNOWN_STATEMENTS if fail */ public static Statements newInstanceFromCSVFile(File file) { // FIXME : final boolean needToPerformBackwardCompatible = BackwardCompatible.needToPerformBackwardCompatible(file); final boolean needToHandleMetadata = BackwardCompatible.needToHandleMetadata(file); boolean status = false; FileInputStream fileInputStream = null; InputStreamReader inputStreamReader = null; CSVReader csvreader = null; Statements s = null; final ThreadSafeFileLock.Lock lock = ThreadSafeFileLock.getLock(file); if (lock == null) { return UNKNOWN_STATEMENTS; } // http://stackoverflow.com/questions/10868423/lock-lock-before-try ThreadSafeFileLock.lockRead(lock); try { fileInputStream = new FileInputStream(file); inputStreamReader = new InputStreamReader(fileInputStream, Charset.forName("UTF-8")); csvreader = new CSVReader(inputStreamReader); final List<String> types = new ArrayList<String>(); String[] nextLine; Map<String, String> metadatas = new LinkedHashMap<String, String>(); if ((nextLine = csvreader.readNext()) != null) { // Metadata handling. while (nextLine != null && nextLine.length == 1) { String[] tokens = nextLine[0].split("=", 2); if (tokens.length == 2) { String key = tokens[0].trim(); String value = tokens[1].trim(); // FIXME : if (needToHandleMetadata) { key = BackwardCompatible.toGoogleCodeIfPossible(key); } if (key.length() > 0) { // Is OK for value to be empty. metadatas.put(key, value); nextLine = csvreader.readNext(); } else { break; } } else { break; } } if (nextLine != null) { types.addAll(Arrays.asList(nextLine)); } } /* if ((nextLine = csvreader.readNext()) != null) */ if (types.isEmpty()) { return UNKNOWN_STATEMENTS; } else { Statement.What what = Statement.what(types); s = new Statements(what.type, what.guiBundleWrapper); } while ((nextLine = csvreader.readNext()) != null) { // Shall we continue to ignore, or shall we just return null to // flag an error? if (nextLine.length != types.size()) { // Give a warning message. log.error("Incorrect CSV format. There should be exactly " + types.size() + " item(s)"); continue; } int i = 0; final List<Atom> atoms = new ArrayList<Atom>(); for (String value : nextLine) { final String type = types.get(i++); // FIXME : if (needToPerformBackwardCompatible && type.equals("Code")) { value = BackwardCompatible.toGoogleCodeIfPossible(value); } final Atom atom = new Atom(value, type); atoms.add(atom); } final Statement statement = new Statement(atoms); if (s.getType() != statement.getType()) { // Doesn't not match. return UNKNOWN_STATEMENTS; } s.statements.add(statement); } // Pump in metadata. s.metadatas.putAll(metadatas); status = true; } catch (IOException ex) { log.error(null, ex); } finally { if (csvreader != null) { try { csvreader.close(); } catch (IOException ex) { log.error(null, ex); } } Utils.close(inputStreamReader); Utils.close(fileInputStream); ThreadSafeFileLock.unlockRead(lock); ThreadSafeFileLock.releaseLock(lock); } if (status) { return s; } return UNKNOWN_STATEMENTS; } /** * Construct Statements based on given stock pairs. * * @param tableModel give stock pairs * @return the constructed Statements. UNKNOWN_STATEMENTS if fail */ public static Statements newInstanceFromUserDefinedDatabase(java.util.List<Pair<Code, Symbol>> pairs) { GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance(GUIBundleWrapper.Language.INDEPENDENT); Statements s = new Statements(Statement.Type.UserDefinedDatabase, guiBundleWrapper); final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String symbol_string = guiBundleWrapper.getString("MainFrame_Symbol"); for (Pair<Code, Symbol> pair : pairs) { final List<Atom> atoms = new ArrayList<Atom>(); atoms.add(new Atom(pair.first, code_string)); atoms.add(new Atom(pair.second, symbol_string)); Statement statement = new Statement(atoms); // They should be the same type. The checking just act as paranoid. if (s.getType() != statement.getType()) { throw new java.lang.RuntimeException("" + statement.getType()); } s.statements.add(statement); } return s; } public static Statements newInstanceFromStockNameDatabase(StockNameDatabase stockNameDatabase) { GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance(GUIBundleWrapper.Language.INDEPENDENT); Statements s = new Statements(Statement.Type.StockNameDatabase, guiBundleWrapper); final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String name_string = guiBundleWrapper.getString("MainFrame_Name"); for (Map.Entry<Code, String> entry : stockNameDatabase.getCodeToName().entrySet()) { final Code code = entry.getKey(); final String name = entry.getValue(); final List<Atom> atoms = new ArrayList<Atom>(); atoms.add(new Atom(code, code_string)); atoms.add(new Atom(name, name_string)); Statement statement = new Statement(atoms); // They should be the same type. The checking just act as paranoid. if (s.getType() != statement.getType()) { throw new java.lang.RuntimeException("" + statement.getType()); } s.statements.add(statement); } return s; } public static Statements newInstanceFromWatchlistInfos(List<WatchlistInfo> wathclistInfos) { GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance(GUIBundleWrapper.Language.INDEPENDENT); Statements s = new Statements(Statement.Type.WatchlistInfos, guiBundleWrapper); final String country_string = guiBundleWrapper.getString("WatchlistInfo_Country"); final String name_string = guiBundleWrapper.getString("WatchlistInfo_Name"); final String size_string = guiBundleWrapper.getString("WatchlistInfo_Size"); for (WatchlistInfo watchlistInfo : wathclistInfos) { final List<Atom> atoms = new ArrayList<Atom>(); atoms.add(new Atom(watchlistInfo.country, country_string)); atoms.add(new Atom(watchlistInfo.name, name_string)); atoms.add(new Atom(watchlistInfo.size, size_string)); Statement statement = new Statement(atoms); // They should be the same type. The checking just act as paranoid. if (s.getType() != statement.getType()) { throw new java.lang.RuntimeException("" + statement.getType()); } s.statements.add(statement); } return s; } public static Statements newInstanceFromPortfolioInfos(List<PortfolioInfo> portfolioInfos) { GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance(GUIBundleWrapper.Language.INDEPENDENT); Statements s = new Statements(Statement.Type.PortfolioInfos, guiBundleWrapper); final String country_string = guiBundleWrapper.getString("PortfolioInfo_Country"); final String name_string = guiBundleWrapper.getString("PortfolioInfo_Name"); final String size_string = guiBundleWrapper.getString("PortfolioInfo_Size"); for (PortfolioInfo portfolioInfo : portfolioInfos) { final List<Atom> atoms = new ArrayList<Atom>(); atoms.add(new Atom(portfolioInfo.country, country_string)); atoms.add(new Atom(portfolioInfo.name, name_string)); atoms.add(new Atom(portfolioInfo.size, size_string)); Statement statement = new Statement(atoms); // They should be the same type. The checking just act as paranoid. if (s.getType() != statement.getType()) { throw new java.lang.RuntimeException("" + statement.getType()); } s.statements.add(statement); } return s; } /** * Construct Statements based on given stock info database. * * @param tableModel give stock info database * @return the constructed Statements. UNKNOWN_STATEMENTS if fail */ public static Statements newInstanceFromStockInfoDatabase(StockInfoDatabase stockInfoDatabase) { GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance(GUIBundleWrapper.Language.INDEPENDENT); Statements s = new Statements(Statement.Type.StockInfoDatabase, guiBundleWrapper); // Build mechanism, to retrieve StockInfo's Board and Industry. Map<StockInfo, Industry> stockInfo2Industry = new HashMap<StockInfo, Industry>(); Map<StockInfo, Board> stockInfo2Board = new HashMap<StockInfo, Board>(); List<Industry> industries = stockInfoDatabase.getIndustries(); List<Board> boards = stockInfoDatabase.getBoards(); for (Industry industry : industries) { List<StockInfo> stockInfos = stockInfoDatabase.getStockInfos(industry); for (StockInfo stockInfo : stockInfos) { stockInfo2Industry.put(stockInfo, industry); } } for (Board board : boards) { List<StockInfo> stockInfos = stockInfoDatabase.getStockInfos(board); for (StockInfo stockInfo : stockInfos) { stockInfo2Board.put(stockInfo, board); } } final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String symbol_string = guiBundleWrapper.getString("MainFrame_Symbol"); final String industry_string = guiBundleWrapper.getString("MainFrame_Industry"); final String board_string = guiBundleWrapper.getString("MainFrame_Board"); List<StockInfo> stockInfos = stockInfoDatabase.getStockInfos(); for (StockInfo stockInfo : stockInfos) { Industry industry = stockInfo2Industry.get(stockInfo); Board board = stockInfo2Board.get(stockInfo); if (industry == null) { // Shouldn't happen. industry = Industry.Unknown; } if (board == null) { // Shouldn't happen. board = Board.Unknown; } final List<Atom> atoms = new ArrayList<Atom>(); atoms.add(new Atom(stockInfo.code, code_string)); atoms.add(new Atom(stockInfo.symbol, symbol_string)); // Do not use toString, as we had overridden toString. atoms.add(new Atom(industry.name(), industry_string)); atoms.add(new Atom(board.name(), board_string)); Statement statement = new Statement(atoms); // They should be the same type. The checking just act as paranoid. if (s.getType() != statement.getType()) { throw new java.lang.RuntimeException("" + statement.getType()); } s.statements.add(statement); } return s; } /** * Construct Statements based on given stock price. * * @param tableModel give stock price * @return the constructed Statements. UNKNOWN_STATEMENTS if fail */ public static Statements newInstanceFromStockPrices(Map<Code, Double> stockPrices, long timestamp) { GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance(GUIBundleWrapper.Language.INDEPENDENT); Statements s = new Statements(Statement.Type.StockPrice, guiBundleWrapper); s.metadatas.put("timestamp", Long.toString(timestamp)); final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String last_string = guiBundleWrapper.getString("MainFrame_Last"); for (Map.Entry<Code, Double> stockPrice : stockPrices.entrySet()) { Code key = stockPrice.getKey(); Double value = stockPrice.getValue(); final List<Atom> atoms = new ArrayList<Atom>(); atoms.add(new Atom(key.toString(), code_string)); atoms.add(new Atom(value.toString(), last_string)); Statement statement = new Statement(atoms); // They should be the same type. The checking just act as paranoid. if (s.getType() != statement.getType()) { throw new java.lang.RuntimeException("" + statement.getType()); } s.statements.add(statement); } return s; } /** * Construct Statements based on given TableModel. * * @param tableModel given TableModel * @return the constructed Statements. UNKNOWN_STATEMENTS if fail */ public static Statements newInstanceFromTableModel(TableModel tableModel, boolean languageIndependent) { final CSVHelper csvHelper = (CSVHelper) tableModel; final GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance( languageIndependent ? GUIBundleWrapper.Language.INDEPENDENT : GUIBundleWrapper.Language.DEFAULT); final int column = tableModel.getColumnCount(); final int row = tableModel.getRowCount(); List<String> strings = new ArrayList<String>(); for (int i = 0; i < column; i++) { final String type = languageIndependent ? csvHelper.getLanguageIndependentColumnName(i) : tableModel.getColumnName(i); final Class c = tableModel.getColumnClass(i); if (c.equals(Stock.class)) { final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String symbol_string = guiBundleWrapper.getString("MainFrame_Symbol"); strings.add(code_string); strings.add(symbol_string); } if (c.equals(StockInfo.class)) { final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String symbol_string = guiBundleWrapper.getString("MainFrame_Symbol"); strings.add(code_string); strings.add(symbol_string); } else { strings.add(type); } } // Comment handling. CommentableContainer commentableContainer = null; if (tableModel instanceof CommentableContainer) { commentableContainer = (CommentableContainer) tableModel; } Statement.What what = Statement.what(strings); final Statements s = new Statements(what.type, what.guiBundleWrapper); for (int i = 0; i < row; i++) { final List<Atom> atoms = new ArrayList<Atom>(); for (int j = 0; j < column; j++) { final String type = languageIndependent ? csvHelper.getLanguageIndependentColumnName(j) : tableModel.getColumnName(j); final Object object = tableModel.getValueAt(i, j); final Class c = tableModel.getColumnClass(j); if (c.equals(Stock.class)) { final Stock stock = (Stock) object; // There are no way to represent Stock in text form. We // will represent them in Code and Symbol. // Code first. Follow by symbol. final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String symbol_string = guiBundleWrapper.getString("MainFrame_Symbol"); atoms.add(new Atom(stock.code.toString(), code_string)); atoms.add(new Atom(stock.symbol.toString(), symbol_string)); } else if (c.equals(StockInfo.class)) { final StockInfo stockInfo = (StockInfo) object; final String code_string = guiBundleWrapper.getString("MainFrame_Code"); final String symbol_string = guiBundleWrapper.getString("MainFrame_Symbol"); atoms.add(new Atom(stockInfo.code.toString(), code_string)); atoms.add(new Atom(stockInfo.symbol.toString(), symbol_string)); } else if (c.equals(Date.class)) { atoms.add(new Atom(object != null ? org.yccheok.jstock.gui.Utils.commonDateFormat(((Date) object).getTime()) : "", type)); } else { // For fall below and rise above, null value is permitted. // Use empty string to represent null value. atoms.add(new Atom(object != null ? object : "", type)); } } // Comment handling. if (commentableContainer != null) { atoms.add(new Atom(commentableContainer.getCommentable(i).getComment(), guiBundleWrapper.getString("PortfolioManagementJPanel_Comment"))); } final Statement statement = new Statement(atoms); if (s.getType() != statement.getType()) { // Doesn't not match. Return UNKNOWN_STATEMENTS to indicate we fail to // construct Statements from TableModel. return UNKNOWN_STATEMENTS; } s.statements.add(statement); } // Any metadata? This is special hack since Android introduction. if (tableModel instanceof StockTableModel) { s.metadatas.put("timestamp", Long.toString(((StockTableModel) tableModel).getTimestamp())); } return s; } public static Statements newInstanceFromSellPortfolioTreeTableModel( SellPortfolioTreeTableModelEx sellPortfolioTreeTableModel, PortfolioRealTimeInfo portfolioRealTimeInfo, boolean languageIndependent) { final GUIBundleWrapper guiBundleWrapper = GUIBundleWrapper.newInstance( languageIndependent ? GUIBundleWrapper.Language.INDEPENDENT : GUIBundleWrapper.Language.DEFAULT); final String[] tmp = { guiBundleWrapper.getString("MainFrame_Code"), guiBundleWrapper.getString("MainFrame_Symbol"), guiBundleWrapper.getString("PortfolioManagementJPanel_ReferenceDate"), guiBundleWrapper.getString("PortfolioManagementJPanel_Date"), guiBundleWrapper.getString("PortfolioManagementJPanel_Units"), guiBundleWrapper.getString("PortfolioManagementJPanel_SellingPrice"), guiBundleWrapper.getString("PortfolioManagementJPanel_PurchasePrice"), guiBundleWrapper.getString("PortfolioManagementJPanel_SellingValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_PurchaseValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_PurchaseBroker"), guiBundleWrapper.getString("PortfolioManagementJPanel_PurchaseClearingFee"), guiBundleWrapper.getString("PortfolioManagementJPanel_PurchaseStampDuty"), guiBundleWrapper.getString("PortfolioManagementJPanel_GainLossPrice"), guiBundleWrapper.getString("PortfolioManagementJPanel_GainLossValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_GainLossPercentage"), guiBundleWrapper.getString("PortfolioManagementJPanel_Broker"), guiBundleWrapper.getString("PortfolioManagementJPanel_ClearingFee"), guiBundleWrapper.getString("PortfolioManagementJPanel_StampDuty"), guiBundleWrapper.getString("PortfolioManagementJPanel_NetSellingValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_NetGainLossValue"), guiBundleWrapper.getString("PortfolioManagementJPanel_NetGainLossPercentage"), guiBundleWrapper.getString("PortfolioManagementJPanel_Comment") }; Statement.What what = Statement.what(Arrays.asList(tmp)); final Statements statements = new Statements(what.type, what.guiBundleWrapper); final Portfolio portfolio = (Portfolio) sellPortfolioTreeTableModel.getRoot(); final int summaryCount = portfolio.getChildCount(); for (int i = 0; i < summaryCount; i++) { Object o = portfolio.getChildAt(i); final TransactionSummary transactionSummary = (TransactionSummary) o; // Metadatas will be used to store TransactionSummary's comment. final String comment = transactionSummary.getComment().trim(); if (comment.isEmpty() == false) { final Stock stock = ((Transaction) transactionSummary.getChildAt(0)).getStock(); statements.metadatas.put(stock.code.toString(), comment); } final int transactionCount = transactionSummary.getChildCount(); for (int j = 0; j < transactionCount; j++) { final Transaction transaction = (Transaction) transactionSummary.getChildAt(j); final Stock stock = transaction.getStock(); final boolean shouldConvertPenceToPound = org.yccheok.jstock.portfolio.Utils .shouldConvertPenceToPound(portfolioRealTimeInfo, stock.code); final List<Atom> atoms = new ArrayList<>(); atoms.add(new Atom(stock.code.toString(), tmp[0])); atoms.add(new Atom(stock.symbol.toString(), tmp[1])); final String referenceDateString = transaction.getReferenceDate() != null ? org.yccheok.jstock.gui.Utils.commonDateFormat(transaction.getReferenceDate().getTime()) : ""; atoms.add(new Atom(referenceDateString, tmp[2])); final String dateString = transaction.getDate() != null ? org.yccheok.jstock.gui.Utils.commonDateFormat(transaction.getDate().getTime()) : ""; atoms.add(new Atom(dateString, tmp[3])); atoms.add(new Atom(transaction.getQuantity(), tmp[4])); atoms.add(new Atom(transaction.getPrice(), tmp[5])); atoms.add(new Atom(transaction.getReferencePrice(), tmp[6])); if (shouldConvertPenceToPound == false) { atoms.add(new Atom(transaction.getTotal(), tmp[7])); atoms.add(new Atom(transaction.getReferenceTotal(), tmp[8])); } else { atoms.add(new Atom(transaction.getTotal() / 100.0, tmp[7])); atoms.add(new Atom(transaction.getReferenceTotal() / 100.0, tmp[8])); } atoms.add(new Atom(transaction.getReferenceBroker(), tmp[9])); atoms.add(new Atom(transaction.getReferenceClearingFee(), tmp[10])); atoms.add(new Atom(transaction.getReferenceStampDuty(), tmp[11])); atoms.add(new Atom(sellPortfolioTreeTableModel.getGainLossPrice(transaction), tmp[12])); if (shouldConvertPenceToPound == false) { atoms.add(new Atom(sellPortfolioTreeTableModel.getGainLossValue(transaction), tmp[13])); } else { atoms.add(new Atom(sellPortfolioTreeTableModel.getGainLossValue(transaction) / 100.0, tmp[13])); } atoms.add(new Atom(sellPortfolioTreeTableModel.getGainLossPercentage(transaction), tmp[14])); atoms.add(new Atom(transaction.getBroker(), tmp[15])); atoms.add(new Atom(transaction.getClearingFee(), tmp[16])); atoms.add(new Atom(transaction.getStampDuty(), tmp[17])); if (shouldConvertPenceToPound == false) { atoms.add(new Atom(transaction.getNetTotal(), tmp[18])); atoms.add(new Atom(sellPortfolioTreeTableModel.getNetGainLossValue(transaction), tmp[19])); } else { atoms.add(new Atom(transaction.getNetTotal() / 100.0, tmp[18])); atoms.add(new Atom(sellPortfolioTreeTableModel.getNetGainLossValue(transaction) / 100.0, tmp[19])); } atoms.add(new Atom(sellPortfolioTreeTableModel.getNetGainLossPercentage(transaction), tmp[20])); atoms.add(new Atom(transaction.getComment(), tmp[21])); final Statement statement = new Statement(atoms); if (statements.getType() != statement.getType()) { return UNKNOWN_STATEMENTS; } statements.statements.add(statement); } } return statements; } public boolean saveAsCSVFile(File file) { if (this.type == Statement.Type.Unknown) { return false; } boolean status = false; FileOutputStream fileOutputStream = null; OutputStreamWriter outputStreamWriter = null; CSVWriter csvwriter = null; final ThreadSafeFileLock.Lock lock = ThreadSafeFileLock.getLock(file); if (lock == null) { return false; } // http://stackoverflow.com/questions/10868423/lock-lock-before-try ThreadSafeFileLock.lockWrite(lock); try { fileOutputStream = new FileOutputStream(file); outputStreamWriter = new OutputStreamWriter(fileOutputStream, Charset.forName("UTF-8")); csvwriter = new CSVWriter(outputStreamWriter); for (Map.Entry<String, String> metadata : metadatas.entrySet()) { String key = metadata.getKey(); String value = metadata.getValue(); String output = key + "=" + value; csvwriter.writeNext(new String[] { output }); } // Do not obtain "type" through statements, as there is possible that // statements is empty. final List<String> strings = Statement.typeToStrings(this.getType(), this.getGUIBundleWrapper()); final int columnCount = strings.size(); String[] datas = new String[columnCount]; // First row. Print out table header. for (int i = 0; i < columnCount; i++) { datas[i] = strings.get(i); } csvwriter.writeNext(datas); final int rowCount = statements.size(); for (int i = 0; i < rowCount; i++) { for (int j = 0; j < columnCount; j++) { // Value shouldn't be null, as we prevent atom with null value. final String value = statements.get(i).getAtom(j).getValue().toString(); datas[j] = value; } csvwriter.writeNext(datas); } status = true; } catch (IOException ex) { log.error(null, ex); } finally { if (csvwriter != null) { try { csvwriter.close(); } catch (IOException ex) { log.error(null, ex); } } Utils.close(outputStreamWriter); Utils.close(fileOutputStream); ThreadSafeFileLock.unlockWrite(lock); ThreadSafeFileLock.releaseLock(lock); } return status; } public boolean saveAsExcelFile(File file, String title) { if (this.type == Statement.Type.Unknown) { return false; } final HSSFWorkbook wb = new HSSFWorkbook(); final HSSFSheet sheet = wb.createSheet(title); // Do not obtain "type" through statements, as there is possible that // statements is empty. final List<String> strings = Statement.typeToStrings(this.getType(), this.getGUIBundleWrapper()); final int columnCount = strings.size(); // First row. Print out table header. { final HSSFRow row = sheet.createRow(0); for (int i = 0; i < columnCount; i++) { row.createCell(i).setCellValue(new HSSFRichTextString(strings.get(i))); } } final int rowCount = statements.size(); for (int i = 0; i < rowCount; i++) { final HSSFRow row = sheet.createRow(i + 1); for (int j = 0; j < columnCount; j++) { // Value shouldn't be null, as we prevent atom with null value. final Object value = statements.get(i).getAtom(j).getValue(); final HSSFCell cell = row.createCell(j); POIUtils.invokeSetCellValue(cell, value); } } boolean status = false; FileOutputStream fileOut = null; final ThreadSafeFileLock.Lock lock = ThreadSafeFileLock.getLock(file); if (lock == null) { return false; } // http://stackoverflow.com/questions/10868423/lock-lock-before-try ThreadSafeFileLock.lockWrite(lock); try { fileOut = new FileOutputStream(file); wb.write(fileOut); status = true; } catch (FileNotFoundException ex) { log.error(null, ex); } catch (IOException ex) { log.error(null, ex); } finally { Utils.close(fileOut); ThreadSafeFileLock.unlockWrite(lock); ThreadSafeFileLock.releaseLock(lock); } return status; } public static boolean saveAsExcelFile(File file, List<StatementsEx> statementsExs) { final HSSFWorkbook wb = new HSSFWorkbook(); boolean needToWrite = false; for (StatementsEx statementsEx : statementsExs) { final String title = statementsEx.title; final Statements statements = statementsEx.statements; assert (statements != null); if (statements.getType() == Statement.Type.Unknown) { continue; } needToWrite = true; final HSSFSheet sheet = wb.createSheet(title); // Do not obtain "type" through statements, as there is possible that // statements is empty. final List<String> strings = Statement.typeToStrings(statements.getType(), statements.getGUIBundleWrapper()); final int columnCount = strings.size(); // First row. Print out table header. { final HSSFRow row = sheet.createRow(0); for (int i = 0; i < columnCount; i++) { row.createCell(i).setCellValue(new HSSFRichTextString(strings.get(i))); } } final int rowCount = statements.size(); for (int i = 0; i < rowCount; i++) { final HSSFRow row = sheet.createRow(i + 1); for (int j = 0; j < columnCount; j++) { // Value shouldn't be null, as we prevent atom with null value. final Object value = statements.get(i).getAtom(j).getValue(); final HSSFCell cell = row.createCell(j); POIUtils.invokeSetCellValue(cell, value); } } } if (needToWrite == false) { return needToWrite; } boolean status = false; FileOutputStream fileOut = null; final ThreadSafeFileLock.Lock lock = ThreadSafeFileLock.getLock(file); if (lock == null) { return false; } // http://stackoverflow.com/questions/10868423/lock-lock-before-try ThreadSafeFileLock.lockWrite(lock); try { fileOut = new FileOutputStream(file); wb.write(fileOut); status = true; } catch (FileNotFoundException ex) { log.error(null, ex); } catch (IOException ex) { log.error(null, ex); } finally { Utils.close(fileOut); ThreadSafeFileLock.unlockWrite(lock); ThreadSafeFileLock.releaseLock(lock); } return status; } public Statement.Type getType() { return type; } /** * @return resource language file used by this statements. */ public GUIBundleWrapper getGUIBundleWrapper() { return guiBundleWrapper; } public int size() { return statements.size(); } public Map<String, String> getMetadatas() { return Collections.unmodifiableMap(metadatas); } public Statement get(int index) { return statements.get(index); } private final Statement.Type type; private final GUIBundleWrapper guiBundleWrapper; private final List<Statement> statements = new ArrayList<Statement>(); // Use LinkedHashMap to ensure insertion order is maintained. private final Map<String, String> metadatas = new LinkedHashMap<String, String>(); private static final Log log = LogFactory.getLog(Statements.class); }