Completed
Push — master ( 8b40f4...1a4202 )
by Peter
13:15
created

IntervalType::equal()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * GpsLab component.
5
 *
6
 * @author    Peter Gribanov <[email protected]>
7
 * @copyright Copyright (c) 2016, Peter Gribanov
8
 * @license   http://opensource.org/licenses/MIT
9
 */
10
11
namespace GpsLab\Component\Interval;
12
13
use GpsLab\Component\Interval\Exception\IncorrectIntervalTypeException;
14
use GpsLab\Component\Interval\Exception\InvalidIntervalFormatException;
15
16
/**
17
 * @see https://en.wikipedia.org/wiki/Interval_(mathematics)
18
 */
19
final class IntervalType
20
{
21
    /**
22
     * @var int
23
     */
24
    const TYPE_START_EXCLUDED = 1;
25
26
    /**
27
     * @var int
28
     */
29
    const TYPE_END_EXCLUDED = 2;
30
31
    /**
32
     * [a, b].
33
     *
34
     * @var int
35
     */
36
    const TYPE_CLOSED = 0;
37
38
    /**
39
     * [a, b).
40
     *
41
     * @var int
42
     */
43
    const TYPE_HALF_CLOSED = self::TYPE_CLOSED | self::TYPE_END_EXCLUDED;
44
45
    /**
46
     * (a, b].
47
     *
48
     * @var int
49
     */
50
    const TYPE_HALF_OPEN = self::TYPE_CLOSED | self::TYPE_START_EXCLUDED;
51
52
    /**
53
     * (a, b).
54
     *
55
     * @var int
56
     */
57
    const TYPE_OPEN = self::TYPE_CLOSED | self::TYPE_START_EXCLUDED | self::TYPE_END_EXCLUDED;
58
59
    /**
60
     * @var string
61
     */
62
    const REGEXP = '/^
63
        (?<start>\(|\[) # start type char
64
        \s*
65
        .+              # start point
66
        \s*,\s*         # separator
67
        .+              # end point
68
        \s*
69
        (?<end>\)|\])   # end type char
70
    $/x';
71
72
    /**
73
     * @var IntervalType[]
74
     */
75
    private static $instances = [];
76
77
    /**
78
     * @var string
79
     */
80
    private $type = '';
81
82
    /**
83
     * @var array
84
     */
85
    private $formats = [
86
        self::TYPE_CLOSED => '[%s, %s]',
87
        self::TYPE_OPEN => '(%s, %s)',
88
        self::TYPE_HALF_CLOSED => '[%s, %s)',
89
        self::TYPE_HALF_OPEN => '(%s, %s]',
90
    ];
91
92
    /**
93
     * @param string $type
94
     */
95
    private function __construct($type)
96
    {
97
        $this->type = $type;
98
    }
99
100
    /**
101
     * @return int[]
102
     */
103
    public static function getAvailableValues()
104
    {
105
        return [
106
            self::TYPE_CLOSED,
107
            self::TYPE_OPEN,
108
            self::TYPE_HALF_CLOSED,
109
            self::TYPE_HALF_OPEN,
110
        ];
111
    }
112
113
    /**
114
     * @param int $value
115
     *
116
     * @return bool
117
     */
118
    public static function isValueSupported($value)
119
    {
120
        return in_array($value, self::getAvailableValues());
121
    }
122
123
    /**
124
     * @return int
125
     */
126
    public function type()
127
    {
128
        return $this->type;
129
    }
130
131
    /**
132
     * @param string $value
133
     *
134
     * @return self
135
     */
136
    private static function safe($value)
137
    {
138
        // limitation of count object instances
139
        if (!isset(self::$instances[$value])) {
140
            self::$instances[$value] = new self($value);
141
        }
142
143
        return self::$instances[$value];
144
    }
145
146
    /**
147
     * @param int $value
148
     *
149
     * @return self
150
     */
151
    public static function create($value)
152
    {
153
        if (!self::isValueSupported($value)) {
154
            throw IncorrectIntervalTypeException::create($value);
155
        }
156
157
        return self::safe($value);
158
    }
159
160
    /**
161
     * @return self
162
     */
163
    public static function closed()
164
    {
165
        return self::safe(self::TYPE_CLOSED);
166
    }
167
168
    /**
169
     * @return self
170
     */
171
    public static function halfClosed()
172
    {
173
        return self::safe(self::TYPE_HALF_CLOSED);
174
    }
175
176
    /**
177
     * @return self
178
     */
179
    public static function halfOpen()
180
    {
181
        return self::safe(self::TYPE_HALF_OPEN);
182
    }
183
184
    /**
185
     * @return self
186
     */
187
    public static function open()
188
    {
189
        return self::safe(self::TYPE_OPEN);
190
    }
191
192
    /**
193
     * Create interval type from string.
194
     *
195
     * All types format:
196
     *   [a, b]
197
     *   (a, b]
198
     *   [a, b)
199
     *   (a, b)
200
     *
201
     * Spaces are ignored in format.
202
     *
203
     * @param string $string
204
     *
205
     * @return self
206
     */
207
    public static function fromString($string)
208
    {
209
        if (!preg_match(self::REGEXP, $string, $match)) {
210
            throw InvalidIntervalFormatException::create('[a, b]', $string);
211
        }
212
213
        $type = self::TYPE_CLOSED;
214
215
        if ($match['start'] == '(') {
216
            $type |= self::TYPE_START_EXCLUDED;
217
        }
218
219
        if ($match['end'] == ')') {
220
            $type |= self::TYPE_END_EXCLUDED;
221
        }
222
223
        return self::create($type);
224
    }
225
226
    /**
227
     * @param IntervalType $type
228
     *
229
     * @return bool
230
     */
231
    public function equal(IntervalType $type)
232
    {
233
        return $this->type() == $type->type();
234
    }
235
236
    /**
237
     * @return bool
238
     */
239
    public function startExcluded()
240
    {
241
        return ($this->type() & self::TYPE_START_EXCLUDED) == self::TYPE_START_EXCLUDED;
242
    }
243
244
    /**
245
     * @return bool
246
     */
247
    public function endExcluded()
248
    {
249
        return ($this->type() & self::TYPE_END_EXCLUDED) == self::TYPE_END_EXCLUDED;
250
    }
251
252
    /**
253
     * @param IntervalInterface $interval
254
     *
255
     * @return string
256
     */
257
    public function getReadable(IntervalInterface $interval)
258
    {
259
        return $this->format((string) $interval->startPoint(), (string) $interval->endPoint());
260
    }
261
262
    /**
263
     * @return string
264
     */
265
    public function __toString()
266
    {
267
        return $this->format('a', 'b');
268
    }
269
270
    /**
271
     * @param string $start
272
     * @param string $end
273
     *
274
     * @return string
275
     */
276
    private function format($start, $end)
277
    {
278
        return sprintf($this->formats[$this->type()], $start, $end);
279
    }
280
}
281