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 | */
|
---|
17 | package org.apache.tools.ant.taskdefs.optional.metamata;
|
---|
18 |
|
---|
19 |
|
---|
20 | import java.io.BufferedReader;
|
---|
21 | import java.io.IOException;
|
---|
22 | import java.io.InputStream;
|
---|
23 | import java.io.InputStreamReader;
|
---|
24 | import java.io.OutputStream;
|
---|
25 | import java.io.OutputStreamWriter;
|
---|
26 | import java.text.DecimalFormat;
|
---|
27 | import java.text.NumberFormat;
|
---|
28 | import java.text.ParseException;
|
---|
29 | import java.util.Date;
|
---|
30 | import java.util.EmptyStackException;
|
---|
31 | import java.util.Enumeration;
|
---|
32 | import java.util.Stack;
|
---|
33 | import java.util.Vector;
|
---|
34 | import javax.xml.transform.OutputKeys;
|
---|
35 | import javax.xml.transform.Transformer;
|
---|
36 | import javax.xml.transform.TransformerFactory;
|
---|
37 | import javax.xml.transform.sax.SAXTransformerFactory;
|
---|
38 | import javax.xml.transform.sax.TransformerHandler;
|
---|
39 | import javax.xml.transform.stream.StreamResult;
|
---|
40 | import org.apache.tools.ant.BuildException;
|
---|
41 | import org.apache.tools.ant.Project;
|
---|
42 | import org.apache.tools.ant.Task;
|
---|
43 | import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
|
---|
44 | import org.apache.tools.ant.util.DateUtils;
|
---|
45 | import org.xml.sax.Attributes;
|
---|
46 | import org.xml.sax.SAXException;
|
---|
47 | import 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 | */
|
---|
59 | public 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 |
|
---|
322 | class 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 |
|
---|