CompiledProgram.java
package autumn.lang.compiler;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.mackenziehigh.autumn.resources.Finished;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
/**
* An instance of this class is a compiled Autumn program.
*
* @author Mackenzie High
*/
@Finished("2014/08/19")
public final class CompiledProgram
{
private final String main_class;
private final List<ClassFile> classes = Lists.newLinkedList();
private final List<URL> libraries = Lists.newLinkedList();
/**
* Constructor.
*
* <p>
* The main-class parameter must be null, if no entry-point is specified.
* For example, this would occur when the compiled program is a library only.
* </p>
*
* <p>
* The dependency files will be added to the generated generated Jar file's manifest.
* </p>
*
* @param main_class is the name of the class that contains the program's entry-point.
* @param classes are the classes that the compiled program is composed of.
*/
public CompiledProgram(final String main_class,
final List<ClassFile> classes)
{
Preconditions.checkNotNull(classes);
this.main_class = main_class;
this.classes.addAll(classes);
}
/**
* Constructor.
*
* @param program is the original compiled program.
* @param libraries are the paths to libraries that the program relies upon.
* @throws NullPointerException if program is null.
* @throws NullPointerException if libraries is null.
*/
public CompiledProgram(final CompiledProgram program,
final List<URL> libraries)
{
this(program.main_class, program.classes);
Preconditions.checkNotNull(libraries);
this.libraries.addAll(libraries);
}
/**
* This method retrieves the name of the module that contains the entry-point.
*
* <p>
* This is the fully-qualified name as it would appear in source code.
* </p>
*
* @return the aforedescribed name (may be null).
*/
public String mainClass()
{
return main_class;
}
/**
* This method retrieves the class-files that are part of the compiled program.
*
* @return the program's class-files.
*/
public List<ClassFile> classes()
{
return ImmutableList.copyOf(classes);
}
/**
* This method retrieves the list of libraries that this program relies upon.
*
* @return an immutable list containing the URLs of the aforesaid libraries.
*/
public List<URL> libraries()
{
return ImmutableList.copyOf(libraries);
}
/**
* This method writes this compiled program to a specified JAR file.
*
* <p>
* If the JAR already exists, then it will be overwritten.
* If the JAR does not exist, then it will be created.
* </p>
*
* @param path is the path to the new jar file.
*/
public void jar(final File path)
throws IOException
{
final Manifest manifest = createManifest();
final FileOutputStream fos = new FileOutputStream(path);
final JarOutputStream jos = new JarOutputStream(fos, manifest);
for (ClassFile file : classes)
{
writeClassFile(jos, file);
}
jos.close();
}
/**
* This method creates the MANIFEST.MF file to put into the JAR file.
*
* @return the manifest as an object.
*/
private Manifest createManifest()
{
final Manifest manifest = new Manifest();
/**
* Set the manifest version.
*/
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
/**
* Set the main-class attribute.
*/
if (mainClass() != null)
{
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, mainClass());
}
return manifest;
}
/**
* This method writes a class-file to a JAR file.
*
* @param jos is the the JAR file being written
* @param file is the class-file to write to the JAR file.
*/
private void writeClassFile(final JarOutputStream jos,
final ClassFile file)
throws IOException
{
/**
* Compute the name of the class-file as a regular file.
* In other words, the package part of the name specifies a folder hierarchy.
*/
final String name = file.name().replace('.', '/') + ".class";
/**
* Add the class-file to the jar-file.
*/
final JarEntry entry = new JarEntry(name);
entry.setTime(System.currentTimeMillis());
jos.putNextEntry(entry);
jos.write(file.bytecode());
jos.closeEntry();
}
/**
* This method creates a new class-loader and uses it to load this program.
*
* @param parent is the parent of the new class-loader.
* @return the newly created class-loader with this program loaded into it.
*/
public DynamicLoader load(final ClassLoader parent)
{
final DynamicLoader loader = new DynamicLoader(parent, this);
return loader;
}
/**
* This method creates a new class-loader and uses it to load this program.
*
* <p>
* Note: The parent of the new class-loader is the system's class-loader.
* </p>
*
* @return the newly created class-loader with this program loaded into it.
*/
public DynamicLoader load()
{
return load(ClassLoader.getSystemClassLoader());
}
}