Java tutorial
/******************************************************************************* * Copyright (c) 2005-2009 VecTrace (Zingo Andersen) 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: * bastian - implementation * Andrei Loskutov - bugfixes * Zsolt Koppany (Intland) *******************************************************************************/ package com.vectrace.MercurialEclipse.utils; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.ide.ResourceUtil; import com.google.common.io.ByteStreams; import com.vectrace.MercurialEclipse.MercurialEclipsePlugin; import com.vectrace.MercurialEclipse.commands.HgLocateClient; import com.vectrace.MercurialEclipse.commands.HgStatusClient; import com.vectrace.MercurialEclipse.exception.HgException; import com.vectrace.MercurialEclipse.model.ChangeSet; import com.vectrace.MercurialEclipse.model.FileFromChangeSet; import com.vectrace.MercurialEclipse.model.HgRoot; import com.vectrace.MercurialEclipse.model.IHgResource; import com.vectrace.MercurialEclipse.model.JHgChangeSet; import com.vectrace.MercurialEclipse.model.PathFromChangeSet; import com.vectrace.MercurialEclipse.team.MercurialTeamProvider; import com.vectrace.MercurialEclipse.team.cache.LocalChangesetCache; import com.vectrace.MercurialEclipse.team.cache.MercurialRootCache; /** * @author bastian * @author Andrei Loskutov */ public final class ResourceUtils { public static final IPath[] NO_PATHS = new Path[0]; private static final File TMP_ROOT = new File(System.getProperty("java.io.tmpdir")); private static long tmpFileSuffix; private ResourceUtils() { // hide constructor of utility class. } public static File getSystemTempDirectory() { return TMP_ROOT; } /** * @return a newly created temp directory which is located inside the default temp directory */ public static File createNewTempDirectory() { File tmp = getSystemTempDirectory(); File newTemp = null; while (!(newTemp = new File(tmp, "hgTemp_" + tmpFileSuffix)).mkdir()) { tmpFileSuffix++; } return newTemp; } /** * If "recursive" is false, then this is a single file/directory delete operation. Directory * should be empty before it can be deleted. If "recursive" is true, then all children will be * deleted too. * * @param source * @return true if source was successfully deleted or if it was not existing */ public static boolean delete(File source, boolean recursive) { if (source == null || !source.exists()) { return true; } if (recursive) { if (source.isDirectory()) { for (File file : source.listFiles()) { boolean ok = delete(file, true); if (!ok) { return false; } } } } boolean result = source.delete(); if (!result && !source.isDirectory()) { MercurialEclipsePlugin.logWarning("Could not delete file '" + source + "'", null); } return result; } /** * Moves contents of one directory to another and deletes source directory if all files were * successfully moved to destination. If any target file with the same relative path exists in the * destination directory, it will be NOT overridden, and kept in the source directory. * * @param sourceDir * - must already exist and be a directory * @param destinationDir * - must already exist and be a directory * @return true if source was successfully moved to destination. */ public static boolean move(File sourceDir, File destinationDir) { File[] files = sourceDir.listFiles(); if (files == null) { // can't be ok return false; } Set<File> fileSet = new LinkedHashSet<File>(Arrays.asList(files)); boolean result = true; while (!fileSet.isEmpty()) { File next = fileSet.iterator().next(); String relative = toRelative(sourceDir, next); File dest = new File(destinationDir, relative); if (!dest.exists()) { result &= next.renameTo(dest); } else if (next.isDirectory()) { files = next.listFiles(); if (files != null) { fileSet.addAll(Arrays.asList(files)); } } else { // file exists in target result = false; } fileSet.remove(next); } try { if (result && !sourceDir.getCanonicalFile().equals(destinationDir.getCanonicalFile())) { return ResourceUtils.delete(sourceDir, true); } } catch (IOException e) { MercurialEclipsePlugin.logError(e); } return false; } /** * Converts given path to the relative * * @param parent * parent path, non null * @param child * a possible child path, non null * @return a parent relative path of a given child file, if the given child file is located * under given parent, otherwise the given child path. If the given child path matches * the parent, returns an empty string */ public static String toRelative(File parent, File child) { // first try with the unresolved path. In most cases it's enough String fullPath = child.getAbsolutePath(); String parentpath = parent.getPath(); if (!pathStartsWith(parentpath, fullPath)) { try { // ok, now try to resolve all the links etc. this takes A LOT of time... fullPath = child.getCanonicalPath(); if (!pathStartsWith(parentpath, fullPath)) { return child.getPath(); } } catch (IOException e) { MercurialEclipsePlugin.logError(e); return child.getPath(); } } if (fullPath.equals(parentpath)) { return Path.EMPTY.toOSString(); } // +1 is to remove the file separator / at the start of the relative path return fullPath.substring(parentpath.length() + 1); } private static boolean pathStartsWith(String parentpath, String childPath) { final int nParentLen = parentpath.length(); return childPath.startsWith(parentpath) && (childPath.length() == nParentLen || isDirSep(childPath.charAt(nParentLen))); } private static boolean isDirSep(char ch) { return ch == '/' || ch == '\\'; } /** * Checks which editor is active an determines the IResource that is edited. */ public static IFile getActiveResourceFromEditor() { IEditorPart editorPart = MercurialEclipsePlugin.getActivePage().getActiveEditor(); if (editorPart != null) { return ResourceUtil.getFile(editorPart.getEditorInput()); } return null; } /** * @param resource * a handle to possibly non-existing resource * @return a (file) path representing given resource, never null. May return an "empty" file. */ public static File getFileHandle(IResource resource) { return getPath(resource).toFile(); } /** * Tries to determine the encoding for a file resource. Returns null, if the encoding cannot be * determined. */ @SuppressWarnings("unused") public static String getFileEncoding(IFile resource) { try { String charset = resource.getCharset(true); if (charset != null) { new String(new byte[] {}, charset); //test that JVM has the charset available } return charset; } catch (CoreException e) { //cannot determine the file charset return null; } catch (UnsupportedEncodingException e) { //unknown encoding, ignore the request return null; } } /** * @param path * a path to possibly non-existing or not mapped resource * @return a (file) representing given resource, may return null if the resource is not in the * workspace */ public static IFile getFileHandle(IPath path) { if (path == null) { return null; } return (IFile) getHandle(path, true); } private static IResource getHandle(final IPath origPath, boolean isFile) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); // origPath may be canonical but not necessarily. // Eclipse allows a project to be symlinked or an arbitrary folder under a project // to be symlinked. Also Eclipse allows a subtree of one project to exist as another // project. // Mercurial doesn't follow symbolic links so if path is canonical it is sufficient to find // a containing hg root. hg roots are always canonical. // There is an unresolvable ambiguity when a project with a sub repo is imported // as a project. Such cases are unsupported for now. // If one of the candidate resources is under a Mercurial managed project it must // be returned. IPath[] paths = NO_PATHS; IResource best = null; loop: for (int i = 0;; i++) { IPath path; switch (i) { case 0: path = origPath; break; case 1: // Only query the root cache if the plain path didn't find a definite match. paths = MercurialRootCache.getInstance().uncanonicalize(origPath); //$FALL-THROUGH$ default: if (i - 1 >= paths.length) { break loop; } path = paths[i - 1]; } URI uri = URIUtil.toURI(path.makeAbsolute()); IResource[] resources = isFile ? root.findFilesForLocationURI(uri) : root.findContainersForLocationURI(uri); if (resources.length > 0) { if (resources.length == 1) { best = resources[0]; } else { // try to find the first file contained in a hg root and managed by our team // provider for (IResource resource : resources) { if (MercurialTeamProvider.isHgTeamProviderFor(resource.getProject())) { return resource; } } } } else { best = ifNull(isFile ? root.getFileForLocation(path) : root.getContainerForLocation(path), best); } IProject proj; // Is best a definite match? if (best != null && (proj = best.getProject()) != null && MercurialTeamProvider.isHgTeamProviderFor(proj)) { return best; } } if (best == null) { Collection<HgRoot> roots = MercurialRootCache.getInstance().getKnownHgRoots(); for (HgRoot hgRoot : roots) { if (!hgRoot.getIPath().isPrefixOf(origPath)) { continue; } IPath relative = hgRoot.toRelative(origPath); if (relative.isEmpty()) { if (!isFile) { // same folder as root return hgRoot.getResource(); } // requested is file => some error! return null; } best = hgRoot.getResource().findMember(relative); if (best != null) { if (isFile && best.getType() == IResource.FILE || !isFile && best.getType() != IResource.FILE) { return best; } if (isFile) { return hgRoot.getResource().getFile(relative); } return hgRoot.getResource().getFolder(relative); } } } if (best != null) { return best; } // here we didn't found any existing file matching any hg root // but we can try to return a handle to not existing resource, // according to API contract of IResource.getFile() // second try: now we return also handles to currently not existing files // if they *could* be located under the first matching root Collection<HgRoot> roots = MercurialRootCache.getInstance().getKnownHgRoots(); for (HgRoot hgRoot : roots) { if (!hgRoot.getIPath().isPrefixOf(origPath)) { continue; } IPath relative = hgRoot.toRelative(origPath); if (!relative.isEmpty()) { if (isFile) { return hgRoot.getResource().getFile(relative); } return hgRoot.getResource().getFolder(relative); } } return null; } private static IResource ifNull(IResource a, IResource b) { return a == null ? b : a; } /** * @param resource * a handle to possibly non-existing resource * @return An absolute (file) path representing given resource, might be {@link Path#EMPTY} in * case the resource location and project location are both unknown. {@link Path#EMPTY} * return value will be logged as error unless virtual. */ public static IPath getPath(IResource resource) { IPath path = resource.getLocation(); if (path == null) { // file was removed IProject project = resource.getProject(); IPath projectLocation = project.getLocation(); if (projectLocation == null) { // project removed too, there is no way to correctly determine the right // location in case project is not located under workspace or project name doesn't // match project root folder name String message = "Failed to resolve location for resource (project deleted): " + resource; MercurialEclipsePlugin.logWarning(message, new IllegalStateException(message)); return Path.EMPTY; } URI locationURI = resource.getLocationURI(); if (isVirtual(locationURI)) { // path is null for virtual folders => we can't do anything here return Path.EMPTY; } path = projectLocation.append(resource.getFullPath().removeFirstSegments(1)); } return path; } /** * see issue 12500: we should check whether the resource is virtual * <p> * TODO as soon as 3.5 is not supported, use resource.isVirtual() call * * @param locationURI * may be null * @return true if the given path is null OR is virtual location */ public static boolean isVirtual(URI locationURI) { return locationURI == null || "virtual".equals(locationURI.getScheme()); } /** * @param linked non null linked resource * @return may return null if the link target is not inside workspace */ public static IResource getRealLocation(IResource linked) { IPath path = getPath(linked); if (path.isEmpty()) { return null; } IResource handle = getHandle(path, linked.getType() == IResource.FILE); if (handle == null || handle.isLinked()) { return null; } return handle; } /** * Converts a {@link java.io.File} to a workspace resource */ public static IResource convert(File file) throws HgException { String canonicalPath; try { canonicalPath = file.getCanonicalPath(); } catch (IOException e) { throw new HgException(e.getLocalizedMessage(), e); } return getHandle(new Path(canonicalPath), !file.isDirectory()); } /** * For a given path, tries to find out first <b>existing</b> parent directory * * @param path * may be null * @return may return null */ public static File getFirstExistingDirectory(File path) { while (path != null && !path.isDirectory()) { path = path.getParentFile(); } return path; } /** * For a given path, tries to find out first <b>existing</b> parent directory * * @param res * may be null * @return may return null */ public static IContainer getFirstExistingDirectory(IResource res) { if (res == null) { return null; } IContainer parent = res instanceof IContainer ? (IContainer) res : res.getParent(); if (parent instanceof IWorkspaceRoot) { return null; } while (parent != null && !parent.exists()) { parent = parent.getParent(); if (parent instanceof IWorkspaceRoot) { return null; } } return parent; } /** * @param resources * non null * @return never null */ public static Map<IProject, List<IResource>> groupByProject(Collection<IResource> resources) { Map<IProject, List<IResource>> result = new HashMap<IProject, List<IResource>>(); for (IResource resource : resources) { IProject root = resource.getProject(); List<IResource> list = result.get(root); if (list == null) { list = new ArrayList<IResource>(); result.put(root, list); } list.add(resource); } return result; } /** * @return never null, a list with all projects contained by given hg root directory */ public static Set<IProject> getProjects(HgRoot root) { Set<IProject> set = new HashSet<IProject>(); IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); for (IProject project : projects) { if (!project.isAccessible()) { continue; } HgRoot proot = MercurialRootCache.getInstance().hasHgRoot(project, true); if (proot == null) { continue; } if (root.equals(proot)) { set.add(project); } } return set; } /** * @param resources * non null * @return never null */ public static Map<HgRoot, List<IResource>> groupByRoot(Collection<? extends IResource> resources) { Map<HgRoot, List<IResource>> result = new HashMap<HgRoot, List<IResource>>(); if (resources != null) { for (IResource resource : resources) { HgRoot root = MercurialRootCache.getInstance().hasHgRoot(resource, true); if (root == null) { continue; } List<IResource> list = result.get(root); if (list == null) { list = new ArrayList<IResource>(); result.put(root, list); } list.add(resource); } } return result; } public static void collectAllResources(IContainer root, Set<IResource> children) { IResource[] members; try { members = root.members(); } catch (CoreException e) { MercurialEclipsePlugin.logError(e); return; } children.add(root); for (IResource res : members) { if (res instanceof IFolder && !res.equals(root)) { collectAllResources((IFolder) res, children); } else { children.add(res); } } } /** * Converts a HgRoot relative path to a project relative IResource. The specified hgRoot * can be higher than, deeper than, or at project level in the directory hierarchy. * @param hgRoot * non null * @param project * non null * @param repoRelPath * path <b>relative</b> to the hg root * @return may return null, if the path is not found in the project */ public static IResource convertRepoRelPath(HgRoot hgRoot, IProject project, String repoRelPath) { // determine absolute path IPath path = hgRoot.toAbsolute(repoRelPath); // determine project relative path int equalSegments = path.matchingFirstSegments(getPath(project)); path = path.removeFirstSegments(equalSegments); return project.findMember(path); } public static Set<IResource> getMembers(IResource r) { return getMembers(r, true); } public static Set<IResource> getMembers(IResource r, boolean withLinks) { HashSet<IResource> set = new HashSet<IResource>(); if (r instanceof IContainer && r.isAccessible()) { IContainer cont = (IContainer) r; try { IResource[] members = cont.members(); if (members != null) { for (IResource member : members) { if (!withLinks && member.isLinked()) { continue; } if (member instanceof IContainer) { set.addAll(getMembers(member)); } else { set.add(member); } } } } catch (CoreException e) { MercurialEclipsePlugin.logError(e); } } set.add(r); return set; } /** * @param o * some object which is or can be adapted to resource * @return given object as resource, may return null */ public static IResource getResource(Object o) { if (o == null) { return null; } if (o instanceof IResource) { return (IResource) o; } if (o instanceof ChangeSet) { ChangeSet changeSet = (ChangeSet) o; Set<IFile> files = changeSet.getFiles(); if (files.size() > 0) { IFile file = files.iterator().next(); if (files.size() == 1) { return file; } return file.getProject(); } return null; } if (o instanceof IAdaptable) { IAdaptable adaptable = (IAdaptable) o; IResource adapter = (IResource) adaptable.getAdapter(IResource.class); if (adapter != null) { return adapter; } adapter = (IResource) adaptable.getAdapter(IFile.class); if (adapter != null) { return adapter; } } return (IResource) Platform.getAdapterManager().getAdapter(o, IResource.class); } public static void touch(final IResource res) { Job job = new Job("Refresh for: " + res.getName()) { @Override protected IStatus run(IProgressMonitor monitor) { // triggers the decoration update try { if (res.isAccessible()) { res.touch(monitor); } } catch (CoreException e) { MercurialEclipsePlugin.logError(e); } return Status.OK_STATUS; } }; job.schedule(); } /** * @param selection may be null * @return never null , may be empty list containing all resources from given selection */ public static List<IResource> getResources(IStructuredSelection selection) { List<IResource> resources = new ArrayList<IResource>(); List<?> list = selection.toList(); for (Object object : list) { if (object instanceof ChangeSet) { Set<IFile> files = ((ChangeSet) object).getFiles(); for (IFile file : files) { if (!resources.contains(file)) { resources.add(file); } } } else if (object instanceof PathFromChangeSet) { PathFromChangeSet pathFromChangeSet = (PathFromChangeSet) object; Set<FileFromChangeSet> files = pathFromChangeSet.getFiles(); for (FileFromChangeSet ffc : files) { IFile file = ffc.getFile(); if (file != null && !resources.contains(file)) { resources.add(file); } } } else { IResource resource = getResource(object); if (resource != null && !resources.contains(resource)) { resources.add(resource); } } } return resources; } /** * This is optimized version of {@link IPath#isPrefixOf(IPath)} (30-50% faster). Main difference is * that we prefer the cheap operations first and check path segments starting from the * end of the first path (with the assumption that paths starts in most cases * with common paths segments => so we postpone redundant comparisons). * @param first non null * @param second non null * @return true if the first path is prefix of the second */ public static boolean isPrefixOf(IPath first, IPath second) { int len = first.segmentCount(); if (len > second.segmentCount()) { return false; } for (int i = len - 1; i >= 0; i--) { if (!first.segment(i).equals(second.segment(i))) { return false; } } return sameDevice(first, second); } private static boolean sameDevice(IPath first, IPath second) { String device = first.getDevice(); if (device == null) { if (second.getDevice() != null) { return false; } } else { if (!device.equalsIgnoreCase(second.getDevice())) { return false; } } return true; } /** * Opens appropriate editor * @param file must be not null * @param activePage can be null * @return */ public static IEditorPart openEditor(IWorkbenchPage activePage, IFile file) { if (activePage == null) { activePage = MercurialEclipsePlugin.getActivePage(); } try { if (file instanceof IHgResource) { return IDE.openEditor(activePage, file.getLocationURI(), EditorsUI.DEFAULT_TEXT_EDITOR_ID, true); } return IDE.openEditor(activePage, file); } catch (PartInitException e) { MercurialEclipsePlugin.logError(e); return null; } } /** * Get the IHgResource for the given resource as it is in the current repository revision. * * @param resource The resource to use * @return The resource as it would be if clean */ public static IHgResource getCleanLocalHgResource(IResource resource) { HgRoot hgRoot = MercurialTeamProvider.getHgRoot(resource); if (hgRoot == null) { return null; } IPath path = hgRoot.getRelativePath(resource); JHgChangeSet cs = null; path = HgStatusClient.getCopySource(hgRoot, path); try { cs = LocalChangesetCache.getInstance().getCurrentChangeSet(hgRoot); } catch (HgException e) { MercurialEclipsePlugin.logError(e); } if (cs != null) { try { return HgLocateClient.getHgResources(hgRoot, path, resource instanceof IStorage, cs, null); } catch (HgException e) { MercurialEclipsePlugin.logError(e); } } return null; } /** * Return a File reference to a copy of the resource in the jar. * <p> * These style files are included in the plugin jar file and need to be copied out of there into * the plugin state area so a path can be given to the hg command. Copied files are set to * deleteOnExit so the don't become stale. * * @param sResource * The name of the the file, eg "/styles/log_renames.tmpl" * @return a File reference to an existing file */ public static File resourceAsFile(String sResource) throws HgException { IPath stateLocation = MercurialEclipsePlugin.getDefault().getStateLocation(); File file = stateLocation.append(sResource).toFile(); if (file.canRead()) { // Already have copies, return the file reference to the style file file.deleteOnExit(); return file; } InputStream istream = ResourceUtils.class.getClassLoader().getResourceAsStream(sResource); file.getParentFile().mkdirs(); try { OutputStream ostream = new BufferedOutputStream(new FileOutputStream(file)); ByteStreams.copy(istream, ostream); ostream.close(); file.deleteOnExit(); return file; } catch (IOException e) { throw new HgException("Failed to copy resource to file: " + sResource, e); } finally { try { if (istream != null) { istream.close(); } } catch (IOException e) { MercurialEclipsePlugin.logError(e); } } } }