PageRenderTime 52ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/php/remote/protocol/Serializer.class.php

https://github.com/ghiata/xp-framework
PHP | 353 lines | 230 code | 46 blank | 77 comment | 29 complexity | e7a0d4dc06406d349f5ec2fc54a6928e MD5 | raw file
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses(
  7. 'remote.protocol.SerializedData',
  8. 'remote.protocol.DateMapping',
  9. 'remote.protocol.LongMapping',
  10. 'remote.protocol.ByteMapping',
  11. 'remote.protocol.ShortMapping',
  12. 'remote.protocol.FloatMapping',
  13. 'remote.protocol.DoubleMapping',
  14. 'remote.protocol.IntegerMapping',
  15. 'remote.protocol.HashmapMapping',
  16. 'remote.protocol.ArrayListMapping',
  17. 'remote.protocol.ExceptionMapping',
  18. 'remote.protocol.StackTraceElementMapping',
  19. 'remote.protocol.ByteArrayMapping',
  20. 'remote.protocol.EnumMapping',
  21. 'remote.UnknownRemoteObject',
  22. 'remote.ExceptionReference',
  23. 'remote.ClassReference',
  24. 'lang.Enum'
  25. );
  26. /**
  27. * Class that reimplements PHP's builtin serialization format.
  28. *
  29. * @see php://serialize
  30. * @test xp://net.xp_framework.unittest.remote.SerializerTest
  31. * @purpose Serializer
  32. */
  33. class Serializer extends Object {
  34. public
  35. $mappings = array(),
  36. $packages = array(0 => array(), 1 => array()),
  37. $exceptions = array();
  38. public
  39. $_classMapping = array();
  40. /**
  41. * Constructor. Initializes the default mappings
  42. *
  43. */
  44. public function __construct() {
  45. $this->mappings['T']= new DateMapping();
  46. $this->mappings['l']= new LongMapping();
  47. $this->mappings['B']= new ByteMapping();
  48. $this->mappings['S']= new ShortMapping();
  49. $this->mappings['f']= new FloatMapping();
  50. $this->mappings['d']= new DoubleMapping();
  51. $this->mappings['i']= new IntegerMapping();
  52. $this->mappings['A']= new ArrayListMapping();
  53. $this->mappings['e']= new ExceptionMapping();
  54. $this->mappings['t']= new StackTraceElementMapping();
  55. $this->mappings['Y']= new ByteArrayMapping();
  56. // A hashmap doesn't have its own token, because it'll be serialized
  57. // as an array. We use HASHMAP as the token, so it will never match
  58. // another one (can only be one char). This is a little bit hackish.
  59. $this->mappings['HASHMAP']= new HashmapMapping();
  60. // Setup default exceptions
  61. $this->exceptions['IllegalArgument']= 'lang.IllegalArgumentException';
  62. $this->exceptions['IllegalAccess']= 'lang.IllegalAccessException';
  63. $this->exceptions['ClassNotFound']= 'lang.ClassNotFoundException';
  64. $this->exceptions['NullPointer']= 'lang.NullPointerException';
  65. }
  66. /**
  67. * Retrieve serialized representation of a variable
  68. *
  69. * @param var var
  70. * @return string
  71. * @throws lang.FormatException if an error is encountered in the format
  72. */
  73. public function representationOf($var, $ctx= array()) {
  74. switch ($type= xp::typeOf($var)) {
  75. case '<null>': case 'NULL':
  76. return 'N;';
  77. case 'boolean':
  78. return 'b:'.($var ? 1 : 0).';';
  79. case 'integer':
  80. return 'i:'.$var.';';
  81. case 'double':
  82. return 'd:'.$var.';';
  83. case 'string':
  84. return 's:'.strlen($var).':"'.$var.'";';
  85. case 'array':
  86. $s= 'a:'.sizeof($var).':{';
  87. foreach (array_keys($var) as $key) {
  88. $s.= serialize($key).$this->representationOf($var[$key], $ctx);
  89. }
  90. return $s.'}';
  91. case 'resource':
  92. return ''; // Ignore (resources can't be serialized)
  93. case $var instanceof Generic: {
  94. if (FALSE !== ($m= $this->mappingFor($var))) {
  95. return $m->representationOf($this, $var, $ctx);
  96. }
  97. // Default object serializing
  98. $props= get_object_vars($var);
  99. $type= strtr($type, $this->packages[1]);
  100. unset($props['__id']);
  101. $s= 'O:'.strlen($type).':"'.$type.'":'.sizeof($props).':{';
  102. foreach (array_keys($props) as $name) {
  103. $s.= serialize($name).$this->representationOf($var->{$name}, $ctx);
  104. }
  105. return $s.'}';
  106. }
  107. default:
  108. throw new FormatException('Cannot serialize unknown type '.$type);
  109. }
  110. }
  111. /**
  112. * Fetch best fitted mapper for the given object
  113. *
  114. * @param lang.Object var
  115. * @return var FALSE in case no mapper could be found, &remote.protocol.SerializerMapping otherwise
  116. */
  117. public function mappingFor($var) {
  118. if (!($var instanceof Generic)) return FALSE; // Safeguard
  119. // Check the mapping-cache for an entry for this object's class
  120. if (isset($this->_classMapping[$var->getClassName()])) {
  121. return $this->_classMapping[$var->getClassName()];
  122. }
  123. // Find most suitable mapping by calculating the distance in the inheritance
  124. // tree of the object's class to the class being handled by the mapping.
  125. $cinfo= array();
  126. foreach (array_keys($this->mappings) as $token) {
  127. $class= $this->mappings[$token]->handledClass();
  128. if (!is($class->getName(), $var)) continue;
  129. $distance= 0; $objectClass= $var->getClass();
  130. do {
  131. // Check for direct match
  132. if ($class->getName() != $objectClass->getName()) $distance++;
  133. } while (0 < $distance && NULL !== ($objectClass= $objectClass->getParentClass()));
  134. // Register distance to object's class in cinfo
  135. $cinfo[$distance]= $this->mappings[$token];
  136. if (isset($cinfo[0])) break;
  137. }
  138. // No handlers found...
  139. if (0 == sizeof($cinfo)) return FALSE;
  140. ksort($cinfo, SORT_NUMERIC);
  141. // First class is best class
  142. // Remember this, so we can take shortcut next time
  143. $this->_classMapping[$var->getClassName()]= $cinfo[key($cinfo)];
  144. return $this->_classMapping[$var->getClassName()];
  145. }
  146. /**
  147. * Register or retrieve a mapping for a token
  148. *
  149. * @param string token
  150. * @param remote.protocol.SerializerMapping mapping
  151. * @return remote.protocol.SerializerMapping mapping
  152. * @throws lang.IllegalArgumentException if the given argument is not a SerializerMapping
  153. */
  154. public function mapping($token, $mapping) {
  155. if (NULL !== $mapping) {
  156. if (!$mapping instanceof SerializerMapping) throw new IllegalArgumentException(
  157. 'Given argument is not a SerializerMapping ('.xp::typeOf($mapping).')'
  158. );
  159. $this->mappings[$token]= $mapping;
  160. $this->_classMapping= array();
  161. }
  162. return $this->mappings[$token];
  163. }
  164. /**
  165. * Register or retrieve a mapping for a token
  166. *
  167. * @param string token
  168. * @param string exception fully qualified class name
  169. * @return string
  170. */
  171. public function exceptionName($name, $exception= NULL) {
  172. if (NULL !== $exception) $this->exceptions[$name]= $exception;
  173. return $this->exceptions[$name];
  174. }
  175. /**
  176. * Retrieve a mapping for a package.
  177. *
  178. * @param string remote
  179. * @param string mapped
  180. */
  181. public function packageMapping($remote) {
  182. return strtr($remote, $this->packages[0]);
  183. }
  184. /**
  185. * Map a remote package name to a local package
  186. *
  187. * @param string remote
  188. * @param lang.reflect.Package mapped
  189. */
  190. public function mapPackage($remote, Package $mapped) {
  191. $this->packages[0][$remote]= $mapped->getName();
  192. $this->packages[1][$mapped->getName()]= $remote;
  193. }
  194. /**
  195. * Retrieve serialized representation of a variable
  196. *
  197. * @param remote.protocol.SerializedData serialized
  198. * @param array context default array()
  199. * @return var
  200. * @throws lang.ClassNotFoundException if a class cannot be found
  201. * @throws lang.FormatException if an error is encountered in the format
  202. */
  203. public function valueOf($serialized, $context= array()) {
  204. static $types= NULL;
  205. if (!$types) $types= array(
  206. 'N' => 'void',
  207. 'b' => 'boolean',
  208. 'i' => 'integer',
  209. 'd' => 'double',
  210. 's' => 'string',
  211. 'B' => new ClassReference('lang.types.Byte'),
  212. 'S' => new ClassReference('lang.types.Short'),
  213. 'f' => new ClassReference('lang.types.Float'),
  214. 'l' => new ClassReference('lang.types.Long'),
  215. 'a' => 'array',
  216. 'A' => new ClassReference('lang.types.ArrayList'),
  217. 'T' => new ClassReference('util.Date')
  218. );
  219. $token= $serialized->consumeToken();
  220. switch ($token) {
  221. case 'N': return NULL;
  222. case 'b': return (bool)$serialized->consumeWord();
  223. case 'i': return (int)$serialized->consumeWord();
  224. case 'd': return (float)$serialized->consumeWord();
  225. case 's': return $serialized->consumeString();
  226. case 'a': { // arrays
  227. $a= array();
  228. $size= $serialized->consumeSize();
  229. $serialized->consume('{');
  230. for ($i= 0; $i < $size; $i++) {
  231. $key= $this->valueOf($serialized, $context);
  232. $a[$key]= $this->valueOf($serialized, $context);
  233. }
  234. $serialized->consume('}');
  235. return $a;
  236. }
  237. case 'E': { // generic exceptions
  238. $instance= new ExceptionReference($serialized->consumeString());
  239. $size= $serialized->consumeSize();
  240. $serialized->consume('{');
  241. for ($i= 0; $i < $size; $i++) {
  242. $member= $this->valueOf($serialized, $context);
  243. $instance->{$member}= $this->valueOf($serialized, $context);
  244. }
  245. $serialized->consume('}');
  246. return $instance;
  247. }
  248. case 'O': { // generic objects
  249. $name= $serialized->consumeString();
  250. $members= array();
  251. try {
  252. $class= XPClass::forName(strtr($name, $this->packages[0]));
  253. } catch (ClassNotFoundException $e) {
  254. $instance= new UnknownRemoteObject($name);
  255. $size= $serialized->consumeSize();
  256. $serialized->consume('{');
  257. for ($i= 0; $i < $size; $i++) {
  258. $member= $this->valueOf($serialized, $context);
  259. $members[$member]= $this->valueOf($serialized, $context);
  260. }
  261. $serialized->consume('}');
  262. $instance->__members= $members;
  263. return $instance;
  264. }
  265. $size= $serialized->consumeSize();
  266. $serialized->consume('{');
  267. if ($class->isEnum()) {
  268. if ($size != 1 || 'name' != $this->valueOf($serialized, $context)) {
  269. throw new FormatException(sprintf(
  270. 'Local class %s is an enum but remote class is not serialized as one (%s)',
  271. $name,
  272. $serialized->toString()
  273. ));
  274. }
  275. $instance= Enum::valueOf($class, $this->valueOf($serialized, $context));
  276. } else {
  277. $instance= $class->newInstance();
  278. for ($i= 0; $i < $size; $i++) {
  279. $member= $this->valueOf($serialized, $context);
  280. $instance->{$member}= $this->valueOf($serialized, $context);
  281. }
  282. }
  283. $serialized->consume('}');
  284. return $instance;
  285. }
  286. case 'c': { // builtin classes
  287. $type= $serialized->consumeWord();
  288. if (!isset($types[$type])) {
  289. throw new FormatException('Unknown type token "'.$type.'"');
  290. }
  291. return $types[$type];
  292. }
  293. case 'C': { // generic classes
  294. return new ClassReference(strtr($serialized->consumeString(), $this->packages[0]));
  295. }
  296. default: { // default, check if we have a mapping
  297. if (!($mapping= $this->mapping($token, $m= NULL))) {
  298. throw new FormatException(
  299. 'Cannot deserialize unknown type "'.$token.'" ('.$serialized->toString().')'
  300. );
  301. }
  302. return $mapping->valueOf($this, $serialized, $context);
  303. }
  304. }
  305. }
  306. }
  307. ?>