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}