1/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. 2 * 3 * This program and the accompanying materials are made available under 4 * the terms of the Common Public License v1.0 which accompanies this distribution, 5 * and is available at http://www.eclipse.org/legal/cpl-v10.html 6 * 7 * $Id: ReportGenerator.java,v 1.1.1.1.2.1 2004/07/16 23:32:30 vlad_r Exp $ 8 */ 9package com.vladium.emma.report.txt; 10 11import java.io.BufferedWriter; 12import java.io.File; 13import java.io.FileOutputStream; 14import java.io.IOException; 15import java.io.OutputStreamWriter; 16import java.io.UnsupportedEncodingException; 17import java.util.Date; 18import java.util.Iterator; 19import java.util.LinkedList; 20 21import com.vladium.util.Files; 22import com.vladium.util.IProperties; 23import com.vladium.util.asserts.$assert; 24import com.vladium.emma.IAppConstants; 25import com.vladium.emma.IAppErrorCodes; 26import com.vladium.emma.EMMAProperties; 27import com.vladium.emma.EMMARuntimeException; 28import com.vladium.emma.data.ICoverageData; 29import com.vladium.emma.data.IMetaData; 30import com.vladium.emma.report.AbstractReportGenerator; 31import com.vladium.emma.report.AllItem; 32import com.vladium.emma.report.ClassItem; 33import com.vladium.emma.report.IItem; 34import com.vladium.emma.report.IItemAttribute; 35import com.vladium.emma.report.ItemComparator; 36import com.vladium.emma.report.MethodItem; 37import com.vladium.emma.report.PackageItem; 38import com.vladium.emma.report.SourcePathCache; 39import com.vladium.emma.report.SrcFileItem; 40 41// ---------------------------------------------------------------------------- 42/** 43 * @author Vlad Roubtsov, (C) 2003 44 */ 45public 46final class ReportGenerator extends AbstractReportGenerator 47 implements IAppErrorCodes 48{ 49 // public: ................................................................ 50 51 // TODO: this is prototype quality, needs major cleanup 52 53 // IReportGenerator: 54 55 public String getType () 56 { 57 return TYPE; 58 } 59 60 public void process (final IMetaData mdata, final ICoverageData cdata, 61 final SourcePathCache cache, final IProperties properties) 62 throws EMMARuntimeException 63 { 64 initialize (mdata, cdata, cache, properties); 65 66 long start = 0, end; 67 final boolean trace1 = m_log.atTRACE1 (); 68 69 if (trace1) start = System.currentTimeMillis (); 70 71 { 72 m_queue = new LinkedList (); 73 for (m_queue.add (m_view.getRoot ()); ! m_queue.isEmpty (); ) 74 { 75 final IItem head = (IItem) m_queue.removeFirst (); 76 77 head.accept (this, null); 78 } 79 line (); 80 81 close (); 82 } 83 84 if (trace1) 85 { 86 end = System.currentTimeMillis (); 87 88 m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms"); 89 } 90 } 91 92 public void cleanup () 93 { 94 m_queue = null; 95 close (); 96 97 super.cleanup (); 98 } 99 100 101 // IItemVisitor: 102 103 public Object visit (final AllItem item, final Object ctx) 104 { 105 File outFile = m_settings.getOutFile (); 106 if (outFile == null) 107 { 108 outFile = new File ("coverage.txt"); 109 m_settings.setOutFile (outFile); 110 } 111 112 final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile); 113 114 m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ..."); 115 116 openOutFile (fullOutFile, m_settings.getOutEncoding (), true); 117 118 // build ID stamp: 119 try 120 { 121 final StringBuffer label = new StringBuffer (101); 122 123 label.append ("["); 124 label.append (IAppConstants.APP_NAME); 125 label.append (" v"); label.append (IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG); 126 label.append (" report, generated "); 127 label.append (new Date (EMMAProperties.getTimeStamp ())); 128 label.append ("]"); 129 130 m_out.write (label.toString ()); 131 m_out.newLine (); 132 133 m_out.flush (); 134 } 135 catch (IOException ioe) 136 { 137 throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); 138 } 139 140 final int [] columns = m_settings.getColumnOrder (); 141 142 line (); 143 144 // [all] coverage summary row: 145 addTitleRow ("OVERALL COVERAGE SUMMARY", 0, 1); 146 { 147 // header row: 148 addHeaderRow (item, columns); 149 150 // coverage row: 151 addItemRow (item, columns); 152 } 153 154 // [all] stats summary table ([all] only): 155 addTitleRow ("OVERALL STATS SUMMARY", 1, 1); 156 { 157 row ("total packages:" + m_separator + item.getChildCount ()); 158 row ("total classes:" + m_separator + item.getAggregate (IItem.TOTAL_CLASS_COUNT)); 159 row ("total methods:" + m_separator + item.getAggregate (IItem.TOTAL_METHOD_COUNT)); 160 161 if (m_srcView && m_hasSrcFileInfo) 162 { 163 row ("total executable files:" + m_separator + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT)); 164 165 if (m_hasLineNumberInfo) 166 row ("total executable lines:" + m_separator + item.getAggregate (IItem.TOTAL_LINE_COUNT)); 167 } 168 } 169 170 final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ()); 171 172 // render package summary rows: 173 addTitleRow ("COVERAGE BREAKDOWN BY PACKAGE", 1, 1); 174 { 175 boolean headerDone = false; 176 final ItemComparator order = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()]; 177 for (Iterator packages = item.getChildren (order); packages.hasNext (); ) 178 { 179 final IItem pkg = (IItem) packages.next (); 180 181 if (! headerDone) 182 { 183 // header row: 184 addHeaderRow (pkg, columns); 185 headerDone = true; 186 } 187 188 // coverage row: 189 addItemRow (pkg, columns); 190 191 if (deeper) m_queue.addLast (pkg); 192 } 193 } 194 195 return ctx; 196 } 197 198 public Object visit (final PackageItem item, final Object ctx) 199 { 200 if (m_verbose) m_log.verbose (" report: processing package [" + item.getName () + "] ..."); 201 202 final int [] columns = m_settings.getColumnOrder (); 203 204 line (); 205 206 // coverage summary row: 207 addTitleRow ("COVERAGE SUMMARY FOR PACKAGE [".concat (item.getName ()).concat ("]"), 0, 1); 208 { 209 // header row: 210 addHeaderRow (item, columns); 211 212 // coverage row: 213 addItemRow (item, columns); 214 } 215 216 217 final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ()); 218 219 // render child summary rows: 220 221 final String summaryTitle = m_srcView ? "COVERAGE BREAKDOWN BY SOURCE FILE" : "COVERAGE BREAKDOWN BY CLASS"; 222 addTitleRow (summaryTitle, 1, 1); 223 { 224 boolean headerDone = false; 225 final ItemComparator order = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()]; 226 for (Iterator srcORclsFiles = item.getChildren (order); srcORclsFiles.hasNext (); ) 227 { 228 final IItem srcORcls = (IItem) srcORclsFiles.next (); 229 230 if (! headerDone) 231 { 232 // header row: 233 addHeaderRow (srcORcls, columns); 234 headerDone = true; 235 } 236 237 // coverage row: 238 addItemRow (srcORcls, columns); 239 240 if (deeper) m_queue.addLast (srcORcls); 241 } 242 } 243 244 return ctx; 245 } 246 247 public Object visit (final SrcFileItem item, final Object ctx) 248 { 249 final int [] columns = m_settings.getColumnOrder (); 250 251 line (); 252 253 // coverage summary row: 254 addTitleRow ("COVERAGE SUMMARY FOR SOURCE FILE [".concat (item.getName ()).concat ("]"), 0, 1); 255 { 256 // header row: 257 addHeaderRow (item, columns); 258 259 // coverage row: 260 addItemRow (item, columns); 261 } 262 263 // render child summary rows: 264 addTitleRow ("COVERAGE BREAKDOWN BY CLASS AND METHOD", 1, 1); 265 { 266 boolean headerDone = false; 267 final ItemComparator order = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()]; 268 for (Iterator classes = item.getChildren (order); classes.hasNext (); ) 269 { 270 final IItem cls = (IItem) classes.next (); 271 272 if (! headerDone) 273 { 274 // header row: 275 addHeaderRow (cls, columns); 276 headerDone = true; 277 } 278 279 // coverage row: 280 //addItemRow (child, columns); 281 282 addTitleRow ("class [" + cls.getName () + "] methods", 0, 0); 283 284 // TODO: select the right comparator here 285 final ItemComparator order2 = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()]; 286 for (Iterator methods = cls.getChildren (order2); methods.hasNext (); ) 287 { 288 final MethodItem method = (MethodItem) methods.next (); 289 290 addItemRow (method, columns); 291 } 292 } 293 } 294 295 return ctx; 296 } 297 298 public Object visit (final ClassItem item, final Object ctx) 299 { 300 final int [] columns = m_settings.getColumnOrder (); 301 302 line (); 303 304 // coverage summary row: 305 addTitleRow ("COVERAGE SUMMARY FOR CLASS [".concat (item.getName ()).concat ("]"), 0, 1); 306 { 307 // header row: 308 addHeaderRow (item, columns); 309 310 // coverage row: 311 addItemRow (item, columns); 312 } 313 314 // render child summary rows: 315 addTitleRow ("COVERAGE BREAKDOWN BY METHOD", 1, 1); 316 { 317 final ItemComparator order = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()]; 318 for (Iterator methods = item.getChildren (order); methods.hasNext (); ) 319 { 320 final IItem method = (IItem) methods.next (); 321 322 // coverage row: 323 addItemRow (method, columns); 324 } 325 } 326 327 return ctx; 328 } 329 330 // protected: ............................................................. 331 332 // package: ............................................................... 333 334 // private: ............................................................... 335 336 337 private void addTitleRow (final String text, final int hlines, final int flines) 338 { 339 for (int i = 0; i < hlines; ++ i) eol (); 340 row (new StringBuffer (text).append (":")); 341 for (int i = 0; i < flines; ++ i) eol (); 342 } 343 344 private void addHeaderRow (final IItem item, final int [] columns) 345 { 346 if ($assert.ENABLED) 347 { 348 $assert.ASSERT (item != null, "null input: item"); 349 $assert.ASSERT (columns != null, "null input: columns"); 350 } 351 352 // header row: 353 final StringBuffer buf = new StringBuffer (); 354 355 for (int c = 0, cLimit = columns.length; c < cLimit; ++ c) 356 { 357 final int attrID = columns [c]; 358 final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ()); 359 360 if (attr != null) 361 { 362 buf.append ('['); 363 buf.append (attr.getName ()); 364 buf.append (']'); 365 } 366 if (c != cLimit - 1) buf.append (m_separator); 367 } 368 369 row (buf); 370 } 371 372 373 /* 374 * No header row, just data rows. 375 */ 376 private void addItemRow (final IItem item, final int [] columns) 377 { 378 if ($assert.ENABLED) 379 { 380 $assert.ASSERT (item != null, "null input: item"); 381 $assert.ASSERT (columns != null, "null input: columns"); 382 } 383 384 final StringBuffer buf = new StringBuffer (11); // TODO: reuse a buffer 385 386 for (int c = 0, cLimit = columns.length; c < cLimit; ++ c) 387 { 388 final int attrID = columns [c]; 389 final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ()); 390 391 if (attr != null) 392 { 393 boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]); 394 395 if (fail) 396 { 397 //buf.append ('('); 398 //buf.append ("! "); 399 attr.format (item, buf); 400 buf.append ('!'); 401 //buf.append (')'); 402 } 403 else 404 { 405 attr.format (item, buf); 406 } 407 } 408 if (c != cLimit - 1) buf.append (m_separator); 409 } 410 411 row (buf); 412 } 413 414 415 private void row (final StringBuffer str) 416 { 417 if ($assert.ENABLED) $assert.ASSERT (str != null, "str = null"); 418 419 try 420 { 421 m_out.write (str.toString ()); 422 m_out.newLine (); 423 } 424 catch (IOException ioe) 425 { 426 throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); 427 } 428 } 429 430 private void row (final String str) 431 { 432 if ($assert.ENABLED) $assert.ASSERT (str != null, "str = null"); 433 434 try 435 { 436 m_out.write (str); 437 m_out.newLine (); 438 } 439 catch (IOException ioe) 440 { 441 throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); 442 } 443 } 444 445 private void line () 446 { 447 try 448 { 449 m_out.write (LINE); 450 m_out.newLine (); 451 } 452 catch (IOException ioe) 453 { 454 throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); 455 } 456 } 457 458 private void eol () 459 { 460 try 461 { 462 m_out.newLine (); 463 } 464 catch (IOException ioe) 465 { 466 throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); 467 } 468 } 469 470 private void close () 471 { 472 if (m_out != null) 473 { 474 try 475 { 476 m_out.flush (); 477 m_out.close (); 478 } 479 catch (IOException ioe) 480 { 481 throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); 482 } 483 finally 484 { 485 m_out = null; 486 } 487 } 488 } 489 490 private void openOutFile (final File file, final String encoding, final boolean mkdirs) 491 { 492 try 493 { 494 if (mkdirs) 495 { 496 final File parent = file.getParentFile (); 497 if (parent != null) parent.mkdirs (); 498 } 499 500 m_out = new BufferedWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE); 501 } 502 catch (UnsupportedEncodingException uee) 503 { 504 // TODO: error code 505 throw new EMMARuntimeException (uee); 506 } 507 // note: in J2SDK 1.3 FileOutputStream constructor's throws clause 508 // was narrowed to FileNotFoundException: 509 catch (IOException fnfe) // FileNotFoundException 510 { 511 // TODO: error code 512 throw new EMMARuntimeException (fnfe); 513 } 514 } 515 516 517 private char m_separator = '\t'; // TODO: set this 518 519 private LinkedList /* IITem */ m_queue; 520 private BufferedWriter m_out; 521 522 private static final String TYPE = "txt"; 523 private static final String LINE = "-------------------------------------------------------------------------------"; 524 525 private static final int IO_BUF_SIZE = 32 * 1024; 526 527} // end of class 528// ----------------------------------------------------------------------------