Passed
Push — master ( a862c8...fef60d )
by Siad
05:54
created

DirectoryScanner   F

Complexity

Total Complexity 101

Size/Duplication

Total Lines 843
Duplicated Lines 0 %

Test Coverage

Coverage 71.73%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 256
c 5
b 1
f 0
dl 0
loc 843
ccs 170
cts 237
cp 0.7173
rs 2
wmc 101

36 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A resetDefaultExcludes() 0 3 1
A getDefaultExcludes() 0 3 1
A setCaseSensitive() 0 3 2
A setBasedir() 0 4 1
A matchPath() 0 3 1
A getBasedir() 0 3 1
A match() 0 3 1
A matchPatternStart() 0 3 1
A setErrorOnMissingDir() 0 3 1
A isExcluded() 0 9 3
A getIncludedDirectoriesCount() 0 6 2
A addDefaultExcludes() 0 8 2
A getDeselectedFiles() 0 5 1
A getNotIncludedDirectories() 0 5 1
A isSelected() 0 17 5
A getExcludedDirectories() 0 5 1
A setExpandSymbolicLinks() 0 3 1
A setExcludes() 0 12 5
A getExcludedFiles() 0 5 1
A isIncluded() 0 9 3
A couldHoldIncluded() 0 9 3
A getIncludedFiles() 0 9 2
A getDeselectedDirectories() 0 5 1
A listDir() 0 3 1
A setIncludes() 0 12 5
A setSelectors() 0 3 1
A isEverythingIncluded() 0 3 1
D scandir() 0 79 24
A getNotIncludedFiles() 0 5 1
A getIncludedFilesCount() 0 6 2
A slowScan() 0 23 6
A getIncludedDirectories() 0 9 2
B scan() 0 59 11
A removeDefaultExclude() 0 11 2
A addDefaultExclude() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like DirectoryScanner often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DirectoryScanner, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
/**
21
 * Class for scanning a directory for files/directories that match a certain
22
 * criteria.
23
 *
24
 * These criteria consist of a set of include and exclude patterns. With these
25
 * patterns, you can select which files you want to have included, and which
26
 * files you want to have excluded.
27
 *
28
 * The idea is simple. A given directory is recursively scanned for all files
29
 * and directories. Each file/directory is matched against a set of include
30
 * and exclude patterns. Only files/directories that match at least one
31
 * pattern of the include pattern list, and don't match a pattern of the
32
 * exclude pattern list will be placed in the list of files/directories found.
33
 *
34
 * When no list of include patterns is supplied, "**" will be used, which
35
 * means that everything will be matched. When no list of exclude patterns is
36
 * supplied, an empty list is used, such that nothing will be excluded.
37
 *
38
 * The pattern matching is done as follows:
39
 * The name to be matched is split up in path segments. A path segment is the
40
 * name of a directory or file, which is bounded by DIRECTORY_SEPARATOR
41
 * ('/' under UNIX, '\' under Windows).
42
 * E.g. "abc/def/ghi/xyz.php" is split up in the segments "abc", "def", "ghi"
43
 * and "xyz.php".
44
 * The same is done for the pattern against which should be matched.
45
 *
46
 * Then the segments of the name and the pattern will be matched against each
47
 * other. When '**' is used for a path segment in the pattern, then it matches
48
 * zero or more path segments of the name.
49
 *
50
 * There are special case regarding the use of DIRECTORY_SEPARATOR at
51
 * the beginning of the pattern and the string to match:
52
 * When a pattern starts with a DIRECTORY_SEPARATOR, the string
53
 * to match must also start with a DIRECTORY_SEPARATOR.
54
 * When a pattern does not start with a DIRECTORY_SEPARATOR, the
55
 * string to match may not start with a DIRECTORY_SEPARATOR.
56
 * When one of these rules is not obeyed, the string will not
57
 * match.
58
 *
59
 * When a name path segment is matched against a pattern path segment, the
60
 * following special characters can be used:
61
 *   '*' matches zero or more characters,
62
 *   '?' matches one character.
63
 *
64
 * Examples:
65
 *
66
 * "**\*.php" matches all .php files/dirs in a directory tree.
67
 *
68
 * "test\a??.php" matches all files/dirs which start with an 'a', then two
69
 * more characters and then ".php", in a directory called test.
70
 *
71
 * "**" matches everything in a directory tree.
72
 *
73
 * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where
74
 * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
75
 *
76
 * Case sensitivity may be turned off if necessary.  By default, it is
77
 * turned on.
78
 *
79
 * Example of usage:
80
 *   $ds = new DirectroyScanner();
81
 *   $includes = array("**\*.php");
82
 *   $excludes = array("modules\*\**");
83
 *   $ds->SetIncludes($includes);
84
 *   $ds->SetExcludes($excludes);
85
 *   $ds->SetBasedir("test");
86
 *   $ds->SetCaseSensitive(true);
87
 *   $ds->Scan();
88
 *
89
 *   print("FILES:");
90
 *   $files = ds->GetIncludedFiles();
91
 *   for ($i = 0; $i < count($files);$i++) {
92
 *     println("$files[$i]\n");
93
 *   }
94
 *
95
 * This will scan a directory called test for .php files, but excludes all
96
 * .php files in all directories under a directory called "modules"
97
 *
98
 * This class is complete preg/ereg free port of the Java class
99
 * org.apache.tools.ant.DirectoryScanner. Even functions that use preg/ereg
100
 * internally (like split()) are not used. Only the _fast_ string functions
101
 * and comparison operators (=== !=== etc) are used for matching and tokenizing.
102
 *
103
 * @author Arnout J. Kuiper, [email protected]
104
 * @author Magesh Umasankar, [email protected]
105
 * @author Andreas Aderhold, [email protected]
106
 *
107
 * @package phing.util
108
 */
