Completed
Push — master ( 9e0037...b2adab )
by Siad
12:57
created

IntrospectionHelper   F

Complexity

Total Complexity 85

Size/Duplication

Total Lines 606
Duplicated Lines 0 %

Test Coverage

Coverage 67.26%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 233
c 2
b 0
f 0
dl 0
loc 606
rs 2
ccs 152
cts 226
cp 0.6726
wmc 85

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getHelper() 0 7 2
A isContainer() 0 3 1
D __construct() 0 133 29
A supportsCharacters() 0 3 1
A storeElement() 0 19 4
A getElementName() 0 32 6
A getNestedElements() 0 3 1
A warn() 0 4 2
C setAttribute() 0 91 16
C createElement() 0 95 17
A getAttributes() 0 8 2
A getPropertyName() 0 5 1
A addText() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like IntrospectionHelper 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 IntrospectionHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
/**
21
 * Helper class that collects the methods that a task or nested element
22
 * holds to set attributes, create nested elements or hold PCDATA
23
 * elements.
24
 *
25
 *<ul>
26
 * <li><strong>SMART-UP INLINE DOCS</strong></li>
27
 * <li><strong>POLISH-UP THIS CLASS</strong></li>
28
 *</ul>
29
 *
30
 * @author    Andreas Aderhold <[email protected]>
31
 * @author    Hans Lellelid <[email protected]>
32
 * @copyright 2001,2002 THYRELL. All rights reserved
33
 * @package   phing
34
 */
35
class IntrospectionHelper
36
{
37
38
    /**
39
     * Holds the attribute setter methods.
40
     *
41
     * @var array string[]
42
     */
43
    private $attributeSetters = [];
44
45
    /**
46
     * Holds methods to create nested elements.
47
     *
48
     * @var array string[]
49
     */
50
    private $nestedCreators = [];
51
52
    /**
53
     * Holds methods to store configured nested elements.
54
     *
55
     * @var array string[]
56
     */
57
    private $nestedStorers = [];
58
59
    /**
60
     * Map from attribute names to nested types.
61
     */
62
    private $nestedTypes = [];
63
64
    /**
65
     * New idea in phing: any class can register certain
66
     * keys -- e.g. "task.current_file" -- which can be used in
67
     * task attributes, if supported.  In the build XML these
68
     * are referred to like this:
69
     *         <regexp pattern="\n" replace="%{task.current_file}"/>
70
     * In the type/task a listener method must be defined:
71
     *         function setListeningReplace($slot) {}
72
     *
73
     * @var array string[]
74
     */
75
    private $slotListeners = [];
76
77
    /**
78
     * The method to add PCDATA stuff.
79
     *
80
     * @var string Method name of the addText (redundant?) method, if class supports it :)
81
     */
82
    private $methodAddText = null;
83
84
    /**
85
     * The Class that's been introspected.
86
     *
87
     * @var object
88
     */
89
    private $bean;
90
91
    /**
92
     * The cache of IntrospectionHelper classes instantiated by getHelper().
93
     *
94
     * @var array IntrospectionHelpers[]
95
     */
96
    private static $helpers = [];
97
98
    /**
99
     * Factory method for helper objects.
100
     *
101
     * @param  string $class The class to create a Helper for
102
     * @return IntrospectionHelper
103
     */
104 633
    public static function getHelper($class)
105
    {
106 633
        if (!isset(self::$helpers[$class])) {
107 143
            self::$helpers[$class] = new IntrospectionHelper($class);
108
        }
109
110 633
        return self::$helpers[$class];
111
    }
112
113
    /**
114
     * This function constructs a new introspection helper for a specific class.
115
     *
116
     * This method loads all methods for the specified class and categorizes them
117
     * as setters, creators, slot listeners, etc.  This way, the setAttribue() doesn't
118
     * need to perform any introspection -- either the requested attribute setter/creator
119
     * exists or it does not & a BuildException is thrown.
120
     *
121
     * @param  string $class The classname for this IH.
122
     * @throws BuildException
123
     */
124 143
    public function __construct($class)
125
    {
126 143
        $this->bean = new ReflectionClass($class);
127
128
        //$methods = get_class_methods($bean);
129 143
        foreach ($this->bean->getMethods() as $method) {
130 143
            if ($method->isPublic()) {
131
                // We're going to keep case-insensitive method names
132
                // for as long as we're allowed :)  It makes it much
133
                // easier to map XML attributes to PHP class method names.
134 143
                $name = strtolower($method->getName());
135
136
                // There are a few "reserved" names that might look like attribute setters
137
                // but should actually just be skipped.  (Note: this means you can't ever
138
                // have an attribute named "location" or "tasktype" or a nested element container
139
                // named "task" [TaskContainer::addTask(Task)].)
140
                if (
141 143
                    $name === "setlocation"
142 143
                    || $name === "settasktype"
143 143
                    || ('addtask' === $name
144 2
                    && $this->isContainer()
145 2
                    && count($method->getParameters()) === 1
146 2
                        && Task::class === $method->getParameters()[0])
147
                ) {
148 114
                    continue;
149
                }
150
151 143
                if ($name === "addtext") {
152 14
                    $this->methodAddText = $method;
153 143
                } elseif (strpos($name, "setlistening") === 0) {
154
                    // Phing supports something unique called "RegisterSlots"
155
                    // These are dynamic values that use a basic slot system so that
156
                    // classes can register to listen to specific slots, and the value
157
                    // will always be grabbed from the slot (and never set in the project
158
                    // component).  This is useful for things like tracking the current
159
                    // file being processed by a filter (e.g. AppendTask sets an append.current_file
160
                    // slot, which can be ready by the XSLTParam type.)
161
162 1
                    if (count($method->getParameters()) !== 1) {
163
                        throw new BuildException(
164
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() must take exactly one parameter."
165
                        );
166
                    }
167
168 1
                    $this->slotListeners[$name] = $method;
169 143
                } elseif (strpos($name, "set") === 0 && count($method->getParameters()) === 1) {
170 136
                    $this->attributeSetters[$name] = $method;
171 143
                } elseif (strpos($name, "create") === 0) {
172 48
                    if ($method->getNumberOfRequiredParameters() > 0) {
173 1
                        throw new BuildException(
174 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() may not take any parameters."
175
                        );
176
                    }
177
178 47
                    if ($method->hasReturnType()) {
179 3
                        $this->nestedTypes[$name] = $method->getReturnType();
180
                    } else {
181 44
                        preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches);
182 44
                        if (!empty($matches[1]) && class_exists($matches[1], false)) {
183 32
                            $this->nestedTypes[$name] = $matches[1];
184
                        } else {
185
                            // assume that method createEquals() creates object of type "Equals"
186
                            // (that example would be false, of course)
187 27
                            $this->nestedTypes[$name] = $this->getPropertyName($name, "create");
188
                        }
189
                    }
190
191 47
                    $this->nestedCreators[$name] = $method;
192 143
                } elseif (strpos($name, "addconfigured") === 0) {
193
                    // *must* use class hints if using addConfigured ...
194
195
                    // 1 param only
196 2
                    $params = $method->getParameters();
197
198 2
                    if (count($params) < 1) {
199
                        throw new BuildException(
200
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() must take at least one parameter."
201
                        );
202
                    }
203
204 2
                    if (count($params) > 1) {
205
                        $this->warn(
206
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() takes more than one parameter. (IH only uses the first)"
207
                        );
208
                    }
209
210 2
                    $classname = null;
211
212 2
                    if (($hint = $params[0]->getClass()) !== null) {
213 1
                        $classname = $hint->getName();
214
                    }
215
216 2
                    if ($classname === null) {
217 1
                        throw new BuildException(
218 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() method MUST use a class hint to indicate the class type of parameter."
219
                        );
220
                    }
221
222 1
                    $this->nestedTypes[$name] = $classname;
223
224 1
                    $this->nestedStorers[$name] = $method;
225 143
                } elseif (strpos($name, "add") === 0) {
226
                    // *must* use class hints if using add ...
227
228
                    // 1 param only
229 45
                    $params = $method->getParameters();
230 45
                    if (count($params) < 1) {
231
                        throw new BuildException(
232
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() must take at least one parameter."
233
                        );
234
                    }
235
236 45
                    if (count($params) > 1) {
237
                        $this->warn(
238
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() takes more than one parameter. (IH only uses the first)"
239
                        );
240
                    }
241
242 45
                    $classname = null;
243
244 45
                    if (($hint = $params[0]->getClass()) !== null) {
245 44
                        $classname = $hint->getName();
246
                    }
247
248
                    // we don't use the classname here, but we need to make sure it exists before
249
                    // we later try to instantiate a non-existent class
250 45
                    if ($classname === null) {
251 1
                        throw new BuildException(
252 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() method MUST use a class hint to indicate the class type of parameter."
253
                        );
254
                    }
255
256 44
                    $this->nestedCreators[$name] = $method;
257
                }
258
            } // if $method->isPublic()
259
        } // foreach
260 142
    }
261
262
    /**
263
     * Indicates whether the introspected class is a task container, supporting arbitrary nested tasks/types.
264
     *
265
     * @return bool true if the introspected class is a container; false otherwise.
266
     */
267 2
    public function isContainer()
268
    {
269 2
        return $this->bean->implementsInterface(TaskContainer::class);
270
    }
271
272
    /**
273
     * Sets the named attribute.
274
     *
275
     * @param  Project $project
276
     * @param  object $element
277
     * @param  string $attributeName
278
     * @param  mixed $value
279
     * @throws BuildException
280
     */
281 613
    public function setAttribute(Project $project, $element, $attributeName, &$value)
282
    {
283
        // we want to check whether the value we are setting looks like
284
        // a slot-listener variable:  %{task.current_file}
285
        //
286
        // slot-listener variables are not like properties, in that they cannot be mixed with
287
        // other text values.  The reason for this disparity is that properties are only
288
        // set when first constructing objects from XML, whereas slot-listeners are always dynamic.
289
        //
290
        // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose
291
        // typing.
292 613
        if (StringHelper::isSlotVar($value)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression 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...
293
            $as = "setlistening" . strtolower($attributeName);
294
295
            if (!isset($this->slotListeners[$as])) {
296
                $msg = $this->getElementName(
297
                    $project,
298
                    $element
299
                ) . " doesn't support a slot-listening '$attributeName' attribute.";
300
                throw new BuildException($msg);
301
            }
302
303
            $method = $this->slotListeners[$as];
304
305
            $key = StringHelper::slotVar($value);
306
            $value = Register::getSlot(
307
                $key
308
            ); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue())
309
        } else {
310
            // Traditional value options
311
312 613
            $as = "set" . strtolower($attributeName);
313
314 613
            if (!isset($this->attributeSetters[$as])) {
315 4
                if ($element instanceof DynamicAttribute) {
316 2
                    $element->setDynamicAttribute($attributeName, (string) $value);
317 2
                    return;
318
                }
319 2
                $msg = $this->getElementName($project, $element) . " doesn't support the '$attributeName' attribute.";
320 2
                throw new BuildException($msg);
321
            }
322
323 613
            $method = $this->attributeSetters[$as];
324
325 613
            if ($as == "setrefid") {
326 19
                $value = new Reference($project, $value);
327
            } else {
328 613
                $params = $method->getParameters();
329 613
                $reflectedAttr = null;
330
331
                // try to determine parameter type
332 613
                if (($argType = $params[0]->getType()) !== null) {
333
                    /** @var ReflectionNamedType $argType */
334 503
                    $reflectedAttr = $argType->getName();
335 585
                } elseif (($classType = $params[0]->getClass()) !== null) {
336
                    /** @var ReflectionClass $classType */
337
                    $reflectedAttr = $classType->getName();
338
                }
339
340
                // value is a string representation of a boolean type,
341
                // convert it to primitive
342 613
                if ($reflectedAttr === 'bool' || ($reflectedAttr !== 'string' && StringHelper::isBoolean($value))) {
343 242
                    $value = StringHelper::booleanValue($value);
344
                }
345
346
                // there should only be one param; we'll just assume ....
347 613
                if ($reflectedAttr !== null) {
348 503
                    switch (strtolower($reflectedAttr)) {
349 503
                        case "phingfile":
350 232
                            $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 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

350
                            $value = $project->resolveFile(/** @scrutinizer ignore-type */ $value);
Loading history...
351 232
                            break;
352 438
                        case "path":
353 15
                            $value = new Path($project, $value);
354 15
                            break;
355 424
                        case "reference":
356
                            $value = new Reference($project, $value);
357
                            break;
358
                        // any other object params we want to support should go here ...
359
                    }
360
                } // if hint !== null
361
            } // if not setrefid
362
        } // if is slot-listener
363
364
        try {
365 613
            $project->log(
366 613
                "    -calling setter " . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
367 613
                Project::MSG_DEBUG
368
            );
369 613
            $method->invoke($element, $value);
370 4
        } catch (Exception $exc) {
371 4
            throw new BuildException($exc->getMessage(), $exc);
372
        }
373 612
    }
374
375
    /**
376
     * Adds PCDATA areas.
377
     *
378
     * @param  Project $project
379
     * @param  string $element
380
     * @param  string $text
381
     * @throws BuildException
382
     */
383 61
    public function addText(Project $project, $element, $text)
384
    {
385 61
        if ($this->methodAddText === null) {
386 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 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

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