Passed
Push — master ( cb5cd1...da0f4b )
by Siad
10:24
created

FileUtils::normalize()   F

Complexity

Conditions 22
Paths 126

Size

Total Lines 104
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 58.141

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 22
eloc 63
c 1
b 0
f 0
nc 126
nop 1
dl 0
loc 104
ccs 33
cts 57
cp 0.5789
crap 58.141
rs 3.95

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 * File utility class.
22
 * - handles os independent stuff etc
23
 * - mapper stuff
24
 * - filter stuff
25
 *
26
 * @package phing.util
27
 */
28
class FileUtils
29
{
30
    /**
31
     * path separator string, static, obtained from FileSystem (; or :)
32
     */
33
    public static $pathSeparator;
34
35
    /**
36
     * separator string, static, obtained from FileSystem
37
     */
38
    public static $separator = '';
39
40 825
    public function __construct()
41
    {
42 825
        if (self::$separator === null || FileUtils::$pathSeparator === null) {
43
            $fs = FileSystem::getFileSystem();
44
            self::$separator = $fs->getSeparator();
45
            self::$pathSeparator = $fs->getPathSeparator();
46
        }
47 825
    }
48
49
    /**
50
     * Returns the path to the temp directory.
51
     *
52
     * @return string
53
     */
54 9
    public static function getTempDir()
55
    {
56 9
        return Phing::getProperty('php.tmpdir');
57
    }
58
59
    /**
60
     * Returns the default file/dir creation mask value
61
     * (The mask value is prepared w.r.t the current user's file-creation mask value)
62
     *
63
     * @param boolean $dirmode Directory creation mask to select
64
     *
65
     * @return int  Creation Mask in octal representation
66
     */
67
    public static function getDefaultFileCreationMask($dirmode = false)
68
    {
69
70
        // Preparing the creation mask base permission
71
        $permission = ($dirmode === true) ? 0777 : 0666;
72
73
        // Default mask information
74
        $defaultmask = sprintf('%03o', ($permission & ($permission - (int) sprintf('%04o', umask()))));
75
76
        return octdec($defaultmask);
77
    }
78
79
    /**
80
     * Returns a new Reader with filterchains applied.  If filterchains are empty,
81
     * simply returns passed reader.
82
     *
83
     * @param  Reader $in Reader to modify (if appropriate).
84
     * @param  array   &$filterChains filter chains to apply.
85
     * @param  Project $project
86
     * @return Reader  Assembled Reader (w/ filter chains).
87
     */
88 38
    public static function getChainedReader(Reader $in, &$filterChains, Project $project)
89
    {
90 38
        if (!empty($filterChains)) {
91 29
            $crh = new ChainReaderHelper();
92 29
            $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
93 29
            $crh->setPrimaryReader($in);
94 29
            $crh->setFilterChains($filterChains);
95 29
            $crh->setProject($project);
96 29
            $rdr = $crh->getAssembledReader();
97
98 29
            return $rdr;
99
        }
100
101 10
        return $in;
102
    }
103
104
    /**
105
     * Copies a file using filter chains.
106
     *
107
     * @param  PhingFile $sourceFile
108
     * @param  PhingFile $destFile
109
     * @param  boolean $overwrite
110
     * @param  boolean $preserveLastModified
111
     * @param  array $filterChains
112
     * @param  Project $project
113
     * @param  integer $mode
114
     * @param  bool $preservePermissions
115
     * @param int $granularity
116
     * @throws Exception
117
     * @throws IOException
118
     * @return void
119
     */
120 37
    public function copyFile(
121
        PhingFile $sourceFile,
122
        PhingFile $destFile,
123
        Project $project,
124
        $overwrite = false,
125
        $preserveLastModified = true,
126
        &$filterChains = null,
127
        $mode = 0755,
128
        $preservePermissions = true,
129
        int $granularity = 0
130
    ) {
131
        if (
132 37
            $overwrite
133 28
            || !$destFile->exists()
134 1
            || $destFile->lastModified() < $sourceFile->lastModified() - $granularity
135
        ) {
136 36
            if ($destFile->exists() && ($destFile->isFile() || $destFile->isLink())) {
137 1
                $destFile->delete();
138
            }
139
140
            // ensure that parent dir of dest file exists!
141 36
            $parent = $destFile->getParentFile();
142 36
            if ($parent !== null && !$parent->exists()) {
143
                // Setting source directory permissions to target
144
                // (On permissions preservation, the target directory permissions
145
                // will be inherited from the source directory, otherwise the 'mode'
146
                // will be used)
147 3
                $dirMode = ($preservePermissions ? $sourceFile->getParentFile()->getMode() : $mode);
148
149 3
                $parent->mkdirs($dirMode);
150
            }
151
152 36
            if ((is_array($filterChains)) && (!empty($filterChains))) {
153 23
                $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
154 23
                $out = new BufferedWriter(new FileWriter($destFile));
155
156
                // New read() methods returns a big buffer.
157 23
                while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
158 23
                    $out->write($buffer);
159
                }
160
161 23
                if ($in !== null) {
162 23
                    $in->close();
163
                }
164 23
                if ($out !== null) {
165 23
                    $out->close();
166
                }
167
168
                // Set/Copy the permissions on the target
169 23
                if ($preservePermissions === true) {
170 23
                    $destFile->setMode($sourceFile->getMode());
171
                }
172
            } else {
173
                // simple copy (no filtering)
174 13
                $sourceFile->copyTo($destFile);
175
176
                // By default, PHP::Copy also copies the file permissions. Therefore,
177
                // re-setting the mode with the "user file-creation mask" information.
178 13
                if ($preservePermissions === false) {
179
                    $destFile->setMode(FileUtils::getDefaultFileCreationMask(false));
180
                }
181
            }
182
183 36
            if ($preserveLastModified && !$destFile->isLink()) {
184 2
                $destFile->setLastModified($sourceFile->lastModified());
185
            }
186
        }
187 37
    }
188
189
190
    /**
191
     * Attempts to rename a file from a source to a destination.
192
     * If overwrite is set to true, this method overwrites existing file even if the destination file is newer.
193
     * Otherwise, the source file is renamed only if the destination file is older than it.
194
     *
195
     * @param  PhingFile $sourceFile
196
     * @param  PhingFile $destFile
197
     * @return void
198
     */
199 1
    public function renameFile(PhingFile $sourceFile, PhingFile $destFile, $overwrite = false)
200
    {
201
        // ensure that parent dir of dest file exists!
202 1
        $parent = $destFile->getParentFile();
203 1
        if ($parent !== null) {
204 1
            if (!$parent->exists()) {
205
                $parent->mkdirs();
206
            }
207
        }
208
209 1
        if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
210 1
            if ($destFile->exists()) {
211
                try {
212
                    $destFile->delete();
213
                } catch (Exception $e) {
214
                    throw new BuildException(
215
                        "Unable to remove existing file " . $destFile->__toString() . ": " . $e->getMessage()
216
                    );
217
                }
218
            }
219
        }
220
221 1
        $sourceFile->renameTo($destFile);
222 1
    }
223
224
    /**
225
     * Interpret the filename as a file relative to the given file -
226
     * unless the filename already represents an absolute filename.
227
     *
228
     * @param PhingFile $file the "reference" file for relative paths. This
229
     *                             instance must be an absolute file and must
230
     *                             not contain ./ or ../ sequences (same for \
231
     *                             instead of /).
232
     * @param string $filename a file name
233
     *
234
     * @throws IOException
235
     *
236
     * @return PhingFile A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
237
     *                   and uses the correct separator for the current platform.
238
     */
239 416
    public function resolveFile($file, $filename)