109
class DirectoryScanner implements FileScanner, SelectorScanner
110
{
111
    /**
112
     * default set of excludes
113
     */
114
    protected static $DEFAULTEXCLUDES = [
115
        "**/*~",
116
        "**/#*#",
117
        "**/.#*",
118
        "**/%*%",
119
        "**/CVS",
120
        "**/CVS/**",
121
        "**/.cvsignore",
122
        "**/SCCS",
123
        "**/SCCS/**",
124
        "**/vssver.scc",
125
        "**/.svn",
126
        "**/.svn/**",
127
        "**/._*",
128
        "**/.DS_Store",
129
        "**/.darcs",
130
        "**/.darcs/**",
131
        "**/.git",
132
        "**/.git/**",
133
        "**/.gitattributes",
134
        "**/.gitignore",
135
        "**/.gitmodules",
136
        "**/.hg",
137
        "**/.hg/**",
138
        "**/.hgignore",
139
        "**/.hgsub",
140
        "**/.hgsubstate",
141
        "**/.hgtags",
142
        "**/.bzr",
143
        "**/.bzr/**",
144
        "**/.bzrignore",
145
    ];
146
147
    private static $defaultExcludeList = [];
148
149
    /**
150
     * The base directory which should be scanned.
151
     *
152
     * @var string
153
     */
154
    protected $basedir;
155
156
    /**
157
     * The patterns for the files that should be included.
158
     *
159
     * @var string[]
160
     */
161
    protected $includes = null;
162
163
    /**
164
     * The patterns for the files that should be excluded.
165
     *
166
     * @var string[]
167
     */
168
    protected $excludes = null;
169
170
    /**
171
     * Whether to expand/dereference symbolic links, default is false
172
     *
173
     * @var bool
174
     */
175
    protected $expandSymbolicLinks = false;
176
177
    /**
178
     * The files that where found and matched at least one includes, and matched
179
     * no excludes.
180
     */
181
    protected $filesIncluded;
182
183
    /**
184
     * The files that where found and did not match any includes. Trie
185
     */
186
    protected $filesNotIncluded;
187
188
    /**
189
     * The files that where found and matched at least one includes, and also
190
     * matched at least one excludes. Trie object.
191
     */
192
    protected $filesExcluded;
193
194
    /**
195
     * The directories that where found and matched at least one includes, and
196
     * matched no excludes.
197
     */
198
    protected $dirsIncluded;
199
200
    /**
201
     * The directories that where found and did not match any includes.
202
     */
203
    protected $dirsNotIncluded;
204
205
    /**
206
     * The files that where found and matched at least one includes, and also
207
     * matched at least one excludes.
208
     */
209
    protected $dirsExcluded;
210
211
    /**
212
     * Have the vars holding our results been built by a slow scan?
213
     */
214
    protected $haveSlowResults = false;
215
216
    /**
217
     * Should the file system be treated as a case sensitive one?
218
     */
219
    protected $isCaseSensitive = true;
220
    /**
221
     * Whether a missing base directory is an error.
222
     */
223
    protected $errorOnMissingDir = false;
224
225
    /**
226
     * @var FileSelector[] Selectors
227
     */
228
    protected $selectorsList = null;
229
230
    protected $filesDeselected;
231
    protected $dirsDeselected;
232
233
    /**
234
     * if there are no deselected files
235
     */
236
    protected $everythingIncluded = true;
237
238 127
    public function __construct()
239
    {
240 127
        if (empty(self::$defaultExcludeList)) {
241
            self::$defaultExcludeList = self::$DEFAULTEXCLUDES;
242
        }
243 127
    }
244
245
    /**
246
     * Does the path match the start of this pattern up to the first "**".
247
     * This is a static mehtod and should always be called static
248
     *
249
     * This is not a general purpose test and should only be used if you
250
     * can live with false positives.
251
     *
252
     * pattern=**\a and str=b will yield true.
253
     *
254
     * @param  string $pattern the pattern to match against
255
     * @param  string $str the string (path) to match
256
     * @param  bool $isCaseSensitive must matches be case sensitive?
257
     * @return boolean true if matches, otherwise false
258
     */
259 36
    public function matchPatternStart($pattern, $str, $isCaseSensitive = true)
260
    {
261 36
        return SelectorUtils::matchPatternStart($pattern, $str, $isCaseSensitive);
262
    }
263
264
    /**
265
     * Matches a path against a pattern.
266
     *
267
     * @param string $pattern the (non-null) pattern to match against
268
     * @param string $str the (non-null) string (path) to match
269
     * @param bool $isCaseSensitive must a case sensitive match be done?
270
     *
271
     * @return bool true when the pattern matches against the string.
272
     *              false otherwise.
273
     */
274 126
    public function matchPath($pattern, $str, $isCaseSensitive = true)
275
    {
276 126
        return SelectorUtils::matchPath($pattern, $str, $isCaseSensitive);
277
    }
278
279
    /**
280
     * Matches a string against a pattern. The pattern contains two special
281
     * characters:
282
     * '*' which means zero or more characters,
283
     * '?' which means one and only one character.
284
     *
285
     * @param string $pattern pattern to match against
286
     * @param string $str string that must be matched against the
287
     *                                pattern
288
     * @param bool $isCaseSensitive
289
     *
290
     * @return boolean true when the string matches against the pattern,
291
     *                 false otherwise.
292
     */
293
    public function match($pattern, $str, $isCaseSensitive = true)
294
    {
295
        return SelectorUtils::match($pattern, $str, $isCaseSensitive);
296
    }
297
298
    /**
299
     * Get the list of patterns that should be excluded by default.
300
     *
301
     * @return string[] An array of <code>String</code> based on the current
302
     *         contents of the <code>defaultExcludes</code>
303
     *         <code>Set</code>.
304
     */
305 107
    public static function getDefaultExcludes()
306
    {
307 107
        return self::$defaultExcludeList;
308
    }
309
310
    /**
311
     * Add a pattern to the default excludes unless it is already a
312
     * default exclude.
313
     *
314
     * @param  string $s A string to add as an exclude pattern.
315
     * @return boolean   <code>true</code> if the string was added;
316
     *                   <code>false</code> if it already existed.
317
     */
318
    public static function addDefaultExclude($s)
319
    {
320
        if (!in_array($s, self::$defaultExcludeList)) {
321
            $return = true;
322
            self::$defaultExcludeList[] = $s;
323
        } else {
324
            $return = false;
325
        }
326
327
        return $return;
328
    }
329
330
    /**
331
     * Remove a string if it is a default exclude.
332
     *
333
     * @param  string $s The string to attempt to remove.
334
     * @return boolean    <code>true</code> if <code>s</code> was a default
335
     *                    exclude (and thus was removed);
336
     *                    <code>false</code> if <code>s</code> was not
337
     *                    in the default excludes list to begin with.
338
     */
339
    public static function removeDefaultExclude($s)
340
    {
341
        $key = array_search($s, self::$defaultExcludeList);
342
343
        if ($key !== false) {
344
            unset(self::$defaultExcludeList[$key]);
345
            self::$defaultExcludeList = array_values(self::$defaultExcludeList);
346
            return true;
347
        }
348
349
        return false;
350
    }
351
352
    /**
353
     * Go back to the hardwired default exclude patterns.
354
     */
355 3
    public static function resetDefaultExcludes()
356
    {
357 3
        self::$defaultExcludeList = self::$DEFAULTEXCLUDES;
358 3
    }
359
360
    /**
361
     * Sets the basedir for scanning. This is the directory that is scanned
362
     * recursively. All '/' and '\' characters are replaced by
363
     * DIRECTORY_SEPARATOR
364
     *
365
     * @param string $basedir the (non-null) basedir for scanning
366
     */
367 127
    public function setBasedir($basedir)
368
    {
369 127
        $basedir = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $basedir);
370 127
        $this->basedir = $basedir;
371 127
    }
