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: ReportProperties.java,v 1.1.1.1 2004/05/09 16:57:38 vlad_r Exp $ 8 */ 9package com.vladium.emma.report; 10 11import java.io.File; 12import java.util.HashSet; 13import java.util.Set; 14import java.util.StringTokenizer; 15 16import com.vladium.util.Files; 17import com.vladium.util.IProperties; 18import com.vladium.util.IntIntMap; 19import com.vladium.util.IntVector; 20import com.vladium.util.ObjectIntMap; 21import com.vladium.util.Property; 22import com.vladium.util.asserts.$assert; 23import com.vladium.emma.IAppErrorCodes; 24import com.vladium.emma.EMMARuntimeException; 25 26// ---------------------------------------------------------------------------- 27/** 28 * @author Vlad Roubtsov, (C) 2003 29 */ 30public 31abstract class ReportProperties implements IAppErrorCodes 32{ 33 // public: ................................................................ 34 35 36 public static final IProperties.IMapper REPORT_PROPERTY_MAPPER; // set in <clinit> 37 38 39 public static final class ParsedProperties 40 { 41 public void setOutEncoding (final String outEncoding) 42 { 43 if ($assert.ENABLED) $assert.ASSERT (outEncoding != null, "null input: outEncoding"); 44 45 m_outEncoding = outEncoding; 46 } 47 48 public String getOutEncoding () 49 { 50 return m_outEncoding; 51 } 52 53 public void setOutDir (final File outDir) 54 { 55 if ($assert.ENABLED) $assert.ASSERT (outDir != null, "null input: outDir"); 56 57 m_outDir = outDir; 58 } 59 60 public File getOutDir () 61 { 62 return m_outDir; 63 } 64 65 public void setOutFile (final File outFile) 66 { 67 if ($assert.ENABLED) $assert.ASSERT (outFile != null, "null input: outFile"); 68 69 m_outFile = outFile; 70 } 71 72 public File getOutFile () 73 { 74 return m_outFile; 75 } 76 77 public void setUnitsType (final int unitsType) 78 { 79 if ($assert.ENABLED) $assert.ASSERT (unitsType >= IItemAttribute.UNITS_COUNT && unitsType <= IItemAttribute.UNITS_INSTR, "invalid units type: " + unitsType); 80 81 m_unitsType = unitsType; 82 } 83 84 public int getUnitsType () 85 { 86 return m_unitsType; 87 } 88 89 public void setViewType (final int viewType) 90 { 91 if ($assert.ENABLED) $assert.ASSERT (viewType >= IReportDataView.HIER_CLS_VIEW && viewType <= IReportDataView.HIER_SRC_VIEW, "invalid view type: " + viewType); 92 93 m_viewType = viewType; 94 } 95 96 public int getViewType () 97 { 98 return m_viewType; 99 } 100 101 public void setDepth (final int depth) 102 { 103 if ($assert.ENABLED) $assert.ASSERT (depth >= IItemMetadata.TYPE_ID_ALL && depth <= IItemMetadata.TYPE_ID_METHOD, "invalid depth: " + depth); 104 105 m_depth = depth; 106 } 107 108 public int getDepth() 109 { 110 return m_depth; 111 } 112 113 public void setHideClasses (final boolean hideClasses) 114 { 115 m_hideClasses = hideClasses; 116 } 117 118 public boolean getHideClasses () 119 { 120 return m_hideClasses; 121 } 122 123 public void setColumnOrder (final int [] columnOrder) 124 { 125 if ($assert.ENABLED) $assert.ASSERT (columnOrder != null && columnOrder.length != 0, "null/empty input: outEncoding"); 126 127 m_columnOrder = columnOrder; 128 } 129 130 public int [] getColumnOrder () 131 { 132 return m_columnOrder; 133 } 134 135 public void setSortOrder (final int [] sortOrder) 136 { 137 if ($assert.ENABLED) $assert.ASSERT (sortOrder != null, "null input: sortOrder"); 138 139 m_sortOrder = sortOrder; 140 } 141 142 public int [] getSortOrder () 143 { 144 return m_sortOrder; 145 } 146 147 public void setMetrics (final IntIntMap metrics) 148 { 149 if ($assert.ENABLED) $assert.ASSERT (metrics != null, "null input: metrics"); 150 151 m_metrics = metrics; 152 } 153 154 public IntIntMap getMetrics () 155 { 156 return m_metrics; 157 } 158 159 // TODO: toString/logging 160 161 void validate () throws IllegalArgumentException 162 { 163 if ($assert.ENABLED) 164 { 165 $assert.ASSERT (m_outEncoding != null, "m_outEncoding not set"); 166 $assert.ASSERT (m_outDir != null || m_outFile != null, "either m_outDir or m_outFile must be set"); 167 $assert.ASSERT (m_columnOrder != null, "m_columnOrder not set"); 168 $assert.ASSERT (m_sortOrder != null, "m_sortOrder not set"); 169 $assert.ASSERT (m_metrics != null, "m_metrics not set"); 170 } 171 } 172 173 174 private String m_outEncoding; 175 private File m_outDir; 176 private File m_outFile; 177 178 private int m_unitsType; 179 private int m_viewType; 180 181 private boolean m_hideClasses; 182 private int m_depth; 183 184 // TODO: fraction/number format strings... 185 186 private int [] m_columnOrder; // attribute IDs [order indicates column order] 187 private int [] m_sortOrder; // if m_sortOrder[i+1]>0 , sort m_columnOrder[m_sortOrder[i]] in ascending order 188 private IntIntMap m_metrics; // pass criteria (column attribute ID -> metric) 189 190 } // end of nested class 191 192 193// /** 194// * Creates a property view specific to 'reportType' report type. 195// * 196// * @param appProperties 197// * @param reportType 198// * @return 199// */ 200// public static Properties getReportProperties (final Properties appProperties, final String reportType) 201// { 202// if ((reportType == null) || (reportType.length () == 0)) 203// throw new IllegalArgumentException ("null/empty input: reportType"); 204// 205// if (appProperties == null) return new XProperties (); 206// 207// return new ReportPropertyLookup (appProperties, reportType); 208// } 209 210 211// /** 212// * @param type [null/empty indicates type-neutral property] 213// */ 214// public static String getReportProperty (final String type, final Map properties, final String key) 215// { 216// if (properties == null) throw new IllegalArgumentException ("null input: properties"); 217// if (key == null) throw new IllegalArgumentException ("null input: key"); 218// 219// String fullKey; 220// 221// if ((type == null) || (type.length () == 0)) 222// fullKey = IReportParameters.PREFIX.concat (key); 223// else 224// { 225// fullKey = IReportParameters.PREFIX.concat (type).concat (".").concat (key); 226// 227// if (! properties.containsKey (fullKey)) // default to type-neutral lookup 228// fullKey = IReportParameters.PREFIX.concat (key); 229// } 230// 231// return (String) properties.get (fullKey); 232// } 233// 234// public static String getReportParameter (final String type, final Map properties, final String key, final String def) 235// { 236// final String value = getReportProperty (type, properties, key); 237// 238// return (value == null) ? def : value; 239// } 240 241 242 public static ParsedProperties parseProperties (final IProperties properties, final String type) 243 { 244 if ($assert.ENABLED) $assert.ASSERT (properties != null, "properties = null"); 245 246 final ParsedProperties result = new ParsedProperties (); 247 { 248 result.setOutEncoding (getReportProperty (properties, type, IReportProperties.OUT_ENCODING, false)); 249 } 250 251 // TODO: outDirName is no longer supported 252 253 { 254 final String outDirName = getReportProperty (properties, type, IReportProperties.OUT_DIR, true); 255 final String outFileName = getReportProperty (properties, type, IReportProperties.OUT_FILE, false); 256 257 // renormalize the out dir and file combination: 258 259 if (outFileName != null) 260 { 261 final File fullOutFile = Files.newFile (outDirName, outFileName); 262 263 final File dir = fullOutFile.getParentFile (); 264 if (dir != null) result.setOutDir (dir); 265 266 result.setOutFile (new File (fullOutFile.getName ())); 267 } 268 else if (outDirName != null) 269 { 270 result.setOutDir (new File (outDirName)); 271 } 272 } 273 274 { 275 final String unitsType = getReportProperty (properties, type, IReportProperties.UNITS_TYPE, true, IReportProperties.DEFAULT_UNITS_TYPE); 276 result.setUnitsType (IReportProperties.COUNT_UNITS.equals (unitsType) ? IItemAttribute.UNITS_COUNT : IItemAttribute.UNITS_INSTR); 277 278 // TODO: invalid setting not checked 279 } 280 { 281 /* view type is no longer a user-overridable property [it is driven by SourceFile attribute presence] 282 283 final String viewType = getReportProperty (properties, type, IReportProperties.VIEW_TYPE, IReportProperties.DEFAULT_VIEW_TYPE); 284 result.setViewType (IReportProperties.SRC_VIEW.equals (viewType) ? IReportDataView.HIER_SRC_VIEW : IReportDataView.HIER_CLS_VIEW); 285 */ 286 287 result.setViewType (IReportDataView.HIER_SRC_VIEW); 288 } 289 290 { 291 final String hideClasses = getReportProperty (properties, type, IReportProperties.HIDE_CLASSES, true, IReportProperties.DEFAULT_HIDE_CLASSES); 292 result.setHideClasses (Property.toBoolean (hideClasses)); 293 294 // TODO: log this 295 if (result.getViewType () == IReportDataView.HIER_CLS_VIEW) 296 result.setHideClasses (false); 297 } 298 { 299 final String depth = getReportProperty (properties, type, IReportProperties.DEPTH, false, IReportProperties.DEFAULT_DEPTH); 300 301 if (IReportProperties.DEPTH_ALL.equals (depth)) 302 result.setDepth (AllItem.getTypeMetadata ().getTypeID ()); 303 else if (IReportProperties.DEPTH_PACKAGE.equals (depth)) 304 result.setDepth (PackageItem.getTypeMetadata ().getTypeID ()); 305 else if (IReportProperties.DEPTH_SRCFILE.equals (depth)) 306 result.setDepth (SrcFileItem.getTypeMetadata ().getTypeID ()); 307 else if (IReportProperties.DEPTH_CLASS.equals (depth)) 308 result.setDepth (ClassItem.getTypeMetadata ().getTypeID ()); 309 else if (IReportProperties.DEPTH_METHOD.equals (depth)) 310 result.setDepth (MethodItem.getTypeMetadata ().getTypeID ()); 311 else 312 // TODO: properly prefixes prop name 313 throw new EMMARuntimeException (INVALID_PARAMETER_VALUE, new Object [] {IReportProperties.DEPTH, depth}); 314 } 315 316 if (result.getHideClasses () && 317 (result.getViewType () == IReportDataView.HIER_SRC_VIEW) && 318 (result.getDepth () == IItemMetadata.TYPE_ID_CLASS)) 319 { 320 result.setDepth (IItemMetadata.TYPE_ID_SRCFILE); 321 } 322 323 final Set /* String */ columnNames = new HashSet (); 324 { 325 final String columnList = getReportProperty (properties, type, IReportProperties.COLUMNS, false, IReportProperties.DEFAULT_COLUMNS); 326 final IntVector _columns = new IntVector (); 327 328 final int [] out = new int [1]; 329 330 for (StringTokenizer tokenizer = new StringTokenizer (columnList, ","); tokenizer.hasMoreTokens (); ) 331 { 332 final String columnName = tokenizer.nextToken ().trim (); 333 if (! COLUMNS.get (columnName, out)) 334 { 335 // TODO: generate the entire enum list in the err msg 336 throw new EMMARuntimeException (INVALID_COLUMN_NAME, new Object [] {columnName}); 337 } 338 339 if (! REMOVE_DUPLICATE_COLUMNS || ! columnNames.contains (columnName)) 340 { 341 columnNames.add (columnName); 342 _columns.add (out [0]); 343 } 344 } 345 346 result.setColumnOrder (_columns.values ()); 347 } 348 // [assertion: columnNames contains all columns for the report (some 349 // may get removed later by individual report generators if some debug info 350 // is missing)] 351 352 { 353 final String sortList = getReportProperty (properties, type, IReportProperties.SORT, false, IReportProperties.DEFAULT_SORT); 354 final IntVector _sort = new IntVector (); 355 356 final int [] out = new int [1]; 357 358 for (StringTokenizer tokenizer = new StringTokenizer (sortList, ","); tokenizer.hasMoreTokens (); ) 359 { 360 final String sortSpec = tokenizer.nextToken ().trim (); 361 final String columnName; 362 final int dir; 363 364 switch (sortSpec.charAt (0)) 365 { 366 case IReportProperties.ASC: 367 { 368 dir = +1; 369 columnName = sortSpec.substring (1); 370 } 371 break; 372 373 case IReportProperties.DESC: 374 { 375 dir = -1; 376 columnName = sortSpec.substring (1); 377 } 378 break; 379 380 default: 381 { 382 dir = +1; 383 columnName = sortSpec; 384 } 385 break; 386 387 } // end of switch 388 389 // silently ignore columns not in the column list: 390 if (columnNames.contains (columnName)) 391 { 392 COLUMNS.get (columnName, out); 393 394 _sort.add (out [0]); // sort attribute ID 395 _sort.add (dir); // sort direction 396 } 397 398 result.setSortOrder (_sort.values ()); 399 } 400 } 401 { 402 final String metricList = getReportProperty (properties, type, IReportProperties.METRICS, true, IReportProperties.DEFAULT_METRICS); 403 final IntIntMap _metrics = new IntIntMap (); 404 405 final int [] out = new int [1]; 406 407 // TODO: perhaps should throw on invalid input here 408 for (StringTokenizer tokenizer = new StringTokenizer (metricList, ","); tokenizer.hasMoreTokens (); ) 409 { 410 final String metricSpec = tokenizer.nextToken ().trim (); 411 final String columnName; 412 final double criterion; 413 414 final int separator = metricSpec.indexOf (IReportProperties.MSEPARATOR); 415 if (separator > 0) // silently ignore invalid entries 416 { 417 // silently ignore invalid cutoff values: 418 try 419 { 420 criterion = Double.parseDouble (metricSpec.substring (separator + 1)); 421 if ((criterion < 0.0) || (criterion > 101.0)) continue; 422 } 423 catch (NumberFormatException nfe) 424 { 425 nfe.printStackTrace (System.out); 426 continue; 427 } 428 429 columnName = metricSpec.substring (0, separator); 430 431 // silently ignore columns not in the column list: 432 if (columnNames.contains (columnName)) 433 { 434 COLUMNS.get (columnName, out); 435 436 _metrics.put (out [0], (int) Math.round (((criterion * IItem.PRECISION) / 100.0))); 437 } 438 } 439 } 440 441 result.setMetrics (_metrics); 442 } 443 444 result.validate (); 445 446 return result; 447 } 448 449 450 // protected: ............................................................. 451 452 // package: ............................................................... 453 454 // private: ............................................................... 455 456 457 private static final class ReportPropertyMapper implements IProperties.IMapper 458 { 459 public String getMappedKey (final String key) 460 { 461 if (key != null) 462 { 463 if (key.startsWith (IReportProperties.PREFIX)) 464 { 465 final int secondDot = key.indexOf ('.', IReportProperties.PREFIX.length ()); 466 if (secondDot > 0) 467 { 468 // TODO: make this more precise (actually check the report type value string) 469 470 return IReportProperties.PREFIX.concat (key.substring (secondDot + 1)); 471 } 472 } 473 } 474 475 return null; 476 } 477 478 } // end of nested class 479 480 481// private static final class ReportPropertyLookup extends XProperties 482// { 483// // note: due to incredibly stupid coding in java.util.Properties 484// // (getProperty() uses a non-virtual call to super.get(), while propertyNames() 485// // uses a virtual call to the same method instead of delegating to getProperty()) 486// // I must override both methods below 487// 488// public String getProperty (String key) 489// { 490// return (String) get (key); 491// } 492// 493// // TODO: this kind of lookup makes the property listing confusing 494// 495// public Object get (final Object _key) 496// { 497// if (! (_key instanceof String)) return null; 498// 499// String key = (String) _key; 500// 501// if (key.startsWith (IReportProperties.PREFIX)) 502// key = key.substring (IReportProperties.PREFIX.length ()); 503// 504// if (key.startsWith (m_reportType)) 505// key = key.substring (m_reportType.length () + 1); 506// 507// String fullKey = IReportProperties.PREFIX.concat (m_reportType).concat (".").concat (key); 508// 509// String result = defaults.getProperty (fullKey, null); 510// if (result != null) return result; 511// 512// // fall back to report type-neutral namespace: 513// fullKey = IReportProperties.PREFIX.concat (key); 514// 515// result = defaults.getProperty (fullKey, null); 516// if (result != null) return result; 517// 518// return null; 519// } 520// 521// 522// ReportPropertyLookup (final Properties appProperties, final String reportType) 523// { 524// super (appProperties); 525// 526// m_reportType = reportType; 527// } 528// 529// 530// private final String m_reportType; // never null or empty [factory-ensured] 531// 532// } // end of nested class 533 534 535 private ReportProperties () {} // prevent subclassing 536 537 538 private static String getReportProperty (final IProperties properties, final String type, final String key, final boolean allowBlank) 539 { 540 return getReportProperty (properties, type, key, allowBlank, null); 541 } 542 543 private static String getReportProperty (final IProperties properties, final String type, final String key, final boolean allowBlank, final String dflt) 544 { 545 if ($assert.ENABLED) $assert.ASSERT (properties != null, "null input: properties"); 546 if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key"); 547 548 final String result = properties.getProperty (IReportProperties.PREFIX.concat (type).concat (".").concat (key), dflt); 549 550 if (! allowBlank && (result != null) && (result.trim ().length () == 0)) 551 return dflt; 552 else 553 return result; 554 } 555 556 557 private static final boolean REMOVE_DUPLICATE_COLUMNS = true; 558 private static final ObjectIntMap /* col name:String -> metadata:IItemMetadata */ COLUMNS; // set in <clinit> 559 560 static 561 { 562 REPORT_PROPERTY_MAPPER = new ReportPropertyMapper (); 563 564 final ObjectIntMap columns = new ObjectIntMap (); 565 566 columns.put (IReportProperties.ITEM_NAME_COLUMN, IItemAttribute.ATTRIBUTE_NAME_ID); 567 columns.put (IReportProperties.CLASS_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_CLASS_COVERAGE_ID); 568 columns.put (IReportProperties.METHOD_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_METHOD_COVERAGE_ID); 569 columns.put (IReportProperties.BLOCK_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_BLOCK_COVERAGE_ID); 570 columns.put (IReportProperties.LINE_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID); 571 572 COLUMNS = columns; 573 } 574 575} // end of class 576// ----------------------------------------------------------------------------