fix selection for left side of a assignment such as "a = b + c"
[eclipsethinslicer.git] / Svelte / src / edu / berkeley / cs / bodik / svelte / slicers / JavaLanguage.java
blob5dfff6fa37dcccfa6fe40c9469f7703f7f1a8c84
1 package edu.berkeley.cs.bodik.svelte.slicers;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.LinkedList;
7 import java.util.List;
9 import org.eclipse.core.resources.IFile;
10 import org.eclipse.core.runtime.CoreException;
11 import org.eclipse.core.runtime.IProgressMonitor;
12 import org.eclipse.core.runtime.IStatus;
13 import org.eclipse.core.runtime.Status;
14 import org.eclipse.debug.core.DebugPlugin;
15 import org.eclipse.debug.core.ILaunchConfiguration;
16 import org.eclipse.debug.core.ILaunchManager;
17 import org.eclipse.jdt.core.ICompilationUnit;
18 import org.eclipse.jdt.core.IJavaProject;
19 import org.eclipse.jdt.core.IMethod;
20 import org.eclipse.jdt.core.IType;
21 import org.eclipse.jdt.core.JavaCore;
22 import org.eclipse.jdt.core.dom.ASTNode;
23 import org.eclipse.jdt.core.dom.ArrayAccess;
24 import org.eclipse.jdt.core.dom.ArrayCreation;
25 import org.eclipse.jdt.core.dom.ArrayInitializer;
26 import org.eclipse.jdt.core.dom.Assignment;
27 import org.eclipse.jdt.core.dom.Expression;
28 import org.eclipse.jdt.core.dom.FieldAccess;
29 import org.eclipse.jdt.core.dom.IMethodBinding;
30 import org.eclipse.jdt.core.dom.ITypeBinding;
31 import org.eclipse.jdt.core.dom.InfixExpression;
32 import org.eclipse.jdt.core.dom.MethodDeclaration;
33 import org.eclipse.jdt.core.dom.MethodInvocation;
34 import org.eclipse.jdt.core.dom.ParenthesizedExpression;
35 import org.eclipse.jdt.core.dom.QualifiedName;
36 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
37 import org.eclipse.jface.text.BadLocationException;
38 import org.eclipse.jface.text.IDocument;
39 import org.eclipse.ui.IEditorInput;
40 import org.eclipse.ui.IEditorPart;
41 import org.eclipse.ui.PlatformUI;
42 import org.eclipse.ui.progress.UIJob;
43 import org.eclipse.ui.texteditor.AbstractTextEditor;
44 import org.eclipse.ui.texteditor.IDocumentProvider;
45 import org.eclipse.ui.texteditor.ITextEditor;
47 import com.ibm.wala.ipa.callgraph.CGNode;
48 import com.ibm.wala.ipa.callgraph.CallGraph;
49 import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
50 import com.ibm.wala.ipa.slicer.NormalReturnCaller;
51 import com.ibm.wala.ipa.slicer.NormalStatement;
52 import com.ibm.wala.ipa.slicer.ParamCallee;
53 import com.ibm.wala.ipa.slicer.Statement;
54 import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
55 import com.ibm.wala.ssa.SSAArrayStoreInstruction;
56 import com.ibm.wala.ssa.SSAInstruction;
57 import com.ibm.wala.ssa.SSAInvokeInstruction;
58 import com.ibm.wala.types.TypeReference;
60 import edu.berkeley.cs.bodik.svelte.CGNodeUtils;
61 import edu.berkeley.cs.bodik.svelte.CallGraphUtils;
62 import edu.berkeley.cs.bodik.svelte.Slice;
63 import edu.berkeley.cs.bodik.svelte.Slicing;
64 import edu.berkeley.cs.bodik.svelte.SourcePosition;
65 import edu.berkeley.cs.bodik.svelte.SvelteAnalysisEngine;
66 import edu.berkeley.cs.bodik.svelte.Slicing.SliceType;
67 import edu.berkeley.cs.bodik.svelte.plugin.EclipseJdtUtils;
68 import edu.berkeley.cs.bodik.svelte.plugin.EclipseUtils;
69 import edu.berkeley.cs.bodik.svelte.plugin.SveltePlugin;
71 /**
72 * A slicer that handles Java code by using CAST's Java support
74 * @author DarkWulf
76 public class JavaLanguage implements ILanguage {
77 public static boolean useShrike = false;
79 /**
81 * @author DarkWulf
83 private class JavaSliceEnvironment extends AbstractSliceEnvironment {
84 private IJavaProject javaProject;
85 private SvelteAnalysisEngine se = new SvelteAnalysisEngine();
87 private JavaSliceEnvironment(IFile file, long modTimeStamp) {
88 super(file, modTimeStamp);
89 this.javaProject = ((ICompilationUnit) JavaCore.create(file)).getJavaProject();
91 // initialize the SvelteAnalysisEngine with appropriate entry points
92 this.se.addEclipseClasspaths(this.javaProject, useShrike);
94 List<String> mainClasses = findLauncherMainClasses(javaProject);
96 // if no launchers for project, try open editor's className anyway.
97 // the user may get the
98 // idea...
99 String fileClassname = this.getClassName(file);
100 if ((!mainClasses.contains(fileClassname)) && EclipseJdtUtils.fileHasMain(file))
101 mainClasses.add(fileClassname);
103 se.findEntrypointFromClassnamesOfMain(mainClasses);
107 * Retrieves the class name of a given file.
109 * Since this ignores position we will never get an inner class from this, so the code
110 * you're looking for may be in another class file.
112 * @param file
113 * @return the fully qualified class name for the file
115 private String getClassName(IFile file) {
116 ICompilationUnit compilationUnit = (ICompilationUnit) JavaCore.create(file);
117 IType type = compilationUnit.findPrimaryType();
118 return type.getFullyQualifiedName();
122 * If the SourcePosition selects a formal parameter in a method declaration, return the
123 * parameter index, starting at 0. If it finds a parameter, also (should but can't since we
124 * can't get the document in this thread) sets the line numbers of the beginning of the
125 * function to make sure we can find the method in WALA's CGNode. Also uses selectAndReveal
126 * to select the whole parameter (hack) if necessary
128 * @param position
129 * @param editor
130 * @return Parameter index of -1 if position does not correspond to a selected or
131 * partially-selected parameter
133 private int getParameterIndex(SourcePosition position, IEditorPart editor) {
134 // get AST
135 ICompilationUnit cu = (ICompilationUnit) JavaCore.create(position.getFile());
136 ASTNode root = EclipseUtils.getRootNode(cu);
138 // get covering node
139 ASTNode covering = EclipseUtils.getCoveringNode(root, position.offsetStart,
140 position.offsetEnd - position.offsetStart);
142 // ascend the tree until we find a method.
143 ASTNode iter = covering;
144 while (iter != null) {
145 if (iter.getNodeType() == ASTNode.SINGLE_VARIABLE_DECLARATION) {
146 SingleVariableDeclaration svd = (SingleVariableDeclaration) iter;
147 System.out.println("testing the singlevariabledeclaration");
148 System.out.println(svd.getLocationInParent());
149 MethodDeclaration md = (MethodDeclaration) svd.getParent();
151 // set position's line number
152 IDocumentProvider prov = ((ITextEditor) editor).getDocumentProvider();
153 IEditorInput input = editor.getEditorInput();
154 IDocument document = prov.getDocument(input);
155 try {
156 position.lineStart = position.lineEnd = document.getLineOfOffset(md
157 .getBody().getStartPosition()) + 1;
158 } catch (BadLocationException e) {
159 e.printStackTrace();
162 selectAndRevealInUIThread(iter.getStartPosition(), iter.getLength());
163 return md.parameters().indexOf(svd);
165 iter = iter.getParent();
168 return -1;
171 @Override
172 public CallGraph getCallGraph() {
173 return se.getCallGraph();
176 @Override
177 public PointerAnalysis getPointerAnalysis() {
178 return se.getPointerAnalysis();
182 * Look at the Java application launchers for a given project and determine all main
183 * classes.
185 * @param project
186 * @return List<String> where each element is the fully qualified name of each entrypoint
187 * listed in the project's launchers
189 public List<String> findLauncherMainClasses(IJavaProject project) {
190 ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
191 List<String> classes = new LinkedList<String>();
192 try {
193 ILaunchConfiguration configs[] = manager.getLaunchConfigurations();
194 for (ILaunchConfiguration cfg : configs) {
195 if (cfg.getType().getIdentifier().equals(
196 "org.eclipse.jdt.launching.localJavaApplication")
197 && cfg.getAttribute("org.eclipse.jdt.launching.PROJECT_ATTR", "")
198 .equals(project.getElementName())) {
199 String classWithMain = cfg.getAttribute("org.eclipse.jdt.launching.MAIN_TYPE",(String) null);
200 IType classWithMainType = project.findType(classWithMain);
201 // check if it really has a main method. TODO: this only works for source. maybe take this out.
202 if ( classWithMainType != null ) {
203 for ( IMethod met: classWithMainType.getMethods() )
204 if ( met.getElementName().equals("main") && met.getParameterTypes().length == 1
205 && (met.getParameterTypes()[0].equals("[QString;")
206 || met.getParameterTypes()[0].equals("[Qjava.lang.String;"))) {
207 classes.add(classWithMain);
208 break;
213 } catch (CoreException e) {
214 e.printStackTrace();
216 return classes;
219 @Override
220 public Slice getSlice(SliceType slicerType, int depth, Collection<Statement> seed) {
222 if (seed.size() == 0)
223 System.err.println("ERROR: slice is size 0");
225 Slice s = Slicing.doSlice(slicerType, this, new Slice(seed, this), depth);
227 s.dump();
229 s.prune(true);
230 return s;
233 @Override
234 public Collection<Statement> generateSeed(SourcePosition position, IEditorPart editor) {
235 CallGraph cg = getCallGraph();
236 IFile file = position.getFile();
238 if ( cg == null ) {
239 EclipseUtils.errorMsgInUIThread("Could not build call graph",
240 "Could not build call graph, probably due to an internal error. Check console output for more info.");
241 return null;
244 CGNode cgn = null;
246 // check if we have selected a parameter.
247 // do this first, in changes position so we can find the method (if the declaration
248 // spans multiple lines)
249 int paramIndex = getParameterIndex(position, editor);
250 int linenum = position.lineStart; // selection.getStartLine() + 1;
252 Collection<Statement> result;
254 if (useShrike) {
255 // Shrike-loaded class don't have source file information. Compare based on
256 // fully-qualified class name.
257 // TODO: this doesn't find correct class name for an inner class.
258 // We have to use the JDT AST to see what the innermost class/interface is.
259 String mainClassName = this.getClassName(file);
261 cgn = CallGraphUtils.findMethodIncludingLineNumInClass(cg, linenum, mainClassName);
262 } else {
263 cgn = CallGraphUtils.findMethodIncludingLineNum(cg, linenum, file.getLocation()
264 .toOSString());
267 if ( cgn == null ) {
268 EclipseUtils.errorMsgInUIThread("Could not find method",
269 "Could not find a method containing the line number of code you have selected. It may be that:\n1) You have selected something outside of a method \n2) The source file is out-of-date and needs to be saved\n3) The method is not reachable from the program entrypoints");
270 return null;
273 if (paramIndex != -1) { // slice from formal parameter
274 int valueNumber = paramIndex + 1; // paramIndex starts at zero
275 if (!cgn.getMethod().isStatic())
276 valueNumber++; // v1 = this
277 result = Collections.singleton((Statement) new ParamCallee(cgn, valueNumber));
278 } else if (useShrike) // all statements from line number
279 return CGNodeUtils.getStatementsFromLineNumber(cgn, linenum);
280 else { // slice from selection
281 result = reduceSubstatement(position,cgn);
282 if ( result.size() > 0 ) {
283 Object first = result.iterator().next();
284 if ( first instanceof NormalStatement && ((NormalStatement)first).getInstruction() == null ) {
285 EclipseUtils.errorMsgInUIThread("Could not find seed",
286 "The statement you have selected as a seed is null in the intermediate representation. This probably means that the statement has been optimized away, as in the case of simple assignment statements (\"x=y\"). Please choose another statement where the value in the assignement statement originated from.");
287 return null;
293 // swap out invoke instructions for their return values
294 ArrayList<Statement> newSeed = new ArrayList<Statement>();
295 for (Statement s : result)
296 if (s instanceof NormalStatement
297 && ((NormalStatement) s).getInstruction() instanceof SSAAbstractInvokeInstruction
298 && ((SSAAbstractInvokeInstruction) ((NormalStatement) s).getInstruction())
299 .getDeclaredResultType() != TypeReference.Void)
300 newSeed.add(new NormalReturnCaller(s.getNode(), ((NormalStatement) s)
301 .getInstructionIndex()));
302 else
303 newSeed.add(s);
305 if (result == null || newSeed.size() == 0 )
306 EclipseUtils.errorMsgInUIThread("Could not find seed",
307 "Could not find a suitable seed to slice from. It may be that:\n1) You have selected something that evaluates to a constant\n2) The statement selected has been optimized out (i.e., simple assignment statements)\n3) The source file is out-of-date and needs to be saved");
309 return newSeed;
313 * Some variables may not have statements at that line, e.g. assume foo, a, b, c, and d are
314 * locals: Then they will not have defining statements in 'foo.bar(a[b],c+d)', but their
315 * value numbers are used directly.
317 * Given a source position, if a simple name is selected, this function changes position
318 * to the the statement that uses the simple name and returns the index of the use.
320 * For example, let's say the user selects 'b' above: then position will be changed to the position
321 * of 'a[b]' and the return value will be 1 (i.e. we should look at use 1 in the WALA arrayload instruction).
323 * To accomplish this, we look at the context of the node referenced by position - i.e., the type of its parent node
324 * and its location in the parent node.
326 * @param position
327 * This variable is changed to the match parent node --the instruction that we should find in the IR,
328 * using the return value as the index into the uses of this instruction
329 * @param cgn
330 * @return
332 private Collection<Statement> reduceSubstatement(SourcePosition position, CGNode cgn) {
333 int substatementIndex = -1;
334 Class<? extends SSAInstruction> parentInstructionClass = null;
336 // get AST
337 ICompilationUnit cu = (ICompilationUnit) JavaCore.create(position.getFile());
338 ASTNode root = EclipseUtils.getRootNodeWithBindings(cu);
340 // get covering node
341 ASTNode covering = EclipseUtils.getCoveringNode(root, position.offsetStart,
342 position.offsetEnd - position.offsetStart);
344 // walk down any parentheses to get to the real expression to check if it is a
345 // simplename
346 while (covering.getNodeType() == ASTNode.PARENTHESIZED_EXPRESSION)
347 covering = ((ParenthesizedExpression) covering).getExpression();
349 if (covering.getNodeType() == ASTNode.SIMPLE_NAME) {
350 final ASTNode toSelect = covering;
352 // get rid of any parentheses above, so the next parent will be the actual parent.
353 // e.g. "(((foo))).bar" -> get to the outermost paren so parent will be
354 // qualifedexpression
355 while (covering.getParent().getNodeType() == ASTNode.PARENTHESIZED_EXPRESSION)
356 covering = covering.getParent();
358 ASTNode parent = covering.getParent();
359 if (parent.getNodeType() == ASTNode.ARRAY_INITIALIZER) {
360 ArrayInitializer ai = (ArrayInitializer) covering.getParent();
361 substatementIndex = 0;
362 boolean found = false;
363 for ( Object o: ai.expressions() ) {
364 if ( o.equals(covering) ) {
365 found = true;
366 break;
368 substatementIndex++;
371 return CGNodeUtils.getNthArrayStoreStatementFromOffset(cgn,
372 parent.getStartPosition(), parent.getStartPosition()+parent.getLength(),
373 substatementIndex, SSAArrayStoreInstruction.class);
374 } else if (parent.getNodeType() == ASTNode.ARRAY_CREATION) {
375 ArrayCreation ac = (ArrayCreation) covering.getParent();
376 substatementIndex = ac.dimensions().indexOf(covering);
377 if (substatementIndex != -1) {
378 position.offsetStart = ac.getStartPosition();
379 position.offsetEnd = ac.getStartPosition() + ac.getLength();
381 } else if (parent.getNodeType() == ASTNode.INFIX_EXPRESSION) {
382 InfixExpression ie = (InfixExpression) covering.getParent();
383 if ( covering == ie.getLeftOperand() ) {
384 substatementIndex = 0;
385 // don't look at extended operands, if there are any.
386 // also, JDT conversion uses start of left operand, not start of infix expression (difference?)
387 position.offsetStart = ie.getLeftOperand().getStartPosition();
388 position.offsetEnd = ie.getRightOperand().getStartPosition() + ie.getRightOperand().getLength();
389 } else if ( covering == ie.getRightOperand() ) {
390 substatementIndex = 1;
391 position.offsetStart = ie.getLeftOperand().getStartPosition();
392 position.offsetEnd = ie.getRightOperand().getStartPosition() + ie.getRightOperand().getLength();
393 } else {
394 // extended operand
395 substatementIndex = 1; // it will always be the second operand, since we add operands to the right hand side
396 // the trick is determining the position: from the start of the first expression to the end of this expression
397 position.offsetStart = ie.getLeftOperand().getStartPosition();
398 // position.offsetEnd same
400 } else if (parent.getNodeType() == ASTNode.METHOD_INVOCATION) {
401 MethodInvocation mi = (MethodInvocation) covering.getParent();
402 position.offsetStart = mi.getStartPosition();
403 position.offsetEnd = mi.getStartPosition() + mi.getLength();
405 // if varargs, args are boxed first, so we have to deal with that.
406 IMethodBinding binding = mi.resolveMethodBinding().getMethodDeclaration();
407 if ( binding.isVarargs() && mi.getExpression() != covering ) {
408 List arguments = mi.arguments();
409 int nActuals = arguments.size();
410 int nFormals = binding.getParameterTypes().length;
412 // if the nActuals != nFormals, it's definitely a varargs invocation. If
413 // nActuals == nFormals, however, we have to check the type of the last argument.
414 // if the last argument is an array type of the correct type, it will be passed in and not boxed.
415 ITypeBinding lastArgType = null;
416 if ( nActuals > 0 && arguments.get(nActuals-1) instanceof Expression ) // if nActuals=0 then it's definitely varargs and nFormals != 0
417 lastArgType = ((Expression)arguments.get(nActuals-1)).resolveTypeBinding();
419 if ( nActuals != nFormals || lastArgType.isSubTypeCompatible(binding.getParameterTypes()[nFormals-1]) ) {
420 int argIndex = arguments.indexOf(covering);
422 if ( argIndex >= (nFormals-1) ) {
423 int indexWithinVarargsArray = argIndex - (nFormals-1);
424 return CGNodeUtils.getNthArrayStoreStatementFromOffset(cgn,
425 parent.getStartPosition(), parent.getStartPosition() + parent.getLength(),
426 indexWithinVarargsArray, SSAArrayStoreInstruction.class);
427 // argIndex - nFormals is
429 // ELSE:
430 // one of the previous, regular args in a varargs invocation.
431 // be very sure to look at invocation instructions only (don't look at the new/array store instructions before it)
434 parentInstructionClass = SSAAbstractInvokeInstruction.class;
436 if (mi.getExpression() == covering)
437 substatementIndex = 0;
438 else {
439 int argIndex = mi.arguments().indexOf(covering);
440 if (argIndex != -1) {
441 substatementIndex = argIndex;
442 if (mi.getExpression() != null)
443 substatementIndex++; // result = argIndex + 1 "foo.bar(a,b,c)" -> a is index 1;
444 // "bar(a,b,c)" -> a is index 0
447 } else if (parent.getNodeType() == ASTNode.ARRAY_ACCESS) {
448 ArrayAccess aa = (ArrayAccess) covering.getParent();
449 if (parent.getParent().getNodeType() == ASTNode.ASSIGNMENT) {
450 // case 1: "a[b] = c" -> WALA statement covers whole thing (putfield) EXCEPT
451 // PARENTHESES (due to WALA/polyglot bug)!
452 position.offsetStart = parent.getParent().getStartPosition();
453 position.offsetEnd = parent.getParent().getStartPosition()
454 + parent.getParent().getLength();
455 } else {
456 // case 2: "a[b].c = d" (select a) or "a[b]"
457 // covers just a.b
458 position.offsetStart = parent.getStartPosition();
459 position.offsetEnd = parent.getStartPosition() + parent.getLength();
462 if (aa.getArray() == covering)
463 substatementIndex = 0;
464 if (aa.getIndex() == covering)
465 substatementIndex = 1;
466 } else if (parent.getNodeType() == ASTNode.QUALIFIED_NAME
467 || parent.getNodeType() == ASTNode.FIELD_ACCESS) {
468 // it will be a FIELD_ACCESS in the case if parentheses: (foo).bar = 5
469 // it will be a QUALIFIED_NAME otherwise: foo.bar = 5
470 ASTNode qualifier = (parent.getNodeType() == ASTNode.QUALIFIED_NAME) ? ((QualifiedName) parent)
471 .getQualifier()
472 : ((FieldAccess) parent).getExpression();
473 if (qualifier == covering) {
474 if (parent.getParent().getNodeType() == ASTNode.ASSIGNMENT &&
475 ((Assignment)parent.getParent()).getLeftHandSide().equals(parent) ) {
476 // case 1: "a.b = c" -> WALA statement covers whole thing (putfield)
477 // EXCEPT PARENTHESES (due to WALA/polyglot bug)!
478 position.offsetStart = parent.getParent().getStartPosition();
479 position.offsetEnd = parent.getParent().getStartPosition()
480 + parent.getParent().getLength();
481 } else {
482 // case 2: "a.b.c = d" (select a) or "a.b" -- getfield, WALA statement
483 // or foo = a.b
484 // covers just a.b
485 position.offsetStart = parent.getStartPosition();
486 position.offsetEnd = parent.getStartPosition() + parent.getLength();
488 substatementIndex = 0;
490 } else if ( parent.getNodeType() == ASTNode.ASSIGNMENT ) {
491 Assignment assign = (Assignment) parent;
492 if ( assign.getLeftHandSide() == covering ) {
493 // slice on value of right hand side
494 Expression rhs = assign.getRightHandSide();
495 // go past parentheses to get to the real value
496 while ( rhs.getNodeType() == ASTNode.PARENTHESIZED_EXPRESSION )
497 rhs = ((ParenthesizedExpression)rhs).getExpression();
498 position.offsetStart = rhs.getStartPosition();
499 position.offsetEnd = rhs.getStartPosition() + rhs.getLength();
501 // else
502 // right hand side, give warning
506 if (substatementIndex != -1) {
507 // selectAndReveal the actual simple name token to clarify what we are slicing
508 // on. this is kind of a hack
509 selectAndRevealInUIThread(toSelect.getStartPosition(), toSelect.getLength());
510 return CGNodeUtils.getStatementFromOffsetAndSubstatementIndex(cgn,
511 position.offsetStart, position.offsetEnd, substatementIndex,
512 parentInstructionClass);
519 return CGNodeUtils.getInnermostStatementFromOffset(cgn, position.offsetStart,
520 position.offsetEnd);
522 // System.out.println(covering.getParent().getNodeType());
524 // if ( covering.getNodeType() == ASTNode.)
525 // if covering node is a simple identifier
526 // check parent type:
527 // if the parent is a binop, getfield, putfield, invokevirtual, invokestatic, array
528 // load/store, new Bla[x][y],...
529 // get the "index".
530 // it would be nice to add more safety here in case JDT AST & WALA statements don't
531 // match up exactly
532 // return 0;
535 private void selectAndRevealInUIThread(final int startPosition, final int length) {
536 new UIJob("hello") {
537 @Override
538 public IStatus runInUIThread(IProgressMonitor monitor) {
539 ((AbstractTextEditor) (PlatformUI.getWorkbench().getActiveWorkbenchWindow()
540 .getActivePage().getActiveEditor())).selectAndReveal(startPosition,
541 length);
542 return new Status(IStatus.OK, SveltePlugin.PLUGIN_ID, null);
544 }.schedule();
547 @Override
548 public SourcePosition statementToPosition(Statement statement) {
549 return CGNodeUtils.getSourcePositionOfInstructionIndex(statement.getNode(),
550 ((NormalStatement) statement).getInstructionIndex(), this.javaProject);
554 @Override
555 public AbstractSliceEnvironment buildEnvironment(IFile file, long modTimestamp) {
556 return new JavaSliceEnvironment(file, modTimestamp);
560 * JavaSlicer can handle files with the .java extension
562 @Override
563 public boolean canSlice(IFile file) {
564 return "java".equalsIgnoreCase(file.getFileExtension());