source: josm/trunk/src/org/openstreetmap/josm/gui/io/UploadDialogModel.java@ 18824

Last change on this file since 18824 was 18824, checked in by taylor.smock, 10 months ago

Fix #23153: Remote Control API call is adding hashtags many times

This occurs due to adding the hashtags to the comment multiple times.

This is fixed by doing the following:
1) When finding hashtags from a comment, only return the distinct hashtags
2) When adding hashtags from the dataset, only add hashtags that are not already

part of the comment.

  • Property svn:eol-style set to native
File size: 6.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import java.util.Arrays;
5import java.util.LinkedHashSet;
6import java.util.List;
7import java.util.Map;
8import java.util.Set;
9import java.util.stream.Collectors;
10
11import org.openstreetmap.josm.data.Version;
12import org.openstreetmap.josm.data.osm.DataSet;
13import org.openstreetmap.josm.gui.tagging.TagEditorModel;
14import org.openstreetmap.josm.gui.tagging.TagModel;
15import org.openstreetmap.josm.spi.preferences.Config;
16import org.openstreetmap.josm.tools.Utils;
17
18/**
19 * A model for the upload dialog
20 *
21 * @since 18173
22 */
23public class UploadDialogModel extends TagEditorModel {
24 /** the "created_by" changeset OSM key */
25 private static final String CREATED_BY = "created_by";
26 /** the "comment" changeset OSM key */
27 public static final String COMMENT = "comment";
28 /** the "source" changeset OSM key */
29 public static final String SOURCE = "source";
30 /** the user-agent */
31 private final String agent = Version.getInstance().getAgentString(false);
32 /** whether to extract hashtags from comment */
33 private final boolean hashtags = Config.getPref().getBoolean("upload.changeset.hashtags", true);
34
35 /** a lock to prevent loops */
36 private boolean locked;
37
38 @Override
39 public void fireTableDataChanged() {
40 if (!locked) {
41 try {
42 locked = true;
43 // add "hashtags" if any
44 if (hashtags) {
45 put("hashtags", findHashTags(getValue(COMMENT)));
46 }
47 // add/update "created_by"
48 final String createdBy = getValue(CREATED_BY);
49 if (createdBy.isEmpty()) {
50 put(CREATED_BY, agent);
51 } else if (!createdBy.contains(agent)) {
52 put(CREATED_BY, createdBy + ';' + agent);
53 }
54 super.fireTableDataChanged();
55 } finally {
56 locked = false;
57 }
58 }
59 }
60
61 /**
62 * Get the value of a key.
63 *
64 * @param key The key to retrieve
65 * @return The value (may be null)
66 */
67 public String getValue(String key) {
68 TagModel tag = get(key);
69 return tag == null ? "" : tag.getValue();
70 }
71
72 /**
73 * Extracts the list of hashtags from the comment text.
74 * @param comment The comment with the hashtags
75 * @return the hashtags separated by ";" or null
76 */
77 String findHashTags(String comment) {
78 String foundHashtags = Arrays.stream(comment.split("\\s", -1))
79 .map(s -> Utils.strip(s, ",;"))
80 .filter(s -> s.matches("#[a-zA-Z0-9][-_a-zA-Z0-9]+"))
81 .distinct().collect(Collectors.joining(";"));
82 return foundHashtags.isEmpty() ? null : foundHashtags;
83 }
84
85 /**
86 * Returns the given comment with appended hashtags from dataset changeset tags, if not already present.
87 * @param comment changeset comment. Can be null
88 * @param dataSet optional dataset, which can contain hashtags in its changeset tags
89 * @return comment with dataset changesets tags, if any, not duplicated
90 */
91 static String addHashTagsFromDataSet(String comment, DataSet dataSet) {
92 StringBuilder result = comment == null ? new StringBuilder() : new StringBuilder(comment);
93 if (dataSet != null) {
94 String hashtags = dataSet.getChangeSetTags().get("hashtags");
95 if (hashtags != null) {
96 Set<String> sanitizedHashtags = new LinkedHashSet<>();
97 for (String hashtag : hashtags.split(";", -1)) {
98 if (comment == null || !comment.contains(hashtag)) {
99 sanitizedHashtags.add(hashtag.startsWith("#") ? hashtag : "#" + hashtag);
100 }
101 }
102 if (!sanitizedHashtags.isEmpty()) {
103 result.append(' ').append(String.join(" ", sanitizedHashtags));
104 }
105 }
106 }
107 return result.toString();
108 }
109
110 /**
111 * Inserts/updates/deletes a tag.
112 * <p>
113 * Existing keys are updated. Others are added. A value of {@code null}
114 * deletes the key.
115 *
116 * @param key The key of the tag to insert.
117 * @param value The value of the tag to insert.
118 */
119 private void doPut(String key, String value) {
120 List<TagModel> l = tags.stream().filter(tm -> tm.getName().equals(key)).collect(Collectors.toList());
121 if (!l.isEmpty()) {
122 if (value != null)
123 for (TagModel tm : l) {
124 tm.setValue(value);
125 }
126 else
127 tags.removeIf(tm -> tm.getName().equals(key));
128 } else if (value != null) {
129 tags.add(new TagModel(key, value));
130 }
131 }
132
133 /**
134 * Inserts/updates/deletes a tag.
135 * <p>
136 * Existing keys are updated. Others are added. A value of {@code null}
137 * deletes the key.
138 *
139 * @param key The key of the tag to insert.
140 * @param value The value of the tag to insert.
141 */
142 public void put(String key, String value) {
143 commitPendingEdit();
144 doPut(key, value);
145 setDirty(true);
146 fireTableDataChanged();
147 }
148
149 /**
150 * Inserts/updates/deletes all tags from {@code map}.
151 * <p>
152 * Existing keys are updated. Others are added. A value of {@code null}
153 * deletes the key.
154 *
155 * @param map a map of tags to insert or update
156 */
157 public void putAll(Map<String, String> map) {
158 commitPendingEdit();
159 map.forEach(this::doPut);
160 setDirty(true);
161 fireTableDataChanged();
162 }
163
164 /**
165 * Inserts all tags from a {@code DataSet}.
166 *
167 * @param dataSet The DataSet to take tags from.
168 */
169 public void putAll(DataSet dataSet) {
170 if (dataSet != null) {
171 putAll(dataSet.getChangeSetTags());
172 put(COMMENT, addHashTagsFromDataSet(getValue(COMMENT), dataSet));
173 }
174 }
175
176 /**
177 * Determines if the key is "comment" or "source".
178 * @param key changeset key
179 * @return {@code true} if the key is "comment" or "source"
180 * @since 18283
181 */
182 public static boolean isCommentOrSource(String key) {
183 return COMMENT.equals(key) || SOURCE.equals(key);
184 }
185}
Note: See TracBrowser for help on using the repository browser.