KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > ruby > spi > project > support > rake > GeneratedFilesHelper


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.ruby.spi.project.support.rake;
21
22 import java.io.BufferedInputStream JavaDoc;
23 import java.io.BufferedOutputStream JavaDoc;
24 import java.io.ByteArrayInputStream JavaDoc;
25 import java.io.ByteArrayOutputStream JavaDoc;
26 import java.io.File JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.io.InputStream JavaDoc;
29 import java.io.OutputStream JavaDoc;
30 import java.net.URI JavaDoc;
31 import java.net.URL JavaDoc;
32 import java.util.HashMap JavaDoc;
33 import java.util.Map JavaDoc;
34 import java.util.Properties JavaDoc;
35 import java.util.zip.CRC32 JavaDoc;
36 import java.util.zip.Checksum JavaDoc;
37 import javax.xml.transform.Transformer JavaDoc;
38 import javax.xml.transform.TransformerException JavaDoc;
39 import javax.xml.transform.TransformerFactory JavaDoc;
40 import javax.xml.transform.stream.StreamResult JavaDoc;
41 import javax.xml.transform.stream.StreamSource JavaDoc;
42 import org.netbeans.api.project.ProjectManager;
43 import org.netbeans.modules.ruby.modules.project.rake.UserQuestionHandler;
44 import org.openide.ErrorManager;
45 import org.openide.filesystems.FileLock;
46 import org.openide.filesystems.FileObject;
47 import org.openide.filesystems.FileSystem;
48 import org.openide.filesystems.FileUtil;
49 import org.openide.util.Mutex;
50 import org.openide.util.MutexException;
51 import org.openide.util.NbBundle;
52 import org.openide.util.UserQuestionException;
53 import org.openide.util.Utilities;
54
55 /**
56  * Helps a project type (re-)generate, and manage the state and versioning of,
57  * <code>build.xml</code> and <code>build-impl.xml</code>.
58  * @author Jesse Glick
59  */

60 public final class GeneratedFilesHelper {
61     
62     /**
63      * Relative path from project directory to the user build script,
64      * <code>build.xml</code>.
65      */

66     public static final String JavaDoc BUILD_XML_PATH = "build.xml"; // NOI18N
67

68     /**
69      * Relative path from project directory to the implementation build script,
70      * <code>build-impl.xml</code>.
71      */

72     public static final String JavaDoc BUILD_IMPL_XML_PATH = "nbproject/build-impl.xml"; // NOI18N
73

74     /**
75      * Path to file storing information about generated files.
76      * It should be kept in version control, since it applies equally after a fresh
77      * project checkout; it does not apply to running Ant, so should not be in
78      * <code>project.properties</code>; and it includes a CRC of <code>project.xml</code>
79      * so it cannot be in that file either. It could be stored in some special
80      * comment at the end of the build script (e.g.) but many users would just
81      * compulsively delete it in this case since it looks weird.
82      */

83     static final String JavaDoc GENFILES_PROPERTIES_PATH = "nbproject/genfiles.properties"; // NOI18N
84

85     /** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for <code>project.xml</code> CRC. */
86     private static final String JavaDoc KEY_SUFFIX_DATA_CRC = ".data.CRC32"; // NOI18N
87

88     /** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for stylesheet CRC. */
89     private static final String JavaDoc KEY_SUFFIX_STYLESHEET_CRC = ".stylesheet.CRC32"; // NOI18N
90

91     /** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for CRC of the script itself. */
92     private static final String JavaDoc KEY_SUFFIX_SCRIPT_CRC = ".script.CRC32"; // NOI18N
93

94     /**
95      * A build script is missing from disk.
96      * This is mutually exclusive with the other flags.
97      * @see #getBuildScriptState
98      */

99     public static final int FLAG_MISSING = 2 << 0;
100     
101     /**
102      * A build script has been modified since last generated by
103      * {@link #generateBuildScriptFromStylesheet}.
104      * <p class="nonnormative">
105      * Probably this means it was edited by the user.
106      * </p>
107      * @see #getBuildScriptState
108      */

109     public static final int FLAG_MODIFIED = 2 << 1;
110     
111     /**
112      * A build script was generated from an older version of <code>project.xml</code>.
113      * It was last generated using a different version of <code>project.xml</code>,
114      * so using the current <code>project.xml</code> might produce a different
115      * result.
116      * <p class="nonnormative">
117      * This is quite likely in the case of
118      * <code>build.xml</code>; in the case of <code>build-impl.xml</code>, it
119      * probably means that the user edited <code>project.xml</code> manually,
120      * since if that were modified from {@link RakeProjectHelper} methods and
121      * the project were saved, the script would have been regenerated
122      * already.
123      * </p>
124      * @see #getBuildScriptState
125      */

126     public static final int FLAG_OLD_PROJECT_XML = 2 << 2;
127     
128     /**
129      * A build script was generated from an older version of a stylesheet.
130      * It was last generated using a different (probably older) version of the
131      * XSLT stylesheet, so using the current stylesheet might produce a different
132      * result.
133      * <p class="nonnormative">
134      * Probably this means the project type
135      * provider module has been upgraded since the project was last saved (in
136      * the case of <code>build-impl.xml</code>) or created (in the case of
137      * <code>build.xml</code>).
138      * </p>
139      * @see #getBuildScriptState
140      */

141     public static final int FLAG_OLD_STYLESHEET = 2 << 3;
142     
143     /**
144      * The build script exists, but nothing else is known about it.
145      * This flag is mutually exclusive with {@link #FLAG_MISSING} but
146      * when set also sets {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_STYLESHEET},
147      * and {@link #FLAG_OLD_PROJECT_XML} - since it is not known whether these
148      * conditions might obtain, it is safest to assume they do.
149      * <p class="nonnormative">
150      * Probably this means that <code>nbproject/genfiles.properties</code> was
151      * deleted by the user.
152      * </p>
153      * @see #getBuildScriptState
154      */

155     public static final int FLAG_UNKNOWN = 2 << 4;
156     
157     /** Associated project helper, or null if using only a directory. */
158     private final RakeProjectHelper h;
159     
160     /** Project directory. */
161     private final FileObject dir;
162     
163     /**
164      * Create a helper based on the supplied project helper handle.
165      * @param h an Ant-based project helper supplied to the project type provider
166      */

167     public GeneratedFilesHelper(RakeProjectHelper h) {
168         this.h = h;
169         dir = h.getProjectDirectory();
170     }
171     
172     /**
173      * Create a helper based only on a project directory.
174      * This can be used to perform the basic refresh functionality
175      * without being the owner of the project.
176      * It is only intended for use from the offline Ant task to
177      * refresh a project, and similar special situations.
178      * For normal circumstances please use only
179      * {@link GeneratedFilesHelper#GeneratedFilesHelper(RakeProjectHelper)}.
180      * @param d the project directory
181      * @throws IllegalArgumentException if the supplied directory has no <code>project.xml</code>
182      */

183     public GeneratedFilesHelper(FileObject d) {
184         if (d == null || !d.isFolder() || d.getFileObject(RakeProjectHelper.PROJECT_XML_PATH) == null) {
185             throw new IllegalArgumentException JavaDoc("Does not look like an Ant-based project: " + d); // NOI18N
186
}
187         h = null;
188         dir = d;
189     }
190     
191     /**
192      * Create <code>build.xml</code> or <code>nbproject/build-impl.xml</code>
193      * from <code>project.xml</code> plus a supplied XSLT stylesheet.
194      * This is the recommended way to create the build scripts from
195      * project metadata.
196      * <p class="nonnormative">
197      * You may wish to first check {@link #getBuildScriptState} to decide whether
198      * you really want to overwrite an existing build script. Typically if you
199      * find {@link #FLAG_MODIFIED} you should not overwrite it; or ask for confirmation
200      * first; or make a backup. Of course if you find neither of {@link #FLAG_OLD_STYLESHEET}
201      * nor {@link #FLAG_OLD_PROJECT_XML} then there is no reason to overwrite the
202      * script to begin with.
203      * </p>
204      * <p>
205      * Acquires write access.
206      * </p>
207      * @param path a project-relative file path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
208      * @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
209      * as input and producing the build script as output
210      * @throws IOException if transforming or writing the output failed
211      * @throws IllegalStateException if the project was modified (and not being saved)
212      */

213     public void generateBuildScriptFromStylesheet(final String JavaDoc path, final URL JavaDoc stylesheet) throws IOException JavaDoc, IllegalStateException JavaDoc {
214         if (path == null) {
215             throw new IllegalArgumentException JavaDoc("Null path"); // NOI18N
216
}
217         if (stylesheet == null) {
218             throw new IllegalArgumentException JavaDoc("Null stylesheet"); // NOI18N
219
}
220         try {
221             ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void JavaDoc>() {
222                 public Void JavaDoc run() throws IOException JavaDoc {
223                     if (h != null && h.isProjectXmlModified()) {
224                         throw new IllegalStateException JavaDoc("Cannot generate build scripts from a modified project"); // NOI18N
225
}
226                     // Need to use an atomic action since otherwise creating new build scripts might
227
// cause them to not be recognized as Ant scripts.
228
dir.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
229                         public void run() throws IOException JavaDoc {
230                             FileObject projectXml = dir.getFileObject(RakeProjectHelper.PROJECT_XML_PATH);
231                             final FileObject buildScriptXml = FileUtil.createData(dir, path);
232                             byte[] projectXmlData;
233                             InputStream JavaDoc is = projectXml.getInputStream();
234                             try {
235                                 projectXmlData = load(is);
236                             } finally {
237                                 is.close();
238                             }
239                             byte[] stylesheetData;
240                             is = stylesheet.openStream();
241                             try {
242                                 stylesheetData = load(is);
243                             } finally {
244                                 is.close();
245                             }
246                             final byte[] resultData;
247                             TransformerFactory JavaDoc tf = TransformerFactory.newInstance();
248                             try {
249                                 StreamSource JavaDoc stylesheetSource = new StreamSource JavaDoc(
250                                     new ByteArrayInputStream JavaDoc(stylesheetData), stylesheet.toExternalForm());
251                                 Transformer JavaDoc t = tf.newTransformer(stylesheetSource);
252                                 File JavaDoc projectXmlF = FileUtil.toFile(projectXml);
253                                 assert projectXmlF != null;
254                                 StreamSource JavaDoc projectXmlSource = new StreamSource JavaDoc(
255                                     new ByteArrayInputStream JavaDoc(projectXmlData), projectXmlF.toURI().toString());
256                                 ByteArrayOutputStream JavaDoc result = new ByteArrayOutputStream JavaDoc();
257                                 t.transform(projectXmlSource, new StreamResult JavaDoc(result));
258                                 resultData = result.toByteArray();
259                             } catch (TransformerException JavaDoc e) {
260                                 throw (IOException JavaDoc)new IOException JavaDoc(e.toString()).initCause(e);
261                             }
262                             // Update genfiles.properties too.
263
final EditableProperties p = new EditableProperties(true);
264                             FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH);
265                             if (genfiles != null && genfiles.isVirtual()) {
266                                 // #55164: deleted from CVS?
267
genfiles = null;
268                             }
269                             if (genfiles != null) {
270                                 is = genfiles.getInputStream();
271                                 try {
272                                     p.load(is);
273                                 } finally {
274                                     is.close();
275                                 }
276                             }
277                             p.setProperty(path + KEY_SUFFIX_DATA_CRC,
278                                 getCrc32(new ByteArrayInputStream JavaDoc(projectXmlData), projectXml));
279                             if (genfiles == null) {
280                                 // New file, set a comment on it. XXX this puts comment in middle if write build-impl.xml before build.xml
281
p.setComment(path + KEY_SUFFIX_DATA_CRC, new String JavaDoc[] {
282                                     "# " + NbBundle.getMessage(GeneratedFilesHelper.class, "COMMENT_genfiles.properties_1"), // NOI18N
283
"# " + NbBundle.getMessage(GeneratedFilesHelper.class, "COMMENT_genfiles.properties_2"), // NOI18N
284
}, false);
285                             }
286                             p.setProperty(path + KEY_SUFFIX_STYLESHEET_CRC,
287                                 getCrc32(new ByteArrayInputStream JavaDoc(stylesheetData), stylesheet));
288                             p.setProperty(path + KEY_SUFFIX_SCRIPT_CRC,
289                                 computeCrc32(new ByteArrayInputStream JavaDoc(resultData)));
290                             if (genfiles == null) {
291                                 genfiles = FileUtil.createData(dir, GENFILES_PROPERTIES_PATH);
292                             }
293                             final FileObject _genfiles = genfiles;
294                             // You get the Spaghetti Code Award if you can follow the control flow in this method!
295
// Now is the time when you wish Java implemented call/cc.
296
// If you didn't understand that last comment, you don't get the Spaghetti Code Award.
297
final FileSystem.AtomicAction body = new FileSystem.AtomicAction() {
298                                 public void run() throws IOException JavaDoc {
299                                     // Try to acquire both locks together, since we need them both written.
300
FileLock lock1 = buildScriptXml.lock();
301                                     try {
302                                         FileLock lock2 = _genfiles.lock();
303                                         try {
304                                             OutputStream JavaDoc os1 = new EolFilterOutputStream(buildScriptXml.getOutputStream(lock1));
305                                             try {
306                                                 OutputStream JavaDoc os2 = _genfiles.getOutputStream(lock2);
307                                                 try {
308                                                     os1.write(resultData);
309                                                     p.store(os2);
310                                                 } finally {
311                                                     os2.close();
312                                                 }
313                                             } finally {
314                                                 os1.close();
315                                             }
316                                         } finally {
317                                             lock2.releaseLock();
318                                         }
319                                     } finally {
320                                         lock1.releaseLock();
321                                     }
322                                 }
323                             };
324                             try {
325                                 body.run();
326                             } catch (UserQuestionException uqe) {
327                                 // #57480: need to regenerate build-impl.xml, really.
328
UserQuestionHandler.handle(uqe, new UserQuestionHandler.Callback() {
329                                     public void accepted() {
330                                         // Try again.
331
try {
332                                             body.run();
333                                         } catch (UserQuestionException uqe2) {
334                                             // Need to try one more time - may have locked bSX but not yet gf.
335
UserQuestionHandler.handle(uqe2, new UserQuestionHandler.Callback() {
336                                                 public void accepted() {
337                                                     try {
338                                                         body.run();
339                                                     } catch (IOException JavaDoc e) {
340                                                         ErrorManager.getDefault().notify(e);
341                                                     }
342                                                 }
343                                                 public void denied() {}
344                                                 public void error(IOException JavaDoc e) {
345                                                     ErrorManager.getDefault().notify(e);
346                                                 }
347                                             });
348                                         } catch (IOException JavaDoc e) {
349                                             // Oh well.
350
ErrorManager.getDefault().notify(e);
351                                         }
352                                     }
353                                     public void denied() {
354                                         // OK, skip it.
355
}
356                                     public void error(IOException JavaDoc e) {
357                                         ErrorManager.getDefault().notify(e);
358                                         // Never mind.
359
}
360                                 });
361                             }
362                         }
363                     });
364                     return null;
365                 }
366             });
367         } catch (MutexException e) {
368             throw (IOException JavaDoc)e.getException();
369         }
370     }
371     
372     /**
373      * Load data from a stream into a buffer.
374      */

