001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.ui.jarpackager;
011:
012: import java.io.File;
013: import java.io.FileOutputStream;
014: import java.io.IOException;
015: import java.io.InputStream;
016: import java.util.ArrayList;
017: import java.util.HashSet;
018: import java.util.List;
019: import java.util.Set;
020: import java.util.jar.JarEntry;
021: import java.util.jar.JarOutputStream;
022: import java.util.jar.Manifest;
023: import java.util.zip.CRC32;
024: import java.util.zip.ZipEntry;
025:
026: import org.eclipse.core.runtime.Assert;
027: import org.eclipse.core.runtime.CoreException;
028: import org.eclipse.core.runtime.IPath;
029: import org.eclipse.core.runtime.OperationCanceledException;
030:
031: import org.eclipse.core.resources.IFile;
032: import org.eclipse.core.resources.IProject;
033: import org.eclipse.core.resources.IResource;
034: import org.eclipse.core.resources.ResourcesPlugin;
035:
036: import org.eclipse.swt.widgets.Shell;
037:
038: import org.eclipse.jdt.internal.corext.util.Messages;
039:
040: import org.eclipse.jdt.internal.ui.JavaPlugin;
041: import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerMessages;
042: import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerUtil;
043:
044: /**
045: * Creates a JAR file for the given JAR package data.
046: * <p>
047: * Clients may subclass.
048: * </p>
049: *
050: * @see org.eclipse.jdt.ui.jarpackager.JarPackageData
051: * @since 3.1
052: *
053: * @deprecated Use JarWriter3 instead which leverages new {@link org.eclipse.core.filesystem.EFS EFS} support
054: */
055: public class JarWriter2 {
056:
057: private JarOutputStream fJarOutputStream;
058: private JarPackageData fJarPackage;
059:
060: private Set fDirectories = new HashSet();
061:
062: /**
063: * Creates an instance which is used to create a JAR based
064: * on the given JarPackage.
065: *
066: * @param jarPackage the JAR specification
067: * @param parent the shell used to display question dialogs,
068: * or <code>null</code> if "false/no/cancel" is the answer
069: * and no dialog should be shown
070: * @throws CoreException to signal any other unusual termination.
071: * This can also be used to return information
072: * in the status object.
073: */
074: public JarWriter2(JarPackageData jarPackage, Shell parent)
075: throws CoreException {
076: Assert.isNotNull(jarPackage, "The JAR specification is null"); //$NON-NLS-1$
077: fJarPackage = jarPackage;
078: Assert.isTrue(fJarPackage.isValid(),
079: "The JAR package specification is invalid"); //$NON-NLS-1$
080: if (!canCreateJar(parent))
081: throw new OperationCanceledException();
082:
083: try {
084: if (fJarPackage.usesManifest()
085: && fJarPackage.areGeneratedFilesExported()) {
086: Manifest manifest = fJarPackage.getManifestProvider()
087: .create(fJarPackage);
088: fJarOutputStream = new JarOutputStream(
089: new FileOutputStream(fJarPackage
090: .getAbsoluteJarLocation().toOSString()),
091: manifest);
092: } else
093: fJarOutputStream = new JarOutputStream(
094: new FileOutputStream(fJarPackage
095: .getAbsoluteJarLocation().toOSString()));
096: String comment = jarPackage.getComment();
097: if (comment != null)
098: fJarOutputStream.setComment(comment);
099: } catch (IOException ex) {
100: throw JarPackagerUtil.createCoreException(ex
101: .getLocalizedMessage(), ex);
102: }
103: }
104:
105: /**
106: * Closes the archive and does all required cleanup.
107: *
108: * @throws CoreException to signal any other unusual termination.
109: * This can also be used to return information
110: * in the status object.
111: */
112: public void close() throws CoreException {
113: if (fJarOutputStream != null)
114: try {
115: fJarOutputStream.close();
116: registerInWorkspaceIfNeeded();
117: } catch (IOException ex) {
118: throw JarPackagerUtil.createCoreException(ex
119: .getLocalizedMessage(), ex);
120: }
121: }
122:
123: /**
124: * Writes the passed resource to the current archive.
125: *
126: * @param resource the file to be written
127: * @param destinationPath the path for the file inside the archive
128: * @throws CoreException to signal any other unusual termination.
129: * This can also be used to return information
130: * in the status object.
131: */
132: public void write(IFile resource, IPath destinationPath)
133: throws CoreException {
134: try {
135: IPath fileLocation = resource.getLocation();
136: File file = null;
137: if (fileLocation != null) {
138: file = new File(fileLocation.toOSString());
139: }
140: if (fJarPackage.areDirectoryEntriesIncluded())
141: addDirectories(destinationPath, file);
142: addFile(resource, destinationPath, file);
143: } catch (IOException ex) {
144: // Ensure full path is visible
145: String message = null;
146: if (ex.getLocalizedMessage() != null)
147: message = Messages
148: .format(
149: JarPackagerMessages.JarWriter_writeProblemWithMessage,
150: new Object[] { resource.getFullPath(),
151: ex.getLocalizedMessage() });
152: else
153: message = Messages.format(
154: JarPackagerMessages.JarWriter_writeProblem,
155: resource.getFullPath());
156: throw JarPackagerUtil.createCoreException(message, ex);
157: }
158: }
159:
160: /**
161: * Creates a new JarEntry with the passed path and contents, and writes it
162: * to the current archive.
163: *
164: * @param resource the file to write
165: * @param path the path inside the archive
166: * @param correspondingFile the corresponding file in the file system
167: * or <code>null</code> if it doesn't exist
168: * @throws IOException if an I/O error has occurred
169: * @throws CoreException if the resource can-t be accessed
170: */
171: protected void addFile(IFile resource, IPath path,
172: File correspondingFile) throws IOException, CoreException {
173: JarEntry newEntry = new JarEntry(path.toString().replace(
174: File.separatorChar, '/'));
175: byte[] readBuffer = new byte[4096];
176:
177: if (fJarPackage.isCompressed())
178: newEntry.setMethod(ZipEntry.DEFLATED);
179: // Entry is filled automatically.
180: else {
181: newEntry.setMethod(ZipEntry.STORED);
182: calculateCrcAndSize(newEntry, resource, readBuffer);
183: }
184:
185: long lastModified = correspondingFile != null
186: && correspondingFile.exists() ? correspondingFile
187: .lastModified() : System.currentTimeMillis();
188: // Set modification time
189: newEntry.setTime(lastModified);
190:
191: InputStream contentStream = resource.getContents(false);
192:
193: try {
194: fJarOutputStream.putNextEntry(newEntry);
195: int count;
196: while ((count = contentStream.read(readBuffer, 0,
197: readBuffer.length)) != -1)
198: fJarOutputStream.write(readBuffer, 0, count);
199: } finally {
200: if (contentStream != null)
201: contentStream.close();
202:
203: /*
204: * Commented out because some JREs throw an NPE if a stream
205: * is closed twice. This works because
206: * a) putNextEntry closes the previous entry
207: * b) closing the stream closes the last entry
208: */
209: // fJarOutputStream.closeEntry();
210: }
211: }
212:
213: /**
214: * Calculates the CRC and size of the resource and updates the jarEntry.
215: *
216: * @param jarEntry the JarEntry to update
217: * @param resource the file to calculate
218: * @param readBuffer a shared read buffer to store temporary data
219: *
220: * @throws IOException if an I/O error has occurred
221: * @throws CoreException if the resource can-t be accessed
222: */
223: private void calculateCrcAndSize(JarEntry jarEntry, IFile resource,
224: byte[] readBuffer) throws IOException, CoreException {
225: InputStream contentStream = resource.getContents(false);
226: int size = 0;
227: CRC32 checksumCalculator = new CRC32();
228: int count;
229: try {
230: while ((count = contentStream.read(readBuffer, 0,
231: readBuffer.length)) != -1) {
232: checksumCalculator.update(readBuffer, 0, count);
233: size += count;
234: }
235: } finally {
236: if (contentStream != null)
237: contentStream.close();
238: }
239: jarEntry.setSize(size);
240: jarEntry.setCrc(checksumCalculator.getValue());
241: }
242:
243: /**
244: * Creates the directory entries for the given path and writes it to the
245: * current archive.
246: *
247: * @param destinationPath the path to add
248: * @param correspondingFile the corresponding file in the file system
249: * or <code>null</code> if it doesn't exist
250: * @throws IOException if an I/O error has occurred
251: */
252: protected void addDirectories(IPath destinationPath,
253: File correspondingFile) throws IOException {
254: String path = destinationPath.toString().replace(
255: File.separatorChar, '/');
256: int lastSlash = path.lastIndexOf('/');
257: List directories = new ArrayList(2);
258: while (lastSlash != -1) {
259: path = path.substring(0, lastSlash + 1);
260: if (!fDirectories.add(path))
261: break;
262:
263: if (correspondingFile != null)
264: correspondingFile = correspondingFile.getParentFile();
265: long timeStamp = correspondingFile != null
266: && correspondingFile.exists() ? correspondingFile
267: .lastModified() : System.currentTimeMillis();
268:
269: JarEntry newEntry = new JarEntry(path);
270: newEntry.setMethod(ZipEntry.STORED);
271: newEntry.setSize(0);
272: newEntry.setCrc(0);
273: newEntry.setTime(timeStamp);
274: directories.add(newEntry);
275:
276: lastSlash = path.lastIndexOf('/', lastSlash - 1);
277: }
278:
279: for (int i = directories.size() - 1; i >= 0; --i) {
280: fJarOutputStream
281: .putNextEntry((JarEntry) directories.get(i));
282: }
283: }
284:
285: /**
286: * Checks if the JAR file can be overwritten.
287: * If the JAR package setting does not allow to overwrite the JAR
288: * then a dialog will ask the user again.
289: *
290: * @param parent the parent for the dialog,
291: * or <code>null</code> if no dialog should be presented
292: * @return <code>true</code> if it is OK to create the JAR
293: */
294: protected boolean canCreateJar(Shell parent) {
295: File file = fJarPackage.getAbsoluteJarLocation().toFile();
296: if (file.exists()) {
297: if (!file.canWrite())
298: return false;
299: if (fJarPackage.allowOverwrite())
300: return true;
301: return parent != null
302: && JarPackagerUtil.askForOverwritePermission(
303: parent, fJarPackage
304: .getAbsoluteJarLocation()
305: .toOSString());
306: }
307:
308: // Test if directory exists
309: String path = file.getAbsolutePath();
310: int separatorIndex = path.lastIndexOf(File.separator);
311: if (separatorIndex == -1) // i.e.- default directory, which is fine
312: return true;
313: File directory = new File(path.substring(0, separatorIndex));
314: if (!directory.exists()) {
315: if (JarPackagerUtil.askToCreateDirectory(parent, directory))
316: return directory.mkdirs();
317: else
318: return false;
319: }
320: return true;
321: }
322:
323: private void registerInWorkspaceIfNeeded() {
324: IPath jarPath = fJarPackage.getAbsoluteJarLocation();
325: IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()
326: .getProjects();
327: for (int i = 0; i < projects.length; i++) {
328: IProject project = projects[i];
329: // The Jar is always put into the local file system. So it can only be
330: // part of a project if the project is local as well. So using getLocation
331: // is currently save here.
332: IPath projectLocation = project.getLocation();
333: if (projectLocation != null
334: && projectLocation.isPrefixOf(jarPath)) {
335: try {
336: jarPath = jarPath
337: .removeFirstSegments(projectLocation
338: .segmentCount());
339: jarPath = jarPath.removeLastSegments(1);
340: IResource containingFolder = project
341: .findMember(jarPath);
342: if (containingFolder != null
343: && containingFolder.isAccessible())
344: containingFolder.refreshLocal(
345: IResource.DEPTH_ONE, null);
346: } catch (CoreException ex) {
347: // don't refresh the folder but log the problem
348: JavaPlugin.log(ex);
349: }
350: }
351: }
352: }
353: }
|