Passed
Push — master ( 972120...1c77fc )
by Michiel
08:19
created

AppendTask   F

Complexity

Total Complexity 79

Size/Duplication

Total Lines 412
Duplicated Lines 0 %

Test Coverage

Coverage 77.84%

Importance

Changes 0
Metric Value
eloc 166
dl 0
loc 412
rs 2.08
c 0
b 0
f 0
ccs 137
cts 176
cp 0.7784
wmc 79

21 Methods

Rating   Name   Duplication   Size   Complexity  
B appendFile() 0 17 7
B appendFiles() 0 33 10
B checkFilename() 0 31 9
A appendFooter() 0 13 3
A appendHeader() 0 14 3
B setEol() 0 11 7
A addHeader() 0 3 1
A setFiltering() 0 3 1
A setFile() 0 3 1
A setFixLastLine() 0 3 1
A addText() 0 3 1
A getFilteredReader() 0 3 1
A sanitizeText() 0 4 3
A addFooter() 0 3 1
F main() 0 74 16
A createPath() 0 5 1
A setOverwrite() 0 3 1
A setDestFile() 0 3 1
A setAppend() 0 3 1
A setText() 0 3 1
B validate() 0 14 9

How to fix   Complexity   

Complex Class

Complex classes like AppendTask 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 AppendTask, 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
namespace Phing\Tasks\System;
21
22
use Exception;
23
use Phing\Exception\BuildException;
24
use Phing\Exception\NullPointerException;
25
use Phing\Io\FileReader;
26
use Phing\Io\FileUtils;
27
use Phing\Io\FileWriter;
28
use Phing\Io\IOException;
29
use Phing\Io\LogWriter;
30
use Phing\Io\File;
31
use Phing\Io\Reader;
32
use Phing\Io\StringReader;
33
use Phing\Io\Writer;
34
use Phing\Project;
35
use Phing\Task;
36
use Phing\Tasks\System\AppendTask\TextElement;
37
use Phing\Type\Element\FileListAware;
38
use Phing\Type\Element\FileSetAware;
39
use Phing\Type\Element\FilterChainAware;
40
use Phing\Type\FileSet;
41
use Phing\Type\Path;
42
use Phing\Util\Register;
43
44
/**
45
 *  Appends text, contents of a file or set of files defined by a filelist to a destination file.
46
 *
47
 * <code>
48
 * <append text="And another thing\n" destfile="badthings.log"/>
49
 * </code>
50
 * OR
51
 * <code>
52
 * <append file="header.html" destfile="fullpage.html"/>
53
 * <append file="body.html" destfile="fullpage.html"/>
54
 * <append file="footer.html" destfile="fullpage.html"/>
55
 * </code>
56
 * OR
57
 * <code>
58
 * <append destfile="${process.outputfile}">
59
 *    <filterchain>
60
 *        <xsltfilter style="${process.stylesheet}">
61
 *            <param name="mode" expression="${process.xslt.mode}"/>
62
 *            <param name="file_name" expression="%{task.append.current_file.basename}"/> <!-- Example of using a RegisterSlot variable -->
63
 *        </xsltfilter>
64
 *    </filterchain>
65
 *     <filelist dir="book/" listfile="book/PhingGuide.book"/>
66
 * </append>
67
 * </code>
68
 *
69
 * @package phing.tasks.system
70
 */
71
class AppendTask extends Task
72
{
73
    use FileListAware;
74
    use FileSetAware;
75
    use FilterChainAware;
76
77
    /**
78
     * Append stuff to this file.
79
     */
80
    private $to;
81
82
    /**
83
     * Explicit file to append.
84
     */
85
    private $file;
86
87
    /**
88
     * Text to append. (cannot be used in conjunction w/ files or filesets)
89
     */
90
    private $text;
91
92
    private $filtering = true;
93
94
    /**
95
     * @var TextElement $header
96
     */
97
    private $header;
98
99
    /**
100
     * @var TextElement $footer
101
     */
102
    private $footer;
103
104
    private $append = true;
105
106
    private $fixLastLine = false;
107
108
    private $overwrite = true;
109
110
    private $eolString;
111
112
    /**
113
     * @param bool $filtering
114
     */
115
    public function setFiltering(bool $filtering)
116
    {
117
        $this->filtering = $filtering;
118
    }
119
120
    /**
121
     * @param bool $overwrite
122
     */
123 1
    public function setOverwrite($overwrite)
124
    {
125 1
        $this->overwrite = $overwrite;
126 1
    }
127
128
    /**
129
     * The more conventional naming for method to set destination file.
130
     *
131
     * @param File $f
132
     *
133
     * @return void
134
     */
135 11
    public function setDestFile(File $f)
136
    {
137 11
        $this->to = $f;
138 11
    }
139
140
    /**
141
     * Sets the behavior when the destination exists. If set to
142
     * <code>true</code> the task will append the stream data an
143
     * {@link Appendable} resource; otherwise existing content will be
144
     * overwritten. Defaults to <code>false</code>.
145
     *
146
     * @param bool $append if true append output.
147
     */
148 1
    public function setAppend($append)
149
    {
150 1
        $this->append = $append;
151 1
    }
152
153
    /**
154
     * Specify the end of line to find and to add if
155
     * not present at end of each input file. This attribute
156
     * is used in conjunction with fixlastline.
157
     *
158
     * @param string $crlf the type of new line to add -
159
     *              cr, mac, lf, unix, crlf, or dos
160
     */
161 1
    public function setEol($crlf)
162
    {
163 1
        $s = $crlf;
164 1
        if ($s === "cr" || $s === "mac") {
165 1
            $this->eolString = "\r";
166
        } elseif ($s === "lf" || $s === "unix") {
167
            $this->eolString = "\n";
168
        } elseif ($s === "crlf" || $s === "dos") {
169
            $this->eolString = "\r\n";
170
        } else {
171
            $this->eolString = $this->getProject()->getProperty('line.separator');
172
        }
173 1
    }
174
175
    /**
176
     * Sets specific file to append.
177
     *
178
     * @param File $f
179
     */
180
    public function setFile(File $f)
181
    {
182
        $this->file = $f;
183
    }
184
185 9
    public function createPath()
186
    {
187 9
        $path = new Path($this->getProject());
188 9
        $this->filesets[] = $path;
189 9
        return $path;
190
    }
191
192
    /**
193
     * Sets text to append.  (cannot be used in conjunction w/ files or filesets).
194
     *
195
     * @param string $txt
196
     *
197
     * @return void
198
     */
199
    public function setText($txt)
200
    {
201
        $this->text = (string) $txt;
202
    }
203
204
    /**
205
     * Sets text to append. Supports CDATA.
206
     *
207
     * @param string $txt
208
     *
209
     * @return void
210
     */
211 11
    public function addText($txt)
212
    {
213 11
        $this->text .= (string) $txt;
214 11
    }
215
216 2
    public function addHeader(TextElement $headerToAdd)
217
    {
218 2
        $this->header = $headerToAdd;
219 2
    }
220
221 1
    public function addFooter(TextElement $footerToAdd)
222
    {
223 1
        $this->footer = $footerToAdd;
224 1
    }
225
226
    /**
227
     * Append line.separator to files that do not end
228
     * with a line.separator, default false.
229
     *
230
     * @param bool $fixLastLine if true make sure each input file has
231
     *                          new line on the concatenated stream
232
     */
233 2
    public function setFixLastLine($fixLastLine)
234
    {
235 2
        $this->fixLastLine = $fixLastLine;
236 2
    }
237
238
    /**
239
     * Append the file(s).
240
     *
241
     * {@inheritdoc}
242
     */
243 15
    public function main()
244
    {
245 15
        $this->validate();
246
247
        try {
248 14
            if ($this->to !== null) {
249
                // create a file writer to append to "to" file.
250 11
                $writer = new FileWriter($this->to, $this->append);
251
            } else {
252 6
                $writer = new LogWriter($this);
253
            }
254
255 13
            if ($this->text !== null) {
256
                // simply append the text
257 10
                if ($this->to instanceof File) {
258 8
                    $this->log("Appending string to " . $this->to->getPath());
259
                }
260
261 10
                $text = $this->text;
262 10
                if ($this->filtering) {
263 10
                    $fr = $this->getFilteredReader(new StringReader($text));
264 10
                    $text = $fr->read();
265
                }
266
267 10
                $text = $this->appendHeader($text);
268 10
                $text = $this->appendFooter($text);
269 10
                $writer->write($text);
270
            } else {
271
                // append explicitly-specified file
272 10
                if ($this->file !== null) {
273
                    try {
274
                        $this->appendFile($writer, $this->file);
275
                    } catch (Exception $ioe) {
276
                        $this->log(
277
                            "Unable to append contents of file " . $this->file->getAbsolutePath() . ": " . $ioe->getMessage(),
278
                            Project::MSG_WARN
279
                        );
280
                    }
281
                }
282
283
                // append any files in filesets
284 10
                foreach ($this->filesets as $fs) {
285
                    try {
286 10
                        if ($fs instanceof Path) {
287 9
                            $files = $fs->listPaths();
288 9
                            $this->appendFiles($writer, $files);
289 1
                        } elseif ($fs instanceof FileSet) {
290 1
                            $files = $fs->getDirectoryScanner($this->project)->getIncludedFiles();
291 8
                            $this->appendFiles($writer, $files, $fs->getDir($this->project));
292
                        }
293 2
                    } catch (BuildException $be) {
294 2
                        if (strpos($be->getMessage(), 'is the same as the output file') === false) {
295 1
                            $this->log($be->getMessage(), Project::MSG_WARN);
296
                        } else {
297 2
                            throw new BuildException($be->getMessage());
298
                        }
299
                    } catch (IOException $ioe) {
300
                        throw new BuildException($ioe);
301
                    }
302
                }
303
304 12
                foreach ($this->filelists as $list) {
305
                    $dir = $list->getDir($this->project);
306
                    $files = $list->getFiles($this->project);
307
                    foreach ($files as $file) {
308
                        $this->appendFile($writer, new File($dir, $file));
309
                    }
310
                }
311
            }
312 2
        } catch (Exception $e) {
313 2
            throw new BuildException($e);
314
        }
315
316 12
        $writer->close();
317 12
    }
318
319 10
    private function appendHeader($string)
320
    {
321 10
        $result = $string;
322 10
        if ($this->header !== null) {
323 2
            $header = $this->header->getValue();
324 2
            if ($this->header->filtering) {
325 1
                $fr = $this->getFilteredReader(new StringReader($header));
326 1
                $header = $fr->read();
327
            }
328
329 2
            $result = $header . $string;
330
        }
331
332 10
        return $result;
333
    }
334
335 10
    private function appendFooter($string)
336
    {
337 10
        $result = $string;
338 10
        if ($this->footer !== null) {
339 1
            $footer = $this->footer->getValue();
340 1
            if ($this->footer->filtering) {
341
                $fr = $this->getFilteredReader(new StringReader($footer));
342
                $footer = $fr->read();
343
            }
344
345 1
            $result = $string . $footer;
346
        }
347 10
        return $result;
348
    }
349
350 15
    private function validate()
351
    {
352 15
        $this->sanitizeText();
353
354 15
        if ($this->file === null && $this->text === null && count($this->filesets) === 0 && count($this->filelists) === 0) {
355 1
            throw new BuildException("You must specify a file, use a filelist/fileset, or specify a text value.");
356
        }
357
358 14
        if ($this->text !== null && ($this->file !== null || count($this->filesets) > 0)) {
359
            throw new BuildException("Cannot use text attribute in conjunction with file or fileset");
360
        }
361
362 14
        if (!$this->eolString) {
363 14
            $this->eolString = $this->getProject()->getProperty('line.separator');
364
        }
365 14
    }
366
367 15
    private function sanitizeText()
368
    {
369 15
        if ($this->text !== null && "" === trim($this->text)) {
370
            $this->text = null;
371
        }
372 15
    }
373
374 10
    private function getFilteredReader(Reader $r)
375
    {
376 10
        return FileUtils::getChainedReader($r, $this->filterChains, $this->getProject());
377
    }
378
379
    /**
380
     * Append an array of files in a directory.
381
     *
382
     * @param Writer $writer The FileWriter that is appending to target file.
383
     * @param array $files array of files to delete; can be of zero length
384
     * @param File $dir directory to work from
385
     *
386
     * @return void
387
     */
388 9
    private function appendFiles(Writer $writer, $files, File $dir = null)
389
    {
390 9
        if (!empty($files)) {
391 9
            $this->log(
392 9
                "Attempting to append " . count(
393 9
                    $files
394 9
                ) . " files" . ($dir !== null ? ", using basedir " . $dir->getPath() : "")
395
            );
396 9
            $basenameSlot = Register::getSlot("task.append.current_file");
397 9
            $pathSlot = Register::getSlot("task.append.current_file.path");
398 9
            foreach ($files as $file) {
399
                try {
400 9
                    if (!$this->checkFilename($file, $dir)) {
401 1
                        continue;
402
                    }
403
404 7
                    if ($dir !== null) {
405
                        $file = is_string($file) ? new File($dir->getPath(), $file) : $file;
406
                    } else {
407 7
                        $file = is_string($file) ? new File($file) : $file;
408
                    }
409 7
                    $basenameSlot->setValue($file);
410 7
                    $pathSlot->setValue($file->getPath());
411 7
                    $this->appendFile($writer, $file);
412 1
                } catch (IOException $ioe) {
413
                    $this->log(
414
                        "Unable to append contents of file " . $file . ": " . $ioe->getMessage(),
415
                        Project::MSG_WARN
416
                    );
417 1
                } catch (NullPointerException $npe) {
418
                    $this->log(
419
                        "Unable to append contents of file " . $file . ": " . $npe->getMessage(),
420
                        Project::MSG_WARN
421
                    );
422
                }
423
            }
424
        }
425 8
    }
426
427 9
    private function checkFilename($filename, $dir = null)
428
    {
429 9
        if ($dir !== null) {
430
            $f = new File($dir, $filename);
431
        } else {
432 9
            $f = new File($filename);
433
        }
434
435 9
        if (!$f->exists()) {
436 1
            $this->log("File " . (string) $f . " does not exist.", Project::MSG_ERR);
437 1
            return false;
438
        }
439 8
        if ($this->to !== null && $f->equals($this->to)) {
440 1
            throw new BuildException(
441
                "Input file \""
442 1
                . $f . "\" "
443 1
                . "is the same as the output file."
444
            );
445
        }
446
447
        if (
448 7
            $this->to !== null
449 7
            && !$this->overwrite
450 7
            && $this->to->exists()
451 7
            && $f->lastModified() > $this->to->lastModified()
452
        ) {
453
            $this->log((string) $this->to . " is up-to-date.", Project::MSG_VERBOSE);
454
            return false;
455
        }
456
457 7
        return true;
458
    }
459
460
    /**
461
     * @param FileWriter $writer
462
     * @param File $f
463
     *
464
     * @return void
465
     */
466 7
    private function appendFile(Writer $writer, File $f)
467
    {
468 7
        $in = $this->getFilteredReader(new FileReader($f));
469
470 7
        $text = '';
471 7
        while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
472 7
            $text .= $buffer;
473
        }
474 7
        if ($this->fixLastLine && ($text[strlen($text) - 1] !== "\n" || $text[strlen($text) - 1] !== "\r")) {
475 2
            $text .= $this->eolString;
476
        }
477
478 7
        $text = $this->appendHeader($text);
479 7
        $text = $this->appendFooter($text);
480 7
        $writer->write($text);
481 7
        if ($f instanceof File && $this->to instanceof File) {
482 4
            $this->log("Appending contents of " . $f->getPath() . " to " . $this->to->getPath());
483
        }
484 7
    }
485
}
486