Passed
Push — master ( a3d2f3...b762af )
by Magnar Ovedal
05:51 queued 03:19
created

SubstringGenerator::applyCurrent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\Formatter;
6
7
use ArrayObject;
8
use InvalidArgumentException;
9
use SplObjectStorage;
10
use Stadly\PasswordPolice\CharTree;
11
use Stadly\PasswordPolice\Formatter;
12
13
final class SubstringGenerator implements Formatter
14
{
15
    use Chaining;
16
17
    /**
18
     * @var int Minimum substring length.
19
     */
20
    private $minLength;
21
22
    /**
23
     * @var int|null Maximum substring length.
24
     */
25
    private $maxLength;
26
27
    /**
28
     * @var SplObjectStorage Memoization for already filtered character trees.
29
     */
30
    private $substringMemoization;
31
32
    /**
33
     * @var SplObjectStorage Memoization for already filtered character trees.
34
     */
35
    private $startsWithMemoization;
36
37
    /**
38
     * @param int $minLength Ignore substrings shorter than this.
39
     * @param int|null $maxLength Ignore substrings longer than this.
40
     */
41 6
    public function __construct(int $minLength = 3, ?int $maxLength = 25)
42
    {
43 6
        if ($minLength < 0) {
44 1
            throw new InvalidArgumentException('Min length must be non-negative.');
45
        }
46 5
        if ($maxLength !== null && $maxLength < $minLength) {
47 1
            throw new InvalidArgumentException('Max length cannot be smaller than min length.');
48
        }
49
50 4
        $this->minLength = $minLength;
51 4
        $this->maxLength = $maxLength;
52 4
        $this->substringMemoization = new SplObjectStorage();
53 4
        $this->startsWithMemoization = new SplObjectStorage();
54 4
    }
55
56
    /**
57
     * @param CharTree $charTree Character tree to format.
58
     * @return CharTree Substrings of the character tree.
59
     */
60 4
    protected function applyCurrent(CharTree $charTree): CharTree
61
    {
62 4
        return $this->applyInternal($charTree, $this->minLength, $this->maxLength);
63
    }
64
65
    /**
66
     * @param CharTree $charTree Character tree to format.
67
     * @param int $minLength Minimum substring length.
68
     * @param int|null $maxLength Maximum substring length.
69
     * @return CharTree Substrings of the character tree.
70
     */
71 4
    private function applyInternal(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
72
    {
73 4
        if (!isset($this->substringMemoization[$charTree])) {
74 4
            $this->substringMemoization[$charTree] = new ArrayObject();
75
        }
76
77 4
        $memoization1 = $this->substringMemoization[$charTree];
78 4
        if (!isset($memoization1[$minLength])) {
79 4
            $memoization1[$minLength] = new ArrayObject();
80
        }
81
82 4
        $memoization2 = $memoization1[$minLength];
83 4
        if (!isset($memoization2[$maxLength ?? ''])) {
84 4
            $memoization2[$maxLength ?? ''] = $this->generate($charTree, $minLength, $maxLength);
85
        }
86
87 4
        return $memoization2[$maxLength ?? ''];
88
    }
89
90
    /**
91
     * @param CharTree $charTree Character tree to format.
92
     * @param int $minLength Minimum substring length.
93
     * @param int|null $maxLength Maximum substring length.
94
     * @return CharTree Substrings of the character tree. Memoization is not used.
95
     */
96 4
    private function generate(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
97
    {
98 4
        $charTrees = [];
99
100 4
        $containsCharTree = $this->generateContains($charTree, $minLength, $maxLength);
101 4
        if ($containsCharTree->getRoot() !== null) {
102 4
            $charTrees[] = $containsCharTree;
103
        }
104
105 4
        $startsWithCharTree = $this->applyInternalStartsWith($charTree, $minLength, $maxLength);
106 4
        if ($startsWithCharTree->getRoot() !== null) {
107 4
            $charTrees[] = $startsWithCharTree;
108
        }
109
110 4
        return CharTree::fromArray($charTrees);
111
    }
112
113
    /**
114
     * @param CharTree $charTree Character tree to format.
115
     * @param int $minLength Minimum substring length.
116
     * @param int|null $maxLength Maximum substring length.
117
     * @return CharTree Substrings of the character tree not starting with root.
118
     */
119 4
    private function generateContains(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
120
    {
121 4
        $root = $charTree->getRoot();
122
123 4
        if ($root === null) {
124 1
            return $charTree;
125
        }
126
127 4
        $branches = $charTree->getBranches();
128 4
        $substringBranches = [];
129 4
        if (0 < $maxLength || $maxLength === null) {
130 4
            foreach ($branches as $branch) {
131 4
                $substringBranch = $this->applyInternal($branch, $minLength, $maxLength);
132 4
                if ($substringBranch->getRoot() !== null) {
133 4
                    $substringBranches[] = $substringBranch;
134
                }
135
            }
136
        }
137
138 4
        if ($substringBranches !== [] || $minLength === 0) {
139 4
            if ($substringBranches !== [] && $minLength === 0) {
140 1
                $substringBranches[] = CharTree::fromNothing();
141
            }
142 4
            return CharTree::fromString('', $substringBranches);
143
        }
144
145 3
        return CharTree::fromNothing();
146
    }
147
148
    /**
149
     * @param CharTree $charTree Character tree to format.
150
     * @param int $minLength Minimum substring length.
151
     * @param int|null $maxLength Maximum substring length.
152
     * @return CharTree Substrings of the character tree starting with root.
153
     */
154 4
    private function applyInternalStartsWith(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
155
    {
156 4
        if (!isset($this->startsWithMemoization[$charTree])) {
157 4
            $this->startsWithMemoization[$charTree] = new ArrayObject();
158
        }
159
160 4
        $memoization1 = $this->startsWithMemoization[$charTree];
161 4
        if (!isset($memoization1[$minLength])) {
162 4
            $memoization1[$minLength] = new ArrayObject();
163
        }
164
165 4
        $memoization2 = $memoization1[$minLength];
166 4
        if (!isset($memoization2[$maxLength ?? ''])) {
167 4
            $memoization2[$maxLength ?? ''] = $this->generateStartsWith($charTree, $minLength, $maxLength);
168
        }
169
170 4
        return $memoization2[$maxLength ?? ''];
171
    }
172
173
    /**
174
     * @param CharTree $charTree Character tree to format.
175
     * @param int $minLength Minimum substring length.
176
     * @param int|null $maxLength Maximum substring length.
177
     * @return CharTree Substrings of the character tree starting with root. Memoization is not used.
178
     */
179 4
    private function generateStartsWith(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
180
    {
181 4
        $root = $charTree->getRoot();
182
183 4
        if ($root === null) {
184 1
            return $charTree;
185
        }
186
187 4
        $rootLength = mb_strlen($root);
188 4
        if ($rootLength <= $maxLength || $maxLength === null) {
189 4
            $branches = $charTree->getBranches();
190 4
            $branchMinLength = $minLength <= $rootLength ? 0 : $minLength-$rootLength;
191 4
            $branchMaxLength = $maxLength === null ? null : $maxLength-$rootLength;
192
193 4
            $substringBranches = [];
194 4
            foreach ($branches as $branch) {
195 4
                $substringBranch = $this->applyInternalStartsWith($branch, $branchMinLength, $branchMaxLength);
196 4
                if ($substringBranch->getRoot() !== null) {
197 4
                    $substringBranches[] = $substringBranch;
198
                }
199
            }
200
201 4
            if ($substringBranches !== [] || $minLength <= $rootLength) {
202 4
                if ($substringBranches !== [] && $minLength <= $rootLength) {
203 3
                    $substringBranches[] = CharTree::fromNothing();
204
                }
205 4
                return CharTree::fromString($root, $substringBranches);
206
            }
207
        }
208
209 3
        return CharTree::fromNothing();
210
    }
211
}
212