/*
 * Copyright (c) 2001, 2002 The XDoclet team
 * All rights reserved.
 */
package xdoclet;

import java.io.File;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;

import xjavadoc.XClass;

import xdoclet.loader.ModuleFinder;
import xdoclet.tagshandler.AbstractProgramElementTagsHandler;
import xdoclet.tagshandler.ClassTagsHandler;
import xdoclet.tagshandler.PackageTagsHandler;
import xdoclet.template.TemplateException;
import xdoclet.util.LogUtil;

/**
 * Verify if the generation is needed for Java files and Xml files based
 * templates.
 *
 * @author    <a href="mailto:vincent.harcq@hubmethods.com">Vincent Harcq</a>
 * @created   4 mei 2002
 * @version   $Revision: 1.11.2.3 $
 */
public class GenerationManager implements java.io.Serializable {
    /**
     * @todo-javadoc   Describe the field
     */
    private final static File xdocletJar = getXdocletJar();

    /**
     * @todo-javadoc   Describe the field
     */
    private final static File xjavadocJar = getXjavadocJar();

    /**
     * @todo-javadoc   Describe the field
     */
    private static transient Map parserDb = null;

    /**
     * @todo-javadoc   Describe the field
     */
    private boolean guessGenerationNeeded = true;

    /**
     * @todo-javadoc   Describe the field
     */
    private transient TemplateSubTask subTask;

    /**
     * Describe what the GenerationManager constructor does
     *
     * @param subTask  Describe what the parameter does
     * @todo-javadoc   Write javadocs for constructor
     * @todo-javadoc   Write javadocs for method parameter
     */
    public GenerationManager(TemplateSubTask subTask) {
        this.subTask = subTask;
    }

    /**
     * Gets the XdocletJar attribute of the GenerationManager class
     *
     * @return   The XdocletJar value
     */
    public final static File getXdocletJar() {
        String path_str = GenerationManager.class.getProtectionDomain().getCodeSource().getLocation().getFile().toString();

        if (path_str != null && path_str.startsWith("/")) {
            path_str = path_str.substring(1);
        }
        return new File(path_str);
    }

    public static File getXjavadocJar() {
        return ModuleFinder.getJar(XClass.class, "xjavadoc.jar");
    }

    /**
     * During parsing we build the Template database. We store it on file.
     *
     * @param templateURL           the template file
     * @param files                 the merge files involved in the generation
     * @exception XDocletException  Describe the exception
     * @todo-javadoc                Write javadocs for exception
     */
    public static void updateParserDb(URL templateURL, String[] files) throws XDocletException {
        // Merge existing list with new list
        String[] mergeFiles = (String[]) getParserDb().get(new File(templateURL.getFile()).getName());
        List complete = new ArrayList(Arrays.asList(files));

        if (mergeFiles != null) {
            for (int j = 0; j < mergeFiles.length; j++) {
                String file = mergeFiles[j];

                if (!complete.contains(file)) {
                    complete.add(file);
                }
            }
        }
        getParserDb().put(new File(templateURL.getFile()).getName(), complete.toArray(new String[complete.size()]));
    }

    /**
     * Return (and construct) the template database. It is a map between a
     * <code>String</code> representing the template file and an array of
     * <String> representing the merge files that are part of the generation.
     *
     * @return   the <code>Map</code>
     */
    private static Map getParserDb() {
        if (parserDb == null) {
            parserDb = new HashMap();
        }
        return parserDb;
    }

    /**
     * Gets the GuessGenerationNeeded attribute of the GenerationManager object
     *
     * @return   The GuessGenerationNeeded value
     */
    public boolean isGuessGenerationNeeded() {
        return guessGenerationNeeded;
    }

    /**
     * Test if a Java source mmust be generated or not depending of timestamp of
     * elements involved.
     *
     * @param clazz                 the Class from wich we generate
     * @param file                  the File that will be generated
     * @param withTemplate
     * @return                      true if generation is needed
     * @exception XDocletException
     */
    public boolean isGenerationNeeded(XClass clazz, File file, boolean withTemplate) throws XDocletException {
        Log log = LogUtil.getLog(GenerationManager.class, "generation");

        if (subTask.getContext().isForce()) {
            log.debug("Force generation enabled");
            return true;
        }

        if (isGuessGenerationNeeded() == false) {
            log.debug("guessGenerationNeeded enabled");
            return true;
        }

        // 1. Check the classpath for timestamp on XDOCLET JAR and XJAVADOC JAR
        if (file.lastModified() < xdocletJar.lastModified()) {
            log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + xdocletJar.getName());
            return true;
        }

        log.debug("Reject file '" + file.getName() + "' because of timestamp of " + xdocletJar.getName());

        if (file.lastModified() < xjavadocJar.lastModified()) {
            log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + xjavadocJar.getName());
            return true;
        }

        log.debug("Reject file '" + file.getName() + "' because of timestamp of " + xjavadocJar.getName());

        // 2. Check the bean timestamp
        if (file.lastModified() < clazz.lastModified()) {
            log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + clazz.qualifiedName());
            return true;
        }

        log.debug("Reject file '" + clazz.qualifiedName() + "' because of timestamp of " + clazz.qualifiedName());

        // 3. Check the superclasses timestamp
        XClass superclazz = clazz.superclass();

        while (superclazz != null) {
            if (superclazz.qualifiedName().equals("java.lang.Object")) {
                return false;
            }

            if (file.lastModified() < superclazz.lastModified()) {
                log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + superclazz.qualifiedName());
                return true;
            }

            log.debug("Reject file '" + clazz.qualifiedName() + "' because of timestamp of " + superclazz.qualifiedName());

            superclazz = superclazz.superclass();
        }

        // 4. Check the timestamp of template file and merge files
        if (withTemplate) {
            if (isGenerationNeeded(file, subTask.getTemplateURL())) {
                return true;
            }
        }

        return false;
    }

    /**
     * Verify if the generation of a file to generate is needed because either
     * the Template used to generate the file have a later timestamp, or because
     * ALL the Java sources imported in this task have a sooner timestamp. This
     * is used to test if xml files generation is needed.
     *
     * @param file                  The file to check
     * @return                      true if the generation is needed
     * @exception XDocletException
     */
    public boolean isGenerationNeeded(File file) throws XDocletException {
        Log log = LogUtil.getLog(GenerationManager.class, "generation");

        log.debug("Generation need check for " + file.getName());

        if (subTask.getContext().isForce()) {
            log.debug("Force generation enabled");
            return true;
        }

        if (isGuessGenerationNeeded() == false) {
            log.debug("guessGenerationNeeded enabled");
            return true;
        }

        // 1. Check the classpath for timestamp on XDOCLET JAR
        if (file.lastModified() < xdocletJar.lastModified()) {
            log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + xdocletJar.getName());
            return true;
        }

        log.debug("Reject file '" + file.getName() + "' because of timestamp of " + xdocletJar.getName());

        if (file.lastModified() < xjavadocJar.lastModified()) {
            log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + xjavadocJar.getName());
            return true;
        }

        log.debug("Reject file '" + file.getName() + "' because of timestamp of " + xdocletJar.getName());
        log.debug("Generation need check for " + file.getName());

        // 2. Check the timestamp of template file and merge files
        if (isGenerationNeeded(file, subTask.getTemplateURL())) {
            return true;
        }

        log.debug("Generation need check for " + file.getName());

        // 3. Check Timestamp of all java sources in sourcepath
        XClass[] classes = AbstractProgramElementTagsHandler.getAllClasses();

        for (int i = 0; i < classes.length; i++) {
            if (isGenerationNeeded(classes[i], file, false)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Sets the GuessGenerationNeeded attribute of the GenerationManager object
     *
     * @param guessGenerationNeeded  The new GuessGenerationNeeded value
     */
    public void setGuessGenerationNeeded(boolean guessGenerationNeeded) {
        this.guessGenerationNeeded = guessGenerationNeeded;
    }

    /**
     * Verify if the generation of a file is needed because either the template
     * file has a sooner timestamp, or because one of the merge files have a
     * sooner timestamp
     *
     * @param file                  The file to generate
     * @param templateURL           the Template file to use
     * @return                      true if generation is needed.
     * @exception XDocletException
     */
    private boolean isGenerationNeeded(File file, URL templateURL) throws XDocletException {
        Log log = LogUtil.getLog(GenerationManager.class, "xml");

        log.debug("Generation need check for " + file.getName());

        // 1. Check Timestamp of Template file
        if (file.lastModified() < new File(subTask.getTemplateURL().getFile()).lastModified()) {
            log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + subTask.getTemplateURL());
            return true;
        }

        log.debug("Reject file '" + file.getName() + "' because of timestamp of " + subTask.getTemplateURL());

        // 2. Check timestamp of Merge files found inside Template
        String[] files;

        if (getParserDb().get(new File(templateURL.getFile()).getName()) == null) {
            subTask.getParser().setOutput(file);
            subTask.getParser().setTemplateURL(templateURL);

            try {
                subTask.getParser().start();
            }
            catch (TemplateException e) {
                throw new XDocletException(e.toString());
            }

            files = subTask.getParser().getMergeFiles();
            if (files != null) {
                updateParserDb(templateURL, files);
            }
        }
        else {
            files = (String[]) getParserDb().get(new File(templateURL.getFile()).getName());

            // debug a list of the files
            if (log.isDebugEnabled()) {
                for (int i = 0; i < files.length; i++) {
                    log.debug(templateURL.getFile() + " : " + files[i]);
                }
            }
        }

        log.debug("Number of Merge files involved = " + files.length);

        for (int i = 0; i < files.length; i++) {
            String mergeFilePattern = files[i];
            List mergeFiles = new ArrayList();

            if (mergeFilePattern.indexOf("{0}") != -1) {
                XClass[] classes = ClassTagsHandler.getAllClasses();

                for (int j = 0; j < classes.length; j++) {
                    XClass aClass = classes[j];
                    String ejb_name = MessageFormat.format(mergeFilePattern, new Object[]{AbstractProgramElementTagsHandler.getClassNameFor(aClass)});
                    String merge_file_name = PackageTagsHandler.packageNameAsPathFor(aClass.containingPackage()) + File.separator + ejb_name;

                    if (subTask.getMergeDir() != null)
                        mergeFiles.add(new File(subTask.getMergeDir(), merge_file_name));
                }
            }
            else {
                if (subTask.getMergeDir() != null)
                    mergeFiles.add(new File(subTask.getMergeDir(), mergeFilePattern));
            }

            // loop over the merge files
            for (Iterator iterator = mergeFiles.iterator(); iterator.hasNext(); ) {
                File mergeFile = (File) iterator.next();

                log.debug("Generation check for '" + file.getName() + "' because of " + mergeFile.getName());

                if (mergeFile.exists()) {
                    if (file.lastModified() < mergeFile.lastModified()) {
                        log.debug("Generation needed for '" + file.getName() + "' because of timestamp of " + mergeFile.getName());
                        return true;
                    }

                    log.debug("Reject file '" + file.getName() + "' because of timestamp of " + mergeFile.getName());
                }
            }
        }
        return false;
    }
}
