Passed
Push — master ( 28a3ce...b9357e )
by Siad
05:23
created

ModifiedSelector   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 472
Duplicated Lines 0 %

Test Coverage

Coverage 76.6%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 165
c 1
b 0
f 0
dl 0
loc 472
ccs 144
cts 188
cp 0.766
rs 2.56
wmc 73

29 Methods

Rating   Name   Duplication   Size   Complexity  
A saveCache() 0 5 2
A getModified() 0 3 1
A getDelayUpdate() 0 3 1
A setComparatorClass() 0 3 1
A taskFinished() 0 4 2
A setSeldirs() 0 3 1
A isSelected() 0 29 6
A targetStarted() 0 2 1
A taskStarted() 0 2 1
A tryToSetAParameter() 0 7 2
A buildFinished() 0 4 2
A setCacheClass() 0 3 1
A buildStarted() 0 2 1
A messageLogged() 0 2 1
A setCache() 0 3 1
A verifySettings() 0 11 5
A targetFinished() 0 4 2
A setParameters() 0 5 2
A addParam() 0 3 1
A getCache() 0 3 1
A setModified() 0 3 1
A setAlgorithmClass() 0 3 1
A __toString() 0 10 3
B useParameter() 0 27 10
F configure() 0 96 19
A setUpdate() 0 3 1
A setDelayUpdate() 0 3 1
A setAlgorithm() 0 3 1
A setComparator() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ModifiedSelector 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 ModifiedSelector, 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
 * @author  Siad Ardroumli <[email protected]>
22
 * @package phing.types.selectors
23
 */