372
373
    /**
374
     * Gets the basedir that is used for scanning. This is the directory that
375
     * is scanned recursively.
376
     *
377
     * @return string the basedir that is used for scanning
378
     */
379 21
    public function getBasedir()
380
    {
381 21
        return $this->basedir;
382
    }
383
384
    /**
385
     * Sets the case sensitivity of the file system
386
     *
387
     * @param bool $isCaseSensitive specifies if the filesystem is case sensitive
388
     */
389 111
    public function setCaseSensitive($isCaseSensitive)
390
    {
391 111
        $this->isCaseSensitive = ($isCaseSensitive) ? true : false;
392 111
    }
393
394
    /**
395
     * Sets whether or not a missing base directory is an error
396
     *
397
     * @param bool $errorOnMissingDir whether or not a missing base directory
398
     *                        is an error
399
     */
400 110
    public function setErrorOnMissingDir($errorOnMissingDir)
401
    {
402 110
        $this->errorOnMissingDir = $errorOnMissingDir;
403 110
    }
404
405
    /**
406
     * Sets the set of include patterns to use. All '/' and '\' characters are
407
     * replaced by DIRECTORY_SEPARATOR. So the separator used need
408
     * not match DIRECTORY_SEPARATOR.
409
     *
410
     * When a pattern ends with a '/' or '\', "**" is appended.
411
     *
412
     * @param array $includes list of include patterns
413
     */
