| /******************************************************************************* |
| * Copyright (c) 2000, 2006 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 Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.ui.jarpackager; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarOutputStream; |
| import java.util.jar.Manifest; |
| import java.util.zip.ZipEntry; |
| |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileInfo; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Path; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| |
| import org.eclipse.swt.widgets.Shell; |
| |
| import org.eclipse.jface.util.Assert; |
| |
| import org.eclipse.ltk.core.refactoring.RefactoringCore; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy; |
| |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerMessages; |
| import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerUtil; |
| |
| /** |
| * Creates a JAR file for the given JAR package data. |
| * <p> |
| * Clients may subclass. |
| * </p> |
| * |
| * @see org.eclipse.jdt.ui.jarpackager.JarPackageData |
| * @since 3.2 |
| */ |
| public class JarWriter3 { |
| |
| private Set fDirectories= new HashSet(); |
| |
| private JarOutputStream fJarOutputStream; |
| |
| private JarPackageData fJarPackage; |
| |
| /** |
| * Creates an instance which is used to create a JAR based |
| * on the given JarPackage. |
| * |
| * @param jarPackage the JAR specification |
| * @param parent the shell used to display question dialogs, |
| * or <code>null</code> if "false/no/cancel" is the answer |
| * and no dialog should be shown |
| * @throws CoreException to signal any other unusual termination. |
| * This can also be used to return information |
| * in the status object. |
| */ |
| public JarWriter3(JarPackageData jarPackage, Shell parent) throws CoreException { |
| Assert.isNotNull(jarPackage, "The JAR specification is null"); //$NON-NLS-1$ |
| fJarPackage= jarPackage; |
| Assert.isTrue(fJarPackage.isValid(), "The JAR package specification is invalid"); //$NON-NLS-1$ |
| if (!canCreateJar(parent)) |
| throw new OperationCanceledException(); |
| |
| try { |
| if (fJarPackage.usesManifest() && fJarPackage.areGeneratedFilesExported()) { |
| Manifest manifest= fJarPackage.getManifestProvider().create(fJarPackage); |
| fJarOutputStream= new JarOutputStream(new FileOutputStream(fJarPackage.getAbsoluteJarLocation().toOSString()), manifest); |
| } else |
| fJarOutputStream= new JarOutputStream(new FileOutputStream(fJarPackage.getAbsoluteJarLocation().toOSString())); |
| String comment= jarPackage.getComment(); |
| if (comment != null) |
| fJarOutputStream.setComment(comment); |
| if (fJarPackage.isRefactoringAware()) { |
| Assert.isTrue(fJarPackage.areDirectoryEntriesIncluded()); |
| final IPath metaPath= new Path(JarPackagerUtil.getMetaEntry()); |
| addDirectories(metaPath); |
| addHistory(fJarPackage, new Path(JarPackagerUtil.getRefactoringsEntry()), new NullProgressMonitor()); |
| } |
| } catch (IOException exception) { |
| throw JarPackagerUtil.createCoreException(exception.getLocalizedMessage(), exception); |
| } |
| } |
| |
| /** |
| * Creates the directory entries for the given path and writes it to the |
| * current archive. |
| * |
| * @param destinationPath |
| * the path to add |
| * |
| * @throws IOException |
| * if an I/O error has occurred |
| */ |
| protected void addDirectories(IPath destinationPath) throws IOException { |
| String path= destinationPath.toString().replace(File.separatorChar, '/'); |
| int lastSlash= path.lastIndexOf('/'); |
| List directories= new ArrayList(2); |
| while (lastSlash != -1) { |
| path= path.substring(0, lastSlash + 1); |
| if (!fDirectories.add(path)) |
| break; |
| |
| JarEntry newEntry= new JarEntry(path); |
| newEntry.setMethod(ZipEntry.STORED); |
| newEntry.setSize(0); |
| newEntry.setCrc(0); |
| newEntry.setTime(System.currentTimeMillis()); |
| directories.add(newEntry); |
| |
| lastSlash= path.lastIndexOf('/', lastSlash - 1); |
| } |
| |
| for (int i= directories.size() - 1; i >= 0; --i) { |
| fJarOutputStream.putNextEntry((JarEntry) directories.get(i)); |
| } |
| } |
| |
| /** |
| * Creates the directory entries for the given path and writes it to the |
| * current archive. |
| * |
| * @param resource |
| * the resource for which the parent directories are to be added |
| * @param destinationPath |
| * the path to add |
| * |
| * @throws IOException |
| * if an I/O error has occurred |
| */ |
| protected void addDirectories(IResource resource, IPath destinationPath) throws IOException, CoreException { |
| IContainer parent= null; |
| String path= destinationPath.toString().replace(File.separatorChar, '/'); |
| int lastSlash= path.lastIndexOf('/'); |
| List directories= new ArrayList(2); |
| while (lastSlash != -1) { |
| path= path.substring(0, lastSlash + 1); |
| if (!fDirectories.add(path)) |
| break; |
| |
| parent= resource.getParent(); |
| long timeStamp= System.currentTimeMillis(); |
| URI location= parent.getLocationURI(); |
| if (location != null) { |
| IFileInfo info= EFS.getStore(location).fetchInfo(); |
| if (info.exists()) |
| timeStamp= info.getLastModified(); |
| } |
| |
| JarEntry newEntry= new JarEntry(path); |
| newEntry.setMethod(ZipEntry.STORED); |
| newEntry.setSize(0); |
| newEntry.setCrc(0); |
| newEntry.setTime(timeStamp); |
| directories.add(newEntry); |
| |
| lastSlash= path.lastIndexOf('/', lastSlash - 1); |
| } |
| |
| for (int i= directories.size() - 1; i >= 0; --i) { |
| fJarOutputStream.putNextEntry((JarEntry) directories.get(i)); |
| } |
| } |
| |
| /** |
| * Creates a new JarEntry with the passed path and contents, and writes it |
| * to the current archive. |
| * |
| * @param resource the file to write |
| * @param path the path inside the archive |
| * |
| * @throws IOException if an I/O error has occurred |
| * @throws CoreException if the resource can-t be accessed |
| */ |
| protected void addFile(IFile resource, IPath path) throws IOException, CoreException { |
| JarEntry newEntry= new JarEntry(path.toString().replace(File.separatorChar, '/')); |
| byte[] readBuffer= new byte[4096]; |
| |
| if (fJarPackage.isCompressed()) |
| newEntry.setMethod(ZipEntry.DEFLATED); |
| // Entry is filled automatically. |
| else { |
| newEntry.setMethod(ZipEntry.STORED); |
| JarPackagerUtil.calculateCrcAndSize(newEntry, resource.getContents(false), readBuffer); |
| } |
| |
| long lastModified= System.currentTimeMillis(); |
| URI locationURI= resource.getLocationURI(); |
| if (locationURI != null) { |
| IFileInfo info= EFS.getStore(locationURI).fetchInfo(); |
| if (info.exists()) |
| lastModified= info.getLastModified(); |
| } |
| |
| // Set modification time |
| newEntry.setTime(lastModified); |
| |
| InputStream contentStream = resource.getContents(false); |
| |
| try { |
| fJarOutputStream.putNextEntry(newEntry); |
| int count; |
| while ((count= contentStream.read(readBuffer, 0, readBuffer.length)) != -1) |
| fJarOutputStream.write(readBuffer, 0, count); |
| } finally { |
| if (contentStream != null) |
| contentStream.close(); |
| |
| /* |
| * Commented out because some JREs throw an NPE if a stream |
| * is closed twice. This works because |
| * a) putNextEntry closes the previous entry |
| * b) closing the stream closes the last entry |
| */ |
| // fJarOutputStream.closeEntry(); |
| } |
| } |
| |
| /** |
| * Creates a new JAR file entry containing the refactoring history. |
| * |
| * @param data |
| * the jar package data |
| * @param path |
| * the path of the refactoring history file within the archive |
| * @param monitor |
| * the progress monitor to use |
| * @throws IOException |
| * if no temp file could be written |
| * @throws CoreException |
| * if an error occurs while transforming the refactorings |
| */ |
| private void addHistory(final JarPackageData data, final IPath path, final IProgressMonitor monitor) throws IOException, CoreException { |
| Assert.isNotNull(data); |
| Assert.isNotNull(path); |
| Assert.isNotNull(monitor); |
| final RefactoringDescriptorProxy[] proxies= data.getRefactoringDescriptors(); |
| Arrays.sort(proxies, new Comparator() { |
| |
| public final int compare(final Object first, final Object second) { |
| final RefactoringDescriptorProxy predecessor= (RefactoringDescriptorProxy) first; |
| final RefactoringDescriptorProxy successor= (RefactoringDescriptorProxy) second; |
| final long delta= predecessor.getTimeStamp() - successor.getTimeStamp(); |
| if (delta > 0) |
| return 1; |
| else if (delta < 0) |
| return -1; |
| return 0; |
| } |
| }); |
| File file= null; |
| OutputStream output= null; |
| try { |
| file= File.createTempFile("history", null); //$NON-NLS-1$ |
| output= new BufferedOutputStream(new FileOutputStream(file)); |
| try { |
| RefactoringCore.getHistoryService().writeRefactoringDescriptors(proxies, output, RefactoringDescriptor.NONE, false, monitor); |
| try { |
| output.close(); |
| output= null; |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| writeMetaData(data, file, path); |
| } finally { |
| if (output != null) { |
| try { |
| output.close(); |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| } |
| } |
| } finally { |
| if (file != null) |
| file.delete(); |
| } |
| } |
| |
| /** |
| * Checks if the JAR file can be overwritten. |
| * If the JAR package setting does not allow to overwrite the JAR |
| * then a dialog will ask the user again. |
| * |
| * @param parent the parent for the dialog, |
| * or <code>null</code> if no dialog should be presented |
| * @return <code>true</code> if it is OK to create the JAR |
| */ |
| protected boolean canCreateJar(Shell parent) { |
| File file= fJarPackage.getAbsoluteJarLocation().toFile(); |
| if (file.exists()) { |
| if (!file.canWrite()) |
| return false; |
| if (fJarPackage.allowOverwrite()) |
| return true; |
| return parent != null && JarPackagerUtil.askForOverwritePermission(parent, fJarPackage.getAbsoluteJarLocation().toOSString()); |
| } |
| |
| // Test if directory exists |
| String path= file.getAbsolutePath(); |
| int separatorIndex = path.lastIndexOf(File.separator); |
| if (separatorIndex == -1) // i.e.- default directory, which is fine |
| return true; |
| File directory= new File(path.substring(0, separatorIndex)); |
| if (!directory.exists()) { |
| if (JarPackagerUtil.askToCreateDirectory(parent, directory)) |
| return directory.mkdirs(); |
| else |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Closes the archive and does all required cleanup. |
| * |
| * @throws CoreException |
| * to signal any other unusual termination. This can also be |
| * used to return information in the status object. |
| */ |
| public void close() throws CoreException { |
| if (fJarOutputStream != null) |
| try { |
| fJarOutputStream.close(); |
| registerInWorkspaceIfNeeded(); |
| } catch (IOException ex) { |
| throw JarPackagerUtil.createCoreException(ex.getLocalizedMessage(), ex); |
| } |
| } |
| |
| private void registerInWorkspaceIfNeeded() { |
| IPath jarPath= fJarPackage.getAbsoluteJarLocation(); |
| IProject[] projects= ResourcesPlugin.getWorkspace().getRoot().getProjects(); |
| for (int i= 0; i < projects.length; i++) { |
| IProject project= projects[i]; |
| // The Jar is always put into the local file system. So it can only be |
| // part of a project if the project is local as well. So using getLocation |
| // is currently save here. |
| IPath projectLocation= project.getLocation(); |
| if (projectLocation != null && projectLocation.isPrefixOf(jarPath)) { |
| try { |
| jarPath= jarPath.removeFirstSegments(projectLocation.segmentCount()); |
| jarPath= jarPath.removeLastSegments(1); |
| IResource containingFolder= project.findMember(jarPath); |
| if (containingFolder != null && containingFolder.isAccessible()) |
| containingFolder.refreshLocal(IResource.DEPTH_ONE, null); |
| } catch (CoreException ex) { |
| // don't refresh the folder but log the problem |
| JavaPlugin.log(ex); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Writes the passed resource to the current archive. |
| * |
| * @param resource |
| * the file to be written |
| * @param destinationPath |
| * the path for the file inside the archive |
| * @throws CoreException |
| * to signal any other unusual termination. This can also be |
| * used to return information in the status object. |
| */ |
| public void write(IFile resource, IPath destinationPath) throws CoreException { |
| try { |
| if (fJarPackage.areDirectoryEntriesIncluded()) |
| addDirectories(resource, destinationPath); |
| addFile(resource, destinationPath); |
| } catch (IOException ex) { |
| // Ensure full path is visible |
| String message= null; |
| if (ex.getLocalizedMessage() != null) |
| message= Messages.format(JarPackagerMessages.JarWriter_writeProblemWithMessage, new Object[] {resource.getFullPath(), ex.getLocalizedMessage()}); |
| else |
| message= Messages.format(JarPackagerMessages.JarWriter_writeProblem, resource.getFullPath()); |
| throw JarPackagerUtil.createCoreException(message, ex); |
| } |
| } |
| |
| /** |
| * Writes the meta file to the JAR file. |
| * |
| * @param data |
| * the jar package data |
| * @param file |
| * the file containing the meta data |
| * @param path |
| * the path of the meta file within the archive |
| * @throws FileNotFoundException |
| * if the meta file could not be found |
| * @throws IOException |
| * if an input/output error occurs |
| */ |
| private void writeMetaData(final JarPackageData data, final File file, final IPath path) throws FileNotFoundException, IOException, CoreException { |
| Assert.isNotNull(data); |
| Assert.isNotNull(file); |
| Assert.isNotNull(path); |
| final JarEntry entry= new JarEntry(path.toString().replace(File.separatorChar, '/')); |
| byte[] buffer= new byte[4096]; |
| if (data.isCompressed()) |
| entry.setMethod(ZipEntry.DEFLATED); |
| else { |
| entry.setMethod(ZipEntry.STORED); |
| JarPackagerUtil.calculateCrcAndSize(entry, new BufferedInputStream(new FileInputStream(file)), buffer); |
| } |
| entry.setTime(System.currentTimeMillis()); |
| final InputStream stream= new BufferedInputStream(new FileInputStream(file)); |
| try { |
| fJarOutputStream.putNextEntry(entry); |
| int count; |
| while ((count= stream.read(buffer, 0, buffer.length)) != -1) |
| fJarOutputStream.write(buffer, 0, count); |
| } finally { |
| try { |
| stream.close(); |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| } |
| } |
| } |