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&#189; 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         }