OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 73 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.erp.modules;

import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.StreamUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipFile;

import net.jcip.annotations.ThreadSafe;

/**
 * Package a module from its properties and classes.
 * 
 * @author Sylvain CUAZ
 */
public class ModulePackager {
    public static final String MODULE_PROPERTIES_PATH = "META-INF/openConcertoModule.properties";

    private final List<File> classesDirs;
    private final List<File> jars = new ArrayList<File>();
    private final File propsFile;
    private final Set<String> dirEntries, fileEntries;
    private boolean skipDuplicateFiles;

    public ModulePackager(final File propsFile, final File classes) {
        this.propsFile = propsFile;
        this.classesDirs = new ArrayList<File>(8);
        this.classesDirs.add(classes);
        this.dirEntries = new HashSet<String>();
        this.fileEntries = new HashSet<String>();
        this.setSkipDuplicateFiles(false);
    }

    public final void setSkipDuplicateFiles(boolean skipDuplicateFiles) {
        this.skipDuplicateFiles = skipDuplicateFiles;
    }

    public final boolean getSkipDuplicateFiles() {
        return this.skipDuplicateFiles;
    }

    public final void addDir(final File classesDir) {
        this.classesDirs.add(classesDir);
    }

    public final void addJarsFromDir(final File dir) {
        if (!dir.exists())
            throw new IllegalArgumentException("Directory " + dir.getAbsolutePath() + " not found");
        final File[] jarFiles = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isFile() && f.getName().endsWith(".jar");
            }
        });
        for (final File jarFile : jarFiles) {
            addJarUnsafe(jarFile);
        }
    }

    public final void addJar(final File jarFile) {
        if (!jarFile.isFile())
            throw new IllegalArgumentException("File " + jarFile.getAbsolutePath() + " not found");
        if (!jarFile.getName().endsWith(".jar"))
            throw new IllegalArgumentException("File " + jarFile.getAbsolutePath() + " is not a jar file");
        addJarUnsafe(jarFile);
    }

    private final void addJarUnsafe(File jarFile) {
        this.jars.add(jarFile);
    }

    /**
     * Write the package to the passed directory.
     * 
     * @param dir where to create the package (its name will contain its id and version).
     * @return the created jar.
     * @throws IOException if the package cannot be created.
     */
    public final File writeToDir(final File dir) throws IOException {
        final RuntimeModuleFactory f = new RuntimeModuleFactory(this.propsFile);
        final File jarFile = new File(dir, f.getID() + "-" + f.getMajorVersion() + "." + f.getMinorVersion() + ".jar");
        final JarOutputStream jarStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile)));
        this.dirEntries.clear();
        this.fileEntries.clear();
        try {
            if (!this.zipExistingFile(jarStream, this.propsFile, MODULE_PROPERTIES_PATH))
                throw new IllegalStateException("Missing properties file : " + this.propsFile);
            for (File jarFileToAdd : this.jars) {
                this.zipJarFile(jarStream, jarFileToAdd);
            }
            for (final File classesDir : this.classesDirs) {
                this.zipBelow(jarStream, classesDir);
            }
        } finally {
            jarStream.close();
            this.dirEntries.clear();
            this.fileEntries.clear();
        }
        return jarFile;
    }

    // doesn't zip f
    protected void zipBelow(JarOutputStream jarStream, File f) throws IOException {
        this.zipRec(jarStream, f, "");
    }

    private String getPath(File f) throws IOException {
        String res = f.getAbsolutePath();
        if (f.isDirectory() && !res.endsWith("/"))
            res += '/';
        return res;
    }

    private String getEntryName(File f, File base) throws IOException {
        // needed otherwise we could pass ('abc', 'a')
        // but if base is a directory then its path is 'a/'
        if (!base.isDirectory())
            throw new IllegalArgumentException("Base not a directory : " + base);
        final String fPath = getPath(f);
        final String basePath = getPath(base);
        if (!fPath.startsWith(basePath))
            throw new IllegalArgumentException("Base is not a parent :\n" + base + "\n" + f);
        return fPath.substring(basePath.length()).replace('\\', '/');
    }

    protected void zipRec(JarOutputStream jarStream, File f, File base) throws IOException {
        zipRec(jarStream, f, getEntryName(f, base));
    }

    protected void zipRec(JarOutputStream jarStream, File f, final String entryName) throws IOException {
        if (entryName.length() > 0)
            this.zipExistingFile(jarStream, f, entryName);
        if (f.isDirectory())
            for (final File child : f.listFiles()) {
                this.zipRec(jarStream, child, entryName + '/' + child.getName());
            }
    }

    protected boolean zipExistingFile(JarOutputStream jarStream, File f, File base) throws IOException {
        return this.zipExistingFile(jarStream, f, getEntryName(f, base));
    }

    protected boolean zipExistingFile(JarOutputStream jarStream, File f, final String entryName) throws IOException {
        if (!f.exists())
            return false;
        final boolean isDir = f.isDirectory();

        String name = entryName;
        if (name.startsWith("/"))
            name = name.substring(1);
        if (isDir && !name.endsWith("/"))
            name += "/";
        final JarEntry entry = new JarEntry(name);
        entry.setTime(f.lastModified());
        if (!isDir)
            entry.setSize(f.length());

        final InputStream in = isDir ? null : new BufferedInputStream(new FileInputStream(f));
        try {
            zip(jarStream, entry, in);
        } finally {
            if (in != null)
                in.close();
        }
        return true;
    }

    private void zip(JarOutputStream jarStream, final JarEntry entry, InputStream in) throws IOException {
        // ignore duplicate directories
        final boolean isDir = entry.isDirectory();
        if (isDir && !this.dirEntries.add(entry.getName()))
            return;
        if (!isDir && this.getSkipDuplicateFiles() && !this.fileEntries.add(entry.getName()))
            return;

        jarStream.putNextEntry(entry);
        if (in != null) {
            StreamUtils.copy(in, jarStream);
        }
        jarStream.closeEntry();
    }

    private void zipJarFile(JarOutputStream out, File file) throws IOException {
        final JarFile f = new JarFile(file, false, ZipFile.OPEN_READ);
        try {
            final Enumeration<JarEntry> entries = f.entries();
            while (entries.hasMoreElements()) {
                final JarEntry e = entries.nextElement();
                if (!e.getName().startsWith("META-INF")) {
                    // use copy-constructor to keep all fields
                    zip(out, new JarEntry(e.getName()), f.getInputStream(e));
                }
            }
        } finally {
            f.close();
        }
    }

    @ThreadSafe
    public static class ModuleFiles {
        private final File baseDir;
        private final File propsFile;

        public ModuleFiles(final File baseDir, final String propsFileName) {
            this.baseDir = baseDir;
            this.propsFile = new File(this.baseDir, propsFileName == null ? "module.properties" : propsFileName);
        }

        public final File getBaseDir() {
            return this.baseDir;
        }

        public final File getPropertiesFile() {
            return this.propsFile;
        }

        public File getClassesDir() {
            return new File(this.getBaseDir(), "bin");
        }

        public File getLibrariesDir() {
            return new File(this.getBaseDir(), "lib");
        }

        public File getDistDir() {
            return new File(this.getBaseDir(), "dist");
        }
    }

    public static File createDist(final File moduleDir) throws IOException {
        return createDist(new ModuleFiles(moduleDir, null));
    }

    public static File createDist(final ModuleFiles files) throws IOException {
        final File distDir = files.getDistDir();
        FileUtils.mkdir_p(distDir);
        final ModulePackager pkger = new ModulePackager(files.getPropertiesFile(), files.getClassesDir());
        final File libDir = files.getLibrariesDir();
        if (libDir.exists()) {
            pkger.setSkipDuplicateFiles(true);
            pkger.addJarsFromDir(libDir);
        }
        return pkger.writeToDir(distDir);
    }

    public static void main(String[] args) throws IOException {
        if (args.length != 1) {
            System.err.println("Usage : " + ModulePackager.class.getName() + " moduleBaseDir");
            System.exit(1);
        }
        final File jar = createDist(ModuleLauncher.createModuleFiles(args[0]));
        System.out.println("Created " + jar.getCanonicalPath());
    }
}