| 1 | package org.jtoolkit.essence.utils; |
| 2 | |
| 3 | import org.apache.commons.logging.Log; |
| 4 | import org.apache.commons.logging.LogFactory; |
| 5 | import org.jetbrains.annotations.NotNull; |
| 6 | import org.jetbrains.annotations.Nullable; |
| 7 | import org.jtoolkit.essence.app.net.DataSocket; |
| 8 | import org.jtoolkit.essence.app.pojo.DatableUtils; |
| 9 | import org.jtoolkit.essence.concurrency.Threads; |
| 10 | import static org.jtoolkit.essence.utils.StringUtils.isSet; |
| 11 | |
| 12 | import java.io.*; |
| 13 | import java.net.MalformedURLException; |
| 14 | import java.net.Socket; |
| 15 | import java.net.URL; |
| 16 | import java.util.*; |
| 17 | import java.util.concurrent.Callable; |
| 18 | import java.util.concurrent.ExecutorService; |
| 19 | import java.util.regex.Pattern; |
| 20 | import static java.util.regex.Pattern.compile; |
| 21 | import java.util.zip.GZIPInputStream; |
| 22 | import java.util.zip.GZIPOutputStream; |
| 23 | |
| 24 | /* |
| 25 | Copyright 2006 Peter Lawrey |
| 26 | |
| 27 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 28 | you may not use this file except in compliance with the License. |
| 29 | You may obtain a copy of the License at |
| 30 | |
| 31 | http://www.apache.org/licenses/LICENSE-2.0 |
| 32 | |
| 33 | Unless required by applicable law or agreed to in writing, software |
| 34 | distributed under the License is distributed on an "AS IS" BASIS, |
| 35 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 36 | See the License for the specific language governing permissions and |
| 37 | limitations under the License. |
| 38 | */ |
| 39 | |
| 40 | /** |
| 41 | * A utility class for low level io and network operations. |
| 42 | * |
| 43 | * @author Peter Lawrey |
| 44 | */ |
| 45 | public class IOUtils { |
| 46 | private static final Log LOG = LogFactory.getLog(IOUtils.class); |
| 47 | private static final Pattern TAB_SEP = compile("\t"); |
| 48 | private static final String UNNAMED = "{Unnamed}"; |
| 49 | public static final String JAVA_IO_TMPDIR = System.getProperty("java.io.tmpdir"); |
| 50 | |
| 51 | private static final ExecutorService CLOSE_SES = Threads.createSingleSES(Threads.CONTROL_THREAD + "closer", |
| 52 | (Thread.MIN_PRIORITY + Thread.NORM_PRIORITY) / 2); |
| 53 | |
| 54 | private static final Set<String> deleteOnExit = new LinkedHashSet<String>(); |
| 55 | |
| 56 | private IOUtils() { |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * This class warms up the IOUtils.class. ONLY required if you checking all thread on test start and finish. |
| 61 | */ |
| 62 | public static void init() { |
| 63 | // start the closer thread. |
| 64 | CLOSE_SES.execute(new Runnable() { |
| 65 | public void run() { |
| 66 | } |
| 67 | }); |
| 68 | } |
| 69 | |
| 70 | public static void close(@Nullable java.io.Closeable closeable) { |
| 71 | if (closeable == null) return; |
| 72 | if (closeable instanceof DataSocket) |
| 73 | close((DataSocket) closeable); |
| 74 | else |
| 75 | try { |
| 76 | DatableUtils.close(closeable); |
| 77 | closeable.close(); |
| 78 | } catch (IOException ignored) { |
| 79 | // ignored |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | private static void close(DataSocket ds) { |
| 84 | if (ds.isClosing()) return; |
| 85 | ds.closing(); |
| 86 | try { |
| 87 | CLOSE_SES.submit(new CloseCallable(ds)); |
| 88 | } catch (Exception e) { |
| 89 | // an InterruptedException is thrown on Java 5 on shutdown, but not Java 6. |
| 90 | //noinspection ConstantConditions |
| 91 | if (!(e instanceof InterruptedException)) |
| 92 | LOG.warn("Unexpected exception ", e); |
| 93 | ds.close(); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | public static void close(@Nullable Socket socket) { |
| 98 | if (socket == null) return; |
| 99 | try { |
| 100 | if (!socket.isClosed() && !socket.isOutputShutdown()) |
| 101 | socket.shutdownOutput(); |
| 102 | } catch (IOException ignored) { |
| 103 | // ignored. |
| 104 | } |
| 105 | try { |
| 106 | if (!socket.isClosed()) |
| 107 | socket.close(); |
| 108 | } catch (IOException ignored) { |
| 109 | // ignored. |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | |
| 114 | public static File createTempDir(String prefix) { |
| 115 | return new File(JAVA_IO_TMPDIR, prefix + '-' + Long.toString(System.nanoTime(), 36)); |
| 116 | } |
| 117 | |
| 118 | public static void deleteDir(File file) throws IOException { |
| 119 | if (!file.exists()) return; |
| 120 | if (file.isDirectory()) |
| 121 | for (File file2 : file.listFiles()) |
| 122 | deleteDir(file2); |
| 123 | |
| 124 | if (file.delete()) return; |
| 125 | if (!file.exists()) return; |
| 126 | if (!file.canWrite()) |
| 127 | throw new IOException("Unable to delete read-only " + file); |
| 128 | File parentFile = file.getParentFile(); |
| 129 | if (parentFile != null && parentFile.exists() && !parentFile.canWrite()) |
| 130 | throw new IOException("Unable to delete file " + file.getName() + " in read-only directory " + parentFile); |
| 131 | throw new IOException("Unable to delete file for an unknown reason " + file); |
| 132 | } |
| 133 | |
| 134 | public static void deleteIfTmp(File file) { |
| 135 | String path = file.getAbsolutePath(); |
| 136 | if (path.startsWith(JAVA_IO_TMPDIR)) |
| 137 | if (deleteOnExit.add(path)) |
| 138 | file.deleteOnExit(); |
| 139 | } |
| 140 | |
| 141 | public static String findDir(String paths) throws FileNotFoundException { |
| 142 | String[] pathArr = paths.split("[:;,]"); |
| 143 | for (String path : pathArr) |
| 144 | if (new File(path).isDirectory()) |
| 145 | return path; |
| 146 | throw new FileNotFoundException("Unable to find a directory from " + paths); |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * @noinspection DuplicateThrows |
| 151 | */ |
| 152 | @NotNull public static InputStream getInputStream(@NotNull String filename) throws FileNotFoundException, IOException { |
| 153 | if (filename.length() == 0) throw new IllegalArgumentException("Filename cannot be empty"); |
| 154 | if (filename.charAt(0) == '=') return new ByteArrayInputStream(filename.substring(1).getBytes()); |
| 155 | |
| 156 | ClassLoader loader = IOUtils.class.getClassLoader(); |
| 157 | InputStream is = loader.getResourceAsStream(filename); |
| 158 | if (is == null) { |
| 159 | is = loader.getResourceAsStream('/' + filename); |
| 160 | if (is == null) { |
| 161 | try { |
| 162 | URL url = new URL(filename); |
| 163 | is = url.openStream(); |
| 164 | } catch (MalformedURLException ignored) { |
| 165 | is = new FileInputStream(filename); |
| 166 | } |
| 167 | } |
| 168 | } |
| 169 | if (isGzipped(new File(filename))) |
| 170 | is = new GZIPInputStream(is); |
| 171 | is = new BufferedInputStream(is); |
| 172 | return is; |
| 173 | } |
| 174 | |
| 175 | // helper for sub-clasess; |
| 176 | @NotNull public static File getNewFile(@NotNull String filename) { |
| 177 | File parentFile = new File(filename).getParentFile(); |
| 178 | if (parentFile != null && !parentFile.exists()) { |
| 179 | if (!parentFile.mkdirs()) |
| 180 | throw new IllegalStateException("Unable to save " + filename + " cannot create parent directory."); |
| 181 | } |
| 182 | File file; |
| 183 | do { |
| 184 | String fileId = Long.toString(System.nanoTime(), Character.MAX_RADIX); |
| 185 | file = new File(filename + '.' + fileId + ".new"); |
| 186 | } while (file.exists()); |
| 187 | file.deleteOnExit(); |
| 188 | return file; |
| 189 | } |
| 190 | |
| 191 | public static OutputStream getOutputStream(@NotNull File file) throws IOException { |
| 192 | OutputStream out = new FileOutputStream(file); |
| 193 | if (isGzipped(file)) |
| 194 | out = new GZIPOutputStream(out); |
| 195 | return new BufferedOutputStream(out); |
| 196 | } |
| 197 | |
| 198 | private static boolean isGzipped(File file) { |
| 199 | String name = file.getName(); |
| 200 | if (name.endsWith(".gz") || name.contains(".gz.")) return true; |
| 201 | |
| 202 | String parent = file.getParent(); |
| 203 | return parent != null && parent.endsWith(".gz"); |
| 204 | } |
| 205 | |
| 206 | private static int length(String[] row) { |
| 207 | int last = -1; |
| 208 | for (int i = 0; i < row.length; i++) |
| 209 | if (isSet(row[i])) |
| 210 | last = i; |
| 211 | return last + 1; |
| 212 | } |
| 213 | |
| 214 | @NotNull public static List<String[]> readTab(@NotNull InputStream is) throws IOException { |
| 215 | List<String[]> rows = new ArrayList<String[]>(); |
| 216 | try { |
| 217 | String line; |
| 218 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); |
| 219 | while ((line = br.readLine()) != null) { |
| 220 | // drop comments. |
| 221 | if (isSet(line) && line.charAt(0) == '#') continue; |
| 222 | String[] parts = TAB_SEP.split(line); |
| 223 | for (int i = 0; i < parts.length; i++) { |
| 224 | String part = parts[i]; |
| 225 | int len = part.length(); |
| 226 | if (len == 0) continue; |
| 227 | if (part.charAt(0) == '"' && part.charAt(len - 1) == '"') { |
| 228 | parts[i] = part.substring(1, len - 1).replaceAll("\"\"", "\""); |
| 229 | } else { |
| 230 | parts[i] = part.trim(); |
| 231 | } |
| 232 | } |
| 233 | rows.add(parts); |
| 234 | } |
| 235 | } finally { |
| 236 | close(is); |
| 237 | } |
| 238 | return rows; |
| 239 | } |
| 240 | |
| 241 | @NotNull public static Map<String, List<String[]>> readTabs(@NotNull InputStream is) throws IOException { |
| 242 | List<String[]> rows = readTab(is); |
| 243 | Map<String, List<String[]>> ret = new LinkedHashMap<String, List<String[]>>(); |
| 244 | int start = ~0; |
| 245 | String name = null; |
| 246 | for (int i = 0; i < rows.size(); i++) { |
| 247 | String[] row = rows.get(i); |
| 248 | int len = length(row); |
| 249 | if (len < 1) { |
| 250 | if (name != null && start >= 0) { |
| 251 | ret.put(name, new ArrayList<String[]>(rows.subList(start, i))); |
| 252 | name = null; |
| 253 | start = ~0; |
| 254 | } |
| 255 | continue; |
| 256 | } |
| 257 | if (len == 1 && name == null || row.length == 1) { |
| 258 | if (name != null) |
| 259 | throw new IllegalArgumentException("Found a 1 element row " + row[0] + " after a 1 element row " + name); |
| 260 | name = row[0]; |
| 261 | continue; |
| 262 | } |
| 263 | if (len > 1) { |
| 264 | if (start >= 0) continue; |
| 265 | if (name == null) |
| 266 | name = UNNAMED; // unnamed. |
| 267 | start = i; |
| 268 | } |
| 269 | } |
| 270 | if (name != null && start > 0) |
| 271 | ret.put(name, new ArrayList<String[]>(rows.subList(start, rows.size()))); |
| 272 | return ret; |
| 273 | } |
| 274 | |
| 275 | public static boolean rename(File file, File file2) { |
| 276 | String threadName = Thread.currentThread().getName(); |
| 277 | if (file.exists() && !file.delete()) |
| 278 | LOG.error(threadName + ": Unable to remove orignial " + file); |
| 279 | if (file.exists()) |
| 280 | LOG.error(threadName + ": Failed to remove orignial " + file); |
| 281 | if (!file2.renameTo(file)) { |
| 282 | LOG.error(threadName + ": Unable to rename " + file2 + ' ' + file2.exists() + " to " + file + ' ' + file.exists()); |
| 283 | return false; |
| 284 | } |
| 285 | return true; |
| 286 | } |
| 287 | |
| 288 | private static class CloseCallable implements Callable<Void> { |
| 289 | private final DataSocket ds; |
| 290 | |
| 291 | CloseCallable(DataSocket ds) { |
| 292 | this.ds = ds; |
| 293 | } |
| 294 | |
| 295 | @Nullable public Void call() { |
| 296 | if (!ds.isClosed()) |
| 297 | ds.close(); |
| 298 | return null; |
| 299 | } |
| 300 | } |
| 301 | } |