View Javadoc

1   /**
2    *  MicroEmulator
3    *  Copyright (C) 2006-2007 Bartek Teodorczyk <barteo@barteo.net>
4    *  Copyright (C) 2006-2007 Vlad Skarzhevskyy
5    *
6    *  It is licensed under the following two licenses as alternatives:
7    *    1. GNU Lesser General Public License (the "LGPL") version 2.1 or any newer version
8    *    2. Apache License (the "AL") Version 2.0
9    *
10   *  You may not use this file except in compliance with at least one of
11   *  the above two licenses.
12   *
13   *  You may obtain a copy of the LGPL at
14   *      http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
15   *
16   *  You may obtain a copy of the AL at
17   *      http://www.apache.org/licenses/LICENSE-2.0
18   *
19   *  Unless required by applicable law or agreed to in writing, software
20   *  distributed under the License is distributed on an "AS IS" BASIS,
21   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22   *  See the LGPL or the AL for the specific language governing permissions and
23   *  limitations.
24   *
25   *  @version $Id: FileSystemFileConnection.java 1874 2008-12-17 11:46:29Z barteo $
26   */
27  package org.microemu.cldc.file;
28  
29  import java.io.DataInputStream;
30  import java.io.DataOutputStream;
31  import java.io.File;
32  import java.io.FileInputStream;
33  import java.io.FileOutputStream;
34  import java.io.FilenameFilter;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.OutputStream;
38  import java.io.RandomAccessFile;
39  import java.lang.reflect.Method;
40  import java.security.AccessControlContext;
41  import java.security.AccessController;
42  import java.security.PrivilegedAction;
43  import java.security.PrivilegedExceptionAction;
44  import java.util.Enumeration;
45  import java.util.Vector;
46  
47  import javax.microedition.io.file.ConnectionClosedException;
48  import javax.microedition.io.file.FileConnection;
49  
50  public class FileSystemFileConnection implements FileConnection {
51  
52  	private String fsRootConfig;
53  
54  	private File fsRoot;
55  
56  	private String host;
57  
58  	private String fullPath;
59  
60  	private File file;
61  
62  	private boolean isRoot;
63  
64  	private boolean isDirectory;
65  
66  	private Throwable locationClosedFrom = null;
67  
68  	private FileSystemConnectorImpl notifyClosed;
69  
70  	private InputStream opendInputStream;
71  
72  	private OutputStream opendOutputStream;
73  
74  	private final static char DIR_SEP = '/';
75  
76  	private final static String DIR_SEP_STR = "/";
77  
78  	/* The context to be used when acessing filesystem */
79  	private AccessControlContext acc;
80  
81  	private static boolean java15 = false;
82  
83  	FileSystemFileConnection(String fsRootConfig, String name, FileSystemConnectorImpl notifyClosed) throws IOException {
84  		// <host>/<path>
85  		int hostEnd = name.indexOf(DIR_SEP);
86  		if (hostEnd == -1) {
87  			throw new IOException("Invalid path " + name);
88  		}
89  		this.fsRootConfig = fsRootConfig;
90  		this.notifyClosed = notifyClosed;
91  
92  		host = name.substring(0, hostEnd);
93  		fullPath = name.substring(hostEnd + 1);
94  		if (fullPath.length() == 0) {
95  			throw new IOException("Invalid path " + name);
96  		}
97  		int rootEnd = fullPath.indexOf(DIR_SEP);
98  		isRoot = ((rootEnd == -1) || (rootEnd == fullPath.length() - 1));
99  		if (fullPath.charAt(fullPath.length() - 1) == DIR_SEP) {
100 			fullPath = fullPath.substring(0, fullPath.length() - 1);
101 		}
102 		acc = AccessController.getContext();
103 		AccessController.doPrivileged(new PrivilegedAction() {
104 			public Object run() {
105 				fsRoot = getRoot(FileSystemFileConnection.this.fsRootConfig);
106 				file = new File(fsRoot, fullPath);
107 				isDirectory = file.isDirectory();
108 				return null;
109 			}
110 		}, acc);
111 	}
112 
113 	private Object doPrivilegedIO(PrivilegedExceptionAction action) throws IOException {
114 		return FileSystemConnectorImpl.doPrivilegedIO(action, acc);
115 	}
116 
117 	private abstract class PrivilegedBooleanAction implements PrivilegedAction {
118 		public Object run() {
119 			return new Boolean(getBoolean());
120 		}
121 
122 		abstract boolean getBoolean();
123 	}
124 
125 	private boolean doPrivilegedBoolean(PrivilegedBooleanAction action) {
126 		return ((Boolean) AccessController.doPrivileged(action)).booleanValue();
127 	}
128 
129 	public static File getRoot(String fsRootConfig) {
130 		try {
131 			if (fsRootConfig == null) {
132 				File fsRoot = new File(System.getProperty("user.home") + "/.microemulator/filesystem");
133 				if (!fsRoot.exists()) {
134 					if (!fsRoot.mkdirs()) {
135 						throw new RuntimeException("Can't create filesystem root " + fsRoot.getAbsolutePath());
136 					}
137 					// Create real roots C and E
138 					File rootC = new File(fsRoot, "c");
139 					if (!rootC.exists()) {
140 						rootC.mkdirs();
141 					}
142 					File rootE = new File(fsRoot, "e");
143 					if (!rootE.exists()) {
144 						rootE.mkdirs();
145 					}
146 				}
147 				return fsRoot;
148 			} else {
149 				File fsRoot = new File(fsRootConfig);
150 				if (!fsRoot.isDirectory()) {
151 					throw new RuntimeException("Can't find filesystem root " + fsRoot.getAbsolutePath());
152 				}
153 				return fsRoot;
154 			}
155 		} catch (SecurityException e) {
156 			System.out.println("Cannot access user.home " + e);
157 			return null;
158 		}
159 	}
160 
161 	static Enumeration listRoots(String fsRootConfig, String fsSingleConfig) {
162 		File[] files;
163 		if (fsSingleConfig != null) {
164 			files = new File[1];
165 			files[0] = getRoot(fsRootConfig + fsSingleConfig);
166 		} else {
167 			files = getRoot(fsRootConfig).listFiles();
168 			if (files == null) { // null if security restricted
169 				return (new Vector()).elements();
170 			}
171 		}
172 		Vector list = new Vector();
173 		for (int i = 0; i < files.length; i++) {
174 			File file = files[i];
175 			if (file.isHidden()) {
176 				continue;
177 			}
178 			if (file.isDirectory()) {
179 				list.add(file.getName() + DIR_SEP);
180 			}
181 		}
182 		return list.elements();
183 	}
184 
185 	public long availableSize() {
186 		throwClosed();
187 		if (fsRoot == null) {
188 			return -1;
189 		}
190 		return getFileValueJava6("getFreeSpace");
191 	}
192 
193 	public long totalSize() {
194 		throwClosed();
195 		if (fsRoot == null) {
196 			return -1;
197 		}
198 		return getFileValueJava6("getTotalSpace");
199 	}
200 
201 	public boolean canRead() {
202 		throwClosed();
203 		return doPrivilegedBoolean(new PrivilegedBooleanAction() {
204 			public boolean getBoolean() {
205 				return file.canRead();
206 			}
207 		});
208 	}
209 
210 	public boolean canWrite() {
211 		throwClosed();
212 		return doPrivilegedBoolean(new PrivilegedBooleanAction() {
213 			public boolean getBoolean() {
214 				return file.canWrite();
215 			}
216 		});
217 	}
218 
219 	public void create() throws IOException {
220 		throwClosed();
221 		doPrivilegedIO(new PrivilegedExceptionAction() {
222 			public Object run() throws IOException {
223 				if (!file.createNewFile()) {
224 					throw new IOException("File already exists  " + file.getAbsolutePath());
225 				}
226 				return null;
227 			}
228 		});
229 	}
230 
231 	public void delete() throws IOException {
232 		throwClosed();
233 		doPrivilegedIO(new PrivilegedExceptionAction() {
234 			public Object run() throws IOException {
235 				if (!file.delete()) {
236 					throw new IOException("Unable to delete " + file.getAbsolutePath());
237 				}
238 				return null;
239 			}
240 		});
241 	}
242 
243 	public long directorySize(final boolean includeSubDirs) throws IOException {
244 		throwClosed();
245 		return ((Long) doPrivilegedIO(new PrivilegedExceptionAction() {
246 			public Object run() throws IOException {
247 				if (!file.isDirectory()) {
248 					throw new IOException("Not a directory " + file.getAbsolutePath());
249 				}
250 				return new Long(directorySize(file, includeSubDirs));
251 			}
252 		})).longValue();
253 	}
254 
255 	private static long directorySize(File dir, boolean includeSubDirs) throws IOException {
256 		long size = 0;
257 
258 		File[] files = dir.listFiles();
259 		if (files == null) { // null if security restricted
260 			return 0L;
261 		}
262 		for (int i = 0; i < files.length; i++) {
263 			File child = files[i];
264 
265 			if (includeSubDirs && child.isDirectory()) {
266 				size += directorySize(child, true);
267 			} else {
268 				size += child.length();
269 			}
270 		}
271 
272 		return size;
273 	}
274 
275 	public boolean exists() {
276 		throwClosed();
277 		return doPrivilegedBoolean(new PrivilegedBooleanAction() {
278 			public boolean getBoolean() {
279 				return file.exists();
280 			}
281 		});
282 	}
283 
284 	public long fileSize() throws IOException {
285 		throwClosed();
286 		return ((Long) doPrivilegedIO(new PrivilegedExceptionAction() {
287 			public Object run() throws IOException {
288 				return new Long(file.length());
289 			}
290 		})).longValue();
291 	}
292 
293 	public String getName() {
294 		// TODO test on real device. Not declared
295 		throwClosed();
296 
297 		if (isRoot) {
298 			return "";
299 		}
300 
301 		if (this.isDirectory) {
302 			return this.file.getName() + DIR_SEP;
303 		} else {
304 			return this.file.getName();
305 		}
306 	}
307 
308 	public String getPath() {
309 		// TODO test on real device. Not declared
310 		throwClosed();
311 
312 		// returns Parent directory
313 		// /<root>/<directory>/
314 		if (isRoot) {
315 			return DIR_SEP + fullPath + DIR_SEP;
316 		}
317 
318 		int pathEnd = fullPath.lastIndexOf(DIR_SEP);
319 		if (pathEnd == -1) {
320 			return DIR_SEP_STR;
321 		}
322 		return DIR_SEP + fullPath.substring(0, pathEnd + 1);
323 	}
324 
325 	public String getURL() {
326 		// TODO test on real device. Not declared
327 		throwClosed();
328 
329 		// file://<host>/<root>/<directory>/<filename.extension>
330 		// or
331 		// file://<host>/<root>/<directory>/<directoryname>/
332 		return Connection.PROTOCOL + this.host + DIR_SEP + fullPath + ((this.isDirectory) ? DIR_SEP_STR : "");
333 	}
334 
335 	public boolean isDirectory() {
336 		throwClosed();
337 		return this.isDirectory;
338 	}
339 
340 	public boolean isHidden() {
341 		throwClosed();
342 		return doPrivilegedBoolean(new PrivilegedBooleanAction() {
343 			public boolean getBoolean() {
344 				return file.isHidden();
345 			}
346 		});
347 	}
348 
349 	public long lastModified() {
350 		throwClosed();
351 		return ((Long) AccessController.doPrivileged(new PrivilegedAction() {
352 			public Object run() {
353 				return new Long(file.lastModified());
354 			}
355 		}, acc)).longValue();
356 	}
357 
358 	public void mkdir() throws IOException {
359 		throwClosed();
360 		doPrivilegedIO(new PrivilegedExceptionAction() {
361 			public Object run() throws IOException {
362 				if (!file.mkdir()) {
363 					throw new IOException("Can't create directory " + file.getAbsolutePath());
364 				}
365 				return null;
366 			}
367 		});
368 	}
369 
370 	public Enumeration list() throws IOException {
371 		return this.list(null, false);
372 	}
373 
374 	public Enumeration list(final String filter, final boolean includeHidden) throws IOException {
375 		throwClosed();
376 		return (Enumeration) doPrivilegedIO(new PrivilegedExceptionAction() {
377 			public Object run() throws IOException {
378 				return listPrivileged(filter, includeHidden);
379 			}
380 		});
381 	}
382 
383 	private Enumeration listPrivileged(String filter, boolean includeHidden) throws IOException {
384 		if (!this.file.isDirectory()) {
385 			throw new IOException("Not a directory " + this.file.getAbsolutePath());
386 		}
387 		// TODO
388 		FilenameFilter filenameFilter = null;
389 
390 		File[] files = this.file.listFiles(filenameFilter);
391 		if (files == null) { // null if security restricted
392 			return (new Vector()).elements();
393 		}
394 		Vector list = new Vector();
395 		for (int i = 0; i < files.length; i++) {
396 			File child = files[i];
397 			if ((!includeHidden) && (child.isHidden())) {
398 				continue;
399 			}
400 			if (child.isDirectory()) {
401 				list.add(child.getName() + DIR_SEP);
402 			} else {
403 				list.add(child.getName());
404 			}
405 		}
406 		return list.elements();
407 	}
408 
409 	public boolean isOpen() {
410 		return (this.file != null);
411 	}
412 
413 	private void throwOpenDirectory() throws IOException {
414 		if (this.isDirectory) {
415 			throw new IOException("Unable to open Stream on directory");
416 		}
417 	}
418 
419 	public InputStream openInputStream() throws IOException {
420 		throwClosed();
421 		throwOpenDirectory();
422 
423 		if (this.opendInputStream != null) {
424 			throw new IOException("InputStream already opened");
425 		}
426 		/**
427 		 * Trying to open more than one InputStream or more than one
428 		 * OutputStream from a StreamConnection causes an IOException.
429 		 */
430 		this.opendInputStream = (InputStream) doPrivilegedIO(new PrivilegedExceptionAction() {
431 			public Object run() throws IOException {
432 				return new FileInputStream(file) {
433 					public void close() throws IOException {
434 						FileSystemFileConnection.this.opendInputStream = null;
435 						super.close();
436 					}
437 				};
438 			}
439 		});
440 		return this.opendInputStream;
441 	}
442 
443 	public DataInputStream openDataInputStream() throws IOException {
444 		return new DataInputStream(openInputStream());
445 	}
446 
447 	public OutputStream openOutputStream() throws IOException {
448 		return openOutputStream(false);
449 	}
450 
451 	private OutputStream openOutputStream(final boolean append) throws IOException {
452 		throwClosed();
453 		throwOpenDirectory();
454 
455 		if (this.opendOutputStream != null) {
456 			throw new IOException("OutputStream already opened");
457 		}
458 		/**
459 		 * Trying to open more than one InputStream or more than one
460 		 * OutputStream from a StreamConnection causes an IOException.
461 		 */
462 		this.opendOutputStream = (OutputStream) doPrivilegedIO(new PrivilegedExceptionAction() {
463 			public Object run() throws IOException {
464 				return new FileOutputStream(file, append) {
465 					public void close() throws IOException {
466 						FileSystemFileConnection.this.opendOutputStream = null;
467 						super.close();
468 					}
469 				};
470 			}
471 		});
472 		return this.opendOutputStream;
473 	}
474 
475 	public DataOutputStream openDataOutputStream() throws IOException {
476 		return new DataOutputStream(openOutputStream());
477 	}
478 
479 	public OutputStream openOutputStream(long byteOffset) throws IOException {
480 		throwClosed();
481 		throwOpenDirectory();
482 		if (this.opendOutputStream != null) {
483 			throw new IOException("OutputStream already opened");
484 		}
485 		truncate(byteOffset);
486 		return openOutputStream(true);
487 	}
488 
489 	public void rename(final String newName) throws IOException {
490 		throwClosed();
491 		if (newName.indexOf(DIR_SEP) != -1) {
492 			throw new IllegalArgumentException("Name contains path specification " + newName);
493 		}
494 		doPrivilegedIO(new PrivilegedExceptionAction() {
495 			public Object run() throws IOException {
496 				File newFile = new File(file.getParentFile(), newName);
497 				if (!file.renameTo(newFile)) {
498 					throw new IOException("Unable to rename " + file.getAbsolutePath() + " to "
499 							+ newFile.getAbsolutePath());
500 				}
501 				return null;
502 			}
503 		});
504 		this.fullPath = this.getPath() + newName;
505 	}
506 
507 	public void setFileConnection(String s) throws IOException {
508 		throwClosed();
509 		// TODO Auto-generated method stub
510 	}
511 
512 	public void setHidden(boolean hidden) throws IOException {
513 		throwClosed();
514 	}
515 
516 	private void fileSetJava16(String mehtodName, final Boolean param) throws IOException {
517 		if (java15) {
518 			throw new IOException("Not supported on Java version < 6");
519 		}
520 		// Use Java6 function in reflection.
521 		try {
522 			final Method setWritable = file.getClass().getMethod(mehtodName, new Class[] { boolean.class });
523 			doPrivilegedIO(new PrivilegedExceptionAction() {
524 				public Object run() throws IOException {
525 					try {
526 						setWritable.invoke(file, new Object[] { param });
527 					} catch (Exception e) {
528 						throw new IOException(e.getCause().getMessage());
529 					}
530 					file.setReadOnly();
531 					return null;
532 				}
533 			});
534 		} catch (NoSuchMethodException e) {
535 			java15 = true;
536 			throw new IOException("Not supported on Java version < 6");
537 		}
538 	}
539 
540 	private long getFileValueJava6(String mehtodName) throws SecurityException {
541 		if (java15) {
542 			throw new SecurityException("Not supported on Java version < 6");
543 		}
544 		// Use Java6 function in reflection.
545 		try {
546 			final Method getter = file.getClass().getMethod(mehtodName, new Class[] {});
547 			Long rc = (Long) doPrivilegedIO(new PrivilegedExceptionAction() {
548 				public Object run() throws IOException {
549 					try {
550 						return getter.invoke(file, new Object[] {});
551 					} catch (Exception e) {
552 						throw new IOException(e.getCause().getMessage());
553 					}
554 				}
555 			});
556 			return rc.longValue();
557 		} catch (IOException e) {
558 			throw new SecurityException(e.getMessage());
559 		} catch (NoSuchMethodException e) {
560 			java15 = true;
561 			throw new SecurityException("Not supported on Java version < 6");
562 		}
563 	}
564 
565 	public void setReadable(boolean readable) throws IOException {
566 		throwClosed();
567 		fileSetJava16("setReadable", new Boolean(readable));
568 	}
569 
570 	public void setWritable(boolean writable) throws IOException {
571 		throwClosed();
572 		if (!writable) {
573 			doPrivilegedIO(new PrivilegedExceptionAction() {
574 				public Object run() throws IOException {
575 					file.setReadOnly();
576 					return null;
577 				}
578 			});
579 		} else {
580 			fileSetJava16("setWritable", new Boolean(writable));
581 		}
582 	}
583 
584 	public void truncate(final long byteOffset) throws IOException {
585 		throwClosed();
586 		doPrivilegedIO(new PrivilegedExceptionAction() {
587 			public Object run() throws IOException {
588 				RandomAccessFile raf = new RandomAccessFile(file, "rw");
589 				try {
590 					raf.setLength(byteOffset);
591 				} finally {
592 					raf.close();
593 				}
594 				return null;
595 			}
596 		});
597 	}
598 
599 	public long usedSize() {
600 		try {
601 			return fileSize();
602 		} catch (IOException e) {
603 			return -1;
604 		}
605 	}
606 
607 	public void close() throws IOException {
608 		if (this.file != null) {
609 			if (this.notifyClosed != null) {
610 				this.notifyClosed.notifyClosed(this);
611 			}
612 			locationClosedFrom = new Throwable();
613 			locationClosedFrom.fillInStackTrace();
614 			this.file = null;
615 		}
616 	}
617 
618 	private void throwClosed() throws ConnectionClosedException {
619 		if (this.file == null) {
620 			if (locationClosedFrom != null) {
621 				locationClosedFrom.printStackTrace();
622 			}
623 			throw new ConnectionClosedException("Connection already closed");
624 		}
625 	}
626 }