package com.github.pfichtner.jrunalyser.base.datasource; import static com.google.common.base.Predicates.compose; import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.not; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.transform; import java.io.IOException; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Set; import com.github.pfichtner.jrunalyser.base.data.SegmentationUnit; import com.github.pfichtner.jrunalyser.base.data.WayPoint; import com.github.pfichtner.jrunalyser.base.data.stat.Orderings; import com.github.pfichtner.jrunalyser.base.data.stat.Statistics; import com.github.pfichtner.jrunalyser.base.data.track.Id; import com.github.pfichtner.jrunalyser.base.data.track.Track; import com.github.pfichtner.jrunalyser.base.data.track.Tracks; import com.github.pfichtner.jrunalyser.base.data.track.comparator.TrackComparators; import com.github.pfichtner.jrunalyser.base.datasource.DatasourceFascadeEvent.Type; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Range; import com.google.common.collect.Sets; public class CachingDatasourceFascadeProxy extends AbstractDatasourceFascade { private final DatasourceFascade delegate; private Set<Id> trackIds; private final Function<Id, Track> loadTrack = new Function<Id, Track>() { @Override public Track apply(Id id) { try { return loadTrack(id); } catch (IOException e) { throw Throwables.propagate(e); } } }; private LoadingCache<Id, Track> tracks = CacheBuilder.newBuilder().build( new CacheLoader<Id, Track>() { @Override public Track load(Id id) throws Exception { return CachingDatasourceFascadeProxy.this.delegate .loadTrack(id); } }); private LoadingCache<SegmentationUnit, List<Id>> topTracksIds = CacheBuilder .newBuilder().build(new CacheLoader<SegmentationUnit, List<Id>>() { @Override public List<Id> load(SegmentationUnit segmentationUnit) throws Exception { return CachingDatasourceFascadeProxy.this.delegate .listTracks(segmentationUnit); } }); private LoadingCache<Id, LoadingCache<SegmentationUnit, Optional<Statistics>>> segmentData = CacheBuilder .newBuilder() .build(new CacheLoader<Id, LoadingCache<SegmentationUnit, Optional<Statistics>>>() { @Override public LoadingCache<SegmentationUnit, Optional<Statistics>> load( final Id id) throws Exception { CacheLoader<SegmentationUnit, Optional<Statistics>> loader = new CacheLoader<SegmentationUnit, Optional<Statistics>>() { @Override public Optional<Statistics> load( SegmentationUnit segmentationUnit) throws Exception { return CachingDatasourceFascadeProxy.this.delegate .loadBestSegment(id, segmentationUnit); } }; return CacheBuilder.newBuilder().build(loader); } }); private LoadingCache<Id, Set<Id>> similarTrackIds = CacheBuilder .newBuilder().build(new CacheLoader<Id, Set<Id>>() { @Override public Set<Id> load(Id id) throws Exception { return Sets .filter(getTrackIds(), com.google.common.base.Predicates .and(compose( similar(id), CachingDatasourceFascadeProxy.this.loadTrack), not(equalTo(id)))); } private Predicate<Track> similar(final Id id) throws IOException { final Track ref = loadTrack(id); return com.google.common.base.Predicates.compose( equalTo(Integer.valueOf(0)), new Function<Track, Integer>() { Comparator<Track> trackComparator = TrackComparators.byAttributes; @Override public Integer apply(Track track) { return Integer.valueOf(this.trackComparator .compare(ref, track)); } }); }; }); private LoadingCache<Id, Boolean> isAwayEqReturn = CacheBuilder .newBuilder().build(new CacheLoader<Id, Boolean>() { @Override public Boolean load(Id id) throws Exception { return Boolean .valueOf(CachingDatasourceFascadeProxy.this.delegate .isAwayEqReturn(id)); }; }); private Set<WayPoint> commonWaypoints; public CachingDatasourceFascadeProxy(DatasourceFascade datasourceFascade) { this.delegate = datasourceFascade; initCaches(); } public Set<Id> getTrackIds() throws IOException { return this.trackIds; } @Override public Iterable<Id> getTrackIds(Date start, Date end) throws IOException { Iterable<Track> tracks = transform(getTrackIds(), this.loadTrack); Iterable<Track> filtered = filter(tracks, fromTo(this, start, end)); return transform( Orderings.time.sortedCopy(filtered), com.github.pfichtner.jrunalyser.base.data.stat.Functions.Tracks.id); } public static Predicate<Track> fromTo(final DatasourceFascade dsf, final Date start, final Date end) { return new Predicate<Track>() { @Override public boolean apply(Track track) { return Range.open(start, end).contains( new Date(Tracks.getStartPoint(track).getTime() .longValue())); } }; } public Track loadTrack(Id id) throws IOException { return this.tracks.getUnchecked(id); } @Override public Iterable<Track> loadTracks(Iterable<Id> ids) throws IOException { return Iterables.transform(ids, this.loadTrack); } public Track addTrack(Track track) throws IOException { Track result = this.delegate.addTrack(track); initCaches(); fire(new DefaultDatasourceFascadeEvent(Type.ADDED, result)); return result; } @Override public Track removeTrack(Id id) throws IOException { Track result = this.delegate.removeTrack(id); initCaches(); fire(new DefaultDatasourceFascadeEvent(Type.REMOVED, result)); return result; } public List<Id> listTracks(SegmentationUnit segmentationUnit) throws IOException { return this.topTracksIds.getUnchecked(segmentationUnit); } @Override public Optional<Statistics> loadBestSegment(Id id, SegmentationUnit segmentationUnit) throws IOException { return this.segmentData.getUnchecked(id).getUnchecked(segmentationUnit); } // ------------------------------------------------------------- public Set<Id> getSimilarTracks(Id id) { return this.similarTrackIds.getUnchecked(id); }; @Override public boolean isAwayEqReturn(Id id) { return this.isAwayEqReturn.getUnchecked(id).booleanValue(); }; // ------------------------------------------------------------- public Set<WayPoint> getCommonWaypoints() { synchronized (this) { if (this.commonWaypoints == null) { this.commonWaypoints = this.delegate.getCommonWaypoints(); } } return this.commonWaypoints; }; // ------------------------------------------------------------- private void initCaches() { try { this.trackIds = ImmutableSet.copyOf(this.delegate.getTrackIds()); // this.tracks.invalidateAll(); this.topTracksIds.invalidateAll(); // this.segmentData.invalidateAll(); this.similarTrackIds.invalidateAll(); // this.isAwayEqReturn.invalidateAll(); } catch (IOException e) { throw Throwables.propagate(e); } } }