Milán Major

netscape-revival

1 branch
Code

sun-java/classsrc/sun/tools/javac/BatchEnvironment.java

/*
 * @(#)BatchEnvironment.java	1.50 95/11/12 Arthur van Hoff
 *
 * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 */

package sun.tools.javac;

import sun.tools.java.*;
import java.util.*;
import java.io.*;
import java.lang.SecurityManager;

/**
 * Main environment of the batch version of the Java compiler,
 * this needs more work.
 */
public 
class BatchEnvironment extends Environment {
    /**
     * The stream where error message are printed.
     */
    OutputStream out;
    
    /**
     * The class path
     */
    ClassPath path;
    
    /**
     * A hashtable of resource contexts.
     */
    Hashtable packages = new Hashtable(31);

    /**
     * The classes, this is only a cache, not a
     * complete list.
     */
    Hashtable classes = new Hashtable(351);

    /**
     * flags
     */
    public int flags;

    /**
     * The number of errors and warnings
     */
    public int nerrors;
    public int nwarnings;

    /**
     * Constructor
     */
    public BatchEnvironment(ClassPath path) {
	this(System.out, path);
    }

    /**
     * Constructor
     */
    public BatchEnvironment(OutputStream out, ClassPath path) {
	this.out = out;
	this.path = path;
    }

    /**
     * Return flags
     */
    public int getFlags() {
        return flags;
    }

    /**
     * Return an enumeration of all the currently defined classes
     */
    public Enumeration getClasses() {
	return classes.elements();
    }

    /**
     * Get a class, given the fully qualified class name
     */
    public ClassDeclaration getClassDeclaration(Identifier nm) {
	ClassDeclaration c = (ClassDeclaration)classes.get(nm);
	if (c == null) {
	    classes.put(nm, c = new ClassDeclaration(nm));
	}
	return c;
    }

    /**
     * Check if a class exists
     */
    public boolean classExists(Identifier nm) {
	try {
	    return (classes.get(nm) != null) ||
		getPackage(nm.getQualifier()).classExists(nm.getName());
	} catch (IOException e) {
	    return true;
	}
    }

    /**
     * Get the package path for a packate
     */
    public Package getPackage(Identifier pkg) throws IOException {
	Package p = (Package)packages.get(pkg);
	if (p == null) {
	    packages.put(pkg, p = new Package(path, pkg));
	}
	return p;
    }
    
    /**
     * Parse a source file
     */
    public void parseFile(ClassFile file) throws FileNotFoundException {
	long tm = System.currentTimeMillis();
	InputStream input;
	BatchParser p;
	try {
	    input = file.getInputStream();
	    p = new BatchParser(new Environment(this, file),
				new BufferedInputStream(input));
	} catch(IOException ex) {
	    throw new FileNotFoundException();
	}

	try {
	    p.parseFile();
	} catch(Exception e) {
	    throw new CompilerError(e);
	}

	try {	
	    input.close();
	} catch (IOException ex) {
	    // We're turn with the input, so ignore this.
	}

	if (verbose()) {
	    tm = System.currentTimeMillis() - tm;
	    output("[parsed " + file.getPath() + " in " + tm + "ms]");
	}
    }

    /**
     * Load a binary file
     */
    BinaryClass loadFile(ClassFile file) throws IOException {
	long tm = System.currentTimeMillis();
	InputStream input = file.getInputStream();
	BinaryClass c = null;

	try {
	    DataInputStream is = 
		new DataInputStream(new BufferedInputStream(input));
	    c = BinaryClass.load(new Environment(this, file), is, 
				 loadFileFlags());
	} catch (ClassFormatError e) {
	    error(0, "class.format", file.getPath(), e.getMessage());
	    return null;
	} catch (Exception e) {
	    e.printStackTrace();
	}

	input.close();
	if (verbose()) {
	    tm = System.currentTimeMillis() - tm;
	    output("[loaded " + file.getPath() + " in " + tm + "ms]");
	}

	return c;
    }
    
    /**
     * Default flags for loadFile.  Subclasses may override this.
     */
    int loadFileFlags() { 
	return 0; 
    }