414 123
    public function setIncludes($includes = [])
415
    {
416 123
        if (empty($includes) || null === $includes) {
417 33
            $this->includes = null;
418
        } else {
419 90
            $numIncludes = count($includes);
420 90
            for ($i = 0; $i < $numIncludes; $i++) {
421 90
                $pattern = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $includes[$i]);
422 90
                if (StringHelper::endsWith(DIRECTORY_SEPARATOR, $pattern)) {
423 2
                    $pattern .= "**";
424
                }
425 90
                $this->includes[] = $pattern;
426
            }
427
        }
428 123
    }
429
430
    /**
431
     * Sets the set of exclude patterns to use. All '/' and '\' characters are
432
     * replaced by <code>File.separatorChar</code>. So the separator used need
433
     * not match <code>File.separatorChar</code>.
434
     *
435
     * When a pattern ends with a '/' or '\', "**" is appended.
436
     *
437
     * @param array $excludes list of exclude patterns
438
     */
439 117
    public function setExcludes($excludes = [])
440
    {
441 117
        if (empty($excludes) || null === $excludes) {
442 108
            $this->excludes = null;
443
        } else {
444 10
            $numExcludes = count($excludes);
445 10
            for ($i = 0; $i < $numExcludes; $i++) {
446 10
                $pattern = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $excludes[$i]);
447 10
                if (StringHelper::endsWith(DIRECTORY_SEPARATOR, $pattern)) {
448 1
                    $pattern .= "**";
449
                }
450 10
                $this->excludes[] = $pattern;
451
            }
452
        }
