All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.ceylon.compiler.java.tools.JarOutputRepositoryManager Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * Copyright Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the authors tag. All rights reserved.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License version 2.
 * 
 * This particular file is subject to the "Classpath" exception as provided in the 
 * LICENSE file that accompanied this code.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License,
 * along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */

package com.redhat.ceylon.compiler.java.tools;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.ArtifactCreator;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.cmr.ceylon.CeylonUtils;
import com.redhat.ceylon.cmr.impl.ShaSigner;
import com.redhat.ceylon.cmr.util.JarUtils;
import com.redhat.ceylon.cmr.util.JarUtils.JarEntryFilter;
import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.common.log.Logger;
import com.redhat.ceylon.javax.tools.JavaFileObject;
import com.redhat.ceylon.javax.tools.StandardLocation;
import com.redhat.ceylon.langtools.source.util.TaskListener;
import com.redhat.ceylon.langtools.tools.javac.main.OptionName;
import com.redhat.ceylon.langtools.tools.javac.util.Log;
import com.redhat.ceylon.langtools.tools.javac.util.Options;
import com.redhat.ceylon.model.loader.OsgiUtil;
import com.redhat.ceylon.model.typechecker.model.Module;

public class JarOutputRepositoryManager {
    
    private Map openJars = new HashMap();
    private Log log;
    private Options options;
    private CeyloncFileManager ceyloncFileManager;
    private TaskListener taskListener;
    
    JarOutputRepositoryManager(Log log, Options options, CeyloncFileManager ceyloncFileManager, TaskListener taskListener){
        this.log = log;
        this.options = options;
        this.ceyloncFileManager = ceyloncFileManager;
        this.taskListener = taskListener;
    }
    
    public JavaFileObject getFileObject(RepositoryManager repositoryManager, Module module, String fileName, File sourceFile) throws IOException{
        ProgressiveJar progressiveJar = getProgressiveJar(repositoryManager, module);
        return progressiveJar.getJavaFileObject(fileName, sourceFile);
    }
    
    private ProgressiveJar getProgressiveJar(RepositoryManager repositoryManager, Module module) throws IOException {
        ProgressiveJar jarFile = openJars.get(module);
        if(jarFile == null){
            jarFile = new ProgressiveJar(repositoryManager, module, log, options, ceyloncFileManager, taskListener);
            openJars.put(module, jarFile);
        }
        return jarFile;
    }

    public void flush() throws IOException {
        Exception ex = null;
        try{
            for(ProgressiveJar jarFile : openJars.values()){
                try {
                    jarFile.close();
                } catch (Exception e) {
                    ex = e;
                }
            }
        }finally{
            // make sure we clear on return and throw, so we don't try to flush again on throw
            openJars.clear();
        }
        if (ex instanceof IOException) {
            throw (IOException)ex;
        }
    }
    
    /***
     * Manages "updating" an existing jar file with the output from 
     * a compilation.*/
    static class ProgressiveJar {
       /* In fact the Java zip/jar APIs don't support updating, so 
        * we have to use temporary files. 
        * 
        * There's also an unspecified requirement that MANIFEST.MF 
        * come first in the archive (#5750). 
        * Since this can be added at any point via a call to
        * getJavaFileObject() we have to:
        * 
        * * Write manifest.jar to hold just the manifest as and when it gets added
        * * Write the rest of the generated files to rest.jar
        * * Then on close() we generate a final.jar by copying the entries from
        *   - manifest.jar and then
        *   - rest.jar
        *   - and then stuff from original.car
        * * And finally rename final.jar to original.car
        */
        private static final String META_INF = "META-INF";
        private static final String MAPPING_FILE = META_INF+"/mapping.txt";
        /** Jar we're "updating" (i.e. will copy any entries not added to the output */
        private File originalJarFile;
        /** Jar we're actually generating, but doesn't include the MANIFEST.MF */
        private File outputJarFile;
        /** Output stream for the {@link #outputJarFile} */
        private JarOutputStream jarOutputStream;
        final private Set modifiedSourceFiles = new HashSet();
        final private Set modifiedResourceFilesRel = new HashSet();
        final private Set modifiedResourceFilesFull = new HashSet();
        /** Mapping of class file name to originating source file name*/
        final private Properties writtenClassesMapping = new Properties(); 
        private Logger cmrLog;
        private Options options;
        private RepositoryManager repoManager;
        private ArtifactContext carContext;
        private ArtifactCreator srcCreator;
        private ArtifactCreator resourceCreator;
        final private Set foldersToAdd = new HashSet();
        private Module module;
        /** Whether to generate an OSGi-compatible MANIFEST.MF */
        private boolean writeOsgiManifest;
        /** The bundles to treat as "provided", thus omitted from {@code Required-Bundle:} */
        private String osgiProvidedBundles;
        private final String resourceRootPath;
        /** Whether to add a pom.xml and pom.properties in module subdir of {@code META-INF}*/
        private boolean writeMavenManifest;
        /** Whether to add a module-info.class file */
        private boolean writeJava9Module;
        private TaskListener taskListener;
        private JarEntryManifestFileObject manifest;
        private Log log;

