001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import java.util.Arrays; 005import java.util.LinkedHashSet; 006import java.util.List; 007import java.util.Map; 008import java.util.Set; 009import java.util.stream.Collectors; 010 011import org.openstreetmap.josm.data.Version; 012import org.openstreetmap.josm.data.osm.DataSet; 013import org.openstreetmap.josm.gui.tagging.TagEditorModel; 014import org.openstreetmap.josm.gui.tagging.TagModel; 015import org.openstreetmap.josm.spi.preferences.Config; 016import org.openstreetmap.josm.tools.Utils; 017 018/** 019 * A model for the upload dialog 020 * 021 * @since 18173 022 */ 023public class UploadDialogModel extends TagEditorModel { 024 /** the "created_by" changeset OSM key */ 025 private static final String CREATED_BY = "created_by"; 026 /** the "comment" changeset OSM key */ 027 public static final String COMMENT = "comment"; 028 /** the "source" changeset OSM key */ 029 public static final String SOURCE = "source"; 030 /** the user-agent */ 031 private final String agent = Version.getInstance().getAgentString(false); 032 /** whether to extract hashtags from comment */ 033 private final boolean hashtags = Config.getPref().getBoolean("upload.changeset.hashtags", true); 034 035 /** a lock to prevent loops */ 036 private boolean locked; 037 038 @Override 039 public void fireTableDataChanged() { 040 if (!locked) { 041 try { 042 locked = true; 043 // add "hashtags" if any 044 if (hashtags) { 045 put("hashtags", findHashTags(getValue(COMMENT))); 046 } 047 // add/update "created_by" 048 final String createdBy = getValue(CREATED_BY); 049 if (createdBy.isEmpty()) { 050 put(CREATED_BY, agent); 051 } else if (!createdBy.contains(agent)) { 052 put(CREATED_BY, createdBy + ';' + agent); 053 } 054 super.fireTableDataChanged(); 055 } finally { 056 locked = false; 057 } 058 } 059 } 060 061 /** 062 * Get the value of a key. 063 * 064 * @param key The key to retrieve 065 * @return The value (may be null) 066 */ 067 public String getValue(String key) { 068 TagModel tag = get(key); 069 return tag == null ? "" : tag.getValue(); 070 } 071 072 /** 073 * Extracts the list of hashtags from the comment text. 074 * @param comment The comment with the hashtags 075 * @return the hashtags separated by ";" or null 076 */ 077 String findHashTags(String comment) { 078 String hashtags = String.join(";", 079 Arrays.stream(comment.split("\\s", -1)) 080 .map(s -> Utils.strip(s, ",;")) 081 .filter(s -> s.matches("#[a-zA-Z0-9][-_a-zA-Z0-9]+")) 082 .collect(Collectors.toList())); 083 return hashtags.isEmpty() ? null : hashtags; 084 } 085 086 /** 087 * Returns the given comment with appended hashtags from dataset changeset tags, if not already present. 088 * @param comment changeset comment. Can be null 089 * @param dataSet optional dataset, which can contain hashtags in its changeset tags 090 * @return comment with dataset changesets tags, if any, not duplicated 091 */ 092 static String addHashTagsFromDataSet(String comment, DataSet dataSet) { 093 StringBuilder result = comment == null ? new StringBuilder() : new StringBuilder(comment); 094 if (dataSet != null) { 095 String hashtags = dataSet.getChangeSetTags().get("hashtags"); 096 if (hashtags != null) { 097 Set<String> sanitizedHashtags = new LinkedHashSet<>(); 098 for (String hashtag : hashtags.split(";", -1)) { 099 sanitizedHashtags.add(hashtag.startsWith("#") ? hashtag : "#" + hashtag); 100 } 101 if (!sanitizedHashtags.isEmpty()) { 102 result.append(' ').append(String.join(" ", sanitizedHashtags)); 103 } 104 } 105 } 106 return result.toString(); 107 } 108 109 /** 110 * Inserts/updates/deletes a tag. 111 * 112 * Existing keys are updated. Others are added. A value of {@code null} 113 * deletes the key. 114 * 115 * @param key The key of the tag to insert. 116 * @param value The value of the tag to insert. 117 */ 118 private void doPut(String key, String value) { 119 List<TagModel> l = tags.stream().filter(tm -> tm.getName().equals(key)).collect(Collectors.toList()); 120 if (!l.isEmpty()) { 121 if (value != null) 122 for (TagModel tm : l) { 123 tm.setValue(value); 124 } 125 else 126 tags.removeIf(tm -> tm.getName().equals(key)); 127 } else if (value != null) { 128 tags.add(new TagModel(key, value)); 129 } 130 } 131 132 /** 133 * Inserts/updates/deletes a tag. 134 * 135 * Existing keys are updated. Others are added. A value of {@code null} 136 * deletes the key. 137 * 138 * @param key The key of the tag to insert. 139 * @param value The value of the tag to insert. 140 */ 141 public void put(String key, String value) { 142 commitPendingEdit(); 143 doPut(key, value); 144 setDirty(true); 145 fireTableDataChanged(); 146 } 147 148 /** 149 * Inserts/updates/deletes all tags from {@code map}. 150 * 151 * Existing keys are updated. Others are added. A value of {@code null} 152 * deletes the key. 153 * 154 * @param map a map of tags to insert or update 155 */ 156 public void putAll(Map<String, String> map) { 157 commitPendingEdit(); 158 map.forEach((key, value) -> doPut(key, value)); 159 setDirty(true); 160 fireTableDataChanged(); 161 } 162 163 /** 164 * Inserts all tags from a {@code DataSet}. 165 * 166 * @param dataSet The DataSet to take tags from. 167 */ 168 public void putAll(DataSet dataSet) { 169 if (dataSet != null) { 170 putAll(dataSet.getChangeSetTags()); 171 put(COMMENT, addHashTagsFromDataSet(getValue(COMMENT), dataSet)); 172 } 173 } 174 175 /** 176 * Determines if the key is "comment" or "source". 177 * @param key changeset key 178 * @return {@code true} if the key is "comment" or "source" 179 * @since 18283 180 */ 181 public static boolean isCommentOrSource(String key) { 182 return COMMENT.equals(key) || SOURCE.equals(key); 183 } 184}