// $Id: incrmnt.cpp,v 1.6 1999/03/10 19:59:21 shields Exp $
copyright notice

#include "config.h"
#include <sys/stat.h>
#include <iostream.h>
#include "bool.h"
#include "control.h"
#include "scanner.h"
#include "parser.h"
#include "semantic.h"
#include "unicode.h"
#include "set.h"

void Control::RemoveTrashedTypes(SymbolSet &type_trash_set)
{
    TypeSymbol *type;

    //
    // For each type T that is going to be trashed, and for each parent P of T that
    // is not itself being trashed, remove T from the set of dependents of P.
    // If T is a subtype of P it is also removed from the subtypes set.
    //
    for (type = (TypeSymbol *) type_trash_set.FirstElement(); type; type = (TypeSymbol *) type_trash_set.NextElement())
    {
        for (TypeSymbol *parent = (TypeSymbol *) type -> parents -> FirstElement();
                         parent;
                         parent = (TypeSymbol *) type -> parents -> NextElement())
        {
            if (! type_trash_set.IsElement(parent))
            {
                parent -> dependents -> RemoveElement(type);
                parent -> subtypes -> RemoveElement(type);
            }
        }
    }

    //
    // We can now safely delete the type.
    //
    for (type = (TypeSymbol *) type_trash_set.FirstElement(); type; type = (TypeSymbol *) type_trash_set.NextElement())
    {
        PackageSymbol *package = type -> ContainingPackage();

        //
        // If a type that is about to be trashed was read in via a class file, remove the class file.
        // Note that invoking RemoveElement for a file that it does not contain has no ill effect.
        //
        FileSymbol *file_symbol = type -> file_symbol;
        if (file_symbol && type -> Identity() == file_symbol -> Identity())
            input_class_file_set.RemoveElement(file_symbol);

        //
        // If a type that is about to be trashed was contained in the unnamed_package,
        // remove it from the set "unnamed_package_types"
        //
        if (package == unnamed_package)
            unnamed_package_types.RemoveElement(type);

        //
        // Remove the type from its containing package.
        //
        package -> DeleteTypeSymbol(type);
    }

    return;
}


inline DirectoryEntry *Control::FindInputFile(FileSymbol *file_symbol)
{
    int length = file_symbol -> Utf8NameLength() + FileSymbol::java_suffix_length;

    char *java_name = new char[length + 1]; // +1 for '\0'
    strcpy(java_name, file_symbol -> Utf8Name());
    strcat(java_name, FileSymbol::java_suffix);

    DirectoryEntry *java_entry = file_symbol -> directory_symbol -> FindEntry(java_name, length);

    delete [] java_name;

    return java_entry;

}


//
// For each file whose associated source (".java") has changed, add it to the list to be recompiled...
//
void Control::FindMoreRecentInputFiles(SymbolSet &file_candidates)
{
    for (FileSymbol *file_symbol = (FileSymbol *) file_candidates.FirstElement();
                     file_symbol;
                     file_symbol = (FileSymbol *) file_candidates.NextElement())
    {
        //
        // If the type is not zipped and it is not already contained in the recompilation set, then check it...
        //
        if ((! file_symbol -> IsZip()) &&
            (! recompilation_file_set.IsElement(file_symbol)) &&
            (! expired_file_set.IsElement(file_symbol)))
        {
            //
            // If there is no java source file or its time stamp is not newer than file_symbol then
            // reset file_symbol to NULL. Otherwise, reset file symbol to the newer file.
            //
            DirectoryEntry *java_entry = FindInputFile(file_symbol);
            if (! java_entry)
            {
                if (file_symbol -> IsJava()) // A source file that was compiled in the previous pass no longer exists.
                    expired_file_set.AddElement(file_symbol);
            }
            else if (java_entry -> Mtime() > file_symbol -> mtime) // a newer file was found
            {
                 file_symbol -> mtime = java_entry -> Mtime();
                 recompilation_file_set.AddElement(file_symbol);
            }
        }
    }

    return;
}


void Control::RereadDirectory(DirectorySymbol *directory_symbol)
{
    directory_symbol -> ResetDirectory();

    for (int i = 0; i < directory_symbol -> subdirectories.Length(); i++)
        RereadDirectory(directory_symbol -> subdirectories[i]);

    return;
}


void Control::RereadDirectories()
{
    for (int i = (dot_classpath_index == 0 ? 0 : 1); i < classpath.Length(); i++)
    {
        PathSymbol *path_symbol = classpath[i];
        if (! path_symbol -> IsZip())
            RereadDirectory(path_symbol -> RootDirectory());
    }

    return;
}