    /**
     * Load a binary class
     */
    boolean needsCompilation(Hashtable check, ClassDeclaration c) {
	switch (c.getStatus()) {
	  case CS_UNDEFINED:
	    loadDefinition(c);
	    return needsCompilation(check, c);

	  case CS_UNDECIDED:
	    if (check.get(c) == null) {
		check.put(c, c);

		BinaryClass bin = (BinaryClass)c.getClassDefinition();
		for (Enumeration e = bin.getDependencies() ; e.hasMoreElements() ;) {
		    ClassDeclaration dep = (ClassDeclaration)e.nextElement();
		    if (needsCompilation(check, dep)) {
			// It must be source, dependencies need compilation
			//output(c + " must be source, dependencies need compilation: " + dep);
			c.setDefinition(bin, CS_SOURCE);
			return true;
		    }
		}
	    }
	    return false;

	  case CS_BINARY:
	    return false;
	}
	return true;
    }

    /**
     * Load the definition of a class
     */
    public void loadDefinition(ClassDeclaration c) {
	switch (c.getStatus()) {
	  case CS_UNDEFINED: {
	    Identifier nm = c.getName();
	    Package pkg;
	    try {
		pkg = getPackage(nm.getQualifier());
	    } catch (IOException e) {
		error(0, "io.exception", c);
		return;
	    }
	    ClassFile binfile = pkg.getBinaryFile(nm.getName());
	    if (binfile == null) {
		// must be source, there is no binary
		//output(c + " must be source, there is no binary");
		c.setDefinition(null, CS_SOURCE);
		return;
	    }

	    ClassFile srcfile = pkg.getSourceFile(nm.getName());
	    if (srcfile == null) {
		BinaryClass bc = null;
		try {
		    bc = loadFile(binfile);
		} catch (IOException e) {
		    error(0, "io.exception", binfile);
		    return;
		}
		if ((bc != null) && !bc.getName().equals(nm)) {
		    error(0, "wrong.class", binfile.getPath(), c, bc);
		    bc = null;
		}
		if (bc == null) {
		    // no source nor binary found
		    //output(c + " is not found, no source nor binary");
		    c.setDefinition(null, CS_NOTFOUND);
		    return;
		}

		// Couldn't find the source, try the one mentioned in the binary
		if (bc.getSource() != null) {
		    srcfile = new ClassFile(new File((String)bc.getSource()));
		    // Look for the source file
		    srcfile = pkg.getSourceFile(srcfile.getName());
		    if ((srcfile != null) && srcfile.exists()) {
			if (srcfile.lastModified() > binfile.lastModified()) {
			    // must be source, it is newer than the binary
			    //output(c + " must be source, source is newer");
			    c.setDefinition(bc, CS_SOURCE);
			    return;
			}
			c.setDefinition(bc, dependencies() ? CS_UNDECIDED : CS_BINARY);
			return;
		    }
		} 

		// It must be binary, there is no source
		//output(c + " must be binary, there is no source");
		c.setDefinition(bc, CS_BINARY);
		return;
	    }
	    BinaryClass bc = null;
	    try {
		if (srcfile.lastModified() > binfile.lastModified()) {
		    // must be source, it is newer than the binary
		    //output(c + " must be source, source is younger");
		    c.setDefinition(null, CS_SOURCE);
		    return;
		}
		bc = loadFile(binfile);
	    } catch (IOException e) {
		error(0, "io.exception", binfile);
	    }
	    if ((bc != null) && !bc.getName().equals(nm)) {
		error(0, "wrong.class", binfile.getPath(), c, bc);
		bc = null;
	    }
	    if (bc != null) {
		if (bc.getName().equals(c.getName())) {
		    c.setDefinition(bc, dependencies() ? CS_UNDECIDED : CS_BINARY);
		} else {
		    c.setDefinition(null, CS_NOTFOUND);
		    getClassDeclaration(bc.getName()).setDefinition(bc, dependencies() ? CS_UNDECIDED : CS_BINARY);
		}
	    } else {
		c.setDefinition(null, CS_NOTFOUND);
	    }
	    return;
	  }
	    
	  case CS_UNDECIDED: {
	    Hashtable tab = new Hashtable();
	    if (!needsCompilation(tab, c)) {
		// All undecided classes that this class depends on must be binary
		for (Enumeration e = tab.keys() ; e.hasMoreElements() ; ) {
		    ClassDeclaration dep = (ClassDeclaration)e.nextElement();
		    if (dep.getStatus() == CS_UNDECIDED) {
			// must be binary, dependencies need compilation
			//output(dep + " must be binary, dependencies need no compilation");
			dep.setDefinition(dep.getClassDefinition(), CS_BINARY);
		    }
		}
	    }
	    return;
	  }

	  case CS_SOURCE: {
	    ClassFile srcfile = null;
	    if (c.getClassDefinition() != null) {
		// Use the source file name from the binary class file
		Package pkg = null;
		try {
		    pkg = getPackage(c.getName().getQualifier());
		    srcfile = pkg.getSourceFile((String)c.getClassDefinition().getSource());
		} catch (IOException e) {
		    error(0, "io.exception", c);
		}
		if (srcfile == null) {
		    String fn = (String)c.getClassDefinition().getSource();
		    srcfile = new ClassFile(new File(fn));
		}
	    } else {
		// Get a source file name from the package
		Identifier nm = c.getName();
		try {
		    srcfile = getPackage(nm.getQualifier()).getSourceFile(nm.getName());
		} catch (IOException e)  {
		    error(0, "io.exception", c);
		}
		if (srcfile == null) {
		    // not found, there is no source
		    //output(c + " is not found, no source");
		    c.setDefinition(null, CS_NOTFOUND);
		    return;
		}
	    }
	    try {
		parseFile(srcfile);
	    } catch (FileNotFoundException e) {
		error(0, "io.exception", srcfile);
	    }
	    
	    if ((c.getClassDefinition() == null) || (c.getStatus() == CS_SOURCE)) {
		// not found after parsing the file
		c.setDefinition(null, CS_NOTFOUND);
	    } 
	    return;
	  }
	}
    }