240
    {
241
        // remove this and use the static class constant File::separator
242
        // as soon as ZE2 is ready
243 416
        $fs = FileSystem::getFileSystem();
244
245 416
        $filename = str_replace(['\\', '/'], $fs->getSeparator(), $filename);
246
247
        // deal with absolute files
248
        if (
249 416
            StringHelper::startsWith($fs->getSeparator(), $filename)
250 391
            || (strlen($filename) >= 2
251 343
            && Character::isLetter($filename[0])
252 282
            && $filename[1] === ':')
253
        ) {
254 82
            return new PhingFile($this->normalize($filename));
255
        }
256
257 391
        if (strlen($filename) >= 2 && Character::isLetter($filename[0]) && $filename[1] === ':') {
258
            return new PhingFile($this->normalize($filename));
259
        }
260
261 391
        $helpFile = new PhingFile($file->getAbsolutePath());
262
263 391
        $tok = strtok($filename, $fs->getSeparator());
264 391
        while ($tok !== false) {
265 391
            $part = $tok;
266 391
            if ($part === '..') {
267 155
                $parentFile = $helpFile->getParent();
268 155
                if ($parentFile === null) {
269
                    $msg = "The file or path you specified ($filename) is invalid relative to " . $file->getPath();
270
                    throw new IOException($msg);
271
                }
272 155
                $helpFile = new PhingFile($parentFile);
273 391
            } elseif ($part !== '.') {
274 342
                $helpFile = new PhingFile($helpFile, $part);
275
            }
276 391
            $tok = strtok($fs->getSeparator());
277
        }
278
279 391
        return new PhingFile($helpFile->getAbsolutePath());
280
    }
281
282
    /**
283
     * Normalize the given absolute path.
284
     *
285
     * This includes:
286
     *   - Uppercase the drive letter if there is one.
287
     *   - Remove redundant slashes after the drive spec.
288
     *   - resolve all ./, .\, ../ and ..\ sequences.
289
     *   - DOS style paths that start with a drive letter will have
290
     *     \ as the separator.
291
     *
292
     * @param string $path Path to normalize.
293
     *
294
     * @throws IOException
295
     *
296
     * @return string
297
     */
298 806
    public function normalize($path)
299
    {
300 806
        $path = (string) $path;
301 806
        $orig = $path;
302
303 806
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
304
305
        // make sure we are dealing with an absolute path
306
        if (
307 806
            !StringHelper::startsWith(DIRECTORY_SEPARATOR, $path)
308
            && !(strlen($path) >= 2
309
            && Character::isLetter($path[0])
310
            && $path[1] === ':')
311
        ) {
312
            throw new IOException("$path is not an absolute path");
313
        }
314
315 806
        $dosWithDrive = false;
316 806
        $root = null;
317
318
        // Eliminate consecutive slashes after the drive spec
319
320 806
        if (strlen($path) >= 2 && Character::isLetter($path[0]) && $path[1] === ':') {
321
            $dosWithDrive = true;
322
323
            $ca = str_replace('/', '\\', $path);
324
325
            $path = strtoupper($ca[0]) . ':';
326
327
            for ($i = 2, $_i = strlen($ca); $i < $_i; $i++) {
328
                if (
329
                    ($ca[$i] !== '\\')
330
                    || ($ca[$i] === '\\'
331
                    && $ca[$i - 1] !== '\\')
332
                ) {
333
                    $path .= $ca[$i];
334
                }
335
            }
336
337
            $path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
338
339
            if (strlen($path) == 2) {
340
                $root = $path;
341
                $path = "";
342
            } else {
343
                $root = substr($path, 0, 3);
344
                $path = substr($path, 3);
345
            }
346
        } else {
347 806
            if (strlen($path) == 1) {
348 3
                $root = DIRECTORY_SEPARATOR;
349 3
                $path = "";
350
            } else {
351 803
                if ($path[1] == DIRECTORY_SEPARATOR) {
352
                    // UNC drive
353
                    $root = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
354
                    $path = substr($path, 2);
355
                } else {
356 803
                    $root = DIRECTORY_SEPARATOR;
357 803
                    $path = substr($path, 1);
358
                }
359
            }
360
        }
361
362 806
        $s = [];
363 806
        $s[] = $root;
364 806
        $tok = strtok($path, DIRECTORY_SEPARATOR);
365 806
        while ($tok !== false) {
366 803
            $thisToken = $tok;
367 803
            if ("." === $thisToken) {
368
                $tok = strtok(DIRECTORY_SEPARATOR);
369
                continue;
370
            }
371
372 803
            if (".." === $thisToken) {
373 20
                if (count($s) < 2) {
374
                    // using '..' in path that is too short
375
                    throw new IOException("Cannot resolve path: $orig");
376
                }
377
378 20
                array_pop($s);
379
            } else { // plain component
380 803
                $s[] = $thisToken;
381
            }
382 803
            $tok = strtok(DIRECTORY_SEPARATOR);
383
        }
384
385 806
        $sb = "";
386 806
        for ($i = 0, $_i = count($s); $i < $_i; $i++) {
387 806
            if ($i > 1) {
388
                // not before the filesystem root and not after it, since root
389
                // already contains one
390 803
                $sb .= DIRECTORY_SEPARATOR;
391
            }
392 806
            $sb .= (string) $s[$i];
393
        }
394
395
396 806
        $path = (string) $sb;
397 806
        if ($dosWithDrive === true) {
398
            $path = str_replace('/', '\\', $path);
399
        }
400
401 806
        return $path;
402
    }
