alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 *
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.spi.project.support.ant;

import java.io.BufferedInputStream;
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.URL;
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.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.Mutex;
import org.openide.util.MutexException;
import org.openide.util.UserQuestionException;

/**
 * Helps a project type (re-)generate, and manage the state and versioning of,
 * build.xml and build-impl.xml.
 * @author Jesse Glick
 */
public final class GeneratedFilesHelper {
    
    /**
     * Relative path from project directory to the user build script,
     * build.xml.
     */
    public static final String BUILD_XML_PATH = "build.xml"; // NOI18N
    
    /**
     * Relative path from project directory to the implementation build script,
     * build-impl.xml.
     */
    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
     * project.properties; and it includes a CRC of project.xml
     * 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 project.xml 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}.
     * 

* Probably this means it was edited by the user. *

* @see #getBuildScriptState */ public static final int FLAG_MODIFIED = 2 << 1; /** * A build script was generated from an older version of project.xml. * It was last generated using a different version of project.xml, * so using the current project.xml might produce a different * result. *

* This is quite likely in the case of * build.xml; in the case of build-impl.xml, it * probably means that the user edited project.xml manually, * since if that were modified from {@link AntProjectHelper} methods and * the project were saved, the script would have been regenerated * already. *

* @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. *

* Probably this means the project type * provider module has been upgraded since the project was last saved (in * the case of build-impl.xml) or created (in the case of * build.xml). *

* @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. *

* Probably this means that nbproject/genfiles.properties was * deleted by the user. *

* @see #getBuildScriptState */ public static final int FLAG_UNKNOWN = 2 << 4; /** Associated project helper, or null if using only a directory. */ private final AntProjectHelper 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(AntProjectHelper 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(AntProjectHelper)}. * @param d the project directory * @throws IllegalArgumentException if the supplied directory has no project.xml */ public GeneratedFilesHelper(FileObject d) { if (d == null || !d.isFolder() || d.getFileObject(AntProjectHelper.PROJECT_XML_PATH) == null) { throw new IllegalArgumentException("Does not look like an Ant-based project: " + d); // NOI18N } h = null; dir = d; } /** * Create build.xml or nbproject/build-impl.xml * from project.xml plus a supplied XSLT stylesheet. * This is the recommended way to create the build scripts from * project metadata. *

* 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. *

*

* Acquires write access. *

* @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 project.xml * 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() { public Object 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(AntProjectHelper.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); } try { FileLock lock = buildScriptXml.lock(); try { OutputStream os = buildScriptXml.getOutputStream(lock); try { os.write(resultData); } finally { os.close(); } } finally { lock.releaseLock(); } // Update genfiles.properties too. EditableProperties p = new EditableProperties(); FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH); if (genfiles != null) { is = genfiles.getInputStream(); try { p.load(is); } finally { is.close(); } } else { // XXX set a file header comment, when EditableProperties supports that } p.setProperty(path + KEY_SUFFIX_DATA_CRC, computeCrc32(new ByteArrayInputStream(projectXmlData))); p.setProperty(path + KEY_SUFFIX_STYLESHEET_CRC, computeCrc32(new ByteArrayInputStream(stylesheetData))); p.setProperty(path + KEY_SUFFIX_SCRIPT_CRC, computeCrc32(new ByteArrayInputStream(resultData))); if (genfiles == null) { genfiles = FileUtil.createData(dir, GENFILES_PROPERTIES_PATH); } lock = genfiles.lock(); try { OutputStream os = genfiles.getOutputStream(lock); try { p.store(os); } finally { os.close(); } } finally { lock.releaseLock(); } } catch (UserQuestionException uqe) { // Don't bother asking to lock it; not important enough. // Cf. #46089. } } }); 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}. *

* 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 project.xml. These CRCs are stored * in a special file nbproject/genfiles.properties. * 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 project.xml or the * stylesheet (e.g. using XSLT's import facility) are not detected. *

*

* 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). *

*

* Acquires read access. *

* @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 project.xml * as input and producing the build script as output * (should match that given to {@link #generateBuildScriptFromStylesheet}) * @return a bitwise OR of various flags, or 0 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 ((Integer)ProjectManager.mutex().readAccess(new Mutex.ExceptionAction() { public Object 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) { return new Integer(FLAG_MISSING); } int flags = 0; Properties p = new Properties(); FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH); if (genfiles == null) { // Who knows? User deleted it; anything might be wrong. Safest to assume // that everything is. return new Integer(FLAG_UNKNOWN | FLAG_MODIFIED | FLAG_OLD_PROJECT_XML | FLAG_OLD_STYLESHEET); } InputStream is = genfiles.getInputStream(); try { p.load(is); } finally { is.close(); } FileObject projectXml = dir.getFileObject(AntProjectHelper.PROJECT_XML_PATH); if (projectXml != null) { is = projectXml.getInputStream(); try { String crc = computeCrc32(new BufferedInputStream(is)); if (!crc.equals(p.getProperty(path + KEY_SUFFIX_DATA_CRC))) { flags |= FLAG_OLD_PROJECT_XML; } } finally { is.close(); } } else { // Broken project?! flags |= FLAG_OLD_PROJECT_XML; } is = stylesheet.openStream(); try { String crc = computeCrc32(new BufferedInputStream(is)); if (!crc.equals(p.getProperty(path + KEY_SUFFIX_STYLESHEET_CRC))) { flags |= FLAG_OLD_STYLESHEET; } } finally { is.close(); } is = script.getInputStream(); try { String crc = computeCrc32(new BufferedInputStream(is)); if (!crc.equals(p.getProperty(path + KEY_SUFFIX_SCRIPT_CRC))) { flags |= FLAG_MODIFIED; } } finally { is.close(); } return new Integer(flags); } })).intValue(); } 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; } /** * Convenience method to refresh a build script if it can and should be. *

* If the script is not modified, and it is either missing, or the flag * checkForProjectXmlModified is false, or it is out of date with * respect to either project.xml or the stylesheet (or both), * it is (re-)generated. *

*

* Acquires write access. *

*

* 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 checkForProjectXmlModified * false (the script is certainly out of date relative to project.xml). * 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 * checkForProjectXmlModified true. *

* @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 project.xml * 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 * project.xml 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 ((Boolean)ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction() { public Object run() throws IOException { int flags = getBuildScriptState(path, stylesheet); if (shouldGenerateBuildScript(flags, checkForProjectXmlModified)) { generateBuildScriptFromStylesheet(path, stylesheet); return Boolean.TRUE; } else { return Boolean.FALSE; } } })).booleanValue(); } 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; } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.