package net.sf.eclipsefp.haskell.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; /** * Cross-platform file utilities and container for commonly used file and folder * names. * * @author Thomas ten Cate (original) * @author Scott Michel (bscottm@ieee.org, modifications) */ public class FileUtil { /** Normal Haskell source extension. */ public static final String EXTENSION_HS = "hs"; //$NON-NLS-1$ /** Literate Haskell (embedded TeX) source extension. */ public static final String EXTENSION_LHS = "lhs"; //$NON-NLS-1$ /** Haskell for C bindings source extension. */ public static final String EXTENSION_HSC = "hsc"; //$NON-NLS-1$ /** * "Common Architecture for Building Applications and Libraries" (cabal) * project file source extension. */ public static final String EXTENSION_CABAL = "cabal"; //$NON-NLS-1$ /** Default project source directory */ public static final String DEFAULT_FOLDER_SRC = "src"; /** Default project documentation directory */ //public static final String DEFAULT_FOLDER_DOC = "doc"; /** Haskell for Alex lexers. */ public static final String EXTENSION_ALEX = "x"; //$NON-NLS-1$ /** Haskell for Happy parsers. */ public static final String EXTENSION_HAPPY = "y"; //$NON-NLS-1$ /** Haskell for UU Attribute Grammars. */ public static final String EXTENSION_UUAGC = "ag"; //$NON-NLS-1$ /** * UTF8 encoding */ public static final String UTF8="UTF8"; //$NON-NLS-1$ /** Candidate locations to search for files on a path */ static final ArrayList<File> candidateLocations = new ArrayList<>(32); public static Set<String> haskellExtensions=new HashSet<>(); /** * PATH is case insensitive on windows * @return the current value of the path */ public static String getPath(){ return getPath(System.getenv()); } /** * PATH is case insensitive on windows * @return the proper name of the PATH env variable */ public static String getPathVariable(){ return getPathVariable(System.getenv()); } /** * PATH is case insensitive on windows * @param env the environment map * @return the current value of the path */ public static String getPath(Map<String,String> env){ for (Entry<String, String> e:env.entrySet()){ if ("PATH".equalsIgnoreCase(e.getKey())){ return e.getValue(); } } return ""; } /** * PATH is case insensitive on windows * @param env the environment map * @return the proper name of the PATH env variable */ public static String getPathVariable(Map<String,String> env){ for (String k:env.keySet()){ if ("PATH".equalsIgnoreCase(k)){ return k; } } return "PATH"; } static { // Initialize the candidate file locations list, since this doesn't // change during runtime. // add all directories from the PATH environment variable String path = getPath(); //$NON-NLS-1$ for (String dir : path.split(File.pathSeparator)) { File f=new File(dir); candidateLocations.add(f); // the haskell platform doesn't always put these extra dirs in the path if (f.getParentFile()!=null && dir.contains("Haskell Platform")){ candidateLocations.add(new File(f.getParentFile(),"lib/extralibs/bin")); candidateLocations.add(new File(f.getParentFile(),"mingw/bin")); } } // add common bin directories from the user's home directory ArrayList<String> homes = new ArrayList<>(); final String envHome = System.getenv("HOME"); //$NON-NLS-1$ homes.add(envHome); final String userHome = System.getProperty("user.home"); //$NON-NLS-1$ if (!userHome.equals(envHome)) homes.add(userHome); String[] userBins = new String[] { ".cabal/bin", //$NON-NLS-1$ "usr/bin", //$NON-NLS-1$ "bin", //$NON-NLS-1$ "Library/Haskell/bin", // MacOS? (http://www.blogger.com/comment.g?blogID=37404288&postID=4199565322584497096) //$NON-NLS-1$ "AppData/Roaming/cabal/bin", // Windows 7 //$NON-NLS-1$ "Application Data/cabal/bin" // Windows XP }; for (String home : homes) { for (String userBin : userBins) { candidateLocations.add(new File(home, userBin)); } } haskellExtensions.add(EXTENSION_HS); haskellExtensions.add(EXTENSION_LHS); haskellExtensions.add(EXTENSION_HSC); } private FileUtil() { // do not instantiate } /** * Makes the given base name into a valid executable file name. On Unix, this * returns the base name itself. On Windows, it appends ".exe". */ public static String makeExecutableName(final String baseName) { if (PlatformUtil.runningOnWindows() && !baseName.endsWith("." + PlatformUtil.WINDOWS_EXTENSION_EXE)) { //$NON-NLS-1$ return baseName + "." + PlatformUtil.WINDOWS_EXTENSION_EXE; //$NON-NLS-1$ } return baseName; } /** Makes the given Path into a valid executable name, which is effectively a NOP on Unix, but ensures * that ".exe" is appended on Windows. */ public static IPath makeExecutableName(final IPath exePath) { if (PlatformUtil.runningOnWindows()) { String ext = exePath.getFileExtension(); if (ext == null || !ext.equals(PlatformUtil.WINDOWS_EXTENSION_EXE)) { return exePath.addFileExtension(PlatformUtil.WINDOWS_EXTENSION_EXE); } } return exePath; } /** * Returns true if the given file is executable. On Unix, this checks the file * permission bits. On Windows, it checks whether the file extension is that * of an executable file. */ public static boolean isExecutable(final File file) { if (PlatformUtil.runningOnWindows()) { return isExecutableWindows(file); } // Assume a UNIX flavor. return isExecutableUnix(file); } private static boolean isExecutableUnix(final File file) { // Until Java 7, there is no way to check the executable bit of a file. // Apart from writing a JNI function (hassle), this is the best we can // do... try { Process process = Runtime.getRuntime().exec(new String[] { "test", "-x", file.getAbsolutePath() }); //$NON-NLS-1$ //$NON-NLS-2$ int exitValue = process.waitFor(); if (exitValue != 0) { return false; } } catch (IOException ex) { // pretend it succeeded } catch (InterruptedException ex) { // pretend it succeeded } return true; } private static boolean isExecutableWindows(final File file) { String fileExt = new Path(file.getPath()).getFileExtension(); for (String ext : PlatformUtil.WINDOWS_EXECUTABLE_EXTENSIONS) { if (fileExt.equals(ext)) { return true; } } return false; } public static File findExecutableInPath(final String shortFileName,final File... extraBins) { return findInPath(makeExecutableName(shortFileName),extraBins, new FileFilter() { @Override public boolean accept(final File pathname) { return FileUtil.isExecutable(pathname); } }); } public static File findInPath(final String shortFileName,final File[] extraBins,final FileFilter ff) { // Shallow copy is sufficient in this case ArrayList<File> candidates = new ArrayList<>(); // Add the current working directory, since it might change. String pwd = System.getProperty("user.dir"); //$NON-NLS-1$ candidates.addAll(Arrays.asList(extraBins)); candidates.add(new File(pwd)); candidates.addAll(candidateLocations); for (File candidate : candidates) { if (candidate!=null){ File file = new File(candidate, shortFileName); if (file.exists() && (ff == null || ff.accept(file))) { return file; } } } return null; } /** * Delete all contents including directories */ static public boolean deleteRecursively(final File file) { if (file == null || !file.exists()) { return true; } // If file is a file, delete it if (file.isFile()) { boolean del = file.delete(); return del; } // The file is a directory File[] files = file.listFiles(); for (File f : files) { deleteRecursively(f); } boolean del = file.delete(); return del; } /** * <p> * returns whether the passed resource is a literate Haskell source file, as * recognized by the file extension '.lhs'. * </p> */ public static boolean hasLiterateExtension(final IResource resource) { return has(resource, EXTENSION_LHS); } /** * Predicate that determines whether the resource is a Cabal project file. * * @param resource * The resource * @return true if the resource is a Cabal project file. */ public static boolean hasCabalExtension(final IResource resource) { return has(resource, EXTENSION_CABAL); } /** * <p> * returns whether the passed resource is a Haskell source file, as recognized * by the file extensions '.hs' and '.lhs'. * </p> */ public static boolean hasHaskellExtension(final IResource resource) { return has(resource, EXTENSION_HS) || has(resource, EXTENSION_LHS) || has(resource, EXTENSION_HSC); } public static boolean hasAnySourceExtension(final IResource resource) { return hasHaskellExtension(resource) || hasCabalExtension(resource) || has(resource,EXTENSION_ALEX) || has(resource,EXTENSION_HAPPY) || has(resource,EXTENSION_UUAGC); } private static boolean has(final IResource resource, final String extension) { if (resource != null) { String resExt = resource.getFileExtension(); return resExt != null && resExt.equalsIgnoreCase(extension); } return false; } /** Return the list of candidate locations in which we search for various executables */ public static final ArrayList<File> getCandidateLocations() { return candidateLocations; } /** * read contents of an Eclipse file * @param f the file * @return a string, maybe empty, but not null * @throws Exception */ public static String getContents(IFile f) throws Exception{ String ret=""; if (f.exists()){ ByteArrayOutputStream baos=new ByteArrayOutputStream(); try (InputStream is=f.getContents(true)) { byte[] buf=new byte[4096]; int r=is.read(buf); while (r>-1){ baos.write(buf,0,r); r=is.read(buf); } } ret=new String(baos.toByteArray(),f.getCharset()); } return ret; } /** * read contents of an file * @param f the file * @param charSet the character set * @return a string, maybe empty, but not null * @throws IOException */ public static String getContents(File f,String charSet) throws IOException{ String ret=""; if (f.exists()){ try (ByteArrayOutputStream baos=new ByteArrayOutputStream(); InputStream is=new BufferedInputStream(new FileInputStream(f))) { byte[] buf=new byte[4096]; int r=is.read(buf); while (r>-1){ baos.write(buf,0,r); r=is.read(buf); } ret=new String(baos.toByteArray(),charSet); } } return ret; } public static void writeSharedFile(File tgt,String contents,int tries) throws IOException{ try (Writer w=new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(tgt)),UTF8)) { w.write(contents); } catch (FileNotFoundException e){ if (tries>1 && e.getMessage().toLowerCase(Locale.ENGLISH).contains("another process")){ try { Thread.sleep(200); } catch (InterruptedException ie){ // NOOP } writeSharedFile(tgt,contents,tries-1); return; } throw e; } } }