/POE-Component-Pluggable-1.26/lib/POE/Component/Pluggable/Pipeline.pm

# · Perl · 587 lines · 420 code · 149 blank · 18 comment · 49 complexity · e2df93f1d20822c7619ccb3fe351256b MD5 · raw file

  1. package POE::Component::Pluggable::Pipeline;
  2. use strict;
  3. use warnings;
  4. use Carp;
  5. use Scalar::Util qw(weaken);
  6. our $VERSION = '1.26';
  7. sub new {
  8. my ($package, $pluggable) = @_;
  9. my $self = bless {
  10. PLUGS => {},
  11. PIPELINE => [],
  12. HANDLES => {},
  13. OBJECT => $pluggable,
  14. }, $package;
  15. weaken($self->{OBJECT});
  16. return $self;
  17. }
  18. sub push {
  19. my ($self, $alias, $plug) = @_;
  20. if ($self->{PLUGS}{$alias}) {
  21. $@ = "Plugin named '$alias' already exists ($self->{PLUGS}{$alias})";
  22. return;
  23. }
  24. my $return = $self->_register($alias, $plug);
  25. return if !$return;
  26. push @{ $self->{PIPELINE} }, $plug;
  27. return scalar @{ $self->{PIPELINE} };
  28. }
  29. sub pop {
  30. my ($self) = @_;
  31. return if !@{ $self->{PIPELINE} };
  32. my $plug = pop @{ $self->{PIPELINE} };
  33. my $alias = $self->{PLUGS}{$plug};
  34. $self->_unregister($alias, $plug);
  35. return wantarray ? ($plug, $alias) : $plug;
  36. }
  37. sub unshift {
  38. my ($self, $alias, $plug) = @_;
  39. if ($self->{PLUGS}{$alias}) {
  40. $@ = "Plugin named '$alias' already exists ($self->{PLUGS}{$alias}";
  41. return;
  42. }
  43. my $return = $self->_register($alias, $plug);
  44. return if !$return;
  45. unshift @{ $self->{PIPELINE} }, $plug;
  46. return scalar @{ $self->{PIPELINE} };
  47. }
  48. sub shift {
  49. my ($self) = @_;
  50. return if !@{ $self->{PIPELINE} };
  51. my $plug = shift @{ $self->{PIPELINE} };
  52. my $alias = $self->{PLUGS}{$plug};
  53. $self->_unregister($alias, $plug);
  54. return wantarray ? ($plug, $alias) : $plug;
  55. }
  56. sub replace {
  57. my ($self, $old, $new_a, $new_p) = @_;
  58. my ($old_a, $old_p) = ref $old
  59. ? ($self->{PLUGS}{$old}, $old)
  60. : ($old, $self->{PLUGS}{$old})
  61. ;
  62. if (!$old_p) {
  63. $@ = "Plugin '$old_a' does not exist";
  64. return;
  65. }
  66. $self->_unregister($old_a, $old_p);
  67. if ($self->{PLUGS}{$new_a}) {
  68. $@ = "Plugin named '$new_a' already exists ($self->{PLUGS}{$new_a}";
  69. return;
  70. }
  71. my $return = $self->_register($new_a, $new_p);
  72. return if !$return;
  73. for my $plugin (@{ $self->{PIPELINE} }) {
  74. if ($plugin == $old_p) {
  75. $plugin = $new_p;
  76. last;
  77. }
  78. }
  79. return 1;
  80. }
  81. sub remove {
  82. my ($self, $old) = @_;
  83. my ($old_a, $old_p) = ref $old
  84. ? ($self->{PLUGS}{$old}, $old)
  85. : ($old, $self->{PLUGS}{$old})
  86. ;
  87. if (!$old_p) {
  88. $@ = "Plugin '$old_a' does not exist";
  89. return;
  90. }
  91. my $i = 0;
  92. for my $plugin (@{ $self->{PIPELINE} }) {
  93. if ($plugin == $old_p) {
  94. splice(@{ $self->{PIPELINE} }, $i, 1);
  95. last;
  96. }
  97. $i++;
  98. }
  99. $self->_unregister($old_a, $old_p);
  100. return wantarray ? ($old_p, $old_a) : $old_p;
  101. }
  102. sub get {
  103. my ($self, $old) = @_;
  104. my ($old_a, $old_p) = ref $old
  105. ? ($self->{PLUGS}{$old}, $old)
  106. : ($old, $self->{PLUGS}{$old})
  107. ;
  108. if (!$old_p) {
  109. $@ = "Plugin '$old_a' does not exist";
  110. return;
  111. }
  112. return wantarray ? ($old_p, $old_a) : $old_p;
  113. }
  114. sub get_index {
  115. my ($self, $old) = @_;
  116. my ($old_a, $old_p) = ref $old
  117. ? ($self->{PLUGS}{$old}, $old)
  118. : ($old, $self->{PLUGS}{$old})
  119. ;
  120. if (!$old_p) {
  121. $@ = "Plugin '$old_a' does not exist";
  122. return -1;
  123. }
  124. my $i = 0;
  125. for my $plugin (@{ $self->{PIPELINE} }) {
  126. return $i if $plugin == $old_p;
  127. $i++;
  128. }
  129. return -1;
  130. }
  131. sub insert_before {
  132. my ($self, $old, $new_a, $new_p) = @_;
  133. my ($old_a, $old_p) = ref $old
  134. ? ($self->{PLUGS}{$old}, $old)
  135. : ($old, $self->{PLUGS}{$old})
  136. ;
  137. if (!$old_p) {
  138. $@ = "Plugin '$old_a' does not exist";
  139. return;
  140. }
  141. if ($self->{PLUGS}{$new_a}) {
  142. $@ = "Plugin named '$new_a' already exists ($self->{PLUGS}{$new_a}";
  143. return;
  144. }
  145. my $return = $self->_register($new_a, $new_p);
  146. return if !$return;
  147. my $i = 0;
  148. for my $plugin (@{ $self->{PIPELINE} }) {
  149. if ($plugin == $old_p) {
  150. splice(@{ $self->{PIPELINE} }, $i, 0, $new_p);
  151. last;
  152. }
  153. $i++;
  154. }
  155. return 1;
  156. }
  157. sub insert_after {
  158. my ($self, $old, $new_a, $new_p) = @_;
  159. my ($old_a, $old_p) = ref $old
  160. ? ($self->{PLUGS}{$old}, $old)
  161. : ($old, $self->{PLUGS}{$old})
  162. ;
  163. if (!$old_p) {
  164. $@ = "Plugin '$old_a' does not exist";
  165. return;
  166. }
  167. if ($self->{PLUGS}{$new_a}) {
  168. $@ = "Plugin named '$new_a' already exists ($self->{PLUGS}{$new_a}";
  169. return;
  170. }
  171. my $return = $self->_register($new_a, $new_p);
  172. return if !$return;
  173. my $i = 0;
  174. for my $plugin (@{ $self->{PIPELINE} }) {
  175. if ($plugin == $old_p) {
  176. splice(@{ $self->{PIPELINE} }, $i+1, 0, $new_p);
  177. last;
  178. }
  179. $i++;
  180. }
  181. return 1;
  182. }
  183. sub bump_up {
  184. my ($self, $old, $diff) = @_;
  185. my $idx = $self->get_index($old);
  186. return -1 if $idx < 0;
  187. my $pipeline = $self->{PIPELINE};
  188. $diff ||= 1;
  189. my $pos = $idx - $diff;
  190. if ($pos < 0) {
  191. carp "$idx - $diff is negative, moving to head of the pipeline";
  192. }
  193. splice(@$pipeline, $pos, 0, splice(@$pipeline, $idx, 1));
  194. return $pos;
  195. }
  196. sub bump_down {
  197. my ($self, $old, $diff) = @_;
  198. my $idx = $self->get_index($old);
  199. return -1 if $idx < 0;
  200. my $pipeline = $self->{PIPELINE};
  201. $diff ||= 1;
  202. my $pos = $idx + $diff;
  203. if ($pos >= @$pipeline) {
  204. carp "$idx + $diff is too high, moving to back of the pipeline";
  205. }
  206. splice(@$pipeline, $pos, 0, splice(@$pipeline, $idx, 1));
  207. return $pos;
  208. }
  209. sub _register {
  210. my ($self, $alias, $plug) = @_;
  211. return if !defined $self->{OBJECT};
  212. my $return;
  213. my $sub = "$self->{OBJECT}{_pluggable_reg_prefix}register";
  214. eval { $return = $plug->$sub($self->{OBJECT}) };
  215. if ($@) {
  216. chomp $@;
  217. my $error = "$sub call on plugin '$alias' failed: $@";
  218. $self->_handle_error($error, $plug, $alias);
  219. }
  220. elsif (!$return) {
  221. my $error = "$sub call on plugin '$alias' did not return a true value";
  222. $self->_handle_error($error, $plug, $alias);
  223. }
  224. $self->{PLUGS}{$plug} = $alias;
  225. $self->{PLUGS}{$alias} = $plug;
  226. $self->{OBJECT}->_pluggable_event(
  227. "$self->{OBJECT}{_pluggable_prefix}plugin_add",
  228. $alias, $plug,
  229. );
  230. return $return;
  231. }
  232. sub _unregister {
  233. my ($self, $alias, $plug) = @_;
  234. return if !defined $self->{OBJECT};
  235. my $return;
  236. my $sub = "$self->{OBJECT}{_pluggable_reg_prefix}unregister";
  237. eval { $return = $plug->$sub($self->{OBJECT}) };
  238. if ($@) {
  239. chomp $@;
  240. my $error = "$sub call on plugin '$alias' failed: $@";
  241. $self->_handle_error($error, $plug, $alias);
  242. }
  243. elsif (!$return) {
  244. my $error = "$sub call on plugin '$alias' did not return a true value";
  245. $self->_handle_error($error, $plug, $alias);
  246. }
  247. delete $self->{PLUGS}{$plug};
  248. delete $self->{PLUGS}{$alias};
  249. delete $self->{HANDLES}{$plug};
  250. $self->{OBJECT}->_pluggable_event(
  251. "$self->{OBJECT}{_pluggable_prefix}plugin_del",
  252. $alias, $plug,
  253. );
  254. return $return;
  255. }
  256. sub _handle_error {
  257. my ($self, $error, $plugin, $alias) = @_;
  258. warn "$error\n" if $self->{OBJECT}{_pluggable_debug};
  259. $self->{OBJECT}->_pluggable_event(
  260. "$self->{OBJECT}{_pluggable_prefix}plugin_error",
  261. $error, $plugin, $alias,
  262. );
  263. return;
  264. }
  265. 1;
  266. __END__
  267. =encoding utf8
  268. =head1 NAME
  269. POE::Component::Pluggable::Pipeline - the plugin pipeline for
  270. POE::Component::Pluggable.
  271. =head1 SYNOPSIS
  272. use POE qw( Component::Pluggable );
  273. use POE::Component::Pluggable::Pipeline;
  274. use My::Plugin;
  275. my $self = POE::Component::Pluggable->new();
  276. # the following operations are presented in pairs
  277. # the first is the general procedure, the second is
  278. # the specific way using the pipeline directly
  279. # to install a plugin
  280. $self->plugin_add(mine => My::Plugin->new);
  281. $self->pipeline->push(mine => My::Plugin->new);
  282. # to remove a plugin
  283. $self->plugin_del('mine'); # or the object
  284. $self->pipeline->remove('mine'); # or the object
  285. # to get a plugin
  286. my $plug = $self->plugin_get('mine');
  287. my $plug = $self->pipeline->get('mine');
  288. # there are other very specific operations that
  289. # the pipeline offers, demonstrated here:
  290. # to get the pipeline object itself
  291. my $pipe = $self->pipeline;
  292. # to install a plugin at the front of the pipeline
  293. $pipe->unshift(mine => My::Plugin->new);
  294. # to remove the plugin at the end of the pipeline
  295. my $plug = $pipe->pop;
  296. # to remove the plugin at the front of the pipeline
  297. my $plug = $pipe->shift;
  298. # to replace a plugin with another
  299. $pipe->replace(mine => newmine => My::Plugin->new);
  300. # to insert a plugin before another
  301. $pipe->insert_before(mine => newmine => My::Plugin->new);
  302. # to insert a plugin after another
  303. $pipe->insert_after(mine => newmine => My::Plugin->new);
  304. # to get the location in the pipeline of a plugin
  305. my $index = $pipe->get_index('mine');
  306. # to move a plugin closer to the front of the pipeline
  307. $pipe->bump_up('mine');
  308. # to move a plugin closer to the end of the pipeline
  309. $pipe->bump_down('mine');
  310. =head1 DESCRIPTION
  311. POE::Component::Pluggable::Pipeline defines the Plugin pipeline system
  312. for L<POE::Component::Pluggable|POE::Component::Pluggable> instances.
  313. =head1 METHODS
  314. =head2 C<new>
  315. Takes one argument, the POE::Component::Pluggable object to attach to.
  316. =head2 C<push>
  317. Takes two arguments, an alias for a plugin and the plugin object itself.
  318. If a plugin with that alias already exists, C<$@> will be set and C<undef>
  319. will be returned. Otherwise, it adds the plugin to the end of the pipeline
  320. and registers it. This will yield a C<plugin_add> event. If successful, it
  321. returns the size of the pipeline.
  322. my $new_size = $pipe->push($name, $plug);
  323. =head2 C<unshift>
  324. Takes two arguments, an alias for a plugin and the plugin object itself.
  325. If a plugin with that alias already exists, C<$@> will be set and C<undef>
  326. will be returned. Otherwise, it adds the plugin to the beginning of the
  327. pipeline and registers it. This will yield a C<plugin_add> event. If
  328. successful, it returns the size of the pipeline.
  329. my $new_size = $pipe->push($name, $plug);
  330. =head2 C<shift>
  331. Takes no arguments. The first plugin in the pipeline is removed. This will
  332. yield a C<plugin_del> event. In list context, it returns the plugin and its
  333. alias; in scalar context, it returns only the plugin. If there were no
  334. elements, an empty list or C<undef> will be returned.
  335. my ($plug, $name) = $pipe->shift;
  336. my $plug = $pipe->shift;
  337. =head2 C<pop>
  338. Takes no arguments. The last plugin in the pipeline is removed. This will
  339. yield an C<plugin_del> event. In list context, it returns the plugin and its
  340. alias; in scalar context, it returns only the plugin. If there were no
  341. elements, an empty list or C<undef> will be returned.
  342. my ($plug, $name) = $pipe->pop;
  343. my $plug = $pipe->pop;
  344. =head2 C<replace>
  345. Take three arguments, the old plugin or its alias, an alias for the new
  346. plugin and the new plugin object itself. If the old plugin doesn't exist,
  347. or if there is already a plugin with the new alias (besides the old plugin),
  348. $@ will be set and C<undef> will be returned. Otherwise, it removes the old
  349. plugin (yielding an C<plugin_del> event) and replaces it with the new
  350. plugin. This will yield an C<plugin_add> event. If successful, it returns 1.
  351. my $success = $pipe->replace($name, $new_name, $new_plug);
  352. my $success = $pipe->replace($plug, $new_name, $new_plug);
  353. =head2 C<insert_before>
  354. Takes three arguments, the plugin that is relative to the operation,
  355. an alias for the new plugin and the new plugin object itself. If the first
  356. plugin doesn't exist, or if there is already a plugin with the new alias,
  357. C<$@> will be set and C<undef> will be returned. Otherwise, the new plugin is
  358. placed just prior to the other plugin in the pipeline. If successful,
  359. it returns 1.
  360. my $success = $pipe->insert_before($name, $new_name, $new_plug);
  361. my $success = $pipe->insert_before($plug, $new_name, $new_plug);
  362. =head2 C<insert_after>
  363. Takes three arguments, the plugin that is relative to the operation,
  364. an alias for the new plugin and the new plugin object itself. If the
  365. first plugin doesn't exist, or if there is already a plugin with the
  366. new alias, C<$@> will be set and C<undef> will be returned. Otherwise,
  367. the new plugin is placed just after to the other plugin in the pipeline.
  368. If successful, it returns 1.
  369. my $success = $pipe->insert_after($name, $new_name, $new_plug);
  370. my $success = $pipe->insert_after($plug, $new_name, $new_plug);
  371. =head2 C<bump_up>
  372. Takes one or two arguments, the plugin or its alias, and the distance to
  373. bump the plugin. The distance defaults to 1. If the plugin doesn't exist,
  374. C<$@> will be set and B<-1 will be returned, not undef>. Otherwise, the
  375. plugin will be moved the given distance closer to the front of the
  376. pipeline. A warning is issued alerting you if it would have been moved
  377. past the beginning of the pipeline, and the plugin is placed at the
  378. beginning. If successful, the new index of the plugin in the pipeline is
  379. returned.
  380. my $pos = $pipe->bump_up($name);
  381. my $pos = $pipe->bump_up($plug);
  382. my $pos = $pipe->bump_up($name, $delta);
  383. my $pos = $pipe->bump_up($plug, $delta);
  384. =head2 C<bump_down>
  385. Takes one or two arguments, the plugin or its alias, and the distance to
  386. bump the plugin. The distance defaults to 1. If the plugin doesn't exist,
  387. C<$@> will be set and B<-1 will be returned, not C<undef>>. Otherwise, the
  388. plugin will be moved the given distance closer to the end of the pipeline.
  389. A warning is issued alerting you if it would have been moved past the end
  390. of the pipeline, and the plugin is placed at the end. If successful, the new
  391. index of the plugin in the pipeline is returned.
  392. my $pos = $pipe->bump_down($name);
  393. my $pos = $pipe->bump_down($plug);
  394. my $pos = $pipe->bump_down($name, $delta);
  395. my $pos = $pipe->bump_down($plug, $delta);
  396. =head2 C<remove>
  397. Takes one argument, a plugin or its alias. If the plugin doesn't exist,
  398. C<$@> will be set and C<undef> will be returned. Otherwise, the plugin is
  399. removed from the pipeline. This will yield an C<plugin_del> event. In list
  400. context,it returns the plugin and its alias; in scalar context, it returns
  401. only the plugin.
  402. my ($plug, $name) = $pipe->remove($the_name);
  403. my ($plug, $name) = $pipe->remove($the_plug);
  404. my $plug = $pipe->remove($the_name);
  405. my $plug = $pipe->remove($the_plug);
  406. =head2 C<get>
  407. Takes one argument, a plugin or its alias. If no such plugin exists, C<$@>
  408. will be set and C<undef> will be returned. In list context, it returns the
  409. plugin and its alias; in scalar context, it returns only the plugin.
  410. my ($plug, $name) = $pipe->get($the_name);
  411. my ($plug, $name) = $pipe->get($the_plug);
  412. my $plug = $pipe->get($the_name);
  413. my $plug = $pipe->get($the_plug);
  414. =head2 C<get_index>
  415. Takes one argument, a plugin or its alias. If no such plugin exists, C<$@>
  416. will be set and B<-1 will be returned, not C<undef>>. Otherwise, the index
  417. in the pipeline is returned.
  418. my $pos = $pipe->get_index($name);
  419. my $pos = $pipe->get_index($plug);
  420. =head1 BUGS
  421. None known so far.
  422. =head1 AUTHOR
  423. Jeff C<japhy> Pinyan, F<japhy@perlmonk.org>.
  424. =head1 MAINTAINER
  425. Chris C<BinGOs> Williams, F<chris@bingosnet.co.uk>.
  426. =head1 SEE ALSO
  427. L<POE::Component::IRC|POE::Component::IRC>,
  428. L<POE::Component::Pluggable|POE::Component::Pluggable>.
  429. =cut