started to implement infrastructure to parse java projects completely

This commit is contained in:
Martin Lippert
2017-08-27 18:48:00 +02:00
parent 6dc57c37b4
commit 3cbce5e81a
11 changed files with 285 additions and 106 deletions

View File

@@ -1,10 +1,25 @@
package org.springframework.ide.vscode.boot.java.symbols;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -15,14 +30,121 @@ public class BootJavaWorkspaceSymbolHandler implements WorkspaceSymbolHandler {
private SimpleLanguageServer server;
private JavaProjectFinder projectFinder;
public BootJavaWorkspaceSymbolHandler(BootJavaLanguageServer bootJavaLanguageServer, JavaProjectFinder javaProjectFinder) {
public BootJavaWorkspaceSymbolHandler(BootJavaLanguageServer server, JavaProjectFinder projectFinder) {
this.server = server;
this.projectFinder = projectFinder;
}
@Override
public List<? extends SymbolInformation> handle(WorkspaceSymbolParams params) {
Path root = this.server.getWorkspaceRoot();
scanFiles(root.toFile());
return collectSymbols();
}
private List<? extends SymbolInformation> collectSymbols() {
return SimpleTextDocumentService.NO_SYMBOLS;
}
private void scanFiles(File directory) {
if (this.projectFinder.isProjectRoot(directory)) {
IJavaProject project = this.projectFinder.find(directory);
if (project != null) {
scanProject(project, directory);
}
}
else if (directory.isDirectory() && directory.exists()) {
File[] files = directory.listFiles();
for (File file : files) {
scanFiles(file);
}
}
}
private void scanProject(IJavaProject project, File directory) {
try {
ASTParser parser = ASTParser.newParser(AST.JLS8);
Map<String, String> options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
parser.setCompilerOptions(options);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setStatementsRecovery(true);
parser.setBindingsRecovery(true);
parser.setResolveBindings(true);
String[] classpathEntries = getClasspathEntries(project);
String[] sourceEntries = new String[] {};
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
scanFiles(parser, directory);
}
catch (Exception e) {
e.printStackTrace();
}
}
private void scanFiles(ASTParser parser, File directory) throws Exception {
File[] javaFiles = directory.listFiles((file) -> file.getName().endsWith(".java"));
for (File javaFile : javaFiles) {
if (javaFile.isFile() && javaFile.exists()) {
scanFile(parser, javaFile);
}
}
File[] directories = directory.listFiles((file) -> file.isDirectory() && file.exists());
for (File dir : directories) {
scanFiles(parser, dir);
}
}
private void scanFile(ASTParser parser, File javaFile) throws Exception {
Path path = javaFile.toPath();
String unitName = path.getFileName().toString();
parser.setUnitName(unitName);
String content = new String(Files.readAllBytes(path));
parser.setSource(content.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
if (cu != null) {
System.out.println("AST node found: " + cu.getClass().getName());
scanAST(cu);
}
}
private void scanAST(CompilationUnit cu) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SingleMemberAnnotation node) {
System.out.println("annotation found: " + node.toString());
return super.visit(node);
}
@Override
public boolean visit(NormalAnnotation node) {
System.out.println("annotation found: " + node.toString());
return super.visit(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
System.out.println("annotation found: " + node.toString());
return super.visit(node);
}
});
}
private String[] getClasspathEntries(IJavaProject project) throws Exception {
IClasspath classpath = project.getClasspath();
Stream<Path> classpathEntries = classpath.getClasspathEntries();
return classpathEntries
.filter(path -> path.toFile().exists())
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
}
}

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.completions.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.Callable;
@@ -24,6 +25,7 @@ import org.junit.Test;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
@@ -35,7 +37,20 @@ import org.springframework.ide.vscode.project.harness.PropertyIndexHarness;
*/
public class ScopeCompletionTest {
private final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject();
protected final JavaProjectFinder javaProjectFinder = new JavaProjectFinder() {
@Override
public boolean isProjectRoot(File file) {
return false;
}
@Override
public IJavaProject find(File file) {
return null;
}
@Override
public IJavaProject find(IDocument doc) {
return getTestProject();
}
};
private LanguageServerHarness harness;
private PropertyIndexHarness indexHarness;

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.completions.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.Callable;
@@ -25,6 +26,7 @@ import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
import org.springframework.ide.vscode.boot.java.completions.ValueCompletionProcessor;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
@@ -35,8 +37,21 @@ import org.springframework.ide.vscode.project.harness.PropertyIndexHarness;
* @author Martin Lippert
*/
public class ValueCompletionTest {
private final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject();
protected final JavaProjectFinder javaProjectFinder = new JavaProjectFinder() {
@Override
public boolean isProjectRoot(File file) {
return false;
}
@Override
public IJavaProject find(File file) {
return null;
}
@Override
public IJavaProject find(IDocument doc) {
return getTestProject();
}
};
private LanguageServerHarness harness;
private IJavaProject testProject;
@@ -49,7 +64,7 @@ public class ValueCompletionTest {
public void setup() throws Exception {
testProject = ProjectsHarness.INSTANCE.mavenProject("test-annotations");
indexHarness = new PropertyIndexHarness();
harness = new LanguageServerHarness(new Callable<BootJavaLanguageServer>() {
@Override
public BootJavaLanguageServer call() throws Exception {
@@ -64,15 +79,15 @@ public class ValueCompletionTest {
};
harness.intialize(null);
}
private IJavaProject getTestProject() {
return testProject;
}
@Test
public void testPrefixIdentification() {
ValueCompletionProcessor processor = new ValueCompletionProcessor(null);
assertEquals("pre", processor.identifyPropertyPrefix("pre", 3));
assertEquals("pre", processor.identifyPropertyPrefix("prefix", 3));
assertEquals("", processor.identifyPropertyPrefix("", 0));
@@ -195,20 +210,20 @@ public class ValueCompletionTest {
public void testPlainPrefixCompletion() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(spri<*>)");
prepareDefaultIndexData();
assertAnnotationCompletions(
"@Value(\"${spring.prop1}\"<*>)");
}
@Test
public void testQoutedPrefixCompletion() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"spri<*>\")");
prepareDefaultIndexData();
assertAnnotationCompletions(
"@Value(\"${spring.prop1}<*>\")");
}
@Test
public void testRandomSpelExpressionNoCompletion() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{<*>}\")");
@@ -219,7 +234,7 @@ public class ValueCompletionTest {
"@Value(\"#{${else.prop3}<*>}\")",
"@Value(\"#{${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyDollar() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345$<*>}\")");
@@ -230,7 +245,7 @@ public class ValueCompletionTest {
"@Value(\"#{345${else.prop3}<*>}\")",
"@Value(\"#{345${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyDollerWithoutClosindBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${<*>}\")");
@@ -241,7 +256,7 @@ public class ValueCompletionTest {
"@Value(\"#{345${else.prop3}<*>}\")",
"@Value(\"#{345${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyDollerWithClosingBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${<*>}}\")");
@@ -252,7 +267,7 @@ public class ValueCompletionTest {
"@Value(\"#{345${else.prop3<*>}}\")",
"@Value(\"#{345${spring.prop1<*>}}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyPrefixWithoutClosingBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${spri<*>}\")");
@@ -261,7 +276,7 @@ public class ValueCompletionTest {
assertAnnotationCompletions(
"@Value(\"#{345${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyPrefixWithClosingBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${spri<*>}}\")");
@@ -270,21 +285,21 @@ public class ValueCompletionTest {
assertAnnotationCompletions(
"@Value(\"#{345${spring.prop1<*>}}\")");
}
private void prepareDefaultIndexData() {
indexHarness.data("spring.prop1", "java.lang.String", null, null);
indexHarness.data("data.prop2", "java.lang.String", null, null);
indexHarness.data("else.prop3", "java.lang.String", null, null);
}
private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception {
InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java");
String content = IOUtils.toString(resource);
content = content.replace(selectedAnnotation, annotationStatementBeforeTest);
editor = new Editor(harness, content, LanguageId.JAVA);
}
private void assertAnnotationCompletions(String... completedAnnotations) throws Exception {
List<CompletionItem> completions = editor.getCompletions();
int i = 0;
@@ -293,7 +308,7 @@ public class ValueCompletionTest {
clonedEditor.apply(completions.get(i++));
assertTrue(clonedEditor.getText().contains(expectedCompleted));
}
assertEquals(i, completions.size());
}

View File

@@ -14,6 +14,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -42,8 +43,22 @@ public abstract class AbstractPropsEditorTest {
public static final String STRING = String.class.getName();
private ProjectsHarness projects = ProjectsHarness.INSTANCE;
protected PropertyIndexHarness md;
protected final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject();
protected final JavaProjectFinder javaProjectFinder = new JavaProjectFinder() {
@Override
public boolean isProjectRoot(File file) {
return false;
}
@Override
public IJavaProject find(File file) {
return null;
}
@Override
public IJavaProject find(IDocument doc) {
return getTestProject();
}
};
private LanguageServerHarness harness;
private IJavaProject testProject;

View File

@@ -11,14 +11,10 @@
package org.springframework.ide.vscode.commons.gradle;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import org.springframework.ide.vscode.commons.languageserver.java.IJavaProjectFinderStrategy;
import org.springframework.ide.vscode.commons.util.FileUtils;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -40,23 +36,19 @@ public class GradleProjectFinderStrategy implements IJavaProjectFinderStrategy {
}
@Override
public GradleJavaProject find(IDocument d) throws ExecutionException, URISyntaxException {
String uriStr = d.getUri();
if (StringUtil.hasText(uriStr)) {
URI uri = new URI(uriStr);
// TODO: This only work with File uri. Should it work with others
// too?
if (uri.getScheme().equalsIgnoreCase("file")) {
File file = new File(uri).getAbsoluteFile();
File gradlebuild = FileUtils.findFile(file, GradleCore.GRADLE_BUILD_FILE);
if (gradlebuild != null) {
return cache.get(gradlebuild.getParentFile(), () -> {
return new GradleJavaProject(gradle, gradlebuild.getParentFile());
});
}
}
public GradleJavaProject find(File file) throws ExecutionException {
File gradlebuild = FileUtils.findFile(file, GradleCore.GRADLE_BUILD_FILE);
if (gradlebuild != null) {
return cache.get(gradlebuild.getParentFile(), () -> {
return new GradleJavaProject(gradle, gradlebuild.getParentFile());
});
}
return null;
}
@Override
public boolean isProjectRoot(File file) {
return FileUtils.findFile(file, GradleCore.GRADLE_BUILD_FILE, false) != null;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Pivotal, Inc.
* Copyright (c) 2016, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -8,11 +8,15 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.java;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
public class DefaultJavaProjectFinder implements JavaProjectFinder {
@@ -24,10 +28,30 @@ public class DefaultJavaProjectFinder implements JavaProjectFinder {
}
@Override
public IJavaProject find(IDocument d) {
public IJavaProject find(IDocument doc) {
try {
String uriStr = doc.getUri();
if (StringUtil.hasText(uriStr)) {
URI uri = new URI(uriStr);
// TODO: This only work with File uri. Should it work with others
// too?
if (uri.getScheme().equalsIgnoreCase("file")) {
File file = new File(uri).getAbsoluteFile();
return find(file);
}
}
}
catch (URISyntaxException e) {
Log.log(e);
}
return null;
}
@Override
public IJavaProject find(File file) {
for (IJavaProjectFinderStrategy strategy : strategies) {
try {
IJavaProject project = strategy.find(d);
IJavaProject project = strategy.find(file);
if (project != null) {
return project;
}
@@ -37,5 +61,19 @@ public class DefaultJavaProjectFinder implements JavaProjectFinder {
}
return null;
}
@Override
public boolean isProjectRoot(File file) {
for (IJavaProjectFinderStrategy strategy : strategies) {
try {
if (strategy.isProjectRoot(file)) {
return true;
}
} catch (Exception e) {
Log.log(e);
}
}
return false;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* Copyright (c) 2016, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -10,18 +10,18 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.java;
import java.io.File;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.IDocument;
/**
* Strategy foe finding Java project for a document
*
* @author Alex Boyko
*
*/
@FunctionalInterface
public interface IJavaProjectFinderStrategy {
IJavaProject find(IDocument document) throws Exception;
IJavaProject find(File file) throws Exception;
boolean isProjectRoot(File file);
}

View File

@@ -8,13 +8,17 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.java;
import java.io.File;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.IDocument;
@FunctionalInterface
public interface JavaProjectFinder {
IJavaProject find(IDocument doc);
IJavaProject find(File file);
boolean isProjectRoot(File file);
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* Copyright (c) 2016, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,15 +11,11 @@
package org.springframework.ide.vscode.commons.maven;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import org.springframework.ide.vscode.commons.languageserver.java.IJavaProjectFinderStrategy;
import org.springframework.ide.vscode.commons.maven.java.classpathfile.JavaProjectWithClasspathFile;
import org.springframework.ide.vscode.commons.util.FileUtils;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -29,32 +25,19 @@ public class JavaProjectWithClasspathFileFinderStrategy implements IJavaProjectF
public Cache<File, JavaProjectWithClasspathFile> cache = CacheBuilder.newBuilder().build();
@Override
public JavaProjectWithClasspathFile find(IDocument d) throws ExecutionException, URISyntaxException {
String uriStr = d.getUri();
if (StringUtil.hasText(uriStr)) {
URI uri = new URI(uriStr);
// TODO: This only work with File uri. Should it work with others
// too?
if (uri.getScheme().equalsIgnoreCase("file")) {
File file = toFile(uri);
File cpFile = FileUtils.findFile(file, MavenCore.CLASSPATH_TXT);
if (cpFile != null) {
return cache.get(cpFile, () -> {
return new JavaProjectWithClasspathFile(cpFile);
});
}
}
public JavaProjectWithClasspathFile find(File file) throws ExecutionException {
File cpFile = FileUtils.findFile(file, MavenCore.CLASSPATH_TXT);
if (cpFile != null) {
return cache.get(cpFile, () -> {
return new JavaProjectWithClasspathFile(cpFile);
});
}
return null;
}
protected File toFile(URI uri) {
// try {
return new File(uri).getAbsoluteFile();
// } catch (Exception e) {
// Log.log("Ignored Uri '"+uri+"'. Not a file?", e);
// return null;
// }
@Override
public boolean isProjectRoot(File file) {
return FileUtils.findFile(file, MavenCore.CLASSPATH_TXT, false) != null;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* Copyright (c) 2016, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,15 +11,11 @@
package org.springframework.ide.vscode.commons.maven;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import org.springframework.ide.vscode.commons.languageserver.java.IJavaProjectFinderStrategy;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.FileUtils;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -28,7 +24,6 @@ import com.google.common.cache.CacheBuilder;
* Finds Maven Project based
*
* @author Alex Boyko
*
*/
public class MavenProjectFinderStrategy implements IJavaProjectFinderStrategy {
@@ -41,23 +36,19 @@ public class MavenProjectFinderStrategy implements IJavaProjectFinderStrategy {
}
@Override
public MavenJavaProject find(IDocument d) throws ExecutionException, URISyntaxException {
String uriStr = d.getUri();
if (StringUtil.hasText(uriStr)) {
URI uri = new URI(uriStr);
// TODO: This only work with File uri. Should it work with others
// too?
if (uri.getScheme().equalsIgnoreCase("file")) {
File file = new File(uri).getAbsoluteFile();
File pomFile = FileUtils.findFile(file, MavenCore.POM_XML);
if (pomFile != null) {
return cache.get(pomFile, () -> {
return new MavenJavaProject(maven, pomFile);
});
}
}
public MavenJavaProject find(File file) throws ExecutionException {
File pomFile = FileUtils.findFile(file, MavenCore.POM_XML);
if (pomFile != null) {
return cache.get(pomFile, () -> {
return new MavenJavaProject(maven, pomFile);
});
}
return null;
}
@Override
public boolean isProjectRoot(File file) {
return FileUtils.findFile(file, MavenCore.POM_XML, false) != null;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* Copyright (c) 2016, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -13,26 +13,30 @@ package org.springframework.ide.vscode.commons.util;
import java.io.File;
/**
* Utilitity methods for working with files
* Utilitity methods for working with files
*
* @authro Kris De Volder
* @author Alex Boyko
*
*/
public class FileUtils {
/**
* Find file given its fil name in the given folder or its parent folders
*
* @param folder Starting folder
* @param fileNameToFind Name of the file to find
* @return Found <code>File</code>
*/
public static File findFile(File folder, String fileNameToFind) {
if (folder!=null && folder.exists()) {
return findFile(folder, fileNameToFind, true);
}
public static File findFile(File folder, String fileNameToFind, boolean recursiveUp) {
if (folder != null && folder.exists()) {
File file = new File(folder, fileNameToFind);
if (file.isFile()) {
return file;
} else {
} else if (recursiveUp) {
return findFile(folder.getParentFile(), fileNameToFind);
}
}