375     private static byte[] load(InputStream JavaDoc is) throws IOException JavaDoc {
376         int size = Math.max(1024, is.available()); // #46235
377
ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc(size);
378         byte[] buf = new byte[size];
379         int read;
380         while ((read = is.read(buf)) != -1) {
381             baos.write(buf, 0, read);
382         }
383         return baos.toByteArray();
384     }
385     
386     /**
387      * Find what state a build script is in.
388      * This may be used by a project type provider to decide whether to create
389      * or overwrite it, and whether to produce a backup in the latter case.
390      * Various abnormal conditions are detected:
391      * {@link #FLAG_MISSING}, {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_PROJECT_XML},
392      * {@link #FLAG_OLD_STYLESHEET}, and {@link #FLAG_UNKNOWN}.
393      * <p class="nonnormative">
394      * Currently {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_STYLESHEET}, and
395      * {@link #FLAG_OLD_PROJECT_XML} are detected by computing a CRC-32
396      * of the script when it is created, as well as the CRC-32s of the
397      * stylesheet and <code>project.xml</code>. These CRCs are stored
398      * in a special file <code>nbproject/genfiles.properties</code>.
399      * The CRCs are based on the textual
400      * contents of the files (so even changed to whitespace etc. are considered
401      * changes), but are independent of platform newline conventions (since e.g.
402      * CVS will by default replace \n with \r\n when checking out on Windows).
403      * Changes to external files included into <code>project.xml</code> or the
404      * stylesheet (e.g. using XSLT's import facility) are <em>not</em> detected.
405      * </p>
406      * <p>
407      * If there is some kind of I/O error reading any files, {@link #FLAG_UNKNOWN}
408      * is returned (in conjunction with {@link #FLAG_MODIFIED},
409      * {@link #FLAG_OLD_STYLESHEET}, and {@link #FLAG_OLD_PROJECT_XML} to be safe).
410      * </p>
411      * <p>
412      * Acquires read access.
413      * </p>
414      * @param path a project-relative path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
415      * @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
416      * as input and producing the build script as output
417      * (should match that given to {@link #generateBuildScriptFromStylesheet})
418      * @return a bitwise OR of various flags, or <code>0</code> if the script
419      * is present on disk and fully up-to-date
420      * @throws IllegalStateException if the project was modified
421      */

422     public int getBuildScriptState(final String JavaDoc path, final URL JavaDoc stylesheet) throws IllegalStateException JavaDoc {
423         try {
424             return ProjectManager.mutex().readAccess(new Mutex.ExceptionAction<Integer JavaDoc>() {
425                 public Integer JavaDoc run() throws IOException JavaDoc {
426                     if (h != null && h.isProjectXmlModified()) {
427                         throw new IllegalStateException JavaDoc("Cannot generate build scripts from a modified project"); // NOI18N
428
}
429                     FileObject script = dir.getFileObject(path);
430                     if (script == null || /* #55164 */script.isVirtual()) {
431                         return FLAG_MISSING;
432                     }
433                     int flags = 0;
434                     Properties JavaDoc p = new Properties JavaDoc();
435                     FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH);
436                     if (genfiles == null || /* #55164 */genfiles.isVirtual()) {
437                         // Who knows? User deleted it; anything might be wrong. Safest to assume
438
// that everything is.
439
return FLAG_UNKNOWN | FLAG_MODIFIED |
440                                 FLAG_OLD_PROJECT_XML | FLAG_OLD_STYLESHEET;
441                     }
442                     InputStream JavaDoc is = new BufferedInputStream JavaDoc(genfiles.getInputStream());
443                     try {
444                         p.load(is);
445                     } finally {
446                         is.close();
447                     }
448                     FileObject projectXml = dir.getFileObject(RakeProjectHelper.PROJECT_XML_PATH);
449                     if (projectXml != null && /* #55164 */!projectXml.isVirtual()) {
450                         String JavaDoc crc = getCrc32(projectXml);
451                         if (!crc.equals(p.getProperty(path + KEY_SUFFIX_DATA_CRC))) {
452                             flags |= FLAG_OLD_PROJECT_XML;
453                         }
454                     } else {
455                         // Broken project?!
456
flags |= FLAG_OLD_PROJECT_XML;
457                     }
458                     String JavaDoc crc = getCrc32(stylesheet);
459                     if (!crc.equals(p.getProperty(path + KEY_SUFFIX_STYLESHEET_CRC))) {
460                         flags |= FLAG_OLD_STYLESHEET;
461                     }
462                     crc = getCrc32(script);
463                     if (!crc.equals(p.getProperty(path + KEY_SUFFIX_SCRIPT_CRC))) {
464                         flags |= FLAG_MODIFIED;
465                     }
466                     return flags;
467                 }
468             });
469         } catch (MutexException e) {
470             ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, (IOException JavaDoc)e.getException());
471             return FLAG_UNKNOWN | FLAG_MODIFIED | FLAG_OLD_PROJECT_XML | FLAG_OLD_STYLESHEET;
472         }
473     }
474     
475     /**
476      * Compute the CRC-32 of the contents of a stream.
477      * \r\n and \r are both normalized to \n for purposes of the calculation.
478      */

479     static String JavaDoc computeCrc32(InputStream JavaDoc is) throws IOException JavaDoc {
480         Checksum JavaDoc crc = new CRC32 JavaDoc();
481         int last = -1;
482         int curr;
483         while ((curr = is.read()) != -1) {
484             if (curr != '\n' && last == '\r') {
485                 crc.update('\n');
486             }
487             if (curr != '\r') {
488                 crc.update(curr);
489             }
490             last = curr;
491         }
492         if (last == '\r') {
493             crc.update('\n');
494         }
495         int val = (int)crc.getValue();
496         String JavaDoc hex = Integer.toHexString(val);
497         while (hex.length() < 8) {
498             hex = "0" + hex; // NOI18N
499
}
500         return hex;
501     }
502     
503     // #50440 - cache CRC32's for various files to save time esp. during startup.
504

505     private static final Map JavaDoc<URL JavaDoc,String JavaDoc> crcCache = new HashMap JavaDoc<URL JavaDoc,String JavaDoc>();
506     private static final Map JavaDoc<URL JavaDoc,Long JavaDoc> crcCacheTimestampsXorSizes = new HashMap JavaDoc<URL JavaDoc,Long JavaDoc>();
507
508     /** Try to find a CRC in the cache according to location of file and last mod time xor size. */
509     private static synchronized String JavaDoc findCachedCrc32(URL JavaDoc u, long footprint) {
510         String JavaDoc crc = crcCache.get(u);
511         if (crc != null) {
512             Long JavaDoc l = crcCacheTimestampsXorSizes.get(u);
513             assert l != null;
514             if (l == footprint) {
515                 // Cache hit.
516
return crc;
517             }
518         }
519         // Cache miss - missing or old.
520
return null;
521     }
522     
523     /** Cache a known CRC for a file, using current last mod time xor size. */
524     private static synchronized void cacheCrc32(String JavaDoc crc, URL JavaDoc u, long footprint) {
525         crcCache.put(u, crc);
526         crcCacheTimestampsXorSizes.put(u, footprint);
527     }
528     
529     /** Find (maybe cached) CRC for a file, using a preexisting input stream (not closed by this method). */
530     private static String JavaDoc getCrc32(InputStream JavaDoc is, FileObject fo) throws IOException JavaDoc {
531         URL JavaDoc u = fo.getURL();
532         fo.refresh(); // in case was written on disk and we did not notice yet...
533
long footprint = fo.lastModified().getTime() ^ fo.getSize();
534         String JavaDoc crc = findCachedCrc32(u, footprint);
535         if (crc == null) {
536             crc = computeCrc32(is);
537             cacheCrc32(crc, u, footprint);
538         }
539         return crc;
540     }
541
542     /** Find the time the file this URL represents was last modified xor its size, if possible. */
543     private static long checkFootprint(URL JavaDoc u) {
544         URL JavaDoc nested = FileUtil.getArchiveFile(u);
545         if (nested != null) {
546             u = nested;
547         }
548         if (u.getProtocol().equals("file")) { // NOI18N
549
File JavaDoc f = new File JavaDoc(URI.create(u.toExternalForm()));
550             return f.lastModified() ^ f.length();
551         } else {
552             return 0L;
553         }
554     }
555     
556     /** Find (maybe cached) CRC for a URL, using a preexisting input stream (not closed by this method). */
557     private static String JavaDoc getCrc32(InputStream JavaDoc is, URL JavaDoc u) throws IOException JavaDoc {
558         long footprint = checkFootprint(u);
559         String JavaDoc crc = null;
560         if (footprint != 0L) {
561             crc = findCachedCrc32(u, footprint);
562         }
563         if (crc == null) {
564             crc = computeCrc32(is);
565             if (footprint != 0L) {
566                 cacheCrc32(crc, u, footprint);
567             }
568         }
569         return crc;
570     }
571     
572     /** Find (maybe cached) CRC for a file. Will open its own input stream. */
573     private static String JavaDoc getCrc32(FileObject fo) throws IOException JavaDoc {
574         URL JavaDoc u = fo.getURL();
575         fo.refresh();
576         long footprint = fo.lastModified().getTime() ^ fo.getSize();
577         String JavaDoc crc = findCachedCrc32(u, footprint);
578         if (crc == null) {
579             InputStream JavaDoc is = fo.getInputStream();
580             try {
581                 crc = computeCrc32(new BufferedInputStream JavaDoc(is));
582                 cacheCrc32(crc, u, footprint);
583             } finally {
584                 is.close();
585             }
586         }
587         return crc;
588     }
589     
590     /** Find (maybe cached) CRC for a URL. Will open its own input stream. */
591     private static String JavaDoc getCrc32(URL JavaDoc u) throws IOException JavaDoc {
592         long footprint = checkFootprint(u);
593         String JavaDoc crc = null;
594         if (footprint != 0L) {
595             crc = findCachedCrc32(u, footprint);
596         }
597         if (crc == null) {
598             InputStream JavaDoc is = u.openStream();
599             try {
600                 crc = computeCrc32(new BufferedInputStream JavaDoc(is));
601                 if (footprint != 0L) {
602                     cacheCrc32(crc, u, footprint);
603                 }
604             } finally {
605                 is.close();
606             }
607         }
608         return crc;
609     }
610
611     /**
612      * Convenience method to refresh a build script if it can and should be.
613      * <p>
614      * If the script is not modified, and it is either missing, or the flag
615      * <code>checkForProjectXmlModified</code> is false, or it is out of date with
616      * respect to either <code>project.xml</code> or the stylesheet (or both),
617      * it is (re-)generated.
618      * </p>
619      * <p>
620      * Acquires write access.
621      * </p>
622      * <p class="nonnormative">
623      * Typical usage from {@link ProjectXmlSavedHook#projectXmlSaved} is to call
624      * this method for both {@link #BUILD_XML_PATH} and {@link #BUILD_IMPL_XML_PATH}
625      * with the appropriate stylesheets and with <code>checkForProjectXmlModified</code>
626      * false (the script is certainly out of date relative to <code>project.xml</code>).
627      * Typical usage from {@link org.netbeans.spi.project.ui.ProjectOpenedHook#projectOpened} is to call
628      * this method for both scripts with the appropriate stylesheets and with
629      * <code>checkForProjectXmlModified</code> true.
630      * </p>
631      * @param path a project-relative path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
632      * @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
633      * as input and producing the build script as output
634      * @param checkForProjectXmlModified true if it is necessary to check whether the
635      * script is out of date with respect to
636      * <code>project.xml</code> and/or the stylesheet
637      * @return true if the script was in fact regenerated
638      * @throws IOException if transforming or writing the output failed
639      * @throws IllegalStateException if the project was modified
640      */

