Gaudi Framework, version v21r9

Home   Generated: 3 May 2010

AIDATupleSvc.cpp

Go to the documentation of this file.
00001 // $Id: AIDATupleSvc.cpp,v 1.5 2006/11/15 10:45:18 hmd Exp $
00002 // Include files
00003 #include <cstdlib>
00004 
00005 #include "GaudiKernel/DataObject.h"
00006 #include "GaudiKernel/Tokenizer.h"
00007 #include "GaudiKernel/SvcFactory.h"
00008 #include "GaudiKernel/GaudiException.h"
00009 #include "GaudiKernel/ISvcLocator.h"
00010 #include "GaudiKernel/IConversionSvc.h"
00011 #include "GaudiKernel/GenericAddress.h"
00012 #include "GaudiKernel/MsgStream.h"
00013 #include "GaudiKernel/Property.h"
00014 #include "GaudiKernel/IRegistry.h"
00015 
00016 #include "AIDATupleSvc.h"
00017 
00018 //-------------------------------------
00019 //For myTest(). REMOVE:
00020 #include "CLHEP/Random/RandGauss.h"
00021 #include "CLHEP/Random/DRand48Engine.h"
00022 #include <math.h>
00023 //-------------------------------------
00024 
00031 // Instantiation of a static factory class used by clients to create
00032 // instances of this service
00033 
00034 DECLARE_SERVICE_FACTORY(AIDATupleSvc)
00035 
00036 
00037 StatusCode AIDATupleSvc::myTest()
00038 {
00039   MsgStream log ( msgSvc(), name() );
00040   log << MSG::INFO << "Welcome to myTest!" << endmsg;
00041 
00042   // Defining the description of the tuple columns
00043   std::string description =  "float px; float py; float pz; float mass";
00044 
00045   pi_aida::Proxy_Store* store = new pi_aida::Proxy_Store("ntuple.root","POOL",0,"CAT=xmlcatalog_file:anotherCatalog.xml,SUB=POOL_ROOTTREE");
00046   pi_aida::Tuple tuple(*store, "1", "tuple1", description);
00047   //AIDATuple tuple( store, "1", "example tuple", description );
00048 
00049   DRand48Engine randomEngine;
00050   RandGauss rBeamEnergy( randomEngine, 90, 5 );
00051   RandGauss rTracksSpread( randomEngine, 0, 2 );
00052   RandGauss rMomentum( randomEngine, 0, 3 );
00053   RandGauss rMass( randomEngine, 1, 0.1 );
00054 
00055   std::cout << "Tuple created ... starting to fill " << std::endl;
00056 
00057   int i_px = tuple.findColumn( "px" );
00058   int i_py = tuple.findColumn( "py" );
00059   int i_pz = tuple.findColumn( "pz" );
00060   int i_mass = tuple.findColumn( "mass" );
00061 
00062   for ( unsigned int i = 0; i < 1000; i++ ) {
00063 
00064       tuple.fill( i_px,  rMomentum.fire() );
00065       tuple.fill( i_py,  rMomentum.fire() );
00066       tuple.fill( i_pz,  rMomentum.fire() );
00067       tuple.fill( i_mass, rMass.fire() );
00068       tuple.addRow();
00069   }
00070   std::cout << "Filled the tuple with " << tuple.rows() << " rows" << std::endl;
00071   store->close();
00072   delete store;
00073 
00074   pi_aida::Proxy_Store* store1 = new pi_aida::Proxy_Store("ntuple.root","POOL",3,"CAT=xmlcatalog_file:anotherCatalog.xml,SUB=POOL_ROOTTREE");
00075   pi_aida::Tuple tuple1 = store1->retrieveTuple("1");
00076 
00077   //AIDATuple tuple1 = store1->retrieveTuple("1");
00078   //AIDATuple tuple = static_cast<AIDATuple>(tmp);
00079 
00080   std::cout << "FOUND TUPLE WITH  " << tuple1.rows() << " ROWS" << std::endl;
00081 
00082   tuple1.start();
00083   int i = 0;
00084   while( tuple1.next() ) {
00085     i++;
00086     std::cout << "ROW " << i << std::endl;
00087     std::cout << "px: " << tuple1.getFloat(i_px) << std::endl;
00088     std::cout << "py: " << tuple1.getFloat(i_py) << std::endl;
00089     std::cout << "pz: " << tuple1.getFloat(i_pz) << std::endl;
00090     std::cout << "mass: " << tuple1.getFloat(i_mass) << std::endl;
00091   }
00092   store1->close();
00093   delete store1;
00094   return StatusCode::SUCCESS;
00095 }
00096 
00097 
00098 // ==============================================
00099 // Methods to book a tuple in the transient store
00100 // ==============================================
00101 AIDA::ITuple*   AIDATupleSvc::book(     const std::string& fullPath,
00102                                   const std::string& title,
00103                                   const std::string& columns    )
00104 {
00105   std::string dirPath, objPath, storePath, storeObj;
00106   parsePath( fullPath, dirPath, objPath, storePath, storeObj);
00107 
00108   Connections::iterator i = m_connections.find(storePath);
00109   if( i != m_connections.end() ) {
00110     pi_aida::Tuple* tmp = new pi_aida::Tuple( *(*i).second, storeObj, title, columns );
00111     AIDA::ITuple* tuple = tmp;
00112 
00113     if( registerObject(dirPath, objPath, tuple).isSuccess() )
00114       return tuple;
00115     else {
00116       delete tuple;
00117       throw GaudiException("Cannot book tuple " + title,"AIDATupleSvc", StatusCode::FAILURE);
00118     }
00119   }
00120   else
00121     throw GaudiException("Cannot find store " + storePath, "AIDATupleSvc", StatusCode::FAILURE);
00122 }
00123 
00124 
00125 AIDA::ITuple*   AIDATupleSvc::book(     const std::string& parentPath,
00126                                   const std::string& objPath,
00127                                   const std::string& title,
00128                                   const std::string& columns    )
00129 {
00130   std::string fullPath = parentPath+"/"+objPath;
00131   return book( fullPath, title, columns );
00132 }
00133 
00134 
00135 AIDA::ITuple*   AIDATupleSvc::book(     const std::string& parentPath,
00136                                   int tupleID,
00137                                   const std::string& title,
00138                                   const std::string& columns    )
00139 {
00140   char objPath[32];
00141   ::_itoa(tupleID, objPath, 10);
00142   std::string fullPath = parentPath+"/"+objPath;
00143   return book( fullPath, title, columns );
00144 }
00145 
00146 
00147 AIDA::ITuple*   AIDATupleSvc::book(     DataObject* pParent,
00148                                   const std::string& objPath,
00149                                   const std::string& title,
00150                                   const std::string& columns    )
00151 {
00152   IRegistry* tmp = pParent->registry();
00153   std::string fullPath, parentPath;
00154   parentPath = tmp->identifier();
00155   fullPath = fullPath = parentPath+"/"+objPath;
00156   return book( fullPath, title, columns );
00157 }
00158 
00159 
00160 AIDA::ITuple* AIDATupleSvc::book(       DataObject* pParent,
00161                                   int tupleID,
00162                                   const std::string& title,
00163                                   const std::string& columns    )
00164 {
00165   char txt[32];
00166   return book( pParent, ::_itoa(tupleID, txt, 10), title, columns );
00167 }
00168 
00169 
00170 // ==================================================
00171 // Methods to register a tuple in the transient store
00172 // ==================================================
00173 StatusCode AIDATupleSvc::registerObject( const std::string& dirPath,
00174                                          const std::string& objPath,
00175                                          AIDA::ITuple* tObj )
00176 {
00177   DataObject* pParent = 0;
00178   pParent = createPath( dirPath );
00179   return registerObject( pParent, objPath, tObj );
00180 
00181 }
00182 
00183 StatusCode AIDATupleSvc::registerObject( DataObject* pParent,
00184                                          const std::string& objPath,
00185                                          AIDA::ITuple* tObj )
00186 {
00187   // Set the tuple id
00188   if ( objPath[0] == SEPARATOR ) {
00189     if ( !tObj->annotation().addItem( "id", objPath.substr(1) ) )
00190       tObj->annotation().setValue( "id", objPath.substr(1) );
00191   }
00192   else {
00193     if ( !tObj->annotation().addItem( "id", objPath ) )
00194       tObj->annotation().setValue( "id", objPath );
00195   }
00196 
00197   // Register the tuple in the tuple data store
00198   StatusCode status = DataSvc::registerObject( pParent,
00199                                                objPath,
00200                                                dynamic_cast<DataObject*>(tObj) );
00201   return status;
00202 }
00203 
00204 
00205 // ====================================================
00206 // Methods to unregister a tuple in the transient store
00207 // ====================================================
00208 StatusCode AIDATupleSvc::unregisterObject( AIDA::ITuple* tObj )
00209 {
00210   StatusCode status = DataSvc::unregisterObject( dynamic_cast<DataObject*>(tObj) );
00211   return status;
00212 }
00213 
00214 
00215 StatusCode AIDATupleSvc::unregisterObject( AIDA::ITuple* tObj,
00216                                            const std::string& objPath )
00217 {
00218   StatusCode status = DataSvc::unregisterObject( dynamic_cast<DataObject*>(tObj),
00219                                                  objPath );
00220   return status;
00221 }
00222 
00223 
00224 // ===============================================
00225 // Methods to retrieve a tuple from the data store
00226 // ===============================================
00227 StatusCode AIDATupleSvc::retrieveObject( const std::string& fullPath,
00228                                          AIDA::ITuple*& tObj )
00229 {
00230   MsgStream log ( msgSvc(), name() );
00231   StatusCode status;
00232   status = findObject(fullPath, tObj);
00233 
00234   // Tuple already in the transient store
00235   if( status.isSuccess() )
00236     return status;
00237 
00238   std::string dirPath, objPath, storePath, storeObj;
00239   parsePath( fullPath, dirPath, objPath, storePath, storeObj);
00240 
00241   // Load tuple from persistency
00242   Connections::iterator i = m_connections.find(storePath);
00243   if( i != m_connections.end() ) {
00244     pi_aida::Tuple* tmp = new pi_aida::Tuple(((*i).second)->retrieveTuple(storeObj));
00245 
00246     tObj = dynamic_cast<AIDA::ITuple*>(tmp);
00247     return StatusCode::SUCCESS;
00248   }
00249   log << MSG::ERROR << "Could not retrieve tuple " << fullPath << endmsg;
00250   return StatusCode::FAILURE;
00251 }
00252 
00253 
00254 StatusCode AIDATupleSvc::retrieveObject( const std::string& parentPath,
00255                                          const std::string& objPath,
00256                                          AIDA::ITuple*& tObj )
00257 {
00258   std::string fullPath = parentPath+'/'+objPath;
00259   return retrieveObject( fullPath, tObj );
00260 }
00261 
00262 
00263 StatusCode AIDATupleSvc::retrieveObject( DataObject* pParent,
00264                                          const std::string& objPath,
00265                                          AIDA::ITuple*& tObj )
00266 {
00267   IRegistry* tmpReg = pParent->registry();
00268   std::string parentPath = tmpReg->identifier();
00269   std::string fullPath = parentPath+'/'+objPath;
00270 
00271   return retrieveObject( fullPath, tObj );
00272 }
00273 
00274 
00275 // =========================================
00276 // Methods to find a tuple in the data store
00277 // =========================================
00278 StatusCode AIDATupleSvc::findObject( const std::string& fullPath,
00279                                      AIDA::ITuple*& tObj )
00280 {
00281   DataObject*  pObject  = 0;
00282   StatusCode sc = DataSvc::findObject( fullPath, pObject );
00283   tObj = dynamic_cast<AIDA::ITuple*>(pObject);
00284   return sc;
00285 }
00286 
00287 
00288 StatusCode AIDATupleSvc::findObject( IRegistry* pRegistry,
00289                                      const std::string& path,
00290                                      AIDA::ITuple*& tObj )
00291 {
00292   DataObject*  pObject  = 0;
00293   StatusCode sc = DataSvc::findObject( pRegistry, path, pObject );
00294   tObj = dynamic_cast<AIDA::ITuple*>(pObject);
00295   return sc;
00296 }
00297 
00298 
00299 StatusCode AIDATupleSvc::findObject( const std::string& parentPath,
00300                                      const std::string& objPath,
00301                                      AIDA::ITuple*& tObj )
00302 {
00303   DataObject*  pObject  = 0;
00304   StatusCode sc = DataSvc::findObject( parentPath, objPath, pObject );
00305   tObj = dynamic_cast<AIDA::ITuple*>(pObject);
00306   return sc;
00307 }
00308 
00309 
00310 StatusCode AIDATupleSvc::findObject( DataObject* parentObj,
00311                                      const std::string& objPath,
00312                                      AIDA::ITuple*& tObj )
00313 {
00314   DataObject*  pObject  = 0;
00315   StatusCode sc = DataSvc::findObject( parentObj, objPath, pObject );
00316   tObj = dynamic_cast<AIDA::ITuple*>(pObject);
00317   return sc;
00318 }
00319 
00320 
00321 void AIDATupleSvc::setCriteria( AIDA::ITuple*& tObj,
00322                                 const std::string& criteria )
00323 {
00324   pi_aida::Tuple* t = dynamic_cast<pi_aida::Tuple*>(tObj);
00325   t->setCriteria(criteria);
00326 }
00327 
00328 
00329 // ==============
00330 // Helper methods
00331 // ==============
00332 
00333 // std::string AIDATupleSvc::storeName(AIDA::ITuple*& tObj)
00334 // {
00335 //   pi_aida::Tuple* t = dynamic_cast<pi_aida::Tuple*>(tObj);
00336 //   std::cout << "NAME: " << t->storeName() << std::endl;
00337 //   return t->storeName();
00338 // }
00339 
00340 
00341 StatusCode AIDATupleSvc::connect( const std::string& ident,
00342                                   int mode )
00343 {
00344   MsgStream log ( msgSvc(), name() );
00345   DataObject* p0 = 0;
00346   StatusCode status = DataSvc::findObject(m_rootName, p0);
00347   if (status.isSuccess() ) {
00348     Tokenizer tok(true);
00349     long loc = ident.find(" ");
00350     int open_mode = 0; // Default is AUTO mode
00351     std::string file, typ = "", tmp_typ = "", opt = "";
00352     std::string logname = ident.substr(0,loc);
00353     tok.analyse(ident.substr(loc+1,ident.length()), " ", "", "", "=", "'", "'");
00354     for ( Tokenizer::Items::iterator i = tok.items().begin(); i != tok.items().end(); i++) {
00355       const std::string& tag = (*i).tag();
00356       switch( ::toupper(tag[0]) ) {
00357       case 'F':   // FILE='<file name>'
00358       case 'D':   // DATAFILE='<file name>'
00359         file = (*i).value();
00360         break;
00361       case 'O':   // OPT='<AUTO, OVERWRITE, CREATE, READONLY, UPDATE>'
00362         switch( ::toupper((*i).value()[0]) ) {
00363         case 'A': // AUTO
00364           if( mode == 0 )
00365             open_mode = 3; // READONLY
00366           else
00367             open_mode = 1; // CREATE
00368           break;
00369         case 'O': // OVERWRITE
00370           if( mode == 0 )
00371             throw GaudiException("Input file " + file + " with improper option (OVERWRITE)", "AIDATupleSvc", StatusCode::FAILURE);
00372           else
00373             open_mode = 1;
00374           break;
00375         case 'C': // CREATE
00376           if( mode == 0 )
00377             throw GaudiException("Input file " + file + " with improper option (CREATE)", "AIDATupleSvc", StatusCode::FAILURE);
00378           else
00379             open_mode = 2;
00380           break;
00381         case 'R': // READONLY
00382           if( mode == 1 )
00383             throw GaudiException("Output file " + file + " with improper option (READONLY)", "AIDATupleSvc", StatusCode::FAILURE);
00384           else
00385             open_mode = 3;
00386           break;
00387         case 'U': // UPDATE
00388           if( mode == 0 )
00389             throw GaudiException("Input file " + file + " with improper option (UPDATE)", "AIDATupleSvc", StatusCode::FAILURE);
00390           else
00391             open_mode = 4;
00392           break;
00393         }
00394         break;
00395       case 'T':   // TYP='<HBOOK,ROOT,XML,POOL>'
00396         typ = (*i).value();
00397         break;
00398       default:
00399         opt += (*i).tag() + "=" + (*i).value() + ",";
00400         break;
00401       }
00402     }
00403 
00404     // Check if persistency type has been specified at job options
00405     if (typ == "") {
00406       log << MSG::WARNING << "File type not specified at job options."
00407           << " Setting 'HistogramPersistency' type as default"
00408           << endmsg;
00409       // Get the value of the Stat persistancy mechanism from the AppMgr
00410       IProperty*   appPropMgr = 0;
00411       status = serviceLocator()->queryInterface(IProperty::interfaceID(),(void **)&appPropMgr );
00412       if( !status.isSuccess() ) {
00413         // Report an error and return the FAILURE status code
00414         log << MSG::ERROR << "Could not get PropMgr" << endmsg;
00415         return status;
00416       }
00417       StringProperty sp("HistogramPersistency","");
00418       status = appPropMgr->getProperty( &sp );
00419       if ( !status.isSuccess() ) {
00420         log << MSG::ERROR << "Could not get NTuple Persistency format"
00421             << " from ApplicationMgr properties" << endmsg;
00422         return status;
00423       }
00424       tmp_typ = sp.value();
00425       if (tmp_typ == "ROOT" || tmp_typ == "XML" || tmp_typ == "HBOOK" || tmp_typ == "POOL")
00426         typ = tmp_typ;
00427       else {
00428         log << MSG::ERROR << "Unknown persistency format at 'HistogramPersistency' option"
00429             << endmsg;
00430         return StatusCode::FAILURE;
00431       }
00432     }
00433 
00434     // Create a new pi_aida::Proxy_Store
00435     pi_aida::Proxy_Store* storePtr = new pi_aida::Proxy_Store( file, typ, open_mode, opt );
00436     m_connections.insert(Connections::value_type(m_rootName+'/'+logname, storePtr));
00437 
00438     return StatusCode::SUCCESS;
00439   }
00440   log << MSG::ERROR << "Cannot add " << ident << " invalid filename!" << endmsg;
00441   return StatusCode::FAILURE;
00442 
00443 }
00444 
00445 
00446 StatusCode AIDATupleSvc::closeAIDAStores()
00447 {
00448    for( Connections::iterator k = m_connections.begin(); k != m_connections.end(); k++ ) {
00449      if ( (*k).second->isOpen()) {
00450        (*k).second->close();
00451        (*k).second = 0;
00452      }
00453    }
00454 
00455    m_connections.erase(m_connections.begin(), m_connections.end());
00456   return StatusCode::SUCCESS;
00457 }
00458 
00459 
00460 void AIDATupleSvc::parsePath( const std::string& fullPath,
00461                               std::string& dirPath,
00462                               std::string& objPath,
00463                               std::string& storePath,
00464                               std::string& storeObj )
00465 {
00466   std::string tmpPath = fullPath;
00467   if ( tmpPath[0] != SEPARATOR ) {
00468     // Insert the top level name of the store (/NTUPLES)
00469     tmpPath.insert(tmpPath.begin(), SEPARATOR);
00470     tmpPath.insert(tmpPath.begin(), m_rootName.begin(), m_rootName.end());
00471   }
00472   // Remove trailing "/" from tmpPath if it exists
00473   if (tmpPath.rfind(SEPARATOR) == tmpPath.length()-1) {
00474     tmpPath.erase(tmpPath.rfind(SEPARATOR),1);
00475   }
00476   int sep   = tmpPath.rfind(SEPARATOR);
00477   dirPath   = tmpPath.substr(0, sep);
00478   objPath   = tmpPath.substr(sep, tmpPath.length()-sep );
00479   sep       = tmpPath.find(SEPARATOR, m_rootName.length()+1);
00480   storePath = tmpPath.substr(0, sep);
00481   storeObj  = tmpPath.substr(sep+1, tmpPath.length()-sep );
00482 }
00483 
00484 
00485 DataObject* AIDATupleSvc::createPath( const std::string& newPath )
00486 {
00487   std::string tmpPath = newPath;
00488   if ( tmpPath[0] != SEPARATOR )    {
00489     tmpPath.insert(tmpPath.begin(), SEPARATOR);
00490     tmpPath.insert(tmpPath.begin(), m_rootName.begin(), m_rootName.end());
00491   }
00492   // Remove trailing "/" from newPath if it exists
00493   if (tmpPath.rfind(SEPARATOR) == tmpPath.length()-1) {
00494     tmpPath.erase(tmpPath.rfind(SEPARATOR),1);
00495   }
00496 
00497   DataObject* pObject = 0;
00498   StatusCode sc = DataSvc::findObject( tmpPath, pObject );
00499   if( sc.isSuccess() ) {
00500     return pObject;
00501   }
00502 
00503   int sep = tmpPath.rfind(SEPARATOR);
00504   std::string rest( tmpPath, sep+1, tmpPath.length()-sep );
00505   std::string subPath( tmpPath, 0, sep );
00506   if( 0 != sep ) {
00507     createPath( subPath );
00508   }
00509   else {
00510     MsgStream log( msgSvc(), name() );
00511     log << MSG::ERROR << "Unable to create the tuple path" << endmsg;
00512     return 0;
00513   }
00514 
00515   pObject = createDirectory( subPath, rest );
00516   return pObject;
00517 }
00518 
00519 
00520 DataObject* AIDATupleSvc::createDirectory( const std::string& parentDir,
00521                                            const std::string& subDir )
00522 {
00523 
00524   StatusCode   status    = StatusCode::FAILURE;
00525   DataObject*  directory = new DataObject();
00526 
00527   if ( 0 != directory )  {
00528     DataObject* pnode;
00529     status = DataSvc::findObject( parentDir, pnode );
00530     if( status.isSuccess() ) {
00531       status = DataSvc::registerObject( pnode, subDir, directory );
00532       if ( !status.isSuccess() )   {
00533         MsgStream log( msgSvc(), name() );
00534         log << MSG::ERROR << "Unable to create the directory: "
00535                           << parentDir << "/" << subDir << endmsg;
00536         delete directory;
00537         return 0;
00538       }
00539     }
00540     else {
00541       MsgStream log( msgSvc(), name() );
00542       log << MSG::ERROR << "Unable to create the directory: "
00543                         << parentDir << "/" << subDir << endmsg;
00544       delete directory;
00545       return 0;
00546     }
00547   }
00548   return directory;
00549 }
00550 
00551 // ========================================
00552 // Constructor, destructor and main methods
00553 // ========================================
00554 AIDATupleSvc::AIDATupleSvc( const std::string& name, ISvcLocator* svc )
00555   : DataSvc( name, svc )
00556 {
00557   // Properties can be declared here
00558   m_rootName = "/NTUPLES";
00559   m_rootCLID = CLID_DataObject;
00560   declareProperty("Input",  m_input);
00561   declareProperty("Output", m_output);
00562 }
00563 
00564 
00565 AIDATupleSvc::~AIDATupleSvc()
00566 {
00567   clearStore().ignore();
00568 }
00569 
00570 
00571 StatusCode AIDATupleSvc::initialize()
00572 {
00573   MsgStream log( msgSvc(), name() );
00574   StatusCode status = DataSvc::initialize();
00575 
00576   if( status.isSuccess() ){
00577     DataObject* rootObj = new DataObject();
00578     status = setRoot( m_rootName, rootObj );
00579     if( !status.isSuccess() ) {
00580       log << MSG::ERROR << "Unable to set NTuple data store root." << endmsg;
00581       delete rootObj;
00582       return status;
00583     }
00584 
00585     //Connect inputs (MODE = 0)
00586     for( DBaseEntries::iterator i = m_input.begin(); i != m_input.end(); i++ ) {
00587       status = connect(*i, 0);
00588       if( !status.isSuccess() )
00589         return status;
00590     }
00591     //Connect outputs (MODE = 1)
00592     for( DBaseEntries::iterator j = m_output.begin(); j != m_output.end(); j++ ) {
00593       status = connect(*j, 1);
00594       if( !status.isSuccess() )
00595         return status;
00596     }
00597   }
00598   return status;
00599 }
00600 
00601 
00602 StatusCode AIDATupleSvc::reinitialize()
00603 {
00604   return StatusCode::SUCCESS;
00605 }
00606 
00607 
00608 StatusCode AIDATupleSvc::finalize()
00609 {
00610   MsgStream log( msgSvc(), name() );
00611   StatusCode status = DataSvc::finalize();
00612   if( status.isSuccess() ) {
00613     status = closeAIDAStores();
00614     if( status.isSuccess() )
00615       return status;
00616   }
00617   log << MSG::ERROR << "Error finalizing AIDATupleSvc." << endmsg;
00618   return status;
00619 }

Generated at Mon May 3 12:14:40 2010 for Gaudi Framework, version v21r9 by Doxygen version 1.5.6 written by Dimitri van Heesch, © 1997-2004