Ticket #219: File.php

File File.php, 28.3 kB (added by hans, 7 months ago)

updated File class (trunk)

Line 
1 <?php
2 /*
3  *  $Id: File.php 313 2007-11-17 04:20:58Z hans $
4  *
5  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16  *
17  * This software consists of voluntary contributions made by many individuals
18  * and is licensed under the LGPL. For more information please see
19  * <http://phing.info>.
20  */
21
22 namespace phing::system::io;
23 use phing::system::io::FileSystem;
24 use phing::system::lang::NullPointerException;
25
26 /**
27  * An representation of file and directory pathnames.
28  *
29  * @package   phing.system.io
30  */
31 class File {
32
33     /**
34      * Separator string, static, obtained from FileSystem.
35      * @see FileSystem::getSeparator()
36      */
37     public static $separator;
38
39     /**
40      * Path separator string, static, obtained from FileSystem (; or :)
41      * @see FileSystem::getSeparator()
42      */
43     public static $pathSeparator;
44
45     /**
46      * This abstract pathname's normalized pathname string.  A normalized
47      * pathname string uses the default name-separator character and does not
48      * contain any duplicate or redundant separators.
49      * @var string
50      */
51     private $path;
52
53     /**
54      * The length of this abstract pathname's prefix, or zero if it has no prefix.
55      * @var int
56      */
57     private $prefixLength = 0;
58
59     /**
60      * Create a new File object.
61      *
62      * This method supports sevarl valid signatures:
63      *     new File(File parent, string filename)
64      *  new File(string filename)
65      *  new File(string parent, string filename)
66      */
67     function __construct($arg1, $arg2 = null) {
68
69         if (self::$separator === null || self::$pathSeparator === null) {
70             $fs = FileSystem::getFileSystem();
71             self::$separator = $fs->getSeparator();
72             self::$pathSeparator = $fs->getPathSeparator();
73         }
74
75         /* simulate constructor overloading */
76         if ($arg1 instanceof File && is_string($arg2)) {
77             $this->__constructFileParentStringChild($arg1, $arg2);
78         } elseif (is_string($arg1) && ($arg2 === null)) {
79             $this->__constructPathname($arg1);
80         } elseif(is_string($arg1) && is_string($arg2)) {
81             $this->__constructStringParentStringChild($arg1, $arg2);
82         } else {
83             if ($arg1 === null) {
84                 throw new NullPointerException("Argument1 to function must not be null");
85             }
86             $this->path = (string) $arg1;
87             $this->prefixLength = (int) $arg2;
88         }
89     }
90
91     /**
92      * Private overloaded constructor when passed a File parent path and string child path.
93      * @param File $parent The parent path
94      * @param string $child (optional) The child path
95      */
96     private function __constructFileParentStringChild(File $parent, $child) {
97         // obtain ref to the filesystem layer
98         $fs = FileSystem::getFileSystem();
99
100         if ($child === null) {
101             throw new NullPointerException("Argument to function must not be null");
102         }
103
104         if ($parent !== null) {
105             if ($parent->getPath() === "") {
106                 $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child));
107             } else {
108                 $this->path = $fs->resolve($parent->getPath(), $fs->normalize($child));
109             }
110         } else {
111             $this->path = $fs->normalize($child);
112         }
113         $this->prefixLength = $fs->prefixLength($this->path);
114     }
115
116     /**
117      * Private constructor when passed a single path string.
118      *
119      * @param string $pathname
120      */
121     private function __constructPathname($pathname) {
122         // obtain ref to the filesystem layer
123         $fs = FileSystem::getFileSystem();
124
125         if ($pathname === null) {
126             throw new NullPointerException("Argument to function must not be null");
127         }
128
129         $this->path = (string) $fs->normalize($pathname);
130         $this->prefixLength = (int) $fs->prefixLength($this->path);
131     }
132
133     /**
134      * Private overloaded constructor when passed a string parent path and child paths.
135      * @param string $parent The parent path
136      * @param string $child (optional) The child path
137      */
138     private function __constructStringParentStringChild($parent, $child) {
139         // obtain ref to the filesystem layer
140         $fs = FileSystem::getFileSystem();
141
142         if ($child === null) {
143             throw new NullPointerException("Argument to function must not be null");
144         }
145         if ($parent !== null) {
146             if ($parent === "") {
147                 $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child));
148             } else {
149                 $this->path = $fs->resolve($fs->normalize($parent), $fs->normalize($child));
150             }
151         } else {
152             $this->path = (string) $fs->normalize($child);
153         }
154         $this->prefixLength = (int) $fs->prefixLength($this->path);
155     }
156
157     /**
158      * Returns the length of this abstract pathname's prefix.
159      * @return int
160      */
161     function getPrefixLength() {
162         return (int) $this->prefixLength;
163     }
164
165     /* -- Path-component accessors -- */
166
167     /**
168      * Returns the name of the file or directory denoted by this abstract
169      * pathname.  This is just the last name in the pathname's name
170      * sequence.  If the pathname's name sequence is empty, then the empty
171      * string is returned.
172      *
173      * @return string The name of the file or directory.
174      */
175     public function getName() {
176         // that's a lastIndexOf
177         $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res);
178         if ($index < $this->prefixLength) {
179             return substr($this->path, $this->prefixLength);
180         }
181         return substr($this->path, $index + 1);
182     }
183
184     /**
185      * Returns the pathname string of this abstract pathname's parent, or
186      * null if this pathname does not name a parent directory.
187      *
188      * The parent of an abstract pathname consists of the pathname's prefix,
189      * if any, and each name in the pathname's name sequence except for the last.
190      * If the name sequence is empty then the pathname does not name a parent
191      * directory.
192      *
193      * @return string The pathname string of the parent directory.
194      */
195     public function getParent() {
196         // that's a lastIndexOf
197         $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res);
198         if ($index < $this->prefixLength) {
199             if (($this->prefixLength > 0) && (strlen($this->path > $this->prefixLength))) {
200                 return substr($this->path, 0, $this->prefixLength);
201             }
202             return null;
203         }
204         return substr($this->path, 0, $index);
205     }
206
207     /**
208      * Returns the parent directory as File object.
209      *
210      * @return File A File of the parent directory for this file.
211      */
212     public function getParentFile() {
213         $p = $this->getParent();
214         if ($p === null) {
215             return null;
216         }
217         return new File((string) $p, (int) $this->prefixLength);
218     }
219
220     /**
221      * Converts this abstract pathname into a pathname string.  The resulting
222      * string uses the default name-separator character to separate the names
223      * in the name sequence.
224      *
225      * @return string The string form of this abstract pathname
226      */
227     public function getPath() {
228         return (string) $this->path;
229     }
230
231     /**
232      * Tests whether this abstract pathname is absolute.  The definition of
233      * absolute pathname is system dependent.  On UNIX systems, a pathname is
234      * absolute if its prefix is "/".  On Win32 systems, a pathname is absolute
235      * if its prefix is a drive specifier followed by "\\", or if its prefix
236      * is "\\".
237      *
238      * @return boolean true if this abstract pathname is absolute, false otherwise
239      */
240     public function isAbsolute() {
241         return ($this->prefixLength !== 0);
242     }
243
244     /**
245      * Returns the absolute pathname string of this abstract pathname.
246      *
247      * If this abstract pathname is already absolute, then the pathname
248      * string is simply returned as if by the getPath method.
249      * If this abstract pathname is the empty abstract pathname then
250      * the pathname string of the current user directory, which is named by the
251      * system property user.dir, is returned.  Otherwise this
252      * pathname is resolved in a system-dependent way.  On UNIX systems, a
253      * relative pathname is made absolute by resolving it against the current
254      * user directory.  On Win32 systems, a relative pathname is made absolute
255      * by resolving it against the current directory of the drive named by the
256      * pathname, if any; if not, it is resolved against the current user
257      * directory.
258      *
259      * @return  string The absolute pathname of this file/directory.
260      * @see     isAbsolute()
261      */
262     public function getAbsolutePath() {
263         $fs = FileSystem::getFileSystem();
264         return $fs->resolveFile($this);
265     }
266
267     /**
268      * Returns a File object containing abs path to this file/dir.
269      *
270      * @see getAbsolutePath()
271      * @return File A File object containing the absolute path to this file/dir.
272      */
273     public function getAbsoluteFile() {
274         return new File((string) $this->getAbsolutePath());
275     }
276
277     /**
278      * Returns the canonical pathname string of this abstract pathname.
279      *
280      * A canonical pathname is both absolute and unique. The precise
281      * definition of canonical form is system-dependent. This method first
282      * converts this pathname to absolute form if necessary, as if by invoking the
283      * getAbsolutePath() method, and then maps it to its unique form in a
284      * system-dependent way.  This typically involves removing redundant names
285      * such as "." and .. from the pathname, resolving symbolic links
286      * (on UNIX platforms), and converting drive letters to a standard case
287      * (on Win32 platforms).
288      *
289      * Every pathname that denotes an existing file or directory has a
290      * unique canonical form.  Every pathname that denotes a nonexistent file
291      * or directory also has a unique canonical form.  The canonical form of
292      * the pathname of a nonexistent file or directory may be different from
293      * the canonical form of the same pathname after the file or directory is
294      * created.  Similarly, the canonical form of the pathname of an existing
295      * file or directory may be different from the canonical form of the same
296      * pathname after the file or directory is deleted.
297      *
298      * @return string The canonical path to this file/dir.
299      */
300     public function getCanonicalPath() {
301         $fs = FileSystem::getFileSystem();
302         return $fs->canonicalize($this->path);
303     }
304
305     /**
306      * Returns the canonical form of this abstract pathname.
307      *
308      * @see getCanonicalPath()
309      * @return File The canonical File to tihs file/dir.
310      */
311     public function getCanonicalFile() {
312         return new File($this->getCanonicalPath());
313     }
314
315     /**
316      * Normalizes the directory separators in the path and adds a trailing '/' to directories.
317      *
318      * @param string $path
319      * @param boolean $isDirectory
320      * @return string
321      */
322     private function _slashify($path, $isDirectory) {
323         $p = (string) $path;
324
325         if (self::$separator !== '/') {
326             $p = str_replace(self::$separator, '/', $p);
327         }
328
329         if (!StringHelper::startsWith('/', $p)) {
330             $p = '/'.$p;
331         }
332
333         if (!StringHelper::endsWith('/', $p) && $isDirectory) {
334             $p = $p.'/';
335         }
336
337         return $p;
338     }
339
340     /* -- Attribute accessors -- */
341
342     /**
343      * Tests whether the application can read the file denoted by this
344      * abstract pathname.
345      *
346      * @return boolean Whether file/dir can be read by application.
347      */
348     public function canRead() {
349         $fs = FileSystem::getFileSystem();
350
351         if ($fs->checkAccess($this)) {
352             return (boolean) @is_readable($this->getAbsolutePath());
353         }
354         return false;
355     }
356
357     /**
358      * Tests whether the application can modify to the file denoted by this
359      * abstract pathname.
360      *
361      * @return boolean Whether file/dir can be written to.
362      *
363      */
364     public function canWrite() {
365         $fs = FileSystem::getFileSystem();
366         return $fs->checkAccess($this, true);
367     }
368
369     /**
370      * Tests whether the file denoted by this abstract pathname exists.
371      *
372      * @return boolean Whether file/dir exists.
373      */
374     public function exists() {
375         clearstatcache();
376         if ($this->isFile()) {
377             return @file_exists($this->path);
378         } else {
379             return @is_dir($this->path);
380         }
381     }
382
383     /**
384      * Tests whether the path represented by this object corresponds to a directory.
385      *
386      * @return boolean Whether path represented is a directory.
387      */
388     public function isDirectory() {
389         clearstatcache();
390         $fs = FileSystem::getFileSystem();
391         if ($fs->checkAccess($this) !== true) {
392             throw new IOException("No read access to ".$this->path);
393         }
394         return @is_dir($this->path);
395     }
396
397     /**
398      * Tests whether the path represented by this object corresponds to a normal file.
399      *
400      * @return boolean Whether path represents a file.
401      */
402     public function isFile() {
403         clearstatcache();
404         //$fs = FileSystem::getFileSystem();
405         return @is_file($this->path);
406     }
407
408     /**
409      * Tests whether the path represented by this object is a hidden file.
410      *
411      * @return boolean Whether file/dir is hidden.
412      */
413     public function isHidden() {
414         $fs = FileSystem::getFileSystem();
415         if ($fs->checkAccess($this) !== true) {
416             throw new IOException("No read access to ".$this->path);
417         }
418         return (($fs->getBooleanAttributes($this) & $fs->BA_HIDDEN) !== 0);
419     }
420
421     /**
422      * Returns the time that the file denoted by this abstract pathname was
423      * last modified.
424      *
425      * @return int  A integer value representing the time the file was
426      *          last modified, measured in milliseconds since the epoch
427      *          (00:00:00 GMT, January 1, 1970), or 0 if the
428      *          file does not exist or if an I/O error occurs
429      */
430     public function lastModified() {
431         $fs = FileSystem::getFileSystem();
432         if ($fs->checkAccess($this) !== true) {
433             throw new IOException("No read access to " . $this->path);
434         }
435         return $fs->getLastModifiedTime($this);
436     }
437
438     /**
439      * Returns the length of the file denoted by this abstract pathname.
440      * The return value is unspecified if this pathname denotes a directory.
441      *
442      * @return int The length, in bytes, of the file denoted by this abstract
443      *              pathname, or 0 if the file does not exist
444      * @throws IOException - if file cannot be read
445      */
446     public function length() {
447         $fs = FileSystem::getFileSystem();
448         if ($fs->checkAccess($this) !== true) {
449             throw new IOException("No read access to ".$this->path."\n");
450         }
451         return $fs->getLength($this);
452     }
453
454     /**
455      * Convenience method for returning the contents of this file as a string.
456      * This method uses file_get_contents() to read file in an optimized way.
457      * @return string
458      * @throws IOException - if file cannot be read
459      */
460     public function contents() {
461         if (!$this->canRead() || !$this->isFile()) {
462             throw new IOException("Cannot read file contents!");
463         }
464         return file_get_contents($this->getAbsolutePath());
465     }
466
467     /* -- File operations -- */
468
469     /**
470      * Atomically creates a new, empty file named by this abstract pathname if
471      * and only if a file with this name does not yet exist.  The check for the
472      * existence of the file and the creation of the file if it does not exist
473      * are a single operation that is atomic with respect to all other
474      * filesystem activities that might affect the file.
475      *
476      * @return  true if the named file does not exist and was
477      *          successfully created; <code>false</code> if the named file
478      *          already exists
479      * @throws IOException if file can't be created
480      */
481     public function createNewFile($parents=true, $mode=0777) {
482         $file = FileSystem::getFileSystem()->createNewFile($this->path);
483         return $file;
484     }
485
486     /**
487      * Deletes the file or directory denoted by this abstract pathname.  If
488      * this pathname denotes a directory, then the directory must be empty in
489      * order to be deleted.
490      *
491      * @return  true if and only if the file or directory is
492      *          successfully deleted; false otherwise
493      */
494     public function delete() {
495         $fs = FileSystem::getFileSystem();
496         if ($fs->canDelete($this) !== true) {
497             throw new IOException("Cannot delete " . $this->path . "\n");
498         }
499         return $fs->delete($this);
500     }
501
502     /**
503      * Requests that the file or directory denoted by this abstract pathname
504      * be deleted when php terminates.  Deletion will be attempted only for
505      * normal termination of php and if and if only Phing::shutdown() is
506      * called.
507      *
508      * Once deletion has been requested, it is not possible to cancel the
509      * request.  This method should therefore be used with care.
510      *
511      */
512     public function deleteOnExit() {
513         $fs = FileSystem::getFileSystem();
514         $fs->deleteOnExit($this);
515     }
516
517     /**
518      * Return an array of names for contents of directory represented by this object.
519      *
520      * If this abstract pathname does not denote a directory, then this
521      * method returns null.
522      *
523      * @return array string[] An array of file and directory names
524      */
525     public function listDir($filter = null) {
526         $fs = FileSystem::getFileSystem();
527         return $fs->lister($this, $filter);
528     }
529
530     /**
531      * Return an array of File objects for contents of directory represented by this object.
532      *
533      * @param unknown_type $filter
534      * @return array File[]
535      */
536     public function listFiles($filter = null) {
537         $ss = $this->listDir($filter);
538         if ($ss === null) {
539             return null;
540         }
541         $n = count($ss);
542         $fs = array();
543         for ($i = 0; $i < $n; $i++) {
544             $fs[$i] = new File((string)$this->path, (string)$ss[$i]);
545         }
546         return $fs;
547     }
548
549     /**
550      * Creates the directory named by this abstract pathname, including any
551      * necessary but nonexistent parent directories.  Note that if this
552      * operation fails it may have succeeded in creating some of the necessary
553      * parent directories.
554      *
555      * @return  true if and only if the directory was created,
556      *          along with all necessary parent directories; false
557      *          otherwise
558      * @throws  IOException
559      */
560     public function mkdirs() {
561         if ($this->exists()) {
562             return false;
563         }
564         try {
565             if ($this->mkdir()) {
566                 return true;
567             }
568         } catch (IOException $ioe) {
569             // IOException from mkdir() means that directory propbably didn't exist.
570         }
571         $parentFile = $this->getParentFile();
572         return (($parentFile !== null) && ($parentFile->mkdirs() && $this->mkdir()));
573     }
574
575     /**
576      * Creates the directory named by this abstract pathname.
577      *
578      * @return  true if and only if the directory was created; false otherwise
579      * @throws  IOException - If no write access
580      */
581     public function mkdir() {
582         $fs = FileSystem::getFileSystem();
583
584         if ($fs->checkAccess(new File($this->path), true) !== true) {
585             throw new IOException("No write access to " . $this->getPath());
586         }
587         return $fs->createDirectory($this);
588     }
589
590     /**
591      * Renames the file denoted by this abstract pathname.
592      *
593      * @param   destFile  The new abstract pathname for the named file
594      * @return  true if and only if the renaming succeeded; false otherwise
595      */
596     public function renameTo(File $destFile) {
597         $fs = FileSystem::getFileSystem();
598         if ($fs->checkAccess($this) !== true) {
599             throw new IOException("No write access to ".$this->getPath());
600         }
601         return $fs->rename($this, $destFile);
602     }
603
604     /**
605      * Simple-copies file denoted by this abstract pathname into another
606      * File
607      *
608      * @param File $destFile  The new abstract pathname for the named file