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: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.ruby.spi.project.support.rake;
043:
044: import java.io.BufferedInputStream;
045: import java.io.BufferedOutputStream;
046: import java.io.ByteArrayInputStream;
047: import java.io.ByteArrayOutputStream;
048: import java.io.File;
049: import java.io.IOException;
050: import java.io.InputStream;
051: import java.io.OutputStream;
052: import java.net.URI;
053: import java.net.URL;
054: import java.util.HashMap;
055: import java.util.Map;
056: import java.util.Properties;
057: import java.util.zip.CRC32;
058: import java.util.zip.Checksum;
059: import javax.xml.transform.Transformer;
060: import javax.xml.transform.TransformerException;
061: import javax.xml.transform.TransformerFactory;
062: import javax.xml.transform.stream.StreamResult;
063: import javax.xml.transform.stream.StreamSource;
064: import org.netbeans.api.project.ProjectManager;
065: import org.netbeans.modules.ruby.modules.project.rake.UserQuestionHandler;
066: import org.openide.ErrorManager;
067: import org.openide.filesystems.FileLock;
068: import org.openide.filesystems.FileObject;
069: import org.openide.filesystems.FileSystem;
070: import org.openide.filesystems.FileUtil;
071: import org.openide.util.Mutex;
072: import org.openide.util.MutexException;
073: import org.openide.util.NbBundle;
074: import org.openide.util.UserQuestionException;
075: import org.openide.util.Utilities;
076:
077: /**
078: * Helps a project type (re-)generate, and manage the state and versioning of,
079: * <code>build.xml</code> and <code>build-impl.xml</code>.
080: * @author Jesse Glick
081: */
082: public final class GeneratedFilesHelper {
083:
084: /**
085: * Relative path from project directory to the user build script,
086: * <code>build.xml</code>.
087: */
088: public static final String BUILD_XML_PATH = "build.xml"; // NOI18N
089:
090: /**
091: * Relative path from project directory to the implementation build script,
092: * <code>build-impl.xml</code>.
093: */
094: public static final String BUILD_IMPL_XML_PATH = "nbproject/build-impl.xml"; // NOI18N
095:
096: /**
097: * Path to file storing information about generated files.
098: * It should be kept in version control, since it applies equally after a fresh
099: * project checkout; it does not apply to running Ant, so should not be in
100: * <code>project.properties</code>; and it includes a CRC of <code>project.xml</code>
101: * so it cannot be in that file either. It could be stored in some special
102: * comment at the end of the build script (e.g.) but many users would just
103: * compulsively delete it in this case since it looks weird.
104: */
105: static final String GENFILES_PROPERTIES_PATH = "nbproject/genfiles.properties"; // NOI18N
106:
107: /** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for <code>project.xml</code> CRC. */
108: private static final String KEY_SUFFIX_DATA_CRC = ".data.CRC32"; // NOI18N
109:
110: /** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for stylesheet CRC. */
111: private static final String KEY_SUFFIX_STYLESHEET_CRC = ".stylesheet.CRC32"; // NOI18N
112:
113: /** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for CRC of the script itself. */
114: private static final String KEY_SUFFIX_SCRIPT_CRC = ".script.CRC32"; // NOI18N
115:
116: /**
117: * A build script is missing from disk.
118: * This is mutually exclusive with the other flags.
119: * @see #getBuildScriptState
120: */
121: public static final int FLAG_MISSING = 2 << 0;
122:
123: /**
124: * A build script has been modified since last generated by
125: * {@link #generateBuildScriptFromStylesheet}.
126: * <p class="nonnormative">
127: * Probably this means it was edited by the user.
128: * </p>
129: * @see #getBuildScriptState
130: */
131: public static final int FLAG_MODIFIED = 2 << 1;
132:
133: /**
134: * A build script was generated from an older version of <code>project.xml</code>.
135: * It was last generated using a different version of <code>project.xml</code>,
136: * so using the current <code>project.xml</code> might produce a different
137: * result.
138: * <p class="nonnormative">
139: * This is quite likely in the case of
140: * <code>build.xml</code>; in the case of <code>build-impl.xml</code>, it
141: * probably means that the user edited <code>project.xml</code> manually,
142: * since if that were modified from {@link RakeProjectHelper} methods and
143: * the project were saved, the script would have been regenerated
144: * already.
145: * </p>
146: * @see #getBuildScriptState
147: */
148: public static final int FLAG_OLD_PROJECT_XML = 2 << 2;
149:
150: /**
151: * A build script was generated from an older version of a stylesheet.
152: * It was last generated using a different (probably older) version of the
153: * XSLT stylesheet, so using the current stylesheet might produce a different
154: * result.
155: * <p class="nonnormative">
156: * Probably this means the project type
157: * provider module has been upgraded since the project was last saved (in
158: * the case of <code>build-impl.xml</code>) or created (in the case of
159: * <code>build.xml</code>).
160: * </p>
161: * @see #getBuildScriptState
162: */
163: public static final int FLAG_OLD_STYLESHEET = 2 << 3;
164:
165: /**
166: * The build script exists, but nothing else is known about it.
167: * This flag is mutually exclusive with {@link #FLAG_MISSING} but
168: * when set also sets {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_STYLESHEET},
169: * and {@link #FLAG_OLD_PROJECT_XML} - since it is not known whether these
170: * conditions might obtain, it is safest to assume they do.
171: * <p class="nonnormative">
172: * Probably this means that <code>nbproject/genfiles.properties</code> was
173: * deleted by the user.
174: * </p>
175: * @see #getBuildScriptState
176: */
177: public static final int FLAG_UNKNOWN = 2 << 4;
178:
179: /** Associated project helper, or null if using only a directory. */
180: private final RakeProjectHelper h;
181:
182: /** Project directory. */
183: private final FileObject dir;
184:
185: /**
186: * Create a helper based on the supplied project helper handle.
187: * @param h an Ant-based project helper supplied to the project type provider
188: */
189: public GeneratedFilesHelper(RakeProjectHelper h) {
190: this .h = h;
191: dir = h.getProjectDirectory();
192: }
193:
194: /**
195: * Create a helper based only on a project directory.
196: * This can be used to perform the basic refresh functionality
197: * without being the owner of the project.
198: * It is only intended for use from the offline Ant task to
199: * refresh a project, and similar special situations.
200: * For normal circumstances please use only
201: * {@link GeneratedFilesHelper#GeneratedFilesHelper(RakeProjectHelper)}.
202: * @param d the project directory
203: * @throws IllegalArgumentException if the supplied directory has no <code>project.xml</code>
204: */
205: public GeneratedFilesHelper(FileObject d) {
206: if (d == null
207: || !d.isFolder()
208: || d.getFileObject(RakeProjectHelper.PROJECT_XML_PATH) == null) {
209: throw new IllegalArgumentException(
210: "Does not look like an Ant-based project: " + d); // NOI18N
211: }
212: h = null;
213: dir = d;
214: }
215:
216: /**
217: * Create <code>build.xml</code> or <code>nbproject/build-impl.xml</code>
218: * from <code>project.xml</code> plus a supplied XSLT stylesheet.
219: * This is the recommended way to create the build scripts from
220: * project metadata.
221: * <p class="nonnormative">
222: * You may wish to first check {@link #getBuildScriptState} to decide whether
223: * you really want to overwrite an existing build script. Typically if you
224: * find {@link #FLAG_MODIFIED} you should not overwrite it; or ask for confirmation
225: * first; or make a backup. Of course if you find neither of {@link #FLAG_OLD_STYLESHEET}
226: * nor {@link #FLAG_OLD_PROJECT_XML} then there is no reason to overwrite the
227: * script to begin with.
228: * </p>
229: * <p>
230: * Acquires write access.
231: * </p>
232: * @param path a project-relative file path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
233: * @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
234: * as input and producing the build script as output
235: * @throws IOException if transforming or writing the output failed
236: * @throws IllegalStateException if the project was modified (and not being saved)
237: */
238: public void generateBuildScriptFromStylesheet(final String path,
239: final URL stylesheet) throws IOException,
240: IllegalStateException {
241: if (path == null) {
242: throw new IllegalArgumentException("Null path"); // NOI18N
243: }
244: if (stylesheet == null) {
245: throw new IllegalArgumentException("Null stylesheet"); // NOI18N
246: }
247: try {
248: ProjectManager.mutex().writeAccess(
249: new Mutex.ExceptionAction<Void>() {
250: public Void run() throws IOException {
251: if (h != null && h.isProjectXmlModified()) {
252: throw new IllegalStateException(
253: "Cannot generate build scripts from a modified project"); // NOI18N
254: }
255: // Need to use an atomic action since otherwise creating new build scripts might
256: // cause them to not be recognized as Ant scripts.
257: dir.getFileSystem().runAtomicAction(
258: new FileSystem.AtomicAction() {
259: public void run()
260: throws IOException {
261: FileObject projectXml = dir
262: .getFileObject(RakeProjectHelper.PROJECT_XML_PATH);
263: final FileObject buildScriptXml = FileUtil
264: .createData(dir,
265: path);
266: byte[] projectXmlData;
267: InputStream is = projectXml
268: .getInputStream();
269: try {
270: projectXmlData = load(is);
271: } finally {
272: is.close();
273: }
274: byte[] stylesheetData;
275: is = stylesheet
276: .openStream();
277: try {
278: stylesheetData = load(is);
279: } finally {
280: is.close();
281: }
282: final byte[] resultData;
283: TransformerFactory tf = TransformerFactory
284: .newInstance();
285: try {
286: StreamSource stylesheetSource = new StreamSource(
287: new ByteArrayInputStream(
288: stylesheetData),
289: stylesheet
290: .toExternalForm());
291: Transformer t = tf
292: .newTransformer(stylesheetSource);
293: File projectXmlF = FileUtil
294: .toFile(projectXml);
295: assert projectXmlF != null;
296: StreamSource projectXmlSource = new StreamSource(
297: new ByteArrayInputStream(
298: projectXmlData),
299: projectXmlF
300: .toURI()
301: .toString());
302: ByteArrayOutputStream result = new ByteArrayOutputStream();
303: t
304: .transform(
305: projectXmlSource,
306: new StreamResult(
307: result));
308: resultData = result
309: .toByteArray();
310: } catch (TransformerException e) {
311: throw (IOException) new IOException(
312: e.toString())
313: .initCause(e);
314: }
315: // Update genfiles.properties too.
316: final EditableProperties p = new EditableProperties(
317: true);
318: FileObject genfiles = dir
319: .getFileObject(GENFILES_PROPERTIES_PATH);
320: if (genfiles != null
321: && genfiles
322: .isVirtual()) {
323: // #55164: deleted from CVS?
324: genfiles = null;
325: }
326: if (genfiles != null) {
327: is = genfiles
328: .getInputStream();
329: try {
330: p.load(is);
331: } finally {
332: is.close();
333: }
334: }
335: p
336: .setProperty(
337: path
338: + KEY_SUFFIX_DATA_CRC,
339: getCrc32(
340: new ByteArrayInputStream(
341: projectXmlData),
342: projectXml));
343: if (genfiles == null) {
344: // New file, set a comment on it. XXX this puts comment in middle if write build-impl.xml before build.xml
345: p
346: .setComment(
347: path
348: + KEY_SUFFIX_DATA_CRC,
349: new String[] {
350: "# "
351: + NbBundle
352: .getMessage(
353: GeneratedFilesHelper.class,
354: "COMMENT_genfiles.properties_1"), // NOI18N
355: "# "
356: + NbBundle
357: .getMessage(
358: GeneratedFilesHelper.class,
359: "COMMENT_genfiles.properties_2"), // NOI18N
360: },
361: false);
362: }
363: p
364: .setProperty(
365: path
366: + KEY_SUFFIX_STYLESHEET_CRC,
367: getCrc32(
368: new ByteArrayInputStream(
369: stylesheetData),
370: stylesheet));
371: p
372: .setProperty(
373: path
374: + KEY_SUFFIX_SCRIPT_CRC,
375: computeCrc32(new ByteArrayInputStream(
376: resultData)));
377: if (genfiles == null) {
378: genfiles = FileUtil
379: .createData(
380: dir,
381: GENFILES_PROPERTIES_PATH);
382: }
383: final FileObject _genfiles = genfiles;
384: // You get the Spaghetti Code Award if you can follow the control flow in this method!
385: // Now is the time when you wish Java implemented call/cc.
386: // If you didn't understand that last comment, you don't get the Spaghetti Code Award.
387: final FileSystem.AtomicAction body = new FileSystem.AtomicAction() {
388: public void run()
389: throws IOException {
390: // Try to acquire both locks together, since we need them both written.
391: FileLock lock1 = buildScriptXml
392: .lock();
393: try {
394: FileLock lock2 = _genfiles
395: .lock();
396: try {
397: OutputStream os1 = new EolFilterOutputStream(
398: buildScriptXml
399: .getOutputStream(lock1));
400: try {
401: OutputStream os2 = _genfiles
402: .getOutputStream(lock2);
403: try {
404: os1
405: .write(resultData);
406: p
407: .store(os2);
408: } finally {
409: os2
410: .close();
411: }
412: } finally {
413: os1
414: .close();
415: }
416: } finally {
417: lock2
418: .releaseLock();
419: }
420: } finally {
421: lock1
422: .releaseLock();
423: }
424: }
425: };
426: try {
427: body.run();
428: } catch (UserQuestionException uqe) {
429: // #57480: need to regenerate build-impl.xml, really.
430: UserQuestionHandler
431: .handle(
432: uqe,
433: new UserQuestionHandler.Callback() {
434: public void accepted() {
435: // Try again.
436: try {
437: body
438: .run();
439: } catch (UserQuestionException uqe2) {
440: // Need to try one more time - may have locked bSX but not yet gf.
441: UserQuestionHandler
442: .handle(
443: uqe2,
444: new UserQuestionHandler.Callback() {
445: public void accepted() {
446: try {
447: body
448: .run();
449: } catch (IOException e) {
450: ErrorManager
451: .getDefault()
452: .notify(
453: e);
454: }
455: }
456:
457: public void denied() {
458: }
459:
460: public void error(
461: IOException e) {
462: ErrorManager
463: .getDefault()
464: .notify(
465: e);
466: }
467: });
468: } catch (IOException e) {
469: // Oh well.
470: ErrorManager
471: .getDefault()
472: .notify(
473: e);
474: }
475: }
476:
477: public void denied() {
478: // OK, skip it.
479: }
480:
481: public void error(
482: IOException e) {
483: ErrorManager
484: .getDefault()
485: .notify(
486: e);
487: // Never mind.
488: }
489: });
490: }
491: }
492: });
493: return null;
494: }
495: });
496: } catch (MutexException e) {
497: throw (IOException) e.getException();
498: }
499: }
500:
501: /**
502: * Load data from a stream into a buffer.
503: */
504: private static byte[] load(InputStream is) throws IOException {
505: int size = Math.max(1024, is.available()); // #46235
506: ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
507: byte[] buf = new byte[size];
508: int read;
509: while ((read = is.read(buf)) != -1) {
510: baos.write(buf, 0, read);
511: }
512: return baos.toByteArray();
513: }
514:
515: /**
516: * Find what state a build script is in.
517: * This may be used by a project type provider to decide whether to create
518: * or overwrite it, and whether to produce a backup in the latter case.
519: * Various abnormal conditions are detected:
520: * {@link #FLAG_MISSING}, {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_PROJECT_XML},
521: * {@link #FLAG_OLD_STYLESHEET}, and {@link #FLAG_UNKNOWN}.
522: * <p class="nonnormative">
523: * Currently {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_STYLESHEET}, and
524: * {@link #FLAG_OLD_PROJECT_XML} are detected by computing a CRC-32
525: * of the script when it is created, as well as the CRC-32s of the
526: * stylesheet and <code>project.xml</code>. These CRCs are stored
527: * in a special file <code>nbproject/genfiles.properties</code>.
528: * The CRCs are based on the textual
529: * contents of the files (so even changed to whitespace etc. are considered
530: * changes), but are independent of platform newline conventions (since e.g.
531: * CVS will by default replace \n with \r\n when checking out on Windows).
532: * Changes to external files included into <code>project.xml</code> or the
533: * stylesheet (e.g. using XSLT's import facility) are <em>not</em> detected.
534: * </p>
535: * <p>
536: * If there is some kind of I/O error reading any files, {@link #FLAG_UNKNOWN}
537: * is returned (in conjunction with {@link #FLAG_MODIFIED},
538: * {@link #FLAG_OLD_STYLESHEET}, and {@link #FLAG_OLD_PROJECT_XML} to be safe).
539: * </p>
540: * <p>
541: * Acquires read access.
542: * </p>
543: * @param path a project-relative path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
544: * @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
545: * as input and producing the build script as output
546: * (should match that given to {@link #generateBuildScriptFromStylesheet})
547: * @return a bitwise OR of various flags, or <code>0</code> if the script
548: * is present on disk and fully up-to-date
549: * @throws IllegalStateException if the project was modified
550: */
551: public int getBuildScriptState(final String path,
552: final URL stylesheet) throws IllegalStateException {
553: try {
554: return ProjectManager.mutex().readAccess(
555: new Mutex.ExceptionAction<Integer>() {
556: public Integer run() throws IOException {
557: if (h != null && h.isProjectXmlModified()) {
558: throw new IllegalStateException(
559: "Cannot generate build scripts from a modified project"); // NOI18N
560: }
561: FileObject script = dir.getFileObject(path);
562: if (script == null
563: || /* #55164 */script.isVirtual()) {
564: return FLAG_MISSING;
565: }
566: int flags = 0;
567: Properties p = new Properties();
568: FileObject genfiles = dir
569: .getFileObject(GENFILES_PROPERTIES_PATH);
570: if (genfiles == null
571: || /* #55164 */genfiles
572: .isVirtual()) {
573: // Who knows? User deleted it; anything might be wrong. Safest to assume
574: // that everything is.
575: return FLAG_UNKNOWN | FLAG_MODIFIED
576: | FLAG_OLD_PROJECT_XML
577: | FLAG_OLD_STYLESHEET;
578: }
579: InputStream is = new BufferedInputStream(
580: genfiles.getInputStream());
581: try {
582: p.load(is);
583: } finally {
584: is.close();
585: }
586: FileObject projectXml = dir
587: .getFileObject(RakeProjectHelper.PROJECT_XML_PATH);
588: if (projectXml != null
589: && /* #55164 */!projectXml
590: .isVirtual()) {
591: String crc = getCrc32(projectXml);
592: if (!crc.equals(p.getProperty(path
593: + KEY_SUFFIX_DATA_CRC))) {
594: flags |= FLAG_OLD_PROJECT_XML;
595: }
596: } else {
597: // Broken project?!
598: flags |= FLAG_OLD_PROJECT_XML;
599: }
600: String crc = getCrc32(stylesheet);
601: if (!crc.equals(p.getProperty(path
602: + KEY_SUFFIX_STYLESHEET_CRC))) {
603: flags |= FLAG_OLD_STYLESHEET;
604: }
605: crc = getCrc32(script);
606: if (!crc.equals(p.getProperty(path
607: + KEY_SUFFIX_SCRIPT_CRC))) {
608: flags |= FLAG_MODIFIED;
609: }
610: return flags;
611: }
612: });
613: } catch (MutexException e) {
614: ErrorManager.getDefault().notify(
615: ErrorManager.INFORMATIONAL,
616: (IOException) e.getException());
617: return FLAG_UNKNOWN | FLAG_MODIFIED | FLAG_OLD_PROJECT_XML
618: | FLAG_OLD_STYLESHEET;
619: }
620: }
621:
622: /**
623: * Compute the CRC-32 of the contents of a stream.
624: * \r\n and \r are both normalized to \n for purposes of the calculation.
625: */
626: static String computeCrc32(InputStream is) throws IOException {
627: Checksum crc = new CRC32();
628: int last = -1;
629: int curr;
630: while ((curr = is.read()) != -1) {
631: if (curr != '\n' && last == '\r') {
632: crc.update('\n');
633: }
634: if (curr != '\r') {
635: crc.update(curr);
636: }
637: last = curr;
638: }
639: if (last == '\r') {
640: crc.update('\n');
641: }
642: int val = (int) crc.getValue();
643: String hex = Integer.toHexString(val);
644: while (hex.length() < 8) {
645: hex = "0" + hex; // NOI18N
646: }
647: return hex;
648: }
649:
650: // #50440 - cache CRC32's for various files to save time esp. during startup.
651:
652: private static final Map<URL, String> crcCache = new HashMap<URL, String>();
653: private static final Map<URL, Long> crcCacheTimestampsXorSizes = new HashMap<URL, Long>();
654:
655: /** Try to find a CRC in the cache according to location of file and last mod time xor size. */
656: private static synchronized String findCachedCrc32(URL u,
657: long footprint) {
658: String crc = crcCache.get(u);
659: if (crc != null) {
660: Long l = crcCacheTimestampsXorSizes.get(u);
661: assert l != null;
662: if (l == footprint) {
663: // Cache hit.
664: return crc;
665: }
666: }
667: // Cache miss - missing or old.
668: return null;
669: }
670:
671: /** Cache a known CRC for a file, using current last mod time xor size. */
672: private static synchronized void cacheCrc32(String crc, URL u,
673: long footprint) {
674: crcCache.put(u, crc);
675: crcCacheTimestampsXorSizes.put(u, footprint);
676: }
677:
678: /** Find (maybe cached) CRC for a file, using a preexisting input stream (not closed by this method). */
679: private static String getCrc32(InputStream is, FileObject fo)
680: throws IOException {
681: URL u = fo.getURL();
682: fo.refresh(); // in case was written on disk and we did not notice yet...
683: long footprint = fo.lastModified().getTime() ^ fo.getSize();
684: String crc = findCachedCrc32(u, footprint);
685: if (crc == null) {
686: crc = computeCrc32(is);
687: cacheCrc32(crc, u, footprint);
688: }
689: return crc;
690: }
691:
692: /** Find the time the file this URL represents was last modified xor its size, if possible. */
693: private static long checkFootprint(URL u) {
694: URL nested = FileUtil.getArchiveFile(u);
695: if (nested != null) {
696: u = nested;
697: }
698: if (u.getProtocol().equals("file")) { // NOI18N
699: File f = new File(URI.create(u.toExternalForm()));
700: return f.lastModified() ^ f.length();
701: } else {
702: return 0L;
703: }
704: }
705:
706: /** Find (maybe cached) CRC for a URL, using a preexisting input stream (not closed by this method). */
707: private static String getCrc32(InputStream is, URL u)
708: throws IOException {
709: long footprint = checkFootprint(u);
710: String crc = null;
711: if (footprint != 0L) {
712: crc = findCachedCrc32(u, footprint);
713: }
714: if (crc == null) {
715: crc = computeCrc32(is);
716: if (footprint != 0L) {
717: cacheCrc32(crc, u, footprint);
718: }
719: }
720: return crc;
721: }
722:
723: /** Find (maybe cached) CRC for a file. Will open its own input stream. */
724: private static String getCrc32(FileObject fo) throws IOException {
725: URL u = fo.getURL();
726: fo.refresh();
727: long footprint = fo.lastModified().getTime() ^ fo.getSize();
728: String crc = findCachedCrc32(u, footprint);
729: if (crc == null) {
730: InputStream is = fo.getInputStream();
731: try {
732: crc = computeCrc32(new BufferedInputStream(is));
733: cacheCrc32(crc, u, footprint);
734: } finally {
735: is.close();
736: }
737: }
738: return crc;
739: }
740:
741: /** Find (maybe cached) CRC for a URL. Will open its own input stream. */
742: private static String getCrc32(URL u) throws IOException {
743: long footprint = checkFootprint(u);
744: String crc = null;
745: if (footprint != 0L) {
746: crc = findCachedCrc32(u, footprint);
747: }
748: if (crc == null) {
749: InputStream is = u.openStream();
750: try {
751: crc = computeCrc32(new BufferedInputStream(is));
752: if (footprint != 0L) {
753: cacheCrc32(crc, u, footprint);
754: }
755: } finally {
756: is.close();
757: }
758: }
759: return crc;
760: }
761:
762: /**
763: * Convenience method to refresh a build script if it can and should be.
764: * <p>
765: * If the script is not modified, and it is either missing, or the flag
766: * <code>checkForProjectXmlModified</code> is false, or it is out of date with
767: * respect to either <code>project.xml</code> or the stylesheet (or both),
768: * it is (re-)generated.
769: * </p>
770: * <p>
771: * Acquires write access.
772: * </p>
773: * <p class="nonnormative">
774: * Typical usage from {@link ProjectXmlSavedHook#projectXmlSaved} is to call
775: * this method for both {@link #BUILD_XML_PATH} and {@link #BUILD_IMPL_XML_PATH}
776: * with the appropriate stylesheets and with <code>checkForProjectXmlModified</code>
777: * false (the script is certainly out of date relative to <code>project.xml</code>).
778: * Typical usage from {@link org.netbeans.spi.project.ui.ProjectOpenedHook#projectOpened} is to call
779: * this method for both scripts with the appropriate stylesheets and with
780: * <code>checkForProjectXmlModified</code> true.
781: * </p>
782: * @param path a project-relative path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
783: * @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
784: * as input and producing the build script as output
785: * @param checkForProjectXmlModified true if it is necessary to check whether the
786: * script is out of date with respect to
787: * <code>project.xml</code> and/or the stylesheet
788: * @return true if the script was in fact regenerated
789: * @throws IOException if transforming or writing the output failed
790: * @throws IllegalStateException if the project was modified
791: */
792: public boolean refreshBuildScript(final String path,
793: final URL stylesheet,
794: final boolean checkForProjectXmlModified)
795: throws IOException, IllegalStateException {
796: try {
797: return ProjectManager.mutex().writeAccess(
798: new Mutex.ExceptionAction<Boolean>() {
799: public Boolean run() throws IOException {
800: int flags = getBuildScriptState(path,
801: stylesheet);
802: if (shouldGenerateBuildScript(flags,
803: checkForProjectXmlModified)) {
804: generateBuildScriptFromStylesheet(path,
805: stylesheet);
806: return true;
807: } else {
808: return false;
809: }
810: }
811: });
812: } catch (MutexException e) {
813: throw (IOException) e.getException();
814: }
815: }
816:
817: private static boolean shouldGenerateBuildScript(int flags,
818: boolean checkForProjectXmlModified) {
819: if ((flags & GeneratedFilesHelper.FLAG_MISSING) != 0) {
820: // Yes, need it.
821: return true;
822: }
823: if ((flags & GeneratedFilesHelper.FLAG_MODIFIED) != 0) {
824: // No, don't overwrite a user build script.
825: // XXX modified build-impl.xml probably counts as a serious condition
826: // to warn the user about...
827: // Modified build.xml is no big deal.
828: return false;
829: }
830: if (!checkForProjectXmlModified) {
831: // OK, assume it is out of date.
832: return true;
833: }
834: // Check whether it is in fact out of date.
835: return (flags & (GeneratedFilesHelper.FLAG_OLD_PROJECT_XML | GeneratedFilesHelper.FLAG_OLD_STYLESHEET)) != 0;
836: }
837:
838: // #45373 - workaround: on Windows make sure that all lines end with CRLF.
839: // marcow: Use at least some buffered output!
840: private static class EolFilterOutputStream extends
841: BufferedOutputStream {
842:
843: private boolean isActive = Utilities.isWindows();
844: private int last = -1;
845:
846: public EolFilterOutputStream(OutputStream os) {
847: super (os, 4096);
848: }
849:
850: public void write(byte[] b, int off, int len)
851: throws IOException {
852: if (isActive) {
853: for (int i = off; i < off + len; i++) {
854: write(b[i]);
855: }
856: } else {
857: super .write(b, off, len);
858: }
859: }
860:
861: public void write(int b) throws IOException {
862: if (isActive) {
863: if (b == '\n' && last != '\r') {
864: super .write('\r');
865: }
866: last = b;
867: }
868: super.write(b);
869: }
870:
871: }
872: }
|