Index: lang.base.php
===================================================================
--- lang.base.php (revision 8050)
+++ lang.base.php (working copy)
@@ -137,7 +137,24 @@
// Sets an SAPI
function sapi() {
foreach ($a= func_get_args() as $name) {
- require_once('sapi'.DIRECTORY_SEPARATOR.strtr($name, '.', DIRECTORY_SEPARATOR).'.sapi.php');
+ foreach (explode(':', ini_get('include_path')) as $path) {
+ $filename= 'sapi'.DIRECTORY_SEPARATOR.strtr($name, '.', DIRECTORY_SEPARATOR).'.sapi.php';
+
+ if (is_dir($path)) {
+ if (file_exists($path.DIRECTORY_SEPARATOR.$filename)) {
+ require_once($path.DIRECTORY_SEPARATOR.$filename);
+ break; // Load next sapi
+ }
+ } elseif (is_file($path)) {
+ if (FALSE !== ($bytes= __xp_load_archive_bytes($path, $filename))) {
+ if (FALSE === eval('?>'.$bytes)) {
+ xp::error('Unable to load sapi '.$name.' from archive '.$path);
+ }
+
+ break; // Load next sapi
+ }
+ }
+ }
}
xp::registry('sapi', $a);
}
@@ -223,10 +240,48 @@
xp::registry('errors', $errors);
}
// }}}
+
+ // {{{ string __xp_load_archive_bytes(string archive, string filename)
+ // Loads a file from an archive
+ function __xp_load_archive_bytes($archivePath, $filename) {
+ static $archive= array();
+
+ if (!isset($archive[$archivePath])) {
+ $archive[$archivePath]= array();
+ $current= &$archive[$archivePath];
+ // Bootstrap loading, only to be used for core classes.
+ $current['handle']= fopen($archivePath, 'rb');
+ $header= unpack('a3id/c1version/i1indexsize/a*reserved', fread($current['handle'], 0x0100));
+ for ($current['index']= array(), $i= 0; $i < $header['indexsize']; $i++) {
+ $entry= unpack(
+ 'a80id/a80filename/a80path/i1size/i1offset/a*reserved',
+ fread($current['handle'], 0x0100)
+ );
+ $current['index'][$entry['id']]= array($entry['size'], $entry['offset']);
+ }
+ }
+
+ $current= &$archive[$archivePath];
+
+ if (!isset($current['index'][$filename])) return FALSE;
+ fseek($current['handle'], 0x0100 + sizeof($current['index']) * 0x0100 + $current['index'][$filename][1], SEEK_SET);
+ $bytes= fread($current['handle'], $current['index'][$filename][0]);
+ return $bytes;
+ }
+ // }}}
+
+
// {{{ void uses (string* args)
// Uses one or more classes
function uses() {
+ $include= &xp::registry('include_path');
+
+ if (0 == sizeof($include)) {
+ $include= array_flip(explode(':', ini_get('include_path')));
+ xp::registry('include_path', $include);
+ }
+
foreach (func_get_args() as $str) {
if (class_exists($class= xp::reflect($str))) continue;
@@ -245,17 +300,51 @@
}
$str= substr($str, strrpos($str, '/')+ 1);
$class= xp::reflect($str);
- } else {
- if (FALSE === ($r= include_once(strtr($str, '.', DIRECTORY_SEPARATOR).'.class.php'))) {
- xp::error(xp::stringOf(new Error('Cannot include '.$str)));
- } else if (TRUE === $r) {
- continue;
+
+ continue;
+ }
+
+
+ foreach ($include as $path => $loader) {
+
+ // If path is a directory and the included file exists, load it
+ if (is_dir($path)) {
+ if (!file_exists($f= $path.DIRECTORY_SEPARATOR.strtr($str, '.', DIRECTORY_SEPARATOR).'.class.php')) {
+ continue;
+ }
+
+ if (FALSE === ($r= include_once($f))) {
+ xp::error(xp::stringOf(new Error('Cannot include '.$str)));
+ }
+
+ break;
+ } elseif (is_file($path)) {
+
+ // To to load via bootstrap class loader, if the file cannot provide the class-to-load
+ // skip to the next include_path part
+ if (FALSE === ($bytes= __xp_load_archive_bytes($path, strtr($str, '.', '/').'.class.php'))) {
+ continue;
+ }
+
+ if (FALSE === eval('?>'.$bytes)) {
+ xp::error('Bootstrap class loading failure at '.$str.' (file= '.$path.')');
+ }
+
+ xp::registry('classloader.'.$str, 'lang.archive.ArchiveClassLoader://'.$path);
+ break;
}
}
- // Register class name and call static initializer if available
- xp::registry('class.'.$class, $str);
- is_callable(array($class, '__static')) && call_user_func(array($class, '__static'));
+ if (!class_exists(xp::reflect($str))) {
+ xp::error('Cannot include '.$str);
+ }
+
+ // Register class name and call static initializer if available and if it has not been
+ // done before (through an ArchiveClassLoader)
+ if (NULL === xp::registry('class.'.$class)) {
+ xp::registry('class.'.$class, $str);
+ is_callable(array($class, '__static')) && call_user_func(array($class, '__static'));
+ }
}
}
// }}}
@@ -428,7 +517,6 @@
? getenv('SKELETON_PATH')
: dirname(__FILE__).DIRECTORY_SEPARATOR
));
- ini_set('include_path', SKELETON_PATH.PATH_SEPARATOR.ini_get('include_path'));
define('LONG_MAX', is_int(2147483648) ? 9223372036854775807 : 2147483647);
define('LONG_MIN', -LONG_MAX - 1);
@@ -455,7 +543,9 @@
'lang.IllegalArgumentException',
'lang.IllegalStateException',
'lang.FormatException',
- 'lang.ClassLoader'
+ 'lang.ClassLoader',
+ 'lang.archive.ArchiveReader',
+ 'lang.archive.ArchiveClassLoader'
);
// }}}
?>
Index: lang/archive/ArchiveClassLoader.class.php
===================================================================
--- lang/archive/ArchiveClassLoader.class.php (revision 8050)
+++ lang/archive/ArchiveClassLoader.class.php (working copy)
@@ -4,7 +4,7 @@
* $Id$
*/
- uses('lang.ClassLoader', 'io.cca.Archive');
+ uses('lang.ClassLoader');
/**
* Loads XP classes from a CCA (Class Collection Archive)
@@ -55,43 +55,7 @@
* @return string
*/
function loadClassBytes($name) {
- $src= '';
- $line= 0;
- $tokens= token_get_all($this->archive->extract($name));
- for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) {
- switch ($tokens[$i][0]) {
- case T_FILE:
- $tokens[$i][1]= "'".strtr($name, '.', '/').'.class.php\'';
- break;
-
- case T_LINE:
- $tokens[$i][1]= $line;
- break;
-
- case T_STRING:
- if ('uses' == $tokens[$i][1] || 'implements' == $tokens[$i][1]) {
- $o= $i+ 1;
- while (')' != $tokens[$o][0]) {
- if (T_CONSTANT_ENCAPSED_STRING == $tokens[$o][0]) {
- $used= trim($tokens[$o][1], '"\'');
- $this->archive->contains($used) && $this->loadClass($used);
- }
- $o++;
- }
- }
- break;
- }
-
- if (is_array($tokens[$i])) {
- $src.= $tokens[$i][1];
- $line+= substr_count($tokens[$i][1], "\n");
- } else {
- $src.= $tokens[$i];
- $line+= substr_count($tokens[$i], "\n");
- }
- }
-
- return $src;
+ return $this->archive->extract(strtr($name, '.', '/').'.class.php');
}
/**
@@ -128,5 +92,58 @@
$c= &new XPClass($name);
return $c;
}
+
+ /**
+ * Loads a resource.
+ *
+ * @access public
+ * @param string string name of resource
+ * @return string
+ */
+ function getResource($string) {
+ return $this->archive->extract($string);
+ }
+
+ /**
+ * Retrieve a stream to the resource
+ *
+ * @access public
+ * @param string string name of resource
+ * @return &io.Stream
+ */
+ function getResourceAsStream($string) {
+ return $this->archive->getStream($string);
+ }
+
+ /**
+ * Checks whether this loader can provide the requested class
+ *
+ * @access public
+ * @param string fqcn
+ * @return bool
+ */
+ function providesClass($class) {
+ return $this->archive->contains(strtr($class, '.', '/').'.class.php');
+ }
+
+ /**
+ * Fetch instance of classloader by the path to the archive
+ *
+ * @model static
+ * @access public
+ * @param string path
+ * @return &lang.archive.ArchiveClassLoader
+ */
+ function &instanceFor($path) {
+ static $pool= array();
+
+ if (isset($pool[$path])) {
+ return $pool[$path];
+ }
+
+ $instance= &new ArchiveClassLoader(new ArchiveReader($path));
+ $pool[$path]= $instance;
+ return $instance;
+ }
}
?>
Index: lang/archive/Archive.class.php
===================================================================
--- lang/archive/Archive.class.php (revision 8050)
+++ lang/archive/Archive.class.php (working copy)
@@ -4,13 +4,8 @@
* $Id$
*/
- uses('lang.ElementNotFoundException');
+ uses('lang.ElementNotFoundException', 'io.EncapsedStream');
- define('ARCHIVE_READ', 0x0000);
- define('ARCHIVE_CREATE', 0x0001);
- define('ARCHIVE_HEADER_SIZE', 0x0100);
- define('ARCHIVE_INDEX_ENTRY_SIZE', 0x0100);
-
/**
* Archives contain a collection of classes.
*
@@ -97,6 +92,25 @@
}
/**
+ * Add a file by its bytes
+ *
+ * @access public
+ * @param string id the id under which this entry will be located
+ * @param string path
+ * @param string filename
+ * @param string bytes
+ */
+ function addFileBytes($id, $path, $filename, $bytes) {
+ $this->_index[$id]= array(
+ $filename,
+ $path,
+ strlen($bytes),
+ -1, // Will be calculated by create()
+ $bytes
+ );
+ }
+
+ /**
* Create CCA archive
*
* @access public
@@ -212,6 +226,31 @@
}
/**
+ * Fetches a stream to the file in the archive
+ *
+ * @access public
+ * @param string id
+ * @return &io.Stream
+ * @throws lang.ElementNotFoundException in case the specified id does not exist
+ */
+ function &getStream($id) {
+ if (!$this->contains($id)) {
+ return throw(new ElementNotFoundException('Element "'.$id.'" not contained in this archive'));
+ }
+
+ // Calculate starting position
+ $pos= (
+ ARCHIVE_HEADER_SIZE +
+ sizeof(array_keys($this->_index)) * ARCHIVE_INDEX_ENTRY_SIZE +
+ $this->_index[$id][3]
+ );
+
+ $class= &XPClass::forName('io.EnclosedStream');
+ $s= &$class->newInstance($this->file, $pos, $this->_index[$id][2]);
+ return $s;
+ }
+
+ /**
* Open this archive
*
* @access public
Index: lang/ClassLoader.class.php
===================================================================
--- lang/ClassLoader.class.php (revision 8050)
+++ lang/ClassLoader.class.php (working copy)
@@ -170,5 +170,37 @@
$c= &new XPClass($name);
return $c;
}
+
+ /**
+ * Loads a resource.
+ *
+ * @access public
+ * @param string string name of resource
+ * @return string
+ */
+ function getResource($string) {
+ foreach (array_unique(explode(PATH_SEPARATOR, ini_get('include_path'))) as $dir) {
+ if (!file_exists($dir.DIRECTORY_SEPARATOR.$filename)) continue;
+ return file_get_contents($dir.DIRECTORY_SEPARATOR.$filename);
+ }
+
+ return throw(new ElementNotFoundException('Could not load resource '.$string));
+ }
+
+ /**
+ * Retrieve a stream to the resource
+ *
+ * @access public
+ * @param string string name of resource
+ * @return &io.File
+ */
+ function getResourceAsStream($string) {
+ foreach (array_unique(explode(PATH_SEPARATOR, ini_get('include_path'))) as $dir) {
+ if (!file_exists($dir.DIRECTORY_SEPARATOR.$filename)) continue;
+ return new File($filename);
+ }
+
+ return throw(new ElementNotFoundException('Could not load resource '.$string));
+ }
}
?>
Index: lang/XPClass.class.php
===================================================================
--- lang/XPClass.class.php (revision 8050)
+++ lang/XPClass.class.php (working copy)
@@ -435,8 +435,25 @@
*/
function &getClassLoader() {
if (!($cl= &xp::registry('classloader.'.$this->name))) {
- $cl= &ClassLoader::getDefault();
+ return ClassLoader::getDefault();
}
+
+ // The class loader information can be a string identifying the responsible
+ // classloader for the class. In that case, fetch it's class and get an
+ // instance through the instanceFor() method.
+ if (is_string($cl)) {
+ list($className, $argument)= sscanf($cl, '%[^:]://%s');
+ $class= &XPClass::forName($className);
+ $method= &$class->getMethod('instanceFor');
+
+ $dummy= NULL;
+ $cl= &$method->invoke($dummy, array($argument));
+
+ // Replace the "symbolic" representation of the classloader with a reference
+ // to an instance.
+ xp::registry('classloader.'.$this->name, $cl);
+ }
+
return $cl;
}
@@ -627,7 +644,16 @@
*/
function &forName($name, $classloader= NULL) {
if (NULL === $classloader) {
- $classloader= &ClassLoader::getDefault();
+
+ // Retrieve classloader via debug_backtrace() or fall back to
+ // the default classloader
+ $d= debug_backtrace();
+ if (isset($d[1]['class'])) {
+ $xpclass= &new XPClass(xp::nameOf($d[1]['class']));
+ $classloader= &$xpclass->getClassLoader();
+ } else {
+ $classloader= &ClassLoader::getDefault();
+ }
}
return $classloader->loadClass($name);
Index: io/cca/ArchiveClassLoader.class.php
===================================================================
--- io/cca/ArchiveClassLoader.class.php (revision 8050)
+++ io/cca/ArchiveClassLoader.class.php (working copy)
@@ -1,132 +0,0 @@
-
- * $l= &new ArchiveClassLoader(new Archive(new File('soap.cca')));
- * try(); {
- * $class= &$l->loadClass($argv[1]);
- * } if (catch('ClassNotFoundException', $e)) {
- * $e->printStackTrace();
- * exit(-1);
- * }
- *
- * $obj= &$class->newInstance();
- *
- *
- * @test xp://net.xp_framework.unittest.io.ArchiveClassLoaderTest
- * @purpose Load classes from an archive
- * @see xp://lang.ClassLoader
- * @see xp://lang.cca.Archive
- * @ext tokenize
- */
- class ArchiveClassLoader extends ClassLoader {
- var
- $archive = NULL;
-
- /**
- * Constructor
- *
- * @access public
- * @param &lang.cca.Archive archive
- */
- function __construct(&$archive) {
- parent::__construct();
- $this->archive= &$archive;
- $this->archive->isOpen() || $this->archive->open(ARCHIVE_READ);
- }
-
- /**
- * Load class bytes
- *
- * @access public
- * @param string name fully qualified class name
- * @return string
- */
- function loadClassBytes($name) {
- $src= '';
- $line= 0;
- $tokens= token_get_all($this->archive->extract($name));
- for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) {
- switch ($tokens[$i][0]) {
- case T_FILE:
- $tokens[$i][1]= "'".strtr($name, '.', '/').'.class.php\'';
- break;
-
- case T_LINE:
- $tokens[$i][1]= $line;
- break;
-
- case T_STRING:
- if ('uses' == $tokens[$i][1] || 'implements' == $tokens[$i][1]) {
- $o= $i+ 1;
- while (')' != $tokens[$o][0]) {
- if (T_CONSTANT_ENCAPSED_STRING == $tokens[$o][0]) {
- $used= trim($tokens[$o][1], '"\'');
- $this->archive->contains($used) && $this->loadClass($used);
- }
- $o++;
- }
- }
- break;
- }
-
- if (is_array($tokens[$i])) {
- $src.= $tokens[$i][1];
- $line+= substr_count($tokens[$i][1], "\n");
- } else {
- $src.= $tokens[$i];
- $line+= substr_count($tokens[$i], "\n");
- }
- }
-
- return $src;
- }
-
- /**
- * Load the class by the specified name
- *
- * @access public
- * @param string class fully qualified class name io.File
- * @return &lang.XPClass
- * @throws lang.ClassNotFoundException in case the class can not be found
- */
- function &loadClass($class) {
- $name= xp::reflect($class);
-
- if (!class_exists($name)) {
- try(); {
- $src= $this->loadClassBytes($class);
- } if (catch('Exception', $e)) {
- return throw(new ClassNotFoundException(sprintf(
- 'Class "%s" not found: %s',
- $class,
- $e->getMessage()
- )));
- }
-
- if (FALSE === eval('?>'.$src)) {
- return throw(new FormatException('Cannot define class "'.$class.'"'));
- }
-
- xp::registry('class.'.$name, $class);
- xp::registry('classloader.'.$class, $this);
- is_callable(array($name, '__static')) && call_user_func(array($name, '__static'));
- }
-
- $c= &new XPClass($name);
- return $c;
- }
- }
-?>
Index: io/cca/Archive.class.php
===================================================================
--- io/cca/Archive.class.php (revision 8050)
+++ io/cca/Archive.class.php (working copy)
@@ -1,315 +0,0 @@
-
- * $a= &new Archive(new File('soap.cca'));
- * try(); {
- * $a->open(ARCHIVE_CREATE);
- * $a->add(
- * new File(SKELETON_PATH.'xml/soap/SOAPMessage.class.php'),
- * 'xml.soap.SOAPMessage'
- * );
- * $a->add(
- * new File(SKELETON_PATH.'xml/soap/SOAPClient.class.php'),
- * 'xml.soap.SOAPClient'
- * );
- * $a->create();
- * } if (catch('Exception', $e)) {
- * $e->printStackTrace();
- * }
- *
- *
- * Usage example (Extracting):
- *
- * $a= &new Archive(new File('soap.cca'));
- * try(); {
- * $a->open(ARCHIVE_READ);
- * $c= array(
- * 'xml.soap.SOAPMessage' => $a->extract('xml.soap.SOAPMessage'),
- * 'xml.soap.SOAPClient' => $a->extract('xml.soap.SOAPClient')
- * );
- * } if (catch('Exception', $e)) {
- * $e->printStackTrace();
- * }
- * var_dump($c);
- *
- *
- * @purpose Provide an archiving
- * @see http://java.sun.com/j2se/1.4/docs/api/java/util/jar/package-summary.html
- */
- class Archive extends Object {
- var
- $file = NULL,
- $version = 1;
-
- var
- $_index = array();
-
- /**
- * Constructor
- *
- * @access public
- * @param &io.File file
- */
- function __construct(&$file) {
- $this->file= &$file;
-
- }
-
- /**
- * Add a file
- *
- * @access public
- * @param &io.File file
- * @param string id the id under which this entry will be located
- * @return bool success
- */
- function add(&$file, $id) {
- try(); {
- $file->open(FILE_MODE_READ);
- $data= $file->read($file->size());
- $file->close();
- } if (catch('Exception', $e)) {
- return throw($e);
- }
- $this->_index[$id]= array(
- $file->filename,
- $file->path,
- strlen($data),
- -1, // Will be calculated by create()
- $data
- );
- return TRUE;
- }
-
- /**
- * Create CCA archive
- *
- * @access public
- * @return bool success
- */
- function create() {
- try(); {
- $this->file->truncate();
- $this->file->write(pack(
- 'a3c1i1a248',
- 'CCA',
- $this->version,
- sizeof(array_keys($this->_index)),
- "\0" // Reserved for future use
- ));
-
- // Write index
- $offset= 0;
- foreach (array_keys($this->_index) as $id) {
- $this->file->write(pack(
- 'a80a80a80i1i1a8',
- $id,
- $this->_index[$id][0],
- $this->_index[$id][1],
- $this->_index[$id][2],
- $offset,
- "\0" // Reserved for future use
- ));
- $offset+= $this->_index[$id][2];
- }
-
- // Write files
- foreach (array_keys($this->_index) as $id) {
- $this->file->write($this->_index[$id][4]);
- }
-
- $this->file->close();
- } if (catch('Exception', $e)) {
- return throw($e);
- }
-
- return TRUE;
- }
-
- /**
- * Check whether a given element exists
- *
- * @access public
- * @param string id the element's id
- * @return bool TRUE when the element exists
- */
- function contains($id) {
- return isset($this->_index[$id]);
- }
-
- /**
- * Get entry (iterative use)
- *
- * $a= &new Archive(new File('port.cca'));
- * $a->open(ARCHIVE_READ);
- * while ($id= $a->getEntry()) {
- * var_dump($id);
- * }
- * $a->close();
- *
- *
- * @access public
- * @return string id or FALSE to indicate the pointer is at the end of the list
- */
- function getEntry() {
- $key= key($this->_index);
- next($this->_index);
- return $key;
- }
-
- /**
- * Rewind archive
- *
- * @access public
- */
- function rewind() {
- reset($this->_index);
- }
-
- /**
- * Extract a file's contents
- *
- * @access public
- * @param string id
- * @return &string content
- * @throws lang.ElementNotFoundException in case the specified id does not exist
- */
- function &extract($id) {
- if (!$this->contains($id)) {
- return throw(new ElementNotFoundException('Element "'.$id.'" not contained in this archive'));
- }
-
- // Calculate starting position
- $pos= (
- ARCHIVE_HEADER_SIZE +
- sizeof(array_keys($this->_index)) * ARCHIVE_INDEX_ENTRY_SIZE +
- $this->_index[$id][3]
- );
-
- try(); {
- $this->file->seek($pos, SEEK_SET);
- $data= $this->file->read($this->_index[$id][2]);
- } if (catch('Exception', $e)) {
- return throw($e);
- }
-
- return $data;
- }
-
- /**
- * Open this archive
- *
- * @access public
- * @param int mode default ARCHIVE_READ one of ARCHIVE_READ | ARCHIVE_CREATE
- * @return bool success
- * @throws lang.IllegalArgumentException in case an illegal mode was specified
- * @throws lang.FormatException in case the header is malformed
- */
- function open($mode) {
- switch ($mode) {
- case ARCHIVE_READ: // Load
- try(); {
- $this->file->open(FILE_MODE_READ);
-
- // Read header
- $header= $this->file->read(ARCHIVE_HEADER_SIZE);
- $data= unpack('a3id/c1version/i1indexsize/a*reserved', $header);
-
- // Check header integrity
- if ('CCA' !== $data['id']) throw(new FormatException(sprintf(
- 'Header malformed: "CCA" expected, have "%s"',
- substr($header, 0, 3)
- )));
- } if (catch('Exception', $e)) {
- return throw($e);
- }
-
- // Copy information
- $this->version = $data['version'];
-
- // Read index
- for ($i= 0; $i < $data['indexsize']; $i++) {
- $entry= unpack(
- 'a80id/a80filename/a80path/i1size/i1offset/a*reserved',
- $this->file->read(ARCHIVE_INDEX_ENTRY_SIZE)
- );
- $this->_index[$entry['id']]= array(
- $entry['filename'],
- $entry['path'],
- $entry['size'],
- $entry['offset'],
- NULL // Will not be read, use extract()
- );
- }
-
- return TRUE;
-
- case ARCHIVE_CREATE: // Create
- return $this->file->open(FILE_MODE_WRITE);
-
- }
-
- return throw(new IllegalArgumentException('Mode '.$mode.' not recognized'));
- }
-
- /**
- * Close this archive
- *
- * @access public
- * @return bool success
- */
- function close() {
- return $this->file->close();
- }
-
- /**
- * Checks whether this archive is open
- *
- * @access public
- * @param bool TRUE when the archive file is open
- */
- function isOpen() {
- return $this->file->isOpen();
- }
-
- /**
- * Returns a string representation of this object
- *
- * @access public
- * @return string
- */
- function toString() {
- return sprintf(
- '%s(version= %s, index size= %d) { %s }',
- $this->getClassName(),
- $this->version,
- sizeof($this->_index),
- xp::stringOf($this->file)
- );
- }
-
- /**
- * Destructor
- *
- * @access public
- */
- function __destruct() {
- if ($this->isOpen()) $this->close();
- }
- }
-?>