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(); - } - } -?>