641     public boolean refreshBuildScript(final String JavaDoc path, final URL JavaDoc stylesheet, final boolean checkForProjectXmlModified) throws IOException JavaDoc, IllegalStateException JavaDoc {
642         try {
643             return ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Boolean JavaDoc>() {
644                 public Boolean JavaDoc run() throws IOException JavaDoc {
645                     int flags = getBuildScriptState(path, stylesheet);
646                     if (shouldGenerateBuildScript(flags, checkForProjectXmlModified)) {
647                         generateBuildScriptFromStylesheet(path, stylesheet);
648                         return true;
649                     } else {
650                         return false;
651                     }
652                 }
653             });
654         } catch (MutexException e) {
655             throw (IOException JavaDoc)e.getException();
656         }
657     }
658     
659     private static boolean shouldGenerateBuildScript(int flags, boolean checkForProjectXmlModified) {
660         if ((flags & GeneratedFilesHelper.FLAG_MISSING) != 0) {
661             // Yes, need it.
662
return true;
663         }
664         if ((flags & GeneratedFilesHelper.FLAG_MODIFIED) != 0) {
665             // No, don't overwrite a user build script.
666
// XXX modified build-impl.xml probably counts as a serious condition
667
// to warn the user about...
668
// Modified build.xml is no big deal.
669
return false;
670         }
671         if (!checkForProjectXmlModified) {
672             // OK, assume it is out of date.
673
return true;
674         }
675         // Check whether it is in fact out of date.
676
return (flags & (GeneratedFilesHelper.FLAG_OLD_PROJECT_XML |
677                          GeneratedFilesHelper.FLAG_OLD_STYLESHEET)) != 0;
678     }
679
680      // #45373 - workaround: on Windows make sure that all lines end with CRLF.
681
// marcow: Use at least some buffered output!
682
private static class EolFilterOutputStream extends BufferedOutputStream JavaDoc {
683
684         private boolean isActive = Utilities.isWindows();
685         private int last = -1;
686         
687         public EolFilterOutputStream(OutputStream JavaDoc os) {
688             super(os, 4096);
689         }
690         
691         public void write(byte[] b, int off, int len) throws IOException JavaDoc {
692             if (isActive) {
693                 for (int i = off; i < off + len; i++) {
694                     write(b[i]);
695                 }
696             }
697             else {
698                 super.write(b, off, len);
699             }
700         }
701
702         public void write(int b) throws IOException JavaDoc {
703             if (isActive) {
704                 if (b == '\n' && last != '\r') {
705                     super.write('\r');
706                 }
707                 last = b;
708             }
709             super.write(b);
710         }
711
712     }
713 }
714
Popular Tags