package water.persist; import java.io.*; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import water.*; import water.util.Log; /** Abstract class describing various persistence targets. * <p><ul> * <li>{@link #store(Value v)} - Store a Value, using storage space.</li> * <li>{@link #load(Value v)} - Load a previously stored Value.</li> * <li>{@link #delete(Value v)} - Free storage from a previously store Value.</li> * </ul> * This class is used to implement both user-mode swapping, and the initial * load of files - typically raw text for parsing. */ public abstract class Persist { /** Store a Value into persistent storage, consuming some storage space. */ abstract public void store(Value v) throws IOException; /** Load a previously stored Value */ abstract public byte[] load(Value v) throws IOException; /** Reclaim space from a previously stored Value */ abstract public void delete(Value v); /** Usable storage space, or -1 for unknown */ public long getUsableSpace() { return /*UNKNOWN*/-1; } /** Total storage space, or -1 for unknown */ public long getTotalSpace() { return /*UNKNOWN*/-1; } /** Transform given uri into file vector holding file name. */ abstract public Key uriToKey(URI uri) throws IOException; /** Delete persistent storage on startup and shutdown */ abstract public void cleanUp(); /** * Calculate typeahead matches for src * * @param filter Source string to match for typeahead * @param limit Max number of entries to return * @return List of matches */ abstract public List<String> calcTypeaheadMatches(String filter, int limit); abstract public void importFiles(String path, String pattern, ArrayList<String> files, ArrayList<String> keys, ArrayList<String> fails, ArrayList<String> dels); // The filename can be either byte encoded if it starts with % followed by // a number, or is a normal key name with special characters encoded in // special ways. // It is questionable whether we need this because the only keys we have on // ice are likely to be Chunks static String getIceName(Value v) { return getIceName(v._key); } static String getIceName(Key k) { return getIceDirectory(k) + File.separator + key2Str(k); } static String getIceDirectory(Key key) { if( !key.isChunkKey() ) return "not_a_Chunk"; // Reverse Chunk key generation return key2Str(key.getVecKey()); } // Verify bijection of key/file-name mappings. protected static String key2Str(Key k) { String s = key2Str_impl(k); Key x; assert (x = str2Key_impl(s)).equals(k) : "bijection fail " + k + " <-> " + s + " <-> " + x; return s; } // Verify bijection of key/file-name mappings. static Key str2Key(String s) { Key k = str2Key_impl(s); assert key2Str_impl(k).equals(s) : "bijection fail " + s + " <-> " + k; return k; } // Convert a Key to a suitable filename string private static String key2Str_impl(Key k) { // check if we are system key StringBuilder sb = new StringBuilder(k._kb.length / 2 + 4); int i = 0; if( k._kb[0] < 32 ) { // System keys: hexalate all the leading non-ascii bytes sb.append('%'); int j = k._kb.length - 1; // Backwards scan for 1st non-ascii while( j >= 0 && k._kb[j] >= 32 && k._kb[j] < 128 ) j--; for( ; i <= j; i++ ) { byte b = k._kb[i]; int nib0 = ((b >>> 4) & 15) + '0'; if( nib0 > '9' ) nib0 += 'A' - 10 - '0'; int nib1 = ((b >>> 0) & 15) + '0'; if( nib1 > '9' ) nib1 += 'A' - 10 - '0'; sb.append((char) nib0).append((char) nib1); } sb.append('%'); } // Escape the special bytes from 'i' to the end return escapeBytes(k._kb, i, sb).toString(); } private static StringBuilder escapeBytes(byte[] bytes, int i, StringBuilder sb) { for( ; i < bytes.length; i++ ) { char b = (char)bytes[i], c=0; switch( b ) { case '%': c='%'; break; case '.': c='d'; break; case '/': c='s'; break; case ':': c='c'; break; case '"': c='q'; break; case '>': c='g'; break; case '<': c='l'; break; case '\\':c='b'; break; case '\0':c='z'; break; } if( c!=0 ) sb.append('%').append(c); else sb.append(b); } return sb; } // Convert a filename string to a Key private static Key str2Key_impl(String s) { String key = s; byte[] kb = new byte[(key.length() - 1) / 2]; int i = 0, j = 0; if( (key.length() > 2) && (key.charAt(0) == '%') && (key.charAt(1) >= '0') && (key.charAt(1) <= '9') ) { // Dehexalate until '%' for( i = 1; i < key.length(); i += 2 ) { if( key.charAt(i) == '%' ) break; char b0 = (char) (key.charAt(i ) - '0'); if( b0 > 9 ) b0 += '0' + 10 - 'A'; char b1 = (char) (key.charAt(i + 1) - '0'); if( b1 > 9 ) b1 += '0' + 10 - 'A'; kb[j++] = (byte) ((b0 << 4) | b1); // De-hexelated byte } i++; // Skip the trailing '%' } // a normal key - ASCII with special characters encoded after % sign for( ; i < key.length(); ++i ) { byte b = (byte) key.charAt(i); if( b == '%' ) { switch( key.charAt(++i) ) { case '%': b = '%'; break; case 'c': b = ':'; break; case 'd': b = '.'; break; case 'g': b = '>'; break; case 'l': b = '<'; break; case 'q': b = '"'; break; case 's': b = '/'; break; case 'b': b = '\\'; break; case 'z': b = '\0'; break; default: Log.warn("Invalid format of filename " + s + " at index " + i); } } if( j >= kb.length ) kb = Arrays.copyOf(kb, Math.max(2, j * 2)); kb[j++] = b; } // now in kb we have the key name return Key.make(Arrays.copyOf(kb, j)); } // ------------------------------- // Node Persistent Storage helpers // ------------------------------- public static class PersistEntry { public PersistEntry(String name, long size, long timestamp) { _name = name; _size = size; _timestamp_millis = timestamp; } public final String _name; public final long _size; public final long _timestamp_millis; } public String getHomeDirectory() { throw new RuntimeException("Not implemented"); } public PersistEntry[] list(String path) { throw new RuntimeException("Not implemented"); } public boolean exists(String path) { throw new RuntimeException("Not implemented"); } public boolean isDirectory(String path) { throw new RuntimeException("Not implemented"); } public long length(String path) { throw new RuntimeException("Not implemented"); } public InputStream open(String path) { throw new RuntimeException("Not implemented"); } public boolean mkdirs(String path) { throw new RuntimeException("Not implemented"); } public boolean rename(String fromPath, String toPath) { throw new RuntimeException("Not implemented"); } /** * Create a new file and return OutputStream for writing. * * The method creates all directories which does not exists on the * referenced path. * * @param path persist layer specific path * @param overwrite overwrite destination file * @return output stream * * @throws IOException in case of underlying FS error */ public OutputStream create(String path, boolean overwrite) { throw new RuntimeException("Not implemented"); } public boolean delete(String path) { throw new RuntimeException("Not implemented"); } /** Returns true if the persist layer understands given path. */ public boolean canHandle(String path) { throw new RuntimeException("Not implemented"); } }