453 117
    }
454
455
    /**
456
     * Sets whether to expand/dereference symbolic links
457
     *
458
     * @param boolean $expandSymbolicLinks
459
     */
460 109
    public function setExpandSymbolicLinks($expandSymbolicLinks)
461
    {
462 109
        $this->expandSymbolicLinks = $expandSymbolicLinks;
463 109
    }
464
465
    /**
466
     * Scans the base directory for files that match at least one include
467
     * pattern, and don't match any exclude patterns.
468
     */
469 127
    public function scan()
470
    {
471 127
        if (empty($this->basedir)) {
472
            return false;
473
        }
474
475 127
        $exception = null;
476
477 127
        if (!@file_exists($this->basedir)) {
478 1
            if ($this->errorOnMissingDir) {
479 1
                $exception = new BuildException(
480 1
                    "basedir  $this->basedir does not exist."
481
                );
482
            } else {
483 1
                return false;
484
            }
485 126
        } elseif (!@is_dir($this->basedir)) {
486
            $exception = new BuildException(
487
                "basedir $this->basedir is not a directory."
488
            );
489
        }
490 127
        if ($exception !== null) {
491 1
            throw $exception;
492
        }
493
494 126
        if ($this->includes === null) {
495
            // No includes supplied, so set it to 'matches all'
496 36
            $this->includes = ["**"];
497
        }
498 126
        if (null === $this->excludes) {
499 11
            $this->excludes = [];
500
        }
501
502 126
        $this->filesIncluded = [];
503 126
        $this->filesNotIncluded = [];
504 126
        $this->filesExcluded = [];
505 126
        $this->dirsIncluded = [];
506 126
        $this->dirsNotIncluded = [];
507 126
        $this->dirsExcluded = [];
508 126
        $this->dirsDeselected = [];
509 126
        $this->filesDeselected = [];
510
511 126
        if ($this->isIncluded("")) {
512 50
            if (!$this->isExcluded("")) {
513 49
                if ($this->isSelected("", $this->basedir)) {
514 47
                    $this->dirsIncluded[] = "";
515
                } else {
516 49
                    $this->dirsDeselected[] = "";
517
                }
518
            } else {
519 50
                $this->dirsExcluded[] = "";
520
            }
521
        } else {
522 76
            $this->dirsNotIncluded[] = "";
523
        }
524
525 126
        $this->scandir($this->basedir, "", true);
526
527 126
        return true;
528
    }
529
530
    /**
531
     * Toplevel invocation for the scan.
532
     *
533
     * Returns immediately if a slow scan has already been requested.
534
     */
535
    protected function slowScan()
536
    {
537
        if ($this->haveSlowResults) {
538
            return;
539
        }
540
541
        // copy trie object add CopyInto() method
542
        $excl = $this->dirsExcluded;
543
        $notIncl = $this->dirsNotIncluded;
544
545
        for ($i = 0, $_i = count($excl); $i < $_i; $i++) {
546
            if (!$this->couldHoldIncluded($excl[$i])) {
547
                $this->scandir($this->basedir . $excl[$i], $excl[$i] . DIRECTORY_SEPARATOR, false);
548
            }
549
        }
550
551
        for ($i = 0, $_i = count($notIncl); $i < $_i; $i++) {
552
            if (!$this->couldHoldIncluded($notIncl[$i])) {
553
                $this->scandir($this->basedir . $notIncl[$i], $notIncl[$i] . DIRECTORY_SEPARATOR, false);
554
            }
555
        }
556
557
        $this->haveSlowResults = true;
558
    }
559
560
    /**
561
     * Lists contents of a given directory and returns array with entries
562
     *
563
     * @param string $_dir directory to list contents for
564
     *
565
     * @return array directory entries
566
     * @author Albert Lash, [email protected]
567
     */
568 126
    public function listDir($_dir)
