| /******************************************************************************* |
| * Copyright (c) 2006, 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.internal.provisional.equinox.p2.jarprocessor; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.util.jar.*; |
| import org.eclipse.equinox.internal.p2.jarprocessor.*; |
| |
| public class JarProcessor { |
| public static final String PACKED_SUFFIX = "pack.gz"; //$NON-NLS-1$ |
| |
| private List steps = new ArrayList(); |
| private String workingDirectory = ""; //$NON-NLS-1$ |
| private int depth = -1; |
| private boolean verbose = false; |
| private boolean processAll = false; |
| private LinkedList containingInfs = new LinkedList(); |
| |
| static public JarProcessor getUnpackProcessor(Properties properties) { |
| if (!canPerformUnpack()) |
| throw new UnsupportedOperationException(); |
| JarProcessor processor = new JarProcessor(); |
| processor.addProcessStep(new UnpackStep(properties)); |
| return processor; |
| } |
| |
| static public JarProcessor getPackProcessor(Properties properties) { |
| if (!canPerformPack()) |
| throw new UnsupportedOperationException(); |
| JarProcessor processor = new JarProcessor(); |
| processor.addProcessStep(new PackStep(properties)); |
| return processor; |
| } |
| |
| static public boolean canPerformPack() { |
| return PackStep.canPack(); |
| } |
| |
| static public boolean canPerformUnpack() { |
| return UnpackStep.canUnpack(); |
| } |
| |
| public String getWorkingDirectory() { |
| return workingDirectory; |
| } |
| |
| public void setWorkingDirectory(String dir) { |
| if (dir != null) |
| workingDirectory = dir; |
| } |
| |
| public void setVerbose(boolean verbose) { |
| this.verbose = verbose; |
| } |
| |
| public void setProcessAll(boolean all) { |
| this.processAll = all; |
| } |
| |
| public void addProcessStep(IProcessStep step) { |
| steps.add(step); |
| } |
| |
| public void clearProcessSteps() { |
| steps.clear(); |
| } |
| |
| public Iterator getStepIterator() { |
| return steps.iterator(); |
| } |
| |
| /** |
| * Recreate a jar file. The replacements map specifies entry names to be replaced, the replacements are |
| * expected to be found in directory. |
| * |
| * @param jar - The input jar |
| * @param outputJar - the output |
| * @param replacements - map of entryName -> new entryName |
| * @param directory - location to find file for new entryName |
| * @throws IOException |
| */ |
| private void recreateJar(JarFile jar, JarOutputStream outputJar, Map replacements, File directory, Properties inf) throws IOException { |
| InputStream in = null; |
| boolean marked = false; |
| try { |
| Enumeration entries = jar.entries(); |
| for (JarEntry entry = (JarEntry) entries.nextElement(); entry != null; entry = entries.hasMoreElements() ? (JarEntry) entries.nextElement() : null) { |
| File replacement = null; |
| JarEntry newEntry = null; |
| if (replacements.containsKey(entry.getName())) { |
| String name = (String) replacements.get(entry.getName()); |
| replacement = new File(directory, name); |
| if (name != null) { |
| if (replacement.exists()) { |
| try { |
| in = new BufferedInputStream(new FileInputStream(replacement)); |
| newEntry = new JarEntry(name); |
| } catch (Exception e) { |
| if (verbose) { |
| e.printStackTrace(); |
| System.out.println("Warning: Problem reading " + replacement.getPath() + ", using " + jar.getName() + File.separator + entry.getName() + " instead."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| } else if (verbose) { |
| System.out.println("Warning: " + replacement.getPath() + " not found, using " + jar.getName() + File.separator + entry.getName() + " instead."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| } |
| if (newEntry == null) { |
| try { |
| in = new BufferedInputStream(jar.getInputStream(entry)); |
| newEntry = new JarEntry(entry.getName()); |
| } catch (Exception e) { |
| if (verbose) { |
| e.printStackTrace(); |
| System.out.println("ERROR: problem reading " + entry.getName() + " from " + jar.getName()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| continue; |
| } |
| } |
| newEntry.setTime(entry.getTime()); |
| outputJar.putNextEntry(newEntry); |
| if (entry.getName().equals(Utils.MARK_FILE_NAME)) { |
| //The eclipse.inf file was read in earlier, don't need to reread it, just write it out now |
| Utils.storeProperties(inf, outputJar); |
| marked = true; |
| } else { |
| Utils.transferStreams(in, outputJar, false); |
| } |
| outputJar.closeEntry(); |
| in.close(); |
| |
| //delete the nested jar file |
| if (replacement != null) { |
| replacement.delete(); |
| } |
| } |
| if (!marked) { |
| JarEntry entry = new JarEntry(Utils.MARK_FILE_NAME); |
| outputJar.putNextEntry(entry); |
| Utils.storeProperties(inf, outputJar); |
| outputJar.closeEntry(); |
| } |
| } finally { |
| Utils.close(outputJar); |
| Utils.close(jar); |
| Utils.close(in); |
| } |
| } |
| |
| private String recursionEffect(String entryName) { |
| String result = null; |
| for (Iterator iter = steps.iterator(); iter.hasNext();) { |
| IProcessStep step = (IProcessStep) iter.next(); |
| |
| result = step.recursionEffect(entryName); |
| if (result != null) |
| entryName = result; |
| } |
| return result; |
| } |
| |
| private void extractEntries(JarFile jar, File tempDir, Map data, Properties inf) throws IOException { |
| if (inf != null) { |
| //skip if excluding children |
| if (inf.containsKey(Utils.MARK_EXCLUDE_CHILDREN)) { |
| String excludeChildren = inf.getProperty(Utils.MARK_EXCLUDE_CHILDREN); |
| if (Boolean.valueOf(excludeChildren).booleanValue()) |
| if (verbose) { |
| for (int i = 0; i <= depth; i++) |
| System.out.print(" "); //$NON-NLS-1$ |
| System.out.println("Children of " + jar.getName() + "are excluded from processing."); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| return; |
| } |
| } |
| |
| Enumeration entries = jar.entries(); |
| if (entries.hasMoreElements()) { |
| for (JarEntry entry = (JarEntry) entries.nextElement(); entry != null; entry = entries.hasMoreElements() ? (JarEntry) entries.nextElement() : null) { |
| String name = entry.getName(); |
| String newName = recursionEffect(name); |
| if (newName != null) { |
| if (verbose) { |
| for (int i = 0; i <= depth; i++) |
| System.out.print(" "); //$NON-NLS-1$ |
| System.out.println("Processing nested file: " + name); //$NON-NLS-1$ |
| } |
| //extract entry to temp directory |
| File extracted = new File(tempDir, name); |
| File parentDir = extracted.getParentFile(); |
| if (!parentDir.exists()) |
| parentDir.mkdirs(); |
| |
| InputStream in = null; |
| OutputStream out = null; |
| try { |
| in = jar.getInputStream(entry); |
| out = new BufferedOutputStream(new FileOutputStream(extracted)); |
| Utils.transferStreams(in, out, true); //this will close both streams |
| } finally { |
| Utils.close(in); |
| Utils.close(out); |
| } |
| extracted.setLastModified(entry.getTime()); |
| |
| //recurse |
| String dir = getWorkingDirectory(); |
| try { |
| containingInfs.addFirst(inf); |
| setWorkingDirectory(parentDir.getCanonicalPath()); |
| File result = processJar(extracted); |
| newName = name.substring(0, name.length() - extracted.getName().length()) + result.getName(); |
| data.put(name, newName); |
| } finally { |
| setWorkingDirectory(dir); |
| containingInfs.removeFirst(); |
| } |
| |
| //delete the extracted item leaving the recursion result |
| if (!name.equals(newName)) |
| extracted.delete(); |
| } |
| } |
| } |
| } |
| |
| private File preProcess(File input, File tempDir) { |
| File result = null; |
| for (Iterator iter = steps.iterator(); iter.hasNext();) { |
| IProcessStep step = (IProcessStep) iter.next(); |
| result = step.preProcess(input, tempDir, containingInfs); |
| if (result != null) |
| input = result; |
| } |
| return input; |
| } |
| |
| private File postProcess(File input, File tempDir) { |
| File result = null; |
| for (Iterator iter = steps.iterator(); iter.hasNext();) { |
| IProcessStep step = (IProcessStep) iter.next(); |
| result = step.postProcess(input, tempDir, containingInfs); |
| if (result != null) |
| input = result; |
| } |
| return input; |
| } |
| |
| private void adjustInf(File input, Properties inf) { |
| for (Iterator iter = steps.iterator(); iter.hasNext();) { |
| IProcessStep step = (IProcessStep) iter.next(); |
| step.adjustInf(input, inf, containingInfs); |
| } |
| } |
| |
| public File processJar(File input) throws IOException { |
| File tempDir = null; |
| ++depth; |
| try { |
| long lastModified = input.lastModified(); |
| File workingDir = new File(getWorkingDirectory()); |
| if (!workingDir.exists()) |
| workingDir.mkdirs(); |
| |
| boolean skip = Utils.shouldSkipJar(input, processAll, verbose); |
| if (depth == 0 && verbose) { |
| if (skip) |
| System.out.println("Skipping " + input.getPath()); //$NON-NLS-1$ |
| else { |
| System.out.print("Running "); //$NON-NLS-1$ |
| for (Iterator iter = steps.iterator(); iter.hasNext();) { |
| IProcessStep step = (IProcessStep) iter.next(); |
| System.out.print(step.getStepName() + " "); //$NON-NLS-1$ |
| } |
| System.out.println("on " + input.getPath()); //$NON-NLS-1$ |
| } |
| } |
| |
| if (skip) { |
| //This jar was not marked as conditioned, and we are only processing conditioned jars, so do nothing |
| return input; |
| } |
| |
| //pre |
| File workingFile = preProcess(input, workingDir); |
| |
| //Extract entries from jar and recurse on them |
| if (depth == 0) { |
| tempDir = new File(workingDir, "temp." + workingFile.getName()); //$NON-NLS-1$ |
| } else { |
| File parent = workingDir.getParentFile(); |
| tempDir = new File(parent, "temp_" + depth + '_' + workingFile.getName()); //$NON-NLS-1$ |
| } |
| |
| JarFile jar = new JarFile(workingFile, false); |
| Map replacements = new HashMap(); |
| Properties inf = Utils.getEclipseInf(workingFile, verbose); |
| extractEntries(jar, tempDir, replacements, inf); |
| |
| if (inf != null) |
| adjustInf(workingFile, inf); |
| |
| //Recreate the jar with replacements. |
| //TODO: This is not strictly necessary if we didn't change the inf file and didn't change any content |
| File tempJar = null; |
| tempJar = new File(tempDir, workingFile.getName()); |
| File parent = tempJar.getParentFile(); |
| if (!parent.exists()) |
| parent.mkdirs(); |
| JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(tempJar))); |
| recreateJar(jar, jarOut, replacements, tempDir, inf); |
| |
| jar.close(); |
| if (tempJar != null) { |
| if (!workingFile.equals(input)) { |
| workingFile.delete(); |
| } |
| workingFile = tempJar; |
| } |
| |
| //post |
| File result = postProcess(workingFile, workingDir); |
| |
| //have to normalize after the post steps |
| normalize(result, workingDir); |
| |
| if (!result.equals(workingFile) && !workingFile.equals(input)) |
| workingFile.delete(); |
| if (!result.getParentFile().equals(workingDir)) { |
| File finalFile = new File(workingDir, result.getName()); |
| if (finalFile.exists()) |
| finalFile.delete(); |
| result.renameTo(finalFile); |
| result = finalFile; |
| } |
| |
| result.setLastModified(lastModified); |
| return result; |
| } finally { |
| --depth; |
| if (tempDir != null && tempDir.exists()) |
| Utils.clear(tempDir); |
| } |
| } |
| |
| private void normalize(File input, File directory) { |
| if (input.getName().endsWith(JarProcessor.PACKED_SUFFIX)) { |
| //not a jar |
| return; |
| } |
| try { |
| File tempJar = new File(directory, "temp_" + input.getName()); //$NON-NLS-1$ |
| JarFile jar = null; |
| try { |
| jar = new JarFile(input, false); |
| } catch (JarException e) { |
| //not a jar |
| return; |
| } |
| JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(tempJar))); |
| InputStream in = null; |
| try { |
| Enumeration entries = jar.entries(); |
| for (JarEntry entry = (JarEntry) entries.nextElement(); entry != null; entry = entries.hasMoreElements() ? (JarEntry) entries.nextElement() : null) { |
| JarEntry newEntry = new JarEntry(entry.getName()); |
| newEntry.setTime(entry.getTime()); |
| in = new BufferedInputStream(jar.getInputStream(entry)); |
| jarOut.putNextEntry(newEntry); |
| Utils.transferStreams(in, jarOut, false); |
| jarOut.closeEntry(); |
| in.close(); |
| } |
| } finally { |
| Utils.close(jarOut); |
| Utils.close(jar); |
| Utils.close(in); |
| } |
| tempJar.setLastModified(input.lastModified()); |
| input.delete(); |
| tempJar.renameTo(input); |
| } catch (IOException e) { |
| if (verbose) { |
| System.out.println("Error normalizing jar " + input.getName()); //$NON-NLS-1$ |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |