001 package com.croftsoft.apps.retirement; 002 003 import java.io.*; 004 import java.text.NumberFormat; 005 006 import javax.servlet.*; 007 import javax.servlet.http.*; 008 009 import com.croftsoft.core.io.FileLib; 010 import com.croftsoft.core.math.FinanceLib; 011 import com.croftsoft.core.text.ParseLib; 012 013 /********************************************************************* 014 * Retirement calculator servlet. 015 * 016 * @version 017 * $Id: RetirementServlet.java,v 1.2 2007/09/30 22:32:25 croft Exp $ 018 * @since 019 * 1999-08-15 020 * @author 021 * <a href="https://www.croftsoft.com/">David Wallace Croft</a> 022 *********************************************************************/ 023 024 public final class RetirementServlet 025 extends HttpServlet 026 ////////////////////////////////////////////////////////////////////// 027 ////////////////////////////////////////////////////////////////////// 028 { 029 030 ////////////////////////////////////////////////////////////////////// 031 // Static variables 032 ////////////////////////////////////////////////////////////////////// 033 034 private static final long serialVersionUID = 0L; 035 036 private static final String TITLE = "Retirement Calculator"; 037 038 private static final String VERSION = "2001-06-09"; 039 040 private static final String DEFAULT_HEADER 041 = "<HTML>\n<HEAD>\n<TITLE>" + TITLE + "</TITLE>\n</HEAD>\n" 042 + "<BODY>\n<CENTER>\n<H1>" + TITLE + "</H1>\n" 043 + "<p><a target=\"_blank\" " 044 + "href=\"https://www.croftsoft.com/people/david/\">" 045 + "David Wallace Croft</a></p>\n" 046 + "<p>Version " + VERSION + "</p>\n"; 047 048 private static final String DEFAULT_FOOTER 049 = "</CENTER>\n</BODY>\n</HTML>"; 050 051 private static final String DEFAULT_HEADER_ALIAS_PATH 052 = "header.html"; 053 054 private static final String DEFAULT_FOOTER_ALIAS_PATH 055 = "footer.html"; 056 057 private static final String [ ] PARAM_TEXT = { 058 "Desired annual retirement income (present value, after taxes)", 059 "Years until retirement (usually at 59½ years of age)", 060 "Annual investment growth rate before retirement (tax-deferred)", 061 "Annual interest earned on retirement savings during retirement", 062 "Tax rate during retirement on savings interest", 063 "Estimated annual inflation" }; 064 065 private static final String [ ] PARAM_NAMES = { 066 "rIncome", 067 "iYears", 068 "iInterest", 069 "rInterest", 070 "rTaxRate", 071 "rInflation" }; 072 073 private static final double [ ] PARAM_DEFAULT_VALUES = { 074 30000.0, 075 40.0, 076 10.0, 077 6.0, 078 15.0, 079 2.0 }; 080 081 private static final boolean [ ] PARAM_IS_PERCENTAGE = { 082 false, 083 false, 084 true, 085 true, 086 true, 087 true }; 088 089 private static final NumberFormat currencyNumberFormat 090 = NumberFormat.getCurrencyInstance ( ); 091 092 ////////////////////////////////////////////////////////////////////// 093 // Instance variables 094 ////////////////////////////////////////////////////////////////////// 095 096 private String htmlHeader; 097 098 private String htmlFooter; 099 100 ////////////////////////////////////////////////////////////////////// 101 // Public static methods 102 ////////////////////////////////////////////////////////////////////// 103 104 public static final double calculateRequiredAnnualInvestment ( 105 double desiredSavingsInterestIncome, 106 double yearsOfSaving, 107 double investmentInterestRate, 108 double savingsInterestRate, 109 double taxRate, 110 double inflationRate ) 111 ////////////////////////////////////////////////////////////////////// 112 { 113 double savings 114 = desiredSavingsInterestIncome * ( 1.0 + inflationRate ) 115 / ( savingsInterestRate * ( 1.0 - taxRate ) - inflationRate ); 116 117 double annualSavings; 118 119 if ( yearsOfSaving == 0.0 ) 120 { 121 annualSavings = savings; 122 } 123 else 124 { 125 double futureValueSavings 126 = savings * Math.pow ( 1.0 + inflationRate, yearsOfSaving ); 127 128 annualSavings = FinanceLib.annualSavingsNeeded ( 129 futureValueSavings, investmentInterestRate, yearsOfSaving ); 130 } 131 132 return annualSavings; 133 } 134 135 ////////////////////////////////////////////////////////////////////// 136 // HttpServlet methods 137 ////////////////////////////////////////////////////////////////////// 138 139 @Override 140 public String getServletInfo ( ) { return TITLE; } 141 142 @Override 143 public void init ( ServletConfig servletConfig ) 144 throws ServletException 145 ////////////////////////////////////////////////////////////////////// 146 { 147 try 148 { 149 super.init ( servletConfig ); 150 151 ServletContext servletContext = getServletContext ( ); 152 153 String headerRealPath 154 = servletContext.getRealPath ( DEFAULT_HEADER_ALIAS_PATH ); 155 156 String footerRealPath 157 = servletContext.getRealPath ( DEFAULT_FOOTER_ALIAS_PATH ); 158 159 try 160 { 161 htmlHeader = FileLib.loadTextFile ( headerRealPath ); 162 } 163 catch ( Exception ex ) 164 { 165 htmlHeader = "<! Unable to load \"" + headerRealPath + "\">\n" 166 + "<! " + ex + " >\n" 167 + DEFAULT_HEADER; 168 } 169 170 try 171 { 172 htmlFooter = FileLib.loadTextFile ( footerRealPath ); 173 } 174 catch ( Exception ex ) 175 { 176 htmlFooter = "<! Unable to load \"" + footerRealPath + "\">\n" 177 + "<! " + ex + " >\n" 178 + DEFAULT_FOOTER; 179 } 180 } 181 catch ( Exception ex ) 182 { 183 log ( "init() Exception", ex ); 184 } 185 } 186 187 @Override 188 public void doGet ( 189 HttpServletRequest req, 190 HttpServletResponse res ) 191 throws ServletException, IOException 192 ////////////////////////////////////////////////////////////////////// 193 { 194 doPost ( req, res ); 195 } 196 197 @Override 198 public void doPost ( 199 HttpServletRequest req, 200 HttpServletResponse res ) 201 throws ServletException, IOException 202 ////////////////////////////////////////////////////////////////////// 203 { 204 res.setContentType ( "text/html" ); 205 206 PrintStream out = new PrintStream ( res.getOutputStream ( ) ); 207 208 try 209 { 210 out.println ( htmlHeader ); 211 212 double [ ] paramDoubles = new double [ PARAM_NAMES.length ]; 213 214 for ( int i = 0; i < paramDoubles.length; i++ ) 215 { 216 String [ ] paramValues 217 = req.getParameterValues ( PARAM_NAMES [ i ] ); 218 219 String paramValue = null; 220 221 if ( ( paramValues != null ) && ( paramValues.length > 0 ) ) 222 { 223 paramValue = paramValues [ 0 ]; 224 } 225 226 paramDoubles [ i ] = ParseLib.parseDouble ( 227 paramValue, PARAM_DEFAULT_VALUES [ i ] ); 228 } 229 230 // change method to POST? 231 232 out.println ( "<FORM ACTION=\"" 233 + req.getRequestURI ( ) + "\" METHOD=GET>\n" ); 234 235 out.println ( "<TABLE>" ); 236 237 for ( int i = 0; i < PARAM_TEXT.length; i++ ) 238 { 239 out.println ( "<TR>" ); 240 241 out.println ( " <TD>" + PARAM_TEXT [ i ] + "</TD>" ); 242 243 out.println ( " <td><input type=\"text\" name=\"" 244 + PARAM_NAMES [ i ] 245 + "\" SIZE=10 VALUE=\"" 246 + paramDoubles [ i ] 247 + "\">" 248 + ( PARAM_IS_PERCENTAGE [ i ] ? "%" : "" ) + "</TD>" ); 249 250 out.println ( "</TR>" ); 251 } 252 253 out.println ( "</TABLE>" ); 254 255 double [ ] normalizedValues = new double [ paramDoubles.length ]; 256 257 for ( int i = 0; i < paramDoubles.length; i++ ) 258 { 259 normalizedValues [ i ] = PARAM_IS_PERCENTAGE [ i ] 260 ? paramDoubles [ i ] / 100 : paramDoubles [ i ]; 261 } 262 263 out.println ( "<P>" ); 264 265 out.println ( "<INPUT TYPE=\"SUBMIT\" VALUE=\"Recalculate\">" ); 266 267 out.println ( "</FORM>\n" ); 268 269 out.println ( "<P>" ); 270 271 double annualSavings = calculateRequiredAnnualInvestment ( 272 normalizedValues [ 0 ], 273 normalizedValues [ 1 ], 274 normalizedValues [ 2 ], 275 normalizedValues [ 3 ], 276 normalizedValues [ 4 ], 277 normalizedValues [ 5 ] ); 278 279 if ( annualSavings < 0 ) 280 { 281 out.println ( "<font color=\"red\">" ); 282 283 out.println ( "The interest rate on retirement savings" 284 + " must exceed the annual inflation rate." ); 285 286 out.println ( "</font>" ); 287 } 288 else 289 { 290 out.println ( "<p align=\"center\"><font color=\"green\">" ); 291 292 out.println ( "You would need to invest " 293 + currencyNumberFormat.format ( annualSavings ) 294 + " each year." ); 295 296 out.println ( "</font></p>" ); 297 298 showTable ( out, 299 annualSavings, 300 normalizedValues [ 0 ], 301 normalizedValues [ 1 ], 302 normalizedValues [ 2 ], 303 normalizedValues [ 3 ], 304 normalizedValues [ 4 ], 305 normalizedValues [ 5 ] ); 306 } 307 } 308 catch ( Exception ex ) 309 { 310 ex.printStackTrace ( ); 311 312 out.println ( "A processing error occurred. Please try again." ); 313 } 314 catch ( Throwable t ) 315 { 316 t.printStackTrace ( ); 317 } 318 finally 319 { 320 try 321 { 322 out.println ( htmlFooter ); 323 324 out.flush ( ); 325 326 out.close ( ); 327 } 328 catch ( Exception ex ) 329 { 330 // ignore 331 } 332 } 333 } 334 335 @Override 336 public void destroy ( ) 337 ////////////////////////////////////////////////////////////////////// 338 { 339 super.destroy ( ); 340 } 341 342 ////////////////////////////////////////////////////////////////////// 343 // Private methods 344 ////////////////////////////////////////////////////////////////////// 345 346 private static void showTable ( 347 PrintStream out, 348 double annualSavings, 349 double desiredIncome, 350 double investmentYears, 351 double investmentInterestRate, 352 double retirementInterestRate, 353 double taxRate, 354 double inflationRate ) 355 ////////////////////////////////////////////////////////////////////// 356 { 357 int yearsWorking = ( int ) investmentYears; 358 359 if ( ( yearsWorking < 0 ) || ( yearsWorking > 100 ) ) return; 360 361 out.println ( "<TABLE BORDER CELLPADDING=10>" ); 362 363 out.println ( " <THEAD> Investment Years </THEAD>" ); 364 out.println ( " <TH> Year </TH>" ); 365 out.println ( " <TH> Investment Earnings </TH>" ); 366 out.println ( " <TH> Additional Investment </TH>" ); 367 out.println ( " <TH> Total Investment </TH>" ); 368 out.println ( " <TH> Present Value </TH>" ); 369 370 double totalSavings = 0.0; 371 372 for ( int i = 1; i < yearsWorking + 1; i++ ) 373 { 374 out.println ( " <TR ALIGN=RIGHT>" ); 375 376 out.println ( " <TD> " + i + "</TD>" ); 377 378 double interest = investmentInterestRate * totalSavings; 379 380 out.println ( " <TD> " 381 + currencyNumberFormat.format ( interest ) + "</TD>" ); 382 383 out.println ( " <TD> " 384 + currencyNumberFormat.format ( annualSavings ) + "</TD>" ); 385 386 totalSavings = FinanceLib.futureValueAnnuity ( 387 annualSavings, investmentInterestRate, i ); 388 389 out.println ( " <TD> " 390 + currencyNumberFormat.format ( totalSavings ) + "</TD>" ); 391 392 double presentValue = FinanceLib.presentValue ( 393 totalSavings, inflationRate, i ); 394 395 out.println ( " <TD> " 396 + currencyNumberFormat.format ( presentValue ) + "</TD>" ); 397 398 out.println ( " </TR>" ); 399 } 400 401 out.println ( "</TABLE>" ); 402 403 out.println ( "<P>" ); 404 405 out.println ( "<TABLE BORDER CELLPADDING=10>" ); 406 407 out.println ( " <THEAD> First Ten Retirement Years </THEAD>" ); 408 out.println ( " <TH> Year </TH>" ); 409 out.println ( " <TH> Interest Earned </TH>" ); 410 out.println ( " <TH> Tax on Interest </TH>" ); 411 out.println ( " <TH> Living Income </TH>" ); 412 out.println ( " <TH> Remaining Savings </TH>" ); 413 out.println ( " <TH> Present Value </TH>" ); 414 415 if ( yearsWorking == 0 ) 416 { 417 totalSavings = annualSavings; 418 } 419 420 for ( int i = yearsWorking + 1; i < yearsWorking + 11; i++ ) 421 { 422 out.println ( " <TR ALIGN=RIGHT>" ); 423 424 out.println ( " <TD> " + i + "</TD>" ); 425 426 double interest = retirementInterestRate * totalSavings; 427 428 out.println ( " <TD> " 429 + currencyNumberFormat.format ( interest ) + "</TD>" ); 430 431 double taxes = -taxRate * interest; 432 433 out.println ( " <TD> " 434 + currencyNumberFormat.format ( taxes ) + "</TD>" ); 435 436 double livingExpenses = -FinanceLib.futureValue ( 437 desiredIncome, inflationRate, i ); 438 439 out.println ( " <TD> " 440 + currencyNumberFormat.format ( livingExpenses ) + "</TD>" ); 441 442 totalSavings += interest + taxes + livingExpenses; 443 444 out.println ( " <TD> " 445 + currencyNumberFormat.format ( totalSavings ) + "</TD>" ); 446 447 double presentValue = FinanceLib.presentValue ( 448 totalSavings, inflationRate, i ); 449 450 out.println ( " <TD> " 451 + currencyNumberFormat.format ( presentValue ) + "</TD>" ); 452 453 out.println ( " </TR>" ); 454 } 455 456 out.println ( "</TABLE>" ); 457 } 458 459 ////////////////////////////////////////////////////////////////////// 460 ////////////////////////////////////////////////////////////////////// 461 }