Ignore:
Timestamp:
2017-05-15T14:14:40+02:00 (3 years ago)
Author:
michael2402
Message:

See #14120: Don't make gpx tracks depend on the isChanged method, use a listener based approach instead.

Location:
trunk/src/org/openstreetmap/josm/data/gpx
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java

    r11747 r12156  
    33
    44import java.io.File;
     5import java.text.MessageFormat;
     6import java.util.AbstractCollection;
     7import java.util.ArrayList;
    58import java.util.Collection;
    69import java.util.Collections;
     
    912import java.util.HashSet;
    1013import java.util.Iterator;
    11 import java.util.LinkedList;
    1214import java.util.Map;
    1315import java.util.NoSuchElementException;
    1416import java.util.Set;
     17import java.util.stream.Stream;
    1518
    1619import org.openstreetmap.josm.Main;
     
    1922import org.openstreetmap.josm.data.DataSource;
    2023import org.openstreetmap.josm.data.coor.EastNorth;
     24import org.openstreetmap.josm.data.gpx.GpxTrack.GpxTrackChangeListener;
     25import org.openstreetmap.josm.tools.ListenerList;
    2126
    2227/**
     
    3540    public String creator;
    3641
    37     /** Tracks */
    38     public final Collection<GpxTrack> tracks = new LinkedList<>();
    39     /** Routes */
    40     public final Collection<GpxRoute> routes = new LinkedList<>();
    41     /** Waypoints */
    42     public final Collection<WayPoint> waypoints = new LinkedList<>();
     42    private ArrayList<GpxTrack> privateTracks = new ArrayList<>();
     43    private ArrayList<GpxRoute> privateRoutes = new ArrayList<>();
     44    private ArrayList<WayPoint> privateWaypoints = new ArrayList<>();
     45    private final GpxTrackChangeListener proxy = e -> fireInvalidate();
     46
     47    /**
     48     * Tracks. Access is discouraged, use {@link #getTracks()} to read.
     49     * @see #getTracks()
     50     */
     51    public final Collection<GpxTrack> tracks = new ListeningCollection<GpxTrack>(privateTracks, this::fireInvalidate) {
     52
     53        @Override
     54        protected void removed(GpxTrack cursor) {
     55            cursor.removeListener(proxy);
     56            super.removed(cursor);
     57        }
     58
     59        @Override
     60        protected void added(GpxTrack cursor) {
     61            super.added(cursor);
     62            cursor.addListener(proxy);
     63        }
     64    };
     65
     66    /**
     67     * Routes. Access is discouraged, use {@link #getTracks()} to read.
     68     * @see #getRoutes()
     69     */
     70    public final Collection<GpxRoute> routes = new ListeningCollection<>(privateRoutes, this::fireInvalidate);
     71
     72    /**
     73     * Waypoints. Access is discouraged, use {@link #getTracks()} to read.
     74     * @see #getWaypoints()
     75     */
     76    public final Collection<WayPoint> waypoints = new ListeningCollection<>(privateWaypoints, this::fireInvalidate);
    4377
    4478    /**
     
    4983     */
    5084    public final Set<DataSource> dataSources = new HashSet<>();
     85
     86    private final ListenerList<GpxDataChangeListener> listeners = ListenerList.create();
    5187
    5288    /**
     
    72108            }
    73109        }
    74         tracks.addAll(other.tracks);
    75         routes.addAll(other.routes);
    76         waypoints.addAll(other.waypoints);
     110        privateTracks.addAll(other.getTracks());
     111        privateRoutes.addAll(other.getRoutes());
     112        privateWaypoints.addAll(other.getWaypoints());
    77113        dataSources.addAll(other.dataSources);
     114        fireInvalidate();
     115    }
     116
     117    /**
     118     * Get all tracks contained in this data set.
     119     * @return The tracks.
     120     */
     121    public Collection<GpxTrack> getTracks() {
     122        return Collections.unmodifiableCollection(privateTracks);
     123    }
     124
     125    /**
     126     * Add a new track
     127     * @param track The new track
     128     * @since 12156
     129     */
     130    public void addTrack(GpxTrack track) {
     131        if (privateTracks.contains(track)) {
     132            throw new IllegalArgumentException(MessageFormat.format("The track was already added to this data: {0}", track));
     133        }
     134        privateTracks.add(track);
     135        track.addListener(proxy);
     136        fireInvalidate();
     137    }
     138
     139    /**
     140     * Remove a track
     141     * @param track The old track
     142     * @since 12156
     143     */
     144    public void removeTrack(GpxTrack track) {
     145        if (!privateTracks.remove(track)) {
     146            throw new IllegalArgumentException(MessageFormat.format("The track was not in this data: {0}", track));
     147        }
     148        track.removeListener(proxy);
     149        fireInvalidate();
     150    }
     151
     152    /**
     153     * Gets the list of all routes defined in this data set.
     154     * @return The routes
     155     * @since 12156
     156     */
     157    public Collection<GpxRoute> getRoutes() {
     158        return Collections.unmodifiableCollection(privateRoutes);
     159    }
     160
     161    /**
     162     * Add a new route
     163     * @param route The new route
     164     * @since 12156
     165     */
     166    public void addRoute(GpxRoute route) {
     167        if (privateRoutes.contains(route)) {
     168            throw new IllegalArgumentException(MessageFormat.format("The route was already added to this data: {0}", route));
     169        }
     170        privateRoutes.add(route);
     171        fireInvalidate();
     172    }
     173
     174    /**
     175     * Remove a route
     176     * @param route The old route
     177     * @since 12156
     178     */
     179    public void removeRoute(GpxRoute route) {
     180        if (!privateRoutes.remove(route)) {
     181            throw new IllegalArgumentException(MessageFormat.format("The route was not in this data: {0}", route));
     182        }
     183        fireInvalidate();
     184    }
     185
     186    /**
     187     * Gets a list of all way points in this data set.
     188     * @return The way points.
     189     * @since 12156
     190     */
     191    public Collection<WayPoint> getWaypoints() {
     192        return Collections.unmodifiableCollection(privateWaypoints);
     193    }
     194
     195    /**
     196     * Add a new waypoint
     197     * @param waypoint The new waypoint
     198     * @since 12156
     199     */
     200    public void addWaypoint(WayPoint waypoint) {
     201        if (privateWaypoints.contains(waypoint)) {
     202            throw new IllegalArgumentException(MessageFormat.format("The route was already added to this data: {0}", waypoint));
     203        }
     204        privateWaypoints.add(waypoint);
     205        fireInvalidate();
     206    }
     207
     208    /**
     209     * Remove a waypoint
     210     * @param waypoint The old waypoint
     211     * @since 12156
     212     */
     213    public void removeWaypoint(WayPoint waypoint) {
     214        if (!privateWaypoints.remove(waypoint)) {
     215            throw new IllegalArgumentException(MessageFormat.format("The route was not in this data: {0}", waypoint));
     216        }
     217        fireInvalidate();
    78218    }
    79219
     
    83223     */
    84224    public boolean hasTrackPoints() {
    85         for (GpxTrack trk : tracks) {
    86             for (GpxTrackSegment trkseg : trk.getSegments()) {
    87                 if (!trkseg.getWayPoints().isEmpty())
    88                     return true;
    89             }
    90         }
    91         return false;
     225        return getTrackPoints().findAny().isPresent();
     226    }
     227
     228    /**
     229     * Gets a stream of all track points in the segments of the tracks of this data.
     230     * @return The stream
     231     * @see #getTracks()
     232     * @see GpxTrack#getSegments()
     233     * @see GpxTrackSegment#getWayPoints()
     234     * @since 12156
     235     */
     236    public Stream<WayPoint> getTrackPoints() {
     237        return getTracks().stream().flatMap(trk -> trk.getSegments().stream()).flatMap(trkseg -> trkseg.getWayPoints().stream());
    92238    }
    93239
     
    97243     */
    98244    public boolean hasRoutePoints() {
    99         for (GpxRoute rte : routes) {
    100             if (!rte.routePoints.isEmpty())
    101                 return true;
    102         }
    103         return false;
     245        return getRoutes().stream().anyMatch(rte -> !rte.routePoints.isEmpty());
    104246    }
    105247
     
    144286    public Bounds recalculateBounds() {
    145287        Bounds bounds = null;
    146         for (WayPoint wpt : waypoints) {
     288        for (WayPoint wpt : getWaypoints()) {
    147289            if (bounds == null) {
    148290                bounds = new Bounds(wpt.getCoor());
     
    151293            }
    152294        }
    153         for (GpxRoute rte : routes) {
     295        for (GpxRoute rte : getRoutes()) {
    154296            for (WayPoint wpt : rte.routePoints) {
    155297                if (bounds == null) {
     
    160302            }
    161303        }
    162         for (GpxTrack trk : tracks) {
     304        for (GpxTrack trk : getTracks()) {
    163305            Bounds trkBounds = trk.getBounds();
    164306            if (trkBounds != null) {
     
    178320     */
    179321    public double length() {
    180         double result = 0.0; // in meters
    181 
    182         for (GpxTrack trk : tracks) {
    183             result += trk.length();
    184         }
    185 
    186         return result;
     322        return getTracks().stream().mapToDouble(GpxTrack::length).sum();
    187323    }
    188324
     
    261397        double py = p.north();
    262398        double rx = 0.0, ry = 0.0, sx, sy, x, y;
    263         if (tracks == null)
    264             return null;
    265         for (GpxTrack track : tracks) {
     399        for (GpxTrack track : getTracks()) {
    266400            for (GpxTrackSegment seg : track.getSegments()) {
    267401                WayPoint r = null;
     
    352486     */
    353487    public void resetEastNorthCache() {
    354         if (waypoints != null) {
    355             for (WayPoint wp : waypoints) {
     488        getWaypoints().forEach(WayPoint::invalidateEastNorthCache);
     489        getTrackPoints().forEach(WayPoint::invalidateEastNorthCache);
     490        for (GpxRoute route: getRoutes()) {
     491            if (route.routePoints == null) {
     492                continue;
     493            }
     494            for (WayPoint wp: route.routePoints) {
    356495                wp.invalidateEastNorthCache();
    357             }
    358         }
    359         if (tracks != null) {
    360             for (GpxTrack track: tracks) {
    361                 for (GpxTrackSegment segment: track.getSegments()) {
    362                     for (WayPoint wp: segment.getWayPoints()) {
    363                         wp.invalidateEastNorthCache();
    364                     }
    365                 }
    366             }
    367         }
    368         if (routes != null) {
    369             for (GpxRoute route: routes) {
    370                 if (route.routePoints == null) {
    371                     continue;
    372                 }
    373                 for (WayPoint wp: route.routePoints) {
    374                     wp.invalidateEastNorthCache();
    375                 }
    376496            }
    377497        }
     
    498618        return true;
    499619    }
     620
     621    /**
     622     * Adds a listener that gets called whenever the data changed.
     623     * @param listener The listener
     624     * @since 12156
     625     */
     626    public void addChangeListener(GpxDataChangeListener listener) {
     627        listeners.addListener(listener);
     628    }
     629
     630    /**
     631     * Adds a listener that gets called whenever the data changed. It is added with a weak link
     632     * @param listener The listener
     633     */
     634    public void addWeakChangeListener(GpxDataChangeListener listener) {
     635        listeners.addWeakListener(listener);
     636    }
     637
     638    /**
     639     * Removes a listener that gets called whenever the data changed.
     640     * @param listener The listener
     641     * @since 12156
     642     */
     643    public void removeChangeListener(GpxDataChangeListener listener) {
     644        listeners.removeListener(listener);
     645    }
     646
     647    private void fireInvalidate() {
     648        GpxDataChangeEvent e = new GpxDataChangeEvent(this);
     649        listeners.fireEvent(l -> l.gpxDataChanged(e));
     650    }
     651
     652    /**
     653     * This is a proxy of a collection that notifies a listener on every collection change
     654     * @author Michael Zangl
     655     *
     656     * @param <T> The entry type
     657     * @since 12156
     658     */
     659    private static class ListeningCollection<T> extends AbstractCollection<T> {
     660        private final ArrayList<T> base;
     661        private final Runnable runOnModification;
     662
     663        ListeningCollection(ArrayList<T> base, Runnable runOnModification) {
     664            this.base = base;
     665            this.runOnModification = runOnModification;
     666        }
     667
     668        @Override
     669        public Iterator<T> iterator() {
     670            Iterator<T> it = base.iterator();
     671            return new Iterator<T>() {
     672                private T cursor;
     673
     674                @Override
     675                public boolean hasNext() {
     676                    return it.hasNext();
     677                }
     678
     679                @Override
     680                public T next() {
     681                    cursor = it.next();
     682                    return cursor;
     683                }
     684
     685                @Override
     686                public void remove() {
     687                    if (cursor != null) {
     688                        removed(cursor);
     689                        cursor = null;
     690                    }
     691                    it.remove();
     692                }
     693            };
     694        }
     695
     696        @Override
     697        public int size() {
     698            return base.size();
     699        }
     700
     701        @Override
     702        public boolean remove(Object o) {
     703            boolean remove = base.remove(o);
     704            if (remove) {
     705                removed((T) o);
     706            }
     707            return remove;
     708        }
     709
     710        @Override
     711        public boolean add(T e) {
     712            boolean add = base.add(e);
     713            added(e);
     714            return add;
     715        }
     716
     717        protected void removed(T cursor) {
     718            runOnModification.run();
     719        }
     720
     721        protected void added(T cursor) {
     722            runOnModification.run();
     723        }
     724    }
     725
     726    /**
     727     * A listener that listens to GPX data changes.
     728     * @author Michael Zangl
     729     * @since 12156
     730     */
     731    @FunctionalInterface
     732    public interface GpxDataChangeListener {
     733        /**
     734         * Called when the gpx data changed.
     735         * @param e The event
     736         */
     737        void gpxDataChanged(GpxDataChangeEvent e);
     738    }
     739
     740    /**
     741     * A data change event in any of the gpx data.
     742     * @author Michael Zangl
     743     * @since 12156
     744     */
     745    public static class GpxDataChangeEvent {
     746        private final GpxData source;
     747
     748        GpxDataChangeEvent(GpxData source) {
     749            super();
     750            this.source = source;
     751        }
     752
     753        /**
     754         * Get the data that was changed.
     755         * @return The data.
     756         */
     757        public GpxData getSource() {
     758            return source;
     759        }
     760    }
    500761}
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxTrack.java

    r9949 r12156  
    4040     * Returns the number of times this track has been changed.
    4141     * @return Number of times this track has been changed. Always 0 for read-only tracks
     42     * @deprecated since 12156 Replaced by change listeners.
    4243     */
    43     int getUpdateCount();
     44    @Deprecated
     45    default int getUpdateCount() {
     46        // to allow removal
     47        return 0;
     48    }
     49
     50    /**
     51     * Add a listener that listens to changes in the GPX track.
     52     * @param l The listener
     53     */
     54    default void addListener(GpxTrackChangeListener l) {
     55        // nop
     56    }
     57
     58    /**
     59     * Remove a listener that listens to changes in the GPX track.
     60     * @param l The listener
     61     */
     62    default void removeListener(GpxTrackChangeListener l) {
     63        // nop
     64    }
     65
     66    /**
     67     * A listener that listens to GPX track changes.
     68     * @author Michael Zangl
     69     * @since 12156
     70     */
     71    @FunctionalInterface
     72    public interface GpxTrackChangeListener {
     73        /**
     74         * Called when the gpx data changed.
     75         * @param e The event
     76         */
     77        void gpxDataChanged(GpxTrackChangeEvent e);
     78    }
     79
     80    /**
     81     * A track change event for the current track.
     82     * @author Michael Zangl
     83     * @since 12156
     84     */
     85    class GpxTrackChangeEvent {
     86        private final GpxTrack source;
     87
     88        GpxTrackChangeEvent(GpxTrack source) {
     89            super();
     90            this.source = source;
     91        }
     92
     93        /**
     94         * Get the track that was changed.
     95         * @return The track.
     96         */
     97        public GpxTrack getSource() {
     98            return source;
     99        }
     100    }
    44101}
Note: See TracChangeset for help on using the changeset viewer.