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.BufferedInputStream;
013: import java.io.BufferedOutputStream;
014: import java.io.File;
015: import java.io.FileInputStream;
016: import java.io.FileNotFoundException;
017: import java.io.FileOutputStream;
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.io.OutputStream;
021: import java.net.URI;
022: import java.util.ArrayList;
023: import java.util.Arrays;
024: import java.util.Comparator;
025: import java.util.HashSet;
026: import java.util.List;
027: import java.util.Set;
028: import java.util.jar.JarEntry;
029: import java.util.jar.JarOutputStream;
030: import java.util.jar.Manifest;
031: import java.util.zip.ZipEntry;
032:
033: import org.eclipse.core.filesystem.EFS;
034: import org.eclipse.core.filesystem.IFileInfo;
035:
036: import org.eclipse.core.runtime.Assert;
037: import org.eclipse.core.runtime.CoreException;
038: import org.eclipse.core.runtime.IPath;
039: import org.eclipse.core.runtime.IProgressMonitor;
040: import org.eclipse.core.runtime.NullProgressMonitor;
041: import org.eclipse.core.runtime.OperationCanceledException;
042: import org.eclipse.core.runtime.Path;
043:
044: import org.eclipse.core.resources.IContainer;
045: import org.eclipse.core.resources.IFile;
046: import org.eclipse.core.resources.IProject;
047: import org.eclipse.core.resources.IResource;
048: import org.eclipse.core.resources.ResourcesPlugin;
049:
050: import org.eclipse.swt.widgets.Shell;
051:
052: import org.eclipse.ltk.core.refactoring.RefactoringCore;
053: import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
054: import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy;
055:
056: import org.eclipse.jdt.internal.corext.util.Messages;
057:
058: import org.eclipse.jdt.internal.ui.JavaPlugin;
059: import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerMessages;
060: import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerUtil;
061:
062: /**
063: * Creates a JAR file for the given JAR package data.
064: * <p>
065: * Clients may subclass.
066: * </p>
067: *
068: * @see org.eclipse.jdt.ui.jarpackager.JarPackageData
069: * @since 3.2
070: */
071: public class JarWriter3 {
072:
073: private Set fDirectories = new HashSet();
074:
075: private JarOutputStream fJarOutputStream;
076:
077: private JarPackageData fJarPackage;
078:
079: /**
080: * Creates an instance which is used to create a JAR based
081: * on the given JarPackage.
082: *
083: * @param jarPackage the JAR specification
084: * @param parent the shell used to display question dialogs,
085: * or <code>null</code> if "false/no/cancel" is the answer
086: * and no dialog should be shown
087: * @throws CoreException to signal any other unusual termination.
088: * This can also be used to return information
089: * in the status object.
090: */
091: public JarWriter3(JarPackageData jarPackage, Shell parent)
092: throws CoreException {
093: Assert.isNotNull(jarPackage, "The JAR specification is null"); //$NON-NLS-1$
094: fJarPackage = jarPackage;
095: Assert.isTrue(fJarPackage.isValid(),
096: "The JAR package specification is invalid"); //$NON-NLS-1$
097: if (!canCreateJar(parent))
098: throw new OperationCanceledException();
099:
100: try {
101: if (fJarPackage.usesManifest()
102: && fJarPackage.areGeneratedFilesExported()) {
103: Manifest manifest = fJarPackage.getManifestProvider()
104: .create(fJarPackage);
105: fJarOutputStream = new JarOutputStream(
106: new FileOutputStream(fJarPackage
107: .getAbsoluteJarLocation().toOSString()),
108: manifest);
109: } else
110: fJarOutputStream = new JarOutputStream(
111: new FileOutputStream(fJarPackage
112: .getAbsoluteJarLocation().toOSString()));
113: String comment = jarPackage.getComment();
114: if (comment != null)
115: fJarOutputStream.setComment(comment);
116: if (fJarPackage.isRefactoringAware()) {
117: Assert
118: .isTrue(fJarPackage
119: .areDirectoryEntriesIncluded());
120: final IPath metaPath = new Path(JarPackagerUtil
121: .getMetaEntry());
122: addDirectories(metaPath);
123: addHistory(fJarPackage, new Path(JarPackagerUtil
124: .getRefactoringsEntry()),
125: new NullProgressMonitor());
126: }
127: } catch (IOException exception) {
128: throw JarPackagerUtil.createCoreException(exception
129: .getLocalizedMessage(), exception);
130: }
131: }
132:
133: /**
134: * Creates the directory entries for the given path and writes it to the
135: * current archive.
136: *
137: * @param destinationPath
138: * the path to add
139: *
140: * @throws IOException
141: * if an I/O error has occurred
142: */
143: protected void addDirectories(IPath destinationPath)
144: throws IOException {
145: String path = destinationPath.toString().replace(
146: File.separatorChar, '/');
147: int lastSlash = path.lastIndexOf('/');
148: List directories = new ArrayList(2);
149: while (lastSlash != -1) {
150: path = path.substring(0, lastSlash + 1);
151: if (!fDirectories.add(path))
152: break;
153:
154: JarEntry newEntry = new JarEntry(path);
155: newEntry.setMethod(ZipEntry.STORED);
156: newEntry.setSize(0);
157: newEntry.setCrc(0);
158: newEntry.setTime(System.currentTimeMillis());
159: directories.add(newEntry);
160:
161: lastSlash = path.lastIndexOf('/', lastSlash - 1);
162: }
163:
164: for (int i = directories.size() - 1; i >= 0; --i) {
165: fJarOutputStream
166: .putNextEntry((JarEntry) directories.get(i));
167: }
168: }
169:
170: /**
171: * Creates the directory entries for the given path and writes it to the
172: * current archive.
173: *
174: * @param resource
175: * the resource for which the parent directories are to be added
176: * @param destinationPath
177: * the path to add
178: *
179: * @throws IOException
180: * if an I/O error has occurred
181: */
182: protected void addDirectories(IResource resource,
183: IPath destinationPath) throws IOException, CoreException {
184: IContainer parent = null;
185: String path = destinationPath.toString().replace(
186: File.separatorChar, '/');
187: int lastSlash = path.lastIndexOf('/');
188: List directories = new ArrayList(2);
189: while (lastSlash != -1) {
190: path = path.substring(0, lastSlash + 1);
191: if (!fDirectories.add(path))
192: break;
193:
194: parent = resource.getParent();
195: long timeStamp = System.currentTimeMillis();
196: URI location = parent.getLocationURI();
197: if (location != null) {
198: IFileInfo info = EFS.getStore(location).fetchInfo();
199: if (info.exists())
200: timeStamp = info.getLastModified();
201: }
202:
203: JarEntry newEntry = new JarEntry(path);
204: newEntry.setMethod(ZipEntry.STORED);
205: newEntry.setSize(0);
206: newEntry.setCrc(0);
207: newEntry.setTime(timeStamp);
208: directories.add(newEntry);
209:
210: lastSlash = path.lastIndexOf('/', lastSlash - 1);
211: }
212:
213: for (int i = directories.size() - 1; i >= 0; --i) {
214: fJarOutputStream
215: .putNextEntry((JarEntry) directories.get(i));
216: }
217: }
218:
219: /**
220: * Creates a new JarEntry with the passed path and contents, and writes it
221: * to the current archive.
222: *
223: * @param resource the file to write
224: * @param path the path inside the archive
225: *
226: * @throws IOException if an I/O error has occurred
227: * @throws CoreException if the resource can-t be accessed
228: */
229: protected void addFile(IFile resource, IPath path)
230: throws IOException, CoreException {
231: JarEntry newEntry = new JarEntry(path.toString().replace(
232: File.separatorChar, '/'));
233: byte[] readBuffer = new byte[4096];
234:
235: if (fJarPackage.isCompressed())
236: newEntry.setMethod(ZipEntry.DEFLATED);
237: // Entry is filled automatically.
238: else {
239: newEntry.setMethod(ZipEntry.STORED);
240: JarPackagerUtil.calculateCrcAndSize(newEntry, resource
241: .getContents(false), readBuffer);
242: }
243:
244: long lastModified = System.currentTimeMillis();
245: URI locationURI = resource.getLocationURI();
246: if (locationURI != null) {
247: IFileInfo info = EFS.getStore(locationURI).fetchInfo();
248: if (info.exists())
249: lastModified = info.getLastModified();
250: }
251:
252: // Set modification time
253: newEntry.setTime(lastModified);
254:
255: InputStream contentStream = resource.getContents(false);
256:
257: try {
258: fJarOutputStream.putNextEntry(newEntry);
259: int count;
260: while ((count = contentStream.read(readBuffer, 0,
261: readBuffer.length)) != -1)
262: fJarOutputStream.write(readBuffer, 0, count);
263: } finally {
264: if (contentStream != null)
265: contentStream.close();
266:
267: /*
268: * Commented out because some JREs throw an NPE if a stream
269: * is closed twice. This works because
270: * a) putNextEntry closes the previous entry
271: * b) closing the stream closes the last entry
272: */
273: // fJarOutputStream.closeEntry();
274: }
275: }
276:
277: /**
278: * Creates a new JAR file entry containing the refactoring history.
279: *
280: * @param data
281: * the jar package data
282: * @param path
283: * the path of the refactoring history file within the archive
284: * @param monitor
285: * the progress monitor to use
286: * @throws IOException
287: * if no temp file could be written
288: * @throws CoreException
289: * if an error occurs while transforming the refactorings
290: */
291: private void addHistory(final JarPackageData data,
292: final IPath path, final IProgressMonitor monitor)
293: throws IOException, CoreException {
294: Assert.isNotNull(data);
295: Assert.isNotNull(path);
296: Assert.isNotNull(monitor);
297: final RefactoringDescriptorProxy[] proxies = data
298: .getRefactoringDescriptors();
299: Arrays.sort(proxies, new Comparator() {
300:
301: public final int compare(final Object first,
302: final Object second) {
303: final RefactoringDescriptorProxy predecessor = (RefactoringDescriptorProxy) first;
304: final RefactoringDescriptorProxy successor = (RefactoringDescriptorProxy) second;
305: final long delta = predecessor.getTimeStamp()
306: - successor.getTimeStamp();
307: if (delta > 0)
308: return 1;
309: else if (delta < 0)
310: return -1;
311: return 0;
312: }
313: });
314: File file = null;
315: OutputStream output = null;
316: try {
317: file = File.createTempFile("history", null); //$NON-NLS-1$
318: output = new BufferedOutputStream(
319: new FileOutputStream(file));
320: try {
321: RefactoringCore.getHistoryService()
322: .writeRefactoringDescriptors(proxies, output,
323: RefactoringDescriptor.NONE, false,
324: monitor);
325: try {
326: output.close();
327: output = null;
328: } catch (IOException exception) {
329: // Do nothing
330: }
331: writeMetaData(data, file, path);
332: } finally {
333: if (output != null) {
334: try {
335: output.close();
336: } catch (IOException exception) {
337: // Do nothing
338: }
339: }
340: }
341: } finally {
342: if (file != null)
343: file.delete();
344: }
345: }
346:
347: /**
348: * Checks if the JAR file can be overwritten.
349: * If the JAR package setting does not allow to overwrite the JAR
350: * then a dialog will ask the user again.
351: *
352: * @param parent the parent for the dialog,
353: * or <code>null</code> if no dialog should be presented
354: * @return <code>true</code> if it is OK to create the JAR
355: */
356: protected boolean canCreateJar(Shell parent) {
357: File file = fJarPackage.getAbsoluteJarLocation().toFile();
358: if (file.exists()) {
359: if (!file.canWrite())
360: return false;
361: if (fJarPackage.allowOverwrite())
362: return true;
363: return parent != null
364: && JarPackagerUtil.askForOverwritePermission(
365: parent, fJarPackage
366: .getAbsoluteJarLocation()
367: .toOSString());
368: }
369:
370: // Test if directory exists
371: String path = file.getAbsolutePath();
372: int separatorIndex = path.lastIndexOf(File.separator);
373: if (separatorIndex == -1) // i.e.- default directory, which is fine
374: return true;
375: File directory = new File(path.substring(0, separatorIndex));
376: if (!directory.exists()) {
377: if (JarPackagerUtil.askToCreateDirectory(parent, directory))
378: return directory.mkdirs();
379: else
380: return false;
381: }
382: return true;
383: }
384:
385: /**
386: * Closes the archive and does all required cleanup.
387: *
388: * @throws CoreException
389: * to signal any other unusual termination. This can also be
390: * used to return information in the status object.
391: */
392: public void close() throws CoreException {
393: if (fJarOutputStream != null)
394: try {
395: fJarOutputStream.close();
396: registerInWorkspaceIfNeeded();
397: } catch (IOException ex) {
398: throw JarPackagerUtil.createCoreException(ex
399: .getLocalizedMessage(), ex);
400: }
401: }
402:
403: private void registerInWorkspaceIfNeeded() {
404: IPath jarPath = fJarPackage.getAbsoluteJarLocation();
405: IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()
406: .getProjects();
407: for (int i = 0; i < projects.length; i++) {
408: IProject project = projects[i];
409: // The Jar is always put into the local file system. So it can only be
410: // part of a project if the project is local as well. So using getLocation
411: // is currently save here.
412: IPath projectLocation = project.getLocation();
413: if (projectLocation != null
414: && projectLocation.isPrefixOf(jarPath)) {
415: try {
416: jarPath = jarPath
417: .removeFirstSegments(projectLocation
418: .segmentCount());
419: jarPath = jarPath.removeLastSegments(1);
420: IResource containingFolder = project
421: .findMember(jarPath);
422: if (containingFolder != null
423: && containingFolder.isAccessible())
424: containingFolder.refreshLocal(
425: IResource.DEPTH_ONE, null);
426: } catch (CoreException ex) {
427: // don't refresh the folder but log the problem
428: JavaPlugin.log(ex);
429: }
430: }
431: }
432: }
433:
434: /**
435: * Writes the passed resource to the current archive.
436: *
437: * @param resource
438: * the file to be written
439: * @param destinationPath
440: * the path for the file inside the archive
441: * @throws CoreException
442: * to signal any other unusual termination. This can also be
443: * used to return information in the status object.
444: */
445: public void write(IFile resource, IPath destinationPath)
446: throws CoreException {
447: try {
448: if (fJarPackage.areDirectoryEntriesIncluded())
449: addDirectories(resource, destinationPath);
450: addFile(resource, destinationPath);
451: } catch (IOException ex) {
452: // Ensure full path is visible
453: String message = null;
454: if (ex.getLocalizedMessage() != null)
455: message = Messages
456: .format(
457: JarPackagerMessages.JarWriter_writeProblemWithMessage,
458: new Object[] { resource.getFullPath(),
459: ex.getLocalizedMessage() });
460: else
461: message = Messages.format(
462: JarPackagerMessages.JarWriter_writeProblem,
463: resource.getFullPath());
464: throw JarPackagerUtil.createCoreException(message, ex);
465: }
466: }
467:
468: /**
469: * Writes the meta file to the JAR file.
470: *
471: * @param data
472: * the jar package data
473: * @param file
474: * the file containing the meta data
475: * @param path
476: * the path of the meta file within the archive
477: * @throws FileNotFoundException
478: * if the meta file could not be found
479: * @throws IOException
480: * if an input/output error occurs
481: */
482: private void writeMetaData(final JarPackageData data,
483: final File file, final IPath path)
484: throws FileNotFoundException, IOException, CoreException {
485: Assert.isNotNull(data);
486: Assert.isNotNull(file);
487: Assert.isNotNull(path);
488: final JarEntry entry = new JarEntry(path.toString().replace(
489: File.separatorChar, '/'));
490: byte[] buffer = new byte[4096];
491: if (data.isCompressed())
492: entry.setMethod(ZipEntry.DEFLATED);
493: else {
494: entry.setMethod(ZipEntry.STORED);
495: JarPackagerUtil.calculateCrcAndSize(entry,
496: new BufferedInputStream(new FileInputStream(file)),
497: buffer);
498: }
499: entry.setTime(System.currentTimeMillis());
500: final InputStream stream = new BufferedInputStream(
501: new FileInputStream(file));
502: try {
503: fJarOutputStream.putNextEntry(entry);
504: int count;
505: while ((count = stream.read(buffer, 0, buffer.length)) != -1)
506: fJarOutputStream.write(buffer, 0, count);
507: } finally {
508: try {
509: stream.close();
510: } catch (IOException exception) {
511: // Do nothing
512: }
513: }
514: }
515: }
|