001: package net.firstpartners.nounit.report.process;
002:
003: /**
004: * Title: NoUnit - Identify Classes that are not being unit Tested
005: *
006: * Copyright (C) 2001 Paul Browne , FirstPartners.net
007: *
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or (at your option) any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: *
023: * @author Paul Browne
024: * @version 0.7
025: */
026:
027: import java.io.File;
028: import java.io.FileOutputStream;
029: import java.io.IOException;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.List;
033:
034: import net.firstpartners.nounit.snippet.xml.IXmlConstants;
035: import net.firstpartners.nounit.utility.NoUnitException;
036: import net.firstpartners.nounit.utility.XmlUtil;
037:
038: import org.jdom.Attribute;
039: import org.jdom.Document;
040: import org.jdom.Element;
041: import org.jdom.JDOMException;
042: import org.jdom.input.SAXBuilder;
043: import org.jdom.output.XMLOutputter;
044:
045: /**
046: * Using the Start Class as a base , traces <b>all</b> calls through the
047: * document to see what calls what. Start Class is defined as any class
048: * that extends @param nameOfBaseClass. While tracing
049: * calls through the Java (XML tree) , it updates the tree, to see who
050: * calls whom, when and a what depth (and to see what this method may
051: * call in turn. <BR>
052: * This process is called the 'Call Chain'. <BR>
053: * The class acts on XML as per dtd @see nounit.dtd
054: */
055: public class CallChainer implements ICallsXmlConstants {
056:
057: /**
058: * Maximum search depth to see how an item is called
059: * Stops Infinite Loops
060: */
061: private final static int MAX_SEARCH_DEPTH = 5;
062:
063: //@todo allow this value to be passed in from the command line
064:
065: /**
066: * Add Calls information to the XMl Tree (@see nounit.dtd)
067: * @param fullXMLFileName file to process
068: * @param outputFile
069: * @param nameOfBaseClass - the calls trail will run from here
070: * @exception NoUnitException
071: * @exception IOException
072: */
073: public void addCallChainInformation(File fullXmlFileName,
074: File outputFile, String nameOfBaseClass)
075: throws NoUnitException, IOException, JDOMException {
076:
077: //Read in the file
078: SAXBuilder builder = new SAXBuilder();
079: Document sourceDocument = builder.build(fullXmlFileName);
080:
081: //Process the file
082: sourceDocument = lookThroughNodes(sourceDocument,
083: nameOfBaseClass);
084:
085: //Now Delete the old outputfile , if it exists
086: if (outputFile.exists()) {
087: outputFile.delete();
088: }
089:
090: //Get Handle to file
091: FileOutputStream output = new FileOutputStream(outputFile);
092:
093: //Output Xml to this stream
094: XMLOutputter fmt = new XMLOutputter(" ", true);
095: fmt.output(sourceDocument, output);
096: }
097:
098: /**
099: * Loops through all the nodes in the JDOM Document and processes them
100: * @param sourceDocument JDom Document to be processed
101: * @param nameOfBaseClass - the calls trail will run from here
102: * @return Document with additional information
103: */
104: private Document lookThroughNodes(Document sourceDocument,
105: String nameOfBaseClass) throws JDOMException {
106:
107: //Get Handle to Nodes as HashSet
108: HashSet allNodes = XmlUtil.getAllNodes(sourceDocument);
109: Iterator nodeLoop = allNodes.iterator();
110:
111: //Loop through and add information to node
112: while (nodeLoop.hasNext()) {
113: Element tmpElement = (Element) nodeLoop.next();
114: searchForExtendsTag(sourceDocument, tmpElement,
115: nameOfBaseClass);
116: }
117: return sourceDocument;
118: }
119:
120: /**
121: * Look for one of the 'Extends' class which marks the Call chain
122: * then o the Actual Processing - starts the call chain.
123: * (i.e. marks classes as called)
124: * @param currentDocument that we are chaining within
125: * @param nameOfBaseClass - the calls trail will run from here
126: * @return Document with additional information
127: */
128: private void searchForExtendsTag(Document currentDocument,
129: Element inElement, String nameOfBaseClass)
130: throws JDOMException {
131:
132: //Only Interested if this class is the 'Extends' tag - otherwise ignore
133: String tmpString = inElement.getName();
134: if ((tmpString != null)
135: && (tmpString
136: .equals(IXmlConstants.ELEMENT_CLASS_EXTENDS))) {
137:
138: Attribute tmpAttribute = inElement
139: .getAttribute(IXmlConstants.ATTRIBUTE_NAME);
140: String tmpName = tmpAttribute.getValue();
141:
142: if ((tmpName != null) && (tmpName.equals(nameOfBaseClass))) {
143: startCallChain(currentDocument, inElement);
144: }
145: }
146: }
147:
148: /**
149: * Starts the call chain from this Element (ie Extends Tag)
150: * Really , it goes to the parent (Class Tag) , finds all <Method>
151: * children of this tag , and starts the call chain from there.
152: * @param currentDocument that we are chaining within
153: * @param inElement to start marking out calls in the XML
154: */
155: private void startCallChain(Document currentDocument,
156: Element inElement) throws JDOMException {
157:
158: //Local variables
159: int currentCallDepth = 0; // start of call depth into document
160:
161: //Get the Parent (ie Class Tag , then see all the method tags
162: Element classElement = inElement.getParent();
163: List possibleMethods = classElement.getChildren();
164: Iterator possMethodsLoop = possibleMethods.iterator();
165:
166: //Loop through possible Methods and follow all possible calls
167: while (possMethodsLoop.hasNext()) {
168:
169: //Get Next Element and see if it is a 'METHOD' tag
170: Element tmpMethodElement = (Element) possMethodsLoop.next();
171: String tmpString = tmpMethodElement.getName();
172: if ((tmpString != null)
173: && (tmpString.equals(IXmlConstants.ELEMENT_METHOD))) {
174:
175: //Mark this method as being on the call chain path
176: updateNodeWithDepth(tmpMethodElement, 0);
177: updateNodeWithVolume(tmpMethodElement);
178:
179: //Now Begin Loop through sub elements of this to check
180: //for calls
181: List possibleCalls = tmpMethodElement.getChildren();
182: Iterator possCallsLoop = possibleCalls.iterator();
183:
184: //Loop through possible Methods and follow all possible calls
185: while (possCallsLoop.hasNext()) {
186:
187: //Get Next Element and see if it is a 'METHOD' tag
188: Element tmpCallsElement = (Element) possCallsLoop
189: .next();
190: tmpString = tmpCallsElement.getName();
191: if ((tmpString != null)
192: && (tmpString
193: .equals(IXmlConstants.ELEMENT_CALLS))) {
194:
195: //Now Begin Loop through sub elements of this to
196: //check for calls
197: followTheCall(currentDocument, tmpCallsElement,
198: currentCallDepth);
199: }
200: }
201: }
202: }
203: }
204:
205: /**
206: * Follows the Call , marking the XML node that it finds , plus
207: * checks to see if there are any further calls from the found node /
208: * method. If so will call itself recursivly ..
209: * @param currentDocument that we are chaining within
210: * @param callsElement to start marking out calls in the XML
211: * (actual Calls Tag)
212: * @param currentSearchDepth
213: */
214: private void followTheCall(Document currentDocument,
215: Element callsElement, int currentSearchDepth)
216: throws JDOMException {
217:
218: //Local Variables
219: Attribute tmpAttribute;
220: String findClassName;
221: String findMethodName;
222: Element possMethodElement;
223: Element nextClassElementInChain;
224: Element nextMethodElementInChain;
225: List possNextMethodElementsInChain;
226: Iterator nextElementMethodsLoop;
227:
228: //Make copy of Current Call depth (so we can vary separately)
229: int localCallDepth = currentSearchDepth;
230:
231: //Get the Class Name
232: tmpAttribute = callsElement
233: .getAttribute(IXmlConstants.ATTRIBUTE_CLASS);
234: if (tmpAttribute == null) {
235: return;
236: }
237: findClassName = tmpAttribute.getValue();
238:
239: // Get the Method Name that we are looking for
240: tmpAttribute = callsElement
241: .getAttribute(IXmlConstants.ATTRIBUTE_METHOD);
242: if (tmpAttribute == null) {
243: return;
244: }
245: findMethodName = tmpAttribute.getValue();
246:
247: //Use the XML Util to Search our document for the target class
248: nextClassElementInChain = XmlUtil.findNode(currentDocument,
249: IXmlConstants.ATTRIBUTE_NAME, findClassName);
250:
251: //Nothing found? then this is the end of *this* chain
252: if (nextClassElementInChain == null) {
253: return;
254: }
255:
256: //see if this has a matching method element
257: possNextMethodElementsInChain = nextClassElementInChain
258: .getChildren(IXmlConstants.ELEMENT_METHOD);
259:
260: nextElementMethodsLoop = possNextMethodElementsInChain
261: .iterator();
262:
263: //Loop to find the matching element
264: while (nextElementMethodsLoop.hasNext()) {
265:
266: //Check to see if this is the matching (called) element
267: possMethodElement = (Element) nextElementMethodsLoop.next();
268: tmpAttribute = possMethodElement
269: .getAttribute(IXmlConstants.ATTRIBUTE_NAME);
270: if ((tmpAttribute != null)
271: && (tmpAttribute.getValue().equals(findMethodName))) {
272:
273: //Update Attributes with Data
274: updateNodeWithDepth(possMethodElement, localCallDepth);
275: updateNodeWithVolume(possMethodElement);
276:
277: //Increase current search depth
278: localCallDepth++;
279:
280: //Now follow any more <CALLS> tags in this method , if required
281: if (localCallDepth < MAX_SEARCH_DEPTH) {
282: followFurtherCalls(currentDocument,
283: possMethodElement, localCallDepth);
284: }
285: } // end -if searched method found
286:
287: } // end-while look for method
288: }
289:
290: /**
291: * Checks to see if there are any futher <Calls> tags in this method , then
292: * follows them
293: * @param currentDocument that we are chaining within
294: * @param callsElement to start marking out calls in the XML (actual Calls Tag
295: * @param currentSearchDepth
296: */
297: private void followFurtherCalls(Document currentDocument,
298: Element methodElement, int currentSearchDepth)
299: throws JDOMException {
300:
301: //Local variables
302: String tmpString;
303: Element tmpCallsElement;
304: List possibleCalls;
305: Iterator possCallsLoop;
306:
307: possibleCalls = methodElement.getChildren();
308: possCallsLoop = possibleCalls.iterator();
309:
310: //Loop through possible Methods and follow all possible calls
311: while (possCallsLoop.hasNext()) {
312:
313: //Get Next Element and see if it is a 'CALLS' tag
314: tmpCallsElement = (Element) possCallsLoop.next();
315: tmpString = tmpCallsElement.getName();
316: if ((tmpString != null)
317: && (tmpString.equals(IXmlConstants.ELEMENT_CALLS))) {
318:
319: //Do (Recursive) call to 'follow the call' (which called
320: // this method in the first place
321: followTheCall(currentDocument, tmpCallsElement,
322: currentSearchDepth);
323:
324: } // end if calls tag
325:
326: } // end if poss calls tag
327:
328: }
329:
330: /**
331: * Updates the current (Method) Node to add call Depthdata to it
332: * @param nodeToUpdate
333: * @param currentSearchDepth
334: */
335: private void updateNodeWithDepth(Element nodeToUpdate,
336: int currentSearchDepth) {
337: //Local Variables
338: int previousCallDepth;
339: int currentCallDepth = currentSearchDepth; //default
340: Attribute tmpAttribute;
341:
342: //Mark the Call depth (if not already done so)
343:
344: //Get current call depth , if it exists
345: try {
346: tmpAttribute = nodeToUpdate
347: .getAttribute(ATTRIBUTE_MIN_CALL_DEPTH);
348: if (tmpAttribute != null) {
349: previousCallDepth = Integer.parseInt(tmpAttribute
350: .getValue());
351:
352: //Compare to incoming and use existing if it is lower
353: if (currentSearchDepth > previousCallDepth) {
354: currentSearchDepth = previousCallDepth;
355: }
356: }
357: } catch (NumberFormatException nfe) {
358: //Do nothing - will use incoming
359: }
360:
361: //Mark Attribute with lower of the two
362: tmpAttribute = new Attribute(ATTRIBUTE_MIN_CALL_DEPTH, String
363: .valueOf(currentSearchDepth));
364: nodeToUpdate.setAttribute(tmpAttribute);
365:
366: }
367:
368: /**
369: * Updates the current (Method) Node to add call Volume data to it
370: * @param nodeToUpdate
371: * @param currentSearchDepth
372: */
373: private void updateNodeWithVolume(Element nodeToUpdate) {
374: //Local Variables
375: int callVolume = 1; //default
376: Attribute tmpAttribute;
377:
378: //Mark the number of time called (if not already done so)
379:
380: //Get current number of times called , if it exists
381: try {
382: tmpAttribute = nodeToUpdate
383: .getAttribute(ATTRIBUTE_NUMBER_OF_CALLS);
384: if (tmpAttribute != null) {
385: callVolume = Integer.parseInt(tmpAttribute.getValue());
386:
387: //Increment this call volume by one
388: callVolume++;
389: }
390: } catch (NumberFormatException nfe) {
391: //Do Nothing - volume will default to 1
392: }
393:
394: //Mark Attribute with the updated volume of calls
395: tmpAttribute = new Attribute(ATTRIBUTE_NUMBER_OF_CALLS, String
396: .valueOf(callVolume));
397: nodeToUpdate.setAttribute(tmpAttribute);
398:
399: }
400:
401: }
|