Added
Link Here
|
1 |
/* |
2 |
* Sun Public License Notice |
3 |
* |
4 |
* The contents of this file are subject to the Sun Public License |
5 |
* Version 1.0 (the "License"). You may not use this file except in |
6 |
* compliance with the License. A copy of the License is available at |
7 |
* http://www.sun.com/ |
8 |
* |
9 |
* The Original Code is NetBeans. The Initial Developer of the Original |
10 |
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun |
11 |
* Microsystems, Inc. All Rights Reserved. |
12 |
*/ |
13 |
|
14 |
package org.netbeans.core; |
15 |
|
16 |
import java.io.*; |
17 |
import java.net.MalformedURLException; |
18 |
import java.net.URL; |
19 |
import java.util.*; |
20 |
|
21 |
import org.openide.ErrorManager; |
22 |
import org.openide.filesystems.FileObject; |
23 |
import org.openide.filesystems.Repository; |
24 |
import org.openide.loaders.DataFolder; |
25 |
import org.openide.loaders.FolderLookup; |
26 |
import org.openide.util.Lookup; |
27 |
import org.openide.util.Utilities; |
28 |
import org.openide.util.io.NbObjectInputStream; |
29 |
import org.openide.util.io.NbObjectOutputStream; |
30 |
|
31 |
import org.netbeans.core.modules.Module; |
32 |
import org.netbeans.core.modules.ModuleManager; |
33 |
import org.netbeans.core.perftool.StartLog; |
34 |
|
35 |
/** |
36 |
* Responsible for persisting the structure of folder lookup. |
37 |
* <p>A cache is kept in serialized form in $userdir/cache/folder-lookup.ser. |
38 |
* Unless the cache is invalidated due to changes in module JARs or |
39 |
* files in $userdir/system/**, it is restored after a regular startup. |
40 |
* The actual objects in lookup are not serialized - only their classes, |
41 |
* instanceof information, position in the Services folder, and so on. This |
42 |
* permits us to avoid calling the XML parser for every .settings object etc. |
43 |
* Other forms of lookup like META-INF/services/* are not persisted. |
44 |
* <p>Can be enabled or disabled with the system property netbeans.cache.lookup. |
45 |
* @author Jesse Glick, Jaroslav Tulach |
46 |
* @see "#20190" |
47 |
*/ |
48 |
class LookupCache { |
49 |
|
50 |
/** whether to enable the cache for this session */ |
51 |
private static final boolean ENABLED = Boolean.valueOf(System.getProperty("netbeans.cache.lookup", "true")).booleanValue(); // NOI18N |
52 |
|
53 |
/** private logging for this class */ |
54 |
private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.netbeans.core.LookupCache"); // NOI18N |
55 |
|
56 |
/** |
57 |
* Get the Services/ folder lookup. |
58 |
* May either do it the slow way, or might load quickly from a cache. |
59 |
* @return the folder lookup for the system |
60 |
*/ |
61 |
public static Lookup load() { |
62 |
err.log("enabled=" + ENABLED); |
63 |
if (ENABLED && cacheHit()) { |
64 |
try { |
65 |
return loadCache(); |
66 |
} catch (Exception e) { |
67 |
err.notify(ErrorManager.INFORMATIONAL, e); |
68 |
} |
69 |
} |
70 |
return loadDirect(); |
71 |
} |
72 |
|
73 |
/** |
74 |
* Load folder lookup directly from the system file system, parsing |
75 |
* as necessary (the slow way). |
76 |
*/ |
77 |
private static Lookup loadDirect() { |
78 |
FileObject services = Repository.getDefault().getDefaultFileSystem().findResource("Services"); // NOI18N |
79 |
if (services != null) { |
80 |
StartLog.logProgress("Got Services folder"); // NOI18N |
81 |
FolderLookup f = new FolderLookup(DataFolder.findFolder(services), "SL["); // NOI18N |
82 |
StartLog.logProgress("created FolderLookup"); // NOI18N |
83 |
err.log("loadDirect from Services"); |
84 |
return f.getLookup(); |
85 |
} else { |
86 |
err.log("loadDirect, but no Services"); |
87 |
return Lookup.EMPTY; |
88 |
} |
89 |
} |
90 |
|
91 |
/** |
92 |
* Determine if there is an existing lookup cache which can be used |
93 |
* now as is. |
94 |
* If there is a cache and a stamp file, and the stamp agrees with |
95 |
* a calculation of the files and timestamps currently available to |
96 |
* constitute the folder lookup, then the cache is used. |
97 |
*/ |
98 |
private static boolean cacheHit() { |
99 |
File f = cacheFile(); |
100 |
if (f == null || !f.exists()) { |
101 |
err.log("no cache file"); |
102 |
return false; |
103 |
} |
104 |
File stampFile = stampFile(); |
105 |
if (stampFile == null || !stampFile.exists()) { |
106 |
err.log("no stamp file"); |
107 |
return false; |
108 |
} |
109 |
StartLog.logStart("check for lookup cache hit"); // NOI18N |
110 |
List files = relevantFiles(); // List<File> |
111 |
if (err.isLoggable(ErrorManager.INFORMATIONAL)) { |
112 |
err.log("checking against " + stampFile + " for files " + files); |
113 |
} |
114 |
boolean hit; |
115 |
try { |
116 |
Stamp stamp = new Stamp(files); |
117 |
long newHash = stamp.getHash(); |
118 |
BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(stampFile), "UTF-8")); // NOI18N |
119 |
try { |
120 |
String line = r.readLine(); |
121 |
long oldHash; |
122 |
try { |
123 |
oldHash = Long.parseLong(line); |
124 |
} catch (NumberFormatException nfe) { |
125 |
throw new IOException(nfe.toString()); |
126 |
} |
127 |
if (oldHash == newHash) { |
128 |
err.log("Cache hit! with hash " + oldHash); |
129 |
hit = true; |
130 |
} else { |
131 |
err.log("Cache miss, " + oldHash + " -> " + newHash); |
132 |
hit = false; |
133 |
} |
134 |
} finally { |
135 |
r.close(); |
136 |
} |
137 |
} catch (IOException ioe) { |
138 |
err.notify(ErrorManager.INFORMATIONAL, ioe); |
139 |
hit = false; |
140 |
} |
141 |
StartLog.logEnd("check for lookup cache hit"); // NOI18N |
142 |
return hit; |
143 |
} |
144 |
|
145 |
/** |
146 |
* The file containing the serialized lookup cache. |
147 |
*/ |
148 |
private static File cacheFile() { |
149 |
String ud = System.getProperty("netbeans.user"); |
150 |
if (ud != null) { |
151 |
File cachedir = new File(ud, "cache"); // NOI18N |
152 |
cachedir.mkdirs(); |
153 |
return new File(cachedir, "folder-lookup.ser"); // NOI18N |
154 |
} else { |
155 |
return null; |
156 |
} |
157 |
} |
158 |
|
159 |
/** |
160 |
* The file containing a stamp which indicates which modules were |
161 |
* enabled, what versions of them, customized services, etc. |
162 |
*/ |
163 |
private static File stampFile() { |
164 |
String ud = System.getProperty("netbeans.user"); |
165 |
if (ud != null) { |
166 |
File cachedir = new File(ud, "cache"); // NOI18N |
167 |
cachedir.mkdirs(); |
168 |
return new File(cachedir, "lookup-stamp.txt"); // NOI18N |
169 |
} else { |
170 |
return null; |
171 |
} |
172 |
} |
173 |
|
174 |
/** |
175 |
* List of all files which might be relevant to the contents of folder lookup. |
176 |
* This means: all JAR files which are modules (skip their extensions and |
177 |
* variants which can be assumed not to contain layer files); and all files |
178 |
* contained in the system/Services/ subdirs (if any) of the home dir, |
179 |
* user dir, and extra installation directories (#27151). |
180 |
* For test modules, use the original JAR, not the physical JAR, |
181 |
* to prevent cache misses on every restart. |
182 |
* For fixed modules with layers (e.g. core.jar), add in the matching JAR, |
183 |
* if that can be ascertained. |
184 |
* No particular order of returned files is assumed. |
185 |
*/ |
186 |
private static List relevantFiles() { |
187 |
final List files = new ArrayList(250); // List<File> |
188 |
final ModuleManager mgr = NbTopManager.get().getModuleSystem().getManager(); |
189 |
mgr.mutex().readAccess(new Runnable() { |
190 |
public void run() { |
191 |
Iterator it = mgr.getEnabledModules().iterator(); |
192 |
while (it.hasNext()) { |
193 |
Module m = (Module)it.next(); |
194 |
String layer = (String)m.getAttribute("OpenIDE-Module-Layer"); // NOI18N |
195 |
if (layer != null) { |
196 |
if (!m.isFixed()) { |
197 |
files.add(m.getJarFile()); |
198 |
} else { |
199 |
URL layerURL = m.getClassLoader().getResource(layer); |
200 |
if (layerURL != null) { |
201 |
String s = layerURL.toExternalForm(); |
202 |
if (s.startsWith("jar:")) { // NOI18N |
203 |
int bangSlash = s.lastIndexOf("!/"); // NOI18N |
204 |
if (bangSlash != -1) { |
205 |
// underlying URL inside jar:, generally file: |
206 |
try { |
207 |
URL layerJarURL = new URL(s.substring(4, bangSlash)); |
208 |
File layerJar = Utilities.toFile(layerJarURL); |
209 |
if (layerJar != null) { |
210 |
files.add(layerJar); |
211 |
} else { |
212 |
err.log(ErrorManager.WARNING, "Weird jar: URL: " + layerJarURL); |
213 |
} |
214 |
} catch (MalformedURLException mfue) { |
215 |
err.notify(ErrorManager.INFORMATIONAL, mfue); |
216 |
} |
217 |
} else { |
218 |
err.log(ErrorManager.WARNING, "Malformed jar: URL: " + s); |
219 |
} |
220 |
} else { |
221 |
err.log(ErrorManager.WARNING, "Not a jar: URL: " + s); |
222 |
} |
223 |
} else { |
224 |
err.log(ErrorManager.WARNING, "Could not find " + layer + " in " + m); |
225 |
} |
226 |
} |
227 |
} |
228 |
// else no layer, ignore |
229 |
} |
230 |
} |
231 |
}); |
232 |
relevantFilesFromInst(files, System.getProperty("netbeans.home")); // NOI18N |
233 |
relevantFilesFromInst(files, System.getProperty("netbeans.user")); // NOI18N |
234 |
String nbdirs = System.getProperty("netbeans.dirs"); // NOI18N |
235 |
if (nbdirs != null) { |
236 |
// #27151 |
237 |
StringTokenizer tok = new StringTokenizer(nbdirs, File.pathSeparator); |
238 |
while (tok.hasMoreTokens()) { |
239 |
relevantFilesFromInst(files, tok.nextToken()); |
240 |
} |
241 |
} |
242 |
return files; |
243 |
} |
244 |
/** |
245 |
* Find relevant files from an installation directory. |
246 |
*/ |
247 |
private static void relevantFilesFromInst(List files, String instDir) { |
248 |
if (instDir == null) { |
249 |
return; |
250 |
} |
251 |
relevantFilesFrom(files, new File(new File(new File(instDir), "system"), "Services")); // NOI18N |
252 |
} |
253 |
/** |
254 |
* Retrieve all files in a directory, recursively. |
255 |
*/ |
256 |
private static void relevantFilesFrom(List files, File dir) { |
257 |
File[] kids = dir.listFiles(); |
258 |
if (kids != null) { |
259 |
for (int i = 0; i < kids.length; i++) { |
260 |
File f = kids[i]; |
261 |
if (f.isFile()) { |
262 |
files.add(f); |
263 |
} else { |
264 |
relevantFilesFrom(files, f); |
265 |
} |
266 |
} |
267 |
} |
268 |
} |
269 |
|
270 |
/** |
271 |
* Load folder lookup from the disk cache. |
272 |
*/ |
273 |
private static Lookup loadCache() throws Exception { |
274 |
StartLog.logStart("load lookup cache"); |
275 |
File f = cacheFile(); |
276 |
err.log("loading from " + f); |
277 |
InputStream is = new FileInputStream(f); |
278 |
try { |
279 |
ObjectInputStream ois = new NbObjectInputStream(new BufferedInputStream(is)); |
280 |
Lookup l = (Lookup)ois.readObject(); |
281 |
StartLog.logEnd("load lookup cache"); |
282 |
return l; |
283 |
} finally { |
284 |
is.close(); |
285 |
} |
286 |
} |
287 |
|
288 |
/** |
289 |
* Store the current contents of folder lookup to disk, hopefully to be used |
290 |
* in the next session to speed startup. |
291 |
* @param l the folder lookup |
292 |
* @throws IOException if it could not be saved |
293 |
*/ |
294 |
public static void store(Lookup l) throws IOException { |
295 |
if (!ENABLED) { |
296 |
return; |
297 |
} |
298 |
File f = cacheFile(); |
299 |
if (f == null) { |
300 |
return; |
301 |
} |
302 |
File stampFile = stampFile(); |
303 |
if (stampFile == null) { |
304 |
return; |
305 |
} |
306 |
StartLog.logStart("store lookup cache"); |
307 |
err.log("storing to " + f + " with stamp in " + stampFile); |
308 |
OutputStream os = new FileOutputStream(f); |
309 |
try { |
310 |
try { |
311 |
ObjectOutputStream oos = new NbObjectOutputStream(new BufferedOutputStream(os)); |
312 |
oos.writeObject(l); |
313 |
oos.flush(); |
314 |
} finally { |
315 |
os.close(); |
316 |
} |
317 |
Stamp stamp = new Stamp(relevantFiles()); |
318 |
Writer wr = new OutputStreamWriter(new FileOutputStream(stampFile), "UTF-8"); // NOI18N |
319 |
try { |
320 |
// Would be nice to write out as zero-padded hex. |
321 |
// Unfortunately while Long.toHexString works fine, |
322 |
// Long.parseLong cannot be asked to parse unsigned longs, |
323 |
// so fails when the high bit is set. |
324 |
wr.write(Long.toString(stamp.getHash())); |
325 |
wr.write("\nLine above is identifying hash key, do not edit!\nBelow is metadata about folder lookup cache, for debugging purposes.\n"); // NOI18N |
326 |
wr.write(stamp.toString()); |
327 |
} finally { |
328 |
wr.close(); |
329 |
} |
330 |
StartLog.logEnd("store lookup cache"); |
331 |
} catch (IOException ioe) { |
332 |
// Delete corrupted cache. |
333 |
if (f.exists()) { |
334 |
f.delete(); |
335 |
} |
336 |
if (stampFile.exists()) { |
337 |
stampFile.delete(); |
338 |
} |
339 |
throw ioe; |
340 |
} |
341 |
} |
342 |
|
343 |
/** |
344 |
* Represents a hash of a bunch of JAR or other files and their timestamps. |
345 |
* Compare ModuleLayeredFileSystem's similar nested class. |
346 |
* .settings files do not get their timestamps checked because generally |
347 |
* changes to them do not reflect changes in the structure of lookup, only |
348 |
* in the contents of one lookup instance. Otherwise autoupdate's settings |
349 |
* alone would trigger a cache miss every time. Generally, all files other |
350 |
* than JARs and .nbattrs (which can affect folder order) should not affect |
351 |
* lookup structure by their contents, except in the pathological case which |
352 |
* we do not consider that they supply zero instances or a recursive lookup |
353 |
* (which even then would only lead to problems if such a file were changed |
354 |
* on disk between IDE sessions, which can be expected to be very rare). |
355 |
*/ |
356 |
private static final class Stamp { |
357 |
private final List files; // List<File> |
358 |
private final long[] times; |
359 |
private final long hash; |
360 |
/** Create a stamp from a list of files. */ |
361 |
public Stamp(List files) throws IOException { |
362 |
this.files = new ArrayList(files); |
363 |
Collections.sort(this.files); |
364 |
times = new long[this.files.size()]; |
365 |
long x = 17L; |
366 |
Iterator it = this.files.iterator(); |
367 |
int i = 0; |
368 |
while (it.hasNext()) { |
369 |
File f = (File)it.next(); |
370 |
x ^= f.hashCode(); |
371 |
x += 98679245L; |
372 |
long m; |
373 |
String name = f.getName().toLowerCase(Locale.US); |
374 |
if (name.endsWith(".jar") || name.equals(".nbattrs")) { // NOI18N |
375 |
m = f.lastModified(); |
376 |
} else { |
377 |
m = 0L; |
378 |
} |
379 |
x ^= (times[i++] = m); |
380 |
} |
381 |
hash = x; |
382 |
} |
383 |
/** Hash of the stamp for comparison purposes. */ |
384 |
public long getHash() { |
385 |
return hash; |
386 |
} |
387 |
/** Debugging information listing which files were used. */ |
388 |
public String toString() { |
389 |
StringBuffer buf = new StringBuffer(); |
390 |
Iterator it = files.iterator(); |
391 |
int i = 0; |
392 |
while (it.hasNext()) { |
393 |
long t = times[i++]; |
394 |
if (t != 0L) { |
395 |
buf.append(new Date(t)); |
396 |
} else { |
397 |
buf.append("<ignoring file contents>"); // NOI18N |
398 |
} |
399 |
buf.append('\t'); |
400 |
buf.append(it.next()); |
401 |
buf.append('\n'); |
402 |
} |
403 |
return buf.toString(); |
404 |
} |
405 |
} |
406 |
|
407 |
} |