    /**
     * True if the properties have been loaded.
     */
    boolean propsLoaded;

    /**
     * Only load the properties when needed.
     */
    String getProperty(String nm) {
	if (!propsLoaded) {
	    propsLoaded = true;
	    String props = System.getProperty("java.home") + 
		System.getProperty("file.separator") + "lib" +
		System.getProperty("file.separator") + "javac.properties";
	    try {
		Properties p = new Properties(System.getProperties());
		InputStream in = new BufferedInputStream(new FileInputStream(props));
		p.load(in);
		in.close();
		System.setProperties(p);
	    } catch (IOException e) {
		output("failed to read: " + props);
	    }
	}
	return System.getProperty(nm);
    }

    /**
     * Error String
     */
    String errorString(String err, Object arg1, Object arg2, Object arg3) {
	String str = null;

	SecurityManager.setScopePermission();
	if (err.startsWith("warn.")) {
	    str = getProperty("javac." + err);
	} else {
	    str = getProperty("javac.err." + err);
	}
	SecurityManager.resetScopePermission();
	if (str == null) {
	    return "error message '" + err + "' not found";
	}
	
	StringBuffer buf = new StringBuffer();
	for (int i = 0 ; i < str.length() ; i++) {
	    char c = str.charAt(i);
	    if ((c == '%') && (i + 1 < str.length())) {
		switch (str.charAt(++i)) {
		  case 's':
		    String arg = arg1.toString();
		    for (int j = 0 ; j < arg.length() ; j++) {
			switch (c = arg.charAt(j)) {
			  case ' ':
			  case '\t':
			  case '\n':
			  case '\r':
			    buf.append((char)c);
			    break;

			  default:
			    if ((c > ' ') && (c <= 255)) {
				buf.append((char)c);
			    } else {
				buf.append('\\');
				buf.append('u');
				buf.append(Integer.toString(c, 16));
			    }
			}
		    }
		    arg1 = arg2;
		    arg2 = arg3;
		    break;
		    
		  case '%':
		    buf.append('%');
		    break;

		  default:
		    buf.append('?');
		    break;
		}
	    } else {
		buf.append((char)c);
	    }
	}
	return buf.toString();
    }

