javaextensions/location/landmarks/src/clapilandmarkstore.cpp
branchRCL_3
changeset 19 04becd199f91
child 23 98ccebc37403
equal deleted inserted replaced
16:f5050f1da672 19:04becd199f91
       
     1 /*
       
     2 * Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description:  Implements native landmark store functionality
       
    15  *
       
    16 */
       
    17 
       
    18 
       
    19 // INTERNAL INCLUDES
       
    20 #include    "clapilandmarkstore.h"
       
    21 #include    "mlapicategorymanager.h"
       
    22 #include    "mlapilmdatabaseeventnotifier.h"
       
    23 #include    "clapilandmarksearchfactory.h"
       
    24 #include    "clapilandmark.h"
       
    25 #include    "tlapisearchcriteria.h"
       
    26 #include    "cleanupresetanddestroy.h"
       
    27 #include    "lapipanics.h"
       
    28 #include    "logger.h"
       
    29 
       
    30 // EXTERNAL INCLUDES
       
    31 #include    <EPos_CPosLandmark.h>
       
    32 #include    <EPos_CPosLandmarkDatabase.h>
       
    33 #include    <EPos_CPosLmItemIterator.h>
       
    34 
       
    35 // UNNAMED LOCAL NAMESPACE
       
    36 namespace
       
    37 {
       
    38 // Minimum usage of the native landmark database. If the usage drops
       
    39 // below this level, the database will be compacted to avoid that the
       
    40 // Landmark Server would do that which eventually locks the database
       
    41 const TReal KLAPIMinCompactUsage = 0.7;
       
    42 }
       
    43 
       
    44 // ---------------------------------------------------------------------------
       
    45 // CLAPILandmarkStore::CLAPILandmarkStore
       
    46 // ---------------------------------------------------------------------------
       
    47 //
       
    48 CLAPILandmarkStore::~CLAPILandmarkStore()
       
    49 {
       
    50     JELOG2(EJavaLocation);
       
    51     // Closes the database and releases all resources
       
    52     Close();
       
    53     delete iStoreUri;
       
    54 }
       
    55 
       
    56 // ---------------------------------------------------------------------------
       
    57 // CLAPILandmarkStore::ConstructL
       
    58 // (other items were commented in a header
       
    59 // ---------------------------------------------------------------------------
       
    60 //
       
    61 void CLAPILandmarkStore::ConstructL()
       
    62 {
       
    63     JELOG2(EJavaLocation);
       
    64     // Get the name of the landmark store
       
    65     iStoreUri = iLandmarkDatabase->DatabaseUriLC();
       
    66     CleanupStack::Pop(iStoreUri);
       
    67     // Create landmark search factory for filtering landmarks
       
    68     iSearchFactory = CLAPILandmarkSearchFactory::NewL(*iLandmarkDatabase);
       
    69 }
       
    70 
       
    71 // ---------------------------------------------------------------------------
       
    72 // CLAPILandmarkStore::NewL
       
    73 // ---------------------------------------------------------------------------
       
    74 //
       
    75 CLAPILandmarkStore* CLAPILandmarkStore::NewL(const TCtorParams& aParams)
       
    76 {
       
    77     JELOG2(EJavaLocation);
       
    78     CLAPILandmarkStore* self = CLAPILandmarkStore::NewLC(aParams);
       
    79     CleanupStack::Pop(self);
       
    80     return self;
       
    81 }
       
    82 
       
    83 // ---------------------------------------------------------------------------
       
    84 // CLAPILandmarkStore::NewLC
       
    85 // ---------------------------------------------------------------------------
       
    86 //
       
    87 CLAPILandmarkStore* CLAPILandmarkStore::NewLC(const TCtorParams& aParams)
       
    88 {
       
    89     JELOG2(EJavaLocation);
       
    90     CLAPILandmarkStore* self = new(ELeave) CLAPILandmarkStore(aParams);
       
    91     CleanupStack::PushL(self);
       
    92     self->ConstructL();
       
    93     return self;
       
    94 }
       
    95 
       
    96 // ---------------------------------------------------------------------------
       
    97 // CLAPILandmarkStore::ReadFullLandmarkL
       
    98 // ---------------------------------------------------------------------------
       
    99 //
       
   100 void CLAPILandmarkStore::ReadFullLandmarkL(CLAPILandmark& aLandmark)
       
   101 {
       
   102     JELOG2(EJavaLocation);
       
   103     // Check if the database has been closed
       
   104     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   105 
       
   106     TLAPIItemId id = aLandmark.Id();
       
   107     // Check that the landmark id is valid
       
   108     __ASSERT_DEBUG(id != KLAPINullItemId, LAPIError::Panic(
       
   109                        ELAPIPanicInvalidLandmarkId));
       
   110 
       
   111     // Read the full landmark from the database and add it to the item
       
   112     // Note that this overwrites the native entry in the item. The function
       
   113     // leaves with KErrNotFound if there is no such item in the database
       
   114     CPosLandmark* fullLandmark = iLandmarkDatabase->ReadLandmarkLC(id);
       
   115     aLandmark.SetPosLandmark(fullLandmark);
       
   116     // The ownership of fullLandmark is transferred to landmark object
       
   117     CleanupStack::Pop(fullLandmark);
       
   118 }
       
   119 
       
   120 // ---------------------------------------------------------------------------
       
   121 // CLAPILandmarkStore::ReadLandmarkAttributesL
       
   122 // ---------------------------------------------------------------------------
       
   123 //
       
   124 void CLAPILandmarkStore::ReadLandmarkAttributesL(CLAPILandmark& aLandmark,
       
   125         const TUint aAttributes, const RArray<TUint>* aAddressInfos)
       
   126 {
       
   127     JELOG2(EJavaLocation);
       
   128     // Check if the database has been closed
       
   129     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   130 
       
   131     // Create new set of partial read parameters. The old parameters in the
       
   132     // store will not be used since those can have additional fields which
       
   133     // are not needed when reading the requested landmark data
       
   134     CPosLmPartialReadParameters* params = CPosLmPartialReadParameters::NewLC();
       
   135     params->SetRequestedAttributes(aAttributes);
       
   136     // Set requested address info fields if specified
       
   137     if (aAddressInfos)
       
   138     {
       
   139         params->SetRequestedPositionFields(*aAddressInfos);
       
   140     }
       
   141 
       
   142     TLAPIItemId id = aLandmark.Id();
       
   143     // Check that the landmark id is valid
       
   144     __ASSERT_DEBUG(id != KLAPINullItemId, LAPIError::Panic(
       
   145                        ELAPIPanicInvalidLandmarkId));
       
   146 
       
   147     // Read the landmark using the partial read parameters
       
   148     iLandmarkDatabase->SetPartialReadParametersL(*params);
       
   149     CleanupStack::PopAndDestroy(params);
       
   150     // Read the partial landmark from the landmark database
       
   151     CPosLandmark* newLm = iLandmarkDatabase->ReadPartialLandmarkLC(id);
       
   152     // The ownership is transferred to landmark object
       
   153     aLandmark.SetPosLandmark(newLm);
       
   154     CleanupStack::Pop(newLm);
       
   155 }
       
   156 
       
   157 // ---------------------------------------------------------------------------
       
   158 // CLAPILandmarkStore::CategoryManager
       
   159 // ---------------------------------------------------------------------------
       
   160 //
       
   161 MLAPICategoryManager* CLAPILandmarkStore::CategoryManagerL() const
       
   162 {
       
   163     JELOG2(EJavaLocation);
       
   164     // Check if the database has been closed
       
   165     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   166     return iCategoryManager;
       
   167 }
       
   168 
       
   169 // ---------------------------------------------------------------------------
       
   170 // CLAPILandmarkStore::LandmarkDisposed
       
   171 // ---------------------------------------------------------------------------
       
   172 //
       
   173 void CLAPILandmarkStore::LandmarkDisposed(CLAPILandmark& aLandmark)
       
   174 {
       
   175     JELOG2(EJavaLocation);
       
   176     TInt landmarkIndex = iLandmarks.Find(&aLandmark);
       
   177     // Remove the specified landmark if it was found from this store
       
   178     if (landmarkIndex != KErrNotFound)
       
   179     {
       
   180         iLandmarks.Remove(landmarkIndex);
       
   181     }
       
   182 }
       
   183 
       
   184 // ---------------------------------------------------------------------------
       
   185 // CLAPILandmarkStore::StoreUri
       
   186 // ---------------------------------------------------------------------------
       
   187 //
       
   188 const TDesC& CLAPILandmarkStore::StoreUri() const
       
   189 {
       
   190     JELOG2(EJavaLocation);
       
   191     return *iStoreUri;
       
   192 }
       
   193 
       
   194 // ---------------------------------------------------------------------------
       
   195 // CLAPILandmarkStore::LandmarksL
       
   196 // ---------------------------------------------------------------------------
       
   197 //
       
   198 CArrayPtr<CLAPILandmark>* CLAPILandmarkStore::LandmarksL(
       
   199     const TUint aAttributes, const TLAPISearchCriteria* aSearchCriteria)
       
   200 {
       
   201     JELOG2(EJavaLocation);
       
   202     // Check if the database has been closed
       
   203     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   204 
       
   205     CPosLmItemIterator* iter = iSearchFactory->CreateIteratorL(aSearchCriteria);
       
   206     CleanupStack::PushL(iter);
       
   207     // Do not initialize anything if there is nothing to initialize
       
   208     TInt itemCount = iter->NumOfItemsL();
       
   209     // Ensure that the granularity is always more than zero. Flat array
       
   210     // is used because the buffer will not be increased after it has
       
   211     // been initialized
       
   212     CArrayPtr<CLAPILandmark>* landmarks = new(ELeave) CArrayPtrFlat<
       
   213     CLAPILandmark> (itemCount + 1);
       
   214     CleanupStack::PushL(landmarks);
       
   215     // Put the array to cleanup stack for reset and destroy since the
       
   216     // array owns the objects and those must also be deleted
       
   217     CleanupResetAndDestroyPushL(*landmarks);
       
   218 
       
   219     if (itemCount > 0)
       
   220     {
       
   221         // Get item identifiers only from the iterator
       
   222         RArray<TPosLmItemId> ids;
       
   223         CleanupClosePushL(ids);
       
   224         iter->GetItemIdsL(ids, 0, iter->NumOfItemsL());
       
   225 
       
   226         // Set partial read paramaters to the native database.
       
   227         CPosLmPartialReadParameters* params =
       
   228             CPosLmPartialReadParameters::NewLC();
       
   229         params->SetRequestedAttributes(aAttributes);
       
   230         iLandmarkDatabase->SetPartialReadParametersL(*params);
       
   231         CleanupStack::PopAndDestroy(params);
       
   232         // Prepare partial landmarks. This reads the specified landmark id array
       
   233         // from the native database and initializes the requested attributes to
       
   234         // the previously read landmark objects.
       
   235         CPosLmOperation* op = iLandmarkDatabase->PreparePartialLandmarksL(ids);
       
   236         // ids are on top of the stack and are not needed anymore
       
   237         CleanupStack::PopAndDestroy(&ids);
       
   238         CleanupStack::PushL(op);
       
   239         // Execute the operation. This needs to be done before the partial
       
   240         // landmarks can be taken from the landmark database. The time of the
       
   241         // operation depends on the partial read parameters set above
       
   242         op->ExecuteL();
       
   243 
       
   244         // The operation has been completed and prepared landmarks are available
       
   245         CArrayPtr<CPosLandmark>* preparedLandmarks =
       
   246             iLandmarkDatabase->TakePreparedPartialLandmarksL(op);
       
   247         CleanupStack::PopAndDestroy(op);
       
   248         // Make this leave-safe since the prepared array cannot be put to
       
   249         // cleanup stack safely because it needs two leaving operations
       
   250         TRAPD(error, HandlePreparedLandmarksL(*preparedLandmarks, *landmarks));
       
   251         // Cleanup the prepared landmarks
       
   252         preparedLandmarks->ResetAndDestroy();
       
   253         delete preparedLandmarks;
       
   254         // Now it is safe to leave
       
   255         User::LeaveIfError(error);
       
   256     }
       
   257 
       
   258     CleanupStack::Pop(2, landmarks); // The object and ResetAndDestroy
       
   259     CleanupStack::PopAndDestroy(iter);
       
   260     return landmarks;
       
   261 }
       
   262 
       
   263 // ---------------------------------------------------------------------------
       
   264 // CLAPILandmarkStore::AddLandmarkL
       
   265 // ---------------------------------------------------------------------------
       
   266 //
       
   267 void CLAPILandmarkStore::AddLandmarkL(CLAPILandmark& aLandmark)
       
   268 {
       
   269     JELOG2(EJavaLocation);
       
   270     // Check if the database has been closed
       
   271     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   272     TUint id = aLandmark.Id();
       
   273     LOG1(EJavaLocation, EInfo, "CLAPILandmarkStore::AddLandmarkL - id %d", id);
       
   274 
       
   275     CPosLandmark* landmark(NULL);
       
   276     TRAPD(err, landmark = iLandmarkDatabase->ReadLandmarkLC(id);
       
   277           CleanupStack::PopAndDestroy(landmark));    // We dont need the landmark
       
   278 
       
   279     // The landmark was not found from the database. Add a new landmark
       
   280     if (err == KErrNotFound)
       
   281     {
       
   282         LOG(EJavaLocation, EInfo,
       
   283             "CLAPILandmarkStore::AddLandmarkL - adding new landmark");
       
   284         // Prepare the landmark for saving. This will guarantee that partially
       
   285         // read landmarks will be up to date when those are added to the native
       
   286         // database. All data are not necessary available in the item if it has
       
   287         // been partially loaded from the native database
       
   288         aLandmark.PrepareForSaveL();
       
   289 
       
   290         // Add new landmark to the database. Note that the added landmark
       
   291         // should not initially belong to any categories
       
   292         CPosLandmark& landmark = aLandmark.PosLandmark();
       
   293         landmark.RemoveLandmarkAttributes(CPosLandmark::ECategoryInfo);
       
   294         iLandmarkDatabase->AddLandmarkL(landmark);
       
   295         err = iLandmarks.Append(&aLandmark);
       
   296         if (err != KErrNone)
       
   297         {
       
   298             // Remove the added landmark to keep the store in sync. Note
       
   299             // that this call leaves only if the database hasn't been initialized
       
   300             iLandmarkDatabase->RemoveLandmarkL(id);
       
   301             User::Leave(err);
       
   302         }
       
   303 
       
   304         // Mark that this landmark has been associated to a landmark store
       
   305         aLandmark.AssociateToStore(this);
       
   306         err = KErrNone;
       
   307         // Compact the database if it is necessary
       
   308         CompactIfNeededL();
       
   309     }
       
   310     User::LeaveIfError(err);
       
   311 }
       
   312 
       
   313 // ---------------------------------------------------------------------------
       
   314 // CLAPILandmarkStore::UpdateLandmarkL
       
   315 // ---------------------------------------------------------------------------
       
   316 //
       
   317 void CLAPILandmarkStore::UpdateLandmarkL(CLAPILandmark& aLandmark)
       
   318 {
       
   319     JELOG2(EJavaLocation);
       
   320     // Check if the database has been closed
       
   321     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   322     LOG1(EJavaLocation, EInfo,
       
   323          "CLAPILandmarkStore::UpdateLandmarkL - id %d",
       
   324          aLandmark.Id());
       
   325     // Refresh all landmarks before updating this landmark. This needs to be
       
   326     // done because it is expected that all Java side landmark objects will
       
   327     // not be updated if one specific landmark is updated
       
   328     RefreshLandmarksL(aLandmark.Id());
       
   329 
       
   330     // Prepare the landmark for saving. This will guarantee that partially
       
   331     // read landmarks will be up to date when those are added to the native
       
   332     // database. All data are not necessary available in the item if it has
       
   333     // been partially loaded from the native database
       
   334     aLandmark.PrepareForSaveL();
       
   335     // Update the existing landmark into the database. The existing data
       
   336     // will be overwritten
       
   337     iLandmarkDatabase->UpdateLandmarkL(aLandmark.PosLandmark());
       
   338     // Compact the database if it is necessary
       
   339     CompactIfNeededL();
       
   340 }
       
   341 
       
   342 // ---------------------------------------------------------------------------
       
   343 // CLAPILandmarkStore::DeleteLandmarkL
       
   344 // ---------------------------------------------------------------------------
       
   345 //
       
   346 void CLAPILandmarkStore::RemoveLandmarkL(CLAPILandmark& aLandmark)
       
   347 {
       
   348     JELOG2(EJavaLocation);
       
   349     // Check if the database has been closed
       
   350     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   351     TUint32 id = aLandmark.Id();
       
   352     LOG1(EJavaLocation, EInfo,
       
   353          "CLAPILandmarkStore::RemoveLandmarkL - id %d", id);
       
   354 
       
   355     // Refresh all related landmarks which match for the given landmark's id
       
   356     TRAPD(err, RefreshLandmarksL(id, ETrue));
       
   357     // Do not leave if the landmark was already removed from the store
       
   358     if (err == KErrNone)
       
   359     {
       
   360         LOG(EJavaLocation, EInfo,
       
   361             "CLAPILandmarkStore::RemoveLandmarkL - removing from database");
       
   362         // Remove the landmark from the native database.
       
   363         iLandmarkDatabase->RemoveLandmarkL(id);
       
   364         // Compact the database if it is necessary
       
   365         CompactIfNeededL();
       
   366     }
       
   367     __ASSERT_ALWAYS(err == KErrNone || err == KErrNotFound,
       
   368                     User::Leave(err));
       
   369     // Not associated anymore. Remove from list
       
   370     aLandmark.AssociateToStore(NULL);
       
   371 }
       
   372 
       
   373 // ---------------------------------------------------------------------------
       
   374 // CLAPILandmarkStore::CompactIfNeededL
       
   375 // ---------------------------------------------------------------------------
       
   376 //
       
   377 void CLAPILandmarkStore::CompactIfNeededL()
       
   378 {
       
   379     JELOG2(EJavaLocation);
       
   380     // Check if the database has been closed
       
   381     __ASSERT_ALWAYS(iLandmarkDatabase, User::Leave(KErrSessionClosed));
       
   382 
       
   383     CPosLandmarkDatabase::TSize databaseSize =
       
   384         iLandmarkDatabase->SizeL();
       
   385     // Execute synchronised compact operation if the the usage is below
       
   386     // the minimum compact limit. This prevents that Landmarks Server will
       
   387     // not do this operation and lock the database
       
   388     if (databaseSize.iUsage < KLAPIMinCompactUsage)
       
   389     {
       
   390         ExecuteAndDeleteLD(iLandmarkDatabase->CompactL());
       
   391     }
       
   392 }
       
   393 
       
   394 // ---------------------------------------------------------------------------
       
   395 // CLAPILandmarkStore::Close
       
   396 // ---------------------------------------------------------------------------
       
   397 //
       
   398 void CLAPILandmarkStore::Close()
       
   399 {
       
   400     JELOG2(EJavaLocation);
       
   401     TInt count = iLandmarks.Count();
       
   402     for (TInt i = 0; i < count; i++)
       
   403     {
       
   404         // The store has been closed. This indicates that there are no
       
   405         // landmark objects in the java side and the landmark store has
       
   406         // gone out of scope
       
   407         iLandmarks[i]->StoreClosed();
       
   408     }
       
   409 
       
   410     // The landmark objects are not owned by the store
       
   411     iLandmarks.Close();
       
   412 
       
   413     delete iCategoryManager;
       
   414     iCategoryManager = NULL;
       
   415     delete iSearchFactory;
       
   416     iSearchFactory = NULL;
       
   417     delete iEventNotifier;
       
   418     iEventNotifier = NULL;
       
   419     delete iLandmarkDatabase;
       
   420     iLandmarkDatabase = NULL;
       
   421 }
       
   422 
       
   423 // ---------------------------------------------------------------------------
       
   424 // CLAPILandmarkStore::HandlePreparedLandmarksL
       
   425 // ---------------------------------------------------------------------------
       
   426 //
       
   427 void CLAPILandmarkStore::HandlePreparedLandmarksL(CArrayPtr<
       
   428         CPosLandmark>& aSrcArray, CArrayPtr<CLAPILandmark>& aDestArray)
       
   429 {
       
   430     JELOG2(EJavaLocation);
       
   431     aDestArray.Reset();
       
   432 
       
   433     // Create Location API landmark objects from each native landmark
       
   434     TInt lmCount = aSrcArray.Count();
       
   435     while (lmCount-- > 0)
       
   436     {
       
   437         // Handle items in accending order from the start of the array
       
   438         CPosLandmark* landmark = aSrcArray.At(0);
       
   439         // The new landmark takes the ownership of the CPosLandmark object
       
   440         // Associate the new landmark to this landmark store
       
   441         CLAPILandmark::TCtorParams params;
       
   442         params.iLandmark = landmark;
       
   443         params.iLandmarkStore = this;
       
   444 
       
   445         CLAPILandmark* newLandmark = CLAPILandmark::NewLC(params);
       
   446         // Remove the landmark from the prepared array since newLandmark
       
   447         // takes the ownership of the returned value
       
   448         aSrcArray.Delete(0);
       
   449         aDestArray.AppendL(newLandmark);
       
   450         CleanupStack::Pop(newLandmark);
       
   451         iLandmarks.AppendL(newLandmark);
       
   452     }
       
   453 }
       
   454 
       
   455 // ---------------------------------------------------------------------------
       
   456 // CLAPILandmarkStore::RefreshLandmarksL
       
   457 // ---------------------------------------------------------------------------
       
   458 //
       
   459 void CLAPILandmarkStore::RefreshLandmarksL(TLAPIItemId aLandmarkId,
       
   460         TBool aRemoveFromStore)
       
   461 {
       
   462     JELOG2(EJavaLocation);
       
   463     TInt landmarksCount = iLandmarks.Count();
       
   464     CPosLandmark* posLm =
       
   465         iLandmarkDatabase->ReadLandmarkLC(aLandmarkId);
       
   466 
       
   467     // Refresh all landmarks which match for the given id. Iterate backwards
       
   468     // if the landmarks are removed from the store
       
   469     while (landmarksCount-- > 0)
       
   470     {
       
   471         CLAPILandmark* landmark = iLandmarks[landmarksCount];
       
   472         if (landmark->Id() == aLandmarkId)
       
   473         {
       
   474             CPosLandmark* copyLandmark = CPosLandmark::NewLC(*posLm);
       
   475             landmark->SetPosLandmark(copyLandmark);
       
   476             // The landmark takes the ownership of copyLandmark
       
   477             CleanupStack::Pop(copyLandmark);
       
   478 
       
   479             // Remove the landmark from this store if requested. This is
       
   480             // usually done because removed landmark must update all its
       
   481             // duplicates since those landmarks must not be updated
       
   482             if (aRemoveFromStore)
       
   483             {
       
   484                 // The landmark disposes itself from this store
       
   485                 landmark->AssociateToStore(NULL);
       
   486             }
       
   487         }
       
   488     }
       
   489 
       
   490     CleanupStack::PopAndDestroy(posLm);
       
   491 }
       
   492 
       
   493 // ---------------------------------------------------------------------------
       
   494 // CLAPILandmarkStore::CLAPILandmarkStore
       
   495 // ---------------------------------------------------------------------------
       
   496 //
       
   497 CLAPILandmarkStore::CLAPILandmarkStore(const TCtorParams& aParams) :
       
   498         iLandmarkDatabase(aParams.iLandmarkDatabase), iCategoryManager(
       
   499             aParams.iCategoryManager), iEventNotifier(
       
   500                 aParams.iEventNotifier)
       
   501 {
       
   502     JELOG2(EJavaLocation);
       
   503 }
       
   504 
       
   505 // End of file