1f4b5cf1d492cc579488c6076128a06ba49fa12a
[u/mrichter/AliRoot.git] / ANALYSIS / AliFileMerger.cxx
1 /**************************************************************************
2  * Copyright(c) 1998-1999, ALICE Experiment at CERN, All rights reserved. *
3  *                                                                        *
4  * Author: The ALICE Off-line Project.                                    *
5  * Contributors are mentioned in the code where appropriate.              *
6  *                                                                        *
7  * Permission to use, copy, modify and distribute this software and its   *
8  * documentation strictly for non-commercial purposes is hereby granted   *
9  * without fee, provided that the above copyright notice appears in all   *
10  * copies and that both the copyright notice and this permission notice   *
11  * appear in the supporting documentation. The authors make no claims     *
12  * about the suitability of this software for any purpose. It is          *
13  * provided "as is" without express or implied warranty.                  *
14  **************************************************************************/
15
16 /* $Id$ */
17
18 //////////////////////////////////////////////////////////////////////////
19 //  marian.ivanov@cern.ch
20 //  Utilities for file merging.
21 //  Additional functionality on top of the standard TFileMerger:
22 //
23 //  1. Possibility to Set the reject/accept list.
24 //     1.a)  Only entries selected in accept list are merged. By default all entries are selected
25 //           use AddAccept 0 to specify your desired entry
26 //     1.b)  Entries selected in reject list are not merged. By default the reject list is empty.
27 //
28 //  2. syswatch.log is created diring mergin procedure. 
29 //     Memeory consumption - for reading and for merging can be monitored
30
31 //  RS: Changed merger to respect the structure of files being merged (directories, collections...)
32 //      Additional option: SetNoTrees (default false) to not merge any tree
33 //      The code mostly taken from root's hadd.cxx
34 /*
35   Usage:
36   // Libraries for all classes to be merged should be loaded before using the class
37   gSystem->Load("libANALYSIS");
38   gSystem->Load("libANALYSIScalib");
39   gSystem->Load("libTPCcalib"); 
40   TH1::AddDirectory(0);
41
42   //Example usage starting from the input data list in text file:
43   //
44   AliFileMerger merger;
45   merger.AddReject("esdFriend");
46   merger.IterTXT("calib.list","CalibObjects.root",kFALSE);
47   //
48
49 */
50 //////////////////////////////////////////////////////////////////////////
51  
52
53 #include <fstream>
54 #include <THashList.h>
55 #include <TChain.h>
56 #include <TKey.h>
57 #include <TH1.h>
58 #include <THStack.h>
59 #include "TSystem.h"
60 #include "TFile.h"
61 #include "TGrid.h"
62 #include "TGridResult.h"
63 #include "TObjString.h"
64 #include "TObjArray.h"
65 #include "TMethodCall.h"
66 #include "Riostream.h"
67 #include "AliSysInfo.h"
68 #include "AliFileMerger.h"
69 #include "AliLog.h"
70
71 ClassImp(AliFileMerger)
72
73 ProcInfo_t procInfo;//TMP
74
75 ////////////////////////////////////////////////////////////////////////
76
77 AliFileMerger::AliFileMerger():
78   TNamed(),
79   fRejectMask(0),
80   fAcceptMask(0),
81   fMaxFilesOpen(800),
82   fNoTrees(kFALSE)
83 {
84   //
85   // Default constructor
86   //
87 }
88
89 //______________________________________________________________________
90
91 AliFileMerger::AliFileMerger(const char* name):
92   TNamed(name,name),
93   fRejectMask(0),
94   fAcceptMask(0),
95   fMaxFilesOpen(800),
96   fNoTrees(kFALSE)
97 {
98   //
99   // 
100   //
101 }
102
103
104 void AliFileMerger::IterAlien(const char* outputDir, const char* outputFileName, const char* pattern, Bool_t dontOverwrite){
105
106   //
107   // Merge the files coming out of the calibration job
108   // 
109   TString command;
110   // looking for files to be merged in the output directory
111   command = Form("find %s/ *%s", outputDir, pattern);
112   printf("command: %s\n", command.Data());
113   TGrid::Connect("alien://");
114   TGridResult *res = gGrid->Command(command);
115   if (!res) return;
116   TIter nextmap(res);
117   TMap *map = 0;
118   // loop over the results
119   TList sourcelist;  
120   sourcelist.SetOwner(kTRUE);
121   //
122   while((map=(TMap*)nextmap())) {
123     // getting the turl
124     TObjString *objs = dynamic_cast<TObjString*>(map->GetValue("turl"));
125     if (!objs || !objs->GetString().Length()) {
126       // Nothing found - skip this output
127       delete res;
128       break;
129     } 
130     printf("looking for file %s\n",(objs->GetString()).Data());
131     AddFile(&sourcelist, (objs->GetString()).Data());;
132   }
133   //
134   IterList(&sourcelist, outputFileName, dontOverwrite);
135   delete res;
136 }
137
138 void AliFileMerger::IterList(const TList* namesList, const char* outputFileName, Bool_t dontOverwrite)
139 {
140   // merge in steps or in one go
141   //
142   gSystem->GetProcInfo(&procInfo);
143   AliInfo(Form(">> memory usage %ld %ld", procInfo.fMemResident, procInfo.fMemVirtual));
144   //
145   TString outputFile(outputFileName);
146   gSystem->ExpandPathName(outputFile);
147   //
148   int nFiles = namesList->GetEntries();
149   int maxSrcOpen = fMaxFilesOpen - 1;
150   TList filesList;
151   filesList.SetOwner(kTRUE);
152   //
153   TString tmpDest[2] = {outputFile,outputFile}; // names for tmp files
154   int npl = outputFile.Last('.');
155   if (npl<0) npl  = outputFile.Length();
156   for (int i=0;i<2;i++) tmpDest[i].Insert(npl,Form("_TMPMERGE%d_",i));
157   //
158   int nsteps = 0, currTmp = 0, start = 0;
159   for (int ifl=0;ifl<nFiles;ifl++) {
160     int st = ifl%maxSrcOpen;
161     if (st==0 && ifl) { // new chunk should be started, merge what was already accumulated
162       OpenNextChunks(namesList,&filesList,start,ifl-1);
163       start = ifl; // remember where to start next step
164       if (nsteps++) { // if not 1st one, merge the privous chunk with this one
165         filesList.AddFirst(TFile::Open(tmpDest[currTmp].Data()));
166         currTmp = (currTmp==0) ? 1:0;         // swap tmp files
167       }
168       // open temp target
169       TFile* targetTmp = TFile::Open( tmpDest[currTmp].Data(), "RECREATE");
170       if (!targetTmp || targetTmp->IsZombie()) {
171         printf("Error opening temporary file %s\n",tmpDest[currTmp].Data());
172         return;
173       }
174       MergeRootfile(targetTmp, &filesList);
175       targetTmp->Close();
176       delete targetTmp;
177       filesList.Clear(); // close all open files
178     }
179     // nothing to do until needed amount of files is accumulated
180   }
181   // merge last step
182   TFile* target = TFile::Open( outputFile.Data(), (dontOverwrite ? "CREATE":"RECREATE") );
183   if (!target || target->IsZombie()) {
184     cerr << "Error opening target file (does " << outputFileName << " exist?)." << endl;
185     cerr << "Use force = kTRUE to re-creation of output file." << endl;
186     return;
187   }
188   OpenNextChunks(namesList,&filesList,start,nFiles-1);
189   // add result of previous merges
190   if (nsteps) filesList.AddFirst(TFile::Open(tmpDest[currTmp].Data()));
191   MergeRootfile( target, &filesList);
192   target->Close();
193   delete target;
194   filesList.Clear();
195   // 
196   for (int i=0;i<2;i++) gSystem->Exec(Form("if [ -e %s ]; then \nrm %s\nfi",tmpDest[i].Data(),tmpDest[i].Data()));
197   //
198   printf("Merged %d files in %d steps\n",nFiles,++nsteps);
199   //  
200   gSystem->GetProcInfo(&procInfo);
201   AliInfo(Form("<< memory usage %ld %ld", procInfo.fMemResident, procInfo.fMemVirtual));
202 }
203
204 void AliFileMerger::IterTXT( const char * fileList,  const char* outputFileName, Bool_t dontOverwrite){
205   
206   // Merge the files indicated in the list - fileList
207   // ASCII file option example: 
208   // find `pwd`/ | grep AliESDfriends_v1.root > calib.list
209   
210   // Open the input stream
211   ifstream in;
212   in.open(fileList);
213   // Read the input list of files 
214   TString objfile;
215   Int_t counter=0;
216   TList sourcelist;  
217   sourcelist.SetOwner(kTRUE);
218   while(in.good()) {
219     in >> objfile;
220     if (!objfile.Contains(".root")) continue; // protection
221     gSystem->ExpandPathName(objfile);
222     printf("Add file:Counter\t%d\tMerging file %s\n",counter++,objfile.Data());
223     AddFile(&sourcelist, objfile.Data());
224   }
225   //
226   IterList(&sourcelist, outputFileName, dontOverwrite);
227   //
228 }
229
230 void AliFileMerger::StoreResults(TObjArray * array, const char* outputFileName){
231   //
232   // Storing the results in one single file
233   //
234   TFile *f = new TFile(outputFileName,"recreate");
235   for (Int_t i=0; i<array->GetEntries(); i++){
236     TObject *object0 = array->At(i);
237     if (!object0) continue;
238     object0->Write();
239   }
240   f->Close();
241   delete f;
242 }
243
244
245 void AliFileMerger::StoreSeparateResults(TObjArray * array, const char* outputFileName){
246   //
247   // Store the results in separate files (one per object)
248   //
249   for (Int_t i=0; i<array->GetEntries(); i++){
250     TObject *object0 = array->At(i);
251     if (!object0) continue;
252     TFile *f = new TFile(Form("%s_%s.root",outputFileName,object0->GetName()),"recreate");
253     object0->Write();
254     f->Close();
255     delete f;
256   }
257 }
258
259 void AliFileMerger::Merge(TFile* fileIn, TObjArray * array){
260   //
261   // Merging procedure
262   //
263   if (!array) return;
264   static Int_t counter=-1;
265   counter++;
266   TObjArray *carray = new TObjArray;   //array of the objects inside current file
267   carray->SetOwner(kTRUE);
268   
269   // load all objects to  memory
270   
271   TList *farr = fileIn->GetListOfKeys();
272   if (!farr) { 
273     delete carray;
274     return;
275   }
276   for (Int_t ical=0; ical<farr->GetEntries(); ical++){
277     if (!farr->At(ical)) continue;
278     TString name(farr->At(ical)->GetName());
279     if (!IsAccepted(name)) continue;                        // skip not accepted entries
280     TObject *obj = fileIn->Get(name.Data());
281     if (obj) carray->AddLast(obj);
282     AliSysInfo::AddStamp(name.Data(),1,ical,counter);  
283   }
284   
285   if (carray->GetEntries()==0)  { 
286     delete carray;
287     return;
288   }
289   TMethodCall callEnv;
290   Int_t entries =carray->GetEntriesFast();
291   for (Int_t i=0; i<entries; i++){
292     
293     TObjArray *templist = new TObjArray(1);
294     templist->SetOwner(kFALSE);
295     TObject *currentObject = carray->At(i);
296     if (!currentObject) { 
297       delete templist;
298       continue;
299     }
300     printf("%s\n",currentObject->GetName());
301     callEnv.InitWithPrototype(currentObject->IsA(), "Merge", "TCollection*");
302     if (!callEnv.IsValid()) {
303       delete templist; 
304       continue;
305     }
306     TString oname=currentObject->GetName();
307     TObject *mergedObject = array->FindObject(currentObject->GetName());
308     if (!mergedObject) {
309       array->AddLast(currentObject);
310       carray->RemoveAt(i);
311       delete templist; 
312       continue;
313     }
314     templist->AddLast(currentObject);
315     callEnv.SetParam((Long_t) templist);
316     callEnv.Execute(mergedObject);
317     AliSysInfo::AddStamp(currentObject->GetName(),2,i,counter);  
318     delete templist;
319   }
320   carray->Delete();
321   delete carray;
322 }
323
324 Bool_t AliFileMerger::IsAccepted(TString name){
325   //
326   // Accept/reject logic
327   // name - name of the entry
328   //
329   //  if fAcceptMask specified   - entry has to be in list of selected
330   //  if fRejectMask speciefied  - entry with name speciief in the list are rejected 
331   //
332   Bool_t accept=kTRUE;
333   if (fAcceptMask){
334     //
335     accept=kFALSE;
336     for (Int_t iaccept=0; iaccept<fAcceptMask->GetEntries(); iaccept++){
337       if (name.Contains(fAcceptMask->At(iaccept)->GetName())) accept=kTRUE;   // entry was selected
338     }
339   }
340   if (!accept) return kFALSE;
341
342   if (fRejectMask){
343     //
344     for (Int_t ireject=0; ireject<fRejectMask->GetEntries(); ireject++){
345       if (name.Contains(fRejectMask->At(ireject)->GetName())) accept=kFALSE;   // entry was rejected
346     }
347   }
348   return accept;
349 }
350
351
352
353
354 void AliFileMerger::AddReject(const char *reject){
355   //
356   // add reject string to the list of entries to be rejected for merging
357   //
358   if (!fRejectMask) fRejectMask = new TObjArray;
359   fRejectMask->AddLast(new TObjString(reject));
360 }
361 void AliFileMerger::AddAccept(const char *accept){
362   //
363   // add reject string to the list of entries to be rejected for merging
364   //
365   if (!fAcceptMask) fAcceptMask = new TObjArray;
366   fAcceptMask->AddLast(new TObjString(accept));
367
368
369 }
370
371 //___________________________________________________________________________
372 int AliFileMerger::MergeRootfile( TDirectory *target, TList *sourcelist)
373 {
374   // Merge all objects in a directory
375   // modified version of root's hadd.cxx
376   gSystem->GetProcInfo(&procInfo);
377   AliInfo(Form(">> memory usage %ld %ld", procInfo.fMemResident, procInfo.fMemVirtual));
378   //
379   int status = 0;
380   cout << "Target path: " << target->GetPath() << endl;
381   TString path( (char*)strstr( target->GetPath(), ":" ) );
382   path.Remove( 0, 2 );
383   //
384   // find 1st valid file
385   TDirectory *first_source = (TDirectory*)sourcelist->First();
386   //
387   Int_t nguess = sourcelist->GetSize()+1000;
388   THashList allNames(nguess);
389   ((THashList*)target->GetList())->Rehash(nguess);
390   ((THashList*)target->GetListOfKeys())->Rehash(nguess);
391   TList listH;
392   TString listHargs;
393   listHargs.Form("((TCollection*)0x%lx)", (ULong_t)&listH);
394   //
395   while(first_source) {
396     //
397     TDirectory *current_sourcedir = first_source->GetDirectory(path);
398     if (!current_sourcedir) {
399       first_source = (TDirectory*)sourcelist->After(first_source);
400       continue;
401     }
402     // loop over all keys in this directory
403     TChain *globChain = 0;
404     TIter nextkey( current_sourcedir->GetListOfKeys() );
405     TKey *key, *oldkey=0;
406     //gain time, do not add the objects in the list in memory
407     TH1::AddDirectory(kFALSE);
408     //
409     int counterK = 0;
410     int counterF=0;
411     //
412     while ( (key = (TKey*)nextkey())) {
413       if (current_sourcedir == target) break;
414       //
415       // check if we don't reject this name
416       TString nameK(key->GetName());
417       if (!IsAccepted(nameK)) {
418         if (!counterF) printf("Object %s is in rejection list, skipping...\n",nameK.Data());
419         continue;
420       }
421       //
422       //keep only the highest cycle number for each key
423       if (oldkey && !strcmp(oldkey->GetName(),key->GetName())) continue;
424       if (!strcmp(key->GetClassName(),"TProcessID")) {key->ReadObj(); continue;}
425       if (allNames.FindObject(key->GetName())) continue;
426       TClass *cl = TClass::GetClass(key->GetClassName());
427       if (!cl || !cl->InheritsFrom(TObject::Class())) {
428         cout << "Cannot merge object type, name: "
429              << key->GetName() << " title: " << key->GetTitle() << endl;
430         continue;
431       }
432       allNames.Add(new TObjString(key->GetName()));
433       AliSysInfo::AddStamp(nameK.Data(),1,++counterK,counterF++); 
434       // read object from first source file
435       //current_sourcedir->cd();
436
437       TObject *obj = key->ReadObj();
438       if (!obj) {
439         AliError(Form("Failed to get the object with key %s from %s",key->GetName(),current_sourcedir->GetFile()->GetName()));
440         continue;
441       }
442
443       if ( obj->IsA()->InheritsFrom( TTree::Class() ) ) {
444         
445         // loop over all source files create a chain of Trees "globChain"
446         if (!fNoTrees) { // 
447           TString obj_name;
448           if (path.Length()) {
449             obj_name = path + "/" + obj->GetName();
450           } else {
451             obj_name = obj->GetName();
452           }
453           globChain = new TChain(obj_name);
454           globChain->Add(first_source->GetName());
455           TFile *nextsource = (TFile*)sourcelist->After( first_source );
456           while ( nextsource ) {
457             //do not add to the list a file that does not contain this Tree
458             TFile *curf = TFile::Open(nextsource->GetName());
459             if (curf) {
460               Bool_t mustAdd = kFALSE;
461               if (curf->FindKey(obj_name)) {
462                 mustAdd = kTRUE;
463               } else {
464                 //we could be more clever here. No need to import the object
465                 //we are missing a function in TDirectory
466                 TObject *aobj = curf->Get(obj_name);
467                 if (aobj) { mustAdd = kTRUE; delete aobj;}
468               }
469               if (mustAdd) {
470                 globChain->Add(nextsource->GetName());
471               }
472             }
473             delete curf;
474             nextsource = (TFile*)sourcelist->After( nextsource );
475           }
476         }
477       } else if ( obj->IsA()->InheritsFrom( TDirectory::Class() ) ) {
478         // it's a subdirectory
479         
480         cout << "Found subdirectory " << obj->GetName() << endl;
481         // create a new subdir of same name and title in the target file
482         target->cd();
483         TDirectory *newdir = target->mkdir( obj->GetName(), obj->GetTitle() );
484         
485         // newdir is now the starting point of another round of merging
486         // newdir still knows its depth within the target file via
487         // GetPath(), so we can still figure out where we are in the recursion
488         status = MergeRootfile( newdir, sourcelist);
489         if (status) return status;
490         
491       } else if ( obj->InheritsFrom(TObject::Class())
492                   && obj->IsA()->GetMethodWithPrototype("Merge", "TCollection*") ) {
493         // object implements Merge(TCollection*)
494         
495         // loop over all source files and merge same-name object
496         TFile *nextsource = (TFile*)sourcelist->After( first_source );
497         while ( nextsource ) {
498           // make sure we are at the correct directory level by cd'ing to path
499           TDirectory *ndir = nextsource->GetDirectory(path);
500           if (ndir) {
501             ndir->cd();
502             TKey *key2 = (TKey*)gDirectory->GetListOfKeys()->FindObject(key->GetName());
503             if (key2) {
504               TObject *hobj = key2->ReadObj();
505               if (!hobj) {
506                 cout << "Failed to get the object with key " << key2->GetName() << " from " << 
507                   ndir->GetFile()->GetName() << "/" << ndir->GetName() << endl;
508                 nextsource = (TFile*)sourcelist->After( nextsource );
509                 continue;
510               }
511               //
512               hobj->ResetBit(kMustCleanup);
513               listH.Add(hobj);
514               Int_t error = 0;
515               obj->Execute("Merge", listHargs.Data(), &error); // RS Probleme here
516               if (error) {
517                 cerr << "Error calling Merge() on " << obj->GetName()
518                      << " with the corresponding object in " << nextsource->GetName() << endl;
519               }
520               listH.Delete();
521               AliSysInfo::AddStamp(nameK.Data(),1,counterK,counterF++); 
522             }
523           }
524           nextsource = (TFile*)sourcelist->After( nextsource );
525         }
526       } else if ( obj->IsA()->InheritsFrom( THStack::Class() ) ) {
527         THStack *hstack1 = (THStack*) obj;
528         TList* l = new TList();
529         
530         // loop over all source files and merge the histos of the
531         // corresponding THStacks with the one pointed to by "hstack1"
532         TFile *nextsource = (TFile*)sourcelist->After( first_source );
533         while ( nextsource ) {
534           // make sure we are at the correct directory level by cd'ing to path
535           TDirectory *ndir = nextsource->GetDirectory(path);
536           if (ndir) {
537             ndir->cd();
538             TKey *key2 = (TKey*)gDirectory->GetListOfKeys()->FindObject(hstack1->GetName());
539             if (key2) {
540               THStack *hstack2 = (THStack*) key2->ReadObj();
541               l->Add(hstack2->GetHists()->Clone());
542               delete hstack2;
543               AliSysInfo::AddStamp(nameK.Data(),1,counterK,counterF++); 
544             }
545           }
546           
547           nextsource = (TFile*)sourcelist->After( nextsource );
548         }
549         hstack1->GetHists()->Merge(l);
550         l->Delete();
551       } else {
552         // object is of no type that we can merge
553         cout << "Cannot merge object type, name: "
554              << obj->GetName() << " title: " << obj->GetTitle() << endl;
555         
556         // loop over all source files and write similar objects directly to the output file
557         TFile *nextsource = (TFile*)sourcelist->After( first_source );
558         while ( nextsource ) {
559           // make sure we are at the correct directory level by cd'ing to path
560           TDirectory *ndir = nextsource->GetDirectory(path);
561           if (ndir) {
562             ndir->cd();
563             TKey *key2 = (TKey*)gDirectory->GetListOfKeys()->FindObject(key->GetName());
564             if (key2) {
565               TObject *nobj = key2->ReadObj();
566               nobj->ResetBit(kMustCleanup);
567               int nbytes1 = target->WriteTObject(nobj, key2->GetName(), "SingleKey" );
568               if (nbytes1 <= 0) status = -1;
569               delete nobj;
570             }
571           }
572           nextsource = (TFile*)sourcelist->After( nextsource );
573         }
574       }
575       
576       // now write the merged histogram (which is "in" obj) to the target file
577       // note that this will just store obj in the current directory level,
578       // which is not persistent until the complete directory itself is stored
579       // by "target->Write()" below
580       target->cd();
581       
582       //!!if the object is a tree, it is stored in globChain...
583       if(obj->IsA()->InheritsFrom( TDirectory::Class() )) {
584         //printf("cas d'une directory\n");
585       } else if(obj->IsA()->InheritsFrom( TTree::Class() )) {
586         if (!fNoTrees) {
587           globChain->ls();
588           globChain->Merge(target->GetFile(),0,"keep fast");
589           delete globChain;
590         }
591       } else {
592         int nbytes2 = obj->Write( key->GetName(), TObject::kSingleKey );
593         if (nbytes2 <= 0) status = -1;
594       }
595       oldkey = key;
596       delete obj;
597     } // while ( ( TKey *key = (TKey*)nextkey() ) )
598     first_source = (TDirectory*)sourcelist->After(first_source);
599   }
600   // save modifications to target file
601   target->SaveSelf(kTRUE);
602   //
603   gSystem->GetProcInfo(&procInfo);
604   AliInfo(Form("<< memory usage %ld %ld", procInfo.fMemResident, procInfo.fMemVirtual));
605
606   return status;
607 }
608
609 //___________________________________________________________________________
610 int AliFileMerger::OpenNextChunks(const TList* namesList, TList* filesList, Int_t from, Int_t to)
611 {
612   gSystem->GetProcInfo(&procInfo);
613   AliInfo(Form(">> memory usage %ld %ld", procInfo.fMemResident, procInfo.fMemVirtual));
614
615   filesList->Clear();
616   int nEnt = namesList->GetEntries();
617   from = from<nEnt ? from : nEnt;
618   to   = to<nEnt ? to : nEnt;
619   int count = 0;
620   for (int i=from;i<=to;i++) {
621     TNamed* fnam = (TNamed*)namesList->At(i);
622     if (!fnam) continue;
623     TString fnamS(fnam->GetName());
624     gSystem->ExpandPathName(fnamS);
625     if (fnamS.BeginsWith("alien://") && !gGrid) TGrid::Connect("alien");
626     TFile* source = TFile::Open(fnam->GetName());
627     if( source==0 ) { printf("Failed to open file %s, will skip\n",fnam->GetName()); continue; }
628     filesList->Add(source);
629     printf("Opened file %s\n",fnam->GetName());
630     count++;
631   }
632   gSystem->GetProcInfo(&procInfo);
633   AliInfo(Form("<< memory usage %ld %ld", procInfo.fMemResident, procInfo.fMemVirtual));
634
635   return count;
636 }
637
638
639 //___________________________________________________________________________
640 int AliFileMerger::AddFile(TList* namesList, std::string entry)
641 {
642   // add a new file to the list of files
643   //  static int count(0);
644   if( entry.empty() ) return 0;
645   size_t j =entry.find_first_not_of(' ');
646   if( j==std::string::npos ) return 0;
647   entry = entry.substr(j);
648   if( entry.substr(0,1)=="@") {
649     std::ifstream indirect_file(entry.substr(1).c_str() );
650     if( ! indirect_file.is_open() ) {
651       std::cerr<< "Could not open indirect file " << entry.substr(1) << std::endl;
652       return 1;
653     }
654     while( indirect_file ){
655       std::string line;
656       std::getline(indirect_file, line);
657       if( AddFile(namesList, line)!=0 ) return 1;;
658     }
659     return 0;
660   }
661   //  cout << "Source file " << (++count) << ": " << entry << endl;
662   namesList->Add(new TNamed(entry,""));
663   return 0;
664 }