569
    {
570 126
        return (new PhingFile($_dir))->listDir();
571
    }
572
573
    /**
574
     * Scans the passed dir for files and directories. Found files and
575
     * directories are placed in their respective collections, based on the
576
     * matching of includes and excludes. When a directory is found, it is
577
     * scanned recursively.
578
     *
579
     * @param string $_rootdir the directory to scan
580
     * @param string $_vpath the path relative to the basedir (needed to prevent
581
     *                         problems with an absolute path when using dir)
582
     * @param bool $_fast
583
     *
584
     * @see #filesIncluded
585
     * @see #filesNotIncluded
586
     * @see #filesExcluded
587
     * @see #dirsIncluded
588
     * @see #dirsNotIncluded
589
     * @see #dirsExcluded
590
     */
591 126
    private function scandir($_rootdir, $_vpath, $_fast)
592
    {
593 126
        if (!is_readable($_rootdir)) {
594
            return;
595
        }
596
597 126
        $newfiles = $this->listDir($_rootdir);
598
599 126
        for ($i = 0, $_i = count($newfiles); $i < $_i; $i++) {
600 126
            $file = $_rootdir . DIRECTORY_SEPARATOR . $newfiles[$i];
601 126
            $name = $_vpath . $newfiles[$i];
602
603 126
            if (@is_link($file) && !$this->expandSymbolicLinks) {
604 6
                if ($this->isIncluded($name)) {
605 2
                    if (!$this->isExcluded($name)) {
606 2
                        if ($this->isSelected($name, $file)) {
607 2
                            $this->filesIncluded[] = $name;
608
                        } else {
609
                            $this->everythingIncluded = false;
610 2
                            $this->filesDeselected[] = $name;
611
                        }
612
                    } else {
613
                        $this->everythingIncluded = false;
614 2
                        $this->filesExcluded[] = $name;
615
                    }
616
                } else {
617 4
                    $this->everythingIncluded = false;
618 6
                    $this->filesNotIncluded[] = $name;
619
                }
620
            } else {
621 124
                if (@is_dir($file)) {
622 48
                    if ($this->isIncluded($name)) {
623 36
                        if (!$this->isExcluded($name)) {
624 31
                            if ($this->isSelected($name, $file)) {
625 31
                                $this->dirsIncluded[] = $name;
626 31
                                if ($_fast) {
627 31
                                    $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
628
                                }
629
                            } else {
630
                                $this->everythingIncluded = false;
631
                                $this->dirsDeselected[] = $name;
632
                                if ($_fast && $this->couldHoldIncluded($name)) {
633 31
                                    $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
634
                                }
635
                            }
636
                        } else {
637 14
                            $this->everythingIncluded = false;
638 14
                            $this->dirsExcluded[] = $name;
639 14
                            if ($_fast && $this->couldHoldIncluded($name)) {
640 36
                                $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
641
                            }
642
                        }
643
                    } else {
644 24
                        $this->everythingIncluded = false;
645 24
                        $this->dirsNotIncluded[] = $name;
646 24
                        if ($_fast && $this->couldHoldIncluded($name)) {
647 10
                            $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
648
                        }
649
                    }
650
651 48
                    if (!$_fast) {
652 48
                        $this->scandir($file, $name . DIRECTORY_SEPARATOR, $_fast);
653
                    }
654 120
                } elseif (@is_file($file)) {
655 120
                    if ($this->isIncluded($name)) {
656 108
                        if (!$this->isExcluded($name)) {
657 102
                            if ($this->isSelected($name, $file)) {
658 101
                                $this->filesIncluded[] = $name;
659
                            } else {
660 3
                                $this->everythingIncluded = false;
661 102
                                $this->filesDeselected[] = $name;
662
                            }
663
                        } else {
664 17
                            $this->everythingIncluded = false;
665 108
                            $this->filesExcluded[] = $name;
666
                        }
667
                    } else {
668 57
                        $this->everythingIncluded = false;
669 57
                        $this->filesNotIncluded[] = $name;
670
                    }
671
                }
672
            }
673
        }
674 126
    }
