001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.vfs;
031:
032: import com.caucho.util.Alarm;
033: import com.caucho.util.ByteBuffer;
034: import com.caucho.util.L10N;
035:
036: import java.io.FileNotFoundException;
037: import java.io.IOException;
038: import java.util.ArrayList;
039: import java.util.Map;
040:
041: public class MemoryPath extends FilesystemPath {
042: private static L10N L = new L10N(MemoryPath.class);
043:
044: private Node _rootNode;
045:
046: protected MemoryPath(FilesystemPath root, String userPath,
047: Map<String, Object> attributes, String path) {
048: super (root, userPath, path);
049:
050: if (root instanceof MemoryPath)
051: _rootNode = ((MemoryPath) root)._rootNode;
052: else
053: _root = this ;
054: }
055:
056: public MemoryPath() {
057: this (null, "/", null, "/");
058:
059: _root = this ;
060: _rootNode = new Node("", null, Node.DIR);
061: }
062:
063: public Path fsWalk(String userPath, Map<String, Object> attributes,
064: String path) {
065: return new MemoryPath(_root, userPath, attributes, path);
066: }
067:
068: public String getScheme() {
069: return "memory";
070: }
071:
072: public String getURL() {
073: return getScheme() + ":" + getFullPath();
074: }
075:
076: @Override
077: public boolean isPathCacheable() {
078: return false;
079: }
080:
081: public boolean exists() {
082: synchronized (_rootNode) {
083: return lookupAll() != null;
084: }
085: }
086:
087: private Node lookupAll() {
088: String fullPath = getFullPath();
089:
090: int head = 0;
091: Node node = _rootNode;
092:
093: while (node != null && head < fullPath.length()) {
094: int tail = fullPath.indexOf('/', head);
095:
096: if (tail == -1) {
097: if (head < fullPath.length())
098: return node.lookup(fullPath.substring(head));
099: else
100: return node;
101: }
102:
103: if (head != tail)
104: node = node.lookup(fullPath.substring(head, tail));
105:
106: head = tail + 1;
107: }
108:
109: return node;
110: }
111:
112: private Node lookupAllButTail() {
113: String fullPath = getFullPath();
114:
115: int head = 0;
116: Node node = this ._rootNode;
117:
118: while (node != null && head < fullPath.length()) {
119: int tail = fullPath.indexOf('/', head);
120:
121: if (tail == -1 || tail == fullPath.length() - 1)
122: return node;
123:
124: if (head != tail)
125: node = node.lookup(fullPath.substring(head, tail));
126:
127: head = tail + 1;
128: }
129:
130: return node;
131: }
132:
133: public boolean isDirectory() {
134: synchronized (_rootNode) {
135: Node node = lookupAll();
136:
137: return node != null && node.type == node.DIR;
138: }
139: }
140:
141: public boolean isFile() {
142: synchronized (_rootNode) {
143: Node node = lookupAll();
144:
145: return node != null && node.type == node.FILE;
146: }
147: }
148:
149: public boolean isObject() {
150: synchronized (_rootNode) {
151: Node node = lookupAll();
152:
153: return node != null && node.type == node.OBJECT;
154: }
155: }
156:
157: public boolean setExecutable(boolean isExecutable) {
158: synchronized (_rootNode) {
159: Node node = lookupAll();
160:
161: if (node != null
162: && (node.type == node.FILE || node.type == node.DIR)) {
163: node.isExecutable = isExecutable;
164: return true;
165: } else
166: return false;
167: }
168: }
169:
170: public boolean isExecutable() {
171: synchronized (_rootNode) {
172: Node node = lookupAll();
173:
174: if (node != null
175: && (node.type == node.FILE || node.type == node.DIR))
176: return node.isExecutable;
177: else
178: return false;
179: }
180: }
181:
182: public long getLength() {
183: synchronized (_rootNode) {
184: Node node = lookupAll();
185:
186: if (node != null && node.type == node.FILE)
187: return ((ByteBuffer) node.data).length();
188: else
189: return 0;
190: }
191: }
192:
193: public long getLastModified() {
194: synchronized (_rootNode) {
195: Node node = lookupAll();
196:
197: return node == null ? 0 : node.lastModified;
198: }
199: }
200:
201: public boolean canRead() {
202: synchronized (_rootNode) {
203: Node node = lookupAll();
204:
205: return node != null;
206: }
207: }
208:
209: public boolean canWrite() {
210: synchronized (_rootNode) {
211: Node node = lookupAll();
212:
213: return node != null;
214: }
215: }
216:
217: public String[] list() {
218: synchronized (_rootNode) {
219: Node node = lookupAll();
220:
221: if (node == null || node.data != null)
222: return new String[0];
223:
224: ArrayList<String> a = new ArrayList<String>();
225: for (Node child = node.firstChild; child != null; child = child.next) {
226: a.add(child.name);
227: }
228:
229: return (String[]) a.toArray(new String[a.size()]);
230: }
231: }
232:
233: // XXX: could have iterator
234:
235: private boolean mkdir(boolean parent) {
236: synchronized (_rootNode) {
237: String fullPath = getFullPath();
238:
239: int head = 0;
240: Node node = this ._rootNode;
241:
242: while (node != null && head < fullPath.length()) {
243: int tail = fullPath.indexOf('/', head);
244: String name;
245:
246: if (tail == head) {
247: head = tail + 1;
248: continue;
249: }
250: if (tail == -1) {
251: name = fullPath.substring(head);
252: if (node.lookup(name) != null)
253: return false;
254: node.createDir(name);
255: return true;
256: }
257:
258: name = fullPath.substring(head, tail);
259: Node next = node.lookup(name);
260:
261: if (next == null && parent)
262: next = node.createDir(name);
263:
264: if (next == null || next.type != next.DIR)
265: return false;
266:
267: node = next;
268: head = tail + 1;
269: }
270:
271: return false;
272: }
273: }
274:
275: public boolean mkdir() {
276: return mkdir(false);
277: }
278:
279: public boolean mkdirs() {
280: return mkdir(true);
281: }
282:
283: public boolean remove() {
284: synchronized (_rootNode) {
285: Node node = lookupAllButTail();
286: String tail = getTail();
287:
288: if (node == null)
289: return false;
290:
291: Node child = node.lookup(tail);
292: if (child == null || child.firstChild != null)
293: return false;
294:
295: node.remove(tail);
296:
297: return true;
298: }
299: }
300:
301: public boolean renameTo(Path path) {
302: synchronized (_rootNode) {
303: if (!(path instanceof MemoryPath))
304: return false;
305:
306: MemoryPath file = (MemoryPath) path;
307: if (_rootNode != file._rootNode)
308: return false;
309:
310: Node oldParent = lookupAllButTail();
311: if (oldParent == null)
312: return false;
313:
314: Node child = oldParent.lookup(getTail());
315: if (child == null)
316: return false;
317:
318: Node newParent = file.lookupAllButTail();
319: if (newParent == null || newParent.type != Node.DIR)
320: return false;
321:
322: if (newParent.lookup(file.getTail()) != null)
323: return false;
324:
325: oldParent.remove(getTail());
326: child.name = file.getTail();
327: newParent.create(child);
328:
329: return true;
330: }
331: }
332:
333: public StreamImpl openReadImpl() throws IOException {
334: synchronized (_rootNode) {
335: Node node = lookupAll();
336:
337: if (node == null)
338: throw new FileNotFoundException(getPath());
339: else if (node.type != node.FILE)
340: throw new IOException("is directory: " + getPath());
341:
342: return new MemoryStream(node, (ByteBuffer) node.data, false);
343: }
344: }
345:
346: public StreamImpl openWriteImpl() throws IOException {
347: return openWriteImpl(false);
348: }
349:
350: public StreamImpl openAppendImpl() throws IOException {
351: return openWriteImpl(true);
352: }
353:
354: private StreamImpl openWriteImpl(boolean append) throws IOException {
355: synchronized (_rootNode) {
356: Node node = lookupAllButTail();
357: String tail = getTail();
358:
359: if (node == null || node.type != Node.DIR)
360: throw new IOException(L.l("can't create file {0}",
361: getFullPath()));
362:
363: Node child = node.lookup(tail);
364: if (child == null)
365: child = node.createFile(tail, new ByteBuffer(256));
366: else if (!append) {
367: node.remove(tail);
368: child = node.createFile(tail, new ByteBuffer(256));
369: } else if (child.type != child.FILE)
370: throw new IOException(L.l("can't create file {0}",
371: getFullPath()));
372: return new MemoryStream(child, (ByteBuffer) child.data,
373: true);
374: }
375: }
376:
377: public Object getValue() throws IOException {
378: synchronized (_rootNode) {
379: Node node = lookupAll();
380:
381: if (node == null || node.type != node.OBJECT)
382: throw new IOException("no such object: "
383: + getFullPath().toString());
384:
385: return node.data;
386: }
387: }
388:
389: synchronized public void setValue(Object object) throws IOException {
390: synchronized (_rootNode) {
391: Node node = lookupAllButTail();
392: String tail = getTail();
393:
394: if (node == null || node.type != Node.DIR)
395: throw new IOException(L.l("can't set object {0}",
396: getFullPath()));
397:
398: Node child = node.lookup(tail);
399: if (child == null)
400: child = node.createObject(tail, object);
401: else if (child.type == child.OBJECT)
402: child.data = object;
403: else
404: throw new IOException(L.l("can't set object {0}",
405: getFullPath()));
406: }
407: }
408:
409: public Path copyCache() {
410: return null;
411: }
412:
413: public MemoryPath copyDeep() {
414: MemoryPath path = new MemoryPath();
415:
416: path._rootNode = _rootNode.copy();
417:
418: return path;
419: }
420:
421: public boolean equals(Object o) {
422: if (o == null || !getClass().equals(o.getClass()))
423: return false;
424:
425: MemoryPath mp = (MemoryPath) o;
426:
427: return getURL().equals(mp.getURL())
428: && _rootNode == mp._rootNode;
429: }
430:
431: private static class Node {
432: static final int DIR = 0;
433: static final int FILE = DIR + 1;
434: static final int OBJECT = FILE + 1;
435:
436: String name;
437: Node next;
438: Node firstChild;
439: long lastModified;
440: int type;
441: Object data;
442: boolean isExecutable;
443:
444: Node(String name, Object data, int type) {
445: if (name == null)
446: throw new NullPointerException();
447: this .name = name;
448: this .data = data;
449: this .type = type;
450: this .lastModified = Alarm.getCurrentTime();
451: }
452:
453: Node lookup(String name) {
454: for (Node node = firstChild; node != null; node = node.next) {
455: if (node.name.equals(name)) {
456: return node;
457: }
458: }
459:
460: return null;
461: }
462:
463: private Node create(String name, Object data, int type) {
464: for (Node node = firstChild; node != null; node = node.next) {
465: if (node.name.equals(name))
466: return null;
467: }
468:
469: Node newNode = new Node(name, data, type);
470: newNode.next = firstChild;
471: firstChild = newNode;
472: lastModified = Alarm.getCurrentTime();
473:
474: return newNode;
475: }
476:
477: Node createDir(String name) {
478: return create(name, null, DIR);
479: }
480:
481: Node createFile(String name, ByteBuffer data) {
482: return create(name, data, FILE);
483: }
484:
485: Node createObject(String name, Object data) {
486: return create(name, data, OBJECT);
487: }
488:
489: Node create(Node newNode) {
490: newNode.next = firstChild;
491: firstChild = newNode;
492:
493: return newNode;
494: }
495:
496: boolean remove(String name) {
497: Node last = null;
498: for (Node node = firstChild; node != null; node = node.next) {
499: if (node.name.equals(name)) {
500: if (node.firstChild != null)
501: return false;
502:
503: if (last != null)
504: last.next = node.next;
505: else
506: firstChild = node.next;
507:
508: return true;
509: }
510:
511: last = node;
512: }
513:
514: return false;
515: }
516:
517: Node copy() {
518: Node newNode = new Node(name, data, type);
519:
520: if (type == DIR) {
521: for (Node child = firstChild; child != null; child = child.next) {
522: Node newChild = child.copy();
523:
524: newChild.next = newNode.firstChild;
525: newNode.firstChild = newChild;
526: }
527: }
528:
529: return newNode;
530: }
531: };
532:
533: public class MemoryStream extends StreamImpl {
534: Node _node;
535: ByteBuffer _bb;
536: int _offset;
537: boolean _write;
538:
539: MemoryStream(Node node, ByteBuffer bb, boolean write) {
540: setPath(MemoryPath.this );
541:
542: _node = node;
543: if (write)
544: node.lastModified = Alarm.getCurrentTime();
545: _write = write;
546:
547: _bb = bb;
548: }
549:
550: public int getAvailable() {
551: return _bb.length() - _offset;
552: }
553:
554: public boolean canRead() {
555: return true;
556: }
557:
558: public int read(byte[] buf, int bufOffset, int length)
559: throws IOException {
560: synchronized (_bb) {
561: int sublen = _bb.length() - _offset;
562: if (length < sublen)
563: sublen = length;
564:
565: if (sublen <= 0)
566: return -1;
567:
568: System.arraycopy(_bb.getBuffer(), _offset, buf,
569: bufOffset, sublen);
570: _offset += sublen;
571:
572: return sublen;
573: }
574: }
575:
576: public int getPosition() {
577: return _offset;
578: }
579:
580: public void seekStart(long pos) {
581: _offset = (int) pos;
582: if (_offset < 0)
583: _offset = 0;
584: if (_offset > _bb.length())
585: _offset = _bb.length();
586: }
587:
588: public boolean canWrite() {
589: return true;
590: }
591:
592: /**
593: * Writes a buffer to the underlying stream.
594: *
595: * @param buffer the byte array to write.
596: * @param offset the offset into the byte array.
597: * @param length the number of bytes to write.
598: * @param isEnd true when the write is flushing a close.
599: */
600: public void write(byte[] buf, int offset, int length,
601: boolean isEnd) throws IOException {
602: synchronized (_bb) {
603: _bb.add(buf, offset, length);
604: }
605:
606: _node.lastModified = Alarm.getCurrentTime();
607: }
608: }
609: }
|