/core/src/main/php/lang/archive/ArchiveClassLoader.class.php

https://github.com/treuter/xp-framework · PHP · 186 lines · 70 code · 18 blank · 98 comment · 11 complexity · 7c215f2422efa290ad3fa2114cb19e38 MD5 · raw file

  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses('lang.AbstractClassLoader');
  7. /**
  8. * Loads XP classes from a XAR (XP Archive)
  9. *
  10. * Usage:
  11. * <code>
  12. * $l= new ArchiveClassLoader(new Archive(new File('soap.xar')));
  13. * try {
  14. * $class= $l->loadClass($argv[1]);
  15. * } catch (ClassNotFoundException $e) {
  16. * $e->printStackTrace();
  17. * exit(-1);
  18. * }
  19. *
  20. * $obj= $class->newInstance();
  21. * </code>
  22. *
  23. * @test xp://net.xp_framework.unittest.core.ArchiveClassLoaderTest
  24. * @see xp://lang.ClassLoader
  25. * @see xp://lang.archive.Archive
  26. */
  27. class ArchiveClassLoader extends AbstractClassLoader {
  28. protected $archive= NULL;
  29. /**
  30. * Constructor
  31. *
  32. * @param var archive either a string or a lang.archive.Archive instance
  33. */
  34. public function __construct($archive) {
  35. $this->path= $archive instanceof Archive ? $archive->getURI() : $archive;
  36. // Archive within an archive
  37. if (0 === strncmp('xar://', $this->path, 6)) {
  38. $this->path= urlencode($this->path);
  39. }
  40. $this->archive= 'xar://'.$this->path.'?';
  41. }
  42. /**
  43. * Load class bytes
  44. *
  45. * @param string name fully qualified class name
  46. * @return string
  47. */
  48. public function loadClassBytes($name) {
  49. return file_get_contents($this->archive.strtr($name, '.', '/').xp::CLASS_FILE_EXT);
  50. }
  51. /**
  52. * Returns URI suitable for include() given a class name
  53. *
  54. * @param string class
  55. * @return string
  56. */
  57. protected function classUri($class) {
  58. return $this->archive.strtr($class, '.', '/').xp::CLASS_FILE_EXT;
  59. }
  60. /**
  61. * Loads a resource.
  62. *
  63. * @param string string name of resource
  64. * @return string
  65. * @throws lang.ElementNotFoundException in case the resource cannot be found
  66. */
  67. public function getResource($string) {
  68. if (FALSE !== ($r= file_get_contents($this->archive.$string))) return $r;
  69. xp::gc(__FILE__);
  70. raise('lang.ElementNotFoundException', 'Could not load resource '.$string);
  71. }
  72. /**
  73. * Retrieve a stream to the resource
  74. *
  75. * @param string string name of resource
  76. * @return io.Stream
  77. * @throws lang.ElementNotFoundException in case the resource cannot be found
  78. */
  79. public function getResourceAsStream($string) {
  80. if (!file_exists($fn= $this->archive.$string)) {
  81. return raise('lang.ElementNotFoundException', 'Could not load resource '.$string);
  82. }
  83. return new File($fn);
  84. }
  85. /**
  86. * Checks whether this loader can provide the requested class
  87. *
  88. * @param string class
  89. * @return bool
  90. */
  91. public function providesClass($class) {
  92. return file_exists($this->archive.strtr($class, '.', '/').xp::CLASS_FILE_EXT);
  93. }
  94. /**
  95. * Checks whether this loader can provide the requested resource
  96. *
  97. * @param string filename
  98. * @return bool
  99. */
  100. public function providesResource($filename) {
  101. return file_exists($this->archive.$filename);
  102. }
  103. /**
  104. * Checks whether this loader can provide the requested package
  105. *
  106. * @param string package
  107. * @return bool
  108. */
  109. public function providesPackage($package) {
  110. $acquired= xarloader::acquire(urldecode(substr($this->archive, 6, -1)));
  111. $cmps= strtr($package, '.', '/').'/';
  112. $cmpl= strlen($cmps);
  113. foreach (array_keys($acquired['index']) as $e) {
  114. if (strncmp($cmps, $e, $cmpl) === 0) return TRUE;
  115. }
  116. return FALSE;
  117. }
  118. /**
  119. * Fetch instance of classloader by the path to the archive
  120. *
  121. * @param string path
  122. * @param bool expand default TRUE whether to expand the path using realpath
  123. * @return lang.archive.ArchiveClassLoader
  124. */
  125. public static function instanceFor($path, $expand= TRUE) {
  126. static $pool= array();
  127. $path= $expand && 0 !== strncmp('xar://', urldecode($path), 6) ? realpath($path) : $path;
  128. if (!isset($pool[$path])) {
  129. $pool[$path]= new self($path);
  130. }
  131. return $pool[$path];
  132. }
  133. /**
  134. * Get package contents
  135. *
  136. * @param string package
  137. * @return string[] filenames
  138. */
  139. public function packageContents($package) {
  140. $contents= array();
  141. $acquired= xarloader::acquire(urldecode(substr($this->archive, 6, -1)));
  142. $cmps= strtr($package, '.', '/');
  143. $cmpl= strlen($cmps);
  144. foreach (array_keys($acquired['index']) as $e) {
  145. if (strncmp($cmps, $e, $cmpl) != 0) continue;
  146. $entry= 0 != $cmpl ? substr($e, $cmpl+ 1) : $e;
  147. // Check to see if we're getting something in a subpackage. Imagine the
  148. // following structure:
  149. //
  150. // archive.xar
  151. // - tests/ClassOne.class.php
  152. // - tests/classes/RecursionTest.class.php
  153. // - tests/classes/ng/NextGenerationRecursionTest.class.php
  154. //
  155. // When this method is invoked with "tests" as name, "ClassOne.class.php"
  156. // and "classes/" should be returned (but neither any of the subdirectories
  157. // nor their contents)
  158. if (FALSE !== ($p= strpos($entry, '/'))) {
  159. $entry= substr($entry, 0, $p);
  160. if (strstr($entry, '/')) continue;
  161. $entry.= '/';
  162. }
  163. $contents[$entry]= NULL;
  164. }
  165. return array_keys($contents);
  166. }
  167. }
  168. ?>