Skip to content

Commit 167bf64

Browse files
committed
EclipseHelper: Support incremental annotation indexing
... which includes removing annotations that are now obsolete... Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent ab0396e commit 167bf64

3 files changed

Lines changed: 82 additions & 4 deletions

File tree

src/main/java/org/scijava/annotations/AbstractIndexWriter.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public interface StreamFactory {
8181
InputStream openInput(String annotationName) throws IOException;
8282

8383
OutputStream openOutput(String annotationName) throws IOException;
84+
85+
boolean isClassObsolete(String className);
8486
}
8587

8688
protected synchronized void write(final StreamFactory factory)
@@ -99,6 +101,19 @@ protected synchronized void write(final StreamFactory factory)
99101
map.clear();
100102
}
101103

104+
/**
105+
* Merges an existing annotation index into the currently-generated one.
106+
* <p>
107+
* This method is used to read previously-indexed annotations and reconcile
108+
* them with the newly-generated ones just.
109+
* </p>
110+
*
111+
* @param annotationName the name of the annotation for which the index
112+
* contains the annotated classes
113+
* @param factory the factory to generate input and output streams given an
114+
* annotation name
115+
* @throws IOException
116+
*/
102117
protected synchronized void merge(final String annotationName,
103118
final StreamFactory factory) throws IOException
104119
{
@@ -111,6 +126,12 @@ protected synchronized void merge(final String annotationName,
111126
map = new LinkedHashMap<String, Object>();
112127
this.map.put(annotationName, map);
113128
}
129+
/*
130+
* To determine whether the index needs to be written out,
131+
* we need to keep track of changed entries.
132+
*/
133+
int changedCount = map.size();
134+
boolean hasObsoletes = false;
114135

115136
final IndexReader reader = new IndexReader(in);
116137
try {
@@ -121,14 +142,26 @@ protected synchronized void merge(final String annotationName,
121142
break;
122143
}
123144
final String className = (String) entry.get("class");
124-
if (!map.containsKey(className)) {
145+
if (factory.isClassObsolete(className)) {
146+
hasObsoletes = true;
147+
}
148+
else if (map.containsKey(className)) {
149+
if (!hasObsoletes && entry.equals(map.get(className))) {
150+
changedCount--;
151+
}
152+
}
153+
else {
125154
map.put(className, entry);
126155
}
127156
}
128157
}
129158
finally {
130159
reader.close();
131160
}
161+
// if this annotation index is unchanged, no need to write it out again
162+
if (changedCount == 0 && !hasObsoletes) {
163+
this.map.remove(annotationName);
164+
}
132165
}
133166

134167
protected Object adapt(final Object o) {

src/main/java/org/scijava/annotations/DirectoryIndexer.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private static <A extends Annotation> boolean isIndexable(final A annotation)
115115
}
116116

117117
protected synchronized void write(final File directory) throws IOException {
118-
write(new StreamFactory() {
118+
final StreamFactory factory = new StreamFactory() {
119119

120120
public InputStream openInput(String annotationName) throws IOException {
121121
final File file =
@@ -137,8 +137,34 @@ public OutputStream openOutput(String annotationName) throws IOException {
137137
{
138138
throw new IOException("Could not make directory " + directory);
139139
}
140-
return new FileOutputStream(file);
140+
return new FileOutputStream(file) {
141+
142+
@Override
143+
public void close() throws IOException {
144+
super.close();
145+
if (file.length() == 0) {
146+
file.delete();
147+
}
148+
}
149+
};
150+
}
151+
152+
public boolean isClassObsolete(String className) {
153+
final String classPath = className.replace('.', '/') + ".class";
154+
return !new File(directory, classPath).exists();
141155
}
142-
});
156+
};
157+
158+
final File[] possiblyObsoletes =
159+
new File(directory, Index.INDEX_PREFIX).listFiles();
160+
if (possiblyObsoletes != null) {
161+
for (final File candidate : possiblyObsoletes) {
162+
if (candidate.isFile()) {
163+
final String annotationName = candidate.getName();
164+
merge(annotationName, factory);
165+
}
166+
}
167+
}
168+
write(factory);
143169
}
144170
}

src/test/java/org/scijava/annotations/EclipseHelperTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,25 @@ public Class<?> loadClass(final String className)
8585
assertTrue(new File(jsonDir, clazz.getName()).exists());
8686
}
8787
assertEquals(2, jsonDir.list().length);
88+
89+
// delete the .class files and verify that the annotation indexes are
90+
// deleted
91+
jsonDir.setLastModified(123456789);
92+
for (final Class<?> clazz : new Class<?>[] { AnnotatedA.class,
93+
AnnotatedB.class, AnnotatedC.class })
94+
{
95+
assertTrue(new File(dir, DirectoryIndexerTest.getResourcePath(clazz))
96+
.delete());
97+
}
98+
long now = System.currentTimeMillis();
99+
EclipseHelper.updateAnnotationIndex(loader);
100+
assertEquals(0, jsonDir.list().length);
101+
/*
102+
* Most file systems provide the mtime at second granularity, not
103+
* milli-second granularity. Hence "now" might be as much as 999
104+
* milliseconds ahead of the stored mtime.
105+
*/
106+
assertTrue(jsonDir.lastModified() >= now - 999);
88107
}
89108

90109
private void copyClasses(final File dir, final Class<?>... classes)

0 commit comments

Comments
 (0)