Fixed: NPE when namespace is used instead of defining package name in AndroidManifest

A few more optimizations
This commit is contained in:
Akash Yadav
2022-07-15 14:38:54 +05:30
parent 26bd8a9622
commit f2f5be0495
12 changed files with 267 additions and 156 deletions
+1
View File
@@ -43,6 +43,7 @@
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
<arrangement>
<groups>
Binary file not shown.
@@ -75,7 +75,7 @@ object FileManager : EventReceiver {
fun getReader(file: Path): BufferedReader {
val document = getActiveDocument(file)
if (document != null) {
return document.content.reader().buffered()
return document.reader()
}
return createFileReader(file)
@@ -84,7 +84,7 @@ object FileManager : EventReceiver {
fun getInputStream(file: Path): InputStream {
val document = getActiveDocument(file)
if (document != null) {
return document.content.byteInputStream().buffered()
return document.inputStream()
}
return createFileInputStream(file)
@@ -19,12 +19,12 @@ package com.itsaky.androidide.projects.api
import android.text.TextUtils
import com.itsaky.androidide.tooling.api.model.GradleTask
import com.itsaky.androidide.utils.ClassTrie
import com.itsaky.androidide.utils.BootClasspathProvider
import com.itsaky.androidide.utils.ClasspathReader
import com.itsaky.androidide.utils.DocumentUtils
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.SourceClassTrie
import com.itsaky.androidide.utils.SourceClassTrie.SourceNode
import com.itsaky.androidide.utils.StopWatch
import java.io.File
import java.nio.file.Path
@@ -97,6 +97,7 @@ abstract class ModuleProject(
fun indexSourcesAndClasspaths() {
log.info("Indexing sources and classpaths for project:", path)
var watch = StopWatch("Indexing sources")
var count = 0
getCompileSourceDirectories().forEach {
val sourceDir = it.toPath()
@@ -110,12 +111,20 @@ abstract class ModuleProject(
}
}
log.debug("Sources indexed for project: '$path'. Found $count source files.")
watch.log()
log.debug("Found $count source files.")
watch = StopWatch("Indexing classpaths")
val paths = getCompileClasspaths().filter { it.exists() }
val topLevelClasses = com.itsaky.androidide.utils.ClasspathReader.listClasses(paths).filter { it.isTopLevel }
val topLevelClasses = ClasspathReader.listClasses(paths).filter { it.isTopLevel }
topLevelClasses.forEach { this.compileClasspathClasses.append(it.name) }
log.debug("Classpaths indexed for project: '$path'. Found ${topLevelClasses.size} classpaths.")
watch.log()
log.debug("Found ${topLevelClasses.size} classpaths.")
if (this is AndroidModule) {
BootClasspathProvider.update(bootClassPaths.map { it.path })
}
}
fun getSourceFilesInDir(dir: Path): List<SourceNode> =
@@ -18,6 +18,9 @@
package com.itsaky.androidide.projects.models
import com.itsaky.androidide.models.Range
import com.itsaky.androidide.utils.DocumentUtils
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.nio.file.Path
import java.time.Instant
@@ -33,4 +36,32 @@ open class ActiveDocument(
var changDelta: Int,
val version: Int,
val modified: Instant
)
) {
companion object {
@JvmStatic
fun create(
file: Path,
content: String,
changeRange: Range,
changDelta: Int,
version: Int,
modified: Instant
): ActiveDocument {
if (DocumentUtils.isJavaFile(file)) {
return ActiveJavaDocument(file, content, changeRange, changDelta, version, modified)
}
return ActiveDocument(file, content, changeRange, changDelta, version, modified)
}
}
fun inputStream(): BufferedInputStream {
return content.byteInputStream().buffered()
}
fun reader(): BufferedReader {
return content.reader().buffered()
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.projects.models
import com.itsaky.androidide.models.Range
import com.itsaky.androidide.projects.util.StringSearch
import java.nio.file.Path
import java.time.Instant
/**
* An active java document.
*
* @author Akash Yadav
*/
class ActiveJavaDocument(
file: Path,
content: String,
changeRange: Range,
changDelta: Int,
version: Int,
modified: Instant
) : ActiveDocument(file, content, changeRange, changDelta, version, modified) {
val packageName: String = StringSearch.packageName(reader())
}
@@ -17,8 +17,10 @@
package com.itsaky.androidide.projects.util;
import androidx.annotation.Nullable;
import com.itsaky.androidide.projects.FileManager;
import com.itsaky.androidide.projects.models.ActiveDocument;
import com.itsaky.androidide.projects.models.ActiveJavaDocument;
import com.itsaky.androidide.utils.Cache;
import java.io.BufferedReader;
@@ -33,7 +35,6 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// Translated from https://golang.org/src/strings/search.go
@@ -44,9 +45,12 @@ import java.util.regex.Pattern;
// https://www.cs.utexas.edu/~moore/publications/fstrpos.pdf (note: this aged
// document uses 1-based indexing)
public class StringSearch {
private static final ByteBuffer SEARCH_BUFFER = ByteBuffer.allocateDirect(1024 * 1024);
private static final Logger LOG = Logger.getLogger("main");
private static final Cache<String, Boolean> cacheContainsClass = new Cache<>();
private static final Cache<String, Boolean> cacheContainsInterface = new Cache<>();
// pattern is the string that we are searching for in the text.
private final byte[] pattern;
// badCharSkip[b] contains the distance between the last byte of pattern
// and the rightmost occurrence of b in pattern. If b is not in pattern,
// badCharSkip[b] is len(pattern).
@@ -56,7 +60,6 @@ public class StringSearch {
// the matching char could be in alignment.
// TODO 256 is not coloring
private final int[] badCharSkip = new int[256];
// goodSuffixSkip[i] defines how far we can shift the matching frame given
// that the suffix pattern[i+1:] matches, but the byte pattern[i] does
// not. There are two cases to consider:
@@ -115,72 +118,6 @@ public class StringSearch {
}
}
public int next(String text) {
return next(text.getBytes());
}
private int next(byte[] text) {
return next(ByteBuffer.wrap(text));
}
private int next(ByteBuffer text) {
return next(text, 0);
}
private int next(ByteBuffer text, int startingAfter) {
int i = startingAfter + pattern.length - 1;
while (i < text.limit()) {
// Compare backwards from the end until the first unmatching character.
int j = pattern.length - 1;
while (j >= 0 && text.get(i) == pattern[j]) {
i--;
j--;
}
if (j < 0) {
return i + 1; // match
}
i += Math.max(badCharSkip[text.get(i) + 128], goodSuffixSkip[j]);
}
return -1;
}
private boolean isWordChar(byte b) {
char c = (char) (b + 128);
return Character.isAlphabetic(c) || Character.isDigit(c) || c == '$' || c == '_';
}
private boolean startsWord(ByteBuffer text, int offset) {
if (offset == 0) return true;
return !isWordChar(text.get(offset - 1));
}
private boolean endsWord(ByteBuffer text, int offset) {
if (offset + 1 >= text.limit()) return true;
return !isWordChar(text.get(offset + 1));
}
private boolean isWord(ByteBuffer text, int offset) {
return startsWord(text, offset) && endsWord(text, offset + pattern.length - 1);
}
public int nextWord(String text) {
return nextWord(text.getBytes());
}
private int nextWord(byte[] text) {
return nextWord(ByteBuffer.wrap(text));
}
private int nextWord(ByteBuffer text) {
int i = 0;
while (true) {
i = next(text, i);
if (i == -1) return -1;
if (isWord(text, i)) return i;
i++;
}
}
private boolean hasPrefix(byte[] s, Slice prefix) {
for (int i = 0; i < prefix.length(); i++) {
if (s[i] != prefix.get(i)) return false;
@@ -198,17 +135,6 @@ public class StringSearch {
return i;
}
private static final ByteBuffer SEARCH_BUFFER = ByteBuffer.allocateDirect(1024 * 1024);
private static String tryGetActiveDocContent(Path file) {
final ActiveDocument document = FileManager.INSTANCE.getActiveDocument(file);
if (document != null) {
return document.getContent();
}
return null;
}
// TODO cache the progress made by searching shorter queries
public static boolean containsWordMatching(Path java, String query) {
String text = tryGetActiveDocContent(java);
@@ -232,48 +158,14 @@ public class StringSearch {
}
}
public static boolean containsWord(Path java, String query) {
StringSearch search = new StringSearch(query);
String text = tryGetActiveDocContent(java);
if (text != null) {
return search.nextWord(text.getBytes()) != -1;
@Nullable
private static String tryGetActiveDocContent(Path file) {
final var doc = FileManager.INSTANCE.getActiveDocument(file);
if (doc != null) {
return doc.getContent();
}
try (FileChannel channel = FileChannel.open(java)) {
// Read up to 1 MB of data from file
int limit = Math.min((int) channel.size(), SEARCH_BUFFER.capacity());
SEARCH_BUFFER.position(0);
SEARCH_BUFFER.limit(limit);
channel.read(SEARCH_BUFFER);
SEARCH_BUFFER.position(0);
return search.nextWord(SEARCH_BUFFER) != -1;
} catch (NoSuchFileException e) {
LOG.warning(e.getMessage());
return false;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static boolean containsString(Path java, String query) {
StringSearch search = new StringSearch(query);
String text = tryGetActiveDocContent(java);
if (text != null) {
return search.next(text.getBytes()) != -1;
}
try (FileChannel channel = FileChannel.open(java)) {
// Read up to 1 MB of data from file
int limit = Math.min((int) channel.size(), SEARCH_BUFFER.capacity());
SEARCH_BUFFER.position(0);
SEARCH_BUFFER.limit(limit);
channel.read(SEARCH_BUFFER);
SEARCH_BUFFER.position(0);
return search.next(SEARCH_BUFFER) != -1;
} catch (NoSuchFileException e) {
LOG.warning(e.getMessage());
return false;
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
/**
@@ -330,7 +222,27 @@ public class StringSearch {
return Character.isAlphabetic(c) || Character.isDigit(c) || c == '_' || c == '$';
}
private static Cache<String, Boolean> cacheContainsClass = new Cache<>();
public static boolean containsWord(Path java, String query) {
StringSearch search = new StringSearch(query);
String text = tryGetActiveDocContent(java);
if (text != null) {
return search.nextWord(text.getBytes()) != -1;
}
try (FileChannel channel = FileChannel.open(java)) {
// Read up to 1 MB of data from file
int limit = Math.min((int) channel.size(), SEARCH_BUFFER.capacity());
SEARCH_BUFFER.position(0);
SEARCH_BUFFER.limit(limit);
channel.read(SEARCH_BUFFER);
SEARCH_BUFFER.position(0);
return search.nextWord(SEARCH_BUFFER) != -1;
} catch (NoSuchFileException e) {
LOG.warning(e.getMessage());
return false;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static boolean containsClass(Path file, String simpleName) {
if (cacheContainsClass.needs(file, simpleName)) {
@@ -340,8 +252,6 @@ public class StringSearch {
return cacheContainsClass.get(file, simpleName);
}
private static Cache<String, Boolean> cacheContainsInterface = new Cache<>();
public static boolean containsInterface(Path file, String simpleName) {
if (cacheContainsInterface.needs(file, simpleName)) {
cacheContainsInterface.load(
@@ -371,12 +281,24 @@ public class StringSearch {
}
public static String packageName(Path file) {
Pattern packagePattern = Pattern.compile("^package +(.*);");
Pattern startOfClass = Pattern.compile("^[\\w ]*class +\\w+");
try (BufferedReader lines = FileManager.INSTANCE.getReader(file)) {
for (String line = lines.readLine(); line != null; line = lines.readLine()) {
if (startOfClass.matcher(line).find()) return "";
Matcher matchPackage = packagePattern.matcher(line);
final var doc = FileManager.INSTANCE.getActiveDocument(file);
if (doc instanceof ActiveJavaDocument) {
return ((ActiveJavaDocument) doc).getPackageName();
}
return packageName(FileManager.INSTANCE.getReader(file));
}
public static String packageName(final BufferedReader reader) {
try (reader) {
final var packagePattern = Pattern.compile("^package +(.*);");
final var startOfClass = Pattern.compile("^[\\w ]*class +\\w+");
for (var line = reader.readLine(); line != null; line = reader.readLine()) {
if (startOfClass.matcher(line).find()) {
return "";
}
final var matchPackage = packagePattern.matcher(line);
if (matchPackage.matches()) {
return matchPackage.group(1);
}
@@ -384,6 +306,7 @@ public class StringSearch {
} catch (IOException e) {
throw new RuntimeException(e);
}
// TODO fall back on parsing file
return "";
}
@@ -410,18 +333,98 @@ public class StringSearch {
return cursor + column;
}
private static boolean containsString(Path java, String query) {
StringSearch search = new StringSearch(query);
String text = tryGetActiveDocContent(java);
if (text != null) {
return search.next(text.getBytes()) != -1;
}
try (FileChannel channel = FileChannel.open(java)) {
// Read up to 1 MB of data from file
int limit = Math.min((int) channel.size(), SEARCH_BUFFER.capacity());
SEARCH_BUFFER.position(0);
SEARCH_BUFFER.limit(limit);
channel.read(SEARCH_BUFFER);
SEARCH_BUFFER.position(0);
return search.next(SEARCH_BUFFER) != -1;
} catch (NoSuchFileException e) {
LOG.warning(e.getMessage());
return false;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public int next(String text) {
return next(text.getBytes());
}
private int next(byte[] text) {
return next(ByteBuffer.wrap(text));
}
private int next(ByteBuffer text) {
return next(text, 0);
}
private int next(ByteBuffer text, int startingAfter) {
int i = startingAfter + pattern.length - 1;
while (i < text.limit()) {
// Compare backwards from the end until the first unmatching character.
int j = pattern.length - 1;
while (j >= 0 && text.get(i) == pattern[j]) {
i--;
j--;
}
if (j < 0) {
return i + 1; // match
}
i += Math.max(badCharSkip[text.get(i) + 128], goodSuffixSkip[j]);
}
return -1;
}
public int nextWord(String text) {
return nextWord(text.getBytes());
}
private int nextWord(byte[] text) {
return nextWord(ByteBuffer.wrap(text));
}
private int nextWord(ByteBuffer text) {
int i = 0;
while (true) {
i = next(text, i);
if (i == -1) return -1;
if (isWord(text, i)) return i;
i++;
}
}
private boolean isWord(ByteBuffer text, int offset) {
return startsWord(text, offset) && endsWord(text, offset + pattern.length - 1);
}
private boolean startsWord(ByteBuffer text, int offset) {
if (offset == 0) return true;
return !isWordChar(text.get(offset - 1));
}
private boolean isWordChar(byte b) {
char c = (char) (b + 128);
return Character.isAlphabetic(c) || Character.isDigit(c) || c == '$' || c == '_';
}
private boolean endsWord(ByteBuffer text, int offset) {
if (offset + 1 >= text.limit()) return true;
return !isWordChar(text.get(offset + 1));
}
private static class Slice {
private final byte[] target;
private int from, until;
int length() {
return until - from;
}
byte get(int i) {
return target[from + i];
}
Slice(byte[] target, int from) {
this(target, from, target.length);
}
@@ -431,7 +434,13 @@ public class StringSearch {
this.from = from;
this.until = until;
}
}
private static final Logger LOG = Logger.getLogger("main");
int length() {
return until - from;
}
byte get(int i) {
return target[from + i];
}
}
}
@@ -141,11 +141,22 @@ public class ProjectReader {
builder.setProjectType(type);
final var module = builder.buildAndroidModule();
tryFillNamespaces(android, module);
addTasks(gradle, module);
return module;
}
private static void tryFillNamespaces(final AndroidProject android, final AndroidModule module) {
try {
module.setNamespace(android.getNamespace());
module.setAndroidTestNamespace(android.getAndroidTestNamespace());
module.setTestFixturesNamespace(android.getTestFixturesNamespace());
} catch (Throwable err) {
log("Unable to get namespaces of module: '" + module.projectPath + "'.", err.getMessage());
}
}
private static void addTasks(GradleProject gradle, IdeGradleProject project) {
for (final var task : gradle.getTasks()) {
project.tasks.add(buildGradleTaskModel(project, task));
@@ -29,11 +29,11 @@ import com.itsaky.androidide.builder.model.DefaultViewBindingOptions
import com.itsaky.androidide.tooling.api.IProject.Type
import com.itsaky.androidide.tooling.api.IProject.Type.Android
import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData
import org.eclipse.lemminx.dom.DOMParser
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager
import java.io.File
import java.io.Serializable
import java.util.concurrent.*
import org.eclipse.lemminx.dom.DOMParser
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager
/**
* Default implementation of [AndroidProject].
@@ -180,6 +180,13 @@ open class AndroidModule(
}
private fun findPackageName() {
if (this.namespace.isNotBlank()) {
this.packageName = namespace
this.shouldLookupPackage = false
return
}
if (mainSourceSet == null) {
shouldLookupPackage = false
return
@@ -197,10 +204,14 @@ open class AndroidModule(
val manifest = document.children.first { it.nodeName == "manifest" }
if (manifest == null) {
shouldLookupPackage = false
return
}
val packageAttr = manifest.attributes.getNamedItem("package")
this.packageName = packageAttr.nodeValue
if (packageAttr != null) {
this.packageName = packageAttr.nodeValue
}
this.shouldLookupPackage = false
}
@@ -95,7 +95,7 @@ public class CompileBatch implements AutoCloseable {
}
void updatePositions(CompilationUnitTree tree, boolean allowDuplicate) {
final StopWatch positionWatch = new StopWatch("Scan method positions");
final StopWatch watch = new StopWatch("Scan method positions");
final List<Pair<Range, TreePath>> positions = new ArrayList<>();
new MethodRangeScanner(this.task).scan(tree, positions);
final String path = new File(tree.getSourceFile().toUri()).getAbsolutePath();
@@ -105,7 +105,7 @@ public class CompileBatch implements AutoCloseable {
"Duplicate CompilationUnitTree for file:" + tree.getSourceFile().toUri());
}
positionWatch.log();
watch.log();
}
private ReusableCompiler.Borrow batchTask(
@@ -44,7 +44,7 @@ public class BootClasspathProvider {
* the cache.
*/
public static synchronized boolean update(Collection<String> classpaths) {
final var start = System.currentTimeMillis();
final var watch = new StopWatch("Indexing " + classpaths.size() + " bootclasspaths");
var count = 0;
for (final var classpath : classpaths) {
if (bootClasspathClasses.containsKey(classpath)) {
@@ -67,8 +67,7 @@ public class BootClasspathProvider {
count += classes.size();
}
LOG.info(
"Indexed", count, "boot classpath classes in", System.currentTimeMillis() - start, "ms");
watch.log();
return count > 0;
}
Binary file not shown.