/[escript]/trunk/escript/src/DataArrayView.h
ViewVC logotype

Contents of /trunk/escript/src/DataArrayView.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 100 - (show annotations)
Wed Dec 15 03:48:48 2004 UTC (14 years, 11 months ago) by jgs
Original Path: trunk/esys2/escript/src/Data/DataArrayView.h
File MIME type: text/plain
File size: 23726 byte(s)
*** empty log message ***

1 /*
2 ******************************************************************************
3 * *
4 * COPYRIGHT ACcESS 2004 - All Rights Reserved *
5 * *
6 * This software is the property of ACcESS. No part of this code *
7 * may be copied in any form or by any means without the expressed written *
8 * consent of ACcESS. Copying, use or modification of this software *
9 * by any unauthorised person is illegal unless that person has a software *
10 * license agreement with ACcESS. *
11 * *
12 ******************************************************************************
13 */
14
15 #if !defined escript_DataArrayView_20040323_H
16 #define escript_DataArrayView_20040323_H
17
18 #include "esysUtils/EsysAssert.h"
19
20 #include <vector>
21 #include <boost/python/numeric.hpp>
22 #include <boost/python/object.hpp>
23 #include <boost/shared_ptr.hpp>
24
25 namespace escript {
26
27 /**
28 \brief
29 DataArrayView provides a view of data allocated externally.
30
31 Description:
32 DataArrayView provides a view of data allocated externally. The
33 external data should provide sufficient values so that the dimensions
34 specified for the view will be satisfied. The viewer can update
35 values within the external data but cannot resize the external data.
36 */
37
38 class DataArrayView {
39
40 friend bool operator==(const DataArrayView& left, const DataArrayView& right);
41 friend bool operator!=(const DataArrayView& left, const DataArrayView& right);
42
43 public:
44
45 typedef std::vector<double> ValueType;
46 typedef std::vector<int> ShapeType;
47 typedef std::vector<std::pair<int, int> > RegionType;
48
49 /**
50 \brief
51 Default constructor for DataArrayView.
52
53 Description:
54 Default constructor for DataArrayView. Creates an
55 empty view with no associated data.
56 */
57 DataArrayView();
58
59 /**
60 \brief
61 Constructor for DataArrayView.
62
63 Description:
64 Constructor for DataArrayView.
65
66 \param data - Input - Container holding data to be viewed. This must
67 be large enough for the specified shape.
68 \param viewShape - Input - The shape of the view.
69 \param offset - Input - Offset into the data at which view should start.
70 */
71 DataArrayView(ValueType& data,
72 const ShapeType& viewShape,
73 int offset=0);
74
75 /**
76 \brief
77 Copy constructor for DataArrayView.
78
79 Description:
80 Copy constructor for DataArrayView.
81
82 \param other - Input - DataArrayView to copy.
83
84 NOTE: The copy references the same data container.
85 */
86 DataArrayView(const DataArrayView& other);
87
88 /**
89 \brief
90 Check if view is empty.
91 */
92 bool
93 isEmpty() const;
94
95 /**
96 \brief
97 Copy from a numarray into the data managed by DataArrayView.
98 */
99 void
100 copy(const boost::python::numeric::array& value);
101
102 /**
103 \brief
104 Copy from another DataArrayViewinto the data managed by this
105 DataArrayView at the default offset.
106 */
107 void
108 copy(const DataArrayView& other);
109
110 /**
111 \brief
112 Copy from another DataArrayView into this view at the
113 given offset.
114 */
115 void
116 copy(ValueType::size_type offset,
117 const DataArrayView& other);
118
119 /**
120 \brief
121 Copy from another DataArrayView into this view at the
122 given offsets.
123
124 \param thisOffset - Input - Offset into this view's data array to copy to.
125 \param other - Input - View for the data to copy.
126 \param otherOffset - Input - Offset into the other view to copy data from.
127 */
128 void
129 copy(ValueType::size_type thisOffset,
130 const DataArrayView& other,
131 ValueType::size_type otherOffset);
132
133 /**
134 \brief
135 Copy the given single value over the entire view.
136
137 \param offset - Input - Offset into this view's data array to copy to.
138 \param value - Input - Value to copy.
139 */
140 void
141 copy(ValueType::size_type offset,
142 ValueType::value_type value);
143
144 /**
145 \brief
146 Return this view's offset to the start of data.
147 */
148 ValueType::size_type
149 getOffset() const;
150
151 /**
152 \brief
153 Return the rank of the data.
154 */
155 int
156 getRank() const;
157
158 /**
159 \brief
160 Return the shape of the data.
161 */
162 const ShapeType&
163 getShape() const;
164
165 /**
166 \brief
167 Calculate the number of values for the given shape.
168 */
169 static int
170 noValues(const ShapeType& shape);
171
172 /**
173 \brief
174 Return the number of values for the current view.
175 */
176 int
177 noValues() const;
178
179 /**
180 \brief
181 Return true if the shapes are the same.
182 */
183 bool
184 checkShape(const DataArrayView::ShapeType& other) const;
185
186 /**
187 \brief
188 Return a reference to the underlying data.
189 */
190 ValueType&
191 getData() const;
192
193 /**
194 \brief
195 Return the value with the given index for the view. This takes into account
196 the offset. Effectively this returns each value of the view in sequential order.
197 */
198 ValueType::reference
199 getData(ValueType::size_type i) const;
200
201 /**
202 \brief
203 Create a shape error message. Normally used when there is a shape
204 mismatch.
205
206 \param messagePrefix - Input - First part of the error message.
207 \param other - Input - The other shape.
208 */
209 std::string
210 createShapeErrorMessage(const std::string& messagePrefix,
211 const DataArrayView::ShapeType& other) const;
212
213 /**
214 \brief
215 Set the offset into the array.
216 */
217 void
218 setOffset(ValueType::size_type offset);
219
220 /**
221 \brief
222 Return the shape of a data view following the slice specified.
223
224 \param region - Input - Slice region.
225 */
226 static DataArrayView::ShapeType
227 getResultSliceShape(const RegionType& region);
228
229 /**
230 \brief
231 Copy a slice from the given view. This view must be the right shape for the slice.
232
233 \param other - Input - Data to copy from.
234 \param region - Input - Slice region.
235 */
236 void
237 copySlice(const DataArrayView& other,
238 const RegionType& region);
239
240 /**
241 \brief
242 Copy a slice from the given view. This view must be the right shape for the slice.
243
244 \param thisOffset - Input - use this view offset instead of the default.
245 \param other - Input - Data to copy from.
246 \param otherOffset - Input - use this slice offset instead of the default.
247 \param region - Input - Slice region.
248 */
249 void
250 copySlice(ValueType::size_type thisOffset,
251 const DataArrayView& other,
252 ValueType::size_type otherOffset,
253 const RegionType& region);
254
255 /**
256 \brief
257 Copy into a slice from the given view. This view must have the same rank as the slice region.
258
259 \param other - Input - Data to copy from.
260 \param region - Input - Slice region.
261 */
262 void
263 copySliceFrom(const DataArrayView& other,
264 const RegionType& region);
265
266 /**
267 \brief
268 Copy into a slice from the given value. This view must have the same rank as the slice region.
269
270 \param thisOffset - Input - use this view offset instead of the default.
271 \param other - Input - Data to copy from.
272 \param otherOffset - Input - use this slice offset instead of the default.
273 \param region - Input - Slice region.
274 */
275 void
276 copySliceFrom(ValueType::size_type thisOffset,
277 const DataArrayView& other,
278 ValueType::size_type otherOffset,
279 const RegionType& region);
280
281 /**
282 \brief
283 Determine the shape of the result array for a matrix multiplication.
284 */
285 static ShapeType
286 determineResultShape(const DataArrayView& left,
287 const DataArrayView& right);
288
289 /**
290 \brief
291 Returns the range for the slices defined by the key which is a Python
292 slice object or a tuple of Python slice object.
293 */
294 DataArrayView::RegionType
295 getSliceRegion(const boost::python::object& key) const;
296 /*
297 DataArrayView::RegionType
298 getSliceRegion2(const boost::python::object& key) const;
299 */
300
301 // *******************************************************************
302 // NOTE: The following relIndex functions are a hack. The indexing
303 // mechanism should be split out from DataArrayView to get the flexability
304 // needed.
305
306 /**
307 \brief
308 Return the 1 dimensional index of the element at position i,j,k,m
309 ignoring the offset.
310 */
311 ValueType::size_type
312 relIndex(ValueType::size_type i,
313 ValueType::size_type j,
314 ValueType::size_type k,
315 ValueType::size_type m) const;
316
317 /**
318 \brief
319 Return the 1 dimensional index of the element at position i,j,k
320 ignoring the offset.
321 */
322 ValueType::size_type
323 relIndex(ValueType::size_type i,
324 ValueType::size_type j,
325 ValueType::size_type k) const;
326
327 /**
328 \brief
329 Return the 1 dimensional index of the element at position i,j
330 ignoring the offset.
331 */
332 ValueType::size_type
333 relIndex(ValueType::size_type i,
334 ValueType::size_type j) const;
335
336 // ********************************************************************
337
338 /**
339 \brief
340 Return the 1 dimensional index of the element at position i,j,k,m.
341 */
342 ValueType::size_type
343 index(ValueType::size_type i,
344 ValueType::size_type j,
345 ValueType::size_type k,
346 ValueType::size_type m) const;
347
348 /**
349 \brief
350 Return the 1 dimensional index of the element at position i,j,k.
351 */
352 ValueType::size_type
353 index(ValueType::size_type i,
354 ValueType::size_type j,
355 ValueType::size_type k) const;
356
357 /**
358 \brief
359 Return the 1 dimensional index of the element at position i,j.
360 */
361 ValueType::size_type
362 index(ValueType::size_type i,
363 ValueType::size_type j) const;
364
365 /**
366 \brief
367 Return the 1 dimensional index of the element at position i.
368 */
369 ValueType::size_type
370 index(ValueType::size_type i) const;
371
372 /**
373 \brief
374 Return a reference to the element at position i.
375 */
376 ValueType::reference
377 operator()(ValueType::size_type i);
378
379 ValueType::const_reference
380 operator()(ValueType::size_type i) const;
381
382 /**
383 \brief
384 Return a reference to the element at position i,j.
385 */
386 ValueType::reference
387 operator()(ValueType::size_type i,
388 ValueType::size_type j);
389
390 ValueType::const_reference
391 operator()(ValueType::size_type i,
392 ValueType::size_type j) const;
393
394 /**
395 \brief
396 Return a reference to the element at position i,j,k.
397 */
398 ValueType::reference
399 operator()(ValueType::size_type i,
400 ValueType::size_type j,
401 ValueType::size_type k);
402
403 ValueType::const_reference
404 operator()(ValueType::size_type i,
405 ValueType::size_type j,
406 ValueType::size_type k) const;
407
408 /**
409 \brief
410 Return a reference to the element at position i,j,k,m.
411 */
412 ValueType::reference
413 operator()(ValueType::size_type i,
414 ValueType::size_type j,
415 ValueType::size_type k,
416 ValueType::size_type m);
417
418 ValueType::const_reference
419 operator()(ValueType::size_type i,
420 ValueType::size_type j,
421 ValueType::size_type k,
422 ValueType::size_type m) const;
423
424 /**
425 \brief
426 Return a reference for the only element, assuming rank 0.
427 */
428 ValueType::reference
429 operator()();
430
431 ValueType::const_reference
432 operator()() const;
433
434 /**
435 \brief
436 Perform the unary operation using the given offset
437 instead of the offset defined within the view.
438 */
439 template <class UnaryFunction>
440 void
441 unaryOp(ValueType::size_type leftOffset,
442 UnaryFunction operation);
443
444 /**
445 \brief
446 Perform the unary operation using the view's offset.
447 */
448 template <class UnaryFunction>
449 void
450 unaryOp(UnaryFunction operation);
451
452 /**
453 \brief
454 Perform the given operation and return a result.
455 */
456 template <class UnaryFunction>
457 double
458 algorithm(ValueType::size_type leftOffset,
459 UnaryFunction operation) const;
460
461 /**
462 \brief
463 Perform the given operation and return a result. Use the default offset.
464 */
465 template <class UnaryFunction>
466 double
467 algorithm(UnaryFunction operation) const;
468
469 /**
470 \brief
471 Perform the binary operation. Version which applies a double value
472 to all values within the view. The given offset is used instead of
473 the default offset specified within the view.
474 */
475 template <class BinaryFunction>
476 void
477 binaryOp(ValueType::size_type leftOffset,
478 double right,
479 BinaryFunction operation);
480
481 /**
482 \brief
483 Perform the binary operation. Version which applies a double value
484 to all values within the view.
485 */
486 template <class BinaryFunction>
487 void
488 binaryOp(double right,
489 BinaryFunction operation);
490
491 /**
492 \brief
493 Perform the binary operation. The given offsets override the default
494 offsets specified within the views.
495 */
496 template <class BinaryFunction>
497 void
498 binaryOp(ValueType::size_type leftOffset,
499 const DataArrayView& right,
500 ValueType::size_type rightOffset,
501 BinaryFunction operation);
502
503 /**
504 \brief
505 Perform the binary operation.
506 */
507 template <class BinaryFunction>
508 void
509 binaryOp(const DataArrayView& right,
510 BinaryFunction operation);
511
512 /**
513 \brief
514 Return the data as a string. Not recommended for very large objects.
515 \param suffix - Input - Suffix appended to index display.
516 */
517 std::string
518 toString(const std::string& suffix=std::string("")) const;
519
520 /**
521 \brief
522 Return the given shape as a string.
523
524 \param shape - Input.
525 */
526 static std::string
527 shapeToString(const DataArrayView::ShapeType& shape);
528
529 /**
530 \brief
531 Perform matrix multiply.
532
533 Description:
534 Perform matrix multiply.
535
536 \param left - Input - The left hand side.
537 \param right - Input - The right hand side.
538 \param result - Output - The result of the operation.
539 */
540 static void
541 matMult(const DataArrayView& left,
542 const DataArrayView& right,
543 DataArrayView& result);
544
545 protected:
546
547 private:
548
549 //
550 static const int m_maxRank=4;
551
552 //
553 // The data values for the view. NOTE: This points to data external to the view.
554 ValueType* m_data;
555
556 //
557 // The offset into the data array used by different views.
558 ValueType::size_type m_offset;
559
560 //
561 // The shape of the data.
562 ShapeType m_shape;
563
564 //
565 // The number of values needed for the array.
566 int m_noValues;
567
568 };
569
570 inline
571 DataArrayView::ValueType::size_type
572 DataArrayView::getOffset() const
573 {
574 return m_offset;
575 }
576
577 inline
578 DataArrayView::ValueType&
579 DataArrayView::getData() const
580 {
581 EsysAssert(!isEmpty(),"Error - View is empty");
582 return *m_data;
583 }
584
585 inline
586 DataArrayView::ValueType::reference
587 DataArrayView::getData(ValueType::size_type i) const
588 {
589 //
590 // don't do any checking to allow one past the end of the vector providing
591 // the equivalent of end()
592 return (*m_data)[i+m_offset];
593 }
594
595 template <class UnaryFunction>
596 inline
597 void
598 DataArrayView::unaryOp(ValueType::size_type leftOffset,
599 UnaryFunction operation)
600 {
601 for (ValueType::size_type i=0;i<(noValues(m_shape));++i) {
602 (*m_data)[i+leftOffset]=operation((*m_data)[i+leftOffset]);
603 }
604 }
605
606 template <class UnaryFunction>
607 inline
608 void
609 DataArrayView::unaryOp(UnaryFunction operation)
610 {
611 unaryOp(m_offset,operation);
612 }
613
614 template <class UnaryFunction>
615 inline
616 double
617 DataArrayView::algorithm(ValueType::size_type leftOffset,
618 UnaryFunction operation) const
619 {
620 for (ValueType::size_type i=0;i<(noValues(m_shape));++i) {
621 operation((*m_data)[i+leftOffset]);
622 }
623 return operation.getResult();
624 }
625
626 template <class UnaryFunction>
627 inline
628 double
629 DataArrayView::algorithm(UnaryFunction operation) const
630 {
631 return algorithm(m_offset,operation);
632 }
633
634 template <class BinaryFunction>
635 inline
636 void
637 DataArrayView::binaryOp(ValueType::size_type leftOffset,
638 const DataArrayView& right,
639 ValueType::size_type rightOffset,
640 BinaryFunction operation)
641 {
642 EsysAssert(getShape()==right.getShape(),
643 "Error - Right hand shape: " << shapeToString(right.getShape()) << " doesn't match the left: " << shapeToString(getShape()));
644 for (ValueType::size_type i=0;i<noValues();++i) {
645 (*m_data)[i+leftOffset]=operation((*m_data)[i+leftOffset],(*right.m_data)[i+rightOffset]);
646 }
647 }
648
649 template <class BinaryFunction>
650 inline
651 void
652 DataArrayView::binaryOp(const DataArrayView& right,
653 BinaryFunction operation)
654 {
655 //
656 // version using the offsets specified within the view
657 binaryOp(m_offset,right,right.getOffset(),operation);
658 }
659
660 template <class BinaryFunction>
661 inline
662 void
663 DataArrayView::binaryOp(ValueType::size_type leftOffset,
664 double right,
665 BinaryFunction operation)
666 {
667 //
668 // If a scalar is to be applied to the entire array force the caller to
669 // explicitly specify a single value
670 for (ValueType::size_type i=0;i<(noValues(m_shape));++i) {
671 (*m_data)[i+leftOffset]=operation((*m_data)[i+leftOffset],right);
672 }
673 }
674
675 template <class BinaryFunction>
676 inline
677 void
678 DataArrayView::binaryOp(double right,
679 BinaryFunction operation)
680 {
681 //
682 // use the default offset
683 binaryOp(m_offset,right,operation);
684 }
685
686 inline
687 DataArrayView::ValueType::size_type
688 DataArrayView::index(ValueType::size_type i) const
689 {
690 //EsysAssert((i >= 0) && (i < noValues(m_shape)), "Invalid index.");
691 EsysAssert((i < noValues(m_shape)), "Invalid index.");
692 return (i+m_offset);
693 }
694
695 inline
696 DataArrayView::ValueType::size_type
697 DataArrayView::relIndex(ValueType::size_type i,
698 ValueType::size_type j) const
699 {
700 ValueType::size_type temp=i+j*m_shape[0];
701 //EsysAssert((temp >= 0 || temp < noValues(m_shape)), "Invalid index.");
702 EsysAssert((temp < noValues(m_shape)), "Invalid index.");
703 //
704 // no offset
705 return temp;
706 }
707
708 inline
709 DataArrayView::ValueType::size_type
710 DataArrayView::index(ValueType::size_type i,
711 ValueType::size_type j) const
712 {
713 ValueType::size_type temp=i+j*m_shape[0];
714 //EsysAssert((temp >= 0 || temp < noValues(m_shape)), "Invalid index.");
715 EsysAssert((temp < noValues(m_shape)), "Invalid index.");
716 return (temp+m_offset);
717 }
718
719 inline
720 DataArrayView::ValueType::size_type
721 DataArrayView::relIndex(ValueType::size_type i,
722 ValueType::size_type j,
723 ValueType::size_type k) const
724 {
725 ValueType::size_type temp=i+j*m_shape[0]+k*m_shape[1]*m_shape[0];
726 //EsysAssert((temp >= 0 || temp < noValues(m_shape)), "Invalid index.");
727 EsysAssert((temp < noValues(m_shape)), "Invalid index.");
728 //
729 // no offset
730 return temp;
731 }
732
733 inline
734 DataArrayView::ValueType::size_type
735 DataArrayView::index(ValueType::size_type i,
736 ValueType::size_type j,
737 ValueType::size_type k) const
738 {
739 ValueType::size_type temp=i+j*m_shape[0]+k*m_shape[1]*m_shape[0];
740 //EsysAssert((temp >= 0 || temp < noValues(m_shape)), "Invalid index.");
741 EsysAssert((temp < noValues(m_shape)), "Invalid index.");
742 return (temp+m_offset);
743 }
744
745 inline
746 DataArrayView::ValueType::size_type
747 DataArrayView::relIndex(ValueType::size_type i,
748 ValueType::size_type j,
749 ValueType::size_type k,
750 ValueType::size_type m) const
751 {
752 ValueType::size_type temp=i+j*m_shape[0]+k*m_shape[1]*m_shape[0]+m*m_shape[2]*m_shape[1]*m_shape[0];
753 //EsysAssert((temp >= 0 || temp < noValues(m_shape)), "Invalid index.");
754 EsysAssert((temp < noValues(m_shape)), "Invalid index.");
755 //
756 // no offset
757 return temp;
758 }
759
760 inline
761 DataArrayView::ValueType::size_type
762 DataArrayView::index(ValueType::size_type i,
763 ValueType::size_type j,
764 ValueType::size_type k,
765 ValueType::size_type m) const
766 {
767 ValueType::size_type temp=i+j*m_shape[0]+k*m_shape[1]*m_shape[0]+m*m_shape[2]*m_shape[1]*m_shape[0];
768 //EsysAssert((temp >= 0 || temp < noValues(m_shape)), "Invalid index.");
769 EsysAssert((temp < noValues(m_shape)), "Invalid index.");
770 return (temp+m_offset);
771 }
772
773 inline
774 DataArrayView::ValueType::reference
775 DataArrayView::operator()(ValueType::size_type i,
776 ValueType::size_type j,
777 ValueType::size_type k,
778 ValueType::size_type m)
779 {
780 EsysAssert((getRank()==4),
781 "Incorrect number of indices for the rank of this object.");
782 return (*m_data)[index(i,j,k,m)];
783 }
784
785 inline
786 DataArrayView::ValueType::const_reference
787 DataArrayView::operator()(ValueType::size_type i,
788 ValueType::size_type j,
789 ValueType::size_type k,
790 ValueType::size_type m) const
791 {
792 EsysAssert((getRank()==4),
793 "Incorrect number of indices for the rank of this object.");
794 return (*m_data)[index(i,j,k,m)];
795 }
796
797 inline
798 DataArrayView::ValueType::reference
799 DataArrayView::operator()(ValueType::size_type i,
800 ValueType::size_type j,
801 ValueType::size_type k)
802 {
803 EsysAssert((getRank()==3),
804 "Incorrect number of indices for the rank of this object.");
805 return (*m_data)[index(i,j,k)];
806 }
807
808 inline
809 DataArrayView::ValueType::const_reference
810 DataArrayView::operator()(ValueType::size_type i,
811 ValueType::size_type j,
812 ValueType::size_type k) const
813 {
814 EsysAssert((getRank()==3),
815 "Incorrect number of indices for the rank of this object.");
816 return (*m_data)[index(i,j,k)];
817 }
818
819 inline
820 DataArrayView::ValueType::reference
821 DataArrayView::operator()(ValueType::size_type i,
822 ValueType::size_type j)
823 {
824 EsysAssert((getRank()==2),
825 "Incorrect number of indices for the rank of this object.");
826 return (*m_data)[index(i,j)];
827 }
828
829 inline
830 DataArrayView::ValueType::const_reference
831 DataArrayView::operator()(ValueType::size_type i,
832 ValueType::size_type j) const
833 {
834 EsysAssert((getRank()==2),
835 "Incorrect number of indices for the rank of this object.");
836 return (*m_data)[index(i,j)];
837 }
838
839 inline
840 DataArrayView::ValueType::reference
841 DataArrayView::operator()(ValueType::size_type i)
842 {
843 EsysAssert((getRank()==1),
844 "Incorrect number of indices for the rank of this object.");
845 return (*m_data)[index(i)];
846 }
847
848 inline
849 DataArrayView::ValueType::const_reference
850 DataArrayView::operator()(ValueType::size_type i) const
851 {
852 EsysAssert((getRank()==1),
853 "Incorrect number of indices for the rank of this object.");
854 return (*m_data)[index(i)];
855 }
856
857 inline
858 DataArrayView::ValueType::reference
859 DataArrayView::operator()()
860 {
861 EsysAssert((getRank()==0),
862 "Incorrect number of indices for the rank of this object.");
863 return (*m_data)[m_offset];
864 }
865
866 inline
867 DataArrayView::ValueType::const_reference
868 DataArrayView::operator()() const
869 {
870 EsysAssert((getRank()==0),
871 "Incorrect number of indices for the rank of this object.");
872 return (*m_data)[m_offset];
873 }
874
875 bool operator==(const DataArrayView& left, const DataArrayView& right);
876 bool operator!=(const DataArrayView& left, const DataArrayView& right);
877
878 /* returns the python slice object key as a pair of ints where the first
879 member is the start and the second member is the end. the presence of a possible
880 step attribute with value different from one will truow an exception */
881 std::pair<int,int>
882 getSliceRange(const int s,
883 const boost::python::object& key);
884
885 } // end of namespace
886
887 #endif

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

  ViewVC Help
Powered by ViewVC 1.1.26