001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000-2005 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: license version 2.1 as published by the Free Software Foundation.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: *************************************************************************
026:
027: For more information, contact:
028:
029: IT Mill Ltd phone: +358 2 4802 7180
030: Ruukinkatu 2-4 fax: +358 2 4802 7181
031: 20540, Turku email: info@itmill.com
032: Finland company www: www.itmill.com
033:
034: Primary source for MillStone information and releases: www.millstone.org
035:
036: ********************************************************************** */
037:
038: package org.millstone.base.data.util;
039:
040: import java.util.ArrayList;
041: import java.util.Arrays;
042: import java.util.Collection;
043: import java.util.Collections;
044: import java.util.Date;
045: import java.util.Iterator;
046: import java.util.LinkedList;
047: import java.util.List;
048: import java.io.File;
049: import java.io.FilenameFilter;
050: import java.io.IOException;
051: import java.lang.reflect.Method;
052: import org.millstone.base.data.Container;
053: import org.millstone.base.data.Item;
054: import org.millstone.base.data.Property;
055: import org.millstone.base.service.FileTypeResolver;
056: import org.millstone.base.terminal.Resource;
057:
058: /** A hierarchical container wrapper for a filesystem.
059: *
060: * @author IT Mill Ltd.
061: * @version 3.1.1
062: * @since 3.0
063: */
064: public class FilesystemContainer implements Container.Hierarchical {
065:
066: /** String identifier of a file's "name" property. */
067: public static String PROPERTY_NAME = "Name";
068:
069: /** String identifier of a file's "size" property. */
070: public static String PROPERTY_SIZE = "Size";
071:
072: /** String identifier of a file's "icon" property. */
073: public static String PROPERTY_ICON = "Icon";
074:
075: /** String identifier of a file's "last modified" property. */
076: public static String PROPERTY_LASTMODIFIED = "Last Modified";
077:
078: /** List of the string identifiers for the available properties */
079: public static Collection FILE_PROPERTIES;
080:
081: private static Method FILEITEM_LASTMODIFIED;
082:
083: private static Method FILEITEM_NAME;
084: private static Method FILEITEM_ICON;
085: private static Method FILEITEM_SIZE;
086:
087: static {
088:
089: FILE_PROPERTIES = new ArrayList();
090: FILE_PROPERTIES.add(PROPERTY_NAME);
091: FILE_PROPERTIES.add(PROPERTY_ICON);
092: FILE_PROPERTIES.add(PROPERTY_SIZE);
093: FILE_PROPERTIES.add(PROPERTY_LASTMODIFIED);
094: FILE_PROPERTIES = Collections
095: .unmodifiableCollection(FILE_PROPERTIES);
096: try {
097: FILEITEM_LASTMODIFIED = FileItem.class.getMethod(
098: "lastModified", new Class[] {});
099: FILEITEM_NAME = FileItem.class.getMethod("getName",
100: new Class[] {});
101: FILEITEM_ICON = FileItem.class.getMethod("getIcon",
102: new Class[] {});
103: FILEITEM_SIZE = FileItem.class.getMethod("getSize",
104: new Class[] {});
105: } catch (NoSuchMethodException e) {
106:
107: }
108: }
109:
110: private File[] roots = new File[] {};
111: private FilenameFilter filter = null;
112: private boolean recursive = true;
113:
114: /** Construct a new <code>FileSystemContainer</code> with the specified
115: * file as the root of the filesystem. The files are included recursively.
116: *
117: * @param root root file for the new file-system container. Null values are ignored.
118: */
119: public FilesystemContainer(File root) {
120: if (root != null) {
121: this .roots = new File[] { root };
122: }
123: }
124:
125: /** Construct a new <code>FileSystemContainer</code> with the specified
126: * file as the root of the filesystem. The files are included recursively.
127: *
128: * @param root root file for the new file-system container
129: * @param recursive should the container recursively contain subdirectories.
130: */
131: public FilesystemContainer(File root, boolean recursive) {
132: this (root);
133: this .setRecursive(recursive);
134: }
135:
136: /** Construct a new <code>FileSystemContainer</code> with the specified
137: * file as the root of the filesystem.
138: *
139: * @param root root file for the new file-system container
140: * @param extension Filename extension (w/o separator) to limit the files in container.
141: * @param recursive should the container recursively contain subdirectories.
142: */
143: public FilesystemContainer(File root, String extension,
144: boolean recursive) {
145: this (root);
146: this .setFilter(extension);
147: this .setRecursive(recursive);
148: }
149:
150: /** Construct a new <code>FileSystemContainer</code> with the specified
151: * root and recursivity status.
152: *
153: * @param root root file for the new file-system container
154: * @param filter Filename filter to limit the files in container.
155: * @param recursive should the container recursively contain subdirectories.
156: */
157: public FilesystemContainer(File root, FilenameFilter filter,
158: boolean recursive) {
159: this (root);
160: this .setFilter(filter);
161: this .setRecursive(recursive);
162: }
163:
164: /** Add new root file directory.
165: * Adds a file to be included as root file directory in the FilesystemContainer.
166: * @param root File to be added as root directory. Null values are ignored.
167: */
168: public void addRoot(File root) {
169: if (root != null) {
170: File[] newRoots = new File[this .roots.length + 1];
171: for (int i = 0; i < this .roots.length; i++) {
172: newRoots[i] = this .roots[i];
173: }
174: newRoots[this .roots.length] = root;
175: this .roots = newRoots;
176: }
177: }
178:
179: /** Tests if the specified Item in the container may have children.
180: * Since a <code>FileSystemContainer</code> contains files and
181: * directories, this method returns <code>true</code> for directory
182: * Items only.
183: *
184: * @return <code>true</code> if the specified Item is a directory,
185: * <code>false</code> otherwise.
186: */
187: public boolean areChildrenAllowed(Object itemId) {
188: return itemId instanceof File && ((File) itemId).canRead()
189: && ((File) itemId).isDirectory();
190: }
191:
192: /* Get the ID's of all Items who are children of the specified Item.
193: * Don't add a JavaDoc comment here, we use the default documentation
194: * from implemented interface.
195: */
196: public Collection getChildren(Object itemId) {
197:
198: if (!(itemId instanceof File))
199: return Collections.unmodifiableCollection(new LinkedList());
200: File[] f;
201: if (this .filter != null)
202: f = ((File) itemId).listFiles(this .filter);
203: else
204: f = ((File) itemId).listFiles();
205:
206: if (f == null)
207: return Collections.unmodifiableCollection(new LinkedList());
208:
209: List l = Arrays.asList(f);
210: Collections.sort(l);
211:
212: return Collections.unmodifiableCollection(l);
213: }
214:
215: /* Get the parent item of the specified Item.
216: * Don't add a JavaDoc comment here, we use the default documentation
217: * from implemented interface.
218: */
219: public Object getParent(Object itemId) {
220:
221: if (!(itemId instanceof File))
222: return null;
223: return ((File) itemId).getParentFile();
224: }
225:
226: /* Test if the specified Item has any children.
227: * Don't add a JavaDoc comment here, we use the default documentation
228: * from implemented interface.
229: */
230: public boolean hasChildren(Object itemId) {
231:
232: if (!(itemId instanceof File))
233: return false;
234: String[] l;
235: if (this .filter != null)
236: l = ((File) itemId).list(this .filter);
237: else
238: l = ((File) itemId).list();
239: return (l != null) && (l.length > 0);
240: }
241:
242: /* Test if the specified Item is the root of the filesystem.
243: * Don't add a JavaDoc comment here, we use the default documentation
244: * from implemented interface.
245: */
246: public boolean isRoot(Object itemId) {
247:
248: if (!(itemId instanceof File))
249: return false;
250: for (int i = 0; i < roots.length; i++) {
251: if (roots[i].equals((File) itemId))
252: return true;
253: }
254: return false;
255: }
256:
257: /* Get the ID's of all root Items in the container.
258: * Don't add a JavaDoc comment here, we use the default documentation
259: * from implemented interface.
260: */
261: public Collection rootItemIds() {
262:
263: File[] f;
264:
265: // in single root case we use children
266: if (roots.length == 1) {
267: if (this .filter != null)
268: f = roots[0].listFiles(this .filter);
269: else
270: f = roots[0].listFiles();
271: } else {
272: f = this .roots;
273: }
274:
275: if (f == null)
276: return Collections.unmodifiableCollection(new LinkedList());
277:
278: List l = Arrays.asList(f);
279: Collections.sort(l);
280:
281: return Collections.unmodifiableCollection(l);
282: }
283:
284: /** Return false - conversion from files to directories is not
285: * supported.
286: *
287: * @return <code>false</code>
288: */
289: public boolean setChildrenAllowed(Object itemId,
290: boolean areChildrenAllowed)
291: throws UnsupportedOperationException {
292:
293: throw new UnsupportedOperationException(
294: "Conversion file to/from directory is not supported");
295: }
296:
297: /** Return false - moving files around in the filesystem is not
298: * supported.
299: *
300: * @return <code>false</code>
301: */
302: public boolean setParent(Object itemId, Object newParentId)
303: throws UnsupportedOperationException {
304:
305: throw new UnsupportedOperationException(
306: "File moving is not supported");
307: }
308:
309: /* Test if the filesystem contains the specified Item.
310: * Don't add a JavaDoc comment here, we use the default documentation
311: * from implemented interface.
312: */
313: public boolean containsId(Object itemId) {
314:
315: if (!(itemId instanceof File))
316: return false;
317: boolean val = false;
318:
319: // Try to match all roots
320: for (int i = 0; i < roots.length; i++) {
321: try {
322: val |= ((File) itemId).getCanonicalPath().startsWith(
323: roots[i].getCanonicalPath());
324: } catch (IOException e) {
325: // Exception ignored
326: }
327:
328: }
329: if (val && this .filter != null)
330: val &= this .filter.accept(((File) itemId).getParentFile(),
331: ((File) itemId).getName());
332: return val;
333: }
334:
335: /* Gets the specified Item from the filesystem.
336: * Don't add a JavaDoc comment here, we use the default documentation
337: * from implemented interface.
338: */
339: public Item getItem(Object itemId) {
340:
341: if (!(itemId instanceof File))
342: return null;
343: return new FileItem((File) itemId);
344: }
345:
346: /** Internal recursive method to add the files under the specified
347: * directory to the collection.
348: *
349: * @param col the collection where the found items are added
350: * @param f the root file where to start adding files
351: */
352: private void addItemIds(Collection col, File f) {
353: File[] l;
354: if (this .filter != null)
355: l = f.listFiles(this .filter);
356: else
357: l = f.listFiles();
358: List ll = Arrays.asList(l);
359: Collections.sort(ll);
360:
361: for (Iterator i = ll.iterator(); i.hasNext();) {
362: File lf = (File) i.next();
363: if (lf.isDirectory())
364: addItemIds(col, lf);
365: else
366: col.add(lf);
367: }
368: }
369:
370: /* Gets the IDs of Items in the filesystem.
371: * Don't add a JavaDoc comment here, we use the default documentation
372: * from implemented interface.
373: */
374: public Collection getItemIds() {
375:
376: if (recursive) {
377: Collection col = new ArrayList();
378: for (int i = 0; i < roots.length; i++) {
379: addItemIds(col, roots[i]);
380: }
381: return Collections.unmodifiableCollection(col);
382: } else {
383: File[] f;
384: if (roots.length == 1) {
385: if (this .filter != null)
386: f = roots[0].listFiles(this .filter);
387: else
388: f = roots[0].listFiles();
389: } else {
390: f = roots;
391: }
392:
393: if (f == null)
394: return Collections
395: .unmodifiableCollection(new LinkedList());
396:
397: List l = Arrays.asList(f);
398: Collections.sort(l);
399: return Collections.unmodifiableCollection(l);
400: }
401:
402: }
403:
404: /** Gets the specified property of the specified file Item. The
405: * available file properties are "Name", "Size" and "Last Modified".
406: * If <code>propertyId</code> is not one of those, <code>null</code> is
407: * returned.
408: *
409: * @param itemId ID of the file whose property is requested
410: * @param propertyId The property's ID
411: * @return the requested property's value, or <code>null</code>
412: */
413: public Property getContainerProperty(Object itemId,
414: Object propertyId) {
415:
416: if (!(itemId instanceof File))
417: return null;
418:
419: if (propertyId.equals(PROPERTY_NAME))
420: return new MethodProperty(getType(propertyId),
421: new FileItem((File) itemId), FILEITEM_NAME, null);
422:
423: if (propertyId.equals(PROPERTY_ICON))
424: return new MethodProperty(getType(propertyId),
425: new FileItem((File) itemId), FILEITEM_ICON, null);
426:
427: if (propertyId.equals(PROPERTY_SIZE))
428: return new MethodProperty(getType(propertyId),
429: new FileItem((File) itemId), FILEITEM_SIZE, null);
430:
431: if (propertyId.equals(PROPERTY_LASTMODIFIED))
432: return new MethodProperty(getType(propertyId),
433: new FileItem((File) itemId), FILEITEM_LASTMODIFIED,
434: null);
435:
436: return null;
437: }
438:
439: /** Gets the collection of available file properties.
440: *
441: * @return Unmodifiable collection containing all available file
442: * properties.
443: */
444: public Collection getContainerPropertyIds() {
445: return FILE_PROPERTIES;
446: }
447:
448: /** Gets the specified property's data type. "Name" is a
449: * <code>String</code>, "Size" is a <code>Long</code>, "Last Modified"
450: * is a <code>Date</code>. If <code>propertyId</code> is not one of
451: * those, <code>null</code> is returned.
452: *
453: * @param propertyId ID of the property whose type is requested.
454: * @return data type of the requested property, or <code>null</code>
455: */
456: public Class getType(Object propertyId) {
457:
458: if (propertyId.equals(PROPERTY_NAME))
459: return String.class;
460: if (propertyId.equals(PROPERTY_ICON))
461: return Resource.class;
462: if (propertyId.equals(PROPERTY_SIZE))
463: return Long.class;
464: if (propertyId.equals(PROPERTY_LASTMODIFIED))
465: return Date.class;
466: return null;
467: }
468:
469: /** Internal method to recursively calculate the number of files under
470: * a root directory.
471: *
472: * @param f the root to start counting from.
473: */
474: private int getFileCounts(File f) {
475: File[] l;
476: if (this .filter != null)
477: l = f.listFiles(this .filter);
478: else
479: l = f.listFiles();
480:
481: if (l == null)
482: return 0;
483: int ret = l.length;
484: for (int i = 0; i < l.length; i++) {
485: if (l[i].isDirectory())
486: ret += getFileCounts(l[i]);
487: }
488: return ret;
489: }
490:
491: /** Gets the number of Items in the container. In effect, this is the
492: * combined amount of files and directories.
493: *
494: * @return Number of Items in the container.
495: */
496: public int size() {
497:
498: if (recursive) {
499: int counts = 0;
500: for (int i = 0; i < roots.length; i++) {
501: counts += getFileCounts(this .roots[i]);
502: }
503: return counts;
504: } else {
505: File[] f;
506: if (roots.length == 1) {
507: if (this .filter != null)
508: f = roots[0].listFiles(this .filter);
509: else
510: f = roots[0].listFiles();
511: } else {
512: f = roots;
513: }
514:
515: if (f == null)
516: return 0;
517: return f.length;
518: }
519: }
520:
521: /** A Item wrapper for files in a filesystem.
522: * @author IT Mill Ltd.
523: * @version 3.1.1
524: * @since 3.0
525: */
526: public class FileItem implements Item {
527:
528: /** The wrapped file. */
529: private File file;
530:
531: /** Construct a FileItem from a existing file. */
532: private FileItem(File file) {
533: this .file = file;
534: }
535:
536: /* Get the specified property of this file.
537: * Don't add a JavaDoc comment here, we use the default documentation
538: * from implemented interface.
539: */
540: public Property getItemProperty(Object id) {
541: return FilesystemContainer.this .getContainerProperty(file,
542: id);
543: }
544:
545: /* Get the IDs of all properties available for this item
546: * Don't add a JavaDoc comment here, we use the default documentation
547: * from implemented interface.
548: */
549: public Collection getItemPropertyIds() {
550: return FilesystemContainer.this .getContainerPropertyIds();
551: }
552:
553: /* Calculates a integer hash-code for the Property that's unique
554: * inside the Item containing the Property. Two different Properties
555: * inside the same Item contained in the same list always have
556: * different hash-codes, though Properties in different Items may
557: * have identical hash-codes.
558: *
559: * @return A locally unique hash-code as integer
560: */
561: public int hashCode() {
562: return file.hashCode()
563: ^ FilesystemContainer.this .hashCode();
564: }
565:
566: /* Tests if the given object is the same as the this object.
567: * Two Properties got from an Item with the same ID are equal.
568: *
569: * @param obj an object to compare with this object
570: * @return <code>true</code> if the given object is the same as
571: * this object, <code>false</code> if not
572: */
573: public boolean equals(Object obj) {
574: if (obj == null || !(obj instanceof FileItem))
575: return false;
576: FileItem fi = (FileItem) obj;
577: return fi.getHost() == getHost() && fi.file.equals(file);
578: }
579:
580: private FilesystemContainer getHost() {
581: return FilesystemContainer.this ;
582: }
583:
584: public Date lastModified() {
585: return new Date(this .file.lastModified());
586: }
587:
588: public String getName() {
589: return this .file.getName();
590: }
591:
592: public Resource getIcon() {
593: return FileTypeResolver.getIcon(this .file);
594: }
595:
596: public long getSize() {
597: if (this .file.isDirectory())
598: return 0;
599: return this .file.length();
600: }
601:
602: /**
603: * @see java.lang.Object#toString()
604: */
605: public String toString() {
606: if ("".equals(file.getName()))
607: return file.getAbsolutePath();
608: return file.getName();
609: }
610:
611: /** Filesystem container does not support adding new properties.
612: * @see org.millstone.base.data.Item#addItemProperty(Object, Property)
613: */
614: public boolean addItemProperty(Object id, Property property)
615: throws UnsupportedOperationException {
616: throw new UnsupportedOperationException(
617: "Filesystem container "
618: + "does not support adding new properties");
619: }
620:
621: /** Filesystem container does not support removing properties.
622: * @see org.millstone.base.data.Item#removeItemProperty(Object)
623: */
624: public boolean removeItemProperty(Object id)
625: throws UnsupportedOperationException {
626: throw new UnsupportedOperationException(
627: "Filesystem container does not support property removal");
628: }
629:
630: }
631:
632: /** Generic file extension filter for displaying only files having certain extension.
633: * @author IT Mill Ltd.
634: * @version 3.1.1
635: * @since 3.0
636: */
637: public class FileExtensionFilter implements FilenameFilter {
638:
639: private String filter;
640:
641: /** Construct new FileExtensionFilter using given extension.
642: * @param fileExtension File extension without the separator (dot).
643: * */
644: public FileExtensionFilter(String fileExtension) {
645: this .filter = "." + fileExtension;
646: }
647:
648: /** Allow only files with the extension and directories.
649: * @see java.io.FilenameFilter#accept(File, String)
650: */
651: public boolean accept(File dir, String name) {
652: if (name.endsWith(filter))
653: return true;
654: return new File(dir, name).isDirectory();
655: }
656:
657: }
658:
659: /** Returns the file filter used to limit the files in this container.
660: * @return Used filter instance or null if no filter is assigned.
661: */
662: public FilenameFilter getFilter() {
663: return filter;
664: }
665:
666: /** Sets the file filter used to limit the files in this container.
667: * @param filter The filter to set. <code>null</code> disables filtering.
668: */
669: public void setFilter(FilenameFilter filter) {
670: this .filter = filter;
671: }
672:
673: /** Sets the file filter used to limit the files in this container.
674: * @param extension Filename extension (w/o separator) to limit the files in container.
675: */
676: public void setFilter(String extension) {
677: this .filter = new FileExtensionFilter(extension);
678: }
679:
680: /**Is this container recursive filesystem.
681: * @return true if container is recursive, false otherwise.
682: */
683: public boolean isRecursive() {
684: return recursive;
685: }
686:
687: /** Sets the container recursive property.
688: * Set this to false to limit the files directly under the root file.
689: * Note, that this is meaningful only if the root really is a directory.
690: * @param New value for recursive property.
691: */
692: public void setRecursive(boolean recursive) {
693: this .recursive = recursive;
694: }
695:
696: /**
697: * @see org.millstone.base.data.Container#addContainerProperty(Object, Class, Object)
698: */
699: public boolean addContainerProperty(Object propertyId, Class type,
700: Object defaultValue) throws UnsupportedOperationException {
701: throw new UnsupportedOperationException(
702: "File system container does not support this operation");
703: }
704:
705: /**
706: * @see org.millstone.base.data.Container#addItem()
707: */
708: public Object addItem() throws UnsupportedOperationException {
709: throw new UnsupportedOperationException(
710: "File system container does not support this operation");
711: }
712:
713: /**
714: * @see org.millstone.base.data.Container#addItem(Object)
715: */
716: public Item addItem(Object itemId)
717: throws UnsupportedOperationException {
718: throw new UnsupportedOperationException(
719: "File system container does not support this operation");
720: }
721:
722: /**
723: * @see org.millstone.base.data.Container#removeAllItems()
724: */
725: public boolean removeAllItems()
726: throws UnsupportedOperationException {
727: throw new UnsupportedOperationException(
728: "File system container does not support this operation");
729: }
730:
731: /**
732: * @see org.millstone.base.data.Container#removeItem(Object)
733: */
734: public boolean removeItem(Object itemId)
735: throws UnsupportedOperationException {
736: throw new UnsupportedOperationException(
737: "File system container does not support this operation");
738: }
739:
740: /**
741: * @see org.millstone.base.data.Container#removeContainerProperty(Object)
742: */
743: public boolean removeContainerProperty(Object propertyId)
744: throws UnsupportedOperationException {
745: throw new UnsupportedOperationException(
746: "File system container does not support this operation");
747: }
748: }
|