/*
 * RHQ Management Platform
 * Copyright (C) 2005-2010 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package org.rhq.core.util.updater;

import java.io.ByteArrayOutputStream;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.rhq.core.template.TemplateEngine;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.core.util.ZipUtil;
import org.rhq.core.util.stream.StreamUtil;

/**
 * A visitor object that will perform some in-memory work for each zip entry it visits.
 *
 * @author John Mazzitelli
 */
public class InMemoryZipFileVisitor implements ZipUtil.ZipEntryVisitor {
    private final FileHashcodeMap fileHashcodeMap = new FileHashcodeMap();
    private final Pattern filesToRealizeRegex;
    private final TemplateEngine templateEngine;
    private final MessageDigestGenerator hashcodeGenerator;

    /**
     * Creates the visitor. When the visitor hits a zip entry whose name matches
     * filesToRealizeRegex, that zip entry will be realized via the template engine prior
     * to its hashcode being computed. In other words the file's hashcode will be computed
     * on the content after its replacement variables have been replaced.
     * If you just want this visitor to walk a zip file without realizing any files, pass in
     * a null pattern or pass in a null template engine. This will, in effect,
     * have this visitor collect all zip file entry names and calculate their hashcodes based on
     * all content within the zip file.
     *
     * @param filesToRealizeRegex pattern of files that are to be realized prior to hashcodes being computed
     * @param templateEngine the template engine that replaces replacement variables in files to be realized
     */
    public InMemoryZipFileVisitor(Pattern filesToRealizeRegex, TemplateEngine templateEngine) {
        if (filesToRealizeRegex == null || templateEngine == null) {
            filesToRealizeRegex = null;
            templateEngine = null;
        }
        this.filesToRealizeRegex = filesToRealizeRegex;
        this.templateEngine = templateEngine;
        this.hashcodeGenerator = new MessageDigestGenerator();
    }

    /**
     * Returns the file/hashcode data this visitor has collected.
     * @return map containing filenames (zip file entry names) and their hashcodes
     */
    public FileHashcodeMap getFileHashcodeMap() {
        return fileHashcodeMap;
    }

    public boolean visit(ZipEntry entry, ZipInputStream stream) throws Exception {

        if (entry.isDirectory()) {
            return true; // skip directory entries, we only care about the files
        }

        String pathname = entry.getName();
        String hashcode;

        if (this.filesToRealizeRegex != null && this.filesToRealizeRegex.matcher(pathname).matches()) {
            // this entry needs to be realized, do it now, then calc the hashcode
            // note: tempateEngine will never be null if we got here
            int contentSize = (int) entry.getSize();
            ByteArrayOutputStream baos = new ByteArrayOutputStream((contentSize > 0) ? contentSize : 32768);
            StreamUtil.copy(stream, baos, false);
            String content = this.templateEngine.replaceTokens(baos.toString());
            baos = null;
            hashcode = this.hashcodeGenerator.calcDigestString(content);
        } else {
            hashcode = this.hashcodeGenerator.calcDigestString(stream);
        }

        this.fileHashcodeMap.put(pathname, hashcode);
        return true;
    }
}
