source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

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