Java tutorial
/* * $Id:$ * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved. * * http://izpack.org/ * http://izpack.codehaus.org/ * * Copyright 2007 Klaus Bartz * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.izforge.izpack.compiler.packager.impl; import java.io.File; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.zip.ZipInputStream; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import com.izforge.izpack.api.data.DynamicInstallerRequirementValidator; import com.izforge.izpack.api.data.DynamicVariable; import com.izforge.izpack.api.data.GUIPrefs; import com.izforge.izpack.api.data.Info; import com.izforge.izpack.api.data.InstallerRequirement; import com.izforge.izpack.api.data.Panel; import com.izforge.izpack.api.rules.Condition; import com.izforge.izpack.compiler.compressor.PackCompressor; import com.izforge.izpack.compiler.data.CompilerData; import com.izforge.izpack.compiler.listener.PackagerListener; import com.izforge.izpack.compiler.merge.PanelMerge; import com.izforge.izpack.compiler.merge.CompilerPathResolver; import com.izforge.izpack.compiler.packager.IPackager; import com.izforge.izpack.compiler.stream.JarOutputStream; import com.izforge.izpack.data.CustomData; import com.izforge.izpack.data.PackInfo; import com.izforge.izpack.merge.MergeManager; import com.izforge.izpack.merge.resolve.MergeableResolver; import com.izforge.izpack.util.FileUtil; import com.izforge.izpack.util.IoHelper; /** * The packager base class. The packager interface <code>IPackager</code> is used by the compiler to put files into an installer, and * create the actual installer files. The packager implementation depends on different requirements (e.g. normal packager versus multi volume packager). * This class implements the common used method which can also be overload as needed. * * @author Klaus Bartz */ public abstract class PackagerBase implements IPackager { /** * Path to resources in jar */ public static final String RESOURCES_PATH = "resources/"; /** * Variables. */ private final Properties properties; /** * The listeners. */ private final PackagerListener listener; /** * Executable zipped output stream. First to open, last to close. * Attention! This is our own JarOutputStream, not the java standard! */ private final JarOutputStream installerJar; /** * The merge manager. */ private final MergeManager mergeManager; /** * The path resolver. */ private final CompilerPathResolver pathResolver; /** * The mergeable resolver. */ private final MergeableResolver mergeableResolver; /** * The compression format to be used for pack compression. */ private final PackCompressor compressor; /** * The compiler data. */ private final CompilerData compilerData; /** * Installer requirements. */ private List<InstallerRequirement> installerRequirements; /** * Basic installer info. */ private Info info; /** * GUI preferences. */ private GUIPrefs guiPrefs; /** * Splash screen image. */ private File splashScreenImage; /** * The ordered panels. */ protected List<Panel> panelList = new ArrayList<Panel>(); /** * The ordered pack information. */ private final List<PackInfo> packsList = new ArrayList<PackInfo>(); /** * The ordered language pack locale names. */ private List<String> langpackNameList = new ArrayList<String>(); /** * The ordered custom actions information. */ private List<CustomData> customDataList = new ArrayList<CustomData>(); /** * The language pack URLs keyed by locale name (e.g. de_CH). */ private final Map<String, URL> installerResourceURLMap = new HashMap<String, URL>(); /** * The conditions. */ private final Map<String, Condition> rules = new HashMap<String, Condition>(); /** * Dynamic variables. */ private final Map<String, List<DynamicVariable>> dynamicVariables = new HashMap<String, List<DynamicVariable>>(); /** * Dynamic conditions. */ private List<DynamicInstallerRequirementValidator> dynamicInstallerRequirements = new ArrayList<DynamicInstallerRequirementValidator>(); /** * Jar file URLs who's contents will be copied into the installer. */ private Set<Object[]> includedJarURLs = new HashSet<Object[]>(); /** * Tracks files which are already written into the container file. */ private Map<FilterOutputStream, Set<String>> alreadyWrittenFiles = new HashMap<FilterOutputStream, Set<String>>(); /** * Constructs a <tt>PackagerBase</tt>. * * @param properties the properties * @param listener the packager listener * @param installerJar the installer jar output stream * @param mergeManager the merge manager * @param pathResolver the path resolver * @param mergeableResolver the mergeable resolver * @param compressor the pack compressor * @param compilerData the compiler data */ public PackagerBase(Properties properties, PackagerListener listener, JarOutputStream installerJar, MergeManager mergeManager, CompilerPathResolver pathResolver, MergeableResolver mergeableResolver, PackCompressor compressor, CompilerData compilerData) { this.properties = properties; this.listener = listener; this.installerJar = installerJar; this.mergeManager = mergeManager; this.pathResolver = pathResolver; this.mergeableResolver = mergeableResolver; this.compressor = compressor; this.compilerData = compilerData; } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#addCustomJar(com.izforge.izpack.CustomData, java.net.URL) */ public void addCustomJar(CustomData ca, URL url) { if (ca != null) { customDataList.add(ca); // serialized to keep order/variables correct } if (url != null) { addJarContent(url); // each included once, no matter how many times added } } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#addJarContent(java.net.URL) */ public void addJarContent(URL jarURL) { sendMsg("Adding content of jar: " + jarURL.getFile(), PackagerListener.MSG_VERBOSE); mergeManager.addResourceToMerge(mergeableResolver.getMergeableFromURL(jarURL)); } public void addLangPack(String iso3, URL xmlURL, URL flagURL) { sendMsg("Adding langpack: " + iso3, PackagerListener.MSG_VERBOSE); // put data & flag as entries in installer, and keep array of iso3's // names langpackNameList.add(iso3); addResource("flag." + iso3, flagURL); installerResourceURLMap.put("langpacks/" + iso3 + ".xml", xmlURL); } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#addNativeLibrary(java.lang.String, java.net.URL) */ public void addNativeLibrary(String name, URL url) { sendMsg("Adding native library: " + name, PackagerListener.MSG_VERBOSE); installerResourceURLMap.put("native/" + name, url); } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#addNativeUninstallerLibrary(com.izforge.izpack.CustomData) */ public void addNativeUninstallerLibrary(CustomData data) { customDataList.add(data); // serialized to keep order/variables // correct } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#addPack(com.izforge.izpack.compiler.PackInfo) */ public void addPack(PackInfo pack) { packsList.add(pack); } public void addPanel(Panel panel) { sendMsg("Adding panel: " + panel.getPanelId() + " :: Classname : " + panel.getClassName()); panelList.add(panel); // serialized to keep order/variables correct PanelMerge mergeable = pathResolver.getPanelMerge(panel.getClassName()); mergeManager.addResourceToMerge(mergeable); } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#addResource(java.lang.String, java.net.URL) */ public void addResource(String resId, URL url) { sendMsg("Adding resource: " + resId, PackagerListener.MSG_VERBOSE); installerResourceURLMap.put(resId, url); } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#getPacksList() */ public List<PackInfo> getPacksList() { return packsList; } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#getVariables() */ public Properties getVariables() { return properties; } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#setGUIPrefs(com.izforge.izpack.GUIPrefs) */ public void setGUIPrefs(GUIPrefs prefs) { sendMsg("Setting the GUI preferences", PackagerListener.MSG_VERBOSE); guiPrefs = prefs; } /** * Sets the splash screen image file. * * @param file the splash screen image file. May be <tt>null</tt> */ @Override public void setSplashScreenImage(File file) { splashScreenImage = file; } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#setInfo(com.izforge.izpack.Info) */ public void setInfo(Info info) { sendMsg("Setting the installer information", PackagerListener.MSG_VERBOSE); this.info = info; if (!compressor.useStandardCompression() && compressor.getDecoderMapperName() != null) { this.info.setPackDecoderClassName(compressor.getDecoderMapperName()); } } public Info getInfo() { return info; } /** * @return the rules */ public Map<String, Condition> getRules() { return this.rules; } /** * @return the dynamic variables */ public Map<String, List<DynamicVariable>> getDynamicVariables() { return dynamicVariables; } /** * @return the dynamic conditions */ public List<DynamicInstallerRequirementValidator> getDynamicInstallerRequirements() { return dynamicInstallerRequirements; } public void addInstallerRequirements(List<InstallerRequirement> conditions) { this.installerRequirements = conditions; } /* (non-Javadoc) * @see com.izforge.izpack.compiler.packager.IPackager#createInstaller(java.io.File) */ @Override public void createInstaller() throws Exception { // preliminary work info.setInstallerBase(compilerData.getOutput().replaceAll(".jar", "")); sendStart(); writeInstaller(); // Finish up. closeAlways is a hack for pack compressions other than // default. Some of it (e.g. BZip2) closes the slave of it also. // But this should not be because the jar stream should be open // for the next pack. Therefore an own JarOutputStream will be used // which close method will be blocked. getInstallerJar().closeAlways(); sendStop(); } /** * Determines if each pack is to be included in a separate jar. * * @return <tt>true</tt> if {@link Info#getWebDirURL()} is non-null */ protected boolean packSeparateJars() { return info != null && info.getWebDirURL() != null; } /** * Writes the installer. * * @throws IOException for any I/O error */ protected void writeInstaller() throws IOException { // write the installer jar. MUST be first so manifest is not overwritten by an included jar writeManifest(); writeSkeletonInstaller(); writeInstallerObject("info", info); writeInstallerObject("vars", properties); writeInstallerObject("GUIPrefs", guiPrefs); writeInstallerObject("panelsOrder", panelList); writeInstallerObject("customData", customDataList); writeInstallerObject("langpacks.info", langpackNameList); writeInstallerObject("rules", rules); writeInstallerObject("dynvariables", dynamicVariables); writeInstallerObject("dynconditions", dynamicInstallerRequirements); writeInstallerObject("installerrequirements", installerRequirements); writeInstallerResources(); writeIncludedJars(); // Pack File Data may be written to separate jars writePacks(); } /** * Write manifest in the install jar. * * @throws IOException for any I/O error */ protected void writeManifest() throws IOException { // Add splash screen configuration List<String> lines = IOUtils.readLines(getClass().getResourceAsStream("MANIFEST.MF")); if (splashScreenImage != null) { String destination = String.format("META-INF/%s", splashScreenImage.getName()); mergeManager.addResourceToMerge(splashScreenImage.getAbsolutePath(), destination); lines.add(String.format("SplashScreen-Image: %s", destination)); } lines.add(""); File tempManifest = com.izforge.izpack.util.file.FileUtils.createTempFile("MANIFEST", ".MF"); FileUtils.writeLines(tempManifest, lines); mergeManager.addResourceToMerge(tempManifest.getAbsolutePath(), "META-INF/MANIFEST.MF"); } /** * Write skeleton installer to the installer jar. * * @throws IOException for any I/O error */ protected void writeSkeletonInstaller() throws IOException { sendMsg("Copying the skeleton installer", PackagerListener.MSG_VERBOSE); mergeManager.addResourceToMerge("com/izforge/izpack/installer/"); mergeManager.addResourceToMerge("org/picocontainer/"); mergeManager.addResourceToMerge("com/izforge/izpack/img/"); mergeManager.addResourceToMerge("com/izforge/izpack/bin/"); mergeManager.addResourceToMerge("com/izforge/izpack/api/"); mergeManager.addResourceToMerge("com/izforge/izpack/event/"); mergeManager.addResourceToMerge("com/izforge/izpack/core/"); mergeManager.addResourceToMerge("com/izforge/izpack/data/"); mergeManager.addResourceToMerge("com/izforge/izpack/gui/"); mergeManager.addResourceToMerge("com/izforge/izpack/merge/"); mergeManager.addResourceToMerge("com/izforge/izpack/util/"); mergeManager.addResourceToMerge("org/apache/regexp/"); mergeManager.addResourceToMerge("com/coi/tools/"); mergeManager.addResourceToMerge("org/apache/tools/zip/"); mergeManager.addResourceToMerge("org/apache/commons/io/FilenameUtils.class"); mergeManager.merge(installerJar); } /** * Write an arbitrary object to installer jar. * * @throws IOException for any I/O error */ protected void writeInstallerObject(String entryName, Object object) throws IOException { installerJar.putNextEntry(new org.apache.tools.zip.ZipEntry(RESOURCES_PATH + entryName)); ObjectOutputStream out = new ObjectOutputStream(installerJar); try { out.writeObject(object); } catch (IOException e) { throw new IOException("Error serializing instance of " + object.getClass().getName() + " as entry \"" + entryName + "\"", e); } finally { out.flush(); installerJar.closeEntry(); } } /** * Write the data referenced by URL to installer jar. * * @throws IOException for any I/O error */ protected void writeInstallerResources() throws IOException { sendMsg("Copying " + installerResourceURLMap.size() + " files into installer"); for (Map.Entry<String, URL> stringURLEntry : installerResourceURLMap.entrySet()) { URL url = stringURLEntry.getValue(); InputStream in = url.openStream(); org.apache.tools.zip.ZipEntry newEntry = new org.apache.tools.zip.ZipEntry( RESOURCES_PATH + stringURLEntry.getKey()); long dateTime = FileUtil.getFileDateTime(url); if (dateTime != -1) { newEntry.setTime(dateTime); } installerJar.putNextEntry(newEntry); IoHelper.copyStream(in, installerJar); installerJar.closeEntry(); in.close(); } } /** * Copy included jars to installer jar. * * @throws IOException for any I/O error */ protected void writeIncludedJars() throws IOException { sendMsg("Merging " + includedJarURLs.size() + " jars into installer"); for (Object[] includedJarURL : includedJarURLs) { InputStream is = ((URL) includedJarURL[0]).openStream(); ZipInputStream inJarStream = new ZipInputStream(is); IoHelper.copyZip(inJarStream, installerJar, (List<String>) includedJarURL[1], alreadyWrittenFiles); } } /** * Write packs to the installer jar, or each to a separate jar. * * @throws IOException for any I/O error */ protected abstract void writePacks() throws IOException; /** * Returns the installer jar stream. * * @return the installer jar stream */ protected JarOutputStream getInstallerJar() { return installerJar; } /** * Returns the pack compressor. * * @return the pack compressor */ protected PackCompressor getCompressor() { return compressor; } /** * Dispatches a message to the listeners. * * @param job the job description. */ protected void sendMsg(String job) { sendMsg(job, PackagerListener.MSG_INFO); } /** * Dispatches a message to the listeners at specified priority. * * @param job the job description. * @param priority the message priority. */ protected void sendMsg(String job, int priority) { if (listener != null) { listener.packagerMsg(job, priority); } } /** * Dispatches a start event to the listeners. */ protected void sendStart() { if (listener != null) { listener.packagerStart(); } } /** * Dispatches a stop event to the listeners. */ protected void sendStop() { if (listener != null) { listener.packagerStop(); } } }