001// License: GPL. For details, see Readme.txt file.
002package org.openstreetmap.gui.jmapviewer;
003
004import java.awt.Desktop;
005import java.awt.image.BufferedImage;
006import java.io.IOException;
007import java.net.URI;
008import java.net.URISyntaxException;
009import java.net.URL;
010import java.text.MessageFormat;
011import java.util.Map;
012import java.util.Objects;
013import java.util.TreeMap;
014import java.util.logging.Level;
015import java.util.logging.Logger;
016
017import javax.imageio.ImageIO;
018
019/**
020 * Feature adapter allows to override JMapViewer behaviours from a client application such as JOSM.
021 */
022public final class FeatureAdapter {
023
024    private static BrowserAdapter browserAdapter = new DefaultBrowserAdapter();
025    private static ImageAdapter imageAdapter = new DefaultImageAdapter();
026    private static TranslationAdapter translationAdapter = new DefaultTranslationAdapter();
027    private static LoggingAdapter loggingAdapter = new DefaultLoggingAdapter();
028    private static SettingsAdapter settingsAdapter = new DefaultSettingsAdapter();
029
030    private FeatureAdapter() {
031        // private constructor for utility classes
032    }
033
034    public interface BrowserAdapter {
035        void openLink(String url);
036    }
037
038    public interface TranslationAdapter {
039        String tr(String text, Object... objects);
040        // TODO: more i18n functions
041    }
042
043    public interface LoggingAdapter {
044        Logger getLogger(String name);
045    }
046
047    public interface ImageAdapter {
048        BufferedImage read(URL input, boolean readMetadata, boolean enforceTransparency) throws IOException;
049    }
050
051    /**
052     * Basic settings system allowing to store/retrieve String key/value pairs.
053     */
054    public interface SettingsAdapter {
055        /**
056         * Get settings value for a certain key and provide a default value.
057         * @param key the identifier for the setting
058         * @param def the default value. For each call of get() with a given key, the
059         * default value must be the same. {@code def} may be null.
060         * @return the corresponding value if the property has been set before, {@code def} otherwise
061         */
062        String get(String key, String def);
063
064        /**
065         * Set a value for a certain setting.
066         * @param key the unique identifier for the setting
067         * @param value the value of the setting. Can be null or "" which both removes the key-value entry.
068         * @return {@code true}, if something has changed (i.e. value is different than before)
069         */
070        boolean put(String key, String value);
071    }
072
073    public static void registerBrowserAdapter(BrowserAdapter browserAdapter) {
074        FeatureAdapter.browserAdapter = Objects.requireNonNull(browserAdapter);
075    }
076
077    public static void registerImageAdapter(ImageAdapter imageAdapter) {
078        FeatureAdapter.imageAdapter = Objects.requireNonNull(imageAdapter);
079    }
080
081    public static void registerTranslationAdapter(TranslationAdapter translationAdapter) {
082        FeatureAdapter.translationAdapter = Objects.requireNonNull(translationAdapter);
083    }
084
085    public static void registerLoggingAdapter(LoggingAdapter loggingAdapter) {
086        FeatureAdapter.loggingAdapter = Objects.requireNonNull(loggingAdapter);
087    }
088
089    /**
090     * Registers settings adapter.
091     * @param settingsAdapter settings adapter, must not be null
092     * @throws NullPointerException if settingsAdapter is null
093     */
094    public static void registerSettingsAdapter(SettingsAdapter settingsAdapter) {
095        FeatureAdapter.settingsAdapter = Objects.requireNonNull(settingsAdapter);
096    }
097
098    public static void openLink(String url) {
099        browserAdapter.openLink(url);
100    }
101
102    public static BufferedImage readImage(URL url) throws IOException {
103        return imageAdapter.read(url, false, false);
104    }
105
106    public static String tr(String text, Object... objects) {
107        return translationAdapter.tr(text, objects);
108    }
109
110    public static Logger getLogger(String name) {
111        return loggingAdapter.getLogger(name);
112    }
113
114    public static Logger getLogger(Class<?> klass) {
115        return loggingAdapter.getLogger(klass.getSimpleName());
116    }
117
118    /**
119     * Get settings value for a certain key and provide a default value.
120     * @param key the identifier for the setting
121     * @param def the default value. For each call of get() with a given key, the
122     * default value must be the same. {@code def} may be null.
123     * @return the corresponding value if the property has been set before, {@code def} otherwise
124     */
125    public static String getSetting(String key, String def) {
126        return settingsAdapter.get(key, def);
127    }
128
129    /**
130     * Get settings value for a certain key and provide a default value.
131     * @param key the identifier for the setting
132     * @param def the default value. For each call of get() with a given key, the
133     * default value must be the same. {@code def} may be null.
134     * @return the corresponding value if the property has been set before, {@code def} otherwise
135     */
136    public static int getIntSetting(String key, int def) {
137        return Integer.parseInt(settingsAdapter.get(key, Integer.toString(def)));
138    }
139
140    /**
141     * Set a value for a certain setting.
142     * @param key the unique identifier for the setting
143     * @param value the value of the setting. Can be null or "" which both removes the key-value entry.
144     * @return {@code true}, if something has changed (i.e. value is different than before)
145     */
146    public static boolean putSetting(String key, String value) {
147        return settingsAdapter.put(key, value);
148    }
149
150    public static class DefaultBrowserAdapter implements BrowserAdapter {
151        @Override
152        public void openLink(String url) {
153            if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
154                try {
155                    Desktop.getDesktop().browse(new URI(url));
156                } catch (IOException e) {
157                    e.printStackTrace();
158                } catch (URISyntaxException e) {
159                    e.printStackTrace();
160                }
161            } else {
162                getLogger(FeatureAdapter.class).log(Level.SEVERE, tr("Opening link not supported on current platform (''{0}'')", url));
163            }
164        }
165    }
166
167    public static class DefaultImageAdapter implements ImageAdapter {
168        @Override
169        public BufferedImage read(URL input, boolean readMetadata, boolean enforceTransparency) throws IOException {
170            return ImageIO.read(input);
171        }
172    }
173
174    public static class DefaultTranslationAdapter implements TranslationAdapter {
175        @Override
176        public String tr(String text, Object... objects) {
177            return MessageFormat.format(text, objects);
178        }
179    }
180
181    public static class DefaultLoggingAdapter implements LoggingAdapter {
182        @Override
183        public Logger getLogger(String name) {
184            return Logger.getLogger(name);
185        }
186    }
187
188    /**
189     * Default settings adapter keeping settings in memory only.
190     */
191    public static class DefaultSettingsAdapter implements SettingsAdapter {
192        private final Map<String, String> settings = new TreeMap<>();
193
194        @Override
195        public String get(String key, String def) {
196            return settings.getOrDefault(key, def);
197        }
198
199        @Override
200        public boolean put(String key, String value) {
201            return !Objects.equals(value == null || value.isEmpty() ? settings.remove(key) : settings.put(key, value), value);
202        }
203    }
204}