/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.ruby.spi.project.support.rake;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.modules.ruby.modules.project.rake.UserQuestionHandler;
import org.openide.ErrorManager;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.util.EditableProperties;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.openide.util.NbBundle;
import org.openide.util.UserQuestionException;
import org.openide.util.Utilities;
/**
* Helps a project type (re-)generate, and manage the state and versioning of,
* <code>build.xml</code> and <code>build-impl.xml</code>.
* @author Jesse Glick
*/
public final class GeneratedFilesHelper {
/**
* Relative path from project directory to the user build script,
* <code>build.xml</code>.
*/
public static final String BUILD_XML_PATH = "build.xml"; // NOI18N
/**
* Relative path from project directory to the implementation build script,
* <code>build-impl.xml</code>.
*/
public static final String BUILD_IMPL_XML_PATH = "nbproject/build-impl.xml"; // NOI18N
/**
* Path to file storing information about generated files.
* It should be kept in version control, since it applies equally after a fresh
* project checkout; it does not apply to running Ant, so should not be in
* <code>project.properties</code>; and it includes a CRC of <code>project.xml</code>
* so it cannot be in that file either. It could be stored in some special
* comment at the end of the build script (e.g.) but many users would just
* compulsively delete it in this case since it looks weird.
*/
static final String GENFILES_PROPERTIES_PATH = "nbproject/genfiles.properties"; // NOI18N
/** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for <code>project.xml</code> CRC. */
private static final String KEY_SUFFIX_DATA_CRC = ".data.CRC32"; // NOI18N
/** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for stylesheet CRC. */
private static final String KEY_SUFFIX_STYLESHEET_CRC = ".stylesheet.CRC32"; // NOI18N
/** Suffix (after script path) of {@link #GENFILES_PROPERTIES_PATH} key for CRC of the script itself. */
private static final String KEY_SUFFIX_SCRIPT_CRC = ".script.CRC32"; // NOI18N
/**
* A build script is missing from disk.
* This is mutually exclusive with the other flags.
* @see #getBuildScriptState
*/
public static final int FLAG_MISSING = 2 << 0;
/**
* A build script has been modified since last generated by
* {@link #generateBuildScriptFromStylesheet}.
* <p class="nonnormative">
* Probably this means it was edited by the user.
* </p>
* @see #getBuildScriptState
*/
public static final int FLAG_MODIFIED = 2 << 1;
/**
* A build script was generated from an older version of <code>project.xml</code>.
* It was last generated using a different version of <code>project.xml</code>,
* so using the current <code>project.xml</code> might produce a different
* result.
* <p class="nonnormative">
* This is quite likely in the case of
* <code>build.xml</code>; in the case of <code>build-impl.xml</code>, it
* probably means that the user edited <code>project.xml</code> manually,
* since if that were modified from {@link RakeProjectHelper} methods and
* the project were saved, the script would have been regenerated
* already.
* </p>
* @see #getBuildScriptState
*/
public static final int FLAG_OLD_PROJECT_XML = 2 << 2;
/**
* A build script was generated from an older version of a stylesheet.
* It was last generated using a different (probably older) version of the
* XSLT stylesheet, so using the current stylesheet might produce a different
* result.
* <p class="nonnormative">
* Probably this means the project type
* provider module has been upgraded since the project was last saved (in
* the case of <code>build-impl.xml</code>) or created (in the case of
* <code>build.xml</code>).
* </p>
* @see #getBuildScriptState
*/
public static final int FLAG_OLD_STYLESHEET = 2 << 3;
/**
* The build script exists, but nothing else is known about it.
* This flag is mutually exclusive with {@link #FLAG_MISSING} but
* when set also sets {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_STYLESHEET},
* and {@link #FLAG_OLD_PROJECT_XML} - since it is not known whether these
* conditions might obtain, it is safest to assume they do.
* <p class="nonnormative">
* Probably this means that <code>nbproject/genfiles.properties</code> was
* deleted by the user.
* </p>
* @see #getBuildScriptState
*/
public static final int FLAG_UNKNOWN = 2 << 4;
/** Associated project helper, or null if using only a directory. */
private final RakeProjectHelper h;
/** Project directory. */
private final FileObject dir;
/**
* Create a helper based on the supplied project helper handle.
* @param h an Ant-based project helper supplied to the project type provider
*/
public GeneratedFilesHelper(RakeProjectHelper h) {
this.h = h;
dir = h.getProjectDirectory();
}
/**
* Create a helper based only on a project directory.
* This can be used to perform the basic refresh functionality
* without being the owner of the project.
* It is only intended for use from the offline Ant task to
* refresh a project, and similar special situations.
* For normal circumstances please use only
* {@link GeneratedFilesHelper#GeneratedFilesHelper(RakeProjectHelper)}.
* @param d the project directory
* @throws IllegalArgumentException if the supplied directory has no <code>project.xml</code>
*/
public GeneratedFilesHelper(FileObject d) {
if (d == null || !d.isFolder() || d.getFileObject(RakeProjectHelper.PROJECT_XML_PATH) == null) {
throw new IllegalArgumentException("Does not look like an Ant-based project: " + d); // NOI18N
}
h = null;
dir = d;
}
/**
* Create <code>build.xml</code> or <code>nbproject/build-impl.xml</code>
* from <code>project.xml</code> plus a supplied XSLT stylesheet.
* This is the recommended way to create the build scripts from
* project metadata.
* <p class="nonnormative">
* You may wish to first check {@link #getBuildScriptState} to decide whether
* you really want to overwrite an existing build script. Typically if you
* find {@link #FLAG_MODIFIED} you should not overwrite it; or ask for confirmation
* first; or make a backup. Of course if you find neither of {@link #FLAG_OLD_STYLESHEET}
* nor {@link #FLAG_OLD_PROJECT_XML} then there is no reason to overwrite the
* script to begin with.
* </p>
* <p>
* Acquires write access.
* </p>
* @param path a project-relative file path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
* @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
* as input and producing the build script as output
* @throws IOException if transforming or writing the output failed
* @throws IllegalStateException if the project was modified (and not being saved)
*/
public void generateBuildScriptFromStylesheet(final String path, final URL stylesheet) throws IOException, IllegalStateException {
if (path == null) {
throw new IllegalArgumentException("Null path"); // NOI18N
}
if (stylesheet == null) {
throw new IllegalArgumentException("Null stylesheet"); // NOI18N
}
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
public Void run() throws IOException {
if (h != null && h.isProjectXmlModified()) {
throw new IllegalStateException("Cannot generate build scripts from a modified project"); // NOI18N
}
// Need to use an atomic action since otherwise creating new build scripts might
// cause them to not be recognized as Ant scripts.
dir.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
public void run() throws IOException {
FileObject projectXml = dir.getFileObject(RakeProjectHelper.PROJECT_XML_PATH);
final FileObject buildScriptXml = FileUtil.createData(dir, path);
byte[] projectXmlData;
InputStream is = projectXml.getInputStream();
try {
projectXmlData = load(is);
} finally {
is.close();
}
byte[] stylesheetData;
is = stylesheet.openStream();
try {
stylesheetData = load(is);
} finally {
is.close();
}
final byte[] resultData;
TransformerFactory tf = TransformerFactory.newInstance();
try {
StreamSource stylesheetSource = new StreamSource(
new ByteArrayInputStream(stylesheetData), stylesheet.toExternalForm());
Transformer t = tf.newTransformer(stylesheetSource);
File projectXmlF = FileUtil.toFile(projectXml);
assert projectXmlF != null;
StreamSource projectXmlSource = new StreamSource(
new ByteArrayInputStream(projectXmlData), projectXmlF.toURI().toString());
ByteArrayOutputStream result = new ByteArrayOutputStream();
t.transform(projectXmlSource, new StreamResult(result));
resultData = result.toByteArray();
} catch (TransformerException e) {
throw (IOException)new IOException(e.toString()).initCause(e);
}
// Update genfiles.properties too.
final EditableProperties p = new EditableProperties(true);
FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH);
if (genfiles != null && genfiles.isVirtual()) {
// #55164: deleted from CVS?
genfiles = null;
}
if (genfiles != null) {
is = genfiles.getInputStream();
try {
p.load(is);
} finally {
is.close();
}
}
p.setProperty(path + KEY_SUFFIX_DATA_CRC,
getCrc32(new ByteArrayInputStream(projectXmlData), projectXml));
if (genfiles == null) {
// New file, set a comment on it. XXX this puts comment in middle if write build-impl.xml before build.xml
p.setComment(path + KEY_SUFFIX_DATA_CRC, new String[] {
"# " + NbBundle.getMessage(GeneratedFilesHelper.class, "COMMENT_genfiles.properties_1"), // NOI18N
"# " + NbBundle.getMessage(GeneratedFilesHelper.class, "COMMENT_genfiles.properties_2"), // NOI18N
}, false);
}
p.setProperty(path + KEY_SUFFIX_STYLESHEET_CRC,
getCrc32(new ByteArrayInputStream(stylesheetData), stylesheet));
p.setProperty(path + KEY_SUFFIX_SCRIPT_CRC,
computeCrc32(new ByteArrayInputStream(resultData)));
if (genfiles == null) {
genfiles = FileUtil.createData(dir, GENFILES_PROPERTIES_PATH);
}
final FileObject _genfiles = genfiles;
// You get the Spaghetti Code Award if you can follow the control flow in this method!
// Now is the time when you wish Java implemented call/cc.
// If you didn't understand that last comment, you don't get the Spaghetti Code Award.
final FileSystem.AtomicAction body = new FileSystem.AtomicAction() {
public void run() throws IOException {
// Try to acquire both locks together, since we need them both written.
FileLock lock1 = buildScriptXml.lock();
try {
FileLock lock2 = _genfiles.lock();
try {
OutputStream os1 = new EolFilterOutputStream(buildScriptXml.getOutputStream(lock1));
try {
OutputStream os2 = _genfiles.getOutputStream(lock2);
try {
os1.write(resultData);
p.store(os2);
} finally {
os2.close();
}
} finally {
os1.close();
}
} finally {
lock2.releaseLock();
}
} finally {
lock1.releaseLock();
}
}
};
try {
body.run();
} catch (UserQuestionException uqe) {
// #57480: need to regenerate build-impl.xml, really.
UserQuestionHandler.handle(uqe, new UserQuestionHandler.Callback() {
public void accepted() {
// Try again.
try {
body.run();
} catch (UserQuestionException uqe2) {
// Need to try one more time - may have locked bSX but not yet gf.
UserQuestionHandler.handle(uqe2, new UserQuestionHandler.Callback() {
public void accepted() {
try {
body.run();
} catch (IOException e) {
ErrorManager.getDefault().notify(e);
}
}
public void denied() {}
public void error(IOException e) {
ErrorManager.getDefault().notify(e);
}
});
} catch (IOException e) {
// Oh well.
ErrorManager.getDefault().notify(e);
}
}
public void denied() {
// OK, skip it.
}
public void error(IOException e) {
ErrorManager.getDefault().notify(e);
// Never mind.
}
});
}
}
});
return null;
}
});
} catch (MutexException e) {
throw (IOException)e.getException();
}
}
/**
* Load data from a stream into a buffer.
*/
private static byte[] load(InputStream is) throws IOException {
int size = Math.max(1024, is.available()); // #46235
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
byte[] buf = new byte[size];
int read;
while ((read = is.read(buf)) != -1) {
baos.write(buf, 0, read);
}
return baos.toByteArray();
}
/**
* Find what state a build script is in.
* This may be used by a project type provider to decide whether to create
* or overwrite it, and whether to produce a backup in the latter case.
* Various abnormal conditions are detected:
* {@link #FLAG_MISSING}, {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_PROJECT_XML},
* {@link #FLAG_OLD_STYLESHEET}, and {@link #FLAG_UNKNOWN}.
* <p class="nonnormative">
* Currently {@link #FLAG_MODIFIED}, {@link #FLAG_OLD_STYLESHEET}, and
* {@link #FLAG_OLD_PROJECT_XML} are detected by computing a CRC-32
* of the script when it is created, as well as the CRC-32s of the
* stylesheet and <code>project.xml</code>. These CRCs are stored
* in a special file <code>nbproject/genfiles.properties</code>.
* The CRCs are based on the textual
* contents of the files (so even changed to whitespace etc. are considered
* changes), but are independent of platform newline conventions (since e.g.
* CVS will by default replace \n with \r\n when checking out on Windows).
* Changes to external files included into <code>project.xml</code> or the
* stylesheet (e.g. using XSLT's import facility) are <em>not</em> detected.
* </p>
* <p>
* If there is some kind of I/O error reading any files, {@link #FLAG_UNKNOWN}
* is returned (in conjunction with {@link #FLAG_MODIFIED},
* {@link #FLAG_OLD_STYLESHEET}, and {@link #FLAG_OLD_PROJECT_XML} to be safe).
* </p>
* <p>
* Acquires read access.
* </p>
* @param path a project-relative path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
* @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
* as input and producing the build script as output
* (should match that given to {@link #generateBuildScriptFromStylesheet})
* @return a bitwise OR of various flags, or <code>0</code> if the script
* is present on disk and fully up-to-date
* @throws IllegalStateException if the project was modified
*/
public int getBuildScriptState(final String path, final URL stylesheet) throws IllegalStateException {
try {
return ProjectManager.mutex().readAccess(new Mutex.ExceptionAction<Integer>() {
public Integer run() throws IOException {
if (h != null && h.isProjectXmlModified()) {
throw new IllegalStateException("Cannot generate build scripts from a modified project"); // NOI18N
}
FileObject script = dir.getFileObject(path);
if (script == null || /* #55164 */script.isVirtual()) {
return FLAG_MISSING;
}
int flags = 0;
Properties p = new Properties();
FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH);
if (genfiles == null || /* #55164 */genfiles.isVirtual()) {
// Who knows? User deleted it; anything might be wrong. Safest to assume
// that everything is.
return FLAG_UNKNOWN | FLAG_MODIFIED |
FLAG_OLD_PROJECT_XML | FLAG_OLD_STYLESHEET;
}
InputStream is = new BufferedInputStream(genfiles.getInputStream());
try {
p.load(is);
} finally {
is.close();
}
FileObject projectXml = dir.getFileObject(RakeProjectHelper.PROJECT_XML_PATH);
if (projectXml != null && /* #55164 */!projectXml.isVirtual()) {
String crc = getCrc32(projectXml);
if (!crc.equals(p.getProperty(path + KEY_SUFFIX_DATA_CRC))) {
flags |= FLAG_OLD_PROJECT_XML;
}
} else {
// Broken project?!
flags |= FLAG_OLD_PROJECT_XML;
}
String crc = getCrc32(stylesheet);
if (!crc.equals(p.getProperty(path + KEY_SUFFIX_STYLESHEET_CRC))) {
flags |= FLAG_OLD_STYLESHEET;
}
crc = getCrc32(script);
if (!crc.equals(p.getProperty(path + KEY_SUFFIX_SCRIPT_CRC))) {
flags |= FLAG_MODIFIED;
}
return flags;
}
});
} catch (MutexException e) {
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, (IOException)e.getException());
return FLAG_UNKNOWN | FLAG_MODIFIED | FLAG_OLD_PROJECT_XML | FLAG_OLD_STYLESHEET;
}
}
/**
* Compute the CRC-32 of the contents of a stream.
* \r\n and \r are both normalized to \n for purposes of the calculation.
*/
static String computeCrc32(InputStream is) throws IOException {
Checksum crc = new CRC32();
int last = -1;
int curr;
while ((curr = is.read()) != -1) {
if (curr != '\n' && last == '\r') {
crc.update('\n');
}
if (curr != '\r') {
crc.update(curr);
}
last = curr;
}
if (last == '\r') {
crc.update('\n');
}
int val = (int)crc.getValue();
String hex = Integer.toHexString(val);
while (hex.length() < 8) {
hex = "0" + hex; // NOI18N
}
return hex;
}
// #50440 - cache CRC32's for various files to save time esp. during startup.
private static final Map<URL,String> crcCache = new HashMap<URL,String>();
private static final Map<URL,Long> crcCacheTimestampsXorSizes = new HashMap<URL,Long>();
/** Try to find a CRC in the cache according to location of file and last mod time xor size. */
private static synchronized String findCachedCrc32(URL u, long footprint) {
String crc = crcCache.get(u);
if (crc != null) {
Long l = crcCacheTimestampsXorSizes.get(u);
assert l != null;
if (l == footprint) {
// Cache hit.
return crc;
}
}
// Cache miss - missing or old.
return null;
}
/** Cache a known CRC for a file, using current last mod time xor size. */
private static synchronized void cacheCrc32(String crc, URL u, long footprint) {
crcCache.put(u, crc);
crcCacheTimestampsXorSizes.put(u, footprint);
}
/** Find (maybe cached) CRC for a file, using a preexisting input stream (not closed by this method). */
private static String getCrc32(InputStream is, FileObject fo) throws IOException {
URL u = fo.getURL();
fo.refresh(); // in case was written on disk and we did not notice yet...
long footprint = fo.lastModified().getTime() ^ fo.getSize();
String crc = findCachedCrc32(u, footprint);
if (crc == null) {
crc = computeCrc32(is);
cacheCrc32(crc, u, footprint);
}
return crc;
}
/** Find the time the file this URL represents was last modified xor its size, if possible. */
private static long checkFootprint(URL u) {
URL nested = FileUtil.getArchiveFile(u);
if (nested != null) {
u = nested;
}
if (u.getProtocol().equals("file")) { // NOI18N
File f = new File(URI.create(u.toExternalForm()));
return f.lastModified() ^ f.length();
} else {
return 0L;
}
}
/** Find (maybe cached) CRC for a URL, using a preexisting input stream (not closed by this method). */
private static String getCrc32(InputStream is, URL u) throws IOException {
long footprint = checkFootprint(u);
String crc = null;
if (footprint != 0L) {
crc = findCachedCrc32(u, footprint);
}
if (crc == null) {
crc = computeCrc32(is);
if (footprint != 0L) {
cacheCrc32(crc, u, footprint);
}
}
return crc;
}
/** Find (maybe cached) CRC for a file. Will open its own input stream. */
private static String getCrc32(FileObject fo) throws IOException {
URL u = fo.getURL();
fo.refresh();
long footprint = fo.lastModified().getTime() ^ fo.getSize();
String crc = findCachedCrc32(u, footprint);
if (crc == null) {
InputStream is = fo.getInputStream();
try {
crc = computeCrc32(new BufferedInputStream(is));
cacheCrc32(crc, u, footprint);
} finally {
is.close();
}
}
return crc;
}
/** Find (maybe cached) CRC for a URL. Will open its own input stream. */
private static String getCrc32(URL u) throws IOException {
long footprint = checkFootprint(u);
String crc = null;
if (footprint != 0L) {
crc = findCachedCrc32(u, footprint);
}
if (crc == null) {
InputStream is = u.openStream();
try {
crc = computeCrc32(new BufferedInputStream(is));
if (footprint != 0L) {
cacheCrc32(crc, u, footprint);
}
} finally {
is.close();
}
}
return crc;
}
/**
* Convenience method to refresh a build script if it can and should be.
* <p>
* If the script is not modified, and it is either missing, or the flag
* <code>checkForProjectXmlModified</code> is false, or it is out of date with
* respect to either <code>project.xml</code> or the stylesheet (or both),
* it is (re-)generated.
* </p>
* <p>
* Acquires write access.
* </p>
* <p class="nonnormative">
* Typical usage from {@link ProjectXmlSavedHook#projectXmlSaved} is to call
* this method for both {@link #BUILD_XML_PATH} and {@link #BUILD_IMPL_XML_PATH}
* with the appropriate stylesheets and with <code>checkForProjectXmlModified</code>
* false (the script is certainly out of date relative to <code>project.xml</code>).
* Typical usage from {@link org.netbeans.spi.project.ui.ProjectOpenedHook#projectOpened} is to call
* this method for both scripts with the appropriate stylesheets and with
* <code>checkForProjectXmlModified</code> true.
* </p>
* @param path a project-relative path such as {@link #BUILD_XML_PATH} or {@link #BUILD_IMPL_XML_PATH}
* @param stylesheet a URL to an XSLT stylesheet accepting <code>project.xml</code>
* as input and producing the build script as output
* @param checkForProjectXmlModified true if it is necessary to check whether the
* script is out of date with respect to
* <code>project.xml</code> and/or the stylesheet
* @return true if the script was in fact regenerated
* @throws IOException if transforming or writing the output failed
* @throws IllegalStateException if the project was modified
*/
public boolean refreshBuildScript(final String path, final URL stylesheet, final boolean checkForProjectXmlModified) throws IOException, IllegalStateException {
try {
return ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Boolean>() {
public Boolean run() throws IOException {
int flags = getBuildScriptState(path, stylesheet);
if (shouldGenerateBuildScript(flags, checkForProjectXmlModified)) {
generateBuildScriptFromStylesheet(path, stylesheet);
return true;
} else {
return false;
}
}
});
} catch (MutexException e) {
throw (IOException)e.getException();
}
}
private static boolean shouldGenerateBuildScript(int flags, boolean checkForProjectXmlModified) {
if ((flags & GeneratedFilesHelper.FLAG_MISSING) != 0) {
// Yes, need it.
return true;
}
if ((flags & GeneratedFilesHelper.FLAG_MODIFIED) != 0) {
// No, don't overwrite a user build script.
// XXX modified build-impl.xml probably counts as a serious condition
// to warn the user about...
// Modified build.xml is no big deal.
return false;
}
if (!checkForProjectXmlModified) {
// OK, assume it is out of date.
return true;
}
// Check whether it is in fact out of date.
return (flags & (GeneratedFilesHelper.FLAG_OLD_PROJECT_XML |
GeneratedFilesHelper.FLAG_OLD_STYLESHEET)) != 0;
}
// #45373 - workaround: on Windows make sure that all lines end with CRLF.
// marcow: Use at least some buffered output!
private static class EolFilterOutputStream extends BufferedOutputStream {
private boolean isActive = Utilities.isWindows();
private int last = -1;
public EolFilterOutputStream(OutputStream os) {
super(os, 4096);
}
public void write(byte[] b, int off, int len) throws IOException {
if (isActive) {
for (int i = off; i < off + len; i++) {
write(b[i]);
}
}
else {
super.write(b, off, len);
}
}
public void write(int b) throws IOException {
if (isActive) {
if (b == '\n' && last != '\r') {
super.write('\r');
}
last = b;
}
super.write(b);
}
}
}