        public ProgressiveJar(RepositoryManager repoManager, Module module, Log log, Options options, CeyloncFileManager ceyloncFileManager, TaskListener taskListener) throws IOException{
            this.options = options;
            this.repoManager = repoManager;
            this.carContext = new ArtifactContext(module.getNameAsString(), module.getVersion(), ArtifactContext.CAR);
            this.log = log;
            this.cmrLog = new JavacLogger(options, Log.instance(ceyloncFileManager.getContext()));
            this.srcCreator = CeylonUtils.makeSourceArtifactCreator(
                    repoManager,
                    ceyloncFileManager.getLocation(StandardLocation.SOURCE_PATH),
                    module.getNameAsString(), module.getVersion(),
                    options.get(OptionName.VERBOSE) != null, cmrLog);
            this.resourceCreator = CeylonUtils.makeResourceArtifactCreator(
                    repoManager,
                    ceyloncFileManager.getLocation(StandardLocation.SOURCE_PATH),
                    ceyloncFileManager.getLocation(CeylonLocation.RESOURCE_PATH),
                    options.get(OptionName.CEYLONRESOURCEROOT),
                    module.getNameAsString(), module.getVersion(),
                    options.get(OptionName.VERBOSE) != null, cmrLog);
            this.module = module;
            this.writeOsgiManifest = !options.isSet(OptionName.CEYLONNOOSGI);
            this.osgiProvidedBundles = options.get(OptionName.CEYLONOSGIPROVIDEDBUNDLES);
            this.writeMavenManifest = !options.isSet(OptionName.CEYLONNOPOM) && !module.isDefault();
            this.writeJava9Module= options.isSet(OptionName.CEYLONJIGSAW) && !module.isDefault();
            
            // Determine the special path that signals that the files it contains
            // should be moved to the root of the output JAR/CAR
            String rrp = module.getNameAsString().replace('.', '/');
            if (!rrp.isEmpty() && !rrp.endsWith("/")) {
                rrp = rrp + "/";
            }
            String rootName = options.get(OptionName.CEYLONRESOURCEROOT);
            if (rootName == null) {
                rootName = Constants.DEFAULT_RESOURCE_ROOT;
            }
            this.resourceRootPath = rrp + rootName + "/";
            this.taskListener = taskListener;
            
            this.originalJarFile = repoManager.getArtifact(carContext);
            this.outputJarFile = File.createTempFile("ceylon-compiler-", ".car");
            this.jarOutputStream = new JarOutputStream(new FileOutputStream(outputJarFile));
        }

        private Properties getPreviousMapping() throws IOException {
            if (originalJarFile != null) {
                JarFile jarFile = null;
                jarFile = new JarFile(originalJarFile);
                try {
                    JarEntry entry = jarFile.getJarEntry(MAPPING_FILE);
                    if (entry != null) {
                        InputStream inputStream = jarFile.getInputStream(entry);
                        try {
                            Properties previousMapping = new Properties();
                            previousMapping.load(inputStream);
                            return previousMapping;
                        } finally {
                            inputStream.close();
                        }
                    }
                } finally {
                    jarFile.close();
                }
            }
            return null;
        }

