Security update for Core, with self-updated composer
[yaffs-website] / vendor / doctrine / annotations / lib / Doctrine / Common / Annotations / DocParser.php
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 MIT license. For more information, see
17  * <http://www.doctrine-project.org>.
18  */
19
20 namespace Doctrine\Common\Annotations;
21
22 use Doctrine\Common\Annotations\Annotation\Attribute;
23 use ReflectionClass;
24 use Doctrine\Common\Annotations\Annotation\Enum;
25 use Doctrine\Common\Annotations\Annotation\Target;
26 use Doctrine\Common\Annotations\Annotation\Attributes;
27
28 /**
29  * A parser for docblock annotations.
30  *
31  * It is strongly discouraged to change the default annotation parsing process.
32  *
33  * @author Benjamin Eberlei <kontakt@beberlei.de>
34  * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
35  * @author Jonathan Wage <jonwage@gmail.com>
36  * @author Roman Borschel <roman@code-factory.org>
37  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
38  * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
39  */
40 final class DocParser
41 {
42     /**
43      * An array of all valid tokens for a class name.
44      *
45      * @var array
46      */
47     private static $classIdentifiers = array(
48         DocLexer::T_IDENTIFIER,
49         DocLexer::T_TRUE,
50         DocLexer::T_FALSE,
51         DocLexer::T_NULL
52     );
53
54     /**
55      * The lexer.
56      *
57      * @var \Doctrine\Common\Annotations\DocLexer
58      */
59     private $lexer;
60
61     /**
62      * Current target context.
63      *
64      * @var integer
65      */
66     private $target;
67
68     /**
69      * Doc parser used to collect annotation target.
70      *
71      * @var \Doctrine\Common\Annotations\DocParser
72      */
73     private static $metadataParser;
74
75     /**
76      * Flag to control if the current annotation is nested or not.
77      *
78      * @var boolean
79      */
80     private $isNestedAnnotation = false;
81
82     /**
83      * Hashmap containing all use-statements that are to be used when parsing
84      * the given doc block.
85      *
86      * @var array
87      */
88     private $imports = array();
89
90     /**
91      * This hashmap is used internally to cache results of class_exists()
92      * look-ups.
93      *
94      * @var array
95      */
96     private $classExists = array();
97
98     /**
99      * Whether annotations that have not been imported should be ignored.
100      *
101      * @var boolean
102      */
103     private $ignoreNotImportedAnnotations = false;
104
105     /**
106      * An array of default namespaces if operating in simple mode.
107      *
108      * @var string[]
109      */
110     private $namespaces = array();
111
112     /**
113      * A list with annotations that are not causing exceptions when not resolved to an annotation class.
114      *
115      * The names must be the raw names as used in the class, not the fully qualified
116      * class names.
117      *
118      * @var bool[] indexed by annotation name
119      */
120     private $ignoredAnnotationNames = array();
121
122     /**
123      * A list with annotations in namespaced format
124      * that are not causing exceptions when not resolved to an annotation class.
125      *
126      * @var bool[] indexed by namespace name
127      */
128     private $ignoredAnnotationNamespaces = array();
129
130     /**
131      * @var string
132      */
133     private $context = '';
134
135     /**
136      * Hash-map for caching annotation metadata.
137      *
138      * @var array
139      */
140     private static $annotationMetadata = array(
141         'Doctrine\Common\Annotations\Annotation\Target' => array(
142             'is_annotation'    => true,
143             'has_constructor'  => true,
144             'properties'       => array(),
145             'targets_literal'  => 'ANNOTATION_CLASS',
146             'targets'          => Target::TARGET_CLASS,
147             'default_property' => 'value',
148             'attribute_types'  => array(
149                 'value'  => array(
150                     'required'  => false,
151                     'type'      =>'array',
152                     'array_type'=>'string',
153                     'value'     =>'array<string>'
154                 )
155              ),
156         ),
157         'Doctrine\Common\Annotations\Annotation\Attribute' => array(
158             'is_annotation'    => true,
159             'has_constructor'  => false,
160             'targets_literal'  => 'ANNOTATION_ANNOTATION',
161             'targets'          => Target::TARGET_ANNOTATION,
162             'default_property' => 'name',
163             'properties'       => array(
164                 'name'      => 'name',
165                 'type'      => 'type',
166                 'required'  => 'required'
167             ),
168             'attribute_types'  => array(
169                 'value'  => array(
170                     'required'  => true,
171                     'type'      =>'string',
172                     'value'     =>'string'
173                 ),
174                 'type'  => array(
175                     'required'  =>true,
176                     'type'      =>'string',
177                     'value'     =>'string'
178                 ),
179                 'required'  => array(
180                     'required'  =>false,
181                     'type'      =>'boolean',
182                     'value'     =>'boolean'
183                 )
184              ),
185         ),
186         'Doctrine\Common\Annotations\Annotation\Attributes' => array(
187             'is_annotation'    => true,
188             'has_constructor'  => false,
189             'targets_literal'  => 'ANNOTATION_CLASS',
190             'targets'          => Target::TARGET_CLASS,
191             'default_property' => 'value',
192             'properties'       => array(
193                 'value' => 'value'
194             ),
195             'attribute_types'  => array(
196                 'value' => array(
197                     'type'      =>'array',
198                     'required'  =>true,
199                     'array_type'=>'Doctrine\Common\Annotations\Annotation\Attribute',
200                     'value'     =>'array<Doctrine\Common\Annotations\Annotation\Attribute>'
201                 )
202              ),
203         ),
204         'Doctrine\Common\Annotations\Annotation\Enum' => array(
205             'is_annotation'    => true,
206             'has_constructor'  => true,
207             'targets_literal'  => 'ANNOTATION_PROPERTY',
208             'targets'          => Target::TARGET_PROPERTY,
209             'default_property' => 'value',
210             'properties'       => array(
211                 'value' => 'value'
212             ),
213             'attribute_types'  => array(
214                 'value' => array(
215                     'type'      => 'array',
216                     'required'  => true,
217                 ),
218                 'literal' => array(
219                     'type'      => 'array',
220                     'required'  => false,
221                 ),
222              ),
223         ),
224     );
225
226     /**
227      * Hash-map for handle types declaration.
228      *
229      * @var array
230      */
231     private static $typeMap = array(
232         'float'     => 'double',
233         'bool'      => 'boolean',
234         // allow uppercase Boolean in honor of George Boole
235         'Boolean'   => 'boolean',
236         'int'       => 'integer',
237     );
238
239     /**
240      * Constructs a new DocParser.
241      */
242     public function __construct()
243     {
244         $this->lexer = new DocLexer;
245     }
246
247     /**
248      * Sets the annotation names that are ignored during the parsing process.
249      *
250      * The names are supposed to be the raw names as used in the class, not the
251      * fully qualified class names.
252      *
253      * @param bool[] $names indexed by annotation name
254      *
255      * @return void
256      */
257     public function setIgnoredAnnotationNames(array $names)
258     {
259         $this->ignoredAnnotationNames = $names;
260     }
261
262     /**
263      * Sets the annotation namespaces that are ignored during the parsing process.
264      *
265      * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name
266      *
267      * @return void
268      */
269     public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces)
270     {
271         $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces;
272     }
273
274     /**
275      * Sets ignore on not-imported annotations.
276      *
277      * @param boolean $bool
278      *
279      * @return void
280      */
281     public function setIgnoreNotImportedAnnotations($bool)
282     {
283         $this->ignoreNotImportedAnnotations = (boolean) $bool;
284     }
285
286     /**
287      * Sets the default namespaces.
288      *
289      * @param string $namespace
290      *
291      * @return void
292      *
293      * @throws \RuntimeException
294      */
295     public function addNamespace($namespace)
296     {
297         if ($this->imports) {
298             throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
299         }
300
301         $this->namespaces[] = $namespace;
302     }
303
304     /**
305      * Sets the imports.
306      *
307      * @param array $imports
308      *
309      * @return void
310      *
311      * @throws \RuntimeException
312      */
313     public function setImports(array $imports)
314     {
315         if ($this->namespaces) {
316             throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
317         }
318
319         $this->imports = $imports;
320     }
321
322     /**
323      * Sets current target context as bitmask.
324      *
325      * @param integer $target
326      *
327      * @return void
328      */
329     public function setTarget($target)
330     {
331         $this->target = $target;
332     }
333
334     /**
335      * Parses the given docblock string for annotations.
336      *
337      * @param string $input   The docblock string to parse.
338      * @param string $context The parsing context.
339      *
340      * @return array Array of annotations. If no annotations are found, an empty array is returned.
341      */
342     public function parse($input, $context = '')
343     {
344         $pos = $this->findInitialTokenPosition($input);
345         if ($pos === null) {
346             return array();
347         }
348
349         $this->context = $context;
350
351         $this->lexer->setInput(trim(substr($input, $pos), '* /'));
352         $this->lexer->moveNext();
353
354         return $this->Annotations();
355     }
356
357     /**
358      * Finds the first valid annotation
359      *
360      * @param string $input The docblock string to parse
361      *
362      * @return int|null
363      */
364     private function findInitialTokenPosition($input)
365     {
366         $pos = 0;
367
368         // search for first valid annotation
369         while (($pos = strpos($input, '@', $pos)) !== false) {
370             $preceding = substr($input, $pos - 1, 1);
371
372             // if the @ is preceded by a space, a tab or * it is valid
373             if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") {
374                 return $pos;
375             }
376
377             $pos++;
378         }
379
380         return null;
381     }
382
383     /**
384      * Attempts to match the given token with the current lookahead token.
385      * If they match, updates the lookahead token; otherwise raises a syntax error.
386      *
387      * @param integer $token Type of token.
388      *
389      * @return boolean True if tokens match; false otherwise.
390      */
391     private function match($token)
392     {
393         if ( ! $this->lexer->isNextToken($token) ) {
394             $this->syntaxError($this->lexer->getLiteral($token));
395         }
396
397         return $this->lexer->moveNext();
398     }
399
400     /**
401      * Attempts to match the current lookahead token with any of the given tokens.
402      *
403      * If any of them matches, this method updates the lookahead token; otherwise
404      * a syntax error is raised.
405      *
406      * @param array $tokens
407      *
408      * @return boolean
409      */
410     private function matchAny(array $tokens)
411     {
412         if ( ! $this->lexer->isNextTokenAny($tokens)) {
413             $this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens)));
414         }
415
416         return $this->lexer->moveNext();
417     }
418
419     /**
420      * Generates a new syntax error.
421      *
422      * @param string     $expected Expected string.
423      * @param array|null $token    Optional token.
424      *
425      * @return void
426      *
427      * @throws AnnotationException
428      */
429     private function syntaxError($expected, $token = null)
430     {
431         if ($token === null) {
432             $token = $this->lexer->lookahead;
433         }
434
435         $message  = sprintf('Expected %s, got ', $expected);
436         $message .= ($this->lexer->lookahead === null)
437             ? 'end of string'
438             : sprintf("'%s' at position %s", $token['value'], $token['position']);
439
440         if (strlen($this->context)) {
441             $message .= ' in ' . $this->context;
442         }
443
444         $message .= '.';
445
446         throw AnnotationException::syntaxError($message);
447     }
448
449     /**
450      * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism
451      * but uses the {@link AnnotationRegistry} to load classes.
452      *
453      * @param string $fqcn
454      *
455      * @return boolean
456      */
457     private function classExists($fqcn)
458     {
459         if (isset($this->classExists[$fqcn])) {
460             return $this->classExists[$fqcn];
461         }
462
463         // first check if the class already exists, maybe loaded through another AnnotationReader
464         if (class_exists($fqcn, false)) {
465             return $this->classExists[$fqcn] = true;
466         }
467
468         // final check, does this class exist?
469         return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn);
470     }
471
472     /**
473      * Collects parsing metadata for a given annotation class
474      *
475      * @param string $name The annotation name
476      *
477      * @return void
478      */
479     private function collectAnnotationMetadata($name)
480     {
481         if (self::$metadataParser === null) {
482             self::$metadataParser = new self();
483
484             self::$metadataParser->setIgnoreNotImportedAnnotations(true);
485             self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames);
486             self::$metadataParser->setImports(array(
487                 'enum'          => 'Doctrine\Common\Annotations\Annotation\Enum',
488                 'target'        => 'Doctrine\Common\Annotations\Annotation\Target',
489                 'attribute'     => 'Doctrine\Common\Annotations\Annotation\Attribute',
490                 'attributes'    => 'Doctrine\Common\Annotations\Annotation\Attributes'
491             ));
492
493             AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Enum.php');
494             AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Target.php');
495             AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attribute.php');
496             AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attributes.php');
497         }
498
499         $class      = new \ReflectionClass($name);
500         $docComment = $class->getDocComment();
501
502         // Sets default values for annotation metadata
503         $metadata = array(
504             'default_property' => null,
505             'has_constructor'  => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0,
506             'properties'       => array(),
507             'property_types'   => array(),
508             'attribute_types'  => array(),
509             'targets_literal'  => null,
510             'targets'          => Target::TARGET_ALL,
511             'is_annotation'    => false !== strpos($docComment, '@Annotation'),
512         );
513
514         // verify that the class is really meant to be an annotation
515         if ($metadata['is_annotation']) {
516             self::$metadataParser->setTarget(Target::TARGET_CLASS);
517
518             foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) {
519                 if ($annotation instanceof Target) {
520                     $metadata['targets']         = $annotation->targets;
521                     $metadata['targets_literal'] = $annotation->literal;
522
523                     continue;
524                 }
525
526                 if ($annotation instanceof Attributes) {
527                     foreach ($annotation->value as $attribute) {
528                         $this->collectAttributeTypeMetadata($metadata, $attribute);
529                     }
530                 }
531             }
532
533             // if not has a constructor will inject values into public properties
534             if (false === $metadata['has_constructor']) {
535                 // collect all public properties
536                 foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
537                     $metadata['properties'][$property->name] = $property->name;
538
539                     if (false === ($propertyComment = $property->getDocComment())) {
540                         continue;
541                     }
542
543                     $attribute = new Attribute();
544
545                     $attribute->required = (false !== strpos($propertyComment, '@Required'));
546                     $attribute->name     = $property->name;
547                     $attribute->type     = (false !== strpos($propertyComment, '@var') && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches))
548                         ? $matches[1]
549                         : 'mixed';
550
551                     $this->collectAttributeTypeMetadata($metadata, $attribute);
552
553                     // checks if the property has @Enum
554                     if (false !== strpos($propertyComment, '@Enum')) {
555                         $context = 'property ' . $class->name . "::\$" . $property->name;
556
557                         self::$metadataParser->setTarget(Target::TARGET_PROPERTY);
558
559                         foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) {
560                             if ( ! $annotation instanceof Enum) {
561                                 continue;
562                             }
563
564                             $metadata['enum'][$property->name]['value']   = $annotation->value;
565                             $metadata['enum'][$property->name]['literal'] = ( ! empty($annotation->literal))
566                                 ? $annotation->literal
567                                 : $annotation->value;
568                         }
569                     }
570                 }
571
572                 // choose the first property as default property
573                 $metadata['default_property'] = reset($metadata['properties']);
574             }
575         }
576
577         self::$annotationMetadata[$name] = $metadata;
578     }
579
580     /**
581      * Collects parsing metadata for a given attribute.
582      *
583      * @param array     $metadata
584      * @param Attribute $attribute
585      *
586      * @return void
587      */
588     private function collectAttributeTypeMetadata(&$metadata, Attribute $attribute)
589     {
590         // handle internal type declaration
591         $type = isset(self::$typeMap[$attribute->type])
592             ? self::$typeMap[$attribute->type]
593             : $attribute->type;
594
595         // handle the case if the property type is mixed
596         if ('mixed' === $type) {
597             return;
598         }
599
600         // Evaluate type
601         switch (true) {
602             // Checks if the property has array<type>
603             case (false !== $pos = strpos($type, '<')):
604                 $arrayType  = substr($type, $pos + 1, -1);
605                 $type       = 'array';
606
607                 if (isset(self::$typeMap[$arrayType])) {
608                     $arrayType = self::$typeMap[$arrayType];
609                 }
610
611                 $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
612                 break;
613
614             // Checks if the property has type[]
615             case (false !== $pos = strrpos($type, '[')):
616                 $arrayType  = substr($type, 0, $pos);
617                 $type       = 'array';
618
619                 if (isset(self::$typeMap[$arrayType])) {
620                     $arrayType = self::$typeMap[$arrayType];
621                 }
622
623                 $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
624                 break;
625         }
626
627         $metadata['attribute_types'][$attribute->name]['type']     = $type;
628         $metadata['attribute_types'][$attribute->name]['value']    = $attribute->type;
629         $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required;
630     }
631
632     /**
633      * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
634      *
635      * @return array
636      */
637     private function Annotations()
638     {
639         $annotations = array();
640
641         while (null !== $this->lexer->lookahead) {
642             if (DocLexer::T_AT !== $this->lexer->lookahead['type']) {
643                 $this->lexer->moveNext();
644                 continue;
645             }
646
647             // make sure the @ is preceded by non-catchable pattern
648             if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
649                 $this->lexer->moveNext();
650                 continue;
651             }
652
653             // make sure the @ is followed by either a namespace separator, or
654             // an identifier token
655             if ((null === $peek = $this->lexer->glimpse())
656                 || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
657                 || $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
658                 $this->lexer->moveNext();
659                 continue;
660             }
661
662             $this->isNestedAnnotation = false;
663             if (false !== $annot = $this->Annotation()) {
664                 $annotations[] = $annot;
665             }
666         }
667
668         return $annotations;
669     }
670
671     /**
672      * Annotation     ::= "@" AnnotationName MethodCall
673      * AnnotationName ::= QualifiedName | SimpleName
674      * QualifiedName  ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
675      * NameSpacePart  ::= identifier | null | false | true
676      * SimpleName     ::= identifier | null | false | true
677      *
678      * @return mixed False if it is not a valid annotation.
679      *
680      * @throws AnnotationException
681      */
682     private function Annotation()
683     {
684         $this->match(DocLexer::T_AT);
685
686         // check if we have an annotation
687         $name = $this->Identifier();
688
689         // only process names which are not fully qualified, yet
690         // fully qualified names must start with a \
691         $originalName = $name;
692
693         if ('\\' !== $name[0]) {
694             $pos = strpos($name, '\\');
695             $alias = (false === $pos)? $name : substr($name, 0, $pos);
696             $found = false;
697             $loweredAlias = strtolower($alias);
698
699             if ($this->namespaces) {
700                 foreach ($this->namespaces as $namespace) {
701                     if ($this->classExists($namespace.'\\'.$name)) {
702                         $name = $namespace.'\\'.$name;
703                         $found = true;
704                         break;
705                     }
706                 }
707             } elseif (isset($this->imports[$loweredAlias])) {
708                 $found = true;
709                 $name  = (false !== $pos)
710                     ? $this->imports[$loweredAlias] . substr($name, $pos)
711                     : $this->imports[$loweredAlias];
712             } elseif ( ! isset($this->ignoredAnnotationNames[$name])
713                 && isset($this->imports['__NAMESPACE__'])
714                 && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)
715             ) {
716                 $name  = $this->imports['__NAMESPACE__'].'\\'.$name;
717                 $found = true;
718             } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) {
719                 $found = true;
720             }
721
722             if ( ! $found) {
723                 if ($this->isIgnoredAnnotation($name)) {
724                     return false;
725                 }
726
727                 throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?', $name, $this->context));
728             }
729         }
730
731         $name = ltrim($name,'\\');
732
733         if ( ! $this->classExists($name)) {
734             throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context));
735         }
736
737         // at this point, $name contains the fully qualified class name of the
738         // annotation, and it is also guaranteed that this class exists, and
739         // that it is loaded
740
741
742         // collects the metadata annotation only if there is not yet
743         if ( ! isset(self::$annotationMetadata[$name])) {
744             $this->collectAnnotationMetadata($name);
745         }
746
747         // verify that the class is really meant to be an annotation and not just any ordinary class
748         if (self::$annotationMetadata[$name]['is_annotation'] === false) {
749             if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$originalName])) {
750                 return false;
751             }
752
753             throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context));
754         }
755
756         //if target is nested annotation
757         $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target;
758
759         // Next will be nested
760         $this->isNestedAnnotation = true;
761
762         //if annotation does not support current target
763         if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) {
764             throw AnnotationException::semanticalError(
765                 sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.',
766                      $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal'])
767             );
768         }
769
770         $values = $this->MethodCall();
771
772         if (isset(self::$annotationMetadata[$name]['enum'])) {
773             // checks all declared attributes
774             foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) {
775                 // checks if the attribute is a valid enumerator
776                 if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) {
777                     throw AnnotationException::enumeratorError($property, $name, $this->context, $enum['literal'], $values[$property]);
778                 }
779             }
780         }
781
782         // checks all declared attributes
783         foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) {
784             if ($property === self::$annotationMetadata[$name]['default_property']
785                 && !isset($values[$property]) && isset($values['value'])) {
786                 $property = 'value';
787             }
788
789             // handle a not given attribute or null value
790             if (!isset($values[$property])) {
791                 if ($type['required']) {
792                     throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']);
793                 }
794
795                 continue;
796             }
797
798             if ($type['type'] === 'array') {
799                 // handle the case of a single value
800                 if ( ! is_array($values[$property])) {
801                     $values[$property] = array($values[$property]);
802                 }
803
804                 // checks if the attribute has array type declaration, such as "array<string>"
805                 if (isset($type['array_type'])) {
806                     foreach ($values[$property] as $item) {
807                         if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) {
808                             throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item);
809                         }
810                     }
811                 }
812             } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) {
813                 throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]);
814             }
815         }
816
817         // check if the annotation expects values via the constructor,
818         // or directly injected into public properties
819         if (self::$annotationMetadata[$name]['has_constructor'] === true) {
820             return new $name($values);
821         }
822
823         $instance = new $name();
824
825         foreach ($values as $property => $value) {
826             if (!isset(self::$annotationMetadata[$name]['properties'][$property])) {
827                 if ('value' !== $property) {
828                     throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not have a property named "%s". Available properties: %s', $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties'])));
829                 }
830
831                 // handle the case if the property has no annotations
832                 if ( ! $property = self::$annotationMetadata[$name]['default_property']) {
833                     throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values)));
834                 }
835             }
836
837             $instance->{$property} = $value;
838         }
839
840         return $instance;
841     }
842
843     /**
844      * MethodCall ::= ["(" [Values] ")"]
845      *
846      * @return array
847      */
848     private function MethodCall()
849     {
850         $values = array();
851
852         if ( ! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
853             return $values;
854         }
855
856         $this->match(DocLexer::T_OPEN_PARENTHESIS);
857
858         if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
859             $values = $this->Values();
860         }
861
862         $this->match(DocLexer::T_CLOSE_PARENTHESIS);
863
864         return $values;
865     }
866
867     /**
868      * Values ::= Array | Value {"," Value}* [","]
869      *
870      * @return array
871      */
872     private function Values()
873     {
874         $values = array($this->Value());
875
876         while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
877             $this->match(DocLexer::T_COMMA);
878
879             if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
880                 break;
881             }
882
883             $token = $this->lexer->lookahead;
884             $value = $this->Value();
885
886             if ( ! is_object($value) && ! is_array($value)) {
887                 $this->syntaxError('Value', $token);
888             }
889
890             $values[] = $value;
891         }
892
893         foreach ($values as $k => $value) {
894             if (is_object($value) && $value instanceof \stdClass) {
895                 $values[$value->name] = $value->value;
896             } else if ( ! isset($values['value'])){
897                 $values['value'] = $value;
898             } else {
899                 if ( ! is_array($values['value'])) {
900                     $values['value'] = array($values['value']);
901                 }
902
903                 $values['value'][] = $value;
904             }
905
906             unset($values[$k]);
907         }
908
909         return $values;
910     }
911
912     /**
913      * Constant ::= integer | string | float | boolean
914      *
915      * @return mixed
916      *
917      * @throws AnnotationException
918      */
919     private function Constant()
920     {
921         $identifier = $this->Identifier();
922
923         if ( ! defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) {
924             list($className, $const) = explode('::', $identifier);
925
926             $pos = strpos($className, '\\');
927             $alias = (false === $pos) ? $className : substr($className, 0, $pos);
928             $found = false;
929             $loweredAlias = strtolower($alias);
930
931             switch (true) {
932                 case !empty ($this->namespaces):
933                     foreach ($this->namespaces as $ns) {
934                         if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
935                              $className = $ns.'\\'.$className;
936                              $found = true;
937                              break;
938                         }
939                     }
940                     break;
941
942                 case isset($this->imports[$loweredAlias]):
943                     $found     = true;
944                     $className = (false !== $pos)
945                         ? $this->imports[$loweredAlias] . substr($className, $pos)
946                         : $this->imports[$loweredAlias];
947                     break;
948
949                 default:
950                     if(isset($this->imports['__NAMESPACE__'])) {
951                         $ns = $this->imports['__NAMESPACE__'];
952
953                         if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
954                             $className = $ns.'\\'.$className;
955                             $found = true;
956                         }
957                     }
958                     break;
959             }
960
961             if ($found) {
962                  $identifier = $className . '::' . $const;
963             }
964         }
965
966         // checks if identifier ends with ::class, \strlen('::class') === 7
967         $classPos = stripos($identifier, '::class');
968         if ($classPos === strlen($identifier) - 7) {
969             return substr($identifier, 0, $classPos);
970         }
971
972         if (!defined($identifier)) {
973             throw AnnotationException::semanticalErrorConstants($identifier, $this->context);
974         }
975
976         return constant($identifier);
977     }
978
979     /**
980      * Identifier ::= string
981      *
982      * @return string
983      */
984     private function Identifier()
985     {
986         // check if we have an annotation
987         if ( ! $this->lexer->isNextTokenAny(self::$classIdentifiers)) {
988             $this->syntaxError('namespace separator or identifier');
989         }
990
991         $this->lexer->moveNext();
992
993         $className = $this->lexer->token['value'];
994
995         while ($this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value']))
996                 && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) {
997
998             $this->match(DocLexer::T_NAMESPACE_SEPARATOR);
999             $this->matchAny(self::$classIdentifiers);
1000
1001             $className .= '\\' . $this->lexer->token['value'];
1002         }
1003
1004         return $className;
1005     }
1006
1007     /**
1008      * Value ::= PlainValue | FieldAssignment
1009      *
1010      * @return mixed
1011      */
1012     private function Value()
1013     {
1014         $peek = $this->lexer->glimpse();
1015
1016         if (DocLexer::T_EQUALS === $peek['type']) {
1017             return $this->FieldAssignment();
1018         }
1019
1020         return $this->PlainValue();
1021     }
1022
1023     /**
1024      * PlainValue ::= integer | string | float | boolean | Array | Annotation
1025      *
1026      * @return mixed
1027      */
1028     private function PlainValue()
1029     {
1030         if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
1031             return $this->Arrayx();
1032         }
1033
1034         if ($this->lexer->isNextToken(DocLexer::T_AT)) {
1035             return $this->Annotation();
1036         }
1037
1038         if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
1039             return $this->Constant();
1040         }
1041
1042         switch ($this->lexer->lookahead['type']) {
1043             case DocLexer::T_STRING:
1044                 $this->match(DocLexer::T_STRING);
1045                 return $this->lexer->token['value'];
1046
1047             case DocLexer::T_INTEGER:
1048                 $this->match(DocLexer::T_INTEGER);
1049                 return (int)$this->lexer->token['value'];
1050
1051             case DocLexer::T_FLOAT:
1052                 $this->match(DocLexer::T_FLOAT);
1053                 return (float)$this->lexer->token['value'];
1054
1055             case DocLexer::T_TRUE:
1056                 $this->match(DocLexer::T_TRUE);
1057                 return true;
1058
1059             case DocLexer::T_FALSE:
1060                 $this->match(DocLexer::T_FALSE);
1061                 return false;
1062
1063             case DocLexer::T_NULL:
1064                 $this->match(DocLexer::T_NULL);
1065                 return null;
1066
1067             default:
1068                 $this->syntaxError('PlainValue');
1069         }
1070     }
1071
1072     /**
1073      * FieldAssignment ::= FieldName "=" PlainValue
1074      * FieldName ::= identifier
1075      *
1076      * @return \stdClass
1077      */
1078     private function FieldAssignment()
1079     {
1080         $this->match(DocLexer::T_IDENTIFIER);
1081         $fieldName = $this->lexer->token['value'];
1082
1083         $this->match(DocLexer::T_EQUALS);
1084
1085         $item = new \stdClass();
1086         $item->name  = $fieldName;
1087         $item->value = $this->PlainValue();
1088
1089         return $item;
1090     }
1091
1092     /**
1093      * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}"
1094      *
1095      * @return array
1096      */
1097     private function Arrayx()
1098     {
1099         $array = $values = array();
1100
1101         $this->match(DocLexer::T_OPEN_CURLY_BRACES);
1102
1103         // If the array is empty, stop parsing and return.
1104         if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
1105             $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
1106
1107             return $array;
1108         }
1109
1110         $values[] = $this->ArrayEntry();
1111
1112         while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
1113             $this->match(DocLexer::T_COMMA);
1114
1115             // optional trailing comma
1116             if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
1117                 break;
1118             }
1119
1120             $values[] = $this->ArrayEntry();
1121         }
1122
1123         $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
1124
1125         foreach ($values as $value) {
1126             list ($key, $val) = $value;
1127
1128             if ($key !== null) {
1129                 $array[$key] = $val;
1130             } else {
1131                 $array[] = $val;
1132             }
1133         }
1134
1135         return $array;
1136     }
1137
1138     /**
1139      * ArrayEntry ::= Value | KeyValuePair
1140      * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant
1141      * Key ::= string | integer | Constant
1142      *
1143      * @return array
1144      */
1145     private function ArrayEntry()
1146     {
1147         $peek = $this->lexer->glimpse();
1148
1149         if (DocLexer::T_EQUALS === $peek['type']
1150                 || DocLexer::T_COLON === $peek['type']) {
1151
1152             if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
1153                 $key = $this->Constant();
1154             } else {
1155                 $this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));
1156                 $key = $this->lexer->token['value'];
1157             }
1158
1159             $this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON));
1160
1161             return array($key, $this->PlainValue());
1162         }
1163
1164         return array(null, $this->Value());
1165     }
1166
1167     /**
1168      * Checks whether the given $name matches any ignored annotation name or namespace
1169      *
1170      * @param string $name
1171      *
1172      * @return bool
1173      */
1174     private function isIgnoredAnnotation($name)
1175     {
1176         if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) {
1177             return true;
1178         }
1179
1180         foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) {
1181             $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\';
1182
1183             if (0 === stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace)) {
1184                 return true;
1185             }
1186         }
1187
1188         return false;
1189     }
1190 }