Passed
Push — master ( e1f86a...4e1a3a )
by Siad
05:23
created

IntrospectionHelper::storeElement()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 5
nop 4
dl 0
loc 19
ccs 0
cts 12
cp 0
crap 20
rs 9.8666
c 0
b 0
f 0
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;
21
22
use Exception;
23
use Phing\Io\File;
24
use Phing\Type\Path;
25
use Phing\Exception\BuildException;
26
use Phing\Parser\CustomChildCreator;
27
use Phing\Parser\DynamicAttribute;
28
use Phing\Phing;
29
use Phing\Project;
30
use Phing\ProjectComponent;
31
use Phing\TaskContainer;
32
use Phing\Util\Register;
33
use Phing\Util\StringHelper;
34
use Phing\Type\Reference;
35
use ReflectionClass;
36
use Phing\Task;
37
38
/**
39
 * Helper class that collects the methods that a task or nested element
40
 * holds to set attributes, create nested elements or hold PCDATA
41
 * elements.
42
 *
43
 *<ul>
44
 * <li><strong>SMART-UP INLINE DOCS</strong></li>
45
 * <li><strong>POLISH-UP THIS CLASS</strong></li>
46
 *</ul>
47
 *
48
 * @author    Andreas Aderhold <[email protected]>
49
 * @author    Hans Lellelid <[email protected]>
50
 * @copyright 2001,2002 THYRELL. All rights reserved
51
 * @package   phing
52
 */
53
class IntrospectionHelper
54
{
55
56
    /**
57
     * Holds the attribute setter methods.
58
     *
59
     * @var array string[]
60
     */
61
    private $attributeSetters = [];
62
63
    /**
64
     * Holds methods to create nested elements.
65
     *
66
     * @var array string[]
67
     */
68
    private $nestedCreators = [];
69
70
    /**
71
     * Holds methods to store configured nested elements.
72
     *
73
     * @var array string[]
74
     */
75
    private $nestedStorers = [];
76
77
    /**
78
     * Map from attribute names to nested types.
79
     */
80
    private $nestedTypes = [];
81
82
    /**
83
     * New idea in phing: any class can register certain
84
     * keys -- e.g. "task.current_file" -- which can be used in
85
     * task attributes, if supported.  In the build XML these
86
     * are referred to like this:
87
     *         <regexp pattern="\n" replace="%{task.current_file}"/>
88
     * In the type/task a listener method must be defined:
89
     *         function setListeningReplace($slot) {}
90
     *
91
     * @var array string[]
92
     */
93
    private $slotListeners = [];
94
95
    /**
96
     * The method to add PCDATA stuff.
97
     *
98
     * @var string Method name of the addText (redundant?) method, if class supports it :)
99
     */
100
    private $methodAddText = null;
101
102
    /**
103
     * The Class that's been introspected.
104
     *
105
     * @var object
106
     */
107
    private $bean;
108
109
    /**
110
     * The cache of IntrospectionHelper classes instantiated by getHelper().
111
     *
112
     * @var array IntrospectionHelpers[]
113
     */
114
    private static $helpers = [];
115
116
    /**
117
     * Factory method for helper objects.
118
     *
119
     * @param string $class The class to create a Helper for
120
     * @return IntrospectionHelper
121
     */
122 702
    public static function getHelper($class)
123
    {
124 702
        if (!isset(self::$helpers[$class])) {
125 147
            self::$helpers[$class] = new IntrospectionHelper($class);
126
        }
127
128 702
        return self::$helpers[$class];
129
    }
130
131
    /**
132
     * This function constructs a new introspection helper for a specific class.
133
     *
134
     * This method loads all methods for the specified class and categorizes them
135
     * as setters, creators, slot listeners, etc.  This way, the setAttribue() doesn't
136
     * need to perform any introspection -- either the requested attribute setter/creator
137
     * exists or it does not & a BuildException is thrown.
138
     *
139
     * @param string $class The classname for this IH.
140
     * @throws BuildException
141
     */
142 147
    public function __construct($class)
143
    {
144 147
        $this->bean = new ReflectionClass($class);
145
146
        //$methods = get_class_methods($bean);
147 147
        foreach ($this->bean->getMethods() as $method) {
148 147
            if ($method->isPublic()) {
149
                // We're going to keep case-insensitive method names
150
                // for as long as we're allowed :)  It makes it much
151
                // easier to map XML attributes to PHP class method names.
152 147
                $name = strtolower($method->getName());
153
154
                // There are a few "reserved" names that might look like attribute setters
155
                // but should actually just be skipped.  (Note: this means you can't ever
156
                // have an attribute named "location" or "tasktype" or a nested element container
157
                // named "task" [TaskContainer::addTask(Task)].)
158
                if (
159 147
                    $name === "setlocation"
160 147
                    || $name === "settasktype"
161 147
                    || ('addtask' === $name
162 147
                        && $this->isContainer()
163 147
                        && count($method->getParameters()) === 1
164 147
                        && Task::class === $method->getParameters()[0])
165
                ) {
166 119
                    continue;
167
                }
168
169 147
                if ($name === "addtext") {
170 14
                    $this->methodAddText = $method;
171 147
                } elseif (strpos($name, "setlistening") === 0) {
172
                    // Phing supports something unique called "RegisterSlots"
173
                    // These are dynamic values that use a basic slot system so that
174
                    // classes can register to listen to specific slots, and the value
175
                    // will always be grabbed from the slot (and never set in the project
176
                    // component).  This is useful for things like tracking the current
177
                    // file being processed by a filter (e.g. AppendTask sets an append.current_file
178
                    // slot, which can be ready by the XSLTParam type.)
179
180 1
                    if (count($method->getParameters()) !== 1) {
181
                        throw new BuildException(
182
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() must take exactly one parameter."
183
                        );
184
                    }
185
186 1
                    $this->slotListeners[$name] = $method;
187 147
                } elseif (strpos($name, "set") === 0 && count($method->getParameters()) === 1) {
188 140
                    $this->attributeSetters[$name] = $method;
189 147
                } elseif (strpos($name, "create") === 0) {
190 50
                    if ($method->getNumberOfRequiredParameters() > 0) {
191 1
                        throw new BuildException(
192 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() may not take any parameters."
193
                        );
194
                    }
195
196 49
                    if ($method->hasReturnType()) {
197 5
                        $this->nestedTypes[$name] = $method->getReturnType();
198
                    } else {
199 45
                        preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches);
200 45
                        if (!empty($matches[1]) && class_exists($matches[1], false)) {
201
                            $this->nestedTypes[$name] = $matches[1];
202
                        } else {
203
                            // assume that method createEquals() creates object of type "Equals"
204
                            // (that example would be false, of course)
205 45
                            $this->nestedTypes[$name] = $this->getPropertyName($name, "create");
206
                        }
207
                    }
208
209 49
                    $this->nestedCreators[$name] = $method;
210 147
                } elseif (strpos($name, "addconfigured") === 0) {
211
                    // *must* use class hints if using addConfigured ...
212
213
                    // 1 param only
214 2
                    $params = $method->getParameters();
215
216 2
                    if (count($params) < 1) {
217
                        throw new BuildException(
218
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() must take at least one parameter."
219
                        );
220
                    }
221
222 2
                    if (count($params) > 1) {
223
                        $this->warn(
224
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() takes more than one parameter. (IH only uses the first)"
225
                        );
226
                    }
227
228
                    /** @var \ReflectionType $hint */
229 2
                    $classname = (($hint = $params[0]->getType()) && !$hint->isBuiltin()) ? $hint->getName() : null;
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

229
                    $classname = (($hint = $params[0]->getType()) && !$hint->isBuiltin()) ? $hint->/** @scrutinizer ignore-call */ getName() : null;
Loading history...
230
231 2
                    if ($classname === null) {
232 1
                        throw new BuildException(
233 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() method MUST use a class hint to indicate the class type of parameter."
234
                        );
235
                    }
236
237 1
                    $this->nestedTypes[$name] = $classname;
238
239 1
                    $this->nestedStorers[$name] = $method;
240 147
                } elseif (strpos($name, "add") === 0) {
241
                    // *must* use class hints if using add ...
242
243
                    // 1 param only
244 50
                    $params = $method->getParameters();
245 50
                    if (count($params) < 1) {
246
                        throw new BuildException(
247
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() must take at least one parameter."
248
                        );
249
                    }
250
251 50
                    if (count($params) > 1) {
252
                        $this->warn(
253
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() takes more than one parameter. (IH only uses the first)"
254
                        );
255
                    }
256
257
                    /** @var \ReflectionType $hint */
258 50
                    $classname = (($hint = $params[0]->getType()) && !$hint->isBuiltin()) ? $hint->getName() : null;
259
260
                    // we don't use the classname here, but we need to make sure it exists before
261
                    // we later try to instantiate a non-existent class
262 50
                    if ($classname === null) {
263 1
                        throw new BuildException(
264 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() method MUST use a class hint to indicate the class type of parameter."
265
                        );
266
                    }
267
268 49
                    $this->nestedCreators[$name] = $method;
269
                }
270
            } // if $method->isPublic()
271
        } // foreach
272 146
    }
273
274
    /**
275
     * Indicates whether the introspected class is a task container, supporting arbitrary nested tasks/types.
276
     *
277
     * @return bool true if the introspected class is a container; false otherwise.
278
     */
279 2
    public function isContainer()
280
    {
281 2
        return $this->bean->implementsInterface(TaskContainer::class);
282
    }
283
284
    /**
285
     * Sets the named attribute.
286
     *
287
     * @param Project $project
288
     * @param object $element
289
     * @param string $attributeName
290
     * @param mixed $value
291
     * @throws BuildException
292
     */
293 683
    public function setAttribute(Project $project, $element, $attributeName, &$value)
294
    {
295
        // we want to check whether the value we are setting looks like
296
        // a slot-listener variable:  %{task.current_file}
297
        //
298
        // slot-listener variables are not like properties, in that they cannot be mixed with
299
        // other text values.  The reason for this disparity is that properties are only
300
        // set when first constructing objects from XML, whereas slot-listeners are always dynamic.
301
        //
302
        // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose
303
        // typing.
304 683
        if (StringHelper::isSlotVar($value)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Phing\Util\StringHelper::isSlotVar($value) of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
305
            $as = "setlistening" . strtolower($attributeName);
306
307
            if (!isset($this->slotListeners[$as])) {
308
                $msg = $this->getElementName(
309
                    $project,
310
                    $element
311
                ) . " doesn't support a slot-listening '$attributeName' attribute.";
312
                throw new BuildException($msg);
313
            }
314
315
            $method = $this->slotListeners[$as];
316
317
            $key = StringHelper::slotVar($value);
318
            $value = Register::getSlot(
319
                $key
320
            ); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue())
321
        } else {
322
            // Traditional value options
323
324 683
            $as = "set" . strtolower($attributeName);
325
326 683
            if (!isset($this->attributeSetters[$as])) {
327 7
                if ($element instanceof DynamicAttribute) {
328 2
                    $element->setDynamicAttribute($attributeName, (string) $value);
329 2
                    return;
330
                }
331 5
                $msg = $this->getElementName($project, $element) . " doesn't support the '$attributeName' attribute.";
332 5
                throw new BuildException($msg);
333
            }
334
335 683
            $method = $this->attributeSetters[$as];
336
337 683
            if ($as == "setrefid") {
338 20
                $value = new Reference($project, $value);
339
            } else {
340 683
                $params = $method->getParameters();
341
342
                /** @var \ReflectionType $hint */
343 683
                $reflectedAttr = ($hint = $params[0]->getType()) ? $hint->getName() : null;
344
345
                // value is a string representation of a boolean type,
346
                // convert it to primitive
347 683
                if ($reflectedAttr === 'bool' || ($reflectedAttr !== 'string' && StringHelper::isBoolean($value))) {
348 265
                    $value = StringHelper::booleanValue($value);
349
                }
350
351
                // there should only be one param; we'll just assume ....
352 683
                if ($reflectedAttr !== null) {
353 589
                    switch ($reflectedAttr) {
354
                        case File::class:
355 321
                            $value = $project->resolveFile($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type boolean; however, parameter $fileName of Phing\Project::resolveFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

355
                            $value = $project->resolveFile(/** @scrutinizer ignore-type */ $value);
Loading history...
356 321
                            break;
357
                        case Path::class:
358 15
                            $value = new Path($project, $value);
359 15
                            break;
360
                        case Reference::class:
361
                            $value = new Reference($project, $value);
362
                            break;
363
                        // any other object params we want to support should go here ...
364
                    }
365
                } // if hint !== null
366
            } // if not setrefid
367
        } // if is slot-listener
368
369
        try {
370 683
            $project->log(
371 683
                "    -calling setter " . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
372 683
                Project::MSG_DEBUG
373
            );
374 683
            $method->invoke($element, $value);
375 11
        } catch (Exception $exc) {
376 11
            throw new BuildException($exc->getMessage(), $exc);
377
        }
378 683
    }
379
380
    /**
381
     * Adds PCDATA areas.
382
     *
383
     * @param Project $project
384
     * @param string $element
385
     * @param string $text
386
     * @throws BuildException
387
     */
388 69
    public function addText(Project $project, $element, $text)
389
    {
390 69
        if ($this->methodAddText === null) {
391 1
            $msg = $this->getElementName($project, $element) . " doesn't support nested text data.";
0 ignored issues
show
Bug introduced by
$element of type string is incompatible with the type object expected by parameter $element of Phing\IntrospectionHelper::getElementName(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

391
            $msg = $this->getElementName($project, /** @scrutinizer ignore-type */ $element) . " doesn't support nested text data.";
Loading history...
392 1
            throw new BuildException($msg);
393
        }
394
        try {
395 69
            $method = $this->methodAddText;
396 69
            $method->invoke($element, $text);
397
        } catch (Exception $exc) {
398
            throw new BuildException($exc->getMessage(), $exc);
399
        }
400 69
    }
401
402
    /**
403
     * Creates a named nested element.
404
     *
405
     * Valid creators can be in the form createFoo() or addFoo(Bar).
406
     *
407
     * @param Project $project
408
     * @param object $element Object the XML tag is child of.
409
     *                              Often a task object.
410
     * @param string $elementName XML tag name
411
     * @return object         Returns the nested element.
412
     * @throws BuildException
413
     */
414 433
    public function createElement(Project $project, $element, $elementName)
415
    {
416 433
        $addMethod = "add" . strtolower($elementName);
417 433
        $createMethod = "create" . strtolower($elementName);
418 433
        $nestedElement = null;
419
420 433
        if (isset($this->nestedCreators[$createMethod])) {
421 350
            $method = $this->nestedCreators[$createMethod];
422
            try { // try to invoke the creator method on object
423 350
                $project->log(
424 350
                    "    -calling creator " . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
425 350
                    Project::MSG_DEBUG
426
                );
427 350
                $nestedElement = $method->invoke($element);
428 1
            } catch (Exception $exc) {
429 350
                throw new BuildException($exc->getMessage(), $exc);
430
            }
431 342
        } elseif (isset($this->nestedCreators[$addMethod])) {
432 339
            $method = $this->nestedCreators[$addMethod];
433
434
            // project components must use class hints to support the add methods
435
436
            try { // try to invoke the adder method on object
437 339
                $project->log(
438 339
                    "    -calling adder " . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
439 339
                    Project::MSG_DEBUG
440
                );
441
                // we've already assured that correct num of params
442
                // exist and that method is using class hints
443 339
                $params = $method->getParameters();
444
445
                /** @var \ReflectionType $hint */
446 339
                $classname = (($hint = $params[0]->getType()) && !$hint->isBuiltin()) ? $hint->getName() : null;
447
448
                // create a new instance of the object and add it via $addMethod
449 339
                $clazz = new ReflectionClass($classname);
450 339
                if ($clazz->getConstructor() !== null && $clazz->getConstructor()->getNumberOfRequiredParameters() >= 1) {
451 4
                    $nestedElement = new $classname(Phing::getCurrentProject() ?? $project);
452
                } else {
453 339
                    $nestedElement = new $classname();
454
                }
455
456 339
                if ($nestedElement instanceof Task && $element instanceof Task) {
457 1
                    $nestedElement->setOwningTarget($element->getOwningTarget());
458
                }
459
460 339
                $method->invoke($element, $nestedElement);
461 2
            } catch (Exception $exc) {
462 339
                throw new BuildException($exc->getMessage(), $exc);
463
            }
464 4
        } elseif ($this->bean->implementsInterface(CustomChildCreator::class)) {
465 3
            $method = $this->bean->getMethod('customChildCreator');
466
467
            try {
468 3
                $nestedElement = $method->invoke($element, strtolower($elementName), $project);
469
            } catch (Exception $exc) {
470 3
                throw new BuildException($exc->getMessage(), $exc);
471
            }
472
        } else {
473
            //try the add method for the element's parent class
474 1
            $typedefs = $project->getDataTypeDefinitions();
475 1
            if (isset($typedefs[$elementName])) {
476
                $elementClass = Phing::import($typedefs[$elementName]);
477
                $parentClass = get_parent_class($elementClass);
478
                $addMethod = 'add' . strtolower($parentClass);
479
480
                if (isset($this->nestedCreators[$addMethod])) {
481
                    $method = $this->nestedCreators[$addMethod];
482
                    try {
483
                        $project->log(
484
                            "    -calling parent adder "
485
                            . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
486
                            Project::MSG_DEBUG
487
                        );
488
                        $nestedElement = new $elementClass();
489
                        $method->invoke($element, $nestedElement);
490
                    } catch (Exception $exc) {
491
                        throw new BuildException($exc->getMessage(), $exc);
492
                    }
493
                }
494
            }
495 1
            if ($nestedElement === null) {
496 1
                $msg = $this->getElementName($project, $element) . " doesn't support the '$elementName' creator/adder.";
497 1
                throw new BuildException($msg);
498
            }
499
        }
500
501 433
        if ($nestedElement instanceof ProjectComponent) {
502 397
            $nestedElement->setProject($project);
503
        }
504
505 433
        return $nestedElement;
506
    }
507
508
    /**
509
     * Creates a named nested element.
510
     *
511
     * @param Project $project
512
     * @param string $element
513
     * @param string $child
514
     * @param string|null $elementName
515
     * @return void
516
     * @throws BuildException
517
     */
518
    public function storeElement($project, $element, $child, $elementName = null)
519
    {
520
        if ($elementName === null) {
521
            return;
522
        }
523
524
        $storer = "addconfigured" . strtolower($elementName);
525
526
        if (isset($this->nestedStorers[$storer])) {
527
            $method = $this->nestedStorers[$storer];
528
529
            try {
530
                $project->log(
531
                    "    -calling storer " . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
532
                    Project::MSG_DEBUG
533
                );
534
                $method->invoke($element, $child);
535
            } catch (Exception $exc) {
536
                throw new BuildException($exc->getMessage(), $exc);
537
            }
538
        }
539
    }
540
541
    /**
542
     * Does the introspected class support PCDATA?
543
     *
544
     * @return boolean
545
     */
546 1
    public function supportsCharacters()
547
    {
548 1
        return ($this->methodAddText !== null);
549
    }
550
551
    /**
552
     * Return all attribues supported by the introspected class.
553
     *
554
     * @return string[]
555
     */
556
    public function getAttributes()
557
    {
558
        $attribs = [];
559
        foreach (array_keys($this->attributeSetters) as $setter) {
560
            $attribs[] = $this->getPropertyName($setter, "set");
561
        }
562
563
        return $attribs;
564
    }
565
566
    /**
567
     * Return all nested elements supported by the introspected class.
568
     *
569
     * @return string[]
570
     */
571
    public function getNestedElements()
572
    {
573
        return $this->nestedTypes;
574
    }
575
576
    /**
577
     * Get the name for an element.
578
     * When possible the full classnam (phing.tasks.system.PropertyTask) will
579
     * be returned.  If not available (loaded in taskdefs or typedefs) then the
580
     * XML element name will be returned.
581
     *
582
     * @param Project $project
583
     * @param object $element The Task or type element.
584
     * @return string  Fully qualified class name of element when possible.
585
     */
586 7
    public function getElementName(Project $project, $element)
587
    {
588 7
        $taskdefs = $project->getTaskDefinitions();
589 7
        $typedefs = $project->getDataTypeDefinitions();
590
591
        // check if class of element is registered with project (tasks & types)
592
        // most element types don't have a getTag() method
593 7
        $elClass = get_class($element);
594
595 7
        if (!in_array('getTag', get_class_methods($elClass))) {
596
            // loop through taskdefs and typesdefs and see if the class name
597
            // matches (case-insensitive) any of the classes in there
598 7
            foreach (array_merge($taskdefs, $typedefs) as $elName => $class) {
599 6
                if (0 === strcasecmp($elClass, StringHelper::unqualify($class))) {
600 4
                    return $class;
601
                }
602
            }
603
604 3
            return "$elClass (unknown)";
605
        }
606
607
// ->getTag() method does exist, so use it
608
        $elName = $element->getTag();
609
        if (isset($taskdefs[$elName])) {
610
            return $taskdefs[$elName];
611
        }
612
613
        if (isset($typedefs[$elName])) {
614
            return $typedefs[$elName];
615
        }
616
617
        return "$elName (unknown)";
618
    }
619
620
    /**
621
     * Extract the name of a property from a method name - subtracting  a given prefix.
622
     *
623
     * @param string $methodName
624
     * @param string $prefix
625
     * @return string
626
     */
627 45
    public function getPropertyName($methodName, $prefix)
628
    {
629 45
        $start = strlen($prefix);
630
631 45
        return strtolower(substr($methodName, $start));
632
    }
633
634
    /**
635
     * Prints warning message to screen if -debug was used.
636
     *
637
     * @param string $msg
638
     */
639
    public function warn($msg)
640
    {
641
        if (Phing::getMsgOutputLevel() === Project::MSG_DEBUG) {
642
            print("[IntrospectionHelper] " . $msg . "\n");
643
        }
644
    }
645
}
646