| <?php |
| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * Avro IO object classes |
| * @package Avro |
| */ |
| |
| /** |
| * Exceptions associated with AvroIO instances. |
| * @package Avro |
| */ |
| class AvroIOException extends AvroException {} |
| |
| /** |
| * Barebones IO base class to provide common interface for file and string |
| * access within the Avro classes. |
| * |
| * @package Avro |
| */ |
| class AvroIO |
| { |
| |
| /** |
| * @var string general read mode |
| */ |
| const READ_MODE = 'r'; |
| /** |
| * @var string general write mode. |
| */ |
| const WRITE_MODE = 'w'; |
| |
| /** |
| * @var int set position to current index + $offset bytes |
| */ |
| const SEEK_CUR = SEEK_CUR; |
| /** |
| * @var int set position equal to $offset bytes |
| */ |
| const SEEK_SET = SEEK_SET; |
| /** |
| * @var int set position to end of file + $offset bytes |
| */ |
| const SEEK_END = SEEK_END; |
| |
| /** |
| * Read $len bytes from AvroIO instance |
| * @var int $len |
| * @return string bytes read |
| */ |
| public function read($len) |
| { |
| throw new AvroNotImplementedException('Not implemented'); |
| } |
| |
| /** |
| * Append bytes to this buffer. (Nothing more is needed to support Avro.) |
| * @param str $arg bytes to write |
| * @returns int count of bytes written. |
| * @throws AvroIOException if $args is not a string value. |
| */ |
| public function write($arg) |
| { |
| throw new AvroNotImplementedException('Not implemented'); |
| } |
| |
| /** |
| * Return byte offset within AvroIO instance |
| * @return int |
| */ |
| public function tell() |
| { |
| throw new AvroNotImplementedException('Not implemented'); |
| } |
| |
| /** |
| * Set the position indicator. The new position, measured in bytes |
| * from the beginning of the file, is obtained by adding $offset to |
| * the position specified by $whence. |
| * |
| * @param int $offset |
| * @param int $whence one of AvroIO::SEEK_SET, AvroIO::SEEK_CUR, |
| * or Avro::SEEK_END |
| * @returns boolean true |
| * |
| * @throws AvroIOException |
| */ |
| public function seek($offset, $whence=self::SEEK_SET) |
| { |
| throw new AvroNotImplementedException('Not implemented'); |
| } |
| |
| /** |
| * Flushes any buffered data to the AvroIO object. |
| * @returns boolean true upon success. |
| */ |
| public function flush() |
| { |
| throw new AvroNotImplementedException('Not implemented'); |
| } |
| |
| /** |
| * Returns whether or not the current position at the end of this AvroIO |
| * instance. |
| * |
| * Note is_eof() is <b>not</b> like eof in C or feof in PHP: |
| * it returns TRUE if the *next* read would be end of file, |
| * rather than if the *most recent* read read end of file. |
| * @returns boolean true if at the end of file, and false otherwise |
| */ |
| public function is_eof() |
| { |
| throw new AvroNotImplementedException('Not implemented'); |
| } |
| |
| /** |
| * Closes this AvroIO instance. |
| */ |
| public function close() |
| { |
| throw new AvroNotImplementedException('Not implemented'); |
| } |
| |
| } |
| |
| /** |
| * AvroIO wrapper for string access |
| * @package Avro |
| */ |
| class AvroStringIO extends AvroIO |
| { |
| /** |
| * @var string |
| */ |
| private $string_buffer; |
| /** |
| * @var int current position in string |
| */ |
| private $current_index; |
| /** |
| * @var boolean whether or not the string is closed. |
| */ |
| private $is_closed; |
| |
| /** |
| * @param string $str initial value of AvroStringIO buffer. Regardless |
| * of the initial value, the pointer is set to the |
| * beginning of the buffer. |
| * @throws AvroIOException if a non-string value is passed as $str |
| */ |
| public function __construct($str = '') |
| { |
| $this->is_closed = false; |
| $this->string_buffer = ''; |
| $this->current_index = 0; |
| |
| if (is_string($str)) |
| $this->string_buffer .= $str; |
| else |
| throw new AvroIOException( |
| sprintf('constructor argument must be a string: %s', gettype($str))); |
| } |
| |
| /** |
| * Append bytes to this buffer. |
| * (Nothing more is needed to support Avro.) |
| * @param str $arg bytes to write |
| * @returns int count of bytes written. |
| * @throws AvroIOException if $args is not a string value. |
| */ |
| public function write($arg) |
| { |
| $this->check_closed(); |
| if (is_string($arg)) |
| return $this->append_str($arg); |
| throw new AvroIOException( |
| sprintf('write argument must be a string: (%s) %s', |
| gettype($arg), var_export($arg, true))); |
| } |
| |
| /** |
| * @returns string bytes read from buffer |
| * @todo test for fencepost errors wrt updating current_index |
| */ |
| public function read($len) |
| { |
| $this->check_closed(); |
| $read=''; |
| for($i=$this->current_index; $i<($this->current_index+$len); $i++) |
| $read .= $this->string_buffer[$i]; |
| if (strlen($read) < $len) |
| $this->current_index = $this->length(); |
| else |
| $this->current_index += $len; |
| return $read; |
| } |
| |
| /** |
| * @returns boolean true if successful |
| * @throws AvroIOException if the seek failed. |
| */ |
| public function seek($offset, $whence=self::SEEK_SET) |
| { |
| if (!is_int($offset)) |
| throw new AvroIOException('Seek offset must be an integer.'); |
| // Prevent seeking before BOF |
| switch ($whence) |
| { |
| case self::SEEK_SET: |
| if (0 > $offset) |
| throw new AvroIOException('Cannot seek before beginning of file.'); |
| $this->current_index = $offset; |
| break; |
| case self::SEEK_CUR: |
| if (0 > $this->current_index + $whence) |
| throw new AvroIOException('Cannot seek before beginning of file.'); |
| $this->current_index += $offset; |
| break; |
| case self::SEEK_END: |
| if (0 > $this->length() + $offset) |
| throw new AvroIOException('Cannot seek before beginning of file.'); |
| $this->current_index = $this->length() + $offset; |
| break; |
| default: |
| throw new AvroIOException(sprintf('Invalid seek whence %d', $whence)); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @returns int |
| * @see AvroIO::tell() |
| */ |
| public function tell() { return $this->current_index; } |
| |
| /** |
| * @returns boolean |
| * @see AvroIO::is_eof() |
| */ |
| public function is_eof() |
| { |
| return ($this->current_index >= $this->length()); |
| } |
| |
| /** |
| * No-op provided for compatibility with AvroIO interface. |
| * @returns boolean true |
| */ |
| public function flush() { return true; } |
| |
| /** |
| * Marks this buffer as closed. |
| * @returns boolean true |
| */ |
| public function close() |
| { |
| $this->check_closed(); |
| $this->is_closed = true; |
| return true; |
| } |
| |
| /** |
| * @throws AvroIOException if the buffer is closed. |
| */ |
| private function check_closed() |
| { |
| if ($this->is_closed()) |
| throw new AvroIOException('Buffer is closed'); |
| } |
| |
| /** |
| * Appends bytes to this buffer. |
| * @param string $str |
| * @returns integer count of bytes written. |
| */ |
| private function append_str($str) |
| { |
| $this->check_closed(); |
| $this->string_buffer .= $str; |
| $len = strlen($str); |
| $this->current_index += $len; |
| return $len; |
| } |
| |
| /** |
| * Truncates the truncate buffer to 0 bytes and returns the pointer |
| * to the beginning of the buffer. |
| * @returns boolean true |
| */ |
| public function truncate() |
| { |
| $this->check_closed(); |
| $this->string_buffer = ''; |
| $this->current_index = 0; |
| return true; |
| } |
| |
| /** |
| * @returns int count of bytes in the buffer |
| * @internal Could probably memoize length for performance, but |
| * no need do this yet. |
| */ |
| public function length() { return strlen($this->string_buffer); } |
| |
| /** |
| * @returns string |
| */ |
| public function __toString() { return $this->string_buffer; } |
| |
| |
| /** |
| * @returns string |
| * @uses self::__toString() |
| */ |
| public function string() { return $this->__toString(); } |
| |
| /** |
| * @returns boolean true if this buffer is closed and false |
| * otherwise. |
| */ |
| public function is_closed() { return $this->is_closed; } |
| } |
| |
| /** |
| * AvroIO wrapper for PHP file access functions |
| * @package Avro |
| */ |
| class AvroFile extends AvroIO |
| { |
| /** |
| * @var string fopen read mode value. Used internally. |
| */ |
| const FOPEN_READ_MODE = 'rb'; |
| |
| /** |
| * @var string fopen write mode value. Used internally. |
| */ |
| const FOPEN_WRITE_MODE = 'wb'; |
| |
| /** |
| * @var string |
| */ |
| private $file_path; |
| |
| /** |
| * @var resource file handle for AvroFile instance |
| */ |
| private $file_handle; |
| |
| public function __construct($file_path, $mode = self::READ_MODE) |
| { |
| /** |
| * XXX: should we check for file existence (in case of reading) |
| * or anything else about the provided file_path argument? |
| */ |
| $this->file_path = $file_path; |
| switch ($mode) |
| { |
| case self::WRITE_MODE: |
| $this->file_handle = fopen($this->file_path, self::FOPEN_WRITE_MODE); |
| if (false == $this->file_handle) |
| throw new AvroIOException('Could not open file for writing'); |
| break; |
| case self::READ_MODE: |
| $this->file_handle = fopen($this->file_path, self::FOPEN_READ_MODE); |
| if (false == $this->file_handle) |
| throw new AvroIOException('Could not open file for reading'); |
| break; |
| default: |
| throw new AvroIOException( |
| sprintf("Only modes '%s' and '%s' allowed. You provided '%s'.", |
| self::READ_MODE, self::WRITE_MODE, $mode)); |
| } |
| } |
| |
| /** |
| * @returns int count of bytes written |
| * @throws AvroIOException if write failed. |
| */ |
| public function write($str) |
| { |
| $len = fwrite($this->file_handle, $str); |
| if (false === $len) |
| throw new AvroIOException(sprintf('Could not write to file')); |
| return $len; |
| } |
| |
| /** |
| * @param int $len count of bytes to read. |
| * @returns string bytes read |
| * @throws AvroIOException if length value is negative or if the read failed |
| */ |
| public function read($len) |
| { |
| if (0 > $len) |
| throw new AvroIOException( |
| sprintf("Invalid length value passed to read: %d", $len)); |
| |
| if (0 == $len) |
| return ''; |
| |
| $bytes = fread($this->file_handle, $len); |
| if (false === $bytes) |
| throw new AvroIOException('Could not read from file'); |
| return $bytes; |
| } |
| |
| /** |
| * @returns int current position within the file |
| * @throws AvroFileExcpetion if tell failed. |
| */ |
| public function tell() |
| { |
| $position = ftell($this->file_handle); |
| if (false === $position) |
| throw new AvroIOException('Could not execute tell on reader'); |
| return $position; |
| } |
| |
| /** |
| * @param int $offset |
| * @param int $whence |
| * @returns boolean true upon success |
| * @throws AvroIOException if seek failed. |
| * @see AvroIO::seek() |
| */ |
| public function seek($offset, $whence = SEEK_SET) |
| { |
| $res = fseek($this->file_handle, $offset, $whence); |
| // Note: does not catch seeking beyond end of file |
| if (-1 === $res) |
| throw new AvroIOException( |
| sprintf("Could not execute seek (offset = %d, whence = %d)", |
| $offset, $whence)); |
| return true; |
| } |
| |
| /** |
| * Closes the file. |
| * @returns boolean true if successful. |
| * @throws AvroIOException if there was an error closing the file. |
| */ |
| public function close() |
| { |
| $res = fclose($this->file_handle); |
| if (false === $res) |
| throw new AvroIOException('Error closing file.'); |
| return $res; |
| } |
| |
| /** |
| * @returns boolean true if the pointer is at the end of the file, |
| * and false otherwise. |
| * @see AvroIO::is_eof() as behavior differs from feof() |
| */ |
| public function is_eof() |
| { |
| $this->read(1); |
| if (feof($this->file_handle)) |
| return true; |
| $this->seek(-1, self::SEEK_CUR); |
| return false; |
| } |
| |
| /** |
| * @returns boolean true if the flush was successful. |
| * @throws AvroIOException if there was an error flushing the file. |
| */ |
| public function flush() |
| { |
| $res = fflush($this->file_handle); |
| if (false === $res) |
| throw new AvroIOException('Could not flush file.'); |
| return true; |
| } |
| |
| } |