1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.backends.gwt; 18 19 import java.io.BufferedInputStream; 20 import java.io.BufferedReader; 21 import java.io.File; 22 import java.io.FileFilter; 23 import java.io.FilenameFilter; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.InputStreamReader; 27 import java.io.OutputStream; 28 import java.io.Reader; 29 import java.io.UnsupportedEncodingException; 30 import java.io.Writer; 31 32 import com.badlogic.gdx.Files.FileType; 33 import com.badlogic.gdx.Gdx; 34 import com.badlogic.gdx.backends.gwt.preloader.Preloader; 35 import com.badlogic.gdx.files.FileHandle; 36 import com.badlogic.gdx.utils.GdxRuntimeException; 37 38 public class GwtFileHandle extends FileHandle { 39 public final Preloader preloader; 40 private final String file; 41 private final FileType type; 42 43 public GwtFileHandle (Preloader preloader, String fileName, FileType type) { 44 if (type != FileType.Internal && type != FileType.Classpath) 45 throw new GdxRuntimeException("FileType '" + type + "' Not supported in GWT backend"); 46 this.preloader = preloader; 47 this.file = fixSlashes(fileName); 48 this.type = type; 49 } 50 51 public GwtFileHandle (String path) { 52 this.type = FileType.Internal; 53 this.preloader = ((GwtApplication)Gdx.app).getPreloader(); 54 this.file = fixSlashes(path); 55 } 56 57 public String path () { 58 return file; 59 } 60 61 public String name () { 62 int index = file.lastIndexOf('/'); 63 if (index < 0) return file; 64 return file.substring(index + 1); 65 } 66 67 public String extension () { 68 String name = name(); 69 int dotIndex = name.lastIndexOf('.'); 70 if (dotIndex == -1) return ""; 71 return name.substring(dotIndex + 1); 72 } 73 74 public String nameWithoutExtension () { 75 String name = name(); 76 int dotIndex = name.lastIndexOf('.'); 77 if (dotIndex == -1) return name; 78 return name.substring(0, dotIndex); 79 } 80 81 /** @return the path and filename without the extension, e.g. dir/dir2/file.png -> dir/dir2/file */ 82 public String pathWithoutExtension () { 83 String path = file; 84 int dotIndex = path.lastIndexOf('.'); 85 if (dotIndex == -1) return path; 86 return path.substring(0, dotIndex); 87 } 88 89 public FileType type () { 90 return type; 91 } 92 93 /** Returns a java.io.File that represents this file handle. Note the returned file will only be usable for 94 * {@link FileType#Absolute} and {@link FileType#External} file handles. */ 95 public File file () { 96 throw new GdxRuntimeException("Not supported in GWT backend"); 97 } 98 99 /** Returns a stream for reading this file as bytes. 100 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 101 public InputStream read () { 102 InputStream in = preloader.read(file); 103 if (in == null) throw new GdxRuntimeException(file + " does not exist"); 104 return in; 105 } 106 107 /** Returns a buffered stream for reading this file as bytes. 108 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 109 public BufferedInputStream read (int bufferSize) { 110 return new BufferedInputStream(read(), bufferSize); 111 } 112 113 /** Returns a reader for reading this file as characters. 114 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 115 public Reader reader () { 116 return new InputStreamReader(read()); 117 } 118 119 /** Returns a reader for reading this file as characters. 120 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 121 public Reader reader (String charset) { 122 try { 123 return new InputStreamReader(read(), charset); 124 } catch (UnsupportedEncodingException e) { 125 throw new GdxRuntimeException("Encoding '" + charset + "' not supported", e); 126 } 127 } 128 129 /** Returns a buffered reader for reading this file as characters. 130 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 131 public BufferedReader reader (int bufferSize) { 132 return new BufferedReader(reader(), bufferSize); 133 } 134 135 /** Returns a buffered reader for reading this file as characters. 136 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 137 public BufferedReader reader (int bufferSize, String charset) { 138 return new BufferedReader(reader(charset), bufferSize); 139 } 140 141 /** Reads the entire file into a string using the platform's default charset. 142 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 143 public String readString () { 144 return readString(null); 145 } 146 147 /** Reads the entire file into a string using the specified charset. 148 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 149 public String readString (String charset) { 150 if (preloader.isText(file)) return preloader.texts.get(file); 151 try { 152 return new String(readBytes(), "UTF-8"); 153 } catch (UnsupportedEncodingException e) { 154 return null; 155 } 156 } 157 158 /** Reads the entire file into a byte array. 159 * @throw GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ 160 public byte[] readBytes () { 161 int length = (int)length(); 162 if (length == 0) length = 512; 163 byte[] buffer = new byte[length]; 164 int position = 0; 165 InputStream input = read(); 166 try { 167 while (true) { 168 int count = input.read(buffer, position, buffer.length - position); 169 if (count == -1) break; 170 position += count; 171 if (position == buffer.length) { 172 // Grow buffer. 173 byte[] newBuffer = new byte[buffer.length * 2]; 174 System.arraycopy(buffer, 0, newBuffer, 0, position); 175 buffer = newBuffer; 176 } 177 } 178 } catch (IOException ex) { 179 throw new GdxRuntimeException("Error reading file: " + this, ex); 180 } finally { 181 try { 182 if (input != null) input.close(); 183 } catch (IOException ignored) { 184 } 185 } 186 if (position < buffer.length) { 187 // Shrink buffer. 188 byte[] newBuffer = new byte[position]; 189 System.arraycopy(buffer, 0, newBuffer, 0, position); 190 buffer = newBuffer; 191 } 192 return buffer; 193 } 194 195 /** Reads the entire file into the byte array. The byte array must be big enough to hold the file's data. 196 * @param bytes the array to load the file into 197 * @param offset the offset to start writing bytes 198 * @param size the number of bytes to read, see {@link #length()} 199 * @return the number of read bytes */ 200 public int readBytes (byte[] bytes, int offset, int size) { 201 InputStream input = read(); 202 int position = 0; 203 try { 204 while (true) { 205 int count = input.read(bytes, offset + position, size - position); 206 if (count <= 0) break; 207 position += count; 208 } 209 } catch (IOException ex) { 210 throw new GdxRuntimeException("Error reading file: " + this, ex); 211 } finally { 212 try { 213 if (input != null) input.close(); 214 } catch (IOException ignored) { 215 } 216 } 217 return position - offset; 218 } 219 220 /** Returns a stream for writing to this file. Parent directories will be created if necessary. 221 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 222 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 223 * {@link FileType#Internal} file, or if it could not be written. */ 224 public OutputStream write (boolean append) { 225 throw new GdxRuntimeException("Cannot write to files in GWT backend"); 226 } 227 228 /** Reads the remaining bytes from the specified stream and writes them to this file. The stream is closed. Parent directories 229 * will be created if necessary. 230 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 231 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 232 * {@link FileType#Internal} file, or if it could not be written. */ 233 public void write (InputStream input, boolean append) { 234 throw new GdxRuntimeException("Cannot write to files in GWT backend"); 235 } 236 237 /** Returns a writer for writing to this file using the default charset. Parent directories will be created if necessary. 238 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 239 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 240 * {@link FileType#Internal} file, or if it could not be written. */ 241 public Writer writer (boolean append) { 242 return writer(append, null); 243 } 244 245 /** Returns a writer for writing to this file. Parent directories will be created if necessary. 246 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 247 * @param charset May be null to use the default charset. 248 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 249 * {@link FileType#Internal} file, or if it could not be written. */ 250 public Writer writer (boolean append, String charset) { 251 throw new GdxRuntimeException("Cannot write to files in GWT backend"); 252 } 253 254 /** Writes the specified string to the file using the default charset. Parent directories will be created if necessary. 255 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 256 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 257 * {@link FileType#Internal} file, or if it could not be written. */ 258 public void writeString (String string, boolean append) { 259 writeString(string, append, null); 260 } 261 262 /** Writes the specified string to the file as UTF-8. Parent directories will be created if necessary. 263 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 264 * @param charset May be null to use the default charset. 265 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 266 * {@link FileType#Internal} file, or if it could not be written. */ 267 public void writeString (String string, boolean append, String charset) { 268 throw new GdxRuntimeException("Cannot write to files in GWT backend"); 269 } 270 271 /** Writes the specified bytes to the file. Parent directories will be created if necessary. 272 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 273 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 274 * {@link FileType#Internal} file, or if it could not be written. */ 275 public void writeBytes (byte[] bytes, boolean append) { 276 throw new GdxRuntimeException("Cannot write to files in GWT backend"); 277 } 278 279 /** Writes the specified bytes to the file. Parent directories will be created if necessary. 280 * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. 281 * @throw GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or 282 * {@link FileType#Internal} file, or if it could not be written. */ 283 public void writeBytes (byte[] bytes, int offset, int length, boolean append) { 284 throw new GdxRuntimeException("Cannot write to files in GWT backend"); 285 } 286 287 /** Returns the paths to the children of this directory. Returns an empty list if this file handle represents a file and not a 288 * directory. On the desktop, an {@link FileType#Internal} handle to a directory on the classpath will return a zero length 289 * array. 290 * @throw GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ 291 public FileHandle[] list () { 292 return preloader.list(file); 293 } 294 295 /** Returns the paths to the children of this directory that satisfy the specified filter. Returns an empty list if this file 296 * handle represents a file and not a directory. On the desktop, an {@link FileType#Internal} handle to a directory on the 297 * classpath will return a zero length array. 298 * @throw GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ 299 public FileHandle[] list (FileFilter filter) { 300 return preloader.list(file, filter); 301 } 302 303 /** Returns the paths to the children of this directory that satisfy the specified filter. Returns an empty list if this file 304 * handle represents a file and not a directory. On the desktop, an {@link FileType#Internal} handle to a directory on the 305 * classpath will return a zero length array. 306 * @throw GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ 307 public FileHandle[] list (FilenameFilter filter) { 308 return preloader.list(file, filter); 309 } 310 311 /** Returns the paths to the children of this directory with the specified suffix. Returns an empty list if this file handle 312 * represents a file and not a directory. On the desktop, an {@link FileType#Internal} handle to a directory on the classpath 313 * will return a zero length array. 314 * @throw GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ 315 public FileHandle[] list (String suffix) { 316 return preloader.list(file, suffix); 317 } 318 319 /** Returns true if this file is a directory. Always returns false for classpath files. On Android, an {@link FileType#Internal} 320 * handle to an empty directory will return false. On the desktop, an {@link FileType#Internal} handle to a directory on the 321 * classpath will return false. */ 322 public boolean isDirectory () { 323 return preloader.isDirectory(file); 324 } 325 326 /** Returns a handle to the child with the specified name. 327 * @throw GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} and the child 328 * doesn't exist. */ 329 public FileHandle child (String name) { 330 return new GwtFileHandle(preloader, (file.isEmpty() ? "" : (file + (file.endsWith("/") ? "" : "/"))) + name, 331 FileType.Internal); 332 } 333 334 public FileHandle parent () { 335 int index = file.lastIndexOf("/"); 336 String dir = ""; 337 if (index > 0) dir = file.substring(0, index); 338 return new GwtFileHandle(preloader, dir, type); 339 } 340 341 public FileHandle sibling (String name) { 342 return parent().child(fixSlashes(name)); 343 } 344 345 /** @throw GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. */ 346 public void mkdirs () { 347 throw new GdxRuntimeException("Cannot mkdirs with an internal file: " + file); 348 } 349 350 /** Returns true if the file exists. On Android, a {@link FileType#Classpath} or {@link FileType#Internal} handle to a directory 351 * will always return false. */ 352 public boolean exists () { 353 return preloader.contains(file); 354 } 355 356 /** Deletes this file or empty directory and returns success. Will not delete a directory that has children. 357 * @throw GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. */ 358 public boolean delete () { 359 throw new GdxRuntimeException("Cannot delete an internal file: " + file); 360 } 361 362 /** Deletes this file or directory and all children, recursively. 363 * @throw GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. */ 364 public boolean deleteDirectory () { 365 throw new GdxRuntimeException("Cannot delete an internal file: " + file); 366 } 367 368 /** Copies this file or directory to the specified file or directory. If this handle is a file, then 1) if the destination is a 369 * file, it is overwritten, or 2) if the destination is a directory, this file is copied into it, or 3) if the destination 370 * doesn't exist, {@link #mkdirs()} is called on the destination's parent and this file is copied into it with a new name. If 371 * this handle is a directory, then 1) if the destination is a file, GdxRuntimeException is thrown, or 2) if the destination is 372 * a directory, this directory is copied into it recursively, overwriting existing files, or 3) if the destination doesn't 373 * exist, {@link #mkdirs()} is called on the destination and this directory is copied into it recursively. 374 * @throw GdxRuntimeException if the destination file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file, 375 * or copying failed. */ 376 public void copyTo (FileHandle dest) { 377 throw new GdxRuntimeException("Cannot copy to an internal file: " + dest); 378 } 379 380 /** Moves this file to the specified file, overwriting the file if it already exists. 381 * @throw GdxRuntimeException if the source or destination file handle is a {@link FileType#Classpath} or 382 * {@link FileType#Internal} file. */ 383 public void moveTo (FileHandle dest) { 384 throw new GdxRuntimeException("Cannot move an internal file: " + file); 385 } 386 387 /** Returns the length in bytes of this file, or 0 if this file is a directory, does not exist, or the size cannot otherwise be 388 * determined. */ 389 public long length () { 390 return preloader.length(file); 391 } 392 393 /** Returns the last modified time in milliseconds for this file. Zero is returned if the file doesn't exist. Zero is returned 394 * for {@link FileType#Classpath} files. On Android, zero is returned for {@link FileType#Internal} files. On the desktop, zero 395 * is returned for {@link FileType#Internal} files on the classpath. */ 396 public long lastModified () { 397 return 0; 398 } 399 400 public String toString () { 401 return file; 402 } 403 404 private static String fixSlashes(String path) { 405 path = path.replace('\\', '/'); 406 if (path.endsWith("/")) { 407 path = path.substring(0, path.length() - 1); 408 } 409 return path; 410 } 411 412 } 413