403
404
    /**
405
     * Create a temporary file in a given directory.
406
     *
407
     * <p>The file denoted by the returned abstract pathname did not
408
     * exist before this method was invoked, any subsequent invocation
409
     * of this method will yield a different file name.</p>
410
     *
411
     * @param  string $prefix prefix before the random number.
412
     * @param  string $suffix file extension; include the '.'.
413
     * @param  PhingFile $parentDir Directory to create the temporary file in;
414
     *                                sys_get_temp_dir() used if not specified.
415
     * @param  boolean $deleteOnExit whether to set the tempfile for deletion on
416
     *                                normal exit.
417
     * @param  boolean $createFile true if the file must actually be created. If false
418
     *                                chances exist that a file with the same name is
419
     *                                created in the time between invoking this method
420
     *                                and the moment the file is actually created. If
421
     *                                possible set to true.
422
     * @return PhingFile            a File reference to the new temporary file.
423
     * @throws BuildException
424
     */
425 1
    public function createTempFile(
426
        $prefix,
427
        $suffix,
428
        PhingFile $parentDir = null,
429
        $deleteOnExit = false,
430
        $createFile = false
431
    ) {
432 1
        $result = null;
433 1
        $parent = ($parentDir === null) ? self::getTempDir() : $parentDir->getPath();
434
435 1
        if ($createFile) {
436
            try {
437
                $directory = new PhingFile($parent);
438
                // quick but efficient hack to create a unique filename ;-)
439
                $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
440
                do {
441
                    $result = new PhingFile($directory, $prefix . substr(md5(time()), 0, 8) . $suffix);
442
                } while (file_exists($result->getPath()));
443
444
                $fs = FileSystem::getFileSystem();
445
                $fs->createNewFile($result->getPath());
446
                $fs->lock($result);
447
            } catch (IOException $e) {
448
                throw new BuildException("Could not create tempfile in " . $parent, $e);
449
            }
450
        } else {
451
            do {
452 1
                $result = new PhingFile($parent, $prefix . substr(md5((string) time()), 0, 8) . $suffix);
453 1
            } while ($result->exists());
454
        }
455
456 1
        if ($deleteOnExit) {
457
            $result->deleteOnExit();
458
        }
459
460 1
        return $result;
461
    }
462
463
    /**
464
     * @param PhingFile $file1
465
     * @param PhingFile $file2
466
     *
467
     * @return boolean Whether contents of two files is the same.
468
     */
469 14
    public function contentEquals(PhingFile $file1, PhingFile $file2)
470
    {
471 14
        if (!($file1->exists() && $file2->exists())) {
472 1
            return false;
473
        }
474
475 14
        if (!($file1->canRead() && $file2->canRead())) {
476
            return false;
477
        }
478
479 14
        if ($file1->isDirectory() || $file2->isDirectory()) {
480 2
            return false;
481
        }
482
483 13
        $c1 = file_get_contents($file1->getAbsolutePath());
484 13
        $c2 = file_get_contents($file2->getAbsolutePath());
485
486 13
        return trim($c1) == trim($c2);
487
    }
488
}
489