/******************************************************************************* * Copyright © 2000, 2013 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.edt.ide.ui.internal.eglarpackager; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.zip.ZipEntry; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; 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.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.swt.widgets.Shell; import org.eclipse.edt.compiler.internal.eglar.EglarManifest; import org.eclipse.edt.ide.ui.EDTUIPlugin; import org.eclipse.edt.ide.ui.internal.property.pages.BasicElementLabels; import org.eclipse.edt.ide.ui.internal.property.pages.Messages; public class EglarWriterUtility { private Set fDirectories= new HashSet(); private EglarOutputStream fEglarOutputStream; private EglarPackageData fEglarPackage; /** * Creates an instance which is used to create a Eglar based * on the given eglarPackage. * * @param eglarPackage the Eglar 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 EglarWriterUtility(EglarPackageData eglarPackage, Shell parent) throws CoreException { Assert.isNotNull(eglarPackage, "The Eglar specification is null"); //$NON-NLS-1$ fEglarPackage= eglarPackage; Assert.isTrue(fEglarPackage.isValid(), "The EGLAR or binary project specification is invalid"); //$NON-NLS-1$ if (!canCreateEglar(parent)) throw new OperationCanceledException(); try { fEglarOutputStream= new EglarOutputStream(new BufferedOutputStream(new FileOutputStream(fEglarPackage.getAbsoluteEglarLocation().toFile()))); String comment= eglarPackage.getComment(); if (comment != null) fEglarOutputStream.setComment(comment); } catch (IOException exception) { throw EglarPackagerUtil.createCoreException(exception.getLocalizedMessage(), exception); } } public EglarWriterUtility(EglarOutputStream eglarOutputStream, Shell parent) { this.fEglarOutputStream = eglarOutputStream; } /** * 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 { addDirectories(destinationPath.toString()); } /** * Creates the directory entries for the given path and writes it to the current archive. * * @param destPath the path to add * * @throws IOException if an I/O error has occurred * @since 3.5 */ protected void addDirectories(String destPath) throws IOException { String path= destPath.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) { fEglarOutputStream.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 * @throws CoreException * if accessing the resource failes */ 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) { fEglarOutputStream.putNextEntry((JarEntry) directories.get(i)); } } /** * Creates a new EglarEntry 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(fEglarPackage == null) { newEntry.setMethod(ZipEntry.DEFLATED); } else { if (fEglarPackage.isCompressed()) newEntry.setMethod(ZipEntry.DEFLATED); // Entry is filled automatically. else { newEntry.setMethod(ZipEntry.STORED); EglarPackagerUtil.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); addEntry(newEntry, contentStream); } protected void addFileByBytes(byte[] bytes, IPath path) throws CoreException { JarEntry newEntry= new JarEntry(path.toString().replace(File.separatorChar, '/')); newEntry.setMethod(ZipEntry.DEFLATED); ByteArrayInputStream contentStream = new ByteArrayInputStream(bytes); try { addEntry(newEntry, contentStream); } catch (IOException e) { handleGeneralEglarException(path, e); } } public void addManifest(EglarManifest eglarManifest) throws IOException { fEglarOutputStream.addManEntry(eglarManifest); } /** * Write the given entry describing the given content to the * current archive * * @param entry the entry to write * @param content the content to write * * @throws IOException If an I/O error occurred * * @since 3.4 */ protected void addEntry(JarEntry entry, InputStream content) throws IOException { byte[] readBuffer= new byte[4096]; try { fEglarOutputStream.putNextEntry(entry); int count; while ((count= content.read(readBuffer, 0, readBuffer.length)) != -1) fEglarOutputStream.write(readBuffer, 0, count); } finally { if (content != null) content.close(); } } /** * Checks if the Eglar file can be overwritten. * If the Eglar package setting does not allow to overwrite the Eglar * 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 canCreateEglar(Shell parent) { File file= fEglarPackage.getAbsoluteEglarLocation().toFile(); if (file.exists()) { if (!file.canWrite()) return false; if (fEglarPackage.isBinaryProjectExport() || fEglarPackage.allowOverwrite()) return true; return parent != null && EglarPackagerUtil.askForOverwritePermission(parent, fEglarPackage.getAbsoluteEglarLocation(), true); } // 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+1)); if (!directory.exists()) { if (fEglarPackage.isBinaryProjectExport() || EglarPackagerUtil.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 { try { if (fEglarOutputStream != null) { fEglarOutputStream.close(); } registerInWorkspaceIfNeeded(); } catch (IOException ex) { throw EglarPackagerUtil.createCoreException(ex.getLocalizedMessage(), ex); } } private void registerInWorkspaceIfNeeded() { IPath eglarPath= fEglarPackage.getAbsoluteEglarLocation(); refresh(eglarPath); refresh(fEglarPackage.getAbsoluteEglarSrcLocation()); } private void refresh(IPath path) { IProject[] projects= ResourcesPlugin.getWorkspace().getRoot().getProjects(); for (int i= 0; i < projects.length; i++) { IProject project= projects[i]; // The Eglar 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(path)) { try { path= path.removeFirstSegments(projectLocation.segmentCount()); path= path.removeLastSegments(1); IResource containingFolder= project.findMember(path); if (containingFolder != null && containingFolder.isAccessible()) containingFolder.refreshLocal(IResource.DEPTH_ONE, null); } catch (CoreException ex) { // don't refresh the folder but log the problem EDTUIPlugin.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 (fEglarPackage.areDirectoryEntriesIncluded()) addDirectories(resource, destinationPath); addFile(resource, destinationPath); } catch (IOException ex) { handleGeneralEglarException(resource.getFullPath(), ex); } } public static void handleGeneralEglarException(IPath path, Exception ex) throws CoreException{ // Ensure full path is visible String message= null; if (ex.getLocalizedMessage() != null) message= Messages.format(EglarPackagerMessages.EglarWriter_writeProblemWithMessage, new Object[] {BasicElementLabels.getPathLabel(path, false), ex.getLocalizedMessage()}); else message= Messages.format(EglarPackagerMessages.EglarWriter_writeProblem, BasicElementLabels.getPathLabel(path, false)); throw EglarPackagerUtil.createCoreException(message, ex); } }