Completed
Push — master ( 20b0ec...0fa80a )
by Siad
15:26
created

FileUtils   F

Complexity

Total Complexity 78

Size/Duplication

Total Lines 419
Duplicated Lines 0 %

Test Coverage

Coverage 72.9%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 161
c 1
b 0
f 0
dl 0
loc 419
ccs 113
cts 155
cp 0.729
rs 2.16
wmc 78

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefaultFileCreationMask() 0 10 2
D copyFile() 0 60 19
B renameFile() 0 23 8
A getChainedReader() 0 14 2
A createTempFile() 0 27 6
F normalize() 0 104 22
B contentEquals() 0 18 7
C resolveFile() 0 45 12

How to fix   Complexity   

Complex Class

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