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// ----------------------------------------------------------------------------