Ticket #23: PhingFile.php

File PhingFile.php, 32.2 kB (added by norman@sefiroth.de, 3 years ago)
Line 
1 <?php
2 /*
3  *  $Id: PhingFile.php,v 1.1 2005/05/26 13:10:52 mrook Exp $
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 include_once 'phing/system/io/FileSystem.php';
23 include_once 'phing/system/lang/NullPointerException.php';
24
25 /**
26  * An abstract representation of file and directory pathnames.
27  *
28  * @version   $Revision: 1.1 $
29  * @package   phing.system.io
30  */
31 class PhingFile {
32
33     /** separator string, static, obtained from FileSystem */
34     public static $separator;
35
36     /** path separator string, static, obtained from FileSystem (; or :)*/
37     public static $pathSeparator;
38     
39     /**
40      * This abstract pathname's normalized pathname string.  A normalized
41      * pathname string uses the default name-separator character and does not
42      * contain any duplicate or redundant separators.
43      */
44     private $path = null;
45
46     /** The length of this abstract pathname's prefix, or zero if it has no prefix. */
47     private $prefixLength = 0;
48
49     /** constructor */
50     function __construct($arg1 = null, $arg2 = null) {
51         
52         if (self::$separator === null || self::$pathSeparator === null) {
53             $fs = FileSystem::getFileSystem();
54             self::$separator = $fs->getSeparator();
55             self::$pathSeparator = $fs->getPathSeparator();
56         }
57
58         /* simulate signature identified constructors */
59         if ($arg1 instanceof PhingFile && is_string($arg2)) {
60             $this->_constructFileParentStringChild($arg1, $arg2);
61         } elseif (is_string($arg1) && ($arg2 === null)) {
62             $this->_constructPathname($arg1);
63         } elseif(is_string($arg1) && is_string($arg2)) {
64             $this->_constructStringParentStringChild($arg1, $arg2);
65         } else {
66             if ($arg1 === null) {
67                 throw new NullPointerException("Argument1 to function must not be null");
68             }
69             $this->path = (string) $arg1;
70             $this->prefixLength = (int) $arg2;
71         }
72     }
73
74     /** Returns the length of this abstract pathname's prefix. */
75     function getPrefixLength() {
76         return (int) $this->prefixLength;
77     }
78     
79     /* -- constructors not called by signature match, so we need some helpers --*/
80
81     function _constructPathname($pathname) {
82         // obtain ref to the filesystem layer
83         $fs = FileSystem::getFileSystem();
84
85         if ($pathname === null) {
86             throw new NullPointerException("Argument to function must not be null");
87         }
88
89         $this->path = (string) $fs->normalize($pathname);
90         $this->prefixLength = (int) $fs->prefixLength($this->path);
91     }
92
93     function _constructStringParentStringChild($parent, $child = null) {
94         // obtain ref to the filesystem layer
95         $fs = FileSystem::getFileSystem();
96
97         if ($child === null) {
98             throw new NullPointerException("Argument to function must not be null");
99         }
100         if ($parent !== null) {
101             if ($parent === "") {
102                 $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child));
103             } else {
104                 $this->path = $fs->resolve($fs->normalize($parent), $fs->normalize($child));
105             }
106         } else {
107             $this->path = (string) $fs->normalize($child);
108         }
109         $this->prefixLength = (int) $fs->prefixLength($this->path);
110     }
111
112     function _constructFileParentStringChild($parent, $child = null) {
113         // obtain ref to the filesystem layer
114         $fs = FileSystem::getFileSystem();
115
116         if ($child === null) {
117             throw new NullPointerException("Argument to function must not be null");
118         }
119
120         if ($parent !== null) {
121             if ($parent->path === "") {
122                 $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child));
123             } else {
124                 $this->path = $fs->resolve($parent->path, $fs->normalize($child));
125             }
126         } else {
127             $this->path = $fs->normalize($child);
128         }
129         $this->prefixLength = $fs->prefixLength($this->path);
130     }
131
132     /* -- Path-component accessors -- */
133
134     /**
135      * Returns the name of the file or directory denoted by this abstract
136      * pathname.  This is just the last name in the pathname's name
137      * sequence.  If the pathname's name sequence is empty, then the empty
138      * string is returned.
139      *
140      * @return  The name of the file or directory denoted by this abstract
141      *          pathname, or the empty string if this pathname's name sequence
142      *          is empty
143      */
144     function getName() {
145         // that's a lastIndexOf
146         $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res);
147         if ($index < $this->prefixLength) {
148             return substr($this->path, $this->prefixLength);
149         }
150         return substr($this->path, $index + 1);
151     }
152
153     /**
154      * Returns the pathname string of this abstract pathname's parent, or
155      * null if this pathname does not name a parent directory.
156      *
157      * The parent of an abstract pathname consists of the pathname's prefix,
158      * if any, and each name in the pathname's name sequence except for the last.
159      * If the name sequence is empty then the pathname does not name a parent
160      * directory.
161      *
162      * @return  The pathname string of the parent directory named by this
163      *          abstract pathname, or null if this pathname does not name a parent
164      */
165     function getParent() {
166         // that's a lastIndexOf
167         $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res);
168         if ($index < $this->prefixLength) {
169             if (($this->prefixLength > 0) && (strlen($this->path > $this->prefixLength))) {
170                 return substr($this->path, 0, $this->prefixLength);
171             }
172             return null;
173         }
174         return substr($this->path, 0, $index);
175     }
176
177     /**
178      * Returns the abstract pathname of this abstract pathname's parent,
179      * or null if this pathname does not name a parent directory.
180      *
181      * The parent of an abstract pathname consists of the pathname's prefix,
182      * if any, and each name in the pathname's name sequence except for the
183      * last.  If the name sequence is empty then the pathname does not name
184      * a parent directory.
185      *
186      * @return  The abstract pathname of the parent directory named by this
187      *          abstract pathname, or null if this pathname
188      *          does not name a parent
189      */
190     function getParentFile() {
191         $p = $this->getParent();
192         if ($p === null) {
193             return null;
194         }
195         return new PhingFile((string) $p, (int) $this->prefixLength);
196     }
197
198     /**
199      * Converts this abstract pathname into a pathname string.  The resulting
200      * string uses the default name-separator character to separate the names
201      * in the name sequence.
202      *
203      * @return  The string form of this abstract pathname
204      */
205     function getPath() {
206         return (string) $this->path;
207     }
208
209     /**
210      * Tests whether this abstract pathname is absolute.  The definition of
211      * absolute pathname is system dependent.  On UNIX systems, a pathname is
212      * absolute if its prefix is "/".  On Win32 systems, a pathname is absolute
213      * if its prefix is a drive specifier followed by "\\", or if its prefix
214      * is "\\".
215      *
216      * @return  true if this abstract pathname is absolute, false otherwise
217      */
218     function isAbsolute() {
219         return ($this->prefixLength !== 0);
220     }
221
222
223     /**
224      * Returns the absolute pathname string of this abstract pathname.
225      *
226      * If this abstract pathname is already absolute, then the pathname
227      * string is simply returned as if by the getPath method.
228      * If this abstract pathname is the empty abstract pathname then
229      * the pathname string of the current user directory, which is named by the
230      * system property user.dir, is returned.  Otherwise this
231      * pathname is resolved in a system-dependent way.  On UNIX systems, a
232      * relative pathname is made absolute by resolving it against the current
233      * user directory.  On Win32 systems, a relative pathname is made absolute
234      * by resolving it against the current directory of the drive named by the
235      * pathname, if any; if not, it is resolved against the current user
236      * directory.
237      *
238      * @return  The absolute pathname string denoting the same file or
239      *          directory as this abstract pathname
240      * @see     #isAbsolute()
241      */
242     function getAbsolutePath() {
243         $fs = FileSystem::getFileSystem();       
244         return $fs->resolveFile($this);
245     }
246
247     /**
248      * Returns the absolute form of this abstract pathname.  Equivalent to
249      * getAbsolutePath.
250      *
251      * @return  The absolute abstract pathname denoting the same file or
252      *          directory as this abstract pathname
253      */
254     function getAbsoluteFile() {
255         return new PhingFile((string) $this->getAbsolutePath());
256     }
257
258
259     /**
260      * Returns the canonical pathname string of this abstract pathname.
261      *
262      * A canonical pathname is both absolute and unique. The precise
263      * definition of canonical form is system-dependent. This method first
264      * converts this pathname to absolute form if necessary, as if by invoking the
265      * getAbsolutePath() method, and then maps it to its unique form in a
266      * system-dependent way.  This typically involves removing redundant names
267      * such as "." and .. from the pathname, resolving symbolic links
268      * (on UNIX platforms), and converting drive letters to a standard case
269      * (on Win32 platforms).
270      *
271      * Every pathname that denotes an existing file or directory has a
272      * unique canonical form.  Every pathname that denotes a nonexistent file
273      * or directory also has a unique canonical form.  The canonical form of
274      * the pathname of a nonexistent file or directory may be different from
275      * the canonical form of the same pathname after the file or directory is
276      * created.  Similarly, the canonical form of the pathname of an existing
277      * file or directory may be different from the canonical form of the same
278      * pathname after the file or directory is deleted.
279      *
280      * @return  The canonical pathname string denoting the same file or
281      *          directory as this abstract pathname
282      */
283     function getCanonicalPath() {
284         $fs = FileSystem::getFileSystem();
285         return $fs->canonicalize($this->path);
286     }
287
288
289     /**
290      * Returns the canonical form of this abstract pathname.  Equivalent to
291      * getCanonicalPath(.
292      *
293      * @return  PhingFile The canonical pathname string denoting the same file or
294      *          directory as this abstract pathname
295      */
296     function getCanonicalFile() {
297         return new PhingFile($this->getCanonicalPath());
298     }
299
300     /**
301      * Converts this abstract pathname into a file: URL.  The
302      * exact form of the URL is system-dependent.  If it can be determined that
303      * the file denoted by this abstract pathname is a directory, then the
304      * resulting URL will end with a slash.
305      *
306      * Usage note: This method does not automatically escape
307      * characters that are illegal in URLs.  It is recommended that new code
308      * convert an abstract pathname into a URL by first converting it into a
309      * URI, via the toURI() method, and then converting the URI
310      * into a URL via the URI::toURL()
311      *
312      * @return  A URL object representing the equivalent file URL
313      *
314      *
315      */
316     function toURL() {
317         /*
318         // URL class not implemented yet
319         return new URL("file", "", $this->_slashify($this->getAbsolutePath(), $this->isDirectory()));
320         */
321     }
322
323     /**
324      * Constructs a file: URI that represents this abstract pathname.
325      * Not implemented yet
326      */
327     function toURI() {
328         /*
329         $f = $this->getAbsoluteFile();
330            $sp = (string) $this->slashify($f->getPath(), $f->isDirectory());
331            if (StringHelper::startsWith('//', $sp))
332         $sp = '//' + sp;
333            return new URI('file', null, $sp, null);
334         */
335     }
336
337     function _slashify($path, $isDirectory) {
338         $p = (string) $path;
339
340         if (self::$separator !== '/') {
341             $p = str_replace(self::$separator, '/', $p);
342         }
343
344         if (!StringHelper::startsWith('/', $p)) {
345             $p = '/'.$p;
346         }
347
348         if (!StringHelper::endsWith('/', $p) && $isDirectory) {
349             $p = $p.'/';
350         }
351
352         return $p;
353     }
354
355     /* -- Attribute accessors -- */
356
357     /**
358      * Tests whether the application can read the file denoted by this
359      * abstract pathname.
360      *
361      * @return  true if and only if the file specified by this
362      *          abstract pathname exists and can be read by the
363      *          application; false otherwise
364      */
365     function canRead() {
366         $fs = FileSystem::getFileSystem();
367
368         if ($fs->checkAccess($this)) {
369             return (boolean) @is_readable($this->getAbsolutePath());
370         }
371         return false;
372     }
373
374     /**
375      * Tests whether the application can modify to the file denoted by this
376      * abstract pathname.
377      *
378      * @return  true if and only if the file system actually
379      *          contains a file denoted by this abstract pathname and
380      *          the application is allowed to write to the file;
381      *          false otherwise.
382      *
383      */
384     function canWrite() {
385         $fs = FileSystem::getFileSystem();
386         return $fs->checkAccess($this, true);
387     }
388
389     /**
390      * Tests whether the file denoted by this abstract pathname exists.
391      *
392      * @return  true if and only if the file denoted by this
393      *          abstract pathname exists; false otherwise
394      *
395      */
396     function exists() {               
397         if ($this->isFile()) {
398             return @file_exists($this->path);
399         } else {
400             return @is_dir($this->path);
401         }
402     }
403
404     /**
405      * Tests whether the file denoted by this abstract pathname is a
406      * directory.
407      *
408      * @return true if and only if the file denoted by this
409      *         abstract pathname exists and is a directory;
410      *         false otherwise
411      *
412      */
413     function isDirectory() {
414         $fs = FileSystem::getFileSystem();
415         if ($fs->checkAccess($this) !== true) {
416             throw new IOException("No read access to ".$this->path);
417         }
418         return @is_dir($this->path);
419     }
420
421     /**
422      * Tests whether the file denoted by this abstract pathname is a normal
423      * file.  A file is normal if it is not a directory and, in
424      * addition, satisfies other system-dependent criteria.  Any non-directory
425      * file created by a Java application is guaranteed to be a normal file.
426      *
427      * @return  true if and only if the file denoted by this
428      *          abstract pathname exists and is a normal file;
429      *          false otherwise
430      */
431     function isFile() {
432         //$fs = FileSystem::getFileSystem();
433         return @is_file($this->path);
434     }
435
436     /**
437      * Tests whether the file named by this abstract pathname is a hidden
438      * file.  The exact definition of hidden is system-dependent.  On
439      * UNIX systems, a file is considered to be hidden if its name begins with
440      * a period character ('.').  On Win32 systems, a file is considered to be
441      * hidden if it has been marked as such in the filesystem. Currently there
442      * seems to be no way to dermine isHidden on Win file systems via PHP
443      *
444      * @return  true if and only if the file denoted by this
445      *          abstract pathname is hidden according to the conventions of the
446      *          underlying platform
447      */
448     function isHidden() {
449         $fs = FileSystem::getFileSystem();
450         if ($fs->checkAccess($this) !== true) {
451             throw new IOException("No read access to ".$this->path);
452         }
453         return (($fs->getBooleanAttributes($this) & $fs->BA_HIDDEN) !== 0);
454     }
455
456     /**
457      * Returns the time that the file denoted by this abstract pathname was
458      * last modified.
459      *
460      * @return  A integer value representing the time the file was
461      *          last modified, measured in milliseconds since the epoch
462      *          (00:00:00 GMT, January 1, 1970), or 0 if the
463      *          file does not exist or if an I/O error occurs
464      */
465     function lastModified() {
466         $fs = FileSystem::getFileSystem();
467         if ($fs->checkAccess($this) !== true) {
468             throw new IOException("No read access to " . $this->path);
469         }
470         return $fs->getLastModifiedTime($this);
471     }
472
473     /**
474      * Returns the length of the file denoted by this abstract pathname.
475      * The return value is unspecified if this pathname denotes a directory.
476      *
477      * @return  The length, in bytes, of the file denoted by this abstract
478      *          pathname, or 0 if the file does not exist
479      */
480     function length() {
481         $fs = FileSystem::getFileSystem();
482         if ($fs->checkAccess($this) !== true) {
483             throw new IOException("No read access to ".$this->path."\n");
484         }
485         return $fs->getLength($this);
486     }
487
488     /**
489      * Convenience method for returning the contents of this file as a string.
490      * This method uses file_get_contents() to read file in an optimized way.
491      * @return string
492      * @throws Exception - if file cannot be read
493      */
494     function contents() {
495         if (!$this->canRead() || !$this->isFile()) {
496             throw new IOException("Cannot read file contents!");
497         }
498         return file_get_contents($this->getAbsolutePath());
499     }
500     
501     /* -- File operations -- */
502
503     /**
504      * Atomically creates a new, empty file named by this abstract pathname if
505      * and only if a file with this name does not yet exist.  The check for the
506      * existence of the file and the creation of the file if it does not exist
507      * are a single operation that is atomic with respect to all other
508      * filesystem activities that might affect the file.
509      *
510      * @return  true if the named file does not exist and was
511      *          successfully created; <code>false</code> if the named file
512      *          already exists
513      * @throws IOException if file can't be created
514      */
515     function createNewFile($parents=true, $mode=0777) {
516         $file = FileSystem::getFileSystem()->createNewFile($this->path);
517         return $file;
518     }
519
520     /**
521      * Deletes the file or directory denoted by this abstract pathname.  If
522      * this pathname denotes a directory, then the directory must be empty in
523      * order to be deleted.
524      *
525      * @return  true if and only if the file or directory is
526      *          successfully deleted; false otherwise
527      */
528     function delete() {
529         $fs = FileSystem::getFileSystem();
530         if ($fs->checkAccess($this, true) !== true) {
531             throw new IOException("No read access to " . $this->path."\n");
532         }
533         return $fs->delete($this);
534     }
535
536     /**
537      * Requests that the file or directory denoted by this abstract pathname
538      * be deleted when php terminates.  Deletion will be attempted only for
539      * normal termination of php and if and if only Phing::shutdown() is
540      * called.
541      *
542      * Once deletion has been requested, it is not possible to cancel the
543      * request.  This method should therefore be used with care.
544      *
545      */
546     function deleteOnExit() {
547         $fs = FileSystem::getFileSystem();
548         $fs->deleteOnExit($this);
549     }
550
551     /**
552      * Returns an array of strings naming the files and directories in the
553      * directory denoted by this abstract pathname.
554      *
555      * If this abstract pathname does not denote a directory, then this
556      * method returns null  Otherwise an array of strings is
557      * returned, one for each file or directory in the directory.  Names
558      * denoting the directory itself and the directory's parent directory are
559      * not included in the result.  Each string is a file name rather than a
560      * complete path.
561      *
562      * There is no guarantee that the name strings in the resulting array
563      * will appear in any specific order; they are not, in particular,
564      * guaranteed to appear in alphabetical order.
565      *
566      * @return  An array of strings naming the files and directories in the
567      *          directory denoted by this abstract pathname.  The array will be
568      *          empty if the directory is empty.  Returns null if
569      *          this abstract pathname does not denote a directory, or if an
570      *          I/O error occurs.
571      *
572      */
573     function listDir($filter = null) {
574         $fs = FileSystem::getFileSystem();
575         return $fs->lister($this, $filter);
576     }
577
578     function listFiles($filter = null) {
579         $ss = $this->listDir($filter);
580         if ($ss === null) {
581             return null;
582         }
583         $n = count($ss);
584         $fs = array();
585         for ($i = 0; $i < $n; $i++) {
586             $fs[$i] = new PhingFile((string)$this->path, (string)$ss[$i]);
587         }
588         return $fs;
589     }
590
591     /**
592      * Creates the directory named by this abstract pathname, including any
593      * necessary but nonexistent parent directories.  Note that if this
594      * operation fails it may have succeeded in creating some of the necessary
595      * parent directories.
596      *
597      * @return  true if and only if the directory was created,
598      *          along with all necessary parent directories; false
599      *          otherwise
600      * @throws  IOException
601      */
602     function mkdirs() {
603         if ($this->exists()) {
604             return false;
605         }
606         try {
607             if ($this->mkdir()) {
608                 return true;
609             }
610         } catch (IOException $ioe) {
611             // IOException from mkdir() means that directory propbably didn't exist.
612         }       
613         $parentFile = $this->getParentFile();
614         return (($parentFile !==