/******************************************************************************* * Copyright (c) 2005, 2015 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 * Martin Oberhuber (Wind River) - [44107] Add symbolic links to ResourceAttributes API * James Blackburn (Broadcom Corp.) - ongoing development * Sergey Prigogin (Google) - ongoing development *******************************************************************************/ package org.eclipse.core.internal.utils; import java.io.*; import java.net.URI; import org.eclipse.core.filesystem.*; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.internal.resources.Workspace; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.osgi.service.environment.Constants; import org.eclipse.osgi.util.NLS; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.prefs.Preferences; /** * Static utility methods for manipulating Files and URIs. */ public class FileUtil { static final boolean MACOSX = Constants.OS_MACOSX.equals(getOS()); /** * Converts a ResourceAttributes object into an IFileInfo object. * @param attributes The resource attributes * @return The file info */ public static IFileInfo attributesToFileInfo(ResourceAttributes attributes) { IFileInfo fileInfo = EFS.createFileInfo(); fileInfo.setAttribute(EFS.ATTRIBUTE_READ_ONLY, attributes.isReadOnly()); fileInfo.setAttribute(EFS.ATTRIBUTE_EXECUTABLE, attributes.isExecutable()); fileInfo.setAttribute(EFS.ATTRIBUTE_ARCHIVE, attributes.isArchive()); fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, attributes.isHidden()); fileInfo.setAttribute(EFS.ATTRIBUTE_SYMLINK, attributes.isSymbolicLink()); fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_READ, attributes.isSet(EFS.ATTRIBUTE_GROUP_READ)); fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, attributes.isSet(EFS.ATTRIBUTE_GROUP_WRITE)); fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_GROUP_EXECUTE)); fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_READ, attributes.isSet(EFS.ATTRIBUTE_OTHER_READ)); fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, attributes.isSet(EFS.ATTRIBUTE_OTHER_WRITE)); fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, attributes.isSet(EFS.ATTRIBUTE_OTHER_EXECUTE)); return fileInfo; } /** * Converts an IPath into its canonical form for the local file system. */ public static IPath canonicalPath(IPath path) { if (path == null) return null; try { final String pathString = path.toOSString(); final String canonicalPath = new java.io.File(pathString).getCanonicalPath(); //only create a new path if necessary if (canonicalPath.equals(pathString)) return path; return new Path(canonicalPath); } catch (IOException e) { return path; } } /** * For a path on a case-insensitive file system returns the path with the actual * case as it exists in the file system. If only a prefix of the path exists on * the file system, the case of remaining part of the returned path is the same * as in the original path. For a case-sensitive file system returns the original * path. * <p> * This method is similar to java.nio.file.Path.toRealPath(LinkOption.NOFOLLOW_LINKS) * in Java 1.7. */ public static IPath realPath(IPath path) { if (path == null) return null; IFileSystem fileSystem = EFS.getLocalFileSystem(); if (fileSystem.isCaseSensitive()) return path; IPath realPath = path.isAbsolute() ? Path.ROOT : Path.EMPTY; String device = path.getDevice(); if (device != null) { realPath = realPath.setDevice(device.toUpperCase()); } IFileStore fileStore = null; for (int i = 0; i < path.segmentCount(); i++) { final String segment = path.segment(i); if (i == 0 && path.isUNC()) { realPath = realPath.append(segment.toUpperCase()); realPath = realPath.makeUNC(true); } else { if (MACOSX) { // IFileInfo.getName() may not return the real name of the file on Mac OS X. // Obtain the real name of the file from a listing of its parent directory. String[] names = realPath.toFile().list(new FilenameFilter() { @Override public boolean accept(File dir, String n) { return n.equalsIgnoreCase(segment); } }); String realName; if (names == null || names.length == 0) { // The remainder of the path doesn't exist on the file system - copy from // the original path. realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); break; } else if (names.length == 1) { realName = names[0]; } else { // More than one file matches the file name. Maybe the file system was // misreported to be case insensitive. Preserve the original name. realName = segment; } realPath = realPath.append(realName); } else { if (fileStore == null) fileStore = fileSystem.getStore(realPath); fileStore = fileStore.getChild(segment); IFileInfo info = fileStore.fetchInfo(); if (!info.exists()) { // The remainder of the path doesn't exist on the file system - copy from // the original path. realPath = realPath.append(path.removeFirstSegments(realPath.segmentCount())); break; } realPath = realPath.append(info.getName()); } } } if (path.hasTrailingSeparator()) { realPath = realPath.addTrailingSeparator(); } // Return the original path if it's the same as the real one. return realPath.equals(path) ? path : realPath; } /** * Returns the current OS. Equivalent to Platform.getOS(), but tolerant of the platform runtime * not being present. */ private static String getOS() { return System.getProperty("osgi.os", ""); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Converts a URI into its canonical form. */ public static URI canonicalURI(URI uri) { if (uri == null) return null; if (EFS.SCHEME_FILE.equals(uri.getScheme())) { //only create a new URI if it is different final IPath inputPath = URIUtil.toPath(uri); final IPath canonicalPath = canonicalPath(inputPath); if (inputPath == canonicalPath) return uri; return URIUtil.toURI(canonicalPath); } return uri; } /** * Converts a URI by replacing the file system path in the URI with the path * with the actual case as it exists in the file system. * * @see #realPath(IPath) */ public static URI realURI(URI uri) { if (uri == null) return null; if (EFS.SCHEME_FILE.equals(uri.getScheme())) { // Only create a new URI if it is different. final IPath inputPath = URIUtil.toPath(uri); final IPath realPath = realPath(inputPath); if (inputPath == realPath) return uri; return URIUtil.toURI(realPath); } return uri; } /** * Returns true if the given file system locations overlap. If "bothDirections" is true, * this means they are the same, or one is a proper prefix of the other. If "bothDirections" * is false, this method only returns true if the locations are the same, or the first location * is a prefix of the second. Returns false if the locations do not overlap * Does the right thing with respect to case insensitive platforms. */ private static boolean computeOverlap(IPath location1, IPath location2, boolean bothDirections) { IPath one = location1; IPath two = location2; // If we are on a case-insensitive file system then convert to all lower case. if (!Workspace.caseSensitive) { one = new Path(location1.toOSString().toLowerCase()); two = new Path(location2.toOSString().toLowerCase()); } return one.isPrefixOf(two) || (bothDirections && two.isPrefixOf(one)); } /** * Returns true if the given file system locations overlap. If "bothDirections" is true, * this means they are the same, or one is a proper prefix of the other. If "bothDirections" * is false, this method only returns true if the locations are the same, or the first location * is a prefix of the second. Returns false if the locations do not overlap */ private static boolean computeOverlap(URI location1, URI location2, boolean bothDirections) { if (location1.equals(location2)) return true; String scheme1 = location1.getScheme(); String scheme2 = location2.getScheme(); if (scheme1 == null ? scheme2 != null : !scheme1.equals(scheme2)) return false; if (EFS.SCHEME_FILE.equals(scheme1) && EFS.SCHEME_FILE.equals(scheme2)) return computeOverlap(URIUtil.toPath(location1), URIUtil.toPath(location2), bothDirections); IFileSystem system = null; try { system = EFS.getFileSystem(scheme1); } catch (CoreException e) { //handled below } if (system == null) { //we are stuck with string comparison String string1 = location1.toString(); String string2 = location2.toString(); return string1.startsWith(string2) || (bothDirections && string2.startsWith(string1)); } IFileStore store1 = system.getStore(location1); IFileStore store2 = system.getStore(location2); return store1.equals(store2) || store1.isParentOf(store2) || (bothDirections && store2.isParentOf(store1)); } /** * Converts an IFileInfo object into a ResourceAttributes object. * @param fileInfo The file info * @return The resource attributes */ public static ResourceAttributes fileInfoToAttributes(IFileInfo fileInfo) { ResourceAttributes attributes = new ResourceAttributes(); attributes.setReadOnly(fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)); attributes.setArchive(fileInfo.getAttribute(EFS.ATTRIBUTE_ARCHIVE)); attributes.setExecutable(fileInfo.getAttribute(EFS.ATTRIBUTE_EXECUTABLE)); attributes.setHidden(fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN)); attributes.setSymbolicLink(fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK)); attributes.set(EFS.ATTRIBUTE_GROUP_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_READ)); attributes.set(EFS.ATTRIBUTE_GROUP_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_WRITE)); attributes.set(EFS.ATTRIBUTE_GROUP_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE)); attributes.set(EFS.ATTRIBUTE_OTHER_READ, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_READ)); attributes.set(EFS.ATTRIBUTE_OTHER_WRITE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_WRITE)); attributes.set(EFS.ATTRIBUTE_OTHER_EXECUTE, fileInfo.getAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE)); return attributes; } private static String getLineSeparatorFromPreferences(Preferences node) { try { // be careful looking up for our node so not to create any nodes as side effect if (node.nodeExists(Platform.PI_RUNTIME)) return node.node(Platform.PI_RUNTIME).get(Platform.PREF_LINE_SEPARATOR, null); } catch (BackingStoreException e) { // ignore } return null; } /** * Returns line separator appropriate for the given file. The returned value * will be the first available value from the list below: * <ol> * <li> Line separator currently used in that file. * <li> Line separator defined in project preferences. * <li> Line separator defined in instance preferences. * <li> Line separator defined in default preferences. * <li> Operating system default line separator. * </ol> * @param file the file for which line separator should be returned * @return line separator for the given file */ public static String getLineSeparator(IFile file) { if (file.exists()) { InputStream input = null; try { input = file.getContents(); int c = input.read(); while (c != -1 && c != '\r' && c != '\n') c = input.read(); if (c == '\n') return "\n"; //$NON-NLS-1$ if (c == '\r') { if (input.read() == '\n') return "\r\n"; //$NON-NLS-1$ return "\r"; //$NON-NLS-1$ } } catch (CoreException e) { // ignore } catch (IOException e) { // ignore } finally { safeClose(input); } } Preferences rootNode = Platform.getPreferencesService().getRootNode(); String value = null; // if the file does not exist or has no content yet, try with project preferences value = getLineSeparatorFromPreferences(rootNode.node(ProjectScope.SCOPE).node(file.getProject().getName())); if (value != null) return value; // try with instance preferences value = getLineSeparatorFromPreferences(rootNode.node(InstanceScope.SCOPE)); if (value != null) return value; // try with default preferences value = getLineSeparatorFromPreferences(rootNode.node(DefaultScope.SCOPE)); if (value != null) return value; // if there is no preference set, fall back to OS default value return System.getProperty("line.separator"); //$NON-NLS-1$ } /** * Returns true if the given file system locations overlap, and false otherwise. * Overlap means the locations are the same, or one is a proper prefix of the other. */ public static boolean isOverlapping(URI location1, URI location2) { return computeOverlap(location1, location2, true); } /** * Returns true if location1 is the same as, or a proper prefix of, location2. * Returns false otherwise. */ public static boolean isPrefixOf(IPath location1, IPath location2) { return computeOverlap(location1, location2, false); } /** * Returns true if location1 is the same as, or a proper prefix of, location2. * Returns false otherwise. */ public static boolean isPrefixOf(URI location1, URI location2) { return computeOverlap(location1, location2, false); } /** * Closes a stream and ignores any resulting exception. This is useful * when doing stream cleanup in a finally block where secondary exceptions * are not worth logging. * *<p> * <strong>WARNING:</strong> * If the API contract requires notifying clients of I/O problems, then you <strong>must</strong> * explicitly close() output streams outside of safeClose(). * Some OutputStreams will defer an IOException from write() to close(). So * while the writes may 'succeed', ignoring the IOExcpetion will result in silent * data loss. * </p> * <p> * This method should only be used as a fail-safe to ensure resources are not * leaked. * </p> * See also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=332543 */ public static void safeClose(Closeable stream) { try { if (stream != null) stream.close(); } catch (IOException e) { //ignore } } /** * Converts a URI to an IPath. Returns null if the URI cannot be represented * as an IPath. * <p> * Note this method differs from URIUtil in its handling of relative URIs * as being relative to path variables. */ public static IPath toPath(URI uri) { if (uri == null) return null; final String scheme = uri.getScheme(); // null scheme represents path variable if (scheme == null || EFS.SCHEME_FILE.equals(scheme)) return new Path(uri.getSchemeSpecificPart()); return null; } public static final void transferStreams(InputStream source, OutputStream destination, String path, IProgressMonitor monitor) throws CoreException { SubMonitor subMonitor = SubMonitor.convert(monitor); try { byte[] buffer = new byte[8192]; while (true) { int bytesRead = -1; try { bytesRead = source.read(buffer); } catch (IOException e) { String msg = NLS.bind(Messages.localstore_failedReadDuringWrite, path); throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, new Path(path), msg, e); } try { if (bytesRead == -1) { // Bug 332543 - ensure we don't ignore failures on close() destination.close(); break; } destination.write(buffer, 0, bytesRead); } catch (IOException e) { String msg = NLS.bind(Messages.localstore_couldNotWrite, path); throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, new Path(path), msg, e); } subMonitor.split(1); } } finally { safeClose(source); safeClose(destination); } } /** * Not intended for instantiation. */ private FileUtil() { super(); } }