PeerCache.java

/* Copyright 2022-2025 Thales Alenia Space
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.frames;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;

/** Cache for frame transforms.
 * <p>
 * This class is thread-safe.
 * </p>
 * @author Luc Maisonobe
 * @since 13.1
 */
class PeerCache {

    /** Origin frame. */
    private final Frame origin;

    /** Cache for transforms with peer frame. */
    private volatile CachedTransformProvider cache;

    /** Lock for peer frame cache. */
    private final ReentrantReadWriteLock lock;

    /** Cache for transforms with peer frame. */
    private volatile Map<Field<? extends CalculusFieldElement<?>>, FieldCachedTransformProvider<?>> fieldCaches;

    /** create an instance not associated with any peer.
     * @param origin origin frame
     */
    PeerCache(final Frame origin) {
        this.origin      = origin;
        this.cache       = null;
        this.fieldCaches = null;
        this.lock        = new ReentrantReadWriteLock();
    }

    /** Associate a cache with a peer frame, caching transforms.
     * <p>
     * The cache is a LRU cache (Least Recently Used), so entries remain in
     * the cache if they are used frequently, and only older entries
     * that have not been accessed for a while will be expunged.
     * </p>
     * <p>
     * If a peer was already associated with this frame, it will be overridden.
     * </p>
     * <p>
     * Peering is unidirectional, i.e. if frameA is peered with frameB,
     * then frameB may be peered with another frameC or no frame at all.
     * This allows several frames to be peered with a pivot one (typically
     * Earth frame and many topocentric frames all peered with one inertial frame).
     * </p>
     * @param peer peer frame (if null, cache is cleared)
     * @param cacheSize number of transforms kept in the date-based cache
     */
    public void setPeerCaching(final Frame peer, final int cacheSize) {

        lock.writeLock().lock();
        try {

            if (peer == null) {
                // clear peering
                cache       = null;
                fieldCaches = null;
            }

            // caching for regular dates
            cache = createCache(peer, cacheSize);

            // caching for field dates
            fieldCaches = new ConcurrentHashMap<>();
        } finally {
            lock.writeLock().unlock();
        }

    }

    /** Get the peer associated to this frame.
     * @return peer associated with this frame, null if not peered at all
     */
    Frame getPeer() {
        lock.readLock().lock();
        try {
            return cache == null ? null : cache.getDestination();
        } finally {
            lock.readLock().unlock();
        }
    }

    /** Get the cached transform provider associated with this destination.
     * @param destination destination frame to which we want to transform vectors
     * @return cached transform provider, or null if destination is not the instance peer
     */
    CachedTransformProvider getCachedTransformProvider(final Frame destination) {
        lock.readLock().lock();
        try {
            if (cache == null || cache.getDestination() != destination) {
                return null;
            } else {
                return cache;
            }
        } finally {
            lock.readLock().unlock();
        }
    }

    /** Get the cached transform provider associated with this destination.
     * @param <T> the type of the field elements
     * @param destination destination frame to which we want to transform vectors
     * @param field field elements belong to
     * @return cached transform provider, or null if destination is not the instance peer
     */
    @SuppressWarnings("unchecked")
    <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T> getCachedTransformProvider(final Frame destination,
                                                                                                   final Field<T> field) {
        lock.readLock().lock();
        try {
            if (cache == null || cache.getDestination() != destination) {
                return null;
            } else {
                @SuppressWarnings("unchedked")
                final FieldCachedTransformProvider<T> tp =
                        (FieldCachedTransformProvider<T>) fieldCaches.computeIfAbsent(field,
                                                                                      f -> createCache(destination,
                                                                                                       cache.getCacheSize(),
                                                                                                       field));
                return tp;
            }
        } finally {
            lock.readLock().unlock();
        }
    }

    /** Create cache.
     * @param peer peer frame
     * @param cacheSize number of transforms kept in the date-based cache
     * @return built cache
     * @since 13.0.3
     */
    private CachedTransformProvider createCache(final Frame peer, final int cacheSize) {
        final Function<AbsoluteDate, Transform> fullGenerator =
                date -> origin.getTransformTo(peer,
                                              Transform.IDENTITY,
                                              frame -> frame.getTransformProvider().getTransform(date),
                                              (t1, t2) -> new Transform(date, t1, t2),
                                              Transform::getInverse);
        final Function<AbsoluteDate, KinematicTransform> kinematicGenerator =
                date -> origin.getTransformTo(peer,
                                              KinematicTransform.getIdentity(),
                                              frame -> frame.getTransformProvider().getTransform(date),
                                              (t1, t2) -> KinematicTransform.compose(date, t1, t2),
                                              KinematicTransform::getInverse);
        final Function<AbsoluteDate, StaticTransform> staticGenerator =
                date -> origin.getTransformTo(peer,
                                              StaticTransform.getIdentity(),
                                              frame -> frame.getTransformProvider().getTransform(date),
                                              (t1, t2) -> StaticTransform.compose(date, t1, t2),
                                              StaticTransform::getInverse);
        return new CachedTransformProvider(origin, peer,
                                           fullGenerator, kinematicGenerator, staticGenerator,
                                           cacheSize);
    }

    /** Create field cache.
     * @param <T> type of the field elements
     * @param peer peer frame
     * @param cacheSize number of transforms kept in the date-based cache
     * @param field field elements belong to
     * @return built cache
     * @since 13.0.3
     */
    private <T extends CalculusFieldElement<T>> FieldCachedTransformProvider<T>
        createCache(final Frame peer, final int cacheSize, final Field<T> field) {
        final Function<FieldAbsoluteDate<T>, FieldTransform<T>> fullGenerator =
                d -> origin.getTransformTo(peer,
                                           FieldTransform.getIdentity(field),
                                           frame -> frame.getTransformProvider().getTransform(d),
                                           (FieldTransform<T> t1, FieldTransform<T> t2) -> new FieldTransform<>(d, t1, t2),
                                           FieldTransform::getInverse);
        final Function<FieldAbsoluteDate<T>, FieldKinematicTransform<T>> kinematicGenerator =
                d -> origin.getTransformTo(peer,
                                           FieldKinematicTransform.getIdentity(field),
                                           frame -> frame.getTransformProvider().getTransform(d),
                                           (t1, t2) -> FieldKinematicTransform.compose(d, t1, t2),
                                           FieldKinematicTransform::getInverse);
        final Function<FieldAbsoluteDate<T>, FieldStaticTransform<T>> staticGenerator =
                d -> origin.getTransformTo(peer,
                                           FieldStaticTransform.getIdentity(field),
                                           frame -> frame.getTransformProvider().getTransform(d),
                                           (t1, t2) -> FieldStaticTransform.compose(d, t1, t2),
                                           FieldStaticTransform::getInverse);
        return new FieldCachedTransformProvider<>(origin, peer,
                                                  fullGenerator, kinematicGenerator, staticGenerator,
                                                  cacheSize);
    }

}