675
676
    /**
677
     * Tests whether a name matches against at least one include pattern.
678
     *
679
     * @param  string $_name the name to match
680
     * @return bool <code>true</code> when the name matches against at least one
681
     */
682 126
    protected function isIncluded($_name)
683
    {
684 126
        for ($i = 0, $_i = count($this->includes); $i < $_i; $i++) {
685 126
            if ($this->matchPath($this->includes[$i], $_name, $this->isCaseSensitive)) {
686 123
                return true;
687
            }
688
        }
689
690 76
        return false;
691
    }
692
693
    /**
694
     * Tests whether a name matches the start of at least one include pattern.
695
     *
696
     * @param  string $_name the name to match
697
     * @return bool <code>true</code> when the name matches against at least one
698
     *                           include pattern, <code>false</code> otherwise.
699
     */
700 36
    protected function couldHoldIncluded($_name)
701
    {
702 36
        for ($i = 0, $includesCount = count($this->includes); $i < $includesCount; $i++) {
703 36
            if ($this->matchPatternStart($this->includes[$i], $_name, $this->isCaseSensitive)) {
704 22
                return true;
705
            }
706
        }
707
708 18
        return false;
709
    }
710
711
    /**
712
     * Tests whether a name matches against at least one exclude pattern.
713
     *
714
     * @param  string $_name the name to match
715
     * @return bool <code>true</code> when the name matches against at least one
716
     *                           exclude pattern, <code>false</code> otherwise.
717
     */
718 123
    protected function isExcluded($_name)
719
    {
720 123
        for ($i = 0, $excludesCount = count($this->excludes); $i < $excludesCount; $i++) {
721 114
            if ($this->matchPath($this->excludes[$i], $_name, $this->isCaseSensitive)) {
722 17
                return true;
723
            }
724
        }
725
726 120
        return false;
727
    }
728
729
    /**
730
     * Return the count of included files.
731
     *
732
     * @return int
733
     *
734
     * @throws UnexpectedValueException
735
     */
736
    public function getIncludedFilesCount(): int
737
    {
738
        if ($this->filesIncluded === null) {
739
            throw new UnexpectedValueException('Must call scan() first');
740
        }
741
        return count($this->filesIncluded);
742
    }
743
744
    /**
745
     * Get the names of the files that matched at least one of the include
746
     * patterns, and matched none of the exclude patterns.
747
     * The names are relative to the basedir.
748
     *
749
     * @return array names of the files
750
     * @throws \UnexpectedValueException
751
     */
752 122
    public function getIncludedFiles(): array
753
    {
754 122
        if ($this->filesIncluded === null) {
755
            throw new UnexpectedValueException('Must call scan() first');
756
        }
757
758 122
        sort($this->filesIncluded);
759
760 122
        return $this->filesIncluded;
761
    }
762
763
    /**
764
     * Get the names of the files that matched at none of the include patterns.
765
     * The names are relative to the basedir.
766
     *
767
     * @return array the names of the files
768
     */
769
    public function getNotIncludedFiles()
770
    {
771
        $this->slowScan();
772
773
        return $this->filesNotIncluded;
774
    }
775
776
    /**
777
     * Get the names of the files that matched at least one of the include
778
     * patterns, an matched also at least one of the exclude patterns.
779
     * The names are relative to the basedir.
780
     *
781
     * @return array the names of the files
782
     */
783
784
    public function getExcludedFiles()
785
    {
786
        $this->slowScan();
787
788
        return $this->filesExcluded;
789
    }
790
791
    /**
792
     * <p>Returns the names of the files which were selected out and
793
     * therefore not ultimately included.</p>
794
     *
795
     * <p>The names are relative to the base directory. This involves
796
     * performing a slow scan if one has not already been completed.</p>
797
     *
798
     * @return array the names of the files which were deselected.
799
     *
800
     * @see #slowScan
801
     */
802
    public function getDeselectedFiles()
803
    {
804
        $this->slowScan();
805
806
        return $this->filesDeselected;
807
    }
808
809
    /**
810
     * Get the names of the directories that matched at least one of the include
811
     * patterns, an matched none of the exclude patterns.
812
     * The names are relative to the basedir.
813
     *
814
     * @return array the names of the directories
815
     * @throws \UnexpectedValueException
816
     */
