1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 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 }