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.loader.DynamicClassLoader;
033: import com.caucho.make.DependencyList;
034: import com.caucho.server.util.CauchoSystem;
035:
036: import java.io.IOException;
037: import java.util.ArrayList;
038: import java.util.Map;
039:
040: /**
041: * A merging of several Paths used like a CLASSPATH. When the MergePath
042: * is opened for read, the first path in the list which contains the file will
043: * be the opened file. When the MergePath is opened for write, the first path
044: * in the list is used for the write.
045: *
046: * <p>In the following example, "first" has priority over "second".
047: * If test.xml exists in both "first" and "second", the open will
048: * return "first/test.xml".
049: *
050: * <code><pre>
051: * MergePage merge = new MergePath();
052: * merge.addMergePath(Vfs.lookup("first");
053: * merge.addMergePath(Vfs.lookup("second");
054: *
055: * Path path = merge.lookup("test.xml");
056: * ReadStream is = path.openRead();
057: * </pre></code>
058: *
059: * <p>MergePath corresponds to the "merge:" Vfs schema
060: * <code><pre>
061: Path path = Vfs.lookup("merge:(../custom-foo;foo)");
062: * </pre></code>
063: *
064: * @since Resin 1.2
065: * @since Resin 3.0.10 merge: schema
066: */
067: public class MergePath extends FilesystemPath {
068: private ArrayList<Path> _pathList;
069:
070: private Path _bestPath;
071:
072: /**
073: * Creates a new merge path.
074: */
075: public MergePath() {
076: super (null, "/", "/");
077:
078: _root = this ;
079: _pathList = new ArrayList<Path>();
080: }
081:
082: /**
083: * @param path canonical path
084: */
085: private MergePath(MergePath root, String userPath,
086: Map<String, Object> attributes, String path) {
087: super (root, userPath, path);
088: }
089:
090: /**
091: * schemeWalk is called by Path for a scheme lookup like file:/tmp/foo
092: *
093: * @param userPath the user's lookup() path
094: * @param attributes the user's attributes
095: * @param filePath the actual lookup() path
096: * @param offset offset into filePath
097: */
098: protected Path schemeWalk(String userPath,
099: Map<String, Object> attributes, String filePath, int offset) {
100: int length = filePath.length();
101:
102: if (length <= offset || filePath.charAt(offset) != '(')
103: return super .schemeWalk(userPath, attributes, filePath,
104: offset);
105:
106: MergePath mergePath = new MergePath();
107: mergePath.setUserPath(userPath);
108:
109: int head = ++offset;
110: int tail = head;
111: while (tail < length) {
112: int ch = filePath.charAt(tail);
113:
114: if (ch == ')') {
115: if (head + 1 != tail) {
116: String subPath = filePath.substring(head, tail);
117:
118: if (subPath.startsWith("(")
119: && subPath.endsWith(")"))
120: subPath = subPath.substring(1,
121: subPath.length() - 1);
122:
123: mergePath.addMergePath(Vfs.lookup(subPath));
124: }
125:
126: if (tail + 1 == length)
127: return mergePath;
128: else
129: return mergePath.fsWalk(userPath, attributes,
130: filePath.substring(tail + 1));
131: } else if (ch == ';') {
132: String subPath = filePath.substring(head, tail);
133:
134: if (subPath.startsWith("(") && subPath.endsWith(")"))
135: subPath = subPath
136: .substring(1, subPath.length() - 1);
137:
138: mergePath.addMergePath(Vfs.lookup(subPath));
139:
140: head = ++tail;
141: } else if (ch == '(') {
142: int depth = 1;
143:
144: for (tail++; tail < length; tail++) {
145: if (filePath.charAt(tail) == '(')
146: depth++;
147: else if (filePath.charAt(tail) == ')') {
148: tail++;
149: depth--;
150: if (depth == 0)
151: break;
152: }
153: }
154:
155: if (depth != 0)
156: return new NotFoundPath(filePath);
157: } else
158: tail++;
159: }
160:
161: return new NotFoundPath(filePath);
162: }
163:
164: /**
165: * Adds a new path to the end of the merge path.
166: *
167: * @param path the new path to search
168: */
169: public void addMergePath(Path path) {
170: if (!(path instanceof MergePath)) {
171: // Need to normalize so directory paths ends with a "./"
172: // XXX:
173: //if (path.isDirectory())
174: // path = path.lookup("./");
175:
176: ArrayList<Path> pathList = ((MergePath) _root)._pathList;
177:
178: if (!pathList.contains(path))
179: pathList.add(path);
180: } else if (((MergePath) path)._root == _root)
181: return;
182: else {
183: MergePath mergePath = (MergePath) path;
184: ArrayList<Path> subPaths = mergePath.getMergePaths();
185: String pathName = "./" + mergePath._pathname + "/";
186:
187: for (int i = 0; i < subPaths.size(); i++) {
188: Path subPath = subPaths.get(i);
189:
190: addMergePath(subPath.lookup(pathName));
191: }
192: }
193: }
194:
195: /**
196: * Adds the classpath as paths in the MergePath.
197: */
198: public void addClassPath() {
199: addClassPath(Thread.currentThread().getContextClassLoader());
200: }
201:
202: /**
203: * Adds the classpath for the loader as paths in the MergePath.
204: *
205: * @param loader class loader whose classpath should be used to search.
206: */
207: public void addClassPath(ClassLoader loader) {
208: String classpath = null;
209:
210: if (loader instanceof DynamicClassLoader)
211: classpath = ((DynamicClassLoader) loader).getClassPath();
212: else
213: classpath = CauchoSystem.getClassPath();
214:
215: addClassPath(classpath);
216: }
217:
218: /**
219: * Adds the classpath for the loader as paths in the MergePath.
220: *
221: * @param loader class loader whose classpath should be used to search.
222: */
223: public void addResourceClassPath(ClassLoader loader) {
224: String classpath = null;
225:
226: if (loader instanceof DynamicClassLoader)
227: classpath = ((DynamicClassLoader) loader)
228: .getResourcePathSpecificFirst();
229: else
230: classpath = CauchoSystem.getClassPath();
231:
232: addClassPath(classpath);
233: }
234:
235: /**
236: * Adds the classpath as paths in the MergePath.
237: */
238: public void addLocalClassPath() {
239: addLocalClassPath(Thread.currentThread()
240: .getContextClassLoader());
241: }
242:
243: /**
244: * Adds the classpath for the loader as paths in the MergePath.
245: *
246: * @param loader class loader whose classpath should be used to search.
247: */
248: public void addLocalClassPath(ClassLoader loader) {
249: String classpath = null;
250:
251: if (loader instanceof DynamicClassLoader)
252: classpath = ((DynamicClassLoader) loader)
253: .getLocalClassPath();
254: else
255: classpath = System.getProperty("java.class.path");
256:
257: addClassPath(classpath);
258: }
259:
260: /**
261: * Adds the classpath for the loader as paths in the MergePath.
262: *
263: * @param classpath class loader whose classpath should be used to search.
264: */
265: public void addClassPath(String classpath) {
266: char sep = CauchoSystem.getPathSeparatorChar();
267: int head = 0;
268: int tail = 0;
269: while (head < classpath.length()) {
270: tail = classpath.indexOf(sep, head);
271:
272: String segment = null;
273: if (tail < 0) {
274: segment = classpath.substring(head);
275: head = classpath.length();
276: } else {
277: segment = classpath.substring(head, tail);
278: head = tail + 1;
279: }
280:
281: if (segment.equals(""))
282: continue;
283: else if (segment.endsWith(".jar")
284: || segment.endsWith(".zip"))
285: addMergePath(JarPath.create(Vfs.lookup(segment)));
286: else
287: addMergePath(Vfs.lookup(segment));
288: }
289: }
290:
291: /**
292: * Return the list of paths searched in the merge path.
293: */
294: public ArrayList<Path> getMergePaths() {
295: return ((MergePath) _root)._pathList;
296: }
297:
298: /**
299: * Walking down the path just extends the path. It won't be evaluated
300: * until opening.
301: */
302: public Path fsWalk(String userPath, Map<String, Object> attributes,
303: String path) {
304: ArrayList<Path> pathList = getMergePaths();
305:
306: if (!userPath.startsWith("/") || pathList.size() == 0)
307: return new MergePath((MergePath) _root, userPath,
308: attributes, path);
309:
310: String bestPrefix = null;
311: for (int i = 0; i < pathList.size(); i++) {
312: Path subPath = pathList.get(i);
313: String prefix = subPath.getPath();
314:
315: if (path.startsWith(prefix)
316: && (bestPrefix == null || bestPrefix.length() < prefix
317: .length())) {
318: bestPrefix = prefix;
319: }
320: }
321:
322: if (bestPrefix != null) {
323: path = path.substring(bestPrefix.length());
324: if (!path.startsWith("/"))
325: path = "/" + path;
326:
327: return new MergePath((MergePath) _root, userPath,
328: attributes, path);
329: }
330:
331: return pathList.get(0).lookup(userPath, attributes);
332: }
333:
334: /**
335: * Returns the scheme of the best path.
336: */
337: public String getScheme() {
338: return getBestPath().getScheme();
339: }
340:
341: /**
342: * Returns the full path name of the best path.
343: */
344: public String getFullPath() {
345: Path path = getBestPath();
346:
347: return path.getFullPath();
348: }
349:
350: /**
351: * Returns the full native path name of the best path.
352: */
353: public String getNativePath() {
354: Path path = getBestPath();
355:
356: return path.getNativePath();
357: }
358:
359: /**
360: * Returns the URL of the best path.
361: */
362: public String getURL() {
363: Path path = getBestPath();
364:
365: if (!path.exists())
366: path = getWritePath();
367:
368: return path.getURL();
369: }
370:
371: /**
372: * Returns the relative path into the merge path.
373: */
374: public String getRelativePath() {
375: if (_pathname.startsWith("/"))
376: return "." + _pathname;
377: else
378: return _pathname;
379: }
380:
381: /**
382: * True if any file matching this path exists.
383: */
384: public boolean exists() {
385: return getBestPath().exists();
386: }
387:
388: /**
389: * True if the best path is a directory.
390: */
391: public boolean isDirectory() {
392: return getBestPath().isDirectory();
393: }
394:
395: /**
396: * True if the best path is a file.
397: */
398: public boolean isFile() {
399: return getBestPath().isFile();
400: }
401:
402: /**
403: * Returns the length of the best path.
404: */
405: public long getLength() {
406: return getBestPath().getLength();
407: }
408:
409: /**
410: * Returns the last modified time of the best path.
411: */
412: public long getLastModified() {
413: return getBestPath().getLastModified();
414: }
415:
416: /**
417: * Returns true if the best path can be read.
418: */
419: public boolean canRead() {
420: return getBestPath().canRead();
421: }
422:
423: /**
424: * Returns true if the best path can be written to.
425: */
426: public boolean canWrite() {
427: return getBestPath().canWrite();
428: }
429:
430: /**
431: * Returns all the resources matching the path.
432: */
433: public ArrayList<Path> getResources(String pathName) {
434: ArrayList<Path> list = new ArrayList<Path>();
435:
436: String pathname = _pathname;
437: // XXX: why was this here?
438: if (pathname.startsWith("/"))
439: pathname = "." + pathname;
440:
441: ArrayList<Path> pathList = ((MergePath) _root)._pathList;
442: for (int i = 0; i < pathList.size(); i++) {
443: Path path = pathList.get(i);
444:
445: path = path.lookup(pathname);
446:
447: ArrayList<Path> subResources = path.getResources(pathName);
448: for (int j = 0; j < subResources.size(); j++) {
449: Path newPath = subResources.get(j);
450:
451: if (!list.contains(newPath))
452: list.add(newPath);
453: }
454: }
455:
456: return list;
457: }
458:
459: /**
460: * Returns all the resources matching the path.
461: */
462: public ArrayList<Path> getResources() {
463: ArrayList<Path> list = new ArrayList<Path>();
464:
465: String pathname = _pathname;
466: // XXX: why?
467: if (pathname.startsWith("/"))
468: pathname = "." + pathname;
469:
470: ArrayList<Path> pathList = ((MergePath) _root)._pathList;
471: for (int i = 0; i < pathList.size(); i++) {
472: Path path = pathList.get(i);
473:
474: path = path.lookup(pathname);
475:
476: ArrayList<Path> subResources = path.getResources();
477: for (int j = 0; j < subResources.size(); j++) {
478: Path newPath = subResources.get(j);
479:
480: if (!list.contains(newPath))
481: list.add(newPath);
482: }
483: }
484:
485: return list;
486: }
487:
488: /**
489: * List the merged directories.
490: */
491: public String[] list() throws IOException {
492: ArrayList<String> list = new ArrayList<String>();
493:
494: String pathname = _pathname;
495: // XXX:??
496: if (pathname.startsWith("/"))
497: pathname = "." + pathname;
498:
499: ArrayList<Path> pathList = ((MergePath) _root)._pathList;
500: for (int i = 0; i < pathList.size(); i++) {
501: Path path = pathList.get(i);
502:
503: path = path.lookup(pathname);
504:
505: if (path.isDirectory()) {
506: String[] subList = path.list();
507: for (int j = 0; j < subList.length; j++) {
508: if (!list.contains(subList[j]))
509: list.add(subList[j]);
510: }
511: }
512: }
513:
514: return (String[]) list.toArray(new String[list.size()]);
515: }
516:
517: /**
518: * XXX: Probably should mkdir in the first path
519: */
520: public boolean mkdir() throws IOException {
521: return getWritePath().mkdir();
522: }
523:
524: /**
525: * XXX: Probably should mkdir in the first path
526: */
527: public boolean mkdirs() throws IOException {
528: return getWritePath().mkdirs();
529: }
530:
531: /**
532: * Remove the matching path.
533: */
534: public boolean remove() throws IOException {
535: return getBestPath().remove();
536: }
537:
538: /**
539: * Renames the path.
540: */
541: public boolean renameTo(Path path) throws IOException {
542: return getBestPath().renameTo(path);
543: }
544:
545: /**
546: * Opens the best path for reading.
547: */
548: public StreamImpl openReadImpl() throws IOException {
549: StreamImpl stream = getBestPath().openReadImpl();
550: stream.setPath(this );
551: return stream;
552: }
553:
554: /**
555: * Opens the best path for writing. XXX: If the best path doesn't
556: * exist, this should probably create the file in the first path.
557: */
558: public StreamImpl openWriteImpl() throws IOException {
559: StreamImpl stream = getWritePath().openWriteImpl();
560: stream.setPath(this );
561: return stream;
562: }
563:
564: /**
565: * Opens the best path for reading and writing. XXX: If the best path
566: * doesn't exist, this should probably create the file in the first path.
567: */
568: public StreamImpl openReadWriteImpl() throws IOException {
569: StreamImpl stream = getWritePath().openReadWriteImpl();
570: stream.setPath(this );
571: return stream;
572: }
573:
574: /**
575: * Opens the best path for appending. XXX: If the best path
576: * doesn't exist, this should probably create the file in the first path.
577: */
578: public StreamImpl openAppendImpl() throws IOException {
579: StreamImpl stream = getWritePath().openAppendImpl();
580: stream.setPath(this );
581: return stream;
582: }
583:
584: /**
585: * Returns the first matching path.
586: */
587: public Path getWritePath() {
588: String pathname = _pathname;
589: // XXX:??
590: if (pathname.startsWith("/"))
591: pathname = "." + pathname;
592:
593: ArrayList<Path> pathList = ((MergePath) _root)._pathList;
594:
595: if (pathList.size() == 0)
596: return new NotFoundPath(pathname);
597: else {
598: return pathList.get(0).lookup(pathname);
599: }
600: }
601:
602: /**
603: * Creates a dependency.
604: */
605: @Override
606: public PersistentDependency createDepend() {
607: ArrayList<Path> pathList = ((MergePath) _root)._pathList;
608:
609: if (pathList.size() == 1)
610: return pathList.get(0).createDepend();
611:
612: DependencyList dependList = new DependencyList();
613:
614: for (int i = 0; i < pathList.size(); i++) {
615: Path path = pathList.get(i);
616:
617: Path realPath = path.lookup(_pathname);
618:
619: dependList.add(realPath.createDepend());
620: }
621:
622: return dependList;
623: }
624:
625: /**
626: * Returns the first matching path.
627: */
628: public Path getBestPath() {
629: if (_bestPath != null)
630: return _bestPath;
631:
632: String pathname = _pathname;
633: // XXX:??
634: if (pathname.startsWith("/"))
635: pathname = "." + pathname;
636:
637: ArrayList<Path> pathList = ((MergePath) _root)._pathList;
638: for (int i = 0; i < pathList.size(); i++) {
639: Path path = pathList.get(i);
640:
641: Path realPath = path.lookup(pathname);
642:
643: realPath.setUserPath(_userPath);
644:
645: if (realPath.exists()) {
646: _bestPath = realPath;
647: return realPath;
648: }
649: }
650:
651: /*
652: pathname = _pathname;
653: for (int i = 0; i < pathList.size(); i++) {
654: Path path = pathList.get(i);
655:
656: Path realPath = path.lookup(pathname);
657:
658: realPath.setUserPath(_userPath);
659:
660: if (realPath.exists()) {
661: _bestPath = realPath;
662: return realPath;
663: }
664: }
665: */
666:
667: if (pathList.size() > 0) {
668: Path path = pathList.get(0);
669:
670: if (pathname.startsWith("/"))
671: pathname = "." + pathname;
672:
673: Path realPath = path.lookup(pathname);
674:
675: realPath.setUserPath(_userPath);
676:
677: return realPath;
678: }
679:
680: return new NotFoundPath(_userPath);
681: }
682:
683: /**
684: * Returns a name for the path
685: */
686: public String toString() {
687: return "MergePath[" + _pathname + "]";
688: }
689: }
|