817
818 70
    public function getIncludedDirectories()
819
    {
820 70
        if ($this->dirsIncluded === null) {
821
            throw new UnexpectedValueException('Must call scan() first');
822
        }
823
824 70
        sort($this->dirsIncluded);
825
826 70
        return $this->dirsIncluded;
827
    }
828
829
    /**
830
     * Return the count of included directories.
831
     *
832
     * @return int
833
     *
834
     * @throws UnexpectedValueException
835
     */
836
    public function getIncludedDirectoriesCount(): int
837
    {
838
        if ($this->dirsIncluded === null) {
839
            throw new UnexpectedValueException('Must call scan() first');
840
        }
841
        return count($this->dirsIncluded);
842
    }
843
844
    /**
845
     * Get the names of the directories that matched at none of the include
846
     * patterns.
847
     * The names are relative to the basedir.
848
     *
849
     * @return array the names of the directories
850
     */
851
    public function getNotIncludedDirectories()
852
    {
853
        $this->slowScan();
854
855
        return $this->dirsNotIncluded;
856
    }
857
858
    /**
859
     * <p>Returns the names of the directories which were selected out and
860
     * therefore not ultimately included.</p>
861
     *
862
     * <p>The names are relative to the base directory. This involves
863
     * performing a slow scan if one has not already been completed.</p>
864
     *
865
     * @return array the names of the directories which were deselected.
866
     *
867
     * @see #slowScan
868
     */
869
    public function getDeselectedDirectories()
870
    {
871
        $this->slowScan();
872
873
        return $this->dirsDeselected;
874
    }
875
876
    /**
877
     * Get the names of the directories that matched at least one of the include
878
     * patterns, an matched also at least one of the exclude patterns.
879
     * The names are relative to the basedir.
880
     *
881
     * @return array the names of the directories
882
     */
883
    public function getExcludedDirectories()
884
    {
885
        $this->slowScan();
886
887
        return $this->dirsExcluded;
888
    }
889
890
    /**
891
     * Adds the array with default exclusions to the current exclusions set.
892
     */
893 107
    public function addDefaultExcludes()
894
    {
895 107
        $defaultExcludesTemp = self::getDefaultExcludes();
896 107
        $newExcludes = [];
897 107
        foreach ($defaultExcludesTemp as $temp) {
898 107
            $newExcludes[] = str_replace(['\\', '/'], FileUtils::getSeparator(), $temp);
899
        }
900 107
        $this->excludes = array_merge((array) $this->excludes, $newExcludes);
901 107
    }
902
903
    /**
904
     * Sets the selectors that will select the filelist.
905
     *
906
     * @param array $selectors the selectors to be invoked on a scan
907
     */
908 109
    public function setSelectors($selectors)
909
    {
910 109
        $this->selectorsList = $selectors;
911 109
    }
912
913
    /**
914
     * Returns whether or not the scanner has included all the files or
915
     * directories it has come across so far.
916
     *
917
     * @return bool <code>true</code> if all files and directories which have
918
     */
919 24
    public function isEverythingIncluded()
920
    {
921 24
        return $this->everythingIncluded;
922
    }
923
924
    /**
925
     * Tests whether a name should be selected.
926
     *
927
     * @param  string $name The filename to check for selecting.
928
     * @param  string $file The full file path.
929
     * @return boolean False when the selectors says that the file
930
     *                      should not be selected, True otherwise.
931
     * @throws \BuildException
932
     * @throws \IOException
933
     * @throws NullPointerException
934
     */
935 120
    protected function isSelected($name, $file)
936
    {
937 120
        if ($this->selectorsList !== null) {
938 107
            $basedir = new PhingFile($this->basedir);
939 107
            $file = new PhingFile($file);
940 107
            if (!$file->canRead()) {
941
                return false;
942
            }
943
944 107
            foreach ($this->selectorsList as $selector) {
945 5
                if (!$selector->isSelected($basedir, $name, $file)) {
946 4
                    return false;
947
                }
948
            }
949
        }
950
951 119
        return true;
952
    }
953
}
954