001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans;
041:
042: import java.io.BufferedOutputStream;
043: import java.io.ByteArrayInputStream;
044: import java.io.DataOutputStream;
045: import java.io.File;
046: import java.io.FileInputStream;
047: import java.io.FileOutputStream;
048: import java.io.IOException;
049: import java.io.InputStream;
050: import java.io.OutputStream;
051: import java.nio.ByteBuffer;
052: import java.nio.ByteBuffer;
053: import java.nio.ByteBuffer;
054: import java.nio.ByteOrder;
055: import java.nio.MappedByteBuffer;
056: import java.nio.channels.FileChannel;
057: import java.util.Collection;
058: import java.util.HashMap;
059: import java.util.HashSet;
060: import java.util.Iterator;
061: import java.util.LinkedList;
062: import java.util.Map;
063: import java.util.Set;
064: import java.util.StringTokenizer;
065: import java.util.concurrent.atomic.AtomicInteger;
066: import java.util.concurrent.atomic.AtomicLong;
067: import java.util.logging.Level;
068: import java.util.logging.Logger;
069: import org.openide.util.Exceptions;
070:
071: /**
072: * Support for optimal checking of time stamps of certain files in
073: * NetBeans directory structure.
074: *
075: * @author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
076: * @since 2.9
077: */
078: public final class Stamps {
079: private static final Logger LOG = Logger.getLogger(Stamps.class
080: .getName());
081: private static AtomicLong moduleJARs;
082:
083: private Worker worker = new Worker();
084:
085: private Stamps() {
086: }
087:
088: /** This class can be executed from command line to perform various checks
089: * on installed NetBeans, however outside of running NetBeans.
090: *
091: */
092: static void main(String... args) {
093: if (args.length == 1 && "reset".equals(args[0])) { // NOI18N
094: moduleJARs = null;
095: stamp(false);
096: return;
097: }
098: }
099:
100: private static final Stamps MODULES_JARS = new Stamps();
101:
102: /** Creates instance of stamp that checks timestamp for all files that affect
103: * module classloading and related caches.
104: */
105: public static Stamps getModulesJARs() {
106: return MODULES_JARS;
107: }
108:
109: /** Finds out the time of last modifications of files that influnce
110: * this cache. Each cached file needs to be "younger".
111: * @return time in ms since epoch
112: */
113: public long lastModified() {
114: return moduleJARs();
115: }
116:
117: /** Checks whether a cache exists
118: *
119: * @param cache name of the cache
120: * @return true if the cache exists and is not out of date
121: */
122: public boolean exists(String cache) {
123: return file(cache, null) != null;
124: }
125:
126: /** Opens the access to cache object as a stream.
127: * @param name name of the cache
128: * @return stream to read from the cache or null if the cache is not valid
129: */
130: public InputStream asStream(String cache) {
131: ByteBuffer bb = asByteBuffer(cache, false, false);
132: if (bb == null) {
133: return null;
134: }
135: return new ByteArrayInputStream(bb.array());
136: }
137:
138: /** Getter for mmapped buffer access to the cache.
139: * @param cache the file to access
140: * @return mmapped read only buffer
141: */
142: public MappedByteBuffer asMappedByteBuffer(String cache) {
143: return (MappedByteBuffer) asByteBuffer(cache, true, true);
144: }
145:
146: /** Returns the stamp for this caches.
147: * @return a date, each cache needs to be newer than this date
148: */
149:
150: /** Opens the access to cache object as a stream.
151: * @param name name of the cache
152: * @return stream to read from the cache or null if the cache is not valid
153: */
154: public ByteBuffer asByteBuffer(String cache) {
155: return asByteBuffer(cache, true, false);
156: }
157:
158: private File file(String cache, int[] len) {
159: String ud = System.getProperty("netbeans.user"); // NOI18N
160: if (ud == null) {
161: return null;
162: }
163: synchronized (this ) {
164: if (worker.isProcessing(cache)) {
165: return null;
166: }
167: }
168:
169: File cacheFile = new File(
170: new File(new File(ud, "var"), "cache"), cache.replace(
171: '/', File.separatorChar)); // NOI18N
172: long last = cacheFile.lastModified();
173: if (last <= 0) {
174: return null;
175: }
176:
177: if (moduleJARs() >= last) {
178: return null;
179: }
180:
181: long longLen = cacheFile.length();
182: if (longLen > Integer.MAX_VALUE) {
183: LOG.warning("Cache file is too big: " + longLen
184: + " bytes for " + cacheFile); // NOI18N
185: return null;
186: }
187: if (len != null) {
188: len[0] = (int) longLen;
189: }
190:
191: return cacheFile;
192: }
193:
194: private ByteBuffer asByteBuffer(String cache, boolean direct,
195: boolean mmap) {
196: int[] len = new int[1];
197: File cacheFile = file(cache, len);
198: if (cacheFile == null) {
199: return null;
200: }
201:
202: try {
203: FileChannel fc = new FileInputStream(cacheFile)
204: .getChannel();
205: ByteBuffer master;
206: if (mmap) {
207: master = fc.map(FileChannel.MapMode.READ_ONLY, 0,
208: len[0]);
209: master.order(ByteOrder.LITTLE_ENDIAN);
210: } else {
211: master = direct ? ByteBuffer.allocateDirect(len[0])
212: : ByteBuffer.allocate(len[0]);
213: int red = fc.read(master);
214: if (red != len[0]) {
215: LOG
216: .warning("Read less than expected: " + red
217: + " expected: " + len + " for "
218: + cacheFile); // NOI18N
219: return null;
220: }
221: master.flip();
222: }
223:
224: fc.close();
225:
226: return master;
227: } catch (IOException ex) {
228: LOG
229: .log(Level.WARNING, "Cannot read cache "
230: + cacheFile, ex); // NOI18N
231: return null;
232: }
233: }
234:
235: /** Method for registering updates to caches.
236: * @param updater the callback to start when flushing caches
237: * @param file name of the file to store the cache into
238: * @param append write from scratch or append?
239: */
240: public void scheduleSave(Updater updater, String cache,
241: boolean append) {
242: LOG.log(Level.FINE, "Scheduling save for {0} cache", cache);
243: synchronized (worker) {
244: worker.addStorage(new Store(updater, cache, append));
245: }
246: }
247:
248: /** Flushes all caches.
249: * @param delay the delay to wait with starting the parsing, if zero, that also means
250: * we want to wait for the end of parsing
251: */
252: public void flush(int delay) {
253: synchronized (worker) {
254: worker.start(delay);
255: }
256: }
257:
258: /** Waits for the worker to finish */
259: public void shutdown() {
260: waitFor(true);
261: }
262:
263: public void discardCaches() {
264: String user = System.getProperty("netbeans.user"); // NOI18N
265: long now = System.currentTimeMillis();
266: if (user != null) {
267: File f = new File(user, ".lastModified");
268: if (f.exists()) {
269: f.setLastModified(now);
270: } else {
271: f.getParentFile().mkdirs();
272: try {
273: f.createNewFile();
274: } catch (IOException ex) {
275: LOG.log(Level.WARNING, "Cannot create " + f, ex);
276: }
277: }
278: }
279: AtomicLong al = moduleJARs;
280: if (al != null) {
281: al.set(now);
282: }
283: }
284:
285: final void waitFor(boolean noNotify) {
286: Worker wait;
287: synchronized (worker) {
288: flush(0);
289: wait = worker;
290: }
291: wait.waitFor(noNotify);
292: }
293:
294: /** Computes and returns timestamp for all files that affect
295: * module classloading and related caches.
296: * @return
297: */
298: static long moduleJARs() {
299: AtomicLong local = moduleJARs;
300: if (local == null) {
301: local = moduleJARs = stamp(true);
302: }
303: return local.longValue();
304: }
305:
306: //
307: // Implementation. As less dependecies on other NetBeans clases, as possible, please.
308: // This will be called externally from a launcher.
309: //
310:
311: private static AtomicLong stamp(boolean checkStampFile) {
312: AtomicLong result = new AtomicLong();
313:
314: Set<File> processedDirs = new HashSet<File>();
315: String home = System.getProperty("netbeans.home"); // NOI18N
316: if (home != null) {
317: stampForCluster(new File(home), result, processedDirs,
318: checkStampFile, true);
319: }
320: String nbdirs = System.getProperty("netbeans.dirs"); // NOI18N
321: if (nbdirs != null) {
322: StringTokenizer tok = new StringTokenizer(nbdirs,
323: File.pathSeparator);
324: while (tok.hasMoreTokens()) {
325: stampForCluster(new File(tok.nextToken()), result,
326: processedDirs, checkStampFile, true);
327: }
328: }
329: String user = System.getProperty("netbeans.user"); // NOI18N
330: if (user != null) {
331: stampForCluster(new File(user), result,
332: new HashSet<File>(), false, false);
333: }
334:
335: return result;
336: }
337:
338: private static void stampForCluster(File cluster,
339: AtomicLong result, Set<File> hashSet,
340: boolean checkStampFile, boolean createStampFile) {
341: File stamp = new File(cluster, ".lastModified"); // NOI18N
342: long time;
343: if (checkStampFile && (time = stamp.lastModified()) > 0) {
344: if (time > result.longValue()) {
345: result.set(time);
346: }
347: return;
348: }
349: String user = System.getProperty("netbeans.user"); // NOI18N
350: if (user != null) {
351: File userDir = new File(user);
352: stamp = new File(
353: new File(
354: new File(new File(userDir, "var"), "cache"),
355: "lastModified"), cluster.getName());
356: if (checkStampFile && (time = stamp.lastModified()) > 0) {
357: if (time > result.longValue()) {
358: result.set(time);
359: }
360: return;
361: }
362: } else {
363: createStampFile = false;
364: }
365:
366: File configDir = new File(new File(cluster, "config"),
367: "Modules"); // NOI18N
368: File modulesDir = new File(cluster, "modules"); // NOI18N
369:
370: highestStampForDir(configDir, result);
371: highestStampForDir(modulesDir, result);
372:
373: if (createStampFile) {
374: try {
375: stamp.getParentFile().mkdirs();
376: stamp.createNewFile();
377: stamp.setLastModified(result.longValue());
378: } catch (IOException ex) {
379: System.err
380: .println("Cannot write timestamp to " + stamp); // NOI18N
381: }
382: }
383: }
384:
385: private static void highestStampForDir(File file, AtomicLong result) {
386: File[] children = file.listFiles();
387: if (children == null) {
388: long time = file.lastModified();
389: if (time > result.longValue()) {
390: result.set(time);
391: }
392: return;
393: }
394:
395: for (File f : children) {
396: highestStampForDir(f, result);
397: }
398:
399: }
400:
401: private static void deleteCache(File cacheFile) throws IOException {
402: int fileCounter = 0;
403: if (cacheFile.exists()) {
404: // all of this mess is here because Windows can't delete mmaped file.
405: File tmpFile = new File(cacheFile.getParentFile(),
406: cacheFile.getName() + "." + fileCounter++);
407: tmpFile.delete(); // delete any leftover file from previous session
408: boolean renamed = false;
409: for (int i = 0; i < 5; i++) {
410: renamed = cacheFile.renameTo(tmpFile); // try to rename it
411: if (renamed) {
412: break;
413: }
414: LOG.fine("cannot rename (#" + i + "): " + cacheFile); // NOI18N
415: // try harder
416: System.gc();
417: System.runFinalization();
418: LOG.fine("after GC"); // NOI18N
419: }
420: if (!renamed) {
421: // still delete on exit, so next start is ok
422: cacheFile.deleteOnExit();
423: throw new IOException("Could not delete: " + cacheFile); // NOI18N
424: }
425: if (!tmpFile.delete()) {
426: tmpFile.deleteOnExit();
427: } // delete now or later
428: }
429: }
430:
431: /** A callback interface to flush content of some cache at a suitable
432: * point in time.
433: */
434: public static interface Updater {
435: /** Callback method to allow storage of the cache to a stream.
436: * If an excetion is thrown, cache is invalidated.
437: *
438: * @param os the stream to write to
439: * @throws IOException exception in case something goes wrong
440: */
441: public void flushCaches(DataOutputStream os) throws IOException;
442:
443: /** Callback method to notify the caller, that
444: * caches are successfully written.
445: */
446: public void cacheReady();
447: }
448:
449: /** Internal structure keeping info about storages.
450: */
451: private static final class Store extends OutputStream {
452: final Updater updater;
453: final String cache;
454: final boolean append;
455:
456: OutputStream os;
457: AtomicInteger delay;
458: int count;
459:
460: public Store(Updater updater, String cache, boolean append) {
461: this .updater = updater;
462: this .cache = cache;
463: this .append = append;
464: }
465:
466: public boolean store(AtomicInteger delay) {
467: assert os == null;
468:
469: String ud = System.getProperty("netbeans.user"); // NOI18N
470: if (ud == null) {
471: LOG.warning("No 'netbeans.user' property to store: "
472: + cache); // NOI18N
473: return false;
474: }
475: File cacheFile = new File(new File(new File(ud, "var"),
476: "cache"), cache); // NOI18N
477: boolean delete = false;
478: try {
479: LOG.log(Level.FINE, "Cleaning cache {0}", cacheFile);
480:
481: if (!append) {
482: deleteCache(cacheFile);
483: }
484: cacheFile.getParentFile().mkdirs();
485:
486: LOG.log(Level.FINE, "Storing cache {0}", cacheFile);
487: os = new FileOutputStream(cacheFile, append); //append new entries only
488: DataOutputStream dos = new DataOutputStream(
489: new BufferedOutputStream(this , 1024 * 1024));
490:
491: this .delay = delay;
492:
493: updater.flushCaches(dos);
494: dos.close();
495: LOG
496: .log(Level.FINE, "Done Storing cache {0}",
497: cacheFile);
498: } catch (IOException ex) {
499: LOG.log(Level.WARNING, "Error saving cache "
500: + cacheFile, ex); // NOI18N
501: delete = true;
502: } finally {
503: if (os != null) {
504: try {
505: os.close();
506: } catch (IOException ex) {
507: LOG
508: .log(Level.WARNING,
509: "Error closing stream for "
510: + cacheFile, ex); // NOI18N
511: }
512: os = null;
513: }
514: if (delete) {
515: cacheFile.delete();
516: cacheFile.deleteOnExit();
517: }
518: }
519: return !delete;
520: }
521:
522: @Override
523: public void close() throws IOException {
524: os.close();
525: }
526:
527: @Override
528: public void flush() throws IOException {
529: os.flush();
530: }
531:
532: @Override
533: public void write(int b) throws IOException {
534: os.write(b);
535: count(1);
536: }
537:
538: @Override
539: public void write(byte[] b) throws IOException {
540: os.write(b);
541: count(b.length);
542: }
543:
544: @Override
545: public void write(byte[] b, int off, int len)
546: throws IOException {
547: os.write(b, off, len);
548: count(len);
549: }
550:
551: private void count(int add) {
552: count += add;
553: if (count > 64 * 1024) {
554: int wait = delay.get();
555: if (wait > 0) {
556: try {
557: Thread.sleep(wait);
558: } catch (InterruptedException ex) {
559: Exceptions.printStackTrace(ex);
560: }
561: }
562: count = 0;
563: }
564: }
565:
566: @Override
567: public boolean equals(Object obj) {
568: if (obj == null) {
569: return false;
570: }
571: if (getClass() != obj.getClass()) {
572: return false;
573: }
574: final Store other = (Store) obj;
575: if (!this .updater.equals(other.updater)) {
576: return false;
577: }
578: if (!this .cache.equals(other.cache)) {
579: return false;
580: }
581: return true;
582: }
583:
584: @Override
585: public int hashCode() {
586: int hash = 7;
587: hash = 19
588: * hash
589: + (this .updater != null ? this .updater.hashCode()
590: : 0);
591: hash = 19 * hash
592: + (this .cache != null ? this .cache.hashCode() : 0);
593: return hash;
594: }
595:
596: @Override
597: public String toString() {
598: return cache;
599: }
600: } // end of Store
601:
602: private final class Worker extends Thread {
603: private final LinkedList<Store> storages;
604: private final HashSet<String> processing;
605: private AtomicInteger delay;
606: private boolean noNotify;
607:
608: public Worker() {
609: super ("Flushing caches");
610: storages = new LinkedList<Stamps.Store>();
611: processing = new HashSet<String>();
612: setPriority(MIN_PRIORITY);
613: }
614:
615: public synchronized void start(int time) {
616: if (delay == null) {
617: delay = new AtomicInteger(time);
618: super .start();
619: }
620: }
621:
622: public synchronized void addStorage(Store s) {
623: processing.add(s.cache);
624: for (Iterator<Stamps.Store> it = storages.iterator(); it
625: .hasNext();) {
626: Stamps.Store store = it.next();
627: if (store.equals(s)) {
628: it.remove();
629: }
630: }
631: storages.add(s);
632: }
633:
634: @Override
635: public void run() {
636: int before = delay.get();
637: for (int till = before; till >= 0; till -= 500) {
638: try {
639: synchronized (this ) {
640: wait(500);
641: }
642: } catch (InterruptedException ex) {
643: LOG.log(Level.INFO, null, ex);
644: }
645: if (before != delay.get()) {
646: break;
647: }
648: }
649: if (before > 512) {
650: delay.compareAndSet(before, 512);
651: }
652:
653: long time = System.currentTimeMillis();
654: LOG.log(Level.FINE, "Storing caches {0}", storages);
655:
656: HashSet<Store> notify = new HashSet<Stamps.Store>();
657: for (;;) {
658: Store store;
659: synchronized (this ) {
660: store = this .storages.poll();
661: if (store == null) {
662: // ready for new round of work
663: worker = new Worker();
664: break;
665: }
666: }
667: if (store.store(delay)) {
668: notify.add(store);
669: }
670: }
671:
672: long much = System.currentTimeMillis() - time;
673: LOG.log(Level.FINE, "Done storing caches {0}", notify);
674: LOG.log(Level.FINE, "Took {0} ms", much);
675:
676: processing.clear();
677:
678: for (Stamps.Store store : notify) {
679: if (!noNotify) {
680: store.updater.cacheReady();
681: }
682: }
683: LOG.log(Level.FINE, "Notified ready {0}", notify);
684:
685: }
686:
687: final void waitFor(boolean noNotify) {
688: try {
689: this .noNotify = noNotify;
690: delay.set(0);
691: synchronized (this ) {
692: notifyAll();
693: }
694: join();
695: } catch (InterruptedException ex) {
696: Exceptions.printStackTrace(ex);
697: }
698: }
699:
700: private boolean isProcessing(String cache) {
701: return processing.contains(cache);
702: }
703:
704: }
705: }
|