Java tutorial
/******************************************************************************* * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * Contributors: * Gregory Amerson - initial implementation and ongoing maintenance *******************************************************************************/ package com.liferay.ide.theme.core.util; import com.liferay.ide.theme.core.ThemeCore; 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.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.eclipse.wst.server.core.internal.Messages; import org.eclipse.wst.server.core.internal.ProgressUtil; /** * @author Gregory Amerson */ @SuppressWarnings("restriction") public class BuildHelper { // size of the buffer private static final int BUFFER = 65536; // the buffer private static byte[] buf = new byte[BUFFER]; private static final IStatus[] EMPTY_STATUS = new IStatus[0]; private static final File defaultTempDir = ThemeCore.getDefault().getStateLocation().toFile(); private static final String TEMPFILE_PREFIX = ".tmp-safe-to-delete-"; //$NON-NLS-1$ private File tempDir; /** * Create a new PublishHelper. * * @param tempDirectory * a temporary directory to use during publishing, or <code>null</code> to use the default. If it does * not exist, the folder will be created */ public BuildHelper() { tempDir = defaultTempDir; if (!tempDir.exists()) { tempDir.mkdirs(); } } /** * Copy a file from a to b. Closes the input stream after use. * * @param in * an input stream * @param to * a path to copy to. the directory must already exist * @param ts * timestamp * @throws CoreException * if anything goes wrong */ private void copyFile(InputStream in, IPath to, long ts, IFile mf) throws CoreException { OutputStream out = null; File tempFile = null; File tempFileParentDir = null; try { File file = to.toFile(); // IDE-796 need to make sure temporary file is generated in same directory as file destination so that // file.renameTo() will never fail due to source/destination being on two different file systems if (file != null && file.getParentFile().exists()) { tempFileParentDir = to.toFile().getParentFile(); } else { tempFileParentDir = tempDir; } tempFile = File.createTempFile(TEMPFILE_PREFIX, "." + to.getFileExtension(), tempFileParentDir); //$NON-NLS-1$ out = new FileOutputStream(tempFile); int avail = in.read(buf); while (avail > 0) { out.write(buf, 0, avail); avail = in.read(buf); } out.close(); out = null; moveTempFile(tempFile, file); if (ts != IResource.NULL_STAMP && ts != 0) file.setLastModified(ts); } catch (CoreException e) { throw e; } catch (Exception e) { } finally { if (tempFile != null && tempFile.exists()) tempFile.deleteOnExit(); try { if (in != null) in.close(); } catch (Exception ex) { // ignore } try { if (out != null) out.close(); } catch (Exception ex) { // ignore } } } /** * Utility method to recursively delete a directory. * * @param dir * a directory * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public static IStatus[] deleteDirectory(File dir, IProgressMonitor monitor) { if (!dir.exists() || !dir.isDirectory()) return new IStatus[] { new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorNotADirectory, dir.getAbsolutePath()), null) }; List<IStatus> status = new ArrayList<IStatus>(2); try { File[] files = dir.listFiles(); int size = files.length; monitor = ProgressUtil.getMonitorFor(monitor); monitor.beginTask(NLS.bind(Messages.deletingTask, new String[] { dir.getAbsolutePath() }), size * 10); // cycle through files boolean deleteCurrent = true; for (int i = 0; i < size; i++) { File current = files[i]; if (current.isFile()) { if (!current.delete()) { status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, files[i].getAbsolutePath()), null)); deleteCurrent = false; } monitor.worked(10); } else if (current.isDirectory()) { monitor.subTask(NLS.bind(Messages.deletingTask, new String[] { current.getAbsolutePath() })); IStatus[] stat = deleteDirectory(current, ProgressUtil.getSubMonitorFor(monitor, 10)); if (stat != null && stat.length > 0) { deleteCurrent = false; addArrayToList(status, stat); } } } if (deleteCurrent && !dir.delete()) status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, dir.getAbsolutePath()), null)); monitor.done(); } catch (Exception e) { ThemeCore.logError("Error deleting directory " + dir.getAbsolutePath(), e); //$NON-NLS-1$ status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, e.getLocalizedMessage(), null)); } IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } /** * Smart copy the given module resources to the given path. * * @param resources * an array of module resources * @param path * an external path to copy to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishSmart(IResource[] resources, IPath path, IProgressMonitor monitor) { return publishSmart(resources, path, null, monitor); } /** * Smart copy the given module resources to the given path. * * @param resources * an array of module resources * @param path * an external path to copy to * @param ignore * an array of paths relative to path to ignore, i.e. not delete or copy over * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishSmart(IResource[] resources, IPath path, IPath[] ignore, IProgressMonitor monitor) { if (resources == null) return EMPTY_STATUS; monitor = ProgressUtil.getMonitorFor(monitor); List<IStatus> status = new ArrayList<IStatus>(2); File toDir = path.toFile(); int fromSize = resources.length; String[] fromFileNames = new String[fromSize]; for (int i = 0; i < fromSize; i++) fromFileNames[i] = resources[i].getName(); List<String> ignoreFileNames = new ArrayList<String>(); if (ignore != null) { for (int i = 0; i < ignore.length; i++) { if (ignore[i].segmentCount() == 1) { ignoreFileNames.add(ignore[i].toOSString()); } } } // cache files and file names for performance File[] toFiles = null; String[] toFileNames = null; boolean foundExistingDir = false; if (toDir.exists()) { if (toDir.isDirectory()) { foundExistingDir = true; toFiles = toDir.listFiles(); int toSize = toFiles.length; toFileNames = new String[toSize]; // check if this exact file exists in the new directory for (int i = 0; i < toSize; i++) { toFileNames[i] = toFiles[i].getName(); boolean isDir = toFiles[i].isDirectory(); boolean found = false; for (int j = 0; j < fromSize; j++) { if (toFileNames[i].equals(fromFileNames[j]) && isDir == resources[j] instanceof IFolder) { found = true; break; } } // delete file if it can't be found or isn't the correct type if (!found) { boolean delete = true; // if should be preserved, don't delete and don't try to copy for (String preserveFileName : ignoreFileNames) { if (toFileNames[i].equals(preserveFileName)) { delete = false; break; } } if (delete) { if (isDir) { IStatus[] stat = deleteDirectory(toFiles[i], null); addArrayToList(status, stat); } else { if (!toFiles[i].delete()) status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, toFiles[i].getAbsolutePath()), null)); } } toFiles[i] = null; toFileNames[i] = null; } } } else { // if (toDir.isFile()) if (!toDir.delete()) { status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, toDir.getAbsolutePath()), null)); IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } } } if (!foundExistingDir && !toDir.mkdirs()) { status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorMkdir, toDir.getAbsolutePath()), null)); IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } if (monitor.isCanceled()) return new IStatus[] { Status.CANCEL_STATUS }; monitor.worked(50); // cycle through files and only copy when it doesn't exist // or is newer if (toFiles == null) { toFiles = toDir.listFiles(); if (toFiles == null) toFiles = new File[0]; } int toSize = toFiles.length; int dw = 0; if (toSize > 0) dw = 500 / toSize; // cache file names and last modified dates for performance if (toFileNames == null) toFileNames = new String[toSize]; long[] toFileMod = new long[toSize]; for (int i = 0; i < toSize; i++) { if (toFiles[i] != null) { if (toFileNames[i] != null) toFileNames[i] = toFiles[i].getName(); toFileMod[i] = toFiles[i].lastModified(); } } for (int i = 0; i < fromSize; i++) { IResource current = resources[i]; String name = fromFileNames[i]; boolean currentIsDir = current instanceof IFolder; if (!currentIsDir) { // check if this is a new or newer file boolean copy = true; IFile mf = (IFile) current; long mod = -1; IFile file = (IFile) mf.getAdapter(IFile.class); if (file != null) { mod = file.getLocalTimeStamp(); } else { File file2 = (File) mf.getAdapter(File.class); mod = file2.lastModified(); } for (int j = 0; j < toSize; j++) { if (name.equals(toFileNames[j]) && mod == toFileMod[j]) { copy = false; break; } } if (copy) { try { copyFile(mf, path.append(name)); } catch (CoreException ce) { status.add(ce.getStatus()); } } monitor.worked(dw); } else { // if (currentIsDir) { IFolder folder = (IFolder) current; IResource[] children = null; try { children = folder.members(); } catch (CoreException e) { e.printStackTrace(); } // build array of ignored Paths that apply to this folder IPath[] ignoreChildren = null; if (ignore != null) { List<IPath> ignoreChildPaths = new ArrayList<IPath>(); for (int j = 0; j < ignore.length; j++) { IPath preservePath = ignore[j]; if (preservePath.segment(0).equals(name)) { ignoreChildPaths.add(preservePath.removeFirstSegments(1)); } } if (ignoreChildPaths.size() > 0) ignoreChildren = ignoreChildPaths.toArray(new Path[ignoreChildPaths.size()]); } monitor.subTask(NLS.bind(Messages.copyingTask, new String[] { name, name })); IStatus[] stat = publishSmart(children, path.append(name), ignoreChildren, ProgressUtil.getSubMonitorFor(monitor, dw)); addArrayToList(status, stat); } } if (monitor.isCanceled()) return new IStatus[] { Status.CANCEL_STATUS }; monitor.worked(500 - dw * toSize); monitor.done(); IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } /** * Handle a delta publish. * * @param delta * a module resource delta * @param path * the path to publish to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishDelta(IResourceDelta[] delta, IPath path, IPath[] restorePaths, IProgressMonitor monitor) { if (delta == null) return EMPTY_STATUS; monitor = ProgressUtil.getMonitorFor(monitor); List<IStatus> status = new ArrayList<IStatus>(2); int size2 = delta.length; for (int i = 0; i < size2; i++) { IStatus[] stat = publishDelta(delta[i], path, restorePaths, monitor); addArrayToList(status, stat); } IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } /** * Handle a delta publish. * * @param delta * a module resource delta * @param path * the path to publish to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishDelta(IResourceDelta delta, IPath path, IPath[] restorePaths, IProgressMonitor monitor) { List<IStatus> status = new ArrayList<IStatus>(2); IResource resource = delta.getResource(); int kind2 = delta.getKind(); if (resource instanceof IFile) { IFile file = (IFile) resource; try { if (kind2 == IResourceDelta.REMOVED) { deleteFile(path, file, restorePaths); } else { IPath diffsRelativePath = getDiffsRelativePath(file.getProjectRelativePath()); if (diffsRelativePath != null) { IPath path2 = path.append(diffsRelativePath); File f = path2.toFile().getParentFile(); if (!f.exists()) { f.mkdirs(); } copyFile(file, path2); } } } catch (CoreException ce) { status.add(ce.getStatus()); } IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } if (kind2 == IResourceDelta.ADDED) { // find relative path from _diffs and append that to path. IPath diffsPath = resource.getProjectRelativePath(); IPath diffsRelativePath = getDiffsRelativePath(diffsPath); if (diffsRelativePath != null) { IPath path2 = path.append(diffsRelativePath); // IPath path2 = path.append( resource.getProjectRelativePath() ).append( resource.getName() ); File file = path2.toFile(); if (!file.exists() && !file.mkdirs()) { status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorMkdir, path2), null)); IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } } } IResourceDelta[] childDeltas = delta.getAffectedChildren(); int size = childDeltas.length; for (int i = 0; i < size; i++) { IStatus[] stat = publishDelta(childDeltas[i], path, restorePaths, monitor); addArrayToList(status, stat); } if (kind2 == IResourceDelta.REMOVED) { IPath diffsRelativePath = getDiffsRelativePath(resource.getProjectRelativePath()); if (diffsRelativePath != null) { IPath path2 = path.append(diffsRelativePath); //IPath path2 = path.append( resource.getProjectRelativePath() ).append( resource.getName() ); File file = path2.toFile(); if (file.exists() && !file.delete()) { status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, path2), null)); } } } IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } private static IPath getDiffsRelativePath(IPath diffsPath) { IPath diffsRelativePath = null; for (int i = 0; i < diffsPath.segmentCount(); i++) { if ("_diffs".equals(diffsPath.segment(i))) //$NON-NLS-1$ { diffsRelativePath = diffsPath.removeFirstSegments(i + 1); break; } } return diffsRelativePath; } private static void deleteFile(IPath path, IFile file, IPath[] restorePaths) throws CoreException { IPath diffsPath = file.getProjectRelativePath(); IPath diffsRelativePath = getDiffsRelativePath(diffsPath); if (diffsRelativePath != null) { // IPath path2 = path.append( file.getProjectRelativePath() ).append( file.getName() ); IPath path2 = path.append(diffsRelativePath); // restore this file from the first restorePaths that matches boolean restored = false; for (IPath restorePath : restorePaths) { final File restoreFile = restorePath.append(diffsRelativePath).toFile(); if (restoreFile.exists()) { try { FileUtils.copyFile(restoreFile, path2.toFile()); restored = true; break; } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind("Error restoring theme file.", path2), null)); //$NON-NLS-1$ } } } if (!restored) { if (path2.toFile().exists() && !path2.toFile().delete()) { throw new CoreException(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, path2), null)); } } } } private void copyFile(IFile mf, IPath path) throws CoreException { if (!isCopyFile(mf, path)) { return; } IFile file = (IFile) mf.getAdapter(IFile.class); if (file != null) copyFile(file.getContents(), path, file.getLocalTimeStamp(), mf); else { File file2 = (File) mf.getAdapter(File.class); InputStream in = null; try { in = new FileInputStream(file2); } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorReading, file2.getAbsolutePath()), e)); } copyFile(in, path, file2.lastModified(), mf); } } /** * Returns <code>true<code/> if the module file should be copied to the destination, <code>false</codre> otherwise. * * @param moduleFile * the module file * @param toPath * destination. * @return <code>true<code/>, if the module file should be copied */ protected boolean isCopyFile(IFile moduleFile, IPath toPath) { return true; } /** * Publish the given module resources to the given path. * * @param resources * an array of module resources * @param path * a path to publish to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishFull(IResource[] resources, IPath path, IProgressMonitor monitor) { if (resources == null) return EMPTY_STATUS; monitor = ProgressUtil.getMonitorFor(monitor); List<IStatus> status = new ArrayList<IStatus>(2); int size = resources.length; for (int i = 0; i < size; i++) { IStatus[] stat = copy(resources[i], path, monitor); addArrayToList(status, stat); if (monitor.isCanceled()) { break; } } IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } private IStatus[] copy(IResource resource, IPath path, IProgressMonitor monitor) { if (monitor != null && monitor.isCanceled()) { return new IStatus[0]; } List<IStatus> status = new ArrayList<IStatus>(2); if (resource instanceof IFolder) { IFolder folder = (IFolder) resource; IStatus[] stat; try { stat = publishFull(folder.members(), path, monitor); addArrayToList(status, stat); } catch (CoreException e) { e.printStackTrace(); } } else { IFile mf = (IFile) resource; IPath diffsRelativePath = getDiffsRelativePath(mf.getProjectRelativePath()); if (diffsRelativePath != null) { // path = path.append( mf.getProjectRelativePath() ).append( name ); path = path.append(diffsRelativePath); File f = path.toFile().getParentFile(); if (f.exists()) { try { copyFile(mf, path); } catch (CoreException ce) { status.add(ce.getStatus()); } } else { // Create the parent directory. if (f.mkdirs()) { try { copyFile(mf, path); } catch (CoreException ce) { status.add(ce.getStatus()); } } else { status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorMkdir, f.getAbsolutePath()), null)); } } } } IStatus[] stat = new IStatus[status.size()]; status.toArray(stat); return stat; } /** * Accepts an IModuleResource array which is expected to contain a single IModuleFile resource and copies it to the * specified path, which should include the name of the file to write. If the array contains more than a single * resource or the resource is not an IModuleFile resource, the file is not created. Currently no error is returned, * but error handling is recommended since that is expected to change in the future. * * @param resources * an array containing a single IModuleFile resource * @param path * the path, including file name, where the file should be created * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishToPath(IResource[] resources, IPath path, IProgressMonitor monitor) { if (resources == null || resources.length == 0) { // should also check if resources consists of all empty directories File file = path.toFile(); if (file.exists()) file.delete(); return EMPTY_STATUS; } monitor = ProgressUtil.getMonitorFor(monitor); if (resources.length == 1 && resources[0] instanceof IFile) { try { copyFile((IFile) resources[0], path); } catch (CoreException e) { return new IStatus[] { e.getStatus() }; } } return EMPTY_STATUS; } /** * Utility method to move a temp file into position by deleting the original and swapping in a new copy. * * @param tempFile * @param file * @throws CoreException */ private void moveTempFile(File tempFile, File file) throws CoreException { if (file.exists()) { if (!safeDelete(file, 2)) { // attempt to rewrite an existing file with the tempFile contents if // the existing file can't be deleted to permit the move try { InputStream in = new FileInputStream(tempFile); IStatus status = copyFile(in, file.getPath()); if (!status.isOK()) { MultiStatus status2 = new MultiStatus(ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, file.toString()), null); status2.add(status); throw new CoreException(status2); } return; } catch (FileNotFoundException e) { // shouldn't occur } finally { tempFile.delete(); } /* * if (!safeDelete(file, 8)) { tempFile.delete(); throw new CoreException(new Status(IStatus.ERROR, * ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, file.toString()), null)); } */ } } if (!safeRename(tempFile, file, 10)) throw new CoreException(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorRename, tempFile.toString()), null)); } /** * Copy a file from a to b. Closes the input stream after use. * * @param in * an InputStream * @param to * the file to copy to * @return a status */ private IStatus copyFile(InputStream in, String to) { OutputStream out = null; try { out = new FileOutputStream(to); int avail = in.read(buf); while (avail > 0) { out.write(buf, 0, avail); avail = in.read(buf); } return Status.OK_STATUS; } catch (Exception e) { ThemeCore.logError("Error copying file", e); //$NON-NLS-1$ return new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorCopyingFile, new String[] { to, e.getLocalizedMessage() }), e); } finally { try { if (in != null) in.close(); } catch (Exception ex) { // ignore } try { if (out != null) out.close(); } catch (Exception ex) { // ignore } } } /** * Safe delete. Tries to delete multiple times before giving up. * * @param f * @return <code>true</code> if it succeeds, <code>false</code> otherwise */ private static boolean safeDelete(File f, int retrys) { int count = 0; while (count < retrys) { if (f.delete()) { return true; } count++; // delay if we are going to try again if (count < retrys) { try { Thread.sleep(100); } catch (Exception e) { // ignore } } } return false; } /** * Safe rename. Will try multiple times before giving up. * * @param from * @param to * @param retrys * number of times to retry * @return <code>true</code> if it succeeds, <code>false</code> otherwise */ private static boolean safeRename(File from, File to, int retrys) { // make sure parent dir exists File dir = to.getParentFile(); if (dir != null && !dir.exists()) dir.mkdirs(); int count = 0; while (count < retrys) { if (from.renameTo(to)) return true; count++; // delay if we are going to try again if (count < retrys) { try { Thread.sleep(100); } catch (Exception e) { // ignore } } } return false; } private static void addArrayToList(List<IStatus> list, IStatus[] a) { if (list == null || a == null || a.length == 0) return; int size = a.length; for (int i = 0; i < size; i++) list.add(a[i]); } }