2
* The Apache Software License, Version 1.1
4
* Copyright (c) 2000 The Apache Software Foundation. All rights
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
11
* 1. Redistributions of source code must retain the above copyright
12
* notice, this list of conditions and the following disclaimer.
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
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.
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.
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.
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
47
* ====================================================================
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/>.
54
package org.apache.tools.ant.taskdefs.optional.metamata;
58
import org.xml.sax.helpers.*;
59
import javax.xml.transform.*;
60
import javax.xml.transform.stream.*;
61
import javax.xml.transform.sax.*;
66
import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
67
import org.apache.tools.ant.Task;
68
import org.apache.tools.ant.Project;
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
76
* This class can be used to transform a text file or to process the
77
* output stream directly.
79
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
81
public class MMetricsStreamHandler implements ExecuteStreamHandler {
83
/** CLASS construct, it should be named something like 'MyClass' */
84
protected final static String CLASS = "class";
86
/** package construct, it should be look like 'com.mycompany.something' */
87
protected final static String PACKAGE = "package";
89
/** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */
90
protected final static String FILE = "file";
92
/** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */
93
protected final static String METHOD = "method";
95
protected final static String[] ATTRIBUTES = { "name", "vg", "loc",
96
"dit", "noa", "nrm", "nlm", "wmc", "rfc", "dac", "fanout", "cbo", "lcom", "nocl"
99
/** reader for stdout */
100
protected InputStream metricsOutput;
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
106
protected OutputStream xmlOutputStream;
108
/** metrics handler */
109
protected TransformerHandler metricsHandler;
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.
118
protected Stack stack = new Stack();
120
/** initialize this handler */
121
MMetricsStreamHandler(Task task, OutputStream xmlOut){
123
this.xmlOutputStream = xmlOut;
127
public void setProcessInputStream(OutputStream p1) throws IOException {
131
public void setProcessErrorStream(InputStream p1) throws IOException {
134
/** Set the inputstream */
135
public void setProcessOutputStream(InputStream is) throws IOException {
139
public void start() throws IOException {
140
// create the transformer handler that will be used to serialize
142
TransformerFactory factory = TransformerFactory.newInstance();
143
if ( !factory.getFeature(SAXTransformerFactory.FEATURE) ){
144
throw new IllegalStateException("Invalid Transformer factory feature");
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");
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);
158
// now parse the whole thing
161
} catch (Exception e){
163
throw new IOException(e.getMessage());
168
* Pretty dangerous business here.
172
// we need to pop everything and close elements that have not been
174
while ( stack.size() > 0){
175
ElementEntry elem = (ElementEntry)stack.pop();
176
metricsHandler.endElement("", elem.getType(), elem.getType());
179
metricsHandler.endElement("", "metrics", "metrics");
180
// document is finished for good
181
metricsHandler.endDocument();
182
} catch (SAXException e){
184
throw new IllegalStateException(e.getMessage());
188
/** read each line and process it */
189
protected void parseOutput() throws IOException, SAXException {
190
BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput));
192
while ( (line = br.readLine()) != null ){
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.
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") ){
207
MetricsElement elem = MetricsElement.parse(line);
209
} catch (ParseException e) {
211
// invalid lines are sent to the output as information, it might be anything,
212
task.log(line, Project.MSG_INFO);
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
220
* @param elem the element to process.
221
* @throws SAXException thrown if there is a problem when sending SAX events.
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.
231
while ( indent <= previous.getIndent() && stack.size() > 0){
233
metricsHandler.endElement("", previous.getType(), previous.getType());
234
previous = (ElementEntry)stack.peek();
236
} catch (EmptyStackException ignored){}
239
// ok, now start the new construct
240
String type = getConstructType(elem);
241
Attributes attrs = createAttributes(elem);
242
metricsHandler.startElement("", type, type, attrs);
244
// make sure we keep track of what we did, that's history
245
stack.push( new ElementEntry(type, indent) );
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
256
protected String getConstructType(MetricsElement elem){
257
// ok no doubt, it's a file
258
if ( elem.isCompilationUnit() ){
262
// same, we're sure it's a method
263
if ( elem.isMethod() ){
267
// if it's empty, and none of the above it should be a package
268
if ( stack.size() == 0 ){
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 ){
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 ){
289
// we assume the other are package
295
* Create all attributes of a MetricsElement skipping those who have an
299
protected Attributes createAttributes(MetricsElement elem){
300
AttributesImpl impl = new AttributesImpl();
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);
316
* helper class to keep track of elements via its type and indent
317
* that's all we need to guess a type.
319
private final static class ElementEntry {
322
ElementEntry(String type, int indent){
324
this.indent = indent;
326
public String getType(){
329
public int getIndent() {
335
class MetricsElement {
337
private final static NumberFormat METAMATA_NF;
339
private final static NumberFormat NEUTRAL_NF;
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.###");
347
NEUTRAL_NF.setMaximumFractionDigits(1);
352
private String construct;
354
private Vector metrics;
356
MetricsElement(int indent, String construct, Vector metrics){
357
this.indent = indent;
358
this.construct = construct;
359
this.metrics = metrics;
362
public int getIndent(){
366
public String getName(){
370
public Enumeration getMetrics(){
371
return metrics.elements();
374
public boolean isCompilationUnit(){
375
return ( construct.endsWith(".java") || construct.endsWith(".class") );
378
public boolean isMethod(){
379
return ( construct.endsWith("(...)") || construct.endsWith("()") );
382
public static MetricsElement parse(String line) throws ParseException {
383
final Vector metrics = new Vector();
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
395
metrics.addElement( token );
396
line = line.substring(pos + 1);
398
metrics.addElement( line );
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);
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);
412
pos = name.lastIndexOf('/');
414
name = name.substring(pos + 1);
415
indent = pos + 1; // indentation is last position of token + 1
417
return new MetricsElement(indent, name, metrics);