Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Hive-3.4/sources/HeuristicLab.Clients.Hive/3.4/HiveExperiment/HiveExperimentClient.cs @ 5055

Last change on this file since 5055 was 5055, checked in by cneumuel, 14 years ago

#1233

  • added test project
File size: 23.0 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2010 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Threading;
26using HeuristicLab.Common;
27using HeuristicLab.Core;
28using HeuristicLab.Tracing;
29using HeuristicLab.Services.Hive.Common;
30using HeuristicLab.Services.Hive.Common.ServiceContracts;
31
32namespace HeuristicLab.Clients.Hive {
33  using DT = HeuristicLab.Services.Hive.Common.DataTransfer;
34  using HeuristicLab.Services.Hive.Common.DataTransfer;
35  using HeuristicLab.Clients.Hive.Jobs;
36  using HeuristicLab.Clients.Common;
37
38  /// <summary>
39  /// An experiment which contains multiple batch runs of algorithms.
40  /// </summary>
41  [Item(itemName, itemDescription)]
42  public class HiveExperimentClient : NamedItem, IExecutable, IProgressReporter {
43    private object locker = new object();
44    private const string itemName = "Hive Experiment";
45    private const string itemDescription = "A runner for a single experiment, which's algorithms are executed in the Hive.";
46    private System.Timers.Timer timer;
47    private DateTime lastUpdateTime;
48    private Guid? rootJobId;
49    private JobResultPoller jobResultPoller;
50
51    private Guid hiveExperimentId;
52    public Guid HiveExperimentId {
53      get { return hiveExperimentId; }
54      set { hiveExperimentId = value; }
55    }
56
57    private HiveJob hiveJob;
58    public HiveJob HiveJob {
59      get { return hiveJob; }
60      set {
61        DeregisterHiveJobEvents();
62        if (hiveJob != value) {
63          hiveJob = value;
64          RegisterHiveJobEvents();
65          OnHiveJobChanged();
66        }
67      }
68    }
69
70    private ILog log;
71    public ILog Log {
72      get { return log; }
73    }
74
75    private string resourceIds;
76    public string ResourceIds {
77      get { return resourceIds; }
78      set {
79        if (resourceIds != value) {
80          resourceIds = value;
81          OnResourceIdsChanged();
82        }
83      }
84    }
85
86    private bool isPollingResults;
87    public bool IsPollingResults {
88      get { return isPollingResults; }
89      private set {
90        if (isPollingResults != value) {
91          isPollingResults = value;
92          OnIsPollingResultsChanged();
93        }
94      }
95    }
96
97    private bool isProgressing;
98    public bool IsProgressing {
99      get { return isProgressing; }
100      set {
101        if (isProgressing != value) {
102          isProgressing = value;
103          OnIsProgressingChanged();
104        }
105      }
106    }
107
108    private IProgress progress;
109    public IProgress Progress {
110      get { return progress; }
111    }
112
113    public HiveExperimentClient() : base(itemName, itemDescription) {
114      // TODO //this.ResourceIds = HeuristicLab.Hive.Experiment.Properties.Settings.Default.ResourceIds;
115      this.ResourceIds = "MyGroup";
116      this.log = new Log();
117      InitTimer();
118    }
119    public HiveExperimentClient(DT.HiveExperiment hiveExperimentDto) : this() {
120      UpdateFromDto(hiveExperimentDto);
121    }
122    protected HiveExperimentClient(HiveExperimentClient original, Cloner cloner)
123      : base(original, cloner) {
124      this.ResourceIds = original.resourceIds;
125      this.ExecutionState = original.executionState;
126      this.ExecutionTime = original.executionTime;
127      this.log = cloner.Clone(original.log);
128      this.lastUpdateTime = original.lastUpdateTime;
129      this.rootJobId = original.rootJobId;
130    }
131    public override IDeepCloneable Clone(Cloner cloner) {
132      return new HiveExperimentClient(this, cloner);
133    }
134
135    public void UpdateFromDto(DT.HiveExperiment hiveExperimentDto) {
136      this.HiveExperimentId = hiveExperimentDto.Id;
137      this.Name = hiveExperimentDto.Name;
138      this.Description = hiveExperimentDto.Description;
139      // TODO: this.ResourceIds = hiveExperimentDto.ResourceIds;
140      this.rootJobId = hiveExperimentDto.RootJobId;
141    }
142
143    public DT.HiveExperiment ToHiveExperimentDto() {
144      return new DT.HiveExperiment() {
145        Id = this.HiveExperimentId,
146        Name = this.Name,
147        Description = this.Description,
148        //ResourceIds = this.ResourceIds,
149        RootJobId = this.rootJobId
150      };
151    }
152
153    public void SetExperiment(Optimization.Experiment experiment) {
154      this.HiveJob = new HiveJob(experiment);
155      Prepare();
156    }
157
158    private void RegisterHiveJobEvents() {
159      if (HiveJob != null) {
160        HiveJob.JobStateChanged += new EventHandler(HiveJob_JobStateChanged);
161      }
162    }
163
164    private void DeregisterHiveJobEvents() {
165      if (HiveJob != null) {
166        HiveJob.JobStateChanged -= new EventHandler(HiveJob_JobStateChanged);
167      }
168    }
169
170    /// <summary>
171    /// Returns the experiment from the root HiveJob
172    /// </summary>
173    public Optimization.Experiment GetExperiment() {
174      if (this.HiveJob != null) {
175        return HiveJob.OptimizerJob.OptimizerAsExperiment;
176      }
177      return null;
178    }
179
180    #region IExecutable Members
181    private Core.ExecutionState executionState;
182    public ExecutionState ExecutionState {
183      get { return executionState; }
184      private set {
185        if (executionState != value) {
186          executionState = value;
187          OnExecutionStateChanged();
188        }
189      }
190    }
191
192    private TimeSpan executionTime;
193    public TimeSpan ExecutionTime {
194      get { return executionTime; }
195      private set {
196        if (executionTime != value) {
197          executionTime = value;
198          OnExecutionTimeChanged();
199        }
200      }
201    }
202
203    public void Pause() {
204      throw new NotSupportedException();
205    }
206
207    public void Prepare() {
208      // do nothing
209    }
210
211    public void Start() {
212      OnStarted();
213      ExecutionTime = new TimeSpan();
214      lastUpdateTime = DateTime.Now;
215      this.ExecutionState = Core.ExecutionState.Started;
216
217      Thread t = new Thread(RunUploadExperiment);
218      t.Name = "RunUploadExperimentThread";
219      t.Start();
220    }
221
222    private void RunUploadExperiment() {
223      try {
224        this.progress = new Progress("Connecting to server...");
225        IsProgressing = true;
226        using (Disposable<IHiveService> service = ServiceLocator.Instance.GetService()) {
227          IEnumerable<string> groups = ToResourceIdList(this.ResourceIds);
228          this.HiveJob.SetIndexInParentOptimizerList(null);
229
230          int totalJobCount = this.HiveJob.GetAllHiveJobs().Count();
231          int jobCount = 0;
232
233          this.progress.Status = "Uploading jobs...";
234          UploadJobWithChildren(service.Obj, this.HiveJob, null, groups, ref jobCount, totalJobCount);
235          this.rootJobId = this.HiveJob.Job.Id;
236          LogMessage("Finished sending jobs to hive");
237
238          // insert or update HiveExperiment
239          this.progress.Status = "Uploading HiveExperiment...";
240
241          DT.HiveExperiment he = service.Obj.GetHiveExperiment(service.Obj.AddHiveExperiment(this.ToHiveExperimentDto()));
242          this.UpdateFromDto(he);
243
244          StartResultPolling();
245        }
246      }
247      catch (Exception e) {
248        OnExceptionOccured(e);
249      }
250      finally {
251        IsProgressing = false;
252      }
253    }
254
255    /// <summary>
256    /// Uploads the given job and all its child-jobs while setting the proper parentJobId values for the childs
257    ///
258    /// </summary>
259    /// <param name="service"></param>
260    /// <param name="hiveJob"></param>
261    /// <param name="parentHiveJob">shall be null if its the root job</param>
262    /// <param name="groups"></param>
263    private void UploadJobWithChildren(IHiveService service, HiveJob hiveJob, HiveJob parentHiveJob, IEnumerable<string> groups, ref int jobCount, int totalJobCount) {
264      jobCount++;
265      this.progress.Status = string.Format("Serializing job {0} of {1}", jobCount, totalJobCount);
266      JobData jobData;
267      if (hiveJob.OptimizerJob.ComputeInParallel &&
268        (hiveJob.OptimizerJob.Optimizer is Optimization.Experiment || hiveJob.OptimizerJob.Optimizer is Optimization.BatchRun)) {
269        hiveJob.Job.JobState = JobState.WaitingForChildJobs;
270        hiveJob.OptimizerJob.CollectChildJobs = false; // don't collect child-jobs on slaves
271        jobData = hiveJob.GetAsJobData(true);
272      } else {
273        jobData = hiveJob.GetAsJobData(false);
274      }
275
276      this.progress.Status = string.Format("Uploading job {0} of {1} ({2} kb)", jobCount, totalJobCount, jobData.Data.Count() / 1024);
277      this.progress.ProgressValue = (double)jobCount / totalJobCount;
278
279      if (parentHiveJob != null) {
280        //response = service.AddChildJob(parentHiveJob.Job.Id, serializedJob);
281        hiveJob.Job.Id = service.AddChildJob(parentHiveJob.Job.Id, hiveJob.Job, jobData);
282      } else {
283        // response = service.AddJobWithGroupStrings(serializedJob, groups);
284        hiveJob.Job.Id = service.AddJob(hiveJob.Job, jobData);
285      }
286
287      LogMessage(hiveJob.Job.Id, "Job sent to Hive");
288
289      foreach (HiveJob child in hiveJob.ChildHiveJobs) {
290        UploadJobWithChildren(service, child, hiveJob, groups, ref jobCount, totalJobCount);
291      }
292    }
293
294    /// <summary>
295    /// Converts a string which can contain Ids separated by ';' to a enumerable
296    /// </summary>
297    private IEnumerable<string> ToResourceIdList(string resourceGroups) {
298      if (!string.IsNullOrEmpty(resourceGroups)) {
299        return resourceIds.Split(';');
300      } else {
301        return new List<string>();
302      }
303    }
304
305    public void Stop() {
306      using (Disposable<IHiveService> service = ServiceLocator.Instance.GetService()) {
307        foreach (HiveJob hj in HiveJob.GetAllHiveJobs()) {
308          service.Obj.StopJob(hj.Job.Id);
309        }
310      }
311    }
312
313    #endregion
314
315    public void StartResultPolling() {
316      if (!jobResultPoller.IsPolling) {
317        jobResultPoller.Start();
318      } else {
319        throw new JobResultPollingException("Result polling already running");
320      }
321    }
322
323    public void StopResultPolling() {
324      if (jobResultPoller.IsPolling) {
325        jobResultPoller.Stop();
326      } else {
327        throw new JobResultPollingException("Result polling not running");
328      }
329    }
330
331    #region HiveJob Events
332    void HiveJob_JobStateChanged(object sender, EventArgs e) {
333      if (HiveJob != null) {
334        rootJobId = HiveJob.Job.Id;
335      }
336    }
337    #endregion
338
339    #region Eventhandler
340
341    public event EventHandler ExecutionTimeChanged;
342    private void OnExecutionTimeChanged() {
343      EventHandler handler = ExecutionTimeChanged;
344      if (handler != null) handler(this, EventArgs.Empty);
345    }
346
347    public event EventHandler ExecutionStateChanged;
348    private void OnExecutionStateChanged() {
349      LogMessage("ExecutionState changed to " + executionState.ToString());
350      EventHandler handler = ExecutionStateChanged;
351      if (handler != null) handler(this, EventArgs.Empty);
352    }
353
354    public event EventHandler Started;
355    private void OnStarted() {
356      LogMessage("Started");
357      timer.Start();
358      EventHandler handler = Started;
359      if (handler != null) handler(this, EventArgs.Empty);
360    }
361
362    public event EventHandler Stopped;
363    private void OnStopped() {
364      LogMessage("Stopped");
365      timer.Stop();
366      EventHandler handler = Stopped;
367      if (handler != null) handler(this, EventArgs.Empty);
368    }
369
370    public event EventHandler Paused;
371    private void OnPaused() {
372      LogMessage("Paused");
373      EventHandler handler = Paused;
374      if (handler != null) handler(this, EventArgs.Empty);
375    }
376
377    public event EventHandler Prepared;
378    protected virtual void OnPrepared() {
379      LogMessage("Prepared");
380      EventHandler handler = Prepared;
381      if (handler != null) handler(this, EventArgs.Empty);
382    }
383
384    public event EventHandler ResourceIdsChanged;
385    protected virtual void OnResourceIdsChanged() {
386      EventHandler handler = ResourceIdsChanged;
387      if (handler != null) handler(this, EventArgs.Empty);
388    }
389
390    public event EventHandler IsResultsPollingChanged;
391    private void OnIsPollingResultsChanged() {
392      if (this.IsPollingResults) {
393        LogMessage("Results Polling Started");
394      } else {
395        LogMessage("Results Polling Stopped");
396      }
397      EventHandler handler = IsResultsPollingChanged;
398      if (handler != null) handler(this, EventArgs.Empty);
399    }
400
401    public event EventHandler<EventArgs<Exception>> ExceptionOccurred;
402    private void OnExceptionOccured(Exception e) {
403      var handler = ExceptionOccurred;
404      if (handler != null) handler(this, new EventArgs<Exception>(e));
405    }
406
407    public event EventHandler HiveJobChanged;
408    private void OnHiveJobChanged() {
409      if (jobResultPoller != null && jobResultPoller.IsPolling) {
410        jobResultPoller.Stop();
411        DeregisterResultPollingEvents();
412      }
413      if (HiveJob != null) {
414        jobResultPoller = new JobResultPoller(HiveJob, ApplicationConstants.ResultPollingInterval);
415        RegisterResultPollingEvents();
416      }
417      EventHandler handler = HiveJobChanged;
418      if (handler != null) handler(this, EventArgs.Empty);
419    }
420
421    public event EventHandler IsProgressingChanged;
422    private void OnIsProgressingChanged() {
423      var handler = IsProgressingChanged;
424      if (handler != null) handler(this, EventArgs.Empty);
425    }
426    #endregion
427
428    #region JobResultPoller Events
429    private void RegisterResultPollingEvents() {
430      jobResultPoller.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
431      jobResultPoller.JobResultsReceived += new EventHandler<EventArgs<IEnumerable<LightweightJob>>>(jobResultPoller_JobResultReceived);
432      jobResultPoller.PollingStarted += new EventHandler(jobResultPoller_PollingStarted);
433      jobResultPoller.PollingFinished += new EventHandler(jobResultPoller_PollingFinished);
434      jobResultPoller.IsPollingChanged += new EventHandler(jobResultPoller_IsPollingChanged);
435    }
436    private void DeregisterResultPollingEvents() {
437      jobResultPoller.ExceptionOccured -= new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
438      jobResultPoller.JobResultsReceived -= new EventHandler<EventArgs<IEnumerable<LightweightJob>>>(jobResultPoller_JobResultReceived);
439      jobResultPoller.PollingStarted -= new EventHandler(jobResultPoller_PollingStarted);
440      jobResultPoller.PollingFinished -= new EventHandler(jobResultPoller_PollingFinished);
441      jobResultPoller.IsPollingChanged -= new EventHandler(jobResultPoller_IsPollingChanged);
442    }
443    void jobResultPoller_IsPollingChanged(object sender, EventArgs e) {
444      this.IsPollingResults = jobResultPoller.IsPolling;
445    }
446    void jobResultPoller_PollingFinished(object sender, EventArgs e) {
447      LogMessage("Polling results finished");
448    }
449    void jobResultPoller_PollingStarted(object sender, EventArgs e) {
450      LogMessage("Polling results started");
451    }
452    void jobResultPoller_JobResultReceived(object sender, EventArgs<IEnumerable<LightweightJob>> e) {
453      foreach (LightweightJob lightweightJob in e.Value) {
454        HiveJob hj = hiveJob.GetHiveJobByJobId(lightweightJob.Id);
455        if (hj != null) {
456          hj.UpdateFromLightweightJob(lightweightJob);
457          if ((hj.Job.JobState == JobState.Aborted ||
458               hj.Job.JobState == JobState.Failed ||
459               hj.Job.JobState == JobState.Finished) &&
460              !hj.IsFinishedOptimizerDownloaded) {
461            LogMessage(hj.Job.Id, "Downloading optimizer for job");
462            OptimizerJob optimizerJob = LoadOptimizerJob(hj.Job.Id);
463            if (optimizerJob == null) {
464              // something bad happened to this job. set to finished to allow the rest beeing downloaded
465              hj.IsFinishedOptimizerDownloaded = true;
466            } else {
467              if (lightweightJob.ParentJobId.HasValue) {
468                HiveJob parentHiveJob = HiveJob.GetHiveJobByJobId(lightweightJob.ParentJobId.Value);
469                parentHiveJob.UpdateChildOptimizer(optimizerJob, hj.Job.Id);
470              } else {
471                this.HiveJob.IsFinishedOptimizerDownloaded = true;
472              }
473            }
474          }
475        }
476      }
477      GC.Collect(); // force GC, because .NET is too lazy here (deserialization takes a lot of memory)
478      if (AllJobsFinished()) {
479        this.ExecutionState = Core.ExecutionState.Stopped;
480        StopResultPolling();
481        OnStopped();
482      }
483    }
484
485    private bool AllJobsFinished() {
486      return HiveJob.GetAllHiveJobs().All(hj => hj.IsFinishedOptimizerDownloaded);
487    }
488
489    void jobResultPoller_ExceptionOccured(object sender, EventArgs<Exception> e) {
490      OnExceptionOccured(e.Value);
491    }
492    #endregion
493
494    #region Execution Time Timer
495    private void InitTimer() {
496      timer = new System.Timers.Timer(100);
497      timer.AutoReset = true;
498      timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
499    }
500
501    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
502      DateTime now = DateTime.Now;
503      ExecutionTime += now - lastUpdateTime;
504      lastUpdateTime = now;
505    }
506    #endregion
507
508    #region Logging
509    private void LogMessage(string message) {
510      // HeuristicLab.Log is not Thread-Safe, so lock on every call
511      lock (locker) {
512        log.LogMessage(message);
513        Logger.Debug(message);
514      }
515    }
516
517    private void LogMessage(Guid jobId, string message) {
518      //GetJobItemById(jobId).LogMessage(message);
519      LogMessage(message + " (jobId: " + jobId + ")");
520    }
521
522    #endregion
523
524    /// <summary>
525    /// Downloads the root job from hive and sets the experiment, rootJob and rootJobItem
526    /// </summary>
527    public void LoadHiveJob() {
528      progress = new Progress();
529      try {
530        IsProgressing = true;
531        int totalJobCount = 0;
532        int jobCount = 0;
533        progress.Status = "Connecting to Server...";
534        using (var service = ServiceLocator.Instance.GetService()) {
535          // fetch all Job objects to create the full tree of tree of HiveJob objects
536          progress.Status = "Downloading list of jobs...";
537          IEnumerable<LightweightJob> allResults = service.Obj.GetLightweightChildJobs(rootJobId.Value, true, true);
538          totalJobCount = allResults.Count();
539
540          // download them first
541          IDictionary<Guid, Job> allJobs = new Dictionary<Guid, Job>();
542          IDictionary<Guid, JobData> allJobDatas = new Dictionary<Guid, JobData>();
543          foreach (LightweightJob lightweightJob in allResults) {
544            jobCount++;
545            progress.Status = string.Format("Downloading {0} of {1} jobs...", jobCount, totalJobCount);
546            allJobs.Add(lightweightJob.Id, service.Obj.GetJob(lightweightJob.Id));
547            allJobDatas.Add(lightweightJob.Id, service.Obj.GetJobData(lightweightJob.Id));
548            progress.ProgressValue = (double)jobCount / totalJobCount;
549          }
550
551          jobCount = 1;
552          progress.Status = string.Format("Deserializing {0} of {1} jobs... ({2} kb)", jobCount, totalJobCount, allJobDatas[this.rootJobId.Value].Data.Count() / 1024);
553          this.HiveJob = new HiveJob(allJobs[this.rootJobId.Value], allJobDatas[this.rootJobId.Value], false);
554          allJobDatas.Remove(this.rootJobId.Value); // reduce memory footprint
555          allJobs.Remove(this.rootJobId.Value);
556          progress.ProgressValue = (double)jobCount / totalJobCount;
557
558          if (this.HiveJob.Job.DateFinished.HasValue) {
559            this.ExecutionTime = this.HiveJob.Job.DateFinished.Value - this.HiveJob.Job.DateCreated.Value;
560            this.lastUpdateTime = this.HiveJob.Job.DateFinished.Value;
561            this.ExecutionState = Core.ExecutionState.Stopped;
562            OnStopped();
563          } else {
564            this.ExecutionTime = DateTime.Now - this.HiveJob.Job.DateCreated.Value;
565            this.lastUpdateTime = DateTime.Now;
566            this.ExecutionState = Core.ExecutionState.Started;
567            OnStarted();
568          }
569
570          // build child-job tree
571          LoadChildResults(service.Obj, this.HiveJob, allResults, allJobs, allJobDatas, progress, totalJobCount, ref jobCount);
572          StartResultPolling();
573        }
574      }
575      catch (Exception e) {
576        OnExceptionOccured(e);
577      }
578      finally {
579        IsProgressing = false;
580      }
581    }
582
583    private void LoadChildResults(IHiveService service, HiveJob parentHiveJob, IEnumerable<LightweightJob> allResults, IDictionary<Guid, Job> allJobs, IDictionary<Guid, JobData> allJobDatas, IProgress progress, int totalJobCount, ref int jobCount) {
584      IEnumerable<LightweightJob> childResults = from result in allResults
585                                                 where result.ParentJobId.HasValue && result.ParentJobId.Value == parentHiveJob.Job.Id
586                                                 orderby result.DateCreated ascending
587                                                 select result;
588      foreach (LightweightJob lightweightJob in childResults) {
589        jobCount++;
590        progress.Status = string.Format("Deserializing {0} of {1} jobs ({2} kb)...", jobCount, totalJobCount, allJobDatas[lightweightJob.Id].Data.Count() / 1024);
591        OptimizerJob optimizerJob = null;
592        try {
593          optimizerJob = PersistenceUtil.Deserialize<OptimizerJob>(allJobDatas[lightweightJob.Id].Data);
594        }
595        catch {
596          optimizerJob = null;
597        }
598        progress.ProgressValue = (double)jobCount / totalJobCount;
599        HiveJob childHiveJob = new HiveJob(optimizerJob, false);
600        parentHiveJob.AddChildHiveJob(childHiveJob);
601        childHiveJob.Job = allJobs[lightweightJob.Id];
602        allJobDatas.Remove(lightweightJob.Id); // reduce memory footprint
603        allJobs.Remove(lightweightJob.Id);
604        if (jobCount % 10 == 0) GC.Collect(); // this is needed or otherwise HL takes over the system when the number of jobs is high
605        LoadChildResults(service, childHiveJob, allResults, allJobs, allJobDatas, progress, totalJobCount, ref jobCount);
606      }
607    }
608
609    private OptimizerJob LoadOptimizerJob(Guid jobId) {
610      using (var service = ServiceLocator.Instance.GetService()) {
611        JobData jobData = service.Obj.GetJobData(jobId);
612        try {
613          return PersistenceUtil.Deserialize<OptimizerJob>(jobData.Data);
614        }
615        catch {
616          return null;
617        }
618      }
619    }
620  }
621}
Note: See TracBrowser for help on using the repository browser.