]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-before/ui/org/eclipse/jdt/ui/jarpackager/JarWriter3.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-before / ui / org / eclipse / jdt / ui / jarpackager / JarWriter3.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2011 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *     Ferenc Hechler, ferenc_hechler@users.sourceforge.net - 83258 [jar exporter] Deploy java application as executable jar
11  *     Ferenc Hechler <ferenc_hechler@users.sourceforge.net> - [jar exporter] export directory entries in "Runnable JAR File" - https://bugs.eclipse.org/bugs/show_bug.cgi?id=243163
12  *******************************************************************************/
13 package org.eclipse.jdt.ui.jarpackager;
14
15 import java.io.BufferedInputStream;
16 import java.io.BufferedOutputStream;
17 import java.io.File;
18 import java.io.FileInputStream;
19 import java.io.FileNotFoundException;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.net.URI;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Comparator;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Set;
31 import java.util.jar.JarEntry;
32 import java.util.jar.JarOutputStream;
33 import java.util.jar.Manifest;
34 import java.util.zip.ZipEntry;
35
36 import org.eclipse.swt.widgets.Shell;
37
38 import org.eclipse.core.filesystem.EFS;
39 import org.eclipse.core.filesystem.IFileInfo;
40
41 import org.eclipse.core.runtime.Assert;
42 import org.eclipse.core.runtime.CoreException;
43 import org.eclipse.core.runtime.IPath;
44 import org.eclipse.core.runtime.IProgressMonitor;
45 import org.eclipse.core.runtime.NullProgressMonitor;
46 import org.eclipse.core.runtime.OperationCanceledException;
47 import org.eclipse.core.runtime.Path;
48
49 import org.eclipse.core.resources.IContainer;
50 import org.eclipse.core.resources.IFile;
51 import org.eclipse.core.resources.IProject;
52 import org.eclipse.core.resources.IResource;
53 import org.eclipse.core.resources.ResourcesPlugin;
54
55 import org.eclipse.ltk.core.refactoring.RefactoringCore;
56 import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
57 import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy;
58
59 import org.eclipse.jdt.internal.corext.util.Messages;
60
61 import org.eclipse.jdt.internal.ui.JavaPlugin;
62 import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerMessages;
63 import org.eclipse.jdt.internal.ui.jarpackager.JarPackagerUtil;
64 import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
65
66
67 /**
68  * Creates a JAR file for the given JAR package data.
69  * <p>
70  * Clients may subclass.
71  * </p>
72  *
73  * @see org.eclipse.jdt.ui.jarpackager.JarPackageData
74  * @since 3.2
75  */
76 public class JarWriter3 {
77
78         private Set<String> fDirectories= new HashSet<String>();
79
80         private JarOutputStream fJarOutputStream;
81
82         private JarPackageData fJarPackage;
83
84         /**
85          * Creates an instance which is used to create a JAR based
86          * on the given JarPackage.
87          *
88          * @param jarPackage            the JAR specification
89          * @param parent                        the shell used to display question dialogs,
90          *                                                      or <code>null</code> if "false/no/cancel" is the answer
91          *                                                      and no dialog should be shown
92          * @throws      CoreException   to signal any other unusual termination.
93          *                                                      This can also be used to return information
94          *                                                      in the status object.
95          */
96         public JarWriter3(JarPackageData jarPackage, Shell parent) throws CoreException {
97                 Assert.isNotNull(jarPackage, "The JAR specification is null"); //$NON-NLS-1$
98                 fJarPackage= jarPackage;
99                 Assert.isTrue(fJarPackage.isValid(), "The JAR package specification is invalid"); //$NON-NLS-1$
100                 if (!canCreateJar(parent))
101                         throw new OperationCanceledException();
102
103                 try {
104                         if (fJarPackage.usesManifest() && fJarPackage.areGeneratedFilesExported()) {
105                                 Manifest manifest= fJarPackage.getManifestProvider().create(fJarPackage);
106                                 fJarOutputStream= new JarOutputStream(new BufferedOutputStream(new FileOutputStream(fJarPackage.getAbsoluteJarLocation().toFile())), manifest);
107                         } else
108                                 fJarOutputStream= new JarOutputStream(new BufferedOutputStream(new FileOutputStream(fJarPackage.getAbsoluteJarLocation().toFile())));
109                         String comment= jarPackage.getComment();
110                         if (comment != null)
111                                 fJarOutputStream.setComment(comment);
112                         if (fJarPackage.isRefactoringAware()) {
113                                 Assert.isTrue(fJarPackage.areDirectoryEntriesIncluded());
114                                 final IPath metaPath= new Path(JarPackagerUtil.getMetaEntry());
115                                 addDirectories(metaPath);
116                                 addHistory(fJarPackage, new Path(JarPackagerUtil.getRefactoringsEntry()), new NullProgressMonitor());
117                         }
118                 } catch (IOException exception) {
119                         throw JarPackagerUtil.createCoreException(exception.getLocalizedMessage(), exception);
120                 }
121         }
122         
123         /**
124          * Creates the directory entries for the given path and writes it to the current archive.
125          * 
126          * @param destinationPath the path to add
127          * 
128          * @throws IOException if an I/O error has occurred
129          */
130         protected void addDirectories(IPath destinationPath) throws IOException {
131                 addDirectories(destinationPath.toString());
132         }
133
134         /**
135          * Creates the directory entries for the given path and writes it to the current archive.
136          * 
137          * @param destPath the path to add
138          * 
139          * @throws IOException if an I/O error has occurred
140          * @since 3.5
141          */
142         protected void addDirectories(String destPath) throws IOException {
143                 String path= destPath.replace(File.separatorChar, '/');
144                 int lastSlash= path.lastIndexOf('/');
145                 List<JarEntry> directories= new ArrayList<JarEntry>(2);
146                 while (lastSlash != -1) {
147                         path= path.substring(0, lastSlash + 1);
148                         if (!fDirectories.add(path))
149                                 break;
150
151                         JarEntry newEntry= new JarEntry(path);
152                         newEntry.setMethod(ZipEntry.STORED);
153                         newEntry.setSize(0);
154                         newEntry.setCrc(0);
155                         newEntry.setTime(System.currentTimeMillis());
156                         directories.add(newEntry);
157
158                         lastSlash= path.lastIndexOf('/', lastSlash - 1);
159                 }
160
161                 for (int i= directories.size() - 1; i >= 0; --i) {
162                         fJarOutputStream.putNextEntry(directories.get(i));
163                 }
164         }
165
166         /**
167          * Creates the directory entries for the given path and writes it to the
168          * current archive.
169          *
170          * @param resource
171          *            the resource for which the parent directories are to be added
172          * @param destinationPath
173          *            the path to add
174          *
175          * @throws IOException
176          *             if an I/O error has occurred
177          * @throws CoreException
178          *             if accessing the resource failes
179          */
180         protected void addDirectories(IResource resource, IPath destinationPath) throws IOException, CoreException {
181                 IContainer parent= null;
182                 String path= destinationPath.toString().replace(File.separatorChar, '/');
183                 int lastSlash= path.lastIndexOf('/');
184                 List<JarEntry> directories= new ArrayList<JarEntry>(2);
185                 while (lastSlash != -1) {
186                         path= path.substring(0, lastSlash + 1);
187                         if (!fDirectories.add(path))
188                                 break;
189
190                         parent= resource.getParent();
191                         long timeStamp= System.currentTimeMillis();
192                         URI location= parent.getLocationURI();
193                         if (location != null) {
194                                 IFileInfo info= EFS.getStore(location).fetchInfo();
195                                 if (info.exists())
196                                         timeStamp= info.getLastModified();
197                         }
198
199                         JarEntry newEntry= new JarEntry(path);
200                         newEntry.setMethod(ZipEntry.STORED);
201                         newEntry.setSize(0);
202                         newEntry.setCrc(0);
203                         newEntry.setTime(timeStamp);
204                         directories.add(newEntry);
205
206                         lastSlash= path.lastIndexOf('/', lastSlash - 1);
207                 }
208
209                 for (int i= directories.size() - 1; i >= 0; --i) {
210                         fJarOutputStream.putNextEntry(directories.get(i));
211                 }
212         }
213
214         /**
215          * Creates a new JarEntry with the passed path and contents, and writes it
216          * to the current archive.
217          *
218          * @param       resource                        the file to write
219          * @param       path                            the path inside the archive
220          *
221      * @throws  IOException                     if an I/O error has occurred
222          * @throws      CoreException           if the resource can-t be accessed
223          */
224         protected void addFile(IFile resource, IPath path) throws IOException, CoreException {
225                 JarEntry newEntry= new JarEntry(path.toString().replace(File.separatorChar, '/'));
226                 byte[] readBuffer= new byte[4096];
227
228                 if (fJarPackage.isCompressed())
229                         newEntry.setMethod(ZipEntry.DEFLATED);
230                         // Entry is filled automatically.
231                 else {
232                         newEntry.setMethod(ZipEntry.STORED);
233                         JarPackagerUtil.calculateCrcAndSize(newEntry, resource.getContents(false), readBuffer);
234                 }
235
236                 long lastModified= System.currentTimeMillis();
237                 URI locationURI= resource.getLocationURI();
238                 if (locationURI != null) {
239                         IFileInfo info= EFS.getStore(locationURI).fetchInfo();
240                         if (info.exists())
241                                 lastModified= info.getLastModified();
242                 }
243
244                 // Set modification time
245                 newEntry.setTime(lastModified);
246
247                 InputStream contentStream = resource.getContents(false);
248
249                 addEntry(newEntry, contentStream);
250         }
251
252         /**
253          * Write the given entry describing the given content to the
254          * current archive
255          *
256          * @param   entry            the entry to write
257          * @param   content          the content to write
258          *
259          * @throws IOException       If an I/O error occurred
260          *
261          * @since 3.4
262          */
263         protected void addEntry(JarEntry entry, InputStream content) throws IOException {
264                 byte[] readBuffer= new byte[4096];
265                 try {
266                         fJarOutputStream.putNextEntry(entry);
267                         int count;
268                         while ((count= content.read(readBuffer, 0, readBuffer.length)) != -1)
269                                 fJarOutputStream.write(readBuffer, 0, count);
270                 } finally  {
271                         if (content != null)
272                                 content.close();
273
274                         /*
275                          * Commented out because some JREs throw an NPE if a stream
276                          * is closed twice. This works because
277                          * a) putNextEntry closes the previous entry
278                          * b) closing the stream closes the last entry
279                          */
280                         // fJarOutputStream.closeEntry();
281                 }
282         }
283
284         /**
285          * Creates a new JAR file entry containing the refactoring history.
286          *
287          * @param data
288          *            the jar package data
289          * @param path
290          *            the path of the refactoring history file within the archive
291          * @param monitor
292          *            the progress monitor to use
293          * @throws IOException
294          *             if no temp file could be written
295          * @throws CoreException
296          *             if an error occurs while transforming the refactorings
297          */
298         private void addHistory(final JarPackageData data, final IPath path, final IProgressMonitor monitor) throws IOException, CoreException {
299                 Assert.isNotNull(data);
300                 Assert.isNotNull(path);
301                 Assert.isNotNull(monitor);
302                 final RefactoringDescriptorProxy[] proxies= data.getRefactoringDescriptors();
303                 Arrays.sort(proxies, new Comparator<RefactoringDescriptorProxy>() {
304
305                         public final int compare(final RefactoringDescriptorProxy first, final RefactoringDescriptorProxy second) {
306                                 final long delta= first.getTimeStamp() - second.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(new FileOutputStream(file));
319                         try {
320                                 RefactoringCore.getHistoryService().writeRefactoringDescriptors(proxies, output, RefactoringDescriptor.NONE, false, monitor);
321                                 try {
322                                         output.close();
323                                         output= null;
324                                 } catch (IOException exception) {
325                                         // Do nothing
326                                 }
327                                 writeMetaData(data, file, path);
328                         } finally {
329                                 if (output != null) {
330                                         try {
331                                                 output.close();
332                                         } catch (IOException exception) {
333                                                 // Do nothing
334                                         }
335                                 }
336                         }
337                 } finally {
338                         if (file != null)
339                                 file.delete();
340                 }
341         }
342
343         /**
344          * Checks if the JAR file can be overwritten.
345          * If the JAR package setting does not allow to overwrite the JAR
346          * then a dialog will ask the user again.
347          *
348          * @param       parent  the parent for the dialog,
349          *                      or <code>null</code> if no dialog should be presented
350          * @return      <code>true</code> if it is OK to create the JAR
351          */
352         protected boolean canCreateJar(Shell parent) {
353                 File file= fJarPackage.getAbsoluteJarLocation().toFile();
354                 if (file.exists()) {
355                         if (!file.canWrite())
356                                 return false;
357                         if (fJarPackage.allowOverwrite())
358                                 return true;
359                         return parent != null && JarPackagerUtil.askForOverwritePermission(parent, fJarPackage.getAbsoluteJarLocation(), true);
360                 }
361
362                 // Test if directory exists
363                 String path= file.getAbsolutePath();
364                 int separatorIndex = path.lastIndexOf(File.separator);
365                 if (separatorIndex == -1) // i.e.- default directory, which is fine
366                         return true;
367                 File directory= new File(path.substring(0, separatorIndex));
368                 if (!directory.exists()) {
369                         if (JarPackagerUtil.askToCreateDirectory(parent, directory))
370                                 return directory.mkdirs();
371                         else
372                                 return false;
373                 }
374                 return true;
375         }
376
377         /**
378          * Closes the archive and does all required cleanup.
379          *
380          * @throws CoreException
381          *             to signal any other unusual termination. This can also be
382          *             used to return information in the status object.
383          */
384         public void close() throws CoreException {
385                 if (fJarOutputStream != null)
386                         try {
387                                 fJarOutputStream.close();
388                                 registerInWorkspaceIfNeeded();
389                         } catch (IOException ex) {
390                                 throw JarPackagerUtil.createCoreException(ex.getLocalizedMessage(), ex);
391                         }
392         }
393
394         private void registerInWorkspaceIfNeeded() {
395                 IPath jarPath= fJarPackage.getAbsoluteJarLocation();
396                 IProject[] projects= ResourcesPlugin.getWorkspace().getRoot().getProjects();
397                 for (int i= 0; i < projects.length; i++) {
398                         IProject project= projects[i];
399                         // The Jar is always put into the local file system. So it can only be
400                         // part of a project if the project is local as well. So using getLocation
401                         // is currently save here.
402                         IPath projectLocation= project.getLocation();
403                         if (projectLocation != null && projectLocation.isPrefixOf(jarPath)) {
404                                 try {
405                                         jarPath= jarPath.removeFirstSegments(projectLocation.segmentCount());
406                                         jarPath= jarPath.removeLastSegments(1);
407                                         IResource containingFolder= project.findMember(jarPath);
408                                         if (containingFolder != null && containingFolder.isAccessible())
409                                                 containingFolder.refreshLocal(IResource.DEPTH_ONE, null);
410                                 } catch (CoreException ex) {
411                                         // don't refresh the folder but log the problem
412                                         JavaPlugin.log(ex);
413                                 }
414                         }
415                 }
416         }
417
418         /**
419          * Writes the passed resource to the current archive.
420          *
421          * @param resource
422          *            the file to be written
423          * @param destinationPath
424          *            the path for the file inside the archive
425          * @throws CoreException
426          *             to signal any other unusual termination. This can also be
427          *             used to return information in the status object.
428          */
429         public void write(IFile resource, IPath destinationPath) throws CoreException {
430                 try {
431                         if (fJarPackage.areDirectoryEntriesIncluded())
432                                 addDirectories(resource, destinationPath);
433                         addFile(resource, destinationPath);
434                 } catch (IOException ex) {
435                         // Ensure full path is visible
436                         String message= null;
437                         if (ex.getLocalizedMessage() != null)
438                                 message= Messages.format(JarPackagerMessages.JarWriter_writeProblemWithMessage, new Object[] {BasicElementLabels.getPathLabel(resource.getFullPath(), false), ex.getLocalizedMessage()});
439                         else
440                                 message= Messages.format(JarPackagerMessages.JarWriter_writeProblem, BasicElementLabels.getPathLabel(resource.getFullPath(), false));
441                         throw JarPackagerUtil.createCoreException(message, ex);
442                 }
443         }
444
445         /**
446          * Writes the meta file to the JAR file.
447          *
448          * @param data
449          *            the jar package data
450          * @param file
451          *            the file containing the meta data
452          * @param path
453          *            the path of the meta file within the archive
454          * @throws FileNotFoundException
455          *             if the meta file could not be found
456          * @throws IOException
457          *             if an input/output error occurs
458          */
459         private void writeMetaData(final JarPackageData data, final File file, final IPath path) throws FileNotFoundException, IOException {
460                 Assert.isNotNull(data);
461                 Assert.isNotNull(file);
462                 Assert.isNotNull(path);
463                 final JarEntry entry= new JarEntry(path.toString().replace(File.separatorChar, '/'));
464                 byte[] buffer= new byte[4096];
465                 if (data.isCompressed())
466                         entry.setMethod(ZipEntry.DEFLATED);
467                 else {
468                         entry.setMethod(ZipEntry.STORED);
469                         JarPackagerUtil.calculateCrcAndSize(entry, new BufferedInputStream(new FileInputStream(file)), buffer);
470                 }
471                 entry.setTime(System.currentTimeMillis());
472                 final InputStream stream= new BufferedInputStream(new FileInputStream(file));
473                 try {
474                         fJarOutputStream.putNextEntry(entry);
475                         int count;
476                         while ((count= stream.read(buffer, 0, buffer.length)) != -1)
477                                 fJarOutputStream.write(buffer, 0, count);
478                 } finally {
479                         try {
480                                 stream.close();
481                         } catch (IOException exception) {
482                                 // Do nothing
483                         }
484                 }
485         }
486 }