org.javacc.parser.OutputFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ph-javacc-maven-plugin Show documentation
Show all versions of ph-javacc-maven-plugin Show documentation
Maven 3 Plugin for processing JavaCC grammar files.
/* Copyright (c) 2007, Paul Cager.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.javacc.parser;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.javacc.Version;
/**
* This class handles the creation and maintenance of the boiler-plate classes,
* such as Token.java, JavaCharStream.java etc. It is responsible for:
*
* - Writing the JavaCC header lines to the file.
* - Writing the checksum line.
* - Using the checksum to determine if an existing file has been changed by
* the user (and so should be left alone).
* - Checking any existing file's version (if the file can not be
* overwritten).
* - Checking any existing file's creation options (if the file can not be
* overwritten).
*
*
*
* @author Paul Cager
*/
public class OutputFile
{
private static final String MD5_LINE_PART_1 = "/* JavaCC - OriginalChecksum=";
private static final String MD5_LINE_PART_1q = "/\\* JavaCC - OriginalChecksum=";
private static final String MD5_LINE_PART_2 = " (do not edit this line) */";
private static final String MD5_LINE_PART_2q = " \\(do not edit this line\\) \\*/";
TrapClosePrintWriter pw;
DigestOutputStream dos;
String toolName = JavaCCGlobals.toolName;
final File file;
final String compatibleVersion;
final String [] options;
/**
* Create a new OutputFile.
*
* @param file
* the file to write to.
* @param compatibleVersion
* the minimum compatible JavaCC version.
* @param options
* if the file already exists, and cannot be overwritten, this is a
* list of options (such s STATIC=false) to check for changes.
* @throws IOException
*/
public OutputFile (final File file, final String compatibleVersion, final String [] options) throws IOException
{
this.file = file;
this.compatibleVersion = compatibleVersion;
this.options = options;
if (file.exists ())
{
// Generate the checksum of the file, and compare with any value
// stored
// in the file.
final BufferedReader br = new BufferedReader (new FileReader (file));
MessageDigest digest;
try
{
digest = MessageDigest.getInstance ("MD5");
}
catch (final NoSuchAlgorithmException e)
{
throw (IOException) (new IOException ("No MD5 implementation").initCause (e));
}
final DigestOutputStream digestStream = new DigestOutputStream (new NullOutputStream (), digest);
final PrintWriter pw = new PrintWriter (digestStream);
String line;
String existingMD5 = null;
while ((line = br.readLine ()) != null)
{
if (line.startsWith (MD5_LINE_PART_1))
{
existingMD5 = line.replaceAll (MD5_LINE_PART_1q, "").replaceAll (MD5_LINE_PART_2q, "");
}
else
{
pw.println (line);
}
}
pw.close ();
final String calculatedDigest = toHexString (digestStream.getMessageDigest ().digest ());
if (existingMD5 == null || !existingMD5.equals (calculatedDigest))
{
// No checksum in file, or checksum differs.
needToWrite = false;
if (compatibleVersion != null)
{
checkVersion (file, compatibleVersion);
}
if (options != null)
{
checkOptions (file, options);
}
}
else
{
// The file has not been altered since JavaCC created it.
// Rebuild it.
System.out.println ("File \"" + file.getName () + "\" is being rebuilt.");
needToWrite = true;
}
}
else
{
// File does not exist
System.out.println ("File \"" + file.getName () + "\" does not exist. Will create one.");
needToWrite = true;
}
}
public OutputFile (final File file) throws IOException
{
this (file, null, null);
}
public boolean needToWrite = true;
/**
* Output a warning if the file was created with an incompatible version of
* JavaCC.
*
* @param fileName
* @param versionId
*/
private void checkVersion (final File file, final String versionId)
{
final String firstLine = "/* " + JavaCCGlobals.getIdString (toolName, file.getName ()) + " Version ";
try
{
final BufferedReader reader = new BufferedReader (new FileReader (file));
String line;
while ((line = reader.readLine ()) != null)
{
if (line.startsWith (firstLine))
{
final String version = firstLine.replaceFirst (".* Version ", "").replaceAll (" \\*/", "");
if (version != versionId)
{
JavaCCErrors.warning (file.getName () +
": File is obsolete. Please rename or delete this file so" +
" that a new one can be generated for you.");
}
return;
}
}
// If no version line is found, do not output the warning.
}
catch (final FileNotFoundException e1)
{
// This should never happen
JavaCCErrors.semantic_error ("Could not open file " + file.getName () + " for writing.");
throw new Error ();
}
catch (final IOException e2)
{}
}
/**
* Read the options line from the file and compare to the options currently in
* use. Output a warning if they are different.
*
* @param fileName
* @param options
*/
private void checkOptions (final File file, final String [] options)
{
try
{
final BufferedReader reader = new BufferedReader (new FileReader (file));
String line;
while ((line = reader.readLine ()) != null)
{
if (line.startsWith ("/* JavaCCOptions:"))
{
final String currentOptions = Options.getOptionsString (options);
if (line.indexOf (currentOptions) == -1)
{
JavaCCErrors.warning (file.getName () +
": Generated using incompatible options. Please rename or delete this file so" +
" that a new one can be generated for you.");
}
return;
}
}
}
catch (final FileNotFoundException e1)
{
// This should never happen
JavaCCErrors.semantic_error ("Could not open file " + file.getName () + " for writing.");
throw new Error ();
}
catch (final IOException e2)
{}
// Not found so cannot check
}
/**
* Return a PrintWriter object that may be used to write to this file. Any
* necessary header information is written by this method.
*
* @return
* @throws IOException
*/
public PrintWriter getPrintWriter () throws IOException
{
if (pw == null)
{
MessageDigest digest;
try
{
digest = MessageDigest.getInstance ("MD5");
}
catch (final NoSuchAlgorithmException e)
{
throw (IOException) (new IOException ("No MD5 implementation").initCause (e));
}
dos = new DigestOutputStream (new BufferedOutputStream (new FileOutputStream (file)), digest);
pw = new TrapClosePrintWriter (dos);
// Write the headers....
final String version = compatibleVersion == null ? Version.versionNumber : compatibleVersion;
pw.println ("/* " + JavaCCGlobals.getIdString (toolName, file.getName ()) + " Version " + version + " */");
if (options != null)
{
pw.println ("/* JavaCCOptions:" + Options.getOptionsString (options) + " */");
}
}
return pw;
}
/**
* Close the OutputFile, writing any necessary trailer information (such as a
* checksum).
*
* @throws IOException
*/
public void close () throws IOException
{
// Write the trailer (checksum).
// Possibly rename the .java.tmp to .java??
if (pw != null)
{
pw.println (MD5_LINE_PART_1 + getMD5sum () + MD5_LINE_PART_2);
pw.closePrintWriter ();
// file.renameTo(dest)
}
}
private String getMD5sum ()
{
pw.flush ();
final byte [] digest = dos.getMessageDigest ().digest ();
return toHexString (digest);
}
private final static char [] HEX_DIGITS = new char [] { '0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f' };
private static final String toHexString (final byte [] bytes)
{
final StringBuffer sb = new StringBuffer (32);
for (final byte b : bytes)
{
sb.append (HEX_DIGITS[(b & 0xF0) >> 4]).append (HEX_DIGITS[b & 0x0F]);
}
return sb.toString ();
}
private static class NullOutputStream extends OutputStream
{
@Override
public void write (final byte [] arg0, final int arg1, final int arg2) throws IOException
{}
@Override
public void write (final byte [] arg0) throws IOException
{}
@Override
public void write (final int arg0) throws IOException
{}
}
private class TrapClosePrintWriter extends PrintWriter
{
public TrapClosePrintWriter (final OutputStream os)
{
super (os);
}
public void closePrintWriter ()
{
super.close ();
}
@Override
public void close ()
{
try
{
OutputFile.this.close ();
}
catch (final IOException e)
{
System.err.println ("Could not close " + file.getAbsolutePath ());
}
}
}
/**
* @return the toolName
*/
public String getToolName ()
{
return toolName;
}
/**
* @param toolName
* the toolName to set
*/
public void setToolName (final String toolName)
{
this.toolName = toolName;
}
public String getPath ()
{
return file.getAbsolutePath ();
}
}