Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
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.Manifest;
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.IOUtils;
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.JVMModuleUtil;
import com.redhat.ceylon.common.log.Logger;
import com.redhat.ceylon.compiler.java.loader.CeylonModelLoader;
import com.redhat.ceylon.javax.tools.JavaFileObject;
import com.redhat.ceylon.javax.tools.StandardLocation;
import com.redhat.ceylon.langtools.classfile.Annotation;
import com.redhat.ceylon.langtools.classfile.ClassFile;
import com.redhat.ceylon.langtools.classfile.ConstantPoolException;
import com.redhat.ceylon.langtools.source.util.TaskListener;
import com.redhat.ceylon.langtools.tools.javac.api.MultiTaskListener;
import com.redhat.ceylon.langtools.tools.javac.main.Option;
import com.redhat.ceylon.langtools.tools.javac.util.Log;
import com.redhat.ceylon.langtools.tools.javac.util.Options;
import com.redhat.ceylon.model.loader.AbstractModelLoader;
import com.redhat.ceylon.model.loader.ClassFileUtil;
import com.redhat.ceylon.model.loader.JdkProvider;
import com.redhat.ceylon.model.loader.NamingBase;
import com.redhat.ceylon.model.loader.OsgiUtil;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
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 MultiTaskListener taskListener;
private boolean aptRound;
JarOutputRepositoryManager(Log log, Options options, CeyloncFileManager ceyloncFileManager, MultiTaskListener 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, aptRound);
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();
}
// Not the most elegant solution, we close all JAR files but we only
// rethrow the last exception (if any)
if (ex instanceof IOException) {
throw (IOException)ex;
} else if (ex instanceof RuntimeException) {
throw (RuntimeException)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 FILE_MAPPING = META_INF + "/mapping.txt";
private static final String FILE_ERRORS = META_INF + "/errors.txt";
private static final String FILE_HASHES = META_INF + "/hashes.txt";
private static final String QUOTED_MODULE_DESCRIPTOR = NamingBase.MODULE_DESCRIPTOR_CLASS_NAME + ".class";
private static final String ANNOTATION_COMPILE_ERROR = "com.redhat.ceylon.compiler.java.metadata.CompileTimeError";
/** 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 outputJarTempFolder;
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();
final private Properties entrySourceMapping = new Properties();
private Logger cmrLog;
private Options options;
private RepositoryManager repoManager;
private ArtifactContext carContext;
private ArtifactCreator srcCreator;
private ArtifactCreator resourceCreator;
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 MultiTaskListener taskListener;
private JarEntryManifestFileObject manifest;
private Log log;
private JdkProvider jdkProvider;
private Map> services;
private boolean validModule;
public ProgressiveJar(RepositoryManager repoManager, Module module, Log log,
Options options, CeyloncFileManager ceyloncFileManager,
MultiTaskListener taskListener, boolean aptRound) throws IOException{
this.options = options;
this.repoManager = repoManager;
this.carContext = new ArtifactContext(null, module.getNameAsString(), module.getVersion(), ArtifactContext.CAR);
this.log = log;
this.cmrLog = new JavacLogger(options, Log.instance(ceyloncFileManager.getContext()));
AbstractModelLoader modelLoader = CeylonModelLoader.instance(ceyloncFileManager.getContext());
this.jdkProvider = modelLoader.getJdkProvider();
this.srcCreator = CeylonUtils.makeSourceArtifactCreator(
repoManager,
ceyloncFileManager.getLocation(StandardLocation.SOURCE_PATH),
module.getNameAsString(), module.getVersion(),
options.get(Option.VERBOSE) != null, cmrLog);
this.resourceCreator = CeylonUtils.makeResourceArtifactCreator(
repoManager,
ceyloncFileManager.getLocation(StandardLocation.SOURCE_PATH),
ceyloncFileManager.getLocation(CeylonLocation.RESOURCE_PATH),
options.get(Option.CEYLONRESOURCEROOT),
module.getNameAsString(), module.getVersion(),
options.get(Option.VERBOSE) != null, cmrLog);
this.module = module;
this.writeOsgiManifest = !options.isSet(Option.CEYLONNOOSGI);
this.osgiProvidedBundles = options.get(Option.CEYLONOSGIPROVIDEDBUNDLES);
this.writeMavenManifest = !options.isSet(Option.CEYLONNOPOM) && !module.isDefaultModule();
this.writeJava9Module= options.isSet(Option.CEYLONJIGSAW) && !module.isDefaultModule();
this.services = module.getServices();
// 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(Option.CEYLONRESOURCEROOT);
if (rootName == null) {
rootName = Constants.DEFAULT_RESOURCE_ROOT;
}
this.resourceRootPath = rrp + rootName + "/";
this.taskListener = taskListener;
this.originalJarFile = repoManager.getArtifact(carContext);
this.outputJarTempFolder = FileUtil.makeTempDir("ceylon-compiler-");
this.validModule = module.isDefaultModule() || aptRound;
}
private Map> getPreviousServices() throws IOException {
Map> result;
if (originalJarFile != null) {
result = MetaInfServices.parseAllServices(originalJarFile);
} else {
result = new HashMap>(0);
}
return result;
}
private Properties getPreviousMapping() throws IOException {
return JarUtils.getMetaInfProperties(originalJarFile, FILE_MAPPING);
}
private Properties getPreviousErrors() throws IOException {
return JarUtils.getMetaInfProperties(originalJarFile, FILE_ERRORS);
}
private Properties getPreviousHashes() throws IOException {
return JarUtils.getMetaInfProperties(originalJarFile, FILE_HASHES);
}
private Manifest getPreviousManifest() throws IOException {
JarFile jarFile = JarUtils.validJar(originalJarFile);
if (jarFile != null) {
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);
Manifest manifestObject = null;
// Add META-INF/MANIFEST.MF
if (writeOsgiManifest && manifest == null) {
// Copy the previous manifest
manifestObject = (module.isDefaultModule()
? new OsgiUtil.DefaultModuleManifest()
// using old compiler-generated manifest, so don't worry about conflicts: null logger
: new OsgiUtil.OsgiManifest(module, jdkProvider, osgiProvidedBundles, getPreviousManifest(), null)).build();
} else if (manifest != null && !module.isDefaultModule()) {
// Use the added manifest
manifestObject = manifest.writeManifest(new JavacLogger(options, log));
}
// Add META-INF/.../pom.xml and pom.properties
if (writeMavenManifest) {
writeMavenManifest(outputJarTempFolder, module);
}
// Add module-info.class
if (writeJava9Module && !module.isDefaultModule()) {
writeJava9Module(outputJarTempFolder, module);
}
// Add META-INF/mapping.txt
Properties previousMapping = getPreviousMapping();
JarEntryFilter jarFilter = getJarFilter(previousMapping, copiedSourceFiles);
writeMappingJarEntry(outputJarTempFolder, previousMapping, jarFilter);
// Write services to the META-INF/services
writeServicesJarEntry(outputJarTempFolder);
// Add META-INF/errors.txt
writeErrorsJarEntry(outputJarTempFolder, copiedSourceFiles);
// Add META-INF/hashes.txt
writeHashesJarEntry(outputJarTempFolder);
// Now add the old jar remains
if (originalJarFile != null && JarUtils.isValidJar(originalJarFile)) {
addOriginalJarFiles(outputJarTempFolder, originalJarFile, jarFilter);
}
// We only create the final .car file if we found a module descriptor.
// Otherwise something has gone seriously wrong during compilation and
// we would be generating output that does more harm than good
if (validModule) {
File finalCarFile = IOUtils.zipFolder(manifestObject, outputJarTempFolder);
if (options.isSet(Option.CEYLONPACK200)) {
JarUtils.repack(finalCarFile, cmrLog);
}
File sha1File = ShaSigner.sign(finalCarFile, cmrLog, options.get(Option.VERBOSE) != null);
JarUtils.publish(finalCarFile, sha1File, carContext, repoManager, cmrLog);
String info;
if(module.isDefaultModule())
info = module.getNameAsString();
else
info = module.getNameAsString() + "/" + module.getVersion();
cmrLog.info("Created module " + info);
if(taskListener != null){
for(TaskListener listener : taskListener.getTaskListeners()){
if(listener instanceof CeylonTaskListener){
((CeylonTaskListener) listener).moduleCompiled(module.getNameAsString(), module.getVersion());
}
}
}
}
} catch (RuntimeException e) {
throw e;
} finally {
FileUtil.deleteQuietly(outputJarTempFolder);
}
}
private void addOriginalJarFiles(File outputJarTempFolder, File originalJarFile, JarEntryFilter filter) throws IOException {
try (JarFile j = new JarFile(originalJarFile)) {
Enumeration inEntries = j.entries();
while (inEntries.hasMoreElements()) {
JarEntry entry = inEntries.nextElement();
String name = entry.getName();
// is the file a module descriptor?
if (name.equals(QUOTED_MODULE_DESCRIPTOR)
|| name.endsWith("/" + QUOTED_MODULE_DESCRIPTOR)) {
// Mark this module as being valid (enough)
validModule = true;
}
if (filter != null && filter.avoid(name)) {
continue;
}
File targetFile = new File(outputJarTempFolder, name);
if (!targetFile.exists()) {// first in wins
if(entry.isDirectory())
FileUtil.mkdirs(targetFile);
else{
FileUtil.mkdirs(targetFile.getParentFile());
try (InputStream in = j.getInputStream(entry);
OutputStream out = new FileOutputStream(targetFile)) {
JarUtils.copy(in, out);
}
}
}
}
}
}
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(FILE_MAPPING)
|| entryFullName.equals(FILE_ERRORS)
|| entryFullName.equals(FILE_HASHES)
|| (writeOsgiManifest && OsgiUtil.OsgiManifest.isManifestFileName(entryFullName))
|| (writeMavenManifest && MavenPomUtil.isMavenDescriptor(entryFullName, module));
}
}
};
}
/**
* Add
* {@code META-INF/maven///pom.xml}
* and {@code META-INF/maven///pom.properties}
* entries for the given {@code module}.
*/
private void writeMavenManifest(File outputFolder, Module module) {
MavenPomUtil.writeMavenManifest2(outputFolder, module, jdkProvider);
}
private void writeJava9Module(File outputFolder, Module module) {
Java9Util.writeModuleDescriptor(outputFolder, new Java9Util.Java9ModuleDescriptor(module));
}
/**
* Add {@code META-INF/services/} entries for each of the services
* we've seen, plus all those which were already present
* (incremental compilation).
* @throws IOException
*/
private void writeServicesJarEntry(File outputFolder) throws IOException {
// First convert declarations into their java names
Map> compiledServices = new HashMap>(services.size());
for (Map.Entry> entry : services.entrySet()) {
ClassOrInterface service = entry.getKey();
Set impls = entry.getValue();
HashSet implNames = new HashSet(impls.size());
for (Class impl : impls) {
// TODO quoting
implNames.add(JVMModuleUtil.quoteJavaKeywords(impl.getQualifiedNameString().replace("::", ".")));
}
// TODO quoting (binary name)
compiledServices.put(JVMModuleUtil.quoteJavaKeywords(service.getQualifiedNameString().replace("::", ".")), implNames);
}
// Now figure out which of the previous services we need to keep
Map> previousServices = getPreviousServices();
// Find out which service interfaces have been deleted, and delete those keys from previousServices
// Find out which service implementations have been deleted, and delete those values from previousServices
// Now remove all the old service implementations which we compiled from the values of previousServices
// Now add all the service implementations which we compiled
// TODO for now
for (Map.Entry> e : compiledServices.entrySet()) {
Set set = previousServices.get(e.getKey());
if (set == null) {
previousServices.put(e.getKey(), e.getValue());
} else {
set.addAll(e.getValue());
}
}
FileUtil.mkdirs(new File(outputFolder, META_INF+"/services/"));
MetaInfServices.writeAllServices(outputFolder, previousServices);
}
/**
* Add a {@code META-INF/mapping.txt} entry
* which records which source files generated which .class files
*/
private void writeMappingJarEntry(File outputFolder, 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
FileUtil.mkdirs(new File(outputFolder, META_INF));
try (OutputStream os = new FileOutputStream(new File(outputFolder, FILE_MAPPING))) {
newMapping.store(os, "List of all class files and their originating source files");
} catch (IOException e) {
// TODO : log to the right place
}
}
/**
* Add a {@code META-INF/errors.txt} entry
* which records which .class files have compile errors
* (ie the classes have CompileTimeError annotations)
*/
private void writeErrorsJarEntry(File outputFolder, Set copiedSourceFiles) throws IOException {
Properties newErrors = new Properties();
// Check the new class files for errors
for (String classFullName : writtenClassesMapping.stringPropertyNames()) {
if (hasErrors(new File(outputFolder, classFullName))) {
String sourceFile = writtenClassesMapping.getProperty(classFullName);
newErrors.setProperty(classFullName, sourceFile);
}
}
Properties previousErrors = getPreviousErrors();
if (previousErrors != null) {
// Add the previous error entries that are not related to an updated source file
for (String classFullName : previousErrors.stringPropertyNames()) {
String sourceFile = previousErrors.getProperty(classFullName);
if (!copiedSourceFiles.contains(sourceFile)) {
newErrors.setProperty(classFullName, sourceFile);
}
}
}
// Write the errors file to the Jar
FileUtil.mkdirs(new File(outputFolder, META_INF));
try (OutputStream os = new FileOutputStream(new File(outputFolder, FILE_ERRORS))) {
newErrors.store(os, "List of class files that have compile errors");
} catch(IOException e) {
// TODO : log to the right place
}
}
/**
* Determines if the given .class file has compile errors
* (ie the class has a CompileTimeError annotation)
*/
private boolean hasErrors(File classFile) {
try (InputStream stream = new FileInputStream(classFile)) {
ClassFile cls = ClassFile.read(stream);
Annotation annot = ClassFileUtil.findAnnotation(cls, ANNOTATION_COMPILE_ERROR);
return annot != null;
} catch (ConstantPoolException e) {
return true;
} catch (IOException e) {
return true;
}
}
/**
* Add a {@code META-INF/hashes.txt} entry
* which records the SHA1 hashes for all input files
*/
private void writeHashesJarEntry(File outputFolder) throws IOException {
Properties newHashes = new Properties();
// Add the SHA1 hashes for all the new/updated (re)source files
for (String entryName : entrySourceMapping.stringPropertyNames()) {
File file = new File(entrySourceMapping.getProperty(entryName));
newHashes.setProperty(entryName, ShaSigner.sha1(file));
}
Properties previousHashes = getPreviousHashes();
if (previousHashes != null) {
// Add the previous hash entries that are not related to an updated (re)source file
for (String inputFile : previousHashes.stringPropertyNames()) {
if (!entrySourceMapping.keySet().contains(inputFile)) {
String hash = previousHashes.getProperty(inputFile);
newHashes.setProperty(inputFile, hash);
}
}
}
// Write the errors file to the Jar
FileUtil.mkdirs(new File(outputFolder, META_INF));
try (OutputStream os = new FileOutputStream(new File(outputFolder, FILE_HASHES))) {
newHashes.store(os, "List of input (re)source files and their SHA1 hashes");
} catch(IOException e) {
// TODO : log to the right place
}
}
public JavaFileObject getJavaFileObject(String fileName, File sourceFile) {
String quotedFileName = JVMModuleUtil.quoteJavaKeywordsInFilename(fileName);
String entryName = handleResourceRoot(quotedFileName.replace(File.separatorChar, '/'));
if (sourceFile != null) {
modifiedSourceFiles.add(sourceFile.getPath());
// record the class file we produce so that we don't save it from the original jar
String sourcePath = JarUtils.toPlatformIndependentPath(srcCreator.getPaths(), sourceFile.getPath());
writtenClassesMapping.put(entryName, sourcePath);
entrySourceMapping.put(sourcePath, sourceFile.getPath());
// is the file a module descriptor?
if (Constants.MODULE_DESCRIPTOR.equals(sourceFile.getName())) {
// Mark this module as being valid (enough)
validModule = true;
}
} else {
modifiedResourceFilesRel.add(entryName);
File full = FileUtil.applyPath(resourceCreator.getPaths(), fileName);
// this can be null for APT-generated resources
if (full != null) {
modifiedResourceFilesFull.add(full.getPath());
String resourcePath = JarUtils.toPlatformIndependentPath(resourceCreator.getPaths(), full.getPath());
entrySourceMapping.put(handleResourceRoot(resourcePath), full.getPath());
}
// FIXME: surely this can be written much easily
if (OsgiUtil.CeylonManifest.isManifestFileName(entryName) &&
(module.isDefaultModule() || writeOsgiManifest)) {
manifest = new JarEntryManifestFileObject(outputJarTempFolder, entryName,
module, osgiProvidedBundles, jdkProvider);
return manifest;
}
}
return new JarEntryFileObject(outputJarTempFolder, entryName);
}
private String handleResourceRoot(String entryName) {
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());
}
return entryName;
}
}
public void setAptRound(boolean b) {
// make sure we mark all future modules as valid
this.aptRound = b;
// and past ones too
for (ProgressiveJar jar : openJars.values()) {
jar.validModule = b;
}
}
}