source: josm/trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java @ 11385

Last change on this file since 11385 was 11385, checked in by Don-vip, 4 months ago

sonar - squid:S1066 - Collapsible "if" statements should be merged

  • Property svn:eol-style set to native
File size: 11.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.imagery;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.Objects;
16import java.util.Set;
17import java.util.TreeSet;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryPreferenceEntry;
21import org.openstreetmap.josm.gui.PleaseWaitRunnable;
22import org.openstreetmap.josm.io.CachedFile;
23import org.openstreetmap.josm.io.OfflineAccessException;
24import org.openstreetmap.josm.io.OnlineResource;
25import org.openstreetmap.josm.io.imagery.ImageryReader;
26import org.openstreetmap.josm.tools.Utils;
27import org.xml.sax.SAXException;
28
29/**
30 * Manages the list of imagery entries that are shown in the imagery menu.
31 */
32public class ImageryLayerInfo {
33
34    public static final ImageryLayerInfo instance = new ImageryLayerInfo();
35    private final List<ImageryInfo> layers = new ArrayList<>();
36    private final Map<String, ImageryInfo> layerIds = new HashMap<>();
37    private static final List<ImageryInfo> defaultLayers = new ArrayList<>();
38    private static final Map<String, ImageryInfo> defaultLayerIds = new HashMap<>();
39
40    private static final String[] DEFAULT_LAYER_SITES = {
41        Main.getJOSMWebsite()+"/maps"
42    };
43
44    /**
45     * Returns the list of imagery layers sites.
46     * @return the list of imagery layers sites
47     * @since 7434
48     */
49    public static Collection<String> getImageryLayersSites() {
50        return Main.pref.getCollection("imagery.layers.sites", Arrays.asList(DEFAULT_LAYER_SITES));
51    }
52
53    private ImageryLayerInfo() {
54    }
55
56    public ImageryLayerInfo(ImageryLayerInfo info) {
57        layers.addAll(info.layers);
58    }
59
60    public void clear() {
61        layers.clear();
62        layerIds.clear();
63    }
64
65    /**
66     * Loads the custom as well as default imagery entries.
67     * @param fastFail whether opening HTTP connections should fail fast, see {@link ImageryReader#setFastFail(boolean)}
68     */
69    public void load(boolean fastFail) {
70        clear();
71        List<ImageryPreferenceEntry> entries = Main.pref.getListOfStructs("imagery.entries", null, ImageryPreferenceEntry.class);
72        if (entries != null) {
73            for (ImageryPreferenceEntry prefEntry : entries) {
74                try {
75                    ImageryInfo i = new ImageryInfo(prefEntry);
76                    add(i);
77                } catch (IllegalArgumentException e) {
78                    Main.warn("Unable to load imagery preference entry:"+e);
79                }
80            }
81            Collections.sort(layers);
82        }
83        loadDefaults(false, true, fastFail);
84    }
85
86    /**
87     * Loads the available imagery entries.
88     *
89     * The data is downloaded from the JOSM website (or loaded from cache).
90     * Entries marked as "default" are added to the user selection, if not
91     * already present.
92     *
93     * @param clearCache if true, clear the cache and start a fresh download.
94     * @param quiet whether not the loading should be performed using a {@link PleaseWaitRunnable} in the background
95     * @param fastFail whether opening HTTP connections should fail fast, see {@link ImageryReader#setFastFail(boolean)}
96     */
97    public void loadDefaults(boolean clearCache, boolean quiet, boolean fastFail) {
98        final DefaultEntryLoader loader = new DefaultEntryLoader(clearCache, fastFail);
99        if (quiet) {
100            loader.realRun();
101            loader.finish();
102        } else {
103            Main.worker.execute(new DefaultEntryLoader(clearCache, fastFail));
104        }
105    }
106
107    /**
108     * Loader/updater of the available imagery entries
109     */
110    class DefaultEntryLoader extends PleaseWaitRunnable {
111
112        private final boolean clearCache;
113        private final boolean fastFail;
114        private final List<ImageryInfo> newLayers = new ArrayList<>();
115        private ImageryReader reader;
116        private boolean canceled;
117
118        DefaultEntryLoader(boolean clearCache, boolean fastFail) {
119            super(tr("Update default entries"));
120            this.clearCache = clearCache;
121            this.fastFail = fastFail;
122        }
123
124        @Override
125        protected void cancel() {
126            canceled = true;
127            Utils.close(reader);
128        }
129
130        @Override
131        protected void realRun() {
132            for (String source : getImageryLayersSites()) {
133                if (canceled) {
134                    return;
135                }
136                loadSource(source);
137            }
138        }
139
140        protected void loadSource(String source) {
141            boolean online = true;
142            try {
143                OnlineResource.JOSM_WEBSITE.checkOfflineAccess(source, Main.getJOSMWebsite());
144            } catch (OfflineAccessException e) {
145                Main.warn(e, false);
146                online = false;
147            }
148            if (clearCache && online) {
149                CachedFile.cleanup(source);
150            }
151            try {
152                reader = new ImageryReader(source);
153                reader.setFastFail(fastFail);
154                Collection<ImageryInfo> result = reader.parse();
155                newLayers.addAll(result);
156            } catch (IOException ex) {
157                Main.error(ex, false);
158            } catch (SAXException ex) {
159                Main.error(ex);
160            }
161        }
162
163        @Override
164        protected void finish() {
165            defaultLayers.clear();
166            defaultLayers.addAll(newLayers);
167            defaultLayerIds.clear();
168            Collections.sort(defaultLayers);
169            buildIdMap(defaultLayers, defaultLayerIds);
170            updateEntriesFromDefaults();
171            buildIdMap(layers, layerIds);
172        }
173    }
174
175    /**
176     * Build the mapping of unique ids to {@link ImageryInfo}s.
177     * @param lst input list
178     * @param idMap output map
179     */
180    private static void buildIdMap(List<ImageryInfo> lst, Map<String, ImageryInfo> idMap) {
181        idMap.clear();
182        Set<String> notUnique = new HashSet<>();
183        for (ImageryInfo i : lst) {
184            if (i.getId() != null) {
185                if (idMap.containsKey(i.getId())) {
186                    notUnique.add(i.getId());
187                    Main.error("Id ''{0}'' is not unique - used by ''{1}'' and ''{2}''!",
188                            i.getId(), i.getName(), idMap.get(i.getId()).getName());
189                    continue;
190                }
191                idMap.put(i.getId(), i);
192            }
193        }
194        for (String i : notUnique) {
195            idMap.remove(i);
196        }
197    }
198
199    /**
200     * Update user entries according to the list of default entries.
201     */
202    public void updateEntriesFromDefaults() {
203        // add new default entries to the user selection
204        boolean changed = false;
205        Collection<String> knownDefaults = Main.pref.getCollection("imagery.layers.default");
206        Collection<String> newKnownDefaults = new TreeSet<>(knownDefaults);
207        for (ImageryInfo def : defaultLayers) {
208            if (def.isDefaultEntry()) {
209                boolean isKnownDefault = false;
210                for (String url : knownDefaults) {
211                    if (isSimilar(url, def.getUrl())) {
212                        isKnownDefault = true;
213                        break;
214                    }
215                }
216                boolean isInUserList = false;
217                if (!isKnownDefault) {
218                    newKnownDefaults.add(def.getUrl());
219                    for (ImageryInfo i : layers) {
220                        if (isSimilar(def, i)) {
221                            isInUserList = true;
222                            break;
223                        }
224                    }
225                }
226                if (!isKnownDefault && !isInUserList) {
227                    add(new ImageryInfo(def));
228                    changed = true;
229                }
230            }
231        }
232        Main.pref.putCollection("imagery.layers.default", newKnownDefaults);
233
234        // Add ids to user entries without id.
235        // Only do this the first time for each id, so the user can have
236        // custom entries that don't get updated automatically
237        Collection<String> addedIds = Main.pref.getCollection("imagery.layers.addedIds");
238        Collection<String> newAddedIds = new TreeSet<>(addedIds);
239        for (ImageryInfo info : layers) {
240            for (ImageryInfo def : defaultLayers) {
241                if (isSimilar(def, info) && def.getId() != null && !addedIds.contains(def.getId())) {
242                    if (!defaultLayerIds.containsKey(def.getId())) {
243                        // ignore ids used more than once (have been purged from the map)
244                        continue;
245                    }
246                    newAddedIds.add(def.getId());
247                    if (info.getId() == null) {
248                        info.setId(def.getId());
249                        changed = true;
250                    }
251                }
252            }
253        }
254        Main.pref.putCollection("imagery.layers.addedIds", newAddedIds);
255
256        // automatically update user entries with same id as a default entry
257        for (int i = 0; i < layers.size(); i++) {
258            ImageryInfo info = layers.get(i);
259            if (info.getId() == null) {
260                continue;
261            }
262            ImageryInfo matchingDefault = defaultLayerIds.get(info.getId());
263            if (matchingDefault != null && !matchingDefault.equalsPref(info)) {
264                layers.set(i, matchingDefault);
265                changed = true;
266            }
267        }
268
269        if (changed) {
270            save();
271        }
272    }
273
274    private static boolean isSimilar(ImageryInfo iiA, ImageryInfo iiB) {
275        if (iiA == null)
276            return false;
277        if (!iiA.getImageryType().equals(iiB.getImageryType()))
278            return false;
279        if (iiA.getId() != null && iiB.getId() != null) return iiA.getId().equals(iiB.getId());
280        return isSimilar(iiA.getUrl(), iiB.getUrl());
281    }
282
283    // some additional checks to respect extended URLs in preferences (legacy workaround)
284    private static boolean isSimilar(String a, String b) {
285        return Objects.equals(a, b) || (a != null && b != null && !a.isEmpty() && !b.isEmpty() && (a.contains(b) || b.contains(a)));
286    }
287
288    public void add(ImageryInfo info) {
289        layers.add(info);
290    }
291
292    public void remove(ImageryInfo info) {
293        layers.remove(info);
294    }
295
296    public void save() {
297        List<ImageryPreferenceEntry> entries = new ArrayList<>();
298        for (ImageryInfo info : layers) {
299            entries.add(new ImageryPreferenceEntry(info));
300        }
301        Main.pref.putListOfStructs("imagery.entries", entries, ImageryPreferenceEntry.class);
302    }
303
304    public List<ImageryInfo> getLayers() {
305        return Collections.unmodifiableList(layers);
306    }
307
308    public List<ImageryInfo> getDefaultLayers() {
309        return Collections.unmodifiableList(defaultLayers);
310    }
311
312    public static void addLayer(ImageryInfo info) {
313        instance.add(info);
314        instance.save();
315    }
316
317    public static void addLayers(Collection<ImageryInfo> infos) {
318        for (ImageryInfo i : infos) {
319            instance.add(i);
320        }
321        instance.save();
322        Collections.sort(instance.layers);
323    }
324
325    /**
326     * Get unique id for ImageryInfo.
327     *
328     * This takes care, that no id is used twice (due to a user error)
329     * @param info the ImageryInfo to look up
330     * @return null, if there is no id or the id is used twice,
331     * the corresponding id otherwise
332     */
333    public String getUniqueId(ImageryInfo info) {
334        if (info.getId() != null && layerIds.get(info.getId()) == info) {
335            return info.getId();
336        }
337        return null;
338    }
339}
Note: See TracBrowser for help on using the repository browser.