void Control::ComputeRecompilationSet(TypeDependenceChecker &dependence_checker)
{
    SymbolSet type_trash_set;

    //
    // Find out if any source files has been touched since the last compilation and
    // add all such files to recompilation_file_set.
    //
    FindMoreRecentInputFiles(dependence_checker.file_set);

    //
    // Before messing with the files, compute a list of all the types that have just been compiled.
    // We need to do this here as we will be "Resetting" and "reScanning" some files in the loop below,
    // which in effect removes the set of types to which they were associated in the previous compilation.
    //
    int length_estimate = input_java_file_set.Size(); // an estimate of the size of the problem
    Tuple<TypeSymbol *> input_types(length_estimate * 2);
    for (FileSymbol *file_symbol = (FileSymbol *) input_java_file_set.FirstElement();
                     file_symbol;
                     file_symbol = (FileSymbol *) input_java_file_set.NextElement())
    {
        for (int i = 0; i < file_symbol -> types.Length(); i++)
            input_types.Next() = file_symbol -> types[i];
    }

    //
    // Declare the closure set, and initialize it with the Union over the closure of the 
    // types in the trash_bin. Essentially, we want to catch all "compiled" types in the
    // compilation that has a dependence on these bad types.
    //
    SymbolSet dependents_closure(length_estimate);
    for (int i = 0; i < type_trash_bin.Length(); i++)
    {
        TypeSymbol *type = type_trash_bin[i];
        if (! dependents_closure.IsElement(type))
        {
            if (type -> dependents_closure)
                 dependents_closure.Union(*(type -> dependents_closure));
            else dependents_closure.AddElement(type);
        }
    }

    //
    // Compute the set of types from the recompilation set that needs to be recompiled
    // and update the recompilation file set.
    //
    SymbolSet new_set(length_estimate),
              file_seen(length_estimate);
    new_set = recompilation_file_set;
    new_set.Union(expired_file_set);
    file_seen = new_set;

    StoragePool *ast_pool = new StoragePool(64); // how much space do we need for a package declaration? estimate 64 tokens.

    //
    // As long as there is a new_set of files to process,...
    //
    do
    {
        //
        // For each file in new_set, compute the reflexive transitive closure of all types contained in that file.
        // Next, reset and rescan the file. If the scan was successful, iterate over the new list of types to see
        // if any of them had already been introduced in the previous compilation via a class file. If so, add all such
        // types to the dependents closure.
        //
        for (FileSymbol *file_symbol = (FileSymbol *) new_set.FirstElement();
                         file_symbol;
                         file_symbol = (FileSymbol *) new_set.NextElement())
        {
            for (int i = 0; i < file_symbol -> types.Length(); i++)
            {
                TypeSymbol *type = file_symbol -> types[i];
                if (! dependents_closure.IsElement(type))
                {
                    if (type -> dependents_closure)
                         dependents_closure.Union(*(type -> dependents_closure));
                    else dependents_closure.AddElement(type);
                }
            }

            if (! expired_file_set.IsElement(file_symbol))
            {
                file_symbol -> Reset();
                file_symbol -> SetJava();

                scanner -> Scan(file_symbol);

                LexStream *lex_stream = file_symbol -> lex_stream;
                if (lex_stream) // did we have a successful scan!
                {
                    AstPackageDeclaration *package_declaration = parser -> PackageHeaderParse(lex_stream, ast_pool);
                    PackageSymbol *package = (package_declaration
                                                  ? FindOrInsertPackage(lex_stream, package_declaration -> name) : unnamed_package);
                    ast_pool -> Reset();

                    //
                    // If the file contained more than one type, only the main one would have
                    // been deleted. We now delete the others if any...
                    //
                    for (int k = 0; k < lex_stream -> NumTypes(); k++)
                    {
                        LexStream::TokenIndex identifier_token = lex_stream -> Next(lex_stream -> Type(k));
                        if (lex_stream -> Kind(identifier_token) == TK_Identifier)
                        {
                            NameSymbol *name_symbol = (NameSymbol *) lex_stream -> NameSymbol(identifier_token);
                            TypeSymbol *type = package -> FindTypeSymbol(name_symbol);
                            if (type && (! dependents_closure.IsElement(type)))
                            {
                                if (type -> dependents_closure)
                                     dependents_closure.Union(*(type -> dependents_closure));
                                else dependents_closure.AddElement(type);
                            }
                        }
                    }
                }
            }
        }
 
        //
        // Iterate over the dependents_closure set. For each type T, add it to the trash pile.
        // If the file with which it is associated had not yet been processed, mark it as having
        // been "seen" and add it to the new_set to be considered later.
        // If the file had already been processed but not yet added to the recompilation set,
        // add it to the recompilation set, read it in and if it contains types other than the
        // the main one (that had previously been read in via class files) add those new types
        // to the trash pile.
        //
        new_set.SetEmpty();
        for (TypeSymbol *type = (TypeSymbol *) dependents_closure.FirstElement();
                         type;
                         type = (TypeSymbol *) dependents_closure.NextElement())
        {
            type_trash_set.AddElement(type);

            FileSymbol *file_symbol = type -> file_symbol;
            if (file_symbol && (! file_seen.IsElement(file_symbol)))
            {
                file_seen.AddElement(file_symbol);
                new_set.AddElement(file_symbol);
                file_symbol -> mtime = 0; // to force a reread of the file.
            }
        }

        //
        // Check that the files in new_set exist, and if so, add them to the recompilation_file_set.
        // Note that if they exist, they will be added because before a file is added to new_set
        // its time stamp is reset to 0. See loop above...
        //
        FindMoreRecentInputFiles(new_set);

        //
        // Empty out the dependents_closure set for the next round.
        //
        dependents_closure.SetEmpty();
    } while (! new_set.IsEmpty());

    delete ast_pool;

    //
    // Clean up the types that were compiled in the previous compilation pass.
    //
    for (int j = 0; j < input_types.Length(); j++)
        input_types[j] -> RemoveCompilationReferences();

    //
    // Reset the closure sets in all the types that were considered in the dependence checker.
    //
    Tuple<TypeSymbol *> &type_list = dependence_checker.TypeList();
    for (int k = 0; k < type_list.Length(); k++)
    {
        TypeSymbol *type = type_list[k];

        type -> index = CycleChecker::OMEGA;
        type -> incremental_index = CycleChecker::OMEGA;
        delete type -> dependents_closure;
        type -> dependents_closure = NULL;
    }

    //
    // Remove all dependence edges that are no longer valid.
    //
    RemoveTrashedTypes(type_trash_set);

    return;
}


//
// Check whether or not there are files to be rempiled.
//
bool Control::IncrementalRecompilation()
{
    SymbolSet candidates(input_java_file_set.Size() + input_class_file_set.Size() + recompilation_file_set.Size());
    ArgumentExpander *new_arguments = NULL;

    if (! recompilation_file_set.IsEmpty())
        candidates = recompilation_file_set;
    else
    {
        cerr << "\nIncremental Jikes: Press Enter to continue or [Q]uit + Enter to exit: ";
        cerr.flush();

        Tuple<char> line;
        char ch;
        cin.get(ch);
        while(ch != U_ESCAPE && ch != U_LINE_FEED)
        {
            line.Next() = ch;
            cin.get(ch);
        }
        if (ch == U_ESCAPE)
            return false;

        new_arguments = new ArgumentExpander(line);
        char q[2] = {U_q, U_NU};
        if (new_arguments -> argc == 1 && (Case::StringEqual(new_arguments -> argv[0], U8S_quit) || 
                                           Case::StringSegmentEqual(new_arguments -> argv[0], q, 2)))
        {
            delete new_arguments;
            return false;
        }

        candidates = input_java_file_set;
        candidates.Union(input_class_file_set);
    }

    if ((! candidates.IsEmpty()) || (new_arguments && new_arguments -> argc > 0))
    {
        TypeDependenceChecker dependence_checker((Control *) this, candidates, type_trash_bin);
        dependence_checker.PartialOrder();

        //
        // Compute the initial set of files that need to be recompiled. Place them in recompilation_file_set.
        //
        RereadDirectories();

        if (new_arguments)
            ProcessNewInputFiles(recompilation_file_set, *new_arguments);

        ComputeRecompilationSet(dependence_checker);
    }

    //
    // Starting with the initial recompilation_file_set, complete the computation of the
    // set of files that need to be recompiled. (Add all new files to recompilation_file_set)
    // Also, complete the computation of type_trash_set, the set of files that should be
    // removed from the database as they will be recompiled.
    //
    cerr << (recompilation_file_set.IsEmpty() && expired_file_set.IsEmpty() ? "\nnothing changed...\n" : "\nok...\n");
    cerr.flush();

    delete new_arguments;

    return true;
}