1 package eu.simuline.util;
2
3 import java.io.File;
4 import java.io.PrintStream;
5 import java.io.IOException;
6
7 import java.util.Stack;
8 import java.util.regex.Pattern;
9
10 /**
11 * Emulates the unix shell command <code>find</code> which is invoked as
12 * <code>find [-H] [-L] [-P] [-Olevel]
13 * [-D help|tree|search|stat|rates|opt|exec] [path...] (expression)*</code>.
14 * If <code>path</code> is left out (possibly more than one path?),
15 * then the default is <code>.</code>, i.e. the current directory.
16 * The <code>path</code> need not be a directory.
17 * If it is a proper file then typically wildcards are used.
18 * Then the name is matched.
19 * If no expression is given, the default is <code>-print</code>.
20 * So we may assume,
21 * that both, a path and a non-empty sequence of expressions, are given.
22 * <p>
23 * The idea behind the <code>find</code> command is,
24 * that, starting with the files matching the <code>path</code>,
25 * each <code>expression</code> serves as a filter,
26 * feeding the filter corresponding to the following expression.
27 * Each filter can have a side effect.
28 * Expressions may be either tests or actions.
29 * For tests, the focus is on the filter-functionality,
30 * whereas actions are trivial filters just passing the files they receive.
31 * Actions are applied because of their side effects like <code>print</code>.
32 * <p>
33 * The most basic kind of finder corresponds with the command
34 * <code>find path</code> iterating just over the files in the path.
35 * This kind of finder is returned by the static method {@link #path(File)}.
36 * Starting with this basic finder, further finders can be added to the pipe
37 * using the member methods
38 * {@link #name(String)} and {@link #print(PrintStream)}.
39 *
40 * Created: Wed Nov 21 17:29:41 2012
41 *
42 * @author <a href="mailto:ernst.reissner@simuline.eu">Ernst Reissner</a>
43 * @version 1.0
44 */
45 public abstract class Finder {
46
47 /* -------------------------------------------------------------------- *
48 * inner classes. *
49 * -------------------------------------------------------------------- */
50
51 /**
52 * The most basic kind of finder:
53 * Method {@link #path(File)} returns an instance of this.
54 * {@link #next()} returns the given file and,
55 * if this is a folder all files therein.
56 */
57 static class Primary extends Finder {
58
59 /* ---------------------------------------------------------------- *
60 * fields. *
61 * ---------------------------------------------------------------- */
62
63 /**
64 * The list of files to be returned by {@link #next()}
65 * unwrapping folders recursively.
66 */
67 private final Stack<File> files;
68
69 /* ---------------------------------------------------------------- *
70 * constructors. *
71 * ---------------------------------------------------------------- */
72
73 /**
74 * Pushes <code>file</code> to {@link #files}.
75 */
76 Primary(File file) {
77 this.files = new Stack<File>();
78 this.files.push(file);
79 }
80
81 Primary(File[] path) {
82 this.files = new Stack<File>();
83 for (File file : path) {
84 this.files.push(file);
85 }
86
87 // for (int i = 0; i < path.length; i++) {
88 // this.files.push(path[i]);
89 // }
90 }
91
92 /* ---------------------------------------------------------------- *
93 * methods. *
94 * ---------------------------------------------------------------- */
95
96 /**
97 * Has a next file iff {@link #files} is not empty.
98 */
99 public boolean hasNext() {
100 return !this.files.isEmpty();
101 }
102
103 /**
104 * If the topmost entry of {@link #files} is no folder,
105 * this is returned as is.
106 * otherwise, in addition, this folder is unwrapped
107 * and the contents is pushed onto the stack {@link #files}.
108 */
109 public File next() {
110 assert hasNext();
111 File file = this.files.pop();
112 if (file.isDirectory()) {
113 File[] list = file.listFiles();
114 if (list == null) {
115 System.out.println("cannot read " + file);
116 } else {
117 // push in inverse order
118 for (int i = list.length - 1; i >= 0; i--) {
119 this.files.push(list[i]);
120 }
121 //this.files.addAll(Arrays.asList(list));
122 }
123 }
124 return file;
125 }
126
127 } // class Primary
128
129 /**
130 * A finder wrapping a {@link Finder.Filter}.
131 * Files are read from {@link #encl} and are passed via {@link #next()}
132 * iff they pass the filter {@link #filter}.
133 */
134 static class Secondary extends Finder {
135
136 /* ---------------------------------------------------------------- *
137 * fields. *
138 * ---------------------------------------------------------------- */
139
140 /**
141 * The next file to be returned by {@link #next()} if any,
142 * i.e. if {@link #hasNext()} returns <code>true</code>;
143 * otherwise this field is <code>null</code>.
144 */
145 private File next;
146
147 /**
148 * The source finder from which the stream of files is read.
149 * **** this is superfluous if this is not static
150 */
151 private final Finder encl;
152
153 /**
154 * The filter to be passed
155 * before a file is returned by {@link #next()}.
156 */
157 private final Filter filter;
158
159 /* ---------------------------------------------------------------- *
160 * constructors. *
161 * ---------------------------------------------------------------- */
162
163 /**
164 * Create a finder receiving a stream of files from <code>encl</code>
165 * and passing them further via {@link #next()}
166 * if they pass the filter <code>filter</code>.
167 */
168 Secondary(Finder encl, Filter filter) {
169 super();
170 this.encl = encl;
171 this.filter = filter;
172 updateNext();
173 }
174
175 /* ---------------------------------------------------------------- *
176 * methods. *
177 * ---------------------------------------------------------------- */
178
179 // **** better implementation if this class were not static
180 protected void updateNext() {
181 // while (Finder.this.hasNext()) {
182 while (this.encl.hasNext()) {
183 // this.next = Finder.this.next();
184 this.next = this.encl.next();
185 assert this.next != null;
186 if (this.filter.pass(this.next)) {
187 return;
188 }
189 }
190 this.next = null;
191 }
192
193 public boolean hasNext() {
194 return this.next != null;
195 }
196
197 public File next() {
198 assert hasNext();
199 File res = this.next;
200 assert res != null;
201 updateNext();
202 return res;
203 }
204
205 protected File getNext() {
206 assert hasNext();
207 File res = this.next;
208 assert res != null;
209 return res;
210 }
211 } // class Secondary
212
213 /**
214 * A filter passing all information and printing it on {@link #str}.
215 */
216 class PrintFilter extends Secondary {
217
218 /* ---------------------------------------------------------------- *
219 * fields. *
220 * ---------------------------------------------------------------- */
221
222 private final PrintStream str;
223 private int idx;
224
225
226 /* ---------------------------------------------------------------- *
227 * constructors. *
228 * ---------------------------------------------------------------- */
229
230 PrintFilter(Finder encl, PrintStream str) {
231 super(encl, TRUE);
232 this.str = str;
233 this.idx = 0;
234 }
235
236 /* ---------------------------------------------------------------- *
237 * methods. *
238 * ---------------------------------------------------------------- */
239
240 public File next() {
241 assert super.hasNext();
242 File res = getNext();
243 assert res != null;
244 this.idx++;
245 this.str.println(idx + " " + res.toString());
246 updateNext();
247 return res;
248 }
249 } // class PrintFilter
250
251 /**
252 * Represents a file filter.
253 */
254 abstract static class Filter {
255
256 /**
257 * Returns for the given file whether this file passes this filter.
258 */
259 abstract boolean pass(File file);
260
261 /**
262 * Returns a filter which passes a file
263 * iff <code>filter</code> does not.
264 * This corresponds the tests <code>\! expr1</code>
265 * in the original find command.
266 */
267 Filter not() {
268 return new NegFilter(this);
269 }
270
271 /**
272 * Returns a filter which passes a file
273 * iff so do all filters in <code>filters</code>.
274 * This corresponds the tests <code>expr1 -a .... exprn</code>
275 * and <code>expr1 -and .... exprn</code>
276 * in the original find command.
277 *
278 * @param filters
279 * a sequence of filters which may be empty.
280 * If empty, this filter passes all files like {@link #TRUE}.
281 * @return
282 * a lazy and-filter of type {@link Finder.AndFilter}.
283 * For more details see there.
284 */
285 static Filter and(Filter[] filters) {
286 return new AndFilter(filters);
287 }
288
289 /**
290 * Returns a filter which passes a file
291 * iff at least one filter in <code>filters</code> does so.
292 * This corresponds the tests <code>expr1 -o .... exprn</code>
293 * and <code>expr1 -or .... exprn</code>
294 * in the original find command.
295 *
296 * @param filters
297 * a sequence of filters which may be empty.
298 * If empty, this filter passes no file like {@link #FALSE}.
299 * @return
300 * a lazy and-filter of type {@link Finder.AndFilter}.
301 * For more details see there.
302 */
303 static Filter or(Filter[] filters) {
304 return new OrFilter(filters);
305 }
306 }
307
308 /**
309 * Filters files by name. See {@link Finder#name(String)}.
310 */
311 static class NameFilter extends Filter {
312
313 /* ---------------------------------------------------------------- *
314 * fields. *
315 * ---------------------------------------------------------------- */
316
317 /**
318 * The pattern to filter the filenames with.
319 */
320 private final Pattern pattern;
321
322 /* ---------------------------------------------------------------- *
323 * constructors. *
324 * ---------------------------------------------------------------- */
325
326 NameFilter(String pattern) {
327 this.pattern = Pattern.compile(pattern);
328 }
329
330 /* ---------------------------------------------------------------- *
331 * methods. *
332 * ---------------------------------------------------------------- */
333
334 public boolean pass(File file) {
335 return this.pattern.matcher(file.toString()).matches();
336 }
337
338 } // class NameFilter
339
340 /**
341 * Filter executing a shell command and passes the file received
342 * if the shell command succeeds according to its return code.
343 *
344 * @see Finder#exec(String[])
345 * @see Finder.ExecJavaFilter
346 */
347 static class ExecFilter extends Filter {
348
349 /* ---------------------------------------------------------------- *
350 * fields. *
351 * ---------------------------------------------------------------- */
352
353 /**
354 * The command to be executed including arguments
355 * separated by space as to be passed to {@link Runtime#exec(String)}
356 * in order to decide whether the given file passes the filter.
357 * For more information on the entries
358 * see {@link Finder#exec(String[])}.
359 */
360 private final String[] cmd;
361
362 /* ---------------------------------------------------------------- *
363 * constructors. *
364 * ---------------------------------------------------------------- */
365
366 /**
367 * Creates an execution filter from the given command and arguments.
368 *
369 * @param cmd
370 * The 0th entry is the command itself and the others are arguments.
371 * For details see {@link Finder#exec(String[])}.
372 */
373 ExecFilter(String[] cmd) {
374 this.cmd = cmd;
375 }
376
377 /* ---------------------------------------------------------------- *
378 * methods. *
379 * ---------------------------------------------------------------- */
380
381 /**
382 * The given file passes this filter,
383 * i.e. this method returns <code>true</code>
384 * if the shell command given by {@link #cmd}
385 * succeeds according to its return value (which is then zero).
386 * <p>
387 * Execution proceeds in the following steps:
388 * <ul>
389 * <li>
390 * Replace the arguments {@link Finder#EXEC_ARG}
391 * by the long name of <code>file</code>.
392 * <li>
393 * Create a separate process to execute the command.
394 * <li>
395 * Wait for execution end and
396 * <li>
397 * pass if the return value 0 indicates success.
398 * </ul>
399 */
400 public boolean pass(File file) {
401 Process proc;
402 int exitVal;
403 String nextStr = file.toString();
404
405 // replace argument by filename
406 String[] cmd = new String[this.cmd.length];
407 System.arraycopy(this.cmd, 0, cmd, 0, this.cmd.length);
408 // the 0th entry should not be EXEC_ARG
409 for (int idx = 0; idx < this.cmd.length; idx++) {
410 if (cmd[idx] == Finder.EXEC_ARG) {
411 cmd[idx] = nextStr;
412 }
413 }
414
415 try {
416 //System.out.println(":"+(this.cmdName+" "+this.next));
417 proc = Runtime.getRuntime().exec(cmd);
418 } catch (IOException e) {
419 System.out.println("exec: " + e.getMessage());
420 return false;
421 }
422 try {
423 exitVal = proc.waitFor(); // exitValue
424 } catch (InterruptedException e) {
425 System.out.println("exec:" + e.getMessage());
426 return false;
427 }
428 return exitVal == 0;
429 }
430
431 } // class ExecFilter
432
433 /**
434 * To be implemented by java classes to apply a filter to a file
435 * and give feedback whether this filter succeeded.
436 * This is analogous to passing a file to as shell command
437 * and reading back whether the command succeeded.
438 * This interface should be used to define a {@link Finder.ExecJavaFilter}.
439 */
440 public interface Callable {
441 /**
442 * Applies some filter on the given file
443 * and returns whether the called operation succeeded.
444 */
445 boolean call(File file);
446 } // interface Callable
447
448 /**
449 * Filter executing a java class implementing {@link Finder.Callable}
450 * which passes the file received
451 * if the method {@link Finder.Callable#call(File)} succeeds
452 * according to its return code.
453 *
454 * @see Finder#exec(String[])
455 * @see Finder.ExecFilter
456 */
457 static class ExecJavaFilter extends Filter {
458
459 /* ---------------------------------------------------------------- *
460 * fields. *
461 * ---------------------------------------------------------------- */
462
463 /**
464 * The instance to be executed to decide
465 * whether a given file passes the filter
466 * invoking {@link Finder.Callable#call(File)}.
467 * Besides filtering, other actions may be taken as side effect.
468 * Parameters are passed to the callable when creating the instance
469 * or later by a setter method, depending on the implementation.
470 */
471 private final Callable callable;
472
473 /* ---------------------------------------------------------------- *
474 * constructors. *
475 * ---------------------------------------------------------------- */
476
477 /**
478 * Creates a java execution filter
479 * from the given {@link Finder.Callable}.
480 *
481 * @param callable
482 * The callable defining the filter.
483 */
484 ExecJavaFilter(Callable callable) {
485 this.callable = callable;
486 }
487
488 /* ---------------------------------------------------------------- *
489 * methods. *
490 * ---------------------------------------------------------------- */
491
492 /**
493 * The given file passes this filter,
494 * i.e. this method returns <code>true</code>,
495 * if so does {@link #callable}.
496 */
497 public boolean pass(File file) {
498 return this.callable.call(file);
499 }
500
501 } // class ExecJavaFilter
502
503
504 /**
505 * One of the logical operations of filters:
506 * Returns a filter which passes a file
507 * iff the original filter {@link #negFilter} does not.
508 * <p>
509 * See {@link Finder.Filter#not()}.
510 */
511 static class NegFilter extends Filter {
512
513 /* ---------------------------------------------------------------- *
514 * fields. *
515 * ---------------------------------------------------------------- */
516
517 /**
518 * The filter to be negated.
519 */
520 private final Filter negFilter;
521
522 /* ---------------------------------------------------------------- *
523 * constructors. *
524 * ---------------------------------------------------------------- */
525
526 NegFilter(Filter negFilter) {
527 this.negFilter = negFilter;
528 }
529
530 /* ---------------------------------------------------------------- *
531 * methods. *
532 * ---------------------------------------------------------------- */
533
534 /**
535 * Passes the given file iff {@link #negFilter} does not.
536 */
537 public boolean pass(File file) {
538 return !this.negFilter.pass(file);
539 }
540
541 } // class NegFilter
542
543 /**
544 * One of the logical operations of filters:
545 * Returns a filter which passes a file
546 * iff all original filters in {@link #filters} do so.
547 * <p>
548 * This is a lazy and-filter, i.e. if one of the filters rejects the file,
549 * the filters later in the sequence are not executed any more.
550 * So the ordering has an effect, if one of the filters has a side effect.
551 * Ordering may also affect performance.
552 * <p>
553 * Maybe lazy and-filters are not so useful, because,
554 * unlike non-lazy and-filters and or-filters,
555 * they could be realized as a sequence of filters.
556 * <p>
557 * See {@link Finder#and(Filter[])}.
558 */
559 static class AndFilter extends Filter {
560
561 /* ---------------------------------------------------------------- *
562 * fields. *
563 * ---------------------------------------------------------------- */
564
565 /**
566 * This filter passes a file iff all of these pass
567 * if invoked in the natural ordering.
568 */
569 private final Filter[] filters;
570
571 /* ---------------------------------------------------------------- *
572 * constructors. *
573 * ---------------------------------------------------------------- */
574
575 AndFilter(Filter[] filters) {
576 this.filters = filters;
577 }
578
579 /* ---------------------------------------------------------------- *
580 * methods. *
581 * ---------------------------------------------------------------- */
582
583
584 public boolean pass(File file) {
585 for (int i = 0; i < filters.length; i++) {
586 if (!this.filters[i].pass(file)) {
587 return false;
588 }
589 } // for
590
591 return true;
592 }
593
594 } // class AndFilter
595
596 /**
597 * One of the logical operations of filters:
598 * Returns a filter which passes a file
599 * iff at least one of the original filters in {@link #filters} do so.
600 * <p>
601 * This is a lazy or-filter, i.e. if one of the filters accepts the file,
602 * the filters later in the sequence are not executed any more.
603 * So the ordering has an effect, if one of the filters has a side effect.
604 * Ordering may also affect performance.
605 * <p>
606 * See {@link Finder#or(Filter[])}.
607 */
608 static class OrFilter extends Filter {
609
610 /* ---------------------------------------------------------------- *
611 * fields. *
612 * ---------------------------------------------------------------- */
613
614 /**
615 * This filter passes a file iff at least one of of these passes
616 * if invoked in the natural ordering.
617 */
618 private final Filter[] filters;
619
620 /* ---------------------------------------------------------------- *
621 * constructors. *
622 * ---------------------------------------------------------------- */
623
624 OrFilter(Filter[] filters) {
625 this.filters = filters;
626 }
627
628 /* ---------------------------------------------------------------- *
629 * methods. *
630 * ---------------------------------------------------------------- */
631
632
633 public boolean pass(File file) {
634 for (int i = 0; i < filters.length; i++) {
635 if (this.filters[i].pass(file)) {
636 return true;
637 }
638 } // for
639
640 return false;
641 }
642
643 } // class OrFilter
644
645
646 /* -------------------------------------------------------------------- *
647 * Class constants (creator methods for Filters come later). *
648 * -------------------------------------------------------------------- */
649
650 /**
651 * For {@link Finder.ExecFilter}s
652 * representing a filter invoking a shell command,
653 * this string represents the argument of the command
654 * which is in the original find a <code>{}</code>
655 * and which is replaced by the file name
656 * received by the preceding portion of the pipe.
657 * CAUTION: It must be used this instance
658 * and no other equivalent string <code>{}</code>.
659 */
660 public static final String EXEC_ARG = "{}";
661
662 /**
663 * A filter passing all files.
664 * This corresponds the test <code>-true</code>
665 * in the original find command.
666 */
667 public static final Filter TRUE = new Filter() {
668 public boolean pass(File file) {
669 return true;
670 }
671 };
672
673 /**
674 * A filter passing no file.
675 * This corresponds the test <code>-false</code>
676 * in the original find command.
677 */
678 public static final Filter FALSE = new Filter() {
679 public boolean pass(File file) {
680 return false;
681 }
682 };
683
684 /**
685 * Filter passing the file received iff it is executable.
686 * This corresponds the test <code>-executable</code>
687 * in the original find command.
688 * Do not mix up with {@link Finder.ExecFilter}.
689 */
690 public static final Filter CAN_EXEC = new Filter() {
691 public boolean pass(File file) {
692 return file.canExecute();
693 }
694 };
695
696 /**
697 * Filter passing the file received iff it is readable.
698 * This corresponds the test <code>-readable</code>
699 * in the original find command.
700 */
701 public static final Filter CAN_READ = new Filter() {
702 public boolean pass(File file) {
703 return file.canRead();
704 }
705 };
706
707 /**
708 * Filter passing the file received iff it is writable.
709 * This corresponds the test <code>-writable</code>
710 * in the original find command.
711 */
712 public static final Filter CAN_WRITE = new Filter() {
713 public boolean pass(File file) {
714 return file.canWrite();
715 }
716 };
717
718 /**
719 * Filter passing the file received iff it is a regular file.
720 * This corresponds the test <code>-type f</code>
721 * in the original find command.
722 */
723 public static final Filter IS_FILE = new Filter() {
724 public boolean pass(File file) {
725 return file.isFile();
726 }
727 };
728
729 /**
730 * Filter passing the file received iff it is a regular file.
731 * This corresponds the test <code>-type d</code>
732 * in the original find command.
733 */
734 public static final Filter IS_DIR = new Filter() {
735 public boolean pass(File file) {
736 return file.isDirectory();
737 }
738 };
739
740
741
742 /* -------------------------------------------------------------------- *
743 * constructors and creator methods for Finders. *
744 * -------------------------------------------------------------------- */
745
746 /**
747 * This is declared <code>private</code> to prevent instantiation.
748 */
749 private Finder() {
750 }
751
752 /**
753 * Returns a basic finder, emitting the given file
754 * if it exists and is accessible and, recursively,
755 * if it is a directory all files therein.
756 * The ordering is the same as in original find-command.
757 * The expression <code>Finder.path(file)</code>
758 * corresponds with <code>find file</code>,
759 * except that the find command implicitly adds a print filter
760 * as defined in {@link #print(PrintStream)}.
761 * Note also that, unlike th original find,
762 * no wildcards are supported.
763 *
764 * @return
765 * an instance of {@link Finder.Primary}
766 */
767 public static Finder path(File file) {
768 return new Finder.Primary(file);
769 }
770
771 /**
772 * Adds a trivial filter passing all files received by this finder
773 * printing their full names to <code>str</code>.
774 */
775 public Finder print(PrintStream str) {
776 return new PrintFilter(this, str);
777 }
778
779 /**
780 * Returns a finder by attachig the given filter.
781 * For adding the most common filters, there are convenience methods.
782 */
783 public Finder add(Filter filter) {
784 return new Secondary(this, filter);
785 }
786
787 /**
788 * Convenience method: adds a name filter to this finder.
789 *
790 * @see #nameFilter(String)
791 */
792 public Finder name(String pattern) {
793 return add(nameFilter(pattern));
794 }
795
796 /**
797 * Convenience method: adds an execution filter to this finder.
798 * @return
799 * an instance of {@link Finder.Secondary}
800 * instantiated with a filter of type {@link Finder.ExecFilter}
801 *
802 * @see #execFilter(String[])
803 */
804 public Finder exec(String[] cmd) {
805 return add(execFilter(cmd));
806 }
807
808 /**
809 * Convenience method: adds a java execution filter to this finder.
810 * @return
811 * an instance of {@link Finder.Secondary}
812 * instantiated with a filter of type {@link Finder.ExecJavaFilter}
813 *
814 * @see #execJavaFilter(Callable)
815 */
816 public Finder execJava(Callable callable) {
817 return add(execJavaFilter(callable));
818 }
819
820 /**
821 * Convenience method:
822 * Returns a finder by attaching the inverse of the given filter.
823 * This corresponds the tests <code>\! expr1</code>
824 * in the original find command.
825 *
826 * @see Finder.Filter#not()
827 */
828 public Finder not(Filter filter) {
829 return add(filter.not());
830 }
831
832 /**
833 * Convenience method:
834 * Returns a finder by attaching an and-filter.
835 * This corresponds the tests <code>\! expr1</code>
836 * in the original find command.
837 *
838 * @see Finder.Filter#and(Filter[])
839 */
840 public Finder and(Filter[] filters) {
841 return add(Filter.and(filters));
842 }
843
844 /**
845 * Convenience method:
846 * Returns a finder by attaching an or-filter.
847 * This corresponds the tests <code>\! expr1</code>
848 * in the original find command.
849 *
850 * @see Finder.Filter#or(Filter[])
851 */
852 public Finder or(Filter[] filters) {
853 return add(Filter.or(filters));
854 }
855
856 /* -------------------------------------------------------------------- *
857 * constructors and creator methods for Filters. *
858 * -------------------------------------------------------------------- */
859
860 /**
861 * Returns a filter passing a file
862 * iff its (short) names of which
863 * match the regular expression <code>pattern</code>.
864 *
865 * @return
866 * an instance of {@link Finder.Secondary}
867 * instantiated with a filter of type {@link Finder.NameFilter}
868 */
869 public static Filter nameFilter(String pattern) {
870 return new NameFilter(pattern);
871 }
872
873 /**
874 * Returns a filter invoking a shell command:
875 * just passes the files received by this finder further
876 * if the command succeeds according to its return value.
877 * Example in original find-terminology:
878 * <code>find . -exec grep "pattern without quotes" {} \; -print</code>.
879 * Here, the portion specifying execution is
880 * <code>-exec grep "pattern without quotes" {} \;</code>:
881 * It starts with <code>-exec</code> and ends with <code>\;</code>.
882 * Note that the command <code>grep</code>
883 * has arguments <code>"pattern without quotes"</code> and <code>{}</code>.
884 * The first argument must be quoted because it contains whitespace
885 * and would otherwise be interpreted as three arguments.
886 * The second argument <code>{}</code>
887 * is silently replaced by the file name
888 * received by the preceeding portion of the find-command.
889 *
890 * @param cmd
891 * a shell command with its arguments.
892 * The 0th entry is the command itself and the others are arguments.
893 * Be aware when escape sequences are needed.
894 * Quotes ensuring that an argument is interpreted as a unit
895 * <em>must</em> be omitted.
896 * <p>
897 * In the original find command,
898 * <code>{}</code> represents arguments to be replaced by the file name
899 * received by the preceding portion of the pipe.
900 * These must be represented by the object instance {@link #EXEC_ARG}
901 * which is also a string <code>{}</code>
902 * but it is wrong to put an equivalent string as e.g.
903 * <code>new String("{}")</code>.
904 * <p>
905 * For example to obtain a grep use
906 * <code>new String{} {"grep", "pattern without quotes", EXEC_ARG}</code>
907 * @return
908 * a filter of type {@link Finder.ExecFilter}
909 * @see #execJavaFilter(Callable)
910 */
911 public static Filter execFilter(String[] cmd) {
912 return new ExecFilter(cmd);
913 }
914
915 /**
916 * Returns a filter invoking method {@link Finder.Callable#call(File)}
917 * of the given <code>callable</code>.
918 *
919 * @param callable
920 * A callable defining a filter.
921 * If parameters are required,
922 * these must be given when instantiating the callable
923 * or later using setter methods.
924 * @return
925 * a filter of type {@link Finder.ExecFilter}
926 * @see #execFilter(String[])
927 */
928 public static Filter execJavaFilter(Callable callable) {
929 return new ExecJavaFilter(callable);
930 }
931
932
933 /* -------------------------------------------------------------------- *
934 * abstract methods. *
935 * -------------------------------------------------------------------- */
936
937 /**
938 * Returns whether this Finder can emit another file.
939 *
940 * @see #next()
941 */
942 public abstract boolean hasNext();
943
944 /**
945 * Returns the next file this finder can emit.
946 * This does not throw an exception iff {@link #hasNext()} returns true.
947 */
948 public abstract File next();
949
950 public static void main(String[] args) {
951
952 File file = new File(args[0]);
953
954 Finder finder = Finder.path(file)
955 .name(".*\\.m")
956 // .exec(new String[] {"egrep", "matlab", EXEC_ARG})
957 .not(execFilter(new String[] {"grep", "'", EXEC_ARG}))
958 // .or(new Filter[] {
959 // execFilter(new String[] {"grep", "enum", EXEC_ARG}),
960 // execFilter(new String[] {"grep", "interface", EXEC_ARG})
961 // }
962 // )
963 .print(System.out);
964
965 // Finder finder = Finder.path(file);
966 // Multiplexer mult = finder.multiplexer();
967 // Finder negFilter = mult.finder()
968 // .exec(new String[] {"grep", "'", EXEC_ARG});
969 // finder = mult.finder().neg(negFilter);
970
971 while (finder.hasNext()) {
972 finder.next();
973 // System.out.println(""+finder.next());
974 }
975 }
976
977 }