    /**
     * The filename where the last errors have occurred
     */
    String errorFileName;

    /**
     * List of outstanding error messages
     */
    ErrorMessage errors;

    /**
     * Insert an error message in the list of outstanding error messages.
     * The list is sorted on input position.
     */
    void insertError(int where, String message) {
	//output("ERR = " + message);
	ErrorMessage msg = new ErrorMessage(where, message);
	if (errors == null) {
	    errors = msg;
	} else if (errors.where > where) {
	    msg.next = errors;
	    errors = msg;
	} else {
	    ErrorMessage m = errors;
	    for (; (m.next != null) && (m.next.where <= where) ; m = m.next);
	    msg.next = m.next;
	    m.next = msg;
	}
    }

    /**
     * Flush outstanding errors
     */
    public void flushErrors() {
	if (errors == null) {
	    return;
	}
	
	try {
	    // Read the file
	    FileInputStream in = new FileInputStream(errorFileName);
	    byte data[] = new byte[in.available()];
	    in.read(data);
	    in.close();

	    // Report the errors
	    for (ErrorMessage msg = errors ; msg != null ; msg = msg.next) {
		int ln = msg.where >>> OFFSETBITS;
		int off = msg.where & ((1 << OFFSETBITS) - 1);
		
		int i, j;
		for (i = off ; (i > 0) && (data[i - 1] != '\n') && (data[i - 1] != '\r') ; i--);
		for (j = off ; (j < data.length) && (data[j] != '\n') && (data[j] != '\r') ; j++);

		String prefix = errorFileName + ":" + ln + ":";
		output(prefix + " " + msg.message);
		output(new String(data, 0, i, j - i));

		char strdata[] = new char[(off - i) + 1];
		for (j = i ; j < off ; j++) {
		    strdata[j-i] = (data[j] == '\t') ? '\t' : ' ';
		}
		strdata[off-i] = '^';
		output(new String(strdata));
	    }
	} catch (IOException e) {
	    output("I/O exception");
	}
	errors = null;
    }

    /**
     * Report error
     */
    void reportError(Object src, int where, String err, String msg) {
	if (src == null) {
	    if (errorFileName != null) {
		flushErrors();
		errorFileName = null;
	    }
	    output("error: " + msg);
	    nerrors++;

	} else if (src instanceof String) {
	    String fileName = (String)src;

	    if (!fileName.equals(errorFileName)) {
		flushErrors();
		errorFileName = fileName;
	    }
	    if (err.startsWith("warn.")) {
		nwarnings++;
	    } else {
		nerrors++;
	    }
	    insertError(where, msg);

	} else if (src instanceof ClassFile) {
	    reportError(((ClassFile)src).getPath(), where, err, msg);

	} else if (src instanceof Identifier) {
	    reportError(src.toString(), where, err, msg);

	} else if (src instanceof ClassDeclaration) {
	    try {
		reportError(((ClassDeclaration)src).getClassDefinition(this), where, err, msg);
	    } catch (ClassNotFound e) {
		reportError(((ClassDeclaration)src).getName(), where, err, msg);
	    }
	} else if (src instanceof ClassDefinition) {
	    ClassDefinition c = (ClassDefinition)src;
	    if (!err.startsWith("warn.")) {
		c.setError(true);
	    } 
	    reportError(c.getSource(), where, err, msg);

	} else if (src instanceof FieldDefinition) {
	    reportError(((FieldDefinition)src).getClassDeclaration(), where, err, msg);

	} else {
	    output(src + ":error=" + err + ":" + msg);
	}
    }

    /**
     * Issue an error
     */
    public void error(Object source, int where, String err, Object arg1, Object arg2, Object arg3) {
	reportError(source, where, err, errorString(err, arg1, arg2, arg3));
    }

    /**
     * Output a string. This can either be an error message or something
     * for debugging. This should be used instead of println.
     */
    public void output(String msg) {
	try {
	    int len = msg.length();
	    for (int i = 0 ; i < len ; i++) {
		out.write(msg.charAt(i));
	    }
	    out.write('\n');
	} catch (IOException e) {
	}
    }
}