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