Changeset 19078 in josm for trunk/src


Ignore:
Timestamp:
2024-05-13T20:34:55+02:00 (6 months ago)
Author:
taylor.smock
Message:

Fix #4142: Track fully downloaded objects (patch by stoecker, GerdP, and myself)

The serialization move from PrimitiveData to AbstractPrimitive should be
reverted prior to 24.05 (see #23677).

The serialization move was required since we want to ensure that all downstream
users of AbstractPrimitive were not using the flags field, which was done by
making the field private instead of protected. They may still be using that
field (via updateFlags) which would not be caught by compile-time or runtime
errors.

Additionally, a good chunk of common functionality was moved up from
OsmPrimitive, even though much of it wasn't useful for PrimitiveData.

Location:
trunk/src/org/openstreetmap/josm
Files:
18 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java

    r18615 r19078  
    22package org.openstreetmap.josm.actions;
    33
     4import static java.util.function.Predicate.not;
    45import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    56import static org.openstreetmap.josm.tools.I18n.tr;
     
    272273        fixNodes.addAll(collectNodesWithExternReferrers(ways));
    273274
    274         // Check if one or more nodes are outside of download area
    275         if (nodes.stream().anyMatch(Node::isOutsideDownloadArea))
    276             throw new InvalidSelection(tr("One or more nodes involved in this action is outside of the downloaded area."));
     275        // Check if one or more nodes does not have all parents available
     276        if (nodes.stream().anyMatch(not(Node::isReferrersDownloaded)))
     277            throw new InvalidSelection(tr("One or more nodes involved in this action may have additional referrers."));
    277278
    278279
  • trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java

    r18208 r19078  
    285285            final Node[] endnodes = {w.firstNode(), w.lastNode()};
    286286            for (Node n : endnodes) {
    287                 if (!n.isNew() && n.isOutsideDownloadArea() && !endNodesOutside.add(n)) {
    288                     new Notification(tr("Combine ways refused<br>" + "(A shared node is outside of the download area)"))
     287                if (!n.isNew() && !n.isReferrersDownloaded() && !endNodesOutside.add(n)) {
     288                    new Notification(tr("Combine ways refused<br>" + "(A shared node may have additional referrers)"))
    289289                            .setIcon(JOptionPane.INFORMATION_MESSAGE).show();
    290290                    return;
  • trunk/src/org/openstreetmap/josm/actions/DeleteAction.java

    r18756 r19078  
    1717import org.openstreetmap.josm.command.DeleteCommand.DeletionCallback;
    1818import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
     19import org.openstreetmap.josm.data.osm.INode;
     20import org.openstreetmap.josm.data.osm.IRelation;
     21import org.openstreetmap.josm.data.osm.IWay;
    1922import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2023import org.openstreetmap.josm.data.osm.Relation;
     
    104107    public static boolean checkAndConfirmOutlyingDelete(Collection<? extends OsmPrimitive> primitives,
    105108            Collection<? extends OsmPrimitive> ignore) {
     109        final boolean nodes = primitives.stream().anyMatch(INode.class::isInstance);
     110        final boolean ways = primitives.stream().anyMatch(IWay.class::isInstance);
     111        final boolean relations = primitives.stream().anyMatch(IRelation.class::isInstance);
     112        final String type;
     113        if (nodes && !ways && !relations) {
     114            type = tr("You are about to delete nodes which can have other referrers not yet downloaded.");
     115        } else if (!nodes && ways && !relations) {
     116            type = tr("You are about to delete ways which can have other referrers not yet downloaded.");
     117        } else if (!nodes && !ways && relations) {
     118            type = tr("You are about to delete relations which can have other referrers not yet downloaded.");
     119        } else {
     120            // OK. We have multiple types being deleted.
     121            type = tr("You are about to delete primitives which can have other referrers not yet downloaded.");
     122        }
    106123        return Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() -> checkAndConfirmOutlyingOperation("delete",
    107124                tr("Delete confirmation"),
    108                 tr("You are about to delete nodes which can have other referrers not yet downloaded."
     125                tr("{0}"
    109126                        + "<br>"
    110127                        + "This can cause problems because other objects (that you do not see) might use them."
    111128                        + "<br>"
    112                         + "Do you really want to delete?"),
     129                        + "Do you really want to delete?", type),
    113130                tr("You are about to delete incomplete objects."
    114131                        + "<br>"
  • trunk/src/org/openstreetmap/josm/actions/JoinAreasAction.java

    r19050 r19078  
    673673            // remove now unconnected nodes without tags
    674674            List<Node> toRemove = oldNodes.stream().filter(
    675                     n -> (n.isNew() || !n.isOutsideDownloadArea()) && !n.hasKeys() && n.getReferrers().isEmpty())
     675                    n -> n.isReferrersDownloaded() && !n.hasKeys() && n.getReferrers().isEmpty())
    676676                    .collect(Collectors.toList());
    677677            if (!toRemove.isEmpty()) {
  • trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java

    r19050 r19078  
    144144                    .show();
    145145            return;
     146        } else if (!checkAndConfirmOutlyingOperation("splitway", tr("Split way confirmation"),
     147                tr("You are about to split a way that may have referrers that are not yet downloaded.")
     148                        + "<br/>"
     149                        + tr("This can lead to broken relations.") + "<br/>"
     150                        + tr("Do you really want to split?"),
     151                tr("The selected area is incomplete. Continue?"),
     152                applicableWays, null)) {
     153            return;
    146154        }
    147155
  • trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadReferrersTask.java

    r19050 r19078  
    124124        DataSetMerger visitor = new DataSetMerger(targetLayer.getDataSet(), parents);
    125125        visitor.merge();
     126        this.children.stream().map(p -> targetLayer.getDataSet().getPrimitiveById(p))
     127                .forEach(p -> p.setReferrersDownloaded(true));
     128
    126129        SwingUtilities.invokeLater(targetLayer::onPostDownloadFromServer);
    127130        if (visitor.getConflicts().isEmpty())
  • trunk/src/org/openstreetmap/josm/command/Command.java

    r17333 r19078  
    228228            if (osm.isIncomplete()) {
    229229                res |= IS_INCOMPLETE;
    230             } else if ((res & IS_OUTSIDE) == 0 && (osm.isOutsideDownloadArea()
    231                     || (osm instanceof Node && !osm.isNew() && osm.getDataSet() != null && osm.getDataSet().getDataSourceBounds().isEmpty()))
    232                             && (ignore == null || !ignore.contains(osm))) {
     230            } else if ((res & IS_OUTSIDE) == 0 && !osm.isReferrersDownloaded() && (ignore == null || !ignore.contains(osm))) {
    233231                res |= IS_OUTSIDE;
    234232            }
  • trunk/src/org/openstreetmap/josm/command/DeleteCommand.java

    r19050 r19078  
    434434        }
    435435
    436         if (!silent && !callback.checkAndConfirmOutlyingDelete(
    437                 primitivesToDelete, Utils.filteredCollection(primitivesToDelete, Way.class)))
     436        if (!silent && !callback.checkAndConfirmOutlyingDelete(primitivesToDelete, null))
    438437            return null;
    439438
  • trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java

    r18891 r19078  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.io.IOException;
     7import java.io.ObjectInputStream;
     8import java.io.ObjectOutputStream;
    69import java.text.MessageFormat;
    710import java.time.Instant;
     
    132135
    133136    /**
     137     * Determines if the primitive has all of its referrers
     138     */
     139    protected static final short FLAG_ALL_REFERRERS_DOWNLOADED = 1 << 14;
     140
     141    /**
    134142     * Put several boolean flags to one short int field to save memory.
    135143     * Other bits of this field are used in subclasses.
    136144     */
    137     protected volatile short flags = FLAG_VISIBLE;   // visible per default
     145    private volatile short flags = FLAG_VISIBLE;   // visible per default
    138146
    139147    /**
     
    366374    }
    367375
     376    /**
     377     * Write common data to a serialization stream. At time of writing, this should <i>only</i> be used by {@link PrimitiveData}.
     378     * @param oos The output stream to write to
     379     * @throws IOException see {@link ObjectOutputStream#write}
     380     */
     381    protected void writeObjectCommon(ObjectOutputStream oos) throws IOException {
     382        oos.writeLong(id);
     383        oos.writeLong(user == null ? -1 : user.getId());
     384        oos.writeInt(version);
     385        oos.writeInt(changesetId);
     386        oos.writeInt(timestamp);
     387        oos.writeObject(keys);
     388        oos.writeShort(flags);
     389    }
     390
     391    /**
     392     * Read common data from a serialization stream. At time of writing, this should <i>only</i> be used by {@link PrimitiveData}.
     393     * @param ois The serialization stream to read from
     394     * @throws ClassNotFoundException see {@link ObjectInputStream#readObject()}
     395     * @throws IOException see {@link ObjectInputStream#read}
     396     */
     397    protected void readObjectCommon(ObjectInputStream ois) throws ClassNotFoundException, IOException {
     398        id = ois.readLong();
     399        final long userId = ois.readLong();
     400        user = userId == -1 ? null : User.getById(userId);
     401        version = ois.readInt();
     402        changesetId = ois.readInt();
     403        timestamp = ois.readInt();
     404        keys = (String[]) ois.readObject();
     405        flags = ois.readShort();
     406    }
     407
    368408    @Override
    369409    public void setModified(boolean modified) {
     
    379419    public boolean isDeleted() {
    380420        return (flags & FLAG_DELETED) != 0;
     421    }
     422
     423    @Override
     424    public void setReferrersDownloaded(boolean referrersDownloaded) {
     425        this.updateFlags(FLAG_ALL_REFERRERS_DOWNLOADED, referrersDownloaded);
    381426    }
    382427
     
    407452        updateFlags(FLAG_DELETED, deleted);
    408453        setModified(deleted ^ !isVisible());
     454    }
     455
     456    @Override
     457    public boolean hasDirectionKeys() {
     458        return (flags & FLAG_HAS_DIRECTIONS) != 0;
     459    }
     460
     461    @Override
     462    public boolean reversedDirection() {
     463        return (flags & FLAG_DIRECTION_REVERSED) != 0;
     464    }
     465
     466    @Override
     467    public boolean isTagged() {
     468        return (flags & FLAG_TAGGED) != 0;
     469    }
     470
     471    @Override
     472    public boolean isAnnotated() {
     473        return (flags & FLAG_ANNOTATED) != 0;
     474    }
     475
     476    @Override
     477    public boolean isHighlighted() {
     478        return (flags & FLAG_HIGHLIGHTED) != 0;
     479    }
     480
     481    @Override
     482    public boolean isDisabled() {
     483        return (flags & FLAG_DISABLED) != 0;
     484    }
     485
     486    @Override
     487    public boolean isDisabledAndHidden() {
     488        return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
     489    }
     490
     491    @Override
     492    public boolean isPreserved() {
     493        return (flags & FLAG_PRESERVED) != 0;
     494    }
     495
     496    @Override
     497    public boolean isReferrersDownloaded() {
     498        return isNew() || (flags & FLAG_ALL_REFERRERS_DOWNLOADED) != 0;
    409499    }
    410500
  • trunk/src/org/openstreetmap/josm/data/osm/DataSetMerger.java

    r19050 r19078  
    370370        }
    371371        if (mergeFromSource) {
     372            boolean backupReferrersDownloadedStatus = target.isReferrersDownloaded() && haveSameVersion;
    372373            target.mergeFrom(source);
     374            if (backupReferrersDownloadedStatus && !target.isReferrersDownloaded()) {
     375                target.setReferrersDownloaded(true);
     376            }
    373377            objectsWithChildrenToMerge.add(source.getPrimitiveId());
    374378        }
  • trunk/src/org/openstreetmap/josm/data/osm/IPrimitive.java

    r19070 r19078  
    3838
    3939    /**
     40     * Set the status of the referrers
     41     * @param referrersDownloaded {@code true} if all referrers for this object have been downloaded
     42     * @since xxx
     43     */
     44    default void setReferrersDownloaded(boolean referrersDownloaded) {
     45        throw new UnsupportedOperationException(this.getClass().getName() + " does not support referrers status");
     46    }
     47
     48    /**
    4049     * Checks if object is known to the server.
    4150     * Replies true if this primitive is either unknown to the server (i.e. its id
     
    7584     */
    7685    void setDeleted(boolean deleted);
     86
     87    /**
     88     * Determines if this primitive is fully downloaded
     89     * @return {@code true} if the primitive is fully downloaded and all parents and children should be available.
     90     * {@code false} otherwise.
     91     * @since xxx
     92     */
     93    default boolean isReferrersDownloaded() {
     94        return false;
     95    }
    7796
    7897    /**
  • trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

    r18801 r19078  
    365365
    366366    @Override
    367     public boolean isDisabled() {
    368         return (flags & FLAG_DISABLED) != 0;
    369     }
    370 
    371     @Override
    372     public boolean isDisabledAndHidden() {
    373         return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
    374     }
    375 
    376     @Override
    377     public boolean isPreserved() {
    378         return (flags & FLAG_PRESERVED) != 0;
    379     }
    380 
    381     @Override
    382367    public boolean isSelectable() {
    383368        // not synchronized -> check disabled twice just to be sure we did not have a race condition.
     
    493478    }
    494479
    495     @Override
    496     public boolean isHighlighted() {
    497         return (flags & FLAG_HIGHLIGHTED) != 0;
    498     }
    499 
    500480    /*---------------
    501481     * DIRECTION KEYS
     
    525505    private void updateAnnotated() {
    526506        updateFlagsNoLock(FLAG_ANNOTATED, hasKeys() && getWorkInProgressKeys().stream().anyMatch(this::hasKey));
    527     }
    528 
    529     @Override
    530     public boolean isTagged() {
    531         return (flags & FLAG_TAGGED) != 0;
    532     }
    533 
    534     @Override
    535     public boolean isAnnotated() {
    536         return (flags & FLAG_ANNOTATED) != 0;
    537507    }
    538508
     
    550520        updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
    551521        updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
    552     }
    553 
    554     @Override
    555     public boolean hasDirectionKeys() {
    556         return (flags & FLAG_HAS_DIRECTIONS) != 0;
    557     }
    558 
    559     @Override
    560     public boolean reversedDirection() {
    561         return (flags & FLAG_DIRECTION_REVERSED) != 0;
    562522    }
    563523
     
    852812                        tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
    853813
     814            setIncomplete(other.isIncomplete());
     815            super.cloneFrom(other);
    854816            setKeys(other.hasKeys() ? other.getKeys() : null);
    855             timestamp = other.timestamp;
    856817            version = other.version;
    857             setIncomplete(other.isIncomplete());
    858             flags = other.flags;
    859             user = other.user;
    860818            changesetId = other.changesetId;
    861819        } finally {
  • trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java

    r18014 r19078  
    8585    private void writeObject(ObjectOutputStream oos) throws IOException {
    8686        // since super class is not Serializable
    87         oos.writeLong(id);
    88         oos.writeLong(user == null ? -1 : user.getId());
    89         oos.writeInt(version);
    90         oos.writeInt(changesetId);
    91         oos.writeInt(timestamp);
    92         oos.writeObject(keys);
    93         oos.writeShort(flags);
     87        super.writeObjectCommon(oos);
    9488        oos.defaultWriteObject();
    9589    }
     
    9791    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
    9892        // since super class is not Serializable
    99         id = ois.readLong();
    100         final long userId = ois.readLong();
    101         user = userId == -1 ? null : User.getById(userId);
    102         version = ois.readInt();
    103         changesetId = ois.readInt();
    104         timestamp = ois.readInt();
    105         keys = (String[]) ois.readObject();
    106         flags = ois.readShort();
     93        super.readObjectCommon(ois);
    10794        ois.defaultReadObject();
    10895    }
  • trunk/src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java

    r11129 r19078  
    2727    @Override
    2828    public void visit(Node n) {
    29         if (n.hasTag("barrier", "entrance") && !n.isOutsideDownloadArea()) {
     29        if (n.hasTag("barrier", "entrance") && n.isReferrersDownloaded()) {
    3030            for (OsmPrimitive p : n.getReferrers()) {
    3131                if (p.hasKey("barrier")) {
  • trunk/src/org/openstreetmap/josm/data/vector/VectorPrimitive.java

    r18520 r19078  
    6060
    6161    @Override
    62     public boolean isTagged() {
    63         return (flags & FLAG_TAGGED) != 0;
    64     }
    65 
    66     @Override
    6762    public boolean isAnnotated() {
    6863        return this.getInterestingTags().size() - this.getKeys().size() > 0;
  • trunk/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java

    r18678 r19078  
    130130            sb.append(tr("deleted-on-server")).append(INDENT);
    131131        }
     132        if (o.isReferrersDownloaded()) {
     133            sb.append(tr("all-referrers-downloaded")).append(INDENT);
     134        } else {
     135            sb.append(tr("referrers-not-all-downloaded")).append(INDENT);
     136        }
    132137        if (o.isModified()) {
    133138            sb.append(tr("modified")).append(INDENT);
  • trunk/src/org/openstreetmap/josm/io/BoundingBoxDownloader.java

    r18320 r19078  
    77import java.io.InputStream;
    88import java.net.SocketException;
     9import java.util.Collection;
     10import java.util.Collections;
    911import java.util.List;
    1012
     
    216218                }
    217219            }
     220            // From https://wiki.openstreetmap.org/wiki/API_v0.6#Retrieving_map_data_by_bounding_box:_GET_/api/0.6/map,
     221            // relations are not recursed up, so they *may* have parent relations.
     222            // Nodes inside the download area should have all relations and ways that refer to them.
     223            // Ways should have all relations that refer to them and all child nodes, but those child nodes may not
     224            //    have their parent referrers.
     225            // Relations will have the *first* parent relations downloaded, but those are not split out in the returns.
     226            // So we always assume that a relation has referrers that need to be downloaded unless it has no child relations.
     227            // Our "full" overpass query doesn't return the same data as a standard download, so we cannot
     228            // mark relations with no child relations as fully downloaded *yet*.
     229            if (this.considerAsFullDownload()) {
     230                final Collection<Bounds> bounds = this.getBounds();
     231                // We cannot use OsmPrimitive#isOutsideDownloadArea yet since some download methods haven't added
     232                // the download bounds to the dataset yet. This is specifically the case for overpass downloads.
     233                ds.getNodes().stream().filter(n -> bounds.stream().anyMatch(b -> b.contains(n)))
     234                        .forEach(i -> i.setReferrersDownloaded(true));
     235                ds.getWays().forEach(i -> i.setReferrersDownloaded(true));
     236            }
    218237            return ds;
    219238        } catch (OsmTransferException e) {
     
    280299    }
    281300
     301    /**
     302     * Get the bounds for this downloader
     303     * @return The bounds for this downloader
     304     * @since xxx
     305     */
     306    protected Collection<Bounds> getBounds() {
     307        return Collections.singleton(new Bounds(this.lat1, this.lon1, this.lat2, this.lon2));
     308    }
    282309}
  • trunk/src/org/openstreetmap/josm/io/OverpassDownloadReader.java

    r18211 r19078  
    1414import java.time.format.DateTimeParseException;
    1515import java.util.Arrays;
     16import java.util.Collection;
     17import java.util.Collections;
    1618import java.util.EnumMap;
    1719import java.util.List;
     
    2022import java.util.NoSuchElementException;
    2123import java.util.Objects;
     24import java.util.Set;
    2225import java.util.concurrent.ConcurrentHashMap;
    2326import java.util.concurrent.TimeUnit;
     
    407410            // add bounds if necessary (note that Overpass API does not return bounds in the response XML)
    408411            if (ds != null && ds.getDataSources().isEmpty() && overpassQuery.contains("{{bbox}}")) {
    409                 if (crosses180th) {
    410                     Bounds bounds = new Bounds(lat1, lon1, lat2, 180.0);
    411                     DataSource src = new DataSource(bounds, getBaseUrl());
    412                     ds.addDataSource(src);
    413 
    414                     bounds = new Bounds(lat1, -180.0, lat2, lon2);
    415                     src = new DataSource(bounds, getBaseUrl());
    416                     ds.addDataSource(src);
    417                 } else {
    418                     Bounds bounds = new Bounds(lat1, lon1, lat2, lon2);
    419                     DataSource src = new DataSource(bounds, getBaseUrl());
    420                     ds.addDataSource(src);
    421                 }
     412                getBounds().forEach(bounds -> ds.addDataSource(new DataSource(bounds, getBaseUrl())));
    422413            }
    423414            return ds;
     
    441432        return overpassQuery.equals(OverpassDownloadSource.FULL_DOWNLOAD_QUERY);
    442433    }
     434
     435    @Override
     436    protected Collection<Bounds> getBounds() {
     437        if (this.overpassQuery.contains("{{bbox}}")) {
     438            if (crosses180th) {
     439                return Set.of(new Bounds(lat1, lon1, lat2, 180.0),
     440                        new Bounds(lat1, -180.0, lat2, lon2));
     441            } else {
     442                return Collections.singleton(new Bounds(lat1, lon1, lat2, lon2));
     443            }
     444        }
     445        return Collections.emptySet();
     446    }
    443447}
Note: See TracChangeset for help on using the changeset viewer.