001: package net.firstpartners.nounit.reader.bytecode;
002:
003: /*
004: This Class Modified from JClassLib , Free software under the terms of the
005: Gnu Public Library (below). You can redistribute it and/or
006: modify it under the terms of the GNU General Public
007: License as published by the Free Software Foundation; either
008: version 2 of the license, or (at your option) any later version.
009: */
010:
011: import java.io.IOException;
012: import java.util.HashMap;
013: import java.util.HashSet;
014: import java.util.Iterator;
015:
016: import net.firstpartners.nounit.reader.ISnippetFactory;
017: import net.firstpartners.nounit.snippet.SnippetCalls;
018: import net.firstpartners.nounit.snippet.Snippets;
019: import net.firstpartners.nounit.utility.NoUnitException;
020:
021: import org.apache.log4j.Logger;
022:
023: import org.gjt.jclasslib.bytecode.AbstractInstruction;
024: import org.gjt.jclasslib.bytecode.BranchInstruction;
025: import org.gjt.jclasslib.bytecode.ImmediateByteInstruction;
026: import org.gjt.jclasslib.bytecode.ImmediateIntInstruction;
027: import org.gjt.jclasslib.bytecode.ImmediateShortInstruction;
028: import org.gjt.jclasslib.bytecode.IncrementInstruction;
029: import org.gjt.jclasslib.bytecode.InvokeInterfaceInstruction;
030: import org.gjt.jclasslib.bytecode.LookupSwitchInstruction;
031: import org.gjt.jclasslib.bytecode.MultianewarrayInstruction;
032: import org.gjt.jclasslib.bytecode.Opcodes;
033: import org.gjt.jclasslib.bytecode.OpcodesUtil;
034: import org.gjt.jclasslib.bytecode.TableSwitchInstruction;
035: import org.gjt.jclasslib.bytecode.MatchOffsetPair;
036: import org.gjt.jclasslib.io.ByteCodeReader;
037: import org.gjt.jclasslib.structures.AttributeInfo;
038: import org.gjt.jclasslib.structures.ClassFile;
039: import org.gjt.jclasslib.structures.InvalidByteCodeException;
040: import org.gjt.jclasslib.structures.MethodInfo;
041: import org.gjt.jclasslib.structures.attributes.CodeAttribute;
042:
043: /**
044: * Get the Other Methods Called by this one
045: * @author modified by Paul Browne
046: * @version 20/11/01
047: */
048: public class ByteCodeCallsSnippetFactory extends
049: AbstractByteCodeSnippetFactory implements ISnippetFactory,
050: Opcodes {
051:
052: /*
053: * Attribute that we are interested in within the file
054: */
055: private CodeAttribute attribute;
056:
057: /*
058: *Source Class file
059: */
060: private ClassFile classFile;
061:
062: private HashMap offsetToPosition = new HashMap();
063: private int opcodeCounterWidth;
064: private int offsetWidth;
065:
066: //handle to logger
067: static Logger log = Logger
068: .getLogger(ByteCodeCallsSnippetFactory.class);
069:
070: /**
071: * Holds Process ByteCode -> Instructions
072: */
073: private HashMap internalInstructions = new HashMap();
074:
075: /*
076: * Position in internalInstruction
077: */
078: private int currentInstructionPosition = 0;
079:
080: /**
081: * Makes note of important positons in the Internal Instructions Table
082: */
083: private HashSet methodCalls = new HashSet();
084:
085: /**
086: * Constant - marks normal instruction
087: */
088: private static final int I_NORMAL = 0;
089:
090: /**
091: * Constant - marks method call instruction
092: */
093: private static final int I_CALL = 1;
094:
095: /**
096: * Construct a bytecode document with an associated <tt>Code</tt>
097: * attribute and a parent class file.
098: * Only use the first Attribute from the method , which for methods should be OK!!
099: * @param styles the style cache
100: * @param attribute the <tt>Code</tt> attribute
101: * @param classFile the class file
102: */
103: public ByteCodeCallsSnippetFactory(MethodInfo this Method,
104: ClassFile classFile) throws IOException,
105: InvalidByteCodeException, NoUnitException {
106:
107: //Local Variables
108: CodeAttribute attribute;
109: int counter = 0;
110: boolean foundFlag = false;
111:
112: //Convert to handle to code attribute
113: //Following presumes only one (CodeAttribute) - throws NoUnitException if not
114: AttributeInfo[] genAttributes = this Method.getAttributes();
115:
116: while ((counter < genAttributes.length) && (!foundFlag)) {
117:
118: //Check and convert if possible
119: if (genAttributes[counter] instanceof CodeAttribute) {
120:
121: //Set flag to skip loop
122: foundFlag = true;
123:
124: //Do conversion and doc preparation
125:
126: log.debug(genAttributes[counter].getClass().toString());
127:
128: attribute = (CodeAttribute) genAttributes[counter];
129:
130: // Once Converted , Same as other constructor
131: this .attribute = attribute;
132: this .classFile = classFile;
133:
134: setupDocument();
135:
136: } else {
137: counter++;
138: }
139: }
140:
141: }
142:
143: /**
144: * Construct a bytecode document with an associated <tt>Code</tt>
145: * attribute and a parent class file.
146: * @param styles the style cache
147: * @param attribute the <tt>Code</tt> attribute
148: * @param classFile the class file
149: */
150: public ByteCodeCallsSnippetFactory(CodeAttribute attribute,
151: ClassFile classFile) throws IOException,
152: InvalidByteCodeException {
153:
154: this .attribute = attribute;
155: this .classFile = classFile;
156:
157: setupDocument();
158: }
159:
160: /**
161: * Get the document containing the opcode counters.
162: * @return internalByteCodes - String describing the internal calls
163: */
164: public String toString() {
165:
166: StringBuffer internalByteCodes = new StringBuffer();
167:
168: //Loop through , adding internal Instructions
169: for (int a = 0; a < this .currentInstructionPosition; a++) {
170:
171: internalByteCodes.append(this .internalInstructions
172: .get(String.valueOf(a)));
173:
174: }
175:
176: return internalByteCodes.toString();
177: }
178:
179: /**
180: * See what methods are called by the implementation of this code
181: * <BR> Called Methods are the one <I>after</I> the stored instruction
182: * <BR> How they are called , is the instruction <I>before</I> the stored instruction
183: * <BR> Constant pool reference is the <I>actual</I> stored instruction
184: * @return Snippets Collection of Called Methods (String , Names)
185: * Get the Collection of Snippets (as read from the source)
186: * @return Snippets
187: */
188: public Snippets getSnippets() throws NoUnitException {
189:
190: //Internal Variables
191: int this Instruction;
192: String tmpString;
193: String tmpClass;
194: String tmpMethod;
195: Snippets calls = new Snippets();
196: SnippetCalls tmpSnippet;
197: Iterator myLoop = methodCalls.iterator();
198:
199: //Loop and add relevant calls
200: while (myLoop.hasNext()) {
201:
202: //Get the Param Info via the Class Pool
203:
204: //Get the Class & Method Info
205: this Instruction = ((Integer) myLoop.next()).intValue();
206: this Instruction++; // we want the one after - see note above
207: tmpString = (String) this .internalInstructions.get(String
208: .valueOf(this Instruction));
209:
210: // Put this into Snippet Form and save
211: tmpClass = getClassFromCalledString(tmpString);
212: tmpMethod = getMethodFromCalledString(tmpString);
213: tmpSnippet = new SnippetCalls(tmpClass, tmpMethod, null);
214: calls.add(tmpSnippet);
215:
216: }
217:
218: return calls;
219: }
220:
221: /**
222: * Store the String as Part of the Internal Instruction Set (nomral type)
223: * @param string to Add
224: */
225: protected void appendString(String string) {
226:
227: appendString(I_NORMAL, string);
228:
229: }
230:
231: /**
232: * Store the String as Part of the Internal Instruction Set
233: * @param type of Instruction Being added (as per constants)
234: * @param string to Add
235: */
236: protected void appendString(int type, String string) {
237:
238: //Add only if the incoming String is not null
239: if (string != null) {
240:
241: //Check to see if it should be noted in the method calls table
242: if ((type == I_CALL)
243: && (this
244: .doubleCheckCalledMethod(currentInstructionPosition))) {
245: this .methodCalls.add(new Integer(
246: currentInstructionPosition));
247:
248: }
249:
250: //Add everything to the internal instruction table
251: internalInstructions.put(String
252: .valueOf(this .currentInstructionPosition), string);
253: currentInstructionPosition++;
254:
255: }
256: }
257:
258: /**
259: * Setup the Internal Document
260: */
261: private void setupDocument() throws IOException,
262: InvalidByteCodeException {
263:
264: byte[] code = attribute.getCode();
265:
266: java.util.List instructions = ByteCodeReader.readByteCode(code);
267:
268: // calculateOffsetWidth(instructions);
269:
270: int[] linesPerOpcode = new int[instructions.size()];
271:
272: Iterator it = instructions.iterator();
273: AbstractInstruction currentInstruction;
274: int instructionCount = 0;
275: while (it.hasNext()) {
276: currentInstruction = (AbstractInstruction) it.next();
277: linesPerOpcode[instructionCount++] = addInstructionToDocument(currentInstruction);
278: }
279:
280: createOpcodeCounterDocument(linesPerOpcode);
281:
282: }
283:
284: /**
285: * Add More Information to internal store
286: */
287: private int addInstructionToDocument(AbstractInstruction instruction)
288: throws InvalidByteCodeException {
289:
290: int offset = instruction.getOffset();
291:
292: appendString(getPaddedValue(offset, offsetWidth));
293:
294: appendString(" " + instruction.getOpcodeVerbose());
295:
296: int additionalLines = addOpcodeSpecificInfo(instruction);
297:
298: appendString("\n");
299:
300: return additionalLines + 1;
301: }
302:
303: /**
304: * Add More Information to internal store
305: */
306: private int addOpcodeSpecificInfo(AbstractInstruction instruction)
307: throws InvalidByteCodeException {
308:
309: int additionalLines = 0;
310:
311: if (instruction instanceof ImmediateByteInstruction) {
312: additionalLines += addImmediateByteSpecificInfo((ImmediateByteInstruction) instruction);
313: } else if (instruction instanceof ImmediateShortInstruction) {
314: additionalLines += addImmediateShortSpecificInfo((ImmediateShortInstruction) instruction);
315: } else if (instruction instanceof ImmediateIntInstruction) {
316: additionalLines += addImmediateIntSpecificInfo((ImmediateIntInstruction) instruction);
317: } else if (instruction instanceof BranchInstruction) {
318: additionalLines += addBranchSpecificInfo((BranchInstruction) instruction);
319: } else if (instruction instanceof TableSwitchInstruction) {
320: additionalLines += addTableSwitchSpecificInfo((TableSwitchInstruction) instruction);
321: } else if (instruction instanceof LookupSwitchInstruction) {
322: additionalLines += addLookupSwitchSpecificInfo((LookupSwitchInstruction) instruction);
323: }
324:
325: return additionalLines;
326: }
327:
328: /**
329: * Add More Information to internal store
330: */
331: private int addImmediateByteSpecificInfo(
332: ImmediateByteInstruction instruction)
333: throws InvalidByteCodeException {
334:
335: int opcode = instruction.getOpcode();
336: int sourceOffset = instruction.getOffset();
337: int immediateByte = instruction.getImmediateByte();
338:
339: if (opcode == OPCODE_LDC) {
340: addConstantPoolLink(immediateByte, sourceOffset);
341: } else if (opcode == OPCODE_NEWARRAY) {
342: String verbose = OpcodesUtil
343: .getArrayTypeVerbose(immediateByte);
344: appendString(" " + immediateByte + " (" + verbose + ")");
345:
346: } else {
347: appendString(" " + immediateByte);
348:
349: if (instruction instanceof IncrementInstruction) {
350: appendString(" by");
351: appendString(" "
352: + ((IncrementInstruction) instruction)
353: .getIncrementConst());
354: }
355: }
356: return 0;
357: }
358:
359: /**
360: * Add More Information to internal store
361: */
362: private int addImmediateShortSpecificInfo(
363: ImmediateShortInstruction instruction)
364: throws InvalidByteCodeException {
365:
366: int opcode = instruction.getOpcode();
367: int sourceOffset = instruction.getOffset();
368: int immediateShort = instruction.getImmediateShort();
369:
370: if (opcode == OPCODE_SIPUSH) {
371: appendString(" " + immediateShort);
372: } else {
373: addConstantPoolLink(immediateShort, sourceOffset);
374:
375: if (instruction instanceof InvokeInterfaceInstruction) {
376: appendString(" count "
377: + ((InvokeInterfaceInstruction) instruction)
378: .getCount());
379:
380: } else if (instruction instanceof MultianewarrayInstruction) {
381: appendString(" dim "
382: + ((MultianewarrayInstruction) instruction)
383: .getDimensions());
384:
385: }
386: }
387:
388: return 0;
389: }
390:
391: /**
392: * Add More Information to internal store
393: */
394: private int addImmediateIntSpecificInfo(
395: ImmediateIntInstruction instruction)
396: throws InvalidByteCodeException {
397:
398: int immediateInt = instruction.getImmediateInt();
399: int sourceOffset = instruction.getOffset();
400:
401: addConstantPoolLink(immediateInt, sourceOffset);
402:
403: return 0;
404: }
405:
406: /**
407: * Add More Information to internal store
408: */
409: private int addBranchSpecificInfo(BranchInstruction instruction) {
410:
411: int branchOffset = instruction.getBranchOffset();
412: int instructionOffset = instruction.getOffset();
413:
414: addOffsetLink(branchOffset, instructionOffset);
415:
416: return 0;
417: }
418:
419: /**
420: * Add More Information to internal store
421: */
422: private int addTableSwitchSpecificInfo(
423: TableSwitchInstruction instruction) {
424:
425: int instructionOffset = instruction.getOffset();
426: int lowByte = instruction.getLowByte();
427: int highByte = instruction.getHighByte();
428: int[] jumpOffsets = instruction.getJumpOffsets();
429:
430: appendString(" " + lowByte + " to " + highByte + "\n");
431:
432: for (int i = 0; i <= highByte - lowByte; i++) {
433: appendString("\u0009" + (i + lowByte) + ": ");
434: addOffsetLink(jumpOffsets[i], instructionOffset);
435: appendString("\n");
436:
437: }
438: appendString("\u0009default: ");
439: addOffsetLink(instruction.getDefaultOffset(), instructionOffset);
440:
441: return highByte - lowByte + 2;
442: }
443:
444: /**
445: * Add More Information to internal store
446: */
447: private int addLookupSwitchSpecificInfo(
448: LookupSwitchInstruction instruction) {
449:
450: int instructionOffset = instruction.getOffset();
451: java.util.List matchOffsetPairs = instruction
452: .getMatchOffsetPairs();
453: int matchOffsetPairsCount = matchOffsetPairs.size();
454:
455: appendString(" " + matchOffsetPairsCount + "\n");
456:
457: MatchOffsetPair matchOffsetPairEntry;
458:
459: for (int i = 0; i < matchOffsetPairsCount; i++) {
460: matchOffsetPairEntry = (MatchOffsetPair) matchOffsetPairs
461: .get(i);
462: appendString("\u0009" + matchOffsetPairEntry.getMatch()
463: + ": ");
464: addOffsetLink(matchOffsetPairEntry.getOffset(),
465: instructionOffset);
466: appendString("\n");
467:
468: }
469: appendString("\u0009default: ");
470: addOffsetLink(instruction.getDefaultOffset(), instructionOffset);
471:
472: return matchOffsetPairsCount + 1;
473: }
474:
475: /**
476: * Add More Information to internal store
477: */
478: private void addConstantPoolLink(int constantPoolIndex,
479: int sourceOffset) throws InvalidByteCodeException {
480:
481: appendString(I_CALL, "#" + constantPoolIndex);
482:
483: String name = classFile
484: .getConstantPoolEntryName(constantPoolIndex);
485: if (name.length() > 0) {
486: appendString(" <" + name + ">");
487: }
488:
489: }
490:
491: /**
492: * Add More Information to internal store
493: */
494: private void addOffsetLink(int branchOffset, int instructionOffset) {
495:
496: int totalOffset = branchOffset + instructionOffset;
497:
498: appendString(String.valueOf(totalOffset));
499:
500: appendString(" (" + (branchOffset > 0 ? "+" : "")
501: + String.valueOf(branchOffset) + ")");
502: }
503:
504: /**
505: *Called during inital setup
506: */
507: private void createOpcodeCounterDocument(int[] linesPerOpcode) {
508:
509: int numberOfOpcodes = linesPerOpcode.length;
510:
511: opcodeCounterWidth = String.valueOf(numberOfOpcodes - 1)
512: .length();
513:
514: for (int i = 0; i < numberOfOpcodes; i++) {
515: appendString(getPaddedValue(i, opcodeCounterWidth));
516:
517: for (int j = 0; j < linesPerOpcode[i]; j++) {
518: appendString("\n");
519: }
520: }
521:
522: }
523:
524: /**
525: * Pads out a number to a certain width
526: * @param number to pad out
527: * @param width required
528: * @return String of required length , containing spaces and number
529: */
530: private static String getPaddedValue(int number, int width) {
531:
532: StringBuffer buffer = new StringBuffer();
533: String value = String.valueOf(number);
534: int valueLength = value.length();
535: for (int i = valueLength; i < width; i++) {
536: buffer.append(' ');
537: }
538: buffer.append(value);
539: return buffer.toString();
540: }
541:
542: /**
543: * Double Checks to see if this is really an instruction to call another method
544: * @see getCalledMethods() javadoc for info on how the internal instruction store
545: * is currently implemented
546: * @param instructionReference to check
547: * @return true if is call to another method , false if not
548: */
549: private boolean doubleCheckCalledMethod(int instructionReference) {
550:
551: //Internal Variables
552: String previousInstruction = (String) internalInstructions
553: .get(String.valueOf(instructionReference - 1));
554:
555: //Check for nothing found
556: if (previousInstruction == null) {
557: return false;
558: }
559:
560: //Check to see what this string says
561: if (previousInstruction.indexOf("invoke") > -1) {
562: return true;
563: } else {
564: return false;
565: }
566:
567: }
568:
569: /**
570: * Extracts the class portion from the called string
571: * @param inString to split
572: * @return String , the class portion only
573: */
574: private String getClassFromCalledString(String inString) {
575:
576: String returnString = "";
577:
578: //Find Place to break
579: int breakplace = inString.lastIndexOf(".");
580:
581: //Break, clean and retrun
582: returnString = inString.substring(0, breakplace);
583:
584: returnString = tidyNames(returnString);
585:
586: return returnString;
587: }
588:
589: /**
590: * Extracts the class portion from the called string
591: * @param inString to split
592: * @return String , the class portion only
593: */
594: private String getMethodFromCalledString(String inString) {
595:
596: String returnString = "";
597:
598: //Find Place to break
599: int breakplace = inString.lastIndexOf(".");
600:
601: //Break , clean and retrun
602: returnString = inString
603: .substring(breakplace, inString.length());
604:
605: returnString = tidyNames(returnString);
606:
607: return returnString;
608: }
609:
610: }
|