24
class ModifiedSelector extends BaseExtendSelector implements BuildListener
25
{
26
    private const CACHE_PREFIX = "cache.";
27
    private const ALGORITHM_PREFIX = "algorithm.";
28
    private const COMPARATOR_PREFIX = "comparator.";
29
30
    /** Cache name for later instantiation. */
31
    private $cacheName;
32
33
    /** User specified classname for Cache. */
34
    private $cacheClass;
35
36
    /** Algorithm name for later instantiation. */
37
    private $algoName;
38
39
    /** User specified classname for Algorithm. */
40
    private $algorithmClass;
41
42
    /** Comparator name for later instantiation. */
43
    private $compName;
44
45
    /** User specified classname for Comparator. */
46
    private $comparatorClass;
47
48
    private $update = true;
49
    private $selectDirectories = true;
50
    /** @var Cache */
51
    private $cache;
52
    /** @var Algorithm */
53
    private $algorithm;
54
    /** @var Comparator */
55
    private $comparator;
56
    private $delayUpdate = true;
57
    private $modified = 0;
58
    private $isConfigured = false;
59
    /** @var PhingFile */
60
    private $cachefile;
61
    /** @var Parameter[] */
62
    private $configParameter = [];
63
    /** @var Parameter[] */
64
    private $specialParameter = [];
65
66
    /**
67
     * Get the cache type to use.
68
     * @return Cache the enumerated cache type
69
     */
70
    public function getCache(): Cache
71
    {
72
        return $this->cache;
73
    }
74
75
    /**
76
     * Set the cache type to use.
77
     * @param string $name an enumerated cache type.
78
     */
79 3
    public function setCache(string $name): void
80
    {
81 3
        $this->cacheName = $name;
82 3
    }
83
84 4
    public function verifySettings()
85
    {
86 4
        $this->configure();
87 4
        if ($this->cache === null) {
88 1
            $this->setError('Cache must be set.');
89 3
        } elseif ($this->algorithm === null) {
90 1
            $this->setError('Algorithm must be set.');
91 2
        } elseif (!$this->cache->isValid()) {
92
            $this->setError('Cache must be proper configured.');
93 2
        } elseif (!$this->algorithm->isValid()) {
94
            $this->setError('Algorithm must be proper configured.');
95
        }
96 4
    }
97
98 4
    public function configure(): void
99
    {
100 4
        if ($this->isConfigured) {
101 2
            return;
102
        }
103 4
        $this->isConfigured = true;
104
105 4
        $p = $this->getProject();
106 4
        $filename = 'cache.properties';
107 4
        if ($p !== null) {
108
            // normal use inside Phing
109 2
            $this->cachefile = new PhingFile($p->getBasedir(), $filename);
110
111
            // set self as a BuildListener to delay cachefile saves
112 2
            $this->getProject()->addBuildListener($this);
113
        } else {
114
            // no reference to project - e.g. during normal JUnit tests
115 2
            $this->cachefile = new PhingFile($filename);
116 2
            $this->setDelayUpdate(false);
117
        }
118 4
        $defaultCache = new PropertiesfileCache($this->cachefile);
119 4
        $defaultAlgorithm = new HashfileAlgorithm();
120 4
        $defaultComparator = new EqualComparator();
121
122
        //
123
        // -----  Set the main attributes, pattern '*'  -----
124
        //
125 4
        foreach ($this->configParameter as $parameter) {
126 2
            if (strpos($parameter->getName(), '.') > 0) {
127
                // this is a *.* parameter for later use
128 2
                $this->specialParameter[] = $parameter;
129
            } else {
130 1
                $this->useParameter($parameter);
131
            }
132
        }
133 4
        $this->configParameter = [];
134
135
        // specify the algorithm classname
136 4
        if ($this->algoName !== null) {
137
            // use Algorithm defined via name
138 3
            if ('hashfile' === $this->algoName) {
139 1
                $this->algorithm = new HashfileAlgorithm();
140 2
            } elseif ('lastmodified' === $this->algoName) {
141 3
                $this->algorithm = new LastModifiedAlgorithm();
142
            }
143 1
        } elseif ($this->algorithmClass !== null) {
144
            // use Algorithm specified by classname
145
            $clz = Phing::import($this->algorithmClass);
146
            $this->algorithm = new $clz();
147
            if (!$this->algorithm instanceof Algorithm) {
148
                throw new BuildException($this->algorithmClass . " is not an Algorithm.");
149
            }
150
        } else {
151
            // nothing specified - use default
152 1
            $this->algorithm = $defaultAlgorithm;
153
        }
154
155
        // specify the cache classname
156 4
        if ($this->cacheName !== null) {
157
            // use Cache defined via name
158 3
            if ('propertyfile' === $this->cacheName) {
159 3
                $this->cache = new PropertiesfileCache();
160
            }
161 1
        } elseif ($this->cacheClass !== null) {
162
            // use Cache specified by classname
163
            $clz = Phing::import($this->cacheClass);
164
            $this->cache = new $clz();
165
            if (!$this->cache instanceof Cache) {
166
                throw new BuildException($this->cacheClass . " is not a Cache.");
167
            }
168
        } else {
169
            // nothing specified - use default
170 1
            $this->cache = $defaultCache;
171
        }
172
173
        // specify the comparator classname
174 4
        if ($this->compName !== null) {
175
            // use Algorithm defined via name
176 2
            if ('equal' === $this->compName) {
177 2
                $this->comparator = new EqualComparator();
178
            }
179 2
        } elseif ($this->comparatorClass !== null) {
180
            // use Comparator specified by classname
181
            $localComparator = Phing::import($this->comparatorClass);
182
            if (!$localComparator instanceof Comparator) {
0 ignored issues
show
introduced by
$localComparator is never a sub-type of Comparator.
Loading history...
183
                throw new BuildException($this->comparatorClass . " is not a Comparator.");
184
            }
185
            $this->comparator = $localComparator;
186
        } else {
187
            // nothing specified - use default
188 2
            $this->comparator = $defaultComparator;
189
        }
190 4
        foreach ($this->specialParameter as $special) {
191 2
            $this->useParameter($special);
192
        }
193 4
        $this->specialParameter = [];
194 4
    }
195
196
    /**
197
     * Support for nested <code>&lt;param name="" value=""/&gt;</code> tags.
198
     * Parameter named <i>cache</i>, <i>algorithm</i>,
199
     * <i>comparator</i> or <i>update</i> are mapped to
200
     * the respective set-Method.
201
     * Parameter which names starts with <i>cache.</i> or
202
     * <i>algorithm.</i> or <i>comparator.</i> are tried
203
     * to set on the appropriate object via its set-methods.
204
     * Other parameters are invalid and an BuildException will
205
     * be thrown.
206
     *
207
     * @param parameter  Key and value as parameter object
0 ignored issues
show
Bug introduced by
The type Key was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
208
     */
209 2
    public function useParameter(Parameter $parameter): void
210
    {
211 2
        $key = $parameter->getName();
212 2
        $value = $parameter->getValue();
213 2
        if ('cache' === $key) {
214 1
            $this->setCache($value);
215 2
        } elseif ('algorithm' === $key) {
216 1
            $this->setAlgorithm($value);
217 2
        } elseif ("comparator" === $key) {
218 1
            $this->setComparator($value);
219 2
        } elseif ('update' === $key) {
220 1
            $this->setUpdate('true' === strtolower($value));
221 2
        } elseif ('delayupdate' === $key) {
222
            $this->setDelayUpdate('true' === strtolower($value));
223 2
        } elseif ('seldirs' === $key) {
224 1
            $this->setSeldirs("true" === strtolower($value));
225 2
        } elseif (StringHelper::startsWith(self::CACHE_PREFIX, $key)) {
226 2
            $name = StringHelper::substring($key, strlen(self::CACHE_PREFIX));
227 2
            $this->tryToSetAParameter($this->cache, $name, $value);
228 2
        } elseif (StringHelper::startsWith(self::ALGORITHM_PREFIX, $key)) {
229 2
            $name = StringHelper::substring($key, strlen(self::ALGORITHM_PREFIX));
230 2
            $this->tryToSetAParameter($this->algorithm, $name, $value);
231
        } elseif (StringHelper::startsWith(self::COMPARATOR_PREFIX, $key)) {
232
            $name = StringHelper::substring($key, strlen(self::COMPARATOR_PREFIX));
233
            $this->tryToSetAParameter($this->comparator, $name, $value);
234
        } else {
235
            $this->setError("Invalid parameter " . $key);
236
        }
237 2
    }
238
239
    /**
240
     * Support for <i>update</i> attribute.
241
     * @param bool $update new value
242
     */
243 2
    public function setUpdate(bool $update): void
244
    {
245 2
        $this->update = $update;
246 2
    }
247
248
    /**
249
     * Support for <i>seldirs</i> attribute.
250
     * @param bool $seldirs new value
251
     */
252 2
    public function setSeldirs(bool $seldirs): void
253
    {
254 2
        $this->selectDirectories = $seldirs;
255 2
    }
256
257
    /**
258
     * Try to set a value on an object using reflection.
259
     * Helper method for easier access to IntrospectionHelper.setAttribute().
260
     * @param object $obj the object on which the attribute should be set
261
     * @param string $name the attributename
262
     * @param string $value the new value
263
     */
264 2
    protected function tryToSetAParameter(object $obj, string $name, string $value): void
265
    {
266 2
        $prj = $this->getProject() ?? new Project();
267 2
        $iHelper = IntrospectionHelper::getHelper(get_class($obj));
268
        try {
269 2
            $iHelper->setAttribute($prj, $obj, $name, $value);
270 1
        } catch (BuildException $e) {
271
            // no-op
272
        }
273 2
    }
274
275
    /**
276
     * Support for nested &lt;param&gt; tags.
277
     * @param Parameter $parameter the parameter object
278
     */
279 2
    public function addParam(Parameter $parameter)
280
    {
281 2
        $this->configParameter[] = $parameter;
282 2
    }
283
284
    /**
285
     * Defined in org.apache.tools.ant.types.Parameterizable.
286
     * Overwrite implementation in superclass because only special
287
     * parameters are valid.
288
     * @see #addParam(String,Object)
289
     * @param array $parameters the parameters to set.
290
     */
291
    public function setParameters(array $parameters): void
292
    {
293
        parent::setParameters($parameters);
294
        foreach ($parameters as $param) {
295
            $this->configParameter[] = $param;
296
        }
297
    }
298
299
    /**
300
     * @param BuildEvent $event
301
     * @return mixed
302
     */
303
    public function buildStarted(BuildEvent $event)
304
    {
305
        // do nothing
306
    }
307
308
    /**
309
     * @param BuildEvent $event
310
     * @return mixed
311
     */
312
    public function buildFinished(BuildEvent $event)
313
    {
314
        if ($this->getDelayUpdate()) {
315
            $this->saveCache();
316
        }
317
    }
318
319
    /**
320
     * Getter for the delay update
321
     * @return bool true if we should delay for performance
322
     */
323 2
    public function getDelayUpdate(): bool
324
    {
325 2
        return $this->delayUpdate;
326
    }
327
328 2
    public function setDelayUpdate(bool $false): void
329
    {
330 2
        $this->delayUpdate = $false;
331 2
    }
332
333
    /**
334
     * save the cache file
335
     */
336 2
    protected function saveCache(): void
337
    {
338 2
        if ($this->getModified() > 0) {
339 1
            $this->cache->save();
340 1
            $this->setModified(0);
341
        }
342 2
    }
343
344
    /**
345
     * Getter for the modified count
346
     * @return int $modified count
347
     */
348 2
    public function getModified(): int
349
    {
350 2
        return $this->modified;
351
    }
352
353
    /**
354
     * Setter for the modified count
355
     * @param int $modified count
356
     */
357 1
    public function setModified(int $modified): void
358
    {
359 1
        $this->modified = $modified;
360 1
    }
361
362
    /**
363
     * Setter for algorithmClass.
364
     * @param string $classname
365
     */
366
    public function setAlgorithmClass(string $classname): void
367
    {
368
        $this->algorithmClass = $classname;
369
    }
370
371
    /**
372
     * Setter for comparatorClass.
373
     * @param string $classname
374
     */
375
    public function setComparatorClass(string $classname): void
376
    {
377
        $this->comparatorClass = $classname;
378
    }
379
380
    /**
381
     * Setter for cacheClass.
382
     * @param string $classname
383
     */
384
    public function setCacheClass(string $classname): void
385
    {
386
        $this->cacheClass = $classname;
387
    }
388
389
    /**
390
     * @param BuildEvent $event
391
     * @return mixed
392
     */
393 2
    public function targetStarted(BuildEvent $event)
394
    {
395
        // do nothing
396 2
    }
397
398
    /**
399
     * @param BuildEvent $event
400
     * @return mixed
401
     */
402 2
    public function targetFinished(BuildEvent $event)
403
    {
404 2
        if ($this->getDelayUpdate()) {
405 2
            $this->saveCache();
406
        }
407 2
    }
408
409
    /**
410
     * @param BuildEvent $event
411
     * @return mixed
412
     */
413 2
    public function taskStarted(BuildEvent $event)
414
    {
415
        // do nothing
416 2
    }
417
418
    /**
419
     * @param BuildEvent $event
420
     * @return mixed
421
     */
422 2
    public function taskFinished(BuildEvent $event)
423
    {
424 2
        if ($this->getDelayUpdate()) {
425 2
            $this->saveCache();
426
        }
427 2
    }
428
429
    /**
430
     * @param BuildEvent $event
431
     * @return mixed
432
     */
433 2
    public function messageLogged(BuildEvent $event)
434
    {
435
        // do nothing
436 2
    }
437
438
    /**
439
     * @param PhingFile $basedir
440
     * @param string $filename
441
     * @param PhingFile $file
442
     * @return bool|null
443
     */
444 2
    public function isSelected(PhingFile $basedir, $filename, PhingFile $file)
445
    {
446 2
        $this->validate();
447
        try {
448 2
            $f = new PhingFile($basedir, $filename);
449
450
            // You can not compute a value for a directory
451 2
            if ($f->isDirectory()) {
452 2
                return $this->selectDirectories;
453
            }
454
        } catch (Throwable $t) {
455
            throw new BuildException($t);
456
        }
457
458
        // Get the values and do the comparison
459 2
        $cachedValue = (string) $this->cache->get($f->getAbsolutePath());
460 2
        $newValue = $this->algorithm->getValue($f);
461
462 2
        $rv = $this->comparator->compare($cachedValue, $newValue) !== 0;
463
464
        // Maybe update the cache
465 2
        if ($this->update && $rv) {
466 1
            $this->cache->put($f->getAbsolutePath(), $newValue);
467 1
            $this->setModified($this->getModified() + 1);
468 1
            if (!$this->getDelayUpdate()) {
469
                $this->saveCache();
470
            }
471
        }
472 2
        return $rv;
473
    }
474
475 2
    public function __toString(): string
476
    {
477 2
        $this->configure();
478 2
        return sprintf(
479 2
            '{modifiedselector update=%s seldirs=%s cache=%s algorithm=%s comparator=%s}',
480 2
            $this->update === true ? 'true' : 'false',
481 2
            $this->selectDirectories === true ? 'true' : 'false',
482 2
            $this->cache,
0 ignored issues
show
Bug introduced by
$this->cache of type Cache is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

482
            /** @scrutinizer ignore-type */ $this->cache,
Loading history...
483 2
            $this->algorithm,
0 ignored issues
show
Bug introduced by
$this->algorithm of type Algorithm is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

483
            /** @scrutinizer ignore-type */ $this->algorithm,
Loading history...
484 2
            $this->comparator
485
        );
486
    }
487
488 3
    public function setAlgorithm(string $an): void
489
    {
490 3
        $this->algoName = $an;
491 3
    }
492
493 2
    public function setComparator($value): void
494
    {
495 2
        $this->compName = $value;
496 2
    }
497
}
498