PageRenderTime 24ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/ghiata/xp-framework
PHP | 226 lines | 147 code | 12 blank | 67 comment | 11 complexity | 0d63ce701ef10ca6b44a9d1037787b30 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. * @test xp://net.xp_framework.unittest.reflection.ClassFromArchiveTest
  25. * @see xp://lang.ClassLoader
  26. * @see xp://lang.archive.Archive
  27. */
  28. class ArchiveClassLoader extends AbstractClassLoader {
  29. protected $archive= NULL;
  30. /**
  31. * Constructor
  32. *
  33. * @param var archive either a string or a lang.archive.Archive instance
  34. */
  35. public function __construct($archive) {
  36. $this->path= $archive instanceof Archive ? $archive->getURI() : $archive;
  37. // Archive within an archive
  38. if (0 === strncmp('xar://', $this->path, 6)) {
  39. $this->path= urlencode($this->path);
  40. }
  41. $this->archive= 'xar://'.$this->path.'?';
  42. }
  43. /**
  44. * Load class bytes
  45. *
  46. * @param string name fully qualified class name
  47. * @return string
  48. */
  49. public function loadClassBytes($name) {
  50. return file_get_contents($this->archive.strtr($name, '.', '/').xp::CLASS_FILE_EXT);
  51. }
  52. /**
  53. * Returns URI suitable for include() given a class name
  54. *
  55. * @param string class
  56. * @return string
  57. */
  58. protected function classUri($class) {
  59. return $this->archive.strtr($class, '.', '/').xp::CLASS_FILE_EXT;
  60. }
  61. /**
  62. * Return a class at the given URI
  63. *
  64. * @param string uri
  65. * @return string fully qualified class name, or NULL
  66. */
  67. protected function classAtUri($uri) {
  68. if (0 !== substr_compare($uri, xp::CLASS_FILE_EXT, -strlen(xp::CLASS_FILE_EXT))) return NULL;
  69. // Absolute URIs have the form "xar://containing.xar?the/classes/Name.class.php"
  70. if ((DIRECTORY_SEPARATOR === $uri{0} || (':' === $uri{1} && '\\' === $uri{2}))) {
  71. return NULL;
  72. } else if (FALSE !== ($p= strpos($uri, '?'))) {
  73. $archive= substr($uri, 0, $p + 1);
  74. if ($archive !== $this->archive) return NULL;
  75. $uri= substr($uri, $p + 1);
  76. } else {
  77. $archive= $this->archive;
  78. }
  79. // Normalize path: Force forward slashes, strip out "." and empty elements,
  80. // interpret ".." by backing up until last forward slash is found.
  81. $path= '';
  82. foreach (explode('/', strtr($uri, DIRECTORY_SEPARATOR, '/')) as $element) {
  83. if ('' === $element || '.' === $element) {
  84. // NOOP
  85. } else if ('..' === $element) {
  86. $path= substr($path, 0, strrpos($path, '/'));
  87. } else {
  88. $path.= '/'.$element;
  89. }
  90. }
  91. return is_file($archive.substr($path, 1))
  92. ? strtr(substr($path, 1, -strlen(xp::CLASS_FILE_EXT)), '/', '.')
  93. : NULL
  94. ;
  95. }
  96. /**
  97. * Loads a resource.
  98. *
  99. * @param string string name of resource
  100. * @return string
  101. * @throws lang.ElementNotFoundException in case the resource cannot be found
  102. */
  103. public function getResource($string) {
  104. if (FALSE !== ($r= file_get_contents($this->archive.$string))) return $r;
  105. xp::gc(__FILE__);
  106. raise('lang.ElementNotFoundException', 'Could not load resource '.$string);
  107. }
  108. /**
  109. * Retrieve a stream to the resource
  110. *
  111. * @param string string name of resource
  112. * @return io.Stream
  113. * @throws lang.ElementNotFoundException in case the resource cannot be found
  114. */
  115. public function getResourceAsStream($string) {
  116. if (!file_exists($fn= $this->archive.$string)) {
  117. return raise('lang.ElementNotFoundException', 'Could not load resource '.$string);
  118. }
  119. return new File($fn);
  120. }
  121. /**
  122. * Checks whether this loader can provide the requested class
  123. *
  124. * @param string class
  125. * @return bool
  126. */
  127. public function providesClass($class) {
  128. return file_exists($this->archive.strtr($class, '.', '/').xp::CLASS_FILE_EXT);
  129. }
  130. /**
  131. * Checks whether this loader can provide the requested resource
  132. *
  133. * @param string filename
  134. * @return bool
  135. */
  136. public function providesResource($filename) {
  137. return file_exists($this->archive.$filename);
  138. }
  139. /**
  140. * Checks whether this loader can provide the requested package
  141. *
  142. * @param string package
  143. * @return bool
  144. */
  145. public function providesPackage($package) {
  146. $acquired= xarloader::acquire(urldecode(substr($this->archive, 6, -1)));
  147. $cmps= strtr($package, '.', '/').'/';
  148. $cmpl= strlen($cmps);
  149. foreach (array_keys($acquired['index']) as $e) {
  150. if (strncmp($cmps, $e, $cmpl) === 0) return TRUE;
  151. }
  152. return FALSE;
  153. }
  154. /**
  155. * Fetch instance of classloader by the path to the archive
  156. *
  157. * @param string path
  158. * @param bool expand default TRUE whether to expand the path using realpath
  159. * @return lang.archive.ArchiveClassLoader
  160. */
  161. public static function instanceFor($path, $expand= TRUE) {
  162. static $pool= array();
  163. $path= $expand && 0 !== strncmp('xar://', urldecode($path), 6) ? realpath($path) : $path;
  164. if (!isset($pool[$path])) {
  165. $pool[$path]= new self($path);
  166. }
  167. return $pool[$path];
  168. }
  169. /**
  170. * Get package contents
  171. *
  172. * @param string package
  173. * @return string[] filenames
  174. */
  175. public function packageContents($package) {
  176. $contents= array();
  177. $acquired= xarloader::acquire(urldecode(substr($this->archive, 6, -1)));
  178. $cmps= strtr($package, '.', '/');
  179. $cmpl= strlen($cmps);
  180. foreach (array_keys($acquired['index']) as $e) {
  181. if (strncmp($cmps, $e, $cmpl) != 0) continue;
  182. $entry= 0 != $cmpl ? substr($e, $cmpl+ 1) : $e;
  183. // Check to see if we're getting something in a subpackage. Imagine the
  184. // following structure:
  185. //
  186. // archive.xar
  187. // - tests/ClassOne.class.php
  188. // - tests/classes/RecursionTest.class.php
  189. // - tests/classes/ng/NextGenerationRecursionTest.class.php
  190. //
  191. // When this method is invoked with "tests" as name, "ClassOne.class.php"
  192. // and "classes/" should be returned (but neither any of the subdirectories
  193. // nor their contents)
  194. if (FALSE !== ($p= strpos($entry, '/'))) {
  195. $entry= substr($entry, 0, $p);
  196. if (strstr($entry, '/')) continue;
  197. $entry.= '/';
  198. }
  199. $contents[$entry]= NULL;
  200. }
  201. return array_keys($contents);
  202. }
  203. }
  204. ?>