Java tutorial
/* * The MIT License * * Copyright 2014 Stephen Stafford <clothcat@gmail.com>. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.clothcat.hpoolauto.model; import com.clothcat.hpoolauto.Constants; import com.clothcat.hpoolauto.HLogger; import com.clothcat.hpoolauto.RpcWorker; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; import org.json.JSONException; import org.json.JSONObject; import org.w3c.tidy.Tidy; /** * Generates static HTML pages displaying the current state of the pools. The * HTML is placed in * <pre>${user.home}/.Hyperpool/html</pre>. This uses templates from * <pre>${user.home}/.Hyperpool/templates</pre> * * @author Stephen Stafford <clothcat@gmail.com> */ public class HtmlGenerator { //<editor-fold defaultstate="collapsed" desc="MASTER_HTML"> private static final String MASTER_HTML_TEMPLATE = "<html>\n" + " <head>\n" + " <title>Hyperpool</title>\n" + " <meta charset=\"UTF-8\">\n" + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" + " <!--CSS-->\n" + " </head>\n" + " <body>\n" + "<p><h1>WARNING!! -- This is a test site and isn't yet in operation.<br/>" + "I make NO promises that it won't break. _ ssta</h1></p>" + " <div id=\"hat_header\"><p>This page was automatically generated the by the Hyperpool Automation Tool (HAT)</p>\n" + " <p>Generated at: <!--CREATION_DATE--></p></div>\n" + "\n" + " <div id=\"current_status\">\n" + " <!--NUM_FILLED_POOLS--> pools filled.<br/>\n" + " <!--NUM_POOLS_MATURING--> pools currently maturing.<br/>\n" + " <!--NUM_POOLS_STAKING--> pools currently staking.<br/>\n" + " <!--NUM_POOLS_PAID--> pools staked and paid out.</br>\n" + " <!--TOTAL_PROFIT_AMOUNT--> total profit so far.</div>\n" + "\n" + "<div id=\"network_stats\">\n" + "Current difficulty: <!--CURRENT_DIFFICULTY--><br/>\n" + "</div>\n" + " <div id=\"master_pool_table\">\n" + " <!--MASTER_POOL_TABLE-->\n" + " </div>\n" + "\n" + "<h1>Pool Address: <!--POOL_ADDRESS--></h1>\n" + " </body>\n" + "</html>\n" + ""; //</editor-fold> //<editor-fold defaultstate="collapsed" desc="POOL_HTML"> private static final String POOL_HTML_TEMPLATE = "<html>\n" + " <head>\n" + " <title>Hyperpool</title>\n" + " <meta charset=\"UTF-8\">\n" + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" + " <!--CSS-->\n" + " </head>\n" + " <body>\n" + " <div id=\"hat_header\"><p>This page was automatically generated the by the Hyperpool Automation Tool (HAT)</p>\n" + " <p>Generated at: <!--CREATION_DATE--></p></div>\n" + "\n" + "<h1 id=\"pool_heading\"><!--POOL_NAME--></h1>\n" + "<p><h1>WARNING!! -- This is a test site and isn't yet in operation.<br/>" + "I make NO promises that it won't break. _ ssta</h1></p>" + "<div id=\"pool_status\">\n" + "Status: <!--POOL_STATUS--><br/>\n" + "Pool filled: <!--POOL_FILL_DATE--><br/>\n" + "Pool age: <!--POOL_AGE--><br/>\n" + "Potential stake: <!--POTENTIAL_STAKE--><br/>\n" + "Transfer fees subtracted: <!--XFER_FEES--><br/>\n" + "\n" + " <div id=\"pool_table\">\n" + " <!--POOL_TABLE-->\n" + " </div>\n" + " <div id=\"other_stuff\">\n" + " <!--OTHER_STUFF-->\n" + " </div>\n" + " </body>\n" + "</html>\n" + ""; //</editor-fold> public static void generateAll(Model m) { HLogger.log(Level.FINEST, "In HtmlGenerator.generateAll()"); try { generateMaster(m); generatePools(m); copyStylesheet(); } catch (IOException ex) { HLogger.log(Level.SEVERE, "Caught exception in HtmlGenerator.generateAll()", ex); Logger.getLogger(HtmlGenerator.class.getName()).log(Level.SEVERE, null, ex); } } /** * Generate the html for master.html */ private static void generateMaster(Model model) throws IOException { String template = MASTER_HTML_TEMPLATE; // date generated template = template.replace(PlaceholderStrings.DATE_GENERATED, toDateString(new Date().getTime())); // number of filled pools template = template.replace(PlaceholderStrings.NUM_FILLED_POOLS, String.valueOf(model.getNumFilledPools())); // number of pools maturing template = template.replace(PlaceholderStrings.NUM_POOLS_MATURING, String.valueOf(model.getNumMaturingPools())); // number of paid pools template = template.replace(PlaceholderStrings.NUM_POOLS_PAID, String.valueOf(model.getNumPoolsPaid())); // number or pools staking template = template.replace(PlaceholderStrings.NUM_POOLS_STAKING, String.valueOf(model.getNumStakingPools())); // pools paid out template = template.replace(PlaceholderStrings.NUM_POOLS_PAID, String.valueOf(model.getNumPoolsPaid())); // total profit template = template.replace(PlaceholderStrings.TOTAL_PROFIT, String.valueOf(model.getTotalProfit() / 1000000)); // difficulty try { JSONObject diffJo = new JSONObject(new RpcWorker().getPosDifficulty()); String diff = diffJo.getString("proof-of-stake"); template = template.replace(PlaceholderStrings.CURRENT_DIFFICULTY, diff); } catch (JSONException ex) { Logger.getLogger(HtmlGenerator.class.getName()).log(Level.SEVERE, null, ex); } // master pool table String poolTable = "<table id=\"pool_table\" border=\"1\">\n" + "<tr id=\"pool_table_header\">\n" + "<th>Pool Name</td>\n" + "<th>Pool Size</td>\n" + "<th>Pool Status</td>\n" + "</tr>\n"; int rownum = 0; for (Pool pool : model.pools) { if (rownum % 2 == 0) { poolTable += "<tr id=\"pool_table_oddrow\">\n"; } else { poolTable += "<tr id=\"pool_table_evenrow\">\n"; } rownum++; poolTable += "<td><a href=\"" + pool.getPoolName() + ".html\">" + pool.getPoolName() + "</a></td>\n" + "<td>" + pool.calculateFillAmount() / Constants.uH_IN_HYP + "</td>\n" + "<td>" + pool.getStatus().name() + "</td>\n" + "</td>\n" + "</tr>\n"; } poolTable += "</table>\n"; template = template.replace(PlaceholderStrings.MASTER_POOL_TABLE, poolTable); template = template.replace((PlaceholderStrings.POOL_ADDRESS), model.getPoolAddress()); // tidy html template = tidyHtml(template); // write file // make sure target directory exists File d = new File(Constants.HTML_FILEPATH); d.mkdirs(); FileUtils.write(new File(Constants.HTML_FILEPATH + "master.html"), template); } /** * Pretty print the given HTML */ private static String tidyHtml(String html) { String result = ""; try { try (StringWriter out = new StringWriter()) { try (InputStream in = new ByteArrayInputStream(html.getBytes())) { Tidy tidy = new Tidy(); tidy.setIndentContent(true); tidy.parse(in, out); result = out.toString(); } } } catch (IOException ex) { HLogger.log(Level.FINEST, "Caught exception in tidyHtml()", ex); } return result; } private static void generatePools(Model model) { for (Pool pool : model.pools) { String template = POOL_HTML_TEMPLATE; // date generated template = template.replace(PlaceholderStrings.DATE_GENERATED, toDateString(new Date().getTime())); template = template.replace(PlaceholderStrings.POOL_NAME, pool.getPoolName()); template = template.replace(PlaceholderStrings.POOL_STATUS, pool.getStatus().toString()); template = template.replace(PlaceholderStrings.POOL_FILL_DATE, toDateString(pool.getStartTimestamp())); long age = pool.getPoolAge(); // this is in ms long ageinseconds = age / 1000; String ageString = convertTimeToString(ageinseconds); template = template.replace(PlaceholderStrings.POOL_AGE, ageString); long potStake = pool.calculatePotentialStake(); double potStaked = potStake; potStaked /= Constants.uH_IN_HYP; String potStakeStr = String.format("%.4f", potStaked); template = template.replace(PlaceholderStrings.POTENTIAL_STAKE, potStakeStr); template = template.replace(PlaceholderStrings.XFER_FEES, "TODO"); String poolTable = "<table id=\"pool_table\" border=\"1\">\n" + "<tr id=\"pool_table_header\">\n" + "<th>Participants</th>\n" + "<th>Investment</th>\n" + "<th>Percentage</th>\n" + "<th>Current return</th>\n" + "</tr>\n" + ""; int rownum = 0; for (Investment inv : pool.getInvestments()) { if (rownum % 2 == 0) { poolTable += "<tr id=\"pool_table_oddrow\">\n"; } else { poolTable += "<tr id=\"pool_table_evenrow\">\n"; } rownum++; double invAmount = inv.getAmount(); invAmount /= Constants.uH_IN_HYP; String invStr = String.format("%.3f", invAmount); poolTable += "<td>" + inv.getFromAddress() + "</td>\n" + "<td>" + invStr + "</td>\n" + "<td>" + calcPercentage(pool.calculateFillAmount(), inv.getAmount()) + "</td>\n" + "<td>" + calcReturn(pool.calculateFillAmount(), inv.getAmount(), potStake) + "</td>\n" + "</tr>\n"; } // TODO: Total row poolTable += "</table>\n"; template = template.replace(PlaceholderStrings.POOL_TABLE, poolTable); template = template.replace(PlaceholderStrings.OTHER_STUFF, "TODO"); // tidy html template = tidyHtml(template); // write file // make sure target directory exists File d = new File(Constants.HTML_FILEPATH); d.mkdirs(); try { FileUtils.write(new File(Constants.HTML_FILEPATH + pool.getPoolName() + ".html"), template); } catch (IOException ex) { HLogger.log(Level.FINEST, "Caught Exception in generatePools()", ex); Logger.getLogger(HtmlGenerator.class.getName()).log(Level.SEVERE, null, ex); } } } private static void copyStylesheet() { // TODO -- implement this once we have a stylesheet to copy } /** * Treat the passed date as milliseconds since the epoch and return a string * with format "HH:mm z yyyy/MM/dd" */ private static CharSequence toDateString(long date) { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm z yyyy/MM/dd"); Date d = new Date(date); return sdf.format(d); } /** * Take a number of seconds and convert it to days, hours, minutes and seconds */ private static String convertTimeToString(long s) { HLogger.log(Level.FINEST, "Converting: " + s + " seconds"); long days = s / Constants.SECS_IN_DAY; s %= Constants.SECS_IN_DAY; long hours = s / Constants.SECS_IN_HOUR; s %= Constants.SECS_IN_HOUR; long minutes = s / Constants.SECS_IN_MINUTE; s %= Constants.SECS_IN_MINUTE; String response = ""; if (days > 0) { response += "" + days + ((days == 1) ? " day " : " days "); } response += "" + hours + ((hours == 1) ? " hour " : " hours "); response += "" + minutes + ((minutes == 1) ? " minute " : " minutes "); response += "" + s + ((s == 1) ? " second" : " seconds"); HLogger.log(Level.FINEST, "Converted to: " + response); return response; } /** * calculate the percentage of poolSize investment is and return it as a * string with 3 digit precision */ private static String calcPercentage(long poolSize, long invSize) { double d = ((double) poolSize * 100) / invSize; HLogger.log(Level.FINEST, "calcPercentage(" + poolSize + ", " + invSize + ");\n" + "calculated: " + d); return String.format("%.3f", d); } /** * Calculate the potential return in HYP and return it as a string with 3 * digit precision */ private static String calcReturn(long poolSize, long invSize, long potStake) { double d = ((double) poolSize) / invSize; double ret = d * potStake; ret += invSize; // profit in in uHyp, so convert it to Hyp ret /= Constants.uH_IN_HYP; return String.format("%.3f", ret); } //<editor-fold defaultstate="collapsed" desc="List of placeholder strings to be replaced"> private class PlaceholderStrings { /** * The date/time this page was generated */ public static final String DATE_GENERATED = "<!--CREATION_DATE-->"; /** * The number of pools which we have ever filled */ public static final String NUM_FILLED_POOLS = "<!--NUM_FILLED_POOLS-->"; /** * How many pools are currently maturing */ public static final String NUM_POOLS_MATURING = "<!--NUM_POOLS_MATURING-->"; /** * Number of pools currently staking. */ public static final String NUM_POOLS_STAKING = "<!--NUM_POOLS_STAKING-->"; /** * Number of pools which have staked and paid out */ public static final String NUM_POOLS_PAID = "<!--NUM_POOLS_PAID-->"; /** * total profit so far */ public static final String TOTAL_PROFIT = "<!--TOTAL_PROFIT_AMOUNT-->"; /** * The master table with information and links to the pool pages */ public static final String MASTER_POOL_TABLE = "<!--MASTER_POOL_TABLE-->"; /** * current POS difficulty */ public static final String CURRENT_DIFFICULTY = "<!--CURRENT_DIFFICULTY-->"; public static final String POOL_NAME = "<!--POOL_NAME-->"; public static final String POOL_ADDRESS = "<!--POOL_ADDRESS-->"; public static final String POOL_STATUS = "<!--POOL_STATUS-->"; public static final String POOL_FILL_DATE = "<!--POOL_FILL_DATE-->"; public static final String POOL_AGE = "<!--POOL_AGE-->"; public static final String POTENTIAL_STAKE = "<!--POTENTIAL_STAKE-->"; public static final String XFER_FEES = "<!--XFER_FEES-->"; public static final String POOL_TABLE = "<!--POOL_TABLE-->"; public static final String OTHER_STUFF = "<!--OTHER_STUFF-->"; } //</editor-fold> }