~ubuntu-branches/ubuntu/jaunty/ant/jaunty-proposed

« back to all changes in this revision

Viewing changes to src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java

  • Committer: Bazaar Package Importer
  • Author(s): Stefan Gybas
  • Date: 2002-02-14 14:28:48 UTC
  • Revision ID: james.westby@ubuntu.com-20020214142848-2ww7ynmqkj31vlmn
Tags: upstream-1.4.1
ImportĀ upstreamĀ versionĀ 1.4.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * The Apache Software License, Version 1.1
 
3
 *
 
4
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 
5
 * reserved.
 
6
 *
 
7
 * Redistribution and use in source and binary forms, with or without
 
8
 * modification, are permitted provided that the following conditions
 
9
 * are met:
 
10
 *
 
11
 * 1. Redistributions of source code must retain the above copyright
 
12
 *    notice, this list of conditions and the following disclaimer.
 
13
 *
 
14
 * 2. Redistributions in binary form must reproduce the above copyright
 
15
 *    notice, this list of conditions and the following disclaimer in
 
16
 *    the documentation and/or other materials provided with the
 
17
 *    distribution.
 
18
 *
 
19
 * 3. The end-user documentation included with the redistribution, if
 
20
 *    any, must include the following acknowlegement:
 
21
 *       "This product includes software developed by the
 
22
 *        Apache Software Foundation (http://www.apache.org/)."
 
23
 *    Alternately, this acknowlegement may appear in the software itself,
 
24
 *    if and wherever such third-party acknowlegements normally appear.
 
25
 *
 
26
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 
27
 *    Foundation" must not be used to endorse or promote products derived
 
28
 *    from this software without prior written permission. For written
 
29
 *    permission, please contact apache@apache.org.
 
30
 *
 
31
 * 5. Products derived from this software may not be called "Apache"
 
32
 *    nor may "Apache" appear in their names without prior written
 
33
 *    permission of the Apache Group.
 
34
 *
 
35
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 
36
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 
37
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 
38
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 
39
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 
40
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 
41
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 
42
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 
43
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 
44
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 
45
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 
46
 * SUCH DAMAGE.
 
47
 * ====================================================================
 
48
 *
 
49
 * This software consists of voluntary contributions made by many
 
50
 * individuals on behalf of the Apache Software Foundation.  For more
 
51
 * information on the Apache Software Foundation, please see
 
52
 * <http://www.apache.org/>.
 
53
 */
 
54
package org.apache.tools.ant.taskdefs.optional.metamata;
 
55
 
 
56
 
 
57
import org.xml.sax.*;
 
58
import org.xml.sax.helpers.*;
 
59
import javax.xml.transform.*;
 
60
import javax.xml.transform.stream.*;
 
61
import javax.xml.transform.sax.*;
 
62
import java.util.*;
 
63
import java.io.*;
 
64
import java.text.*;
 
65
 
 
66
import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
 
67
import org.apache.tools.ant.Task;
 
68
import org.apache.tools.ant.Project;
 
69
 
 
70
/**
 
71
 * A handy metrics handler. Most of this code was done only with the
 
72
 * screenshots on the documentation since the evaluation version as
 
73
 * of this writing does not allow to save metrics or to run it via
 
74
 * command line. 
 
75
 * <p>
 
76
 * This class can be used to transform a text file or to process the
 
77
 * output stream directly.
 
78
 *
 
79
 * @author  <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
 
80
 */
 
81
public class MMetricsStreamHandler implements ExecuteStreamHandler {
 
82
 
 
83
    /** CLASS construct, it should be named something like 'MyClass' */
 
84
    protected final static String CLASS = "class";
 
85
    
 
86
    /** package construct, it should be look like 'com.mycompany.something' */
 
87
    protected final static String PACKAGE = "package";
 
88
    
 
89
    /** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */
 
90
    protected final static String FILE = "file";
 
91
    
 
92
    /** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */
 
93
    protected final static String METHOD = "method";
 
94
    
 
95
    protected final static String[] ATTRIBUTES = { "name", "vg", "loc",
 
96
    "dit", "noa", "nrm", "nlm", "wmc", "rfc", "dac", "fanout", "cbo", "lcom", "nocl"
 
97
    };
 
98
    
 
99
    /** reader for stdout */
 
100
    protected InputStream metricsOutput;
 
101
    
 
102
    /**
 
103
     * this is where the XML output will go, should mostly be a file
 
104
     * the caller is responsible for flushing and closing this stream
 
105
     */
 
106
    protected OutputStream xmlOutputStream;
 
107
 
 
108
    /** metrics handler */
 
109
    protected TransformerHandler metricsHandler;
 
110
    
 
111
    /** the task */
 
112
    protected Task task;
 
113
 
 
114
    /**
 
115
     * the stack where are stored the metrics element so that they we can
 
116
     * know if we have to close an element or not.
 
117
     */
 
118
    protected Stack stack = new Stack();
 
119
          
 
120
    /** initialize this handler */
 
121
    MMetricsStreamHandler(Task task, OutputStream xmlOut){
 
122
        this.task = task;
 
123
        this.xmlOutputStream = xmlOut;
 
124
    }
 
125
 
 
126
    /** Ignore. */
 
127
    public void setProcessInputStream(OutputStream p1) throws IOException {
 
128
    }
 
129
 
 
130
    /** Ignore. */
 
131
    public void setProcessErrorStream(InputStream p1) throws IOException {
 
132
    }
 
133
 
 
134
    /** Set the inputstream */
 
135
    public void setProcessOutputStream(InputStream is) throws IOException {
 
136
            metricsOutput = is;
 
137
    }
 
138
 
 
139
    public void start() throws IOException {
 
140
        // create the transformer handler that will be used to serialize
 
141
        // the output.
 
142
        TransformerFactory factory = TransformerFactory.newInstance();
 
143
        if ( !factory.getFeature(SAXTransformerFactory.FEATURE) ){
 
144
            throw new IllegalStateException("Invalid Transformer factory feature");
 
145
        }
 
146
        try {
 
147
            metricsHandler = ((SAXTransformerFactory)factory).newTransformerHandler();
 
148
            metricsHandler.setResult( new StreamResult( new OutputStreamWriter(xmlOutputStream, "UTF-8")) );
 
149
            Transformer transformer = metricsHandler.getTransformer();
 
150
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
 
151
            
 
152
            // start the document with a 'metrics' root
 
153
            metricsHandler.startDocument();
 
154
            AttributesImpl attr = new AttributesImpl();
 
155
            attr.addAttribute("", "company", "company", "CDATA", "metamata");
 
156
            metricsHandler.startElement("", "metrics", "metrics", attr);
 
157
 
 
158
            // now parse the whole thing
 
159
            parseOutput();
 
160
 
 
161
        } catch (Exception e){
 
162
            e.printStackTrace();
 
163
            throw new IOException(e.getMessage());
 
164
        }
 
165
    }
 
166
 
 
167
    /**
 
168
     * Pretty dangerous business here. 
 
169
     */
 
170
    public void stop() {
 
171
        try {
 
172
            // we need to pop everything and close elements that have not been
 
173
            // closed yet.
 
174
            while ( stack.size() > 0){
 
175
                ElementEntry elem = (ElementEntry)stack.pop();
 
176
                metricsHandler.endElement("", elem.getType(), elem.getType());
 
177
            }
 
178
            // close the root
 
179
            metricsHandler.endElement("", "metrics", "metrics");
 
180
            // document is finished for good
 
181
            metricsHandler.endDocument();
 
182
        } catch (SAXException e){
 
183
            e.printStackTrace();
 
184
            throw new IllegalStateException(e.getMessage());
 
185
        }
 
186
    }  
 
187
 
 
188
    /** read each line and process it */
 
189
    protected void parseOutput() throws IOException, SAXException {
 
190
        BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput));
 
191
        String line = null;
 
192
        while ( (line = br.readLine()) != null ){
 
193
            processLine(line);
 
194
        }
 
195
    }
 
196
 
 
197
    /**
 
198
     * Process a metrics line. If the metrics is invalid and that this is not
 
199
     * the header line, it is display as info.
 
200
     * @param line the line to process, it is normally a line full of metrics.
 
201
     */
 
202
    protected void processLine(String line) throws SAXException {
 
203
        if ( line.startsWith("Construct\tV(G)\tLOC\tDIT\tNOA\tNRM\tNLM\tWMC\tRFC\tDAC\tFANOUT\tCBO\tLCOM\tNOCL") ){
 
204
            return;
 
205
        }
 
206
        try {
 
207
            MetricsElement elem = MetricsElement.parse(line);
 
208
            startElement(elem);
 
209
        } catch (ParseException e) {
 
210
                e.printStackTrace();
 
211
            // invalid lines are sent to the output as information, it might be anything,
 
212
           task.log(line, Project.MSG_INFO);
 
213
        }
 
214
    }
 
215
    
 
216
    /**
 
217
     * Start a new construct. Elements are popped until we are on the same
 
218
     * parent node, then the element type is guessed and pushed on the
 
219
     * stack.
 
220
     * @param elem the element to process.
 
221
     * @throws SAXException thrown if there is a problem when sending SAX events.
 
222
     */
 
223
    protected void startElement(MetricsElement elem) throws SAXException {
 
224
        // if there are elements in the stack we possibly need to close one or
 
225
        // more elements previous to this one until we got its parent
 
226
        int indent = elem.getIndent();
 
227
        if ( stack.size() > 0 ){
 
228
            ElementEntry previous = (ElementEntry)stack.peek();
 
229
            // close nodes until you got the parent.
 
230
            try {
 
231
                while ( indent <= previous.getIndent() && stack.size() > 0){
 
232
                    stack.pop();
 
233
                    metricsHandler.endElement("", previous.getType(), previous.getType());
 
234
                        previous = (ElementEntry)stack.peek();
 
235
                }
 
236
            } catch (EmptyStackException ignored){}
 
237
        }
 
238
        
 
239
        // ok, now start the new construct
 
240
        String type = getConstructType(elem);
 
241
        Attributes attrs = createAttributes(elem);
 
242
        metricsHandler.startElement("", type, type, attrs);
 
243
 
 
244
        // make sure we keep track of what we did, that's history
 
245
        stack.push( new ElementEntry(type, indent) );
 
246
    }
 
247
 
 
248
    /**
 
249
     * return the construct type of the element. We can hardly recognize the
 
250
     * type of a metrics element, so we are kind of forced to do some black
 
251
     * magic based on the name and indentation to recognize the type.
 
252
     * @param elem  the metrics element to guess for its type.
 
253
     * @return the type of the metrics element, either PACKAGE, FILE, CLASS or
 
254
     * METHOD.
 
255
     */
 
256
    protected String getConstructType(MetricsElement elem){
 
257
        // ok no doubt, it's a file
 
258
        if ( elem.isCompilationUnit() ){
 
259
            return FILE;
 
260
        }
 
261
        
 
262
        // same, we're sure it's a method
 
263
        if ( elem.isMethod() ){
 
264
            return METHOD;
 
265
        }
 
266
 
 
267
        // if it's empty, and none of the above it should be a package
 
268
        if ( stack.size() == 0 ){
 
269
            return PACKAGE;
 
270
        }
 
271
        
 
272
        // ok, this is now black magic time, we will guess the type based on
 
273
        // the previous type and its indent...
 
274
        final ElementEntry previous = (ElementEntry)stack.peek();
 
275
        final String prevType = previous.getType();
 
276
        final int prevIndent = previous.getIndent();
 
277
        final int indent = elem.getIndent();
 
278
        // we're just under a file with a bigger indent so it's a class
 
279
        if ( prevType.equals(FILE) && indent > prevIndent ){
 
280
            return CLASS;
 
281
        }
 
282
 
 
283
        // we're just under a class with a greater or equals indent, it's a class
 
284
        // (there might be several classes in a compilation unit and inner classes as well)
 
285
        if ( prevType.equals(CLASS) && indent >= prevIndent ){
 
286
            return CLASS;
 
287
        }
 
288
        
 
289
        // we assume the other are package
 
290
        return PACKAGE;
 
291
    }    
 
292
    
 
293
    
 
294
    /**
 
295
     * Create all attributes of a MetricsElement skipping those who have an
 
296
     * empty string
 
297
     * @param   elem    
 
298
     */
 
299
    protected Attributes createAttributes(MetricsElement elem){
 
300
        AttributesImpl impl = new AttributesImpl();
 
301
        int i = 0;
 
302
        String name = ATTRIBUTES[i++];
 
303
        impl.addAttribute("", name, name, "CDATA", elem.getName());
 
304
        Enumeration metrics = elem.getMetrics();
 
305
        for (; metrics.hasMoreElements(); i++){
 
306
            String value = (String)metrics.nextElement();
 
307
            if ( value.length() > 0 ){
 
308
                name = ATTRIBUTES[i];
 
309
                impl.addAttribute("", name, name, "CDATA", value);
 
310
            }
 
311
        }
 
312
        return impl;
 
313
    }
 
314
    
 
315
    /**
 
316
     * helper class to keep track of elements via its type and indent
 
317
     * that's all we need to guess a type.
 
318
     */
 
319
    private final static class ElementEntry {
 
320
        private String type;
 
321
        private int indent;
 
322
        ElementEntry(String type, int indent){
 
323
            this.type = type;
 
324
            this.indent = indent;
 
325
        }
 
326
        public String getType(){
 
327
            return type;
 
328
        }
 
329
        public int getIndent() {
 
330
            return indent;
 
331
        }
 
332
    }
 
333
}
 
334
 
 
335
class MetricsElement {
 
336
 
 
337
        private final static NumberFormat METAMATA_NF;
 
338
        
 
339
        private final static NumberFormat NEUTRAL_NF;
 
340
        static {
 
341
        METAMATA_NF = NumberFormat.getInstance();
 
342
        METAMATA_NF.setMaximumFractionDigits(1);
 
343
        NEUTRAL_NF = NumberFormat.getInstance();
 
344
                if (NEUTRAL_NF instanceof DecimalFormat) {
 
345
                        ((DecimalFormat) NEUTRAL_NF).applyPattern("###0.###;-###0.###");
 
346
                }
 
347
        NEUTRAL_NF.setMaximumFractionDigits(1);
 
348
        }
 
349
    
 
350
    private int indent;
 
351
    
 
352
    private String construct;
 
353
    
 
354
    private Vector metrics;
 
355
    
 
356
    MetricsElement(int indent, String construct, Vector metrics){
 
357
        this.indent = indent;
 
358
        this.construct = construct;
 
359
        this.metrics = metrics;
 
360
    }
 
361
    
 
362
    public int getIndent(){
 
363
        return indent;
 
364
    }
 
365
    
 
366
    public String getName(){
 
367
        return construct;
 
368
    }
 
369
 
 
370
    public Enumeration getMetrics(){
 
371
        return metrics.elements();
 
372
    }
 
373
    
 
374
    public boolean isCompilationUnit(){
 
375
        return ( construct.endsWith(".java") || construct.endsWith(".class") );
 
376
    }
 
377
        
 
378
    public boolean isMethod(){
 
379
        return ( construct.endsWith("(...)") || construct.endsWith("()") );
 
380
    }
 
381
    
 
382
    public static MetricsElement parse(String line) throws ParseException {
 
383
        final Vector metrics = new Vector();
 
384
        int pos;
 
385
        
 
386
        // i'm using indexOf since I need to know if there are empty strings
 
387
        // between tabs and I find it easier than with StringTokenizer
 
388
        while ( (pos = line.indexOf('\t')) != -1 ){
 
389
            String token = line.substring(0, pos);
 
390
            // only parse what coudl be a valid number. ie not constructs nor no value
 
391
            /*if (metrics.size() != 0 || token.length() != 0){
 
392
                    Number num = METAMATA_NF.parse(token); // parse with Metamata NF
 
393
                    token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF
 
394
                }*/
 
395
            metrics.addElement( token );
 
396
            line = line.substring(pos + 1);
 
397
        }
 
398
        metrics.addElement( line );
 
399
        
 
400
        // there should be exactly 14 tokens (1 name + 13 metrics), if not, there is a problem !
 
401
        if ( metrics.size() != 14 ){
 
402
            throw new ParseException("Could not parse the following line as a metrics: -->" + line +"<--", -1);
 
403
        }
 
404
        
 
405
        // remove the first token it's made of the indentation string and the
 
406
        // construct name, we'll need all this to figure out what type of
 
407
        // construct it is since we lost all semantics :(
 
408
        // (#indent[/]*)(#construct.*)
 
409
        String name = (String)metrics.elementAt(0);
 
410
        metrics.removeElementAt(0);
 
411
        int indent = 0;
 
412
        pos = name.lastIndexOf('/');
 
413
        if (pos != -1){
 
414
            name = name.substring(pos + 1);
 
415
            indent = pos + 1; // indentation is last position of token + 1
 
416
        }
 
417
        return new MetricsElement(indent, name, metrics);
 
418
    }
 
419
}
 
420