        private Manifest getPreviousManifest() throws IOException {
            if (originalJarFile != null) {
                JarFile jarFile = null;
                jarFile = new JarFile(originalJarFile);
                try {
                    return jarFile.getManifest();
                } finally {
                    jarFile.close();
                }
            }
            return null;
        }

        public void close() throws IOException {
            try {
                // Create the .src archive
                Set copiedSourceFiles = srcCreator.copy(modifiedSourceFiles);
                resourceCreator.copy(modifiedResourceFilesFull);
                
                jarOutputStream.flush();
                jarOutputStream.close();
                
                // Create a jar with the MANIFEST.MF first
                File metaFirstFile = File.createTempFile("compile", "car");
                JarOutputStream manifestFirst = new JarOutputStream(new FileOutputStream(metaFirstFile));
                
                // Add META-INF/
                Set foldersAdded = new HashSet();
                JarUtils.makeFolder(foldersAdded, manifestFirst, META_INF+"/");
                
                // Add META-INF/MANIFEST.MF
                if (writeOsgiManifest && manifest == null) {
                    // Copy the previous manifest
                    Manifest manifest = (module.isDefault() 
                    		? new OsgiUtil.DefaultModuleManifest() 
                                    // using old compiler-generated manifest, so don't worry about conflicts: null logger 
                    	    : new OsgiUtil.OsgiManifest(module, getPreviousManifest(), osgiProvidedBundles, null)).build();
                    writeManifestJarEntry(manifestFirst, manifest);
                } else if (manifest != null && !module.isDefault()) {
                    // Use the added manifest
                    manifest.writeManifest(manifestFirst, new JavacLogger(options, log));
                }
                
                // Add META-INF/.../pom.xml and pom.properties
                if (writeMavenManifest) {
                    writeMavenManifest(foldersAdded, manifestFirst, module);
                }

                // Add module-info.class
                if (writeJava9Module && !module.isDefault()) {
                    writeJava9Module(foldersAdded, manifestFirst, module);
                }

                // Add META-INF/mapping.txt
                Properties previousMapping = getPreviousMapping();
                JarEntryFilter jarFilter = getJarFilter(previousMapping, copiedSourceFiles);
                writeMappingJarEntry(manifestFirst, foldersAdded, previousMapping, jarFilter);
                
                manifestFirst.close();
                
                File finalCarFile = File.createTempFile("ceylon-compiler-", ".car");
                JarCat jc = new JarCat(finalCarFile);
                jc.cat(metaFirstFile);
                jc.cat(outputJarFile);
                jc.cat(originalJarFile, jarFilter);
                jc.close();
                FileUtil.deleteQuietly(metaFirstFile);
            
                if (options.isSet(OptionName.CEYLONPACK200)) {
                    JarUtils.repack(finalCarFile, cmrLog);
                }
                File sha1File = ShaSigner.sign(finalCarFile, cmrLog, options.get(OptionName.VERBOSE) != null);
                JarUtils.publish(finalCarFile, sha1File, carContext, repoManager, cmrLog);
                
                String info;
                if(module.isDefault())
                    info = module.getNameAsString();
                else
                    info = module.getNameAsString() + "/" + module.getVersion();
                cmrLog.info("Created module " + info);
                if(taskListener instanceof CeylonTaskListener){
                    ((CeylonTaskListener) taskListener).moduleCompiled(module.getNameAsString(), module.getVersion());
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } catch (RuntimeException e) {
                throw e;
            } finally {
                FileUtil.deleteQuietly(outputJarFile);
            }
        }

        private JarUtils.JarEntryFilter getJarFilter(final Properties previousMapping, final Set copiedSourceFiles) {
            return new JarUtils.JarEntryFilter() {
                @Override
                public boolean avoid(String entryFullName) {
                    if (entryFullName.endsWith(".class")) {
                        boolean classWasUpdated = writtenClassesMapping.containsKey(entryFullName);
                        if (previousMapping != null) {
                            String sourceFileForClass = previousMapping.getProperty(entryFullName);
                            classWasUpdated = classWasUpdated || copiedSourceFiles.contains(sourceFileForClass);
                        }
                        return classWasUpdated;
                    } else {
                        return modifiedResourceFilesRel.contains(entryFullName)
                                || entryFullName.equals(MAPPING_FILE)
                                || (writeOsgiManifest && OsgiUtil.OsgiManifest.isManifestFileName(entryFullName))
                                || (writeMavenManifest && MavenPomUtil.isMavenDescriptor(entryFullName, module));
                    }
                }
            };
        }

        /** Add a {@code META-INF/MANIFEST.MF} entry using the given {@code manifest} */
        private static void writeManifestJarEntry(JarOutputStream out, Manifest manifest) {
            try {
                out.putNextEntry(new ZipEntry(OsgiUtil.OsgiManifest.MANIFEST_FILE_NAME));
                manifest.write(out);
            }
            catch (IOException e) {
                // TODO : log to the right place
            }
            finally {
                try {
                    out.closeEntry();
                }
                catch (IOException ignore) {
                }
            }
        }
        
        /** 
         * Add 
         * {@code META-INF/maven///pom.xml}
         * and {@code META-INF/maven///pom.properties}
         * entries for the given {@code module}.
         * @param manifestFirst 
         */
        private void writeMavenManifest(Set foldersAlreadyAdded, JarOutputStream manifestFirst, Module module) {
            MavenPomUtil.writeMavenManifest2(manifestFirst, module, foldersAlreadyAdded);
        }

        private void writeJava9Module(Set foldersAlreadyAdded, JarOutputStream manifestFirst, Module module) {
        	Java9Util.writeModuleDescriptor(manifestFirst, new Java9Util.Java9ModuleDescriptor(module));
        }

        /** 
         * Add a {@code META-INF/mapping.txt} entry
         * which records which source files generated which .class files
         * @param outputStream 
         */
        private void writeMappingJarEntry(JarOutputStream outputStream, Set foldersAlreadyAdded, Properties previousMapping, JarUtils.JarEntryFilter filter) {
            Properties newMapping = new Properties();
            newMapping.putAll(writtenClassesMapping);
            if (previousMapping != null) {
                // Add the previous mapping entries that are not related to an updated source file 
                for (String classFullName : previousMapping.stringPropertyNames()) {
                    if (!filter.avoid(classFullName)) {
                        newMapping.setProperty(classFullName, previousMapping.getProperty(classFullName));
                    }
                }
            }
            // Write the mapping file to the Jar
            try {
                JarUtils.makeFolder(foldersAlreadyAdded, outputStream, META_INF+"/");
                outputStream.putNextEntry(new ZipEntry(MAPPING_FILE));
                newMapping.store(outputStream, "");
            }
            catch(IOException e) {
                // TODO : log to the right place
            }
            finally {
                try {
                    outputStream.closeEntry();
                } catch (IOException e) {
                }
            }
        }

        public JavaFileObject getJavaFileObject(String fileName, File sourceFile) {
            String entryName = fileName.replace(File.separatorChar, '/');
            
            if (!resourceRootPath.isEmpty() && entryName.startsWith(resourceRootPath)) {
                // Files in the special "resource root path" get moved
                // to the root of the output JAR/CAR
                entryName = entryName.substring(resourceRootPath.length());
            }
            
            String folder = JarUtils.getFolder(entryName);
            if (folder != null) {
                foldersToAdd.add(folder);
            }

            if (sourceFile != null) {
                modifiedSourceFiles.add(sourceFile.getPath());
                // record the class file we produce so that we don't save it from the original jar
            	addMappingEntry(entryName, JarUtils.toPlatformIndependentPath(srcCreator.getPaths(), sourceFile.getPath()));
            } else {
                modifiedResourceFilesRel.add(entryName);
                modifiedResourceFilesFull.add(FileUtil.applyPath(resourceCreator.getPaths(), fileName).getPath());
                if (OsgiUtil.CeylonManifest.isManifestFileName(entryName) && 
                        (module.isDefault() || writeOsgiManifest)) {
                    manifest = new JarEntryManifestFileObject(outputJarFile.getPath(), entryName, module, osgiProvidedBundles);
                    return manifest;
                }
            }
            return new JarEntryFileObject(outputJarFile.getPath(), jarOutputStream, entryName);
        }

        private void addMappingEntry(String className,
                String sourcePath) {
            writtenClassesMapping.put(className, sourcePath);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy