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-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.compiler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.OutputStreamWriter;
import java.text.MessageFormat;
import java.util.StringTokenizer;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.openide.execution.*;
import org.openide.filesystems.*;
import org.openide.filesystems.FileSystem.Environment;
import org.openide.loaders.DataObject;
import org.openide.util.MapFormat;
import org.openide.util.Utilities;
import org.openide.ErrorManager;
import org.openide.compiler.ExternalCompiler.ErrorExpression;

/** A group holding several ExternalCompilers.
* When they are compiled, all the filename arguments are collected and the process is run
* only once.
* @see ExternalCompiler
*
* @author Ales Novak, Jaroslav Tulach
*/
public class ExternalCompilerGroup extends CompilerGroup {

    /** regexp meta characters; cf. #10371 */
    private static final String META_CHARACTERS = "\\[](){}^$.|?*+"; // NOI18N
    /** regexp escape character */
    private static final char ESCAPE_CHAR = '\\';
    
    /** The compilers to be used. */
    private Set compilers = new IdSet (); // Set

    /** flag for indicating errors */
    private boolean dirty;
    
    /** dimension */
    //private static final int DIM = 7;

    /** Regular expression which matches e.g. c:\java */
    private Pattern clsPath;

    /** Create an external compiler group. */
    public ExternalCompilerGroup() {
    }

    /* Consumes a compiler. Should absorb all information
    * contained in the compiler.
    *
    * @param c an instance of ExternalCompiler
    * @exception IllegalArgumentException if this compiler
    *   does not belong to this group (the group's class is not the
    *   same as the one returned from c.compilerGroupClass)
    */
    public void add (Compiler c) throws IllegalArgumentException {
        if (! (c instanceof ExternalCompiler)) {
            throw new IllegalArgumentException();
        }
        compilers.add (c);
        //System.err.println("ExternalCompilerGroup.add; c=" + c);
    }

    /** Allows subclasses to provide their own format for parsing
    * the arguments of NbProcessDescriptor contained in the
    * ExternalCompiler; assumes interesting content of "compiler type".
    * 

By default, delegates to the variant that does not take a * "compiler type" argument, as this is deprecated usage. * * @param desc description of program to start * @param files the argument to compiler list of files to compile (or reference * to the file with @files) * @param compilerType the type of compiler for all this files, * this is the compiler dependent object returned from method * ExternalCompiler.compilerType () * @return format to use for changing the command line of the compiler * @exception IOException if exec fails * * @see ExternalCompiler#compilerType * * @deprecated Please instead directly override {@link #createProcess(NbProcessDescriptor,String[])} * as this version does not use the now-deprecated "compiler type" object. */ protected Process createProcess ( NbProcessDescriptor desc, String[] files, Object compilerType ) throws IOException { return createProcess (desc, files); } /** Allows subclasses to provide their own format for parsing * the arguments of NbProcessDescriptor contained in the * ExternalCompiler. *

* This implementation creates new format Format with settings * from NbClassPath.createXXXX and executes them in the provided * process descriptor. * * @param desc description of program to start * @param files the argument to compiler list of files to compile (or reference * to the file with @files) * @return format to use for changing the command line of the compiler * @exception IOException if exec fails */ protected Process createProcess ( NbProcessDescriptor desc, String[] files ) throws IOException { return desc.exec (new Format (files)); } /** Allows subclasses to provide their own format for parsing * the arguments of NbProcessDescriptor contained in the * ExternalCompiler. *

* This implementation creates new format Format with settings * from NbClassPath.createXXXX and executes them in the provided * process descriptor. * * @param desc description of program to start * @param files the argument to compiler list of files to compile (or reference * to the file with @files) * @param cwd current working directory. * @return format to use for changing the command line of the compiler * @exception IOException if exec fails */ protected Process createProcess ( NbProcessDescriptor desc, String[] files, java.io.File cwd ) throws IOException { return desc.exec(new Format(files), null, cwd); } /** Creates human readable String used in status line - should contain * information what is compiled - Compiling MyClass.java * * @return String */ protected String getStatusLineText() { String msg; if (compilers.size() == 1) { FileObject fo = getAllCompilers()[0].getFileObject(); if (fo != null) { msg = java.text.MessageFormat.format( getString("CTL_FMT_CompilingMessage"), // NOI18N new Object[] { fo.getPath() }); return msg; } } return getString("FMT_GenericCompilingMessage"); // NOI18N } /** * @return an array containing all Compilers that were added to this * CompilerGroup */ protected final ExternalCompiler[] getAllCompilers() { return (ExternalCompiler[]) compilers.toArray(new ExternalCompiler[compilers.size()]); } /** access method for firing errors */ void fireErrEvent(ErrorEvent ev, boolean isWarning) { if (ev.getFile() != null && !isWarning) { dirty = true; } fireErrorEvent(ev); } /** * This implementation filters compilers that have the same hashCode and equals, * but they report different up-to-date status. The compiler that is not up-to-date * will be passed on. * @return Collection of unique compilers. */ protected Collection filterCompilers(Collection allCompilers) { HashMap compilerMap = new HashMap(allCompilers.size() * 4 / 3); for (Iterator it = allCompilers.iterator(); it.hasNext(); ) { ExternalCompiler comp1 = (ExternalCompiler)it.next(); ExternalCompiler comp2 = (ExternalCompiler)compilerMap.put(comp1, comp1); if (comp2 != null && comp1 != comp2 && (!comp2.isUpToDate() && comp1.isUpToDate())) { // should happen iff comp2 is BUILD, comp1 COMPILE and both are for the same, // but up-to-date source. compilerMap.put(comp1, comp2); } } return compilerMap.values(); } /* Starts compilation. It should check which files realy needs to be * compiled and compile only those which really need to. *

* The compilation should fire info to status listeners and report * all errors to error listeners. * * @return true if successful, false otherwise */ public boolean start () { //System.err.println("ExternalCompilerGroup.start; this=" + this + "; thread=" + Thread.currentThread ()); dirty = false; if (compilers.isEmpty ()) return true; // if no compilers have been added -> succeed CompilerExecutor cexec = null; Set folders = new HashSet (7); // Set Iterator it = filterCompilers(compilers).iterator (); while (it.hasNext ()) { ExternalCompiler c = (ExternalCompiler) it.next (); if (cexec == null) { cexec = new CompilerExecutor (this, c.getCompilerDescriptor (), c.getErrorExpression (), c.compilerType ()); // XXX todo: It seems that there is a some kind // of timing issue in parseErrors - we should read // the stream ASAP, otherwise we sometimes get // an Exception "stream closed" the stream is closed // before we had a chance read it. // // Since the following is a kind of // costly operation I am moving that here, // before a java.lang.Process is created. //getErrorPattern(c.getErrorExpression()); } String name = c.getFileName (); // System.err.println("\tname=" + name + "; this=" + this); if (name != null && ! "".equals (name)) cexec.addFile (name); // NOI18N FileObject folder = c.getFileObject (); if (folder != null) { if (folder.isData ()) { folder = folder.getParent (); } folders.add (folder); } } try { //System.err.println("\twill execute; this=" + this); int result = cexec.execute((DataObject)null).result(); //System.err.println("\tresult=" + result + "; this=" + this); dirty |= (result != 0); // refresh folders if (! dirty) { it = folders.iterator (); while (it.hasNext ()) refreshPackage(((FileObject) it.next ())); } if (result == CompilerSysProcess.INTERRUPTED) { ErrorEvent ev = new ErrorEvent(this, null, -1, -1, getString("CTL_Interrupted"), ""); //NOI18N fireErrorEvent(ev); } } catch (IOException ioe) { StringWriter swriter = new StringWriter(); PrintWriter pw = new PrintWriter(swriter); ioe.printStackTrace(pw); fireErrorEvent (new ErrorEvent ( this, null, // wrong 0, 0, swriter.toString(), "" // NOI18N )); dirty = true; } return !dirty; // dirty == false means success } private void refreshPackage(FileObject pfile) { pfile.refresh(); FileObject[] contents = pfile.getChildren(); for (int i = 0; i < contents.length; i++) { FileObject f = contents[i]; if ("class".equals(f.getExt())) { f.refresh(); } } } /** @return A localized String */ private static String getString(String x) { return org.openide.util.NbBundle.getMessage(ExternalCompilerType.class, x); } /** Parses a compiler errors described by err from parsedReader * @param err describes format of errors of the related compiler * @param parsedReader the parsed stream */ protected void parseErrors(ErrorExpression err, java.io.Reader parsedReader) { Pattern errorPattern = getErrorPattern(err); // See #36482. Cannot read ahead indefinitely looking for a match. // So report expected EOF as 10k ahead of where we have actually read. // So long as the error regexp does not need to match anything longer // than this (very unlikely), should work. And at most 10k chars are // ignored in the last find() call. ReaderCharSequence s = new ReaderCharSequence(parsedReader, 4096, 10000, 4.0f / 3.0f); Matcher m = errorPattern.matcher(s); int idx = 0; while (m.find(idx)) { if (err.getFilePos() < 0) { return; } int line; int column; String msg; String refText; int lowerBound = 0; int upperBound = m.groupCount(); String file = m.group(err.getFilePos()); if (err.getLinePos() < lowerBound || err.getLinePos() > upperBound) { line = 1; } else { try { line = Integer.parseInt(m.group(err.getLinePos())); } catch (NumberFormatException ex) { line = 1; } } if (err.getColumnPos() < lowerBound || err.getColumnPos() > upperBound) { column = 1; } else { try { column = Integer.parseInt(m.group(err.getColumnPos())); } catch (NumberFormatException ex) { column = 1; } } if (err.getDescriptionPos() < lowerBound || err.getDescriptionPos() > upperBound) { msg = ""; // NOI18N } else { String sg = m.group(err.getDescriptionPos()); msg = sg == null ? "" : sg.trim(); // NOI18N } refText = ""; // NOI18N // everything between idx and result.getBeginIndex() must be printed out String rst = s.subSequence(idx, m.start()).toString(); if (!rst.equals("")) { // NOI18N notifyError(null, -1, -1, trimString(rst), null); } // trim for JIKES compiler notifyError(file != null ? file.trim() : null, line, column, msg, refText); idx = m.end(); s.prune(idx); } // print the rest String rst = s.subSequence(idx, s.length()).toString(); if (!rst.equals("")) { // NOI18N notifyError(null, 0, 0, trimString(rst), null); } } /** trims String - patch for Microsoft jvc * @param s a String to trim * @return trimmed String */ private static String trimString(String s) { int idx = 0; char c; final int slen = s.length(); if (slen == 0) { return s; } do { c = s.charAt(idx++); } while ((c == '\n' || c == '\r') && (idx < slen)); s = s.substring(--idx); idx = s.length() - 1; if (idx < 0) { return s; } do { c = s.charAt(idx--); } while ((c == '\n' || c == '\r') && (idx >= 0)); return s.substring(0, idx + 2); } /** Notifies (fires) about an error; all parameters are description of the error * @param file in which file * @param line on which line * @param column on which column * @param message * @param ref */ protected void notifyError(String file, int line, int column, String message, String ref) { String warning = getString("FMT_Warning"); // NOI18N boolean isWarning = message.toLowerCase().indexOf(warning) >= 0; FileObject fo = null; if (file != null) { fo = string2File(file); if (fo == null) { // panic - write as is Object[] args = new Object[] { file.replace(java.io.File.separatorChar, '/'), new Integer(line), new Integer(column), message }; message = getUnknownFile().format(args); } } else if (message.equals("")) { // NOI18N return; // nothing to say } ErrorEvent ev = new ErrorEvent(this, fo, line, column, message, ref ); fireErrEvent(ev, isWarning); } /** @return items from filesystems which take part in compilation */ private Pattern getClassPathEntries() { if (clsPath == null) { String repPath = NbClassPath.createRepositoryPath(FileSystemCapability.COMPILE).getClassPath(); if (repPath.charAt(0) == '"') { repPath = repPath.substring(1, repPath.length() - 1); } try { clsPath = Pattern.compile(sortAndMakeOr(escapeMetaCharacters(repPath))); } catch (PatternSyntaxException e) { ErrorManager.getDefault().notify(e); } } return clsPath; } /** @return pattern for given ErrorExpression */ private static Pattern getErrorPattern(ErrorExpression err) { String regexp = err.getErrorExpression(); try { return Pattern.compile(regexp, Pattern.MULTILINE); } catch (PatternSyntaxException e) { ErrorManager.getDefault().notify(e); return null; } } /** Sorts given string (in format first-class-path-entry:second....) to * (longest-class-path-entry) and replaces : to || */ static String sortAndMakeOr(String path) { // prepare path int len = path.length() - 1; while (path.charAt(len--) == java.io.File.pathSeparatorChar); path = path.substring(0, len + 2); ArrayList list = new ArrayList(10); // tokenize Strings by : or ; StringTokenizer stok = new StringTokenizer(path, File.pathSeparator); while (stok.hasMoreTokens()) { list.add(stok.nextToken()); } if (list.size() > 0) { // create a String array to sort them String[] a = new String[list.size()]; a = (String[]) list.toArray(a); // sort Arrays.sort(a, new StringComparator()); // create new class path // run index down to zero StringBuffer sb = new StringBuffer(300); for (int i = a.length - 1; i > 0; i-- ) { sb.append((String) a[i]); sb.append('|'); } sb.append((String) a[0]); return sb.toString(); } else { return path; } } /** Compares to object by lenght of Stringd returned by toString(). */ static final class StringComparator implements Comparator { public int compare(Object o1, Object o2) { return (o1.toString().length() - o2.toString().length()); } } /** * Determines if the character is a regexp meta character. * @param c is the character to check for * @return true if c is the meta character. False otherwise. * */ private static boolean isMetaCharacter( char c ) { return ( META_CHARACTERS.indexOf( c ) >= 0 ); } /** * Scans for the regexp meta characters * and escape them out * @param s is a string which may contain the meta character * @return the string after all its meta characters are escaped out. * */ private static String escapeMetaCharacters(String s) { if (META_CHARACTERS.indexOf(File.pathSeparatorChar) != -1) { throw new IllegalStateException("org.openide.compiler.ErrorsParsingThread must be patched for this operating system"); // NOI18N } StringBuffer buf = new StringBuffer(Math.max (1, s.length() * 2)); int len = s.length(); for (int i = 0; i < len; i++) { char c = s.charAt(i); // If a regexp meta character, escape it out. // else leave it alone. if (isMetaCharacter(c)) { buf.append(ESCAPE_CHAR); } buf.append(c); } return buf.toString(); } /** A utility class that helps extract path to the root folder of a FileSystem */ static final class OneItemEnv extends Environment { String classPathEntry; public void addClassPath(String s) { classPathEntry = s; } public String getClassPathEntry() { return classPathEntry; } } /** converts string to FileObject * e.g z:\java\huh\Suck.java to file object in fs z:\java with package huh * name Suck and extension java */ private FileObject string2File(String ffile) { ffile = ffile.replace('/', File.separatorChar); Matcher m = getClassPathEntries().matcher(ffile); if (m.lookingAt()) { // should be true String tmp = ffile.substring(m.end()); tmp = tmp.replace(File.separatorChar, '/'); java.util.Enumeration res = FileSystemCapability.COMPILE.findAllResources(tmp); FileObject first = null; String matchedEntry = ffile.substring(0, m.end()); try { OneItemEnv env = new OneItemEnv(); while (res.hasMoreElements()) { FileObject tmpfo = (FileObject) res.nextElement(); tmpfo.getFileSystem().prepareEnvironment(env); String sysname = env.getClassPathEntry(); if (sysname.equals(matchedEntry)) { return tmpfo; } else if (first == null) { first = tmpfo; } } } catch (FileStateInvalidException e) { // cannot happen (haha) ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } catch (EnvironmentNotSupportedException e) { ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, e); } return first; } return null; } /** * @return MessageFormat */ private static MessageFormat getUnknownFile() { return new MessageFormat(ExternalCompiler.getLocalizedString("MSG_Unknown_file")); // NOI18N } /** Default format that can format tags related to compilation. These include settings of classpath * (can be composed from repository, class path, boot class path and libraries) and * putting somewhere list of files to compile. */ public static class Format extends MapFormat { /** Tag replaced with ProcessExecutors.getClassPath () */ public static final String TAG_CLASSPATH = ProcessExecutor.Format.TAG_CLASSPATH; /** Tag replaced with ProcessExecutors.getBootClassPath () */ public static final String TAG_BOOTCLASSPATH = ProcessExecutor.Format.TAG_BOOTCLASSPATH; /** Tag replaced with ProcessExecutors.getRepositoryPath () */ public static final String TAG_REPOSITORY = ProcessExecutor.Format.TAG_REPOSITORY; /** Tag replaced with ProcessExecutors.getLibraryPath () */ public static final String TAG_LIBRARY = ProcessExecutor.Format.TAG_LIBRARY; /** Tag replaced with arguments of the program */ public static final String TAG_FILES = "files"; // NOI18N /** Tag replaced with home directory of JDK */ public static final String TAG_JAVAHOME = ProcessExecutor.Format.TAG_JAVAHOME; /** Tag replaced with root directory of JDK */ public static final String TAG_JDKHOME = ProcessExecutor.Format.TAG_JDKHOME; /** Tag replaced with separator between filename components */ public static final String TAG_SEPARATOR = ProcessExecutor.Format.TAG_SEPARATOR; /** Tag replaced with separator between path components */ public static final String TAG_PATHSEPARATOR = ProcessExecutor.Format.TAG_PATHSEPARATOR; static final long serialVersionUID =-8630048144603405233L; /** * Default value for max cummulative length of filenames passed right on * the commandline. */ static final int DEFAULT_FILELIST_THRESHOLD = 2048; /** * Name of the system property, which controls the value of filelist * length threshold. */ static final String SYSPROP_FILELIST_THRESHOLD = "org.openide.compilers.filelistThreshold"; // NOI18N /** All values for the paths takes from NbClassPath.createXXX methods. * * @param files files to compile */ public Format (String[] files) { this ( files, NbClassPath.createClassPath (), NbClassPath.createBootClassPath (), NbClassPath.createRepositoryPath (FileSystemCapability.COMPILE), NbClassPath.createLibraryPath () ); } /** @param files files to compile * @param classPath to substitute instead of CLASSPATH * @param bootClassPath boot class path * @param repository repository path * @param library library path */ public Format ( String[] files, NbClassPath classPath, NbClassPath bootClassPath, NbClassPath repository, NbClassPath library ) { super (createMap7 ()); java.util.Map map = getMap (); map.put (TAG_CLASSPATH, classPath.getClassPath ()); map.put (TAG_BOOTCLASSPATH, bootClassPath.getClassPath ()); map.put (TAG_REPOSITORY, repository.getClassPath ()); map.put (TAG_LIBRARY, library.getClassPath ()); map.put (TAG_FILES, asParameterString (files)); map.put (TAG_JAVAHOME, System.getProperty ("java.home")); map.put (TAG_JDKHOME, System.getProperty ("jdk.home")); map.put (TAG_SEPARATOR, File.separator); map.put (TAG_PATHSEPARATOR, File.pathSeparator); } /** Helper method to allows conversion of list of files to compile to * one string that can be passed as parameter to external process. * Necessarily platform-specific. *

    *
  • On non-Windows machines the method simply concatenates the strings * into one. *
  • On Windows, if the file count is greater then ten, it * creates a temporary file, writes the strings into it and returns * "@filename" which is accepted by common programs instead of the * list of files. *
  • On OpenVMS, we use the @ regardless of the number * of files. *
* * @param files array of files to compile * @return the string representing the files to compile, or null if it * cannot be created (for example, the temporary file cannot be created) */ public static String asParameterString (String[] files) { boolean vms = false; int nameThreshold = Integer.getInteger(SYSPROP_FILELIST_THRESHOLD, DEFAULT_FILELIST_THRESHOLD).intValue(); String filesValue = null; if ((files.length > 10 && (Utilities.isWindows())) || (vms = Utilities.getOperatingSystem() == Utilities.OS_VMS) || ((filesValue = constructString(files, nameThreshold)) == null)) { try { File f = constructFile(files, !vms); StringBuffer sb = new StringBuffer(50); String fname = f.toString(); boolean space = fname.indexOf(' ') >= 0; if (space) { sb.append('"'); } sb.append('@'); sb.append(fname); if (space) { sb.append('"'); } return sb.toString(); } catch (IOException e) { ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, e); return null; } } else { return filesValue; } } /** Creates default size hash map. */ private static java.util.HashMap createMap7 () { return new java.util.HashMap (7); } /** prefix for a tmp file */ private static final String PREFIX = "compilerparams"; // NOI18N /** suffix for a tmp file */ private static final String SUFFIX = ".pms"; // NOI18N /** @return File containing all files to compile. */ private static File constructFile(String[] files, boolean escape) throws IOException { File f = File.createTempFile(PREFIX, SUFFIX); f.deleteOnExit(); PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f))); for (int i = 0; i < files.length; i++) { String file = files[i]; if (escape && file.indexOf(' ') > -1) { // javac reads the file through StreamTokenizer, which // honours escape sequences - so we need to escape backslashes. StringBuffer sb = new StringBuffer(); sb.append('"'); char[] content = file.toCharArray(); int pos; int lastPos = 0; while ((pos = file.indexOf('\\', lastPos)) > -1) { sb.append(content, lastPos, pos - lastPos + 1); sb.append('\\'); lastPos = pos + 1; } if (lastPos < file.length()) sb.append(content, lastPos, file.length() - lastPos); sb.append('"'); file = sb.toString(); } pw.println(file); } pw.close(); return f; } /** @return StringBuffer containing all files to compile. */ private static String constructString(String[] files, int nameThreshold) { StringBuffer sb = new StringBuffer (); String add = ""; // NOI18N for (int i = 0; i < files.length; i++) { sb.append (add); if (files[i].indexOf(' ') >= 0) { sb.append("\""); // NOI18N sb.append(files[i]); sb.append("\""); // NOI18N } else { sb.append (files[i]); } add = " "; // NOI18N if (sb.length() > nameThreshold) return null; } return sb.toString (); } } }
... 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.