5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.graalvm.compiler.debug; 24 25 import java.io.IOException; 26 import java.nio.file.InvalidPathException; 27 import java.nio.file.Path; 28 import java.nio.file.Paths; 29 import java.util.HashMap; 30 import java.util.concurrent.atomic.AtomicInteger; 31 import java.util.concurrent.atomic.AtomicLong; 32 33 import org.graalvm.compiler.options.OptionKey; 34 import org.graalvm.compiler.options.OptionValues; 35 36 /** 37 * Miscellaneous methods for modifying and generating file system paths. 38 */ 39 public class PathUtilities { 40 41 private static final AtomicLong globalTimeStamp = new AtomicLong(); 42 /** 43 * This generates a per thread persistent id to aid mapping related dump files with each other. 44 */ 45 private static final ThreadLocal<PerThreadSequence> threadDumpId = new ThreadLocal<>(); 46 private static final AtomicInteger dumpId = new AtomicInteger(); 47 48 static class PerThreadSequence { 49 final int threadID; 50 HashMap<String, Integer> sequences = new HashMap<>(2); 51 52 PerThreadSequence(int threadID) { 53 this.threadID = threadID; 54 } 55 56 String generateID(String extension) { 57 Integer box = sequences.get(extension); 58 if (box == null) { 59 sequences.put(extension, 1); 60 return Integer.toString(threadID); 61 } else { 62 sequences.put(extension, box + 1); 63 return Integer.toString(threadID) + '-' + box; 64 } 65 } 66 } 67 68 private static String getThreadDumpId(String extension) { 69 PerThreadSequence id = threadDumpId.get(); 70 if (id == null) { 71 id = new PerThreadSequence(dumpId.incrementAndGet()); 72 threadDumpId.set(id); 73 } 74 return id.generateID(extension); 75 } 76 77 /** 78 * Prepends a period (i.e., {@code '.'}) to an non-null, non-empty string representation a file 79 * extension if the string does not already start with a period. 80 * 81 * @return {@code ext} unmodified if it is null, empty or already starts with a period other 82 * {@code "." + ext} 83 */ 84 public static String formatExtension(String ext) { 85 if (ext == null || ext.length() == 0) { 86 return ""; 87 } 88 return "." + ext; 89 } 90 91 /** 92 * Gets a time stamp for the current process. This method will always return the same value for 93 * the current VM execution. 94 */ 95 public static long getGlobalTimeStamp() { 96 if (globalTimeStamp.get() == 0) { 97 globalTimeStamp.compareAndSet(0, System.currentTimeMillis()); 98 } 99 return globalTimeStamp.get(); 100 } 101 102 /** 103 * Generates a {@link Path} using the format "%s-%d_%d%s" with the {@code baseNameOption}, a 104 * {@link #getGlobalTimeStamp() global timestamp} , {@link #getThreadDumpId a per thread unique 105 * id} and an optional {@code extension}. 106 * 107 * @return the output file path or null if the flag is null 108 */ 109 public static Path getPath(OptionValues options, OptionKey<String> baseNameOption, String extension) throws IOException { 110 return getPath(options, baseNameOption, extension, true); 111 } 112 113 /** 114 * Generate a {@link Path} using the format "%s-%d_%s" with the {@code baseNameOption}, a 115 * {@link #getGlobalTimeStamp() global timestamp} and an optional {@code extension} . 116 * 117 * @return the output file path or null if the flag is null 118 */ 119 public static Path getPathGlobal(OptionValues options, OptionKey<String> baseNameOption, String extension) throws IOException { 120 return getPath(options, baseNameOption, extension, false); 121 } 122 123 private static Path getPath(OptionValues options, OptionKey<String> baseNameOption, String extension, boolean includeThreadId) throws IOException { 124 if (baseNameOption.getValue(options) == null) { 125 return null; 126 } 127 String ext = formatExtension(extension); 128 final String name = includeThreadId 129 ? String.format("%s-%d_%s%s", baseNameOption.getValue(options), getGlobalTimeStamp(), getThreadDumpId(ext), ext) 130 : String.format("%s-%d%s", baseNameOption.getValue(options), getGlobalTimeStamp(), ext); 131 Path result = Paths.get(name); 132 if (result.isAbsolute()) { 133 return result; 134 } 135 Path dumpDir = DebugOptions.getDumpDirectory(options); 136 return dumpDir.resolve(name).normalize(); 137 } 138 139 /** 140 * Gets a value based on {@code name} that can be passed to {@link Paths#get(String, String...)} 141 * without causing an {@link InvalidPathException}. 142 * 143 * @return {@code name} with all characters invalid for the current file system replaced by 144 * {@code '_'} 145 */ 146 public static String sanitizeFileName(String name) { 147 try { 148 Paths.get(name); 149 return name; 150 } catch (InvalidPathException e) { 151 // fall through 152 } 153 StringBuilder buf = new StringBuilder(name.length()); 154 for (int i = 0; i < name.length(); i++) { 155 char c = name.charAt(i); 156 try { 157 Paths.get(String.valueOf(c)); 158 } catch (InvalidPathException e) { 159 buf.append('_'); 160 } 161 buf.append(c); 162 } 163 return buf.toString(); 164 } 165 } | 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.graalvm.compiler.debug; 24 25 import java.io.File; 26 import java.io.IOException; 27 import java.nio.file.FileAlreadyExistsException; 28 import java.nio.file.Files; 29 import java.nio.file.InvalidPathException; 30 import java.nio.file.Path; 31 import java.nio.file.Paths; 32 import java.util.concurrent.atomic.AtomicLong; 33 34 import org.graalvm.compiler.options.OptionKey; 35 import org.graalvm.compiler.options.OptionValues; 36 37 /** 38 * Miscellaneous methods for modifying and generating file system paths. 39 */ 40 public class PathUtilities { 41 42 private static final AtomicLong globalTimeStamp = new AtomicLong(); 43 44 /** 45 * Gets a time stamp for the current process. This method will always return the same value for 46 * the current VM execution. 47 */ 48 public static long getGlobalTimeStamp() { 49 if (globalTimeStamp.get() == 0) { 50 globalTimeStamp.compareAndSet(0, System.currentTimeMillis()); 51 } 52 return globalTimeStamp.get(); 53 } 54 55 /** 56 * Gets a value based on {@code name} that can be passed to {@link Paths#get(String, String...)} 57 * without causing an {@link InvalidPathException}. 58 * 59 * @return {@code name} with all characters invalid for the current file system replaced by 60 * {@code '_'} 61 */ 62 public static String sanitizeFileName(String name) { 63 try { 64 Path path = Paths.get(name); 65 if (path.getNameCount() == 0) { 66 return name; 67 } 68 } catch (InvalidPathException e) { 69 // fall through 70 } 71 StringBuilder buf = new StringBuilder(name.length()); 72 for (int i = 0; i < name.length(); i++) { 73 char c = name.charAt(i); 74 if (c != File.separatorChar && c != ' ' && !Character.isISOControl(c)) { 75 try { 76 Paths.get(String.valueOf(c)); 77 buf.append(c); 78 continue; 79 } catch (InvalidPathException e) { 80 } 81 } 82 buf.append('_'); 83 } 84 return buf.toString(); 85 } 86 87 /** 88 * A maximum file name length supported by most file systems. There is no platform independent 89 * way to get this in Java. 90 */ 91 private static final int MAX_FILE_NAME_LENGTH = 255; 92 93 private static final String ELLIPSIS = "..."; 94 95 static Path createUnique(OptionValues options, OptionKey<String> baseNameOption, String id, String label, String ext, boolean createDirectory) throws IOException { 96 String uniqueTag = ""; 97 int dumpCounter = 1; 98 String prefix; 99 if (id == null) { 100 prefix = baseNameOption.getValue(options); 101 int slash = prefix.lastIndexOf(File.separatorChar); 102 prefix = prefix.substring(slash + 1); 103 } else { 104 prefix = id; 105 } 106 for (;;) { 107 int fileNameLengthWithoutLabel = uniqueTag.length() + ext.length() + prefix.length() + "[]".length(); 108 int labelLengthLimit = MAX_FILE_NAME_LENGTH - fileNameLengthWithoutLabel; 109 String fileName; 110 if (labelLengthLimit < ELLIPSIS.length()) { 111 // This means `id` is very long 112 String suffix = uniqueTag + ext; 113 int idLengthLimit = Math.min(MAX_FILE_NAME_LENGTH - suffix.length(), prefix.length()); 114 fileName = sanitizeFileName(prefix.substring(0, idLengthLimit) + suffix); 115 } else { 116 if (label == null) { 117 fileName = sanitizeFileName(prefix + uniqueTag + ext); 118 } else { 119 String adjustedLabel = label; 120 if (label.length() > labelLengthLimit) { 121 adjustedLabel = label.substring(0, labelLengthLimit - ELLIPSIS.length()) + ELLIPSIS; 122 } 123 fileName = sanitizeFileName(prefix + '[' + adjustedLabel + ']' + uniqueTag + ext); 124 } 125 } 126 Path dumpDir = DebugOptions.getDumpDirectory(options); 127 Path result = Paths.get(dumpDir.toString(), fileName); 128 try { 129 if (createDirectory) { 130 return Files.createDirectory(result); 131 } else { 132 return Files.createFile(result); 133 } 134 } catch (FileAlreadyExistsException e) { 135 uniqueTag = "_" + dumpCounter++; 136 } 137 } 138 } 139 140 } |