Development

/plugins/sfThriftPlugin/trunk/lib/Thrift.php

You must first sign up to be able to contribute.

root/plugins/sfThriftPlugin/trunk/lib/Thrift.php

Revision 31143, 22.1 kB (checked in by tomi, 3 years ago)

copy from code.google.com

Line 
1 <?php
2 /*
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements. See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership. The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License. You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied. See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  * @package thrift
21  */
22
23
24 /**
25  * Data types that can be sent via Thrift
26  */
27 class TType {
28   const STOP   = 0;
29   const VOID   = 1;
30   const BOOL   = 2;
31   const BYTE   = 3;
32   const I08    = 3;
33   const DOUBLE = 4;
34   const I16    = 6;
35   const I32    = 8;
36   const I64    = 10;
37   const STRING = 11;
38   const UTF7   = 11;
39   const STRUCT = 12;
40   const MAP    = 13;
41   const SET    = 14;
42   const LST    = 15;    // N.B. cannot use LIST keyword in PHP!
43   const UTF8   = 16;
44   const UTF16  = 17;
45 }
46
47 /**
48  * Message types for RPC
49  */
50 class TMessageType {
51   const CALL  = 1;
52   const REPLY = 2;
53   const EXCEPTION = 3;
54   const ONEWAY = 4;
55 }
56
57 /**
58  * NOTE(mcslee): This currently contains a ton of duplicated code from TBase
59  * because we need to save CPU cycles and this is not yet in an extension.
60  * Ideally we'd multiply-inherit TException from both Exception and Base, but
61  * that's not possible in PHP and there are no modules either, so for now we
62  * apologetically take a trip to HackTown.
63  *
64  * Can be called with standard Exception constructor (message, code) or with
65  * Thrift Base object constructor (spec, vals).
66  *
67  * @param mixed $p1 Message (string) or type-spec (array)
68  * @param mixed $p2 Code (integer) or values (array)
69  */
70 class TException extends Exception {
71   function __construct($p1=null, $p2=0) {
72     if (is_array($p1) && is_array($p2)) {
73       $spec = $p1;
74       $vals = $p2;
75       foreach ($spec as $fid => $fspec) {
76         $var = $fspec['var'];
77         if (isset($vals[$var])) {
78           $this->$var = $vals[$var];
79         }
80       }
81     } else {
82       parent::__construct($p1, $p2);
83     }
84   }
85
86   static $tmethod = array(TType::BOOL   => 'Bool',
87                           TType::BYTE   => 'Byte',
88                           TType::I16    => 'I16',
89                           TType::I32    => 'I32',
90                           TType::I64    => 'I64',
91                           TType::DOUBLE => 'Double',
92                           TType::STRING => 'String');
93
94   private function _readMap(&$var, $spec, $input) {
95     $xfer = 0;
96     $ktype = $spec['ktype'];
97     $vtype = $spec['vtype'];
98     $kread = $vread = null;
99     if (isset(TBase::$tmethod[$ktype])) {
100       $kread = 'read'.TBase::$tmethod[$ktype];
101     } else {
102       $kspec = $spec['key'];
103     }
104     if (isset(TBase::$tmethod[$vtype])) {
105       $vread = 'read'.TBase::$tmethod[$vtype];
106     } else {
107       $vspec = $spec['val'];
108     }
109     $var = array();
110     $_ktype = $_vtype = $size = 0;
111     $xfer += $input->readMapBegin($_ktype, $_vtype, $size);
112     for ($i = 0; $i < $size; ++$i) {
113       $key = $val = null;
114       if ($kread !== null) {
115         $xfer += $input->$kread($key);
116       } else {
117         switch ($ktype) {
118         case TType::STRUCT:
119           $class = $kspec['class'];
120           $key = new $class();
121           $xfer += $key->read($input);
122           break;
123         case TType::MAP:
124           $xfer += $this->_readMap($key, $kspec, $input);
125           break;
126         case TType::LST:
127           $xfer += $this->_readList($key, $kspec, $input, false);
128           break;
129         case TType::SET:
130           $xfer += $this->_readList($key, $kspec, $input, true);
131           break;
132         }
133       }
134       if ($vread !== null) {
135         $xfer += $input->$vread($val);
136       } else {
137         switch ($vtype) {
138         case TType::STRUCT:
139           $class = $vspec['class'];
140           $val = new $class();
141           $xfer += $val->read($input);
142           break;
143         case TType::MAP:
144           $xfer += $this->_readMap($val, $vspec, $input);
145           break;
146         case TType::LST:
147           $xfer += $this->_readList($val, $vspec, $input, false);
148           break;
149         case TType::SET:
150           $xfer += $this->_readList($val, $vspec, $input, true);
151           break;
152         }
153       }
154       $var[$key] = $val;
155     }
156     $xfer += $input->readMapEnd();
157     return $xfer;
158   }
159
160   private function _readList(&$var, $spec, $input, $set=false) {
161     $xfer = 0;
162     $etype = $spec['etype'];
163     $eread = $vread = null;
164     if (isset(TBase::$tmethod[$etype])) {
165       $eread = 'read'.TBase::$tmethod[$etype];
166     } else {
167       $espec = $spec['elem'];
168     }
169     $var = array();
170     $_etype = $size = 0;
171     if ($set) {
172       $xfer += $input->readSetBegin($_etype, $size);
173     } else {
174       $xfer += $input->readListBegin($_etype, $size);
175     }
176     for ($i = 0; $i < $size; ++$i) {
177       $elem = null;
178       if ($eread !== null) {
179         $xfer += $input->$eread($elem);
180       } else {
181         $espec = $spec['elem'];
182         switch ($etype) {
183         case TType::STRUCT:
184           $class = $espec['class'];
185           $elem = new $class();
186           $xfer += $elem->read($input);
187           break;
188         case TType::MAP:
189           $xfer += $this->_readMap($elem, $espec, $input);
190           break;
191         case TType::LST:
192           $xfer += $this->_readList($elem, $espec, $input, false);
193           break;
194         case TType::SET:
195           $xfer += $this->_readList($elem, $espec, $input, true);
196           break;
197         }
198       }
199       if ($set) {
200         $var[$elem] = true;
201       } else {
202         $var []= $elem;
203       }
204     }
205     if ($set) {
206       $xfer += $input->readSetEnd();
207     } else {
208       $xfer += $input->readListEnd();
209     }
210     return $xfer;
211   }
212
213   protected function _read($class, $spec, $input) {
214     $xfer = 0;
215     $fname = null;
216     $ftype = 0;
217     $fid = 0;
218     $xfer += $input->readStructBegin($fname);
219     while (true) {
220       $xfer += $input->readFieldBegin($fname, $ftype, $fid);
221       if ($ftype == TType::STOP) {
222         break;
223       }
224       if (isset($spec[$fid])) {
225         $fspec = $spec[$fid];
226         $var = $fspec['var'];
227         if ($ftype == $fspec['type']) {
228           $xfer = 0;
229           if (isset(TBase::$tmethod[$ftype])) {
230             $func = 'read'.TBase::$tmethod[$ftype];
231             $xfer += $input->$func($this->$var);
232           } else {
233             switch ($ftype) {
234             case TType::STRUCT:
235               $class = $fspec['class'];
236               $this->$var = new $class();
237               $xfer += $this->$var->read($input);
238               break;
239             case TType::MAP:
240               $xfer += $this->_readMap($this->$var, $fspec, $input);
241               break;
242             case TType::LST:
243               $xfer += $this->_readList($this->$var, $fspec, $input, false);
244               break;
245             case TType::SET:
246               $xfer += $this->_readList($this->$var, $fspec, $input, true);
247               break;
248             }
249           }
250         } else {
251           $xfer += $input->skip($ftype);
252         }
253       } else {
254         $xfer += $input->skip($ftype);
255       }
256       $xfer += $input->readFieldEnd();
257     }
258     $xfer += $input->readStructEnd();
259     return $xfer;
260   }
261
262   private function _writeMap($var, $spec, $output) {
263     $xfer = 0;
264     $ktype = $spec['ktype'];
265     $vtype = $spec['vtype'];
266     $kwrite = $vwrite = null;
267     if (isset(TBase::$tmethod[$ktype])) {
268       $kwrite = 'write'.TBase::$tmethod[$ktype];
269     } else {
270       $kspec = $spec['key'];
271     }
272     if (isset(TBase::$tmethod[$vtype])) {
273       $vwrite = 'write'.TBase::$tmethod[$vtype];
274     } else {
275       $vspec = $spec['val'];
276     }
277     $xfer += $output->writeMapBegin($ktype, $vtype, count($var));
278     foreach ($var as $key => $val) {
279       if (isset($kwrite)) {
280         $xfer += $output->$kwrite($key);
281       } else {
282         switch ($ktype) {
283         case TType::STRUCT:
284           $xfer += $key->write($output);
285           break;
286         case TType::MAP:
287           $xfer += $this->_writeMap($key, $kspec, $output);
288           break;
289         case TType::LST:
290           $xfer += $this->_writeList($key, $kspec, $output, false);
291           break;
292         case TType::SET:
293           $xfer += $this->_writeList($key, $kspec, $output, true);
294           break;
295         }
296       }
297       if (isset($vwrite)) {
298         $xfer += $output->$vwrite($val);
299       } else {
300         switch ($vtype) {
301         case TType::STRUCT:
302           $xfer += $val->write($output);
303           break;
304         case TType::MAP:
305           $xfer += $this->_writeMap($val, $vspec, $output);
306           break;
307         case TType::LST:
308           $xfer += $this->_writeList($val, $vspec, $output, false);
309           break;
310         case TType::SET:
311           $xfer += $this->_writeList($val, $vspec, $output, true);
312           break;
313         }
314       }
315     }
316     $xfer += $output->writeMapEnd();
317     return $xfer;
318   }
319
320   private function _writeList($var, $spec, $output, $set=false) {
321     $xfer = 0;
322     $etype = $spec['etype'];
323     $ewrite = null;
324     if (isset(TBase::$tmethod[$etype])) {
325       $ewrite = 'write'.TBase::$tmethod[$etype];
326     } else {
327       $espec = $spec['elem'];
328     }
329     if ($set) {
330       $xfer += $output->writeSetBegin($etype, count($var));
331     } else {
332       $xfer += $output->writeListBegin($etype, count($var));
333     }
334     foreach ($var as $key => $val) {
335       $elem = $set ? $key : $val;
336       if (isset($ewrite)) {
337         $xfer += $output->$ewrite($elem);
338       } else {
339         switch ($etype) {
340         case TType::STRUCT:
341           $xfer += $elem->write($output);
342           break;
343         case TType::MAP:
344           $xfer += $this->_writeMap($elem, $espec, $output);
345           break;
346         case TType::LST:
347           $xfer += $this->_writeList($elem, $espec, $output, false);
348           break;
349         case TType::SET:
350           $xfer += $this->_writeList($elem, $espec, $output, true);
351           break;
352         }
353       }
354     }
355     if ($set) {
356       $xfer += $output->writeSetEnd();
357     } else {
358       $xfer += $output->writeListEnd();
359     }
360     return $xfer;
361   }
362
363   protected function _write($class, $spec, $output) {
364     $xfer = 0;
365     $xfer += $output->writeStructBegin($class);
366     foreach ($spec as $fid => $fspec) {
367       $var = $fspec['var'];
368       if ($this->$var !== null) {
369         $ftype = $fspec['type'];
370         $xfer += $output->writeFieldBegin($var, $ftype, $fid);
371         if (isset(TBase::$tmethod[$ftype])) {
372           $func = 'write'.TBase::$tmethod[$ftype];
373           $xfer += $output->$func($this->$var);
374         } else {
375           switch ($ftype) {
376           case TType::STRUCT:
377             $xfer += $this->$var->write($output);
378             break;
379           case TType::MAP:
380             $xfer += $this->_writeMap($this->$var, $fspec, $output);
381             break;
382           case TType::LST:
383             $xfer += $this->_writeList($this->$var, $fspec, $output, false);
384             break;
385           case TType::SET:
386             $xfer += $this->_writeList($this->$var, $fspec, $output, true);
387             break;
388           }
389         }
390         $xfer += $output->writeFieldEnd();
391       }
392     }
393     $xfer += $output->writeFieldStop();
394     $xfer += $output->writeStructEnd();
395     return $xfer;
396   }
397
398 }
399
400 /**
401  * Base class from which other Thrift structs extend. This is so that we can
402  * cut back on the size of the generated code which is turning out to have a
403  * nontrivial cost just to load thanks to the wondrously abysmal implementation
404  * of PHP. Note that code is intentionally duplicated in here to avoid making
405  * function calls for every field or member of a container..
406  */
407 abstract class TBase {
408
409   static $tmethod = array(TType::BOOL   => 'Bool',
410                           TType::BYTE   => 'Byte',
411                           TType::I16    => 'I16',
412                           TType::I32    => 'I32',
413                           TType::I64    => 'I64',
414                           TType::DOUBLE => 'Double',
415                           TType::STRING => 'String');
416
417   abstract function read($input);
418
419   abstract function write($output);
420
421   public function __construct($spec=null, $vals=null) {
422     if (is_array($spec) && is_array($vals)) {
423       foreach ($spec as $fid => $fspec) {
424         $var = $fspec['var'];
425         if (isset($vals[$var])) {
426           $this->$var = $vals[$var];
427         }
428       }
429     }
430   }
431
432   private function _readMap(&$var, $spec, $input) {
433     $xfer = 0;
434     $ktype = $spec['ktype'];
435     $vtype = $spec['vtype'];
436     $kread = $vread = null;
437     if (isset(TBase::$tmethod[$ktype])) {
438       $kread = 'read'.TBase::$tmethod[$ktype];
439     } else {
440       $kspec = $spec['key'];
441     }
442     if (isset(TBase::$tmethod[$vtype])) {
443       $vread = 'read'.TBase::$tmethod[$vtype];
444     } else {
445       $vspec = $spec['val'];
446     }
447     $var = array();
448     $_ktype = $_vtype = $size = 0;
449     $xfer += $input->readMapBegin($_ktype, $_vtype, $size);
450     for ($i = 0; $i < $size; ++$i) {
451       $key = $val = null;
452       if ($kread !== null) {
453         $xfer += $input->$kread($key);
454       } else {
455         switch ($ktype) {
456         case TType::STRUCT:
457           $class = $kspec['class'];
458           $key = new $class();
459           $xfer += $key->read($input);
460           break;
461         case TType::MAP:
462           $xfer += $this->_readMap($key, $kspec, $input);
463           break;
464         case TType::LST:
465           $xfer += $this->_readList($key, $kspec, $input, false);
466           break;
467         case TType::SET:
468           $xfer += $this->_readList($key, $kspec, $input, true);
469           break;
470         }
471       }
472       if ($vread !== null) {
473         $xfer += $input->$vread($val);
474       } else {
475         switch ($vtype) {
476         case TType::STRUCT:
477           $class = $vspec['class'];
478           $val = new $class();
479           $xfer += $val->read($input);
480           break;
481         case TType::MAP:
482           $xfer += $this->_readMap($val, $vspec, $input);
483           break;
484         case TType::LST:
485           $xfer += $this->_readList($val, $vspec, $input, false);
486           break;
487         case TType::SET:
488           $xfer += $this->_readList($val, $vspec, $input, true);
489           break;
490         }
491       }
492       $var[$key] = $val;
493     }
494     $xfer += $input->readMapEnd();
495     return $xfer;
496   }
497
498   private function _readList(&$var, $spec, $input, $set=false) {
499     $xfer = 0;
500     $etype = $spec['etype'];
501     $eread = $vread = null;
502     if (isset(TBase::$tmethod[$etype])) {
503       $eread = 'read'.TBase::$tmethod[$etype];
504     } else {
505       $espec = $spec['elem'];
506     }
507     $var = array();
508     $_etype = $size = 0;
509     if ($set) {
510       $xfer += $input->readSetBegin($_etype, $size);
511     } else {
512       $xfer += $input->readListBegin($_etype, $size);
513     }
514     for ($i = 0; $i < $size; ++$i) {
515       $elem = null;
516       if ($eread !== null) {
517         $xfer += $input->$eread($elem);
518       } else {
519         $espec = $spec['elem'];
520         switch ($etype) {
521         case TType::STRUCT:
522           $class = $espec['class'];
523           $elem = new $class();
524           $xfer += $elem->read($input);
525           break;
526         case TType::MAP:
527           $xfer += $this->_readMap($elem, $espec, $input);
528           break;
529         case TType::LST:
530           $xfer += $this->_readList($elem, $espec, $input, false);
531           break;
532         case TType::SET:
533           $xfer += $this->_readList($elem, $espec, $input, true);
534           break;
535         }
536       }
537       if ($set) {
538         $var[$elem] = true;
539       } else {
540         $var []= $elem;
541       }
542     }
543     if ($set) {
544       $xfer += $input->readSetEnd();
545     } else {
546       $xfer += $input->readListEnd();
547     }
548     return $xfer;
549   }
550
551   protected function _read($class, $spec, $input) {
552     $xfer = 0;
553     $fname = null;
554     $ftype = 0;
555     $fid = 0;
556     $xfer += $input->readStructBegin($fname);
557     while (true) {
558       $xfer += $input->readFieldBegin($fname, $ftype, $fid);
559       if ($ftype == TType::STOP) {
560         break;
561       }
562       if (isset($spec[$fid])) {
563         $fspec = $spec[$fid];
564         $var = $fspec['var'];
565         if ($ftype == $fspec['type']) {
566           $xfer = 0;
567           if (isset(TBase::$tmethod[$ftype])) {
568             $func = 'read'.TBase::$tmethod[$ftype];
569             $xfer += $input->$func($this->$var);
570           } else {
571             switch ($ftype) {
572             case TType::STRUCT:
573               $class = $fspec['class'];
574               $this->$var = new $class();
575               $xfer += $this->$var->read($input);
576               break;
577             case TType::MAP:
578               $xfer += $this->_readMap($this->$var, $fspec, $input);
579               break;
580             case TType::LST:
581               $xfer += $this->_readList($this->$var, $fspec, $input, false);
582               break;
583             case TType::SET:
584               $xfer += $this->_readList($this->$var, $fspec, $input, true);
585               break;
586             }
587           }
588         } else {
589           $xfer += $input->skip($ftype);
590         }
591       } else {
592         $xfer += $input->skip($ftype);
593       }
594       $xfer += $input->readFieldEnd();
595     }
596     $xfer += $input->readStructEnd();
597     return $xfer;
598   }
599
600   private function _writeMap($var, $spec, $output) {
601     $xfer = 0;
602     $ktype = $spec['ktype'];
603     $vtype = $spec['vtype'];
604     $kwrite = $vwrite = null;
605     if (isset(TBase::$tmethod[$ktype])) {
606       $kwrite = 'write'.TBase::$tmethod[$ktype];
607     } else {
608       $kspec = $spec['key'];
609     }
610     if (isset(TBase::$tmethod[$vtype])) {
611       $vwrite = 'write'.TBase::$tmethod[$vtype];
612     } else {
613       $vspec = $spec['val'];
614     }
615     $xfer += $output->writeMapBegin($ktype, $vtype, count($var));
616     foreach ($var as $key => $val) {
617       if (isset($kwrite)) {
618         $xfer += $output->$kwrite($key);
619       } else {
620         switch ($ktype) {
621         case TType::STRUCT:
622           $xfer += $key->write($output);
623           break;
624         case TType::MAP:
625           $xfer += $this->_writeMap($key, $kspec, $output);
626           break;
627         case TType::LST:
628           $xfer += $this->_writeList($key, $kspec, $output, false);
629           break;
630         case TType::SET:
631           $xfer += $this->_writeList($key, $kspec, $output, true);
632           break;
633         }
634       }
635       if (isset($vwrite)) {
636         $xfer += $output->$vwrite($val);
637       } else {
638         switch ($vtype) {
639         case TType::STRUCT:
640           $xfer += $val->write($output);
641           break;
642         case TType::MAP:
643           $xfer += $this->_writeMap($val, $vspec, $output);
644           break;
645         case TType::LST:
646           $xfer += $this->_writeList($val, $vspec, $output, false);
647           break;
648         case TType::SET:
649           $xfer += $this->_writeList($val, $vspec, $output, true);
650           break;
651         }
652       }
653     }
654     $xfer += $output->writeMapEnd();
655     return $xfer;
656   }
657
658   private function _writeList($var, $spec, $output, $set=false) {
659     $xfer = 0;
660     $etype = $spec['etype'];
661     $ewrite = null;
662     if (isset(TBase::$tmethod[$etype])) {
663       $ewrite = 'write'.TBase::$tmethod[$etype];
664     } else {
665       $espec = $spec['elem'];
666     }
667     if ($set) {
668       $xfer += $output->writeSetBegin($etype, count($var));
669     } else {
670       $xfer += $output->writeListBegin($etype, count($var));
671     }
672     foreach ($var as $key => $val) {
673       $elem = $set ? $key : $val;
674       if (isset($ewrite)) {
675         $xfer += $output->$ewrite($elem);
676       } else {
677         switch ($etype) {
678         case TType::STRUCT:
679           $xfer += $elem->write($output);
680           break;
681         case TType::MAP:
682           $xfer += $this->_writeMap($elem, $espec, $output);
683           break;
684         case TType::LST:
685           $xfer += $this->_writeList($elem, $espec, $output, false);
686           break;
687         case TType::SET:
688           $xfer += $this->_writeList($elem, $espec, $output, true);
689           break;
690         }
691       }
692     }
693     if ($set) {
694       $xfer += $output->writeSetEnd();
695     } else {
696       $xfer += $output->writeListEnd();
697     }
698     return $xfer;
699   }
700
701   protected function _write($class, $spec, $output) {
702     $xfer = 0;
703     $xfer += $output->writeStructBegin($class);
704     foreach ($spec as $fid => $fspec) {
705       $var = $fspec['var'];
706       if ($this->$var !== null) {
707         $ftype = $fspec['type'];
708         $xfer += $output->writeFieldBegin($var, $ftype, $fid);
709         if (isset(TBase::$tmethod[$ftype])) {
710           $func = 'write'.TBase::$tmethod[$ftype];
711           $xfer += $output->$func($this->$var);
712         } else {
713           switch ($ftype) {
714           case TType::STRUCT:
715             $xfer += $this->$var->write($output);
716             break;
717           case TType::MAP:
718             $xfer += $this->_writeMap($this->$var, $fspec, $output);
719             break;
720           case TType::LST:
721             $xfer += $this->_writeList($this->$var, $fspec, $output, false);
722             break;
723           case TType::SET:
724             $xfer += $this->_writeList($this->$var, $fspec, $output, true);
725             break;
726           }
727         }
728         $xfer += $output->writeFieldEnd();
729       }
730     }
731     $xfer += $output->writeFieldStop();
732     $xfer += $output->writeStructEnd();
733     return $xfer;
734   }
735 }
736
737 class TApplicationException extends TException {
738   static $_TSPEC =
739     array(1 => array('var' => 'message',
740                      'type' => TType::STRING),
741           2 => array('var' => 'code',
742                      'type' => TType::I32));
743
744   const UNKNOWN = 0;
745   const UNKNOWN_METHOD = 1;
746   const INVALID_MESSAGE_TYPE = 2;
747   const WRONG_METHOD_NAME = 3;
748   const BAD_SEQUENCE_ID = 4;
749   const MISSING_RESULT = 5;
750
751   function __construct($message=null, $code=0) {
752     parent::__construct($message, $code);
753   }
754
755   public function read($output) {
756     return $this->_read('TApplicationException', self::$_TSPEC, $output);
757   }
758
759   public function write($output) {
760     $xfer = 0;
761     $xfer += $output->writeStructBegin('TApplicationException');
762     if ($message = $this->getMessage()) {
763       $xfer += $output->writeFieldBegin('message', TType::STRING, 1);
764       $xfer += $output->writeString($message);
765       $xfer += $output->writeFieldEnd();
766     }
767     if ($code = $this->getCode()) {
768       $xfer += $output->writeFieldBegin('type', TType::I32, 2);
769       $xfer += $output->writeI32($code);
770       $xfer += $output->writeFieldEnd();
771     }
772     $xfer += $output->writeFieldStop();
773     $xfer += $output->writeStructEnd();
774     return $xfer;
775   }
776 }
Note: See TracBrowser for help on using the browser.