From 2598faa19838766c92db69ca0630046a03f0211e Mon Sep 17 00:00:00 2001
From: Bram Goffings <bramgoffings@gmail.com>
Date: Sat, 11 Aug 2012 09:59:40 +0200
Subject: [PATCH] annotations

---
 .../Drupal/Component/Reflection/MockFileFinder.php | 50 +++++++++++++
 .../Drupal/Core/Annotation/AnnotationInterface.php | 20 +++++
 core/lib/Drupal/Core/Annotation/Plugin.php         | 57 ++++++++++++++
 core/lib/Drupal/Core/Annotation/Translation.php    | 55 ++++++++++++++
 .../Plugin/Discovery/AnnotatedClassDiscovery.php   | 87 ++++++++++++++++++++++
 core/modules/aggregator/aggregator.module          | 13 ----
 .../Drupal/aggregator/Plugin/FetcherManager.php    |  4 +-
 .../Plugin/aggregator/fetcher/DefaultFetcher.php   |  8 ++
 8 files changed, 279 insertions(+), 15 deletions(-)
 create mode 100644 core/lib/Drupal/Component/Reflection/MockFileFinder.php
 create mode 100644 core/lib/Drupal/Core/Annotation/AnnotationInterface.php
 create mode 100644 core/lib/Drupal/Core/Annotation/Plugin.php
 create mode 100644 core/lib/Drupal/Core/Annotation/Translation.php
 create mode 100644 core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php

diff --git a/core/lib/Drupal/Component/Reflection/MockFileFinder.php b/core/lib/Drupal/Component/Reflection/MockFileFinder.php
new file mode 100644
index 0000000..140d190
--- /dev/null
+++ b/core/lib/Drupal/Component/Reflection/MockFileFinder.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Component\Reflection\MockFileFinder.
+ */
+
+namespace Drupal\Component\Reflection;
+
+use Doctrine\Common\Reflection\ClassFinderInterface;
+
+/**
+ * Defines a mock file finder that only returns a single filename.
+ *
+ * This can be used with \Doctrine\Common\Reflection\StaticReflectionParser if
+ * the filename is known and inheritance is not a concern (for example, if
+ * only the class annotation is needed).
+ */
+class MockFileFinder implements ClassFinderInterface {
+
+  /**
+   * The only filename this finder ever returns.
+   *
+   * @var string
+   */
+  protected $filename;
+
+  /**
+   * Constructs a MockFileFinder object.
+   */
+  public function __construct($prefixes) {
+  }
+
+  /**
+   * Implements \Doctrine\Common\Reflection\ClassFinderInterface::findFile().
+   */
+  public function findFile($class) {
+    return $this->filename;
+  }
+
+  /**
+   * Creates new mock file finder objects.
+   */
+  static public function create($filename) {
+    $object = new static(array());
+    $object->filename = $filename;
+    return $object;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Annotation/AnnotationInterface.php b/core/lib/Drupal/Core/Annotation/AnnotationInterface.php
new file mode 100644
index 0000000..3382d48
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/AnnotationInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Annotation\AnnotationInterface.
+ */
+
+namespace Drupal\Core\Annotation;
+
+/**
+ * Defines a common interface for classed annotations.
+ */
+interface AnnotationInterface {
+
+  /**
+   * Returns the value of an annotation.
+   */
+  public function get();
+
+}
diff --git a/core/lib/Drupal/Core/Annotation/Plugin.php b/core/lib/Drupal/Core/Annotation/Plugin.php
new file mode 100644
index 0000000..708b2b1
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/Plugin.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Annotation\Plugin.
+ */
+
+namespace Drupal\Core\Annotation;
+
+use Drupal\Core\Annotation\AnnotationInterface;
+
+/**
+ * Defines a Plugin annotation object.
+ *
+ * Annotations in a plugin classes can utilize this class in order to pass
+ * various metadata about the plugin through the parser to be made available
+ * during DiscoveryInterface::getDefinitions() calls. This allows the metadata
+ * of a class to be located with the class itself ensuring that developers do
+ * not need to hunt through module based info hooks or other methodologies in
+ * order to find this information.
+ *
+ * @Annotation
+ */
+class Plugin implements AnnotationInterface {
+
+  /**
+   * The plugin definiton read from the class annotation.
+   *
+   * @var array
+   */
+  protected $definition;
+
+  /**
+   * Constructs a Plugin object.
+   *
+   * Builds up the plugin definition and invokes the get() method for any
+   * classed annotations that were used.
+   */
+  public function __construct($values) {
+    foreach ($values as $key => $value) {
+      if ($value instanceof AnnotationInterface) {
+        $this->definition[$key] = $value->get();
+      }
+      else {
+        $this->definition[$key] = $value;
+      }
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\Annotation\AnnotationInterface::get().
+   */
+  public function get() {
+    return $this->definition;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Annotation/Translation.php b/core/lib/Drupal/Core/Annotation/Translation.php
new file mode 100644
index 0000000..1e4ec6d
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/Translation.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Annotation\Translation.
+ */
+
+namespace Drupal\Core\Annotation;
+
+use Drupal\Core\Annotation\AnnotationInterface;
+
+/**
+ * Defines a translatable annotation object.
+ *
+ * Some metadata within an annotation needs to be translatable. This class
+ * supports that need by allowing both the translatable string and, if
+ * specified, a context for that string. This class is essentially a wrapper
+ * around the traditional t() function in drupal.
+ *
+ * @Annotation
+ */
+class Translation implements AnnotationInterface {
+
+  /**
+   * The translation of the value passed to the constructor of the class.
+   *
+   * @var string
+   */
+  protected $translation;
+
+  /**
+   * Constructs a Translation object.
+   *
+   * Parses values passed into this class through the t() function in Drupal and
+   * handles an optional context for the string.
+   */
+  public function __construct($values) {
+    $string = $values['value'];
+    $options = array();
+    if (!empty($values['context'])) {
+      $options = array(
+        'context' => $values['context'],
+      );
+    }
+    $this->translation = t($string, array(), $options);
+  }
+
+  /**
+   * Implements Drupal\Core\Annotation\AnnotationInterface::get().
+   */
+  public function get() {
+    return $this->translation;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
new file mode 100644
index 0000000..969fc2f
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery.
+ */
+
+namespace Drupal\Core\Plugin\Discovery;
+
+use DirectoryIterator;
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Component\Reflection\MockFileFinder;
+use Drupal\Core\Annotation\Plugin;
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Doctrine\Common\Reflection\StaticReflectionParser;
+
+/**
+ * Defines a discovery mechanism to find annotated plugins in PSR-0 namespaces.
+ */
+class AnnotatedClassDiscovery implements DiscoveryInterface {
+
+  /**
+   * Constructs an AnnotatedClassDiscovery object.
+   */
+  function __construct($owner, $type) {
+    $this->owner = $owner;
+    $this->type = $type;
+  }
+
+  /**
+   * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinition().
+   */
+  public function getDefinition($plugin_id) {
+    $plugins = $this->getDefinitions();
+    return isset($plugins[$plugin_id]) ? $plugins[$plugin_id] : array();
+  }
+
+  /**
+   * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions().
+   */
+  public function getDefinitions() {
+    $definitions = array();
+    $reader = new AnnotationReader();
+    // Register namespace of classes that can be used for annotations.
+    AnnotationRegistry::registerAutoloadNamespace('Drupal\Core\Annotation', array(DRUPAL_ROOT . '/core/lib'));
+    // Get all PSR-0 namespaces.
+    $namespaces = drupal_classloader()->getNamespaces();
+    foreach ($namespaces as $ns => $namespace_dirs) {
+      // OS Safe directory separators.
+      $ns = str_replace('\\', DIRECTORY_SEPARATOR, $ns);
+      foreach ($namespace_dirs as $dir) {
+        // Check for the pre-determined directory structure to find plugins.
+        $prefix = implode(DIRECTORY_SEPARATOR, array(
+          $ns,
+          'Plugin',
+          $this->owner,
+          $this->type
+        ));
+        $dir .= DIRECTORY_SEPARATOR . $prefix;
+        // If the directory structure exists, look for classes
+        if (file_exists($dir)) {
+          $directories = new DirectoryIterator($dir);
+          foreach ($directories as $fileinfo) {
+            // @todo Once core requires 5.3.6 use $fileinfo->getExtension().
+            if (pathinfo($fileinfo->getFilename(), PATHINFO_EXTENSION) == 'php') {
+              $class = str_replace(DIRECTORY_SEPARATOR, '\\', "$prefix/" . $fileinfo->getBasename('.php'));
+              // The file name is already known so no need to find the file,
+              // but StaticReflectionParser needs one so use a mock version.
+              $finder = MockFileFinder::create($fileinfo->getPathName());
+              $parser = new StaticReflectionParser($class, $finder);
+              if ($annotation = $reader->getClassAnnotation($parser->getReflectionClass(), 'Drupal\Core\Annotation\Plugin')) {
+                // AnnotationInterface::get() returns the array definition
+                // instead of requiring us to work with the annotation object.
+                $definition = $annotation->get();
+                $definition['class'] = $class;
+                $definitions[$definition['id']] = $definition;
+              }
+            }
+          }
+        }
+      }
+    }
+    return $definitions;
+  }
+
+}
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 723de86..047026a 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -801,19 +801,6 @@ function _aggregator_items($count) {
 }
 
 /**
- * Implements hook_aggregator_fetch_info().
- */
-function aggregator_aggregator_fetch_info() {
-  return array(
-    'aggregator' => array(
-      'class' => 'Drupal\aggregator\Plugin\aggregator\fetcher\DefaultFetcher',
-      'title' => t('Default fetcher'),
-      'description' => t('Downloads data from a URL using Drupal\'s HTTP request handler.'),
-    ),
-  );
-}
-
-/**
  * Implements hook_preprocess_HOOK() for block.tpl.php.
  */
 function aggregator_preprocess_block(&$variables) {
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherManager.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherManager.php
index ae24934..f2fd0f7 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherManager.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherManager.php
@@ -8,8 +8,8 @@
 namespace Drupal\aggregator\Plugin;
 
 use Drupal\Component\Plugin\PluginManagerBase;
-use Drupal\Core\Plugin\Discovery\HookDiscovery;
 use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 
 /**
  * Manages aggregator fetcher plugins.
@@ -17,7 +17,7 @@ use Drupal\Component\Plugin\Factory\DefaultFactory;
 class FetcherManager extends PluginManagerBase {
 
   public function __construct() {
-    $this->discovery = new HookDiscovery('aggregator_fetch_info');
+    $this->discovery = new AnnotatedClassDiscovery('aggregator', 'fetcher');
     $this->factory = new DefaultFactory($this->discovery);
   }
 }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php
index e4b663b..bbe883c 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php
@@ -8,11 +8,19 @@
 namespace Drupal\aggregator\Plugin\aggregator\fetcher;
 
 use Drupal\aggregator\Plugin\FetcherInterface;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
 
 /**
  * Defines a default fetcher implementation.
  *
  * Uses drupal_http_request() to download the feed.
+ *
+ * @Plugin(
+ *   id = "aggregator",
+ *   title = @Translation("Default fetcher"),
+ *   description = @Translation("Downloads data from a URL using Drupal's HTTP request handler.")
+ * )
  */
 class DefaultFetcher implements FetcherInterface {
 
-- 
1.7.11.msysgit.1

