001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.data.osm.visitor.paint;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Graphics2D;
007    import java.lang.reflect.Constructor;
008    import java.lang.reflect.InvocationTargetException;
009    import java.text.MessageFormat;
010    import java.util.ArrayList;
011    import java.util.Collections;
012    import java.util.Iterator;
013    import java.util.List;
014    
015    import org.openstreetmap.josm.Main;
016    import org.openstreetmap.josm.gui.NavigatableComponent;
017    import org.openstreetmap.josm.plugins.PluginHandler;
018    import org.openstreetmap.josm.tools.CheckParameterUtil;
019    
020    /**
021     * <p>MapRendererFactory manages a list of map renderer classes and associated
022     * meta data (display name, description).</p>
023     * 
024     * <p>Plugins can implement and supply their own map renderers.</p>
025     * <strong>Sample code in a plugin</strong>
026     * <pre>
027     * public class MyMapRenderer extends AbstractMapRenderer {
028     *    // ....
029     * }
030     * 
031     * // to be called when the plugin is created
032     * MapRendererFactory factory = MapRendererFactory.getInstance();
033     * factory.register(MyMapRenderer.class, "My map renderer", "This is is a fast map renderer");
034     * factory.activate(MyMapRenderer.class);
035     * 
036     * </pre>
037     *
038     */
039    public class MapRendererFactory {
040    
041        /** preference key for the renderer class name. Default: class name for {@link StyledMapRenderer}
042         * 
043         */
044        static public final String PREF_KEY_RENDERER_CLASS_NAME = "mappaint.renderer-class-name";
045    
046        static public class MapRendererFactoryException extends RuntimeException {
047            public MapRendererFactoryException() {
048            }
049    
050            public MapRendererFactoryException(String message, Throwable cause) {
051                super(message, cause);
052            }
053    
054            public MapRendererFactoryException(String message) {
055                super(message);
056            }
057    
058            public MapRendererFactoryException(Throwable cause) {
059                super(cause);
060            }
061        }
062    
063        static public class Descriptor {
064            private Class<? extends AbstractMapRenderer> renderer;
065            private String displayName;
066            private String description;
067    
068            public Descriptor(Class<? extends AbstractMapRenderer> renderer, String displayName, String description) {
069                this.renderer = renderer;
070                this.displayName  = displayName;
071                this.description = description;
072            }
073    
074            public Class<? extends AbstractMapRenderer> getRenderer() {
075                return renderer;
076            }
077    
078            public String getDisplayName() {
079                return displayName;
080            }
081    
082            public String getDescription() {
083                return description;
084            }
085        }
086    
087        static private MapRendererFactory instance;
088    
089        /**
090         * Replies the unique instance
091         * @return
092         */
093        public static MapRendererFactory getInstance() {
094            if (instance == null) {
095                instance = new MapRendererFactory();
096            }
097            return instance;
098        }
099    
100        private static Class<?> loadRendererClass(String className) {
101            for (ClassLoader cl : PluginHandler.getResourceClassLoaders()) {
102                try {
103                    return Class.forName(className, true, cl);
104                } catch (final ClassNotFoundException e) {
105                    // ignore
106                }
107            }
108            System.err.println(tr("Error: failed to load map renderer class ''{0}''. The class wasn''t found.", className));
109            return null;
110        }
111    
112        private final List<Descriptor> descriptors = new ArrayList<Descriptor>();
113        private Class<? extends AbstractMapRenderer> activeRenderer = null;
114    
115        private MapRendererFactory() {
116            registerDefaultRenderers();
117            String rendererClassName = Main.pref.get(PREF_KEY_RENDERER_CLASS_NAME, null);
118            if (rendererClassName != null) {
119                activateMapRenderer(rendererClassName);
120            } else {
121                activateDefault();
122            }
123        }
124    
125        private void activateMapRenderer(String rendererClassName){
126            Class<?> c = loadRendererClass(rendererClassName);
127            if (c == null){
128                System.err.println(tr("Can''t activate map renderer class ''{0}'', because the class wasn''t found.", rendererClassName));
129                System.err.println(tr("Activating the standard map renderer instead."));
130                activateDefault();
131            } else if (! AbstractMapRenderer.class.isAssignableFrom(c)) {
132                System.err.println(tr("Can''t activate map renderer class ''{0}'', because it isn''t a subclass of ''{1}''.", rendererClassName, AbstractMapRenderer.class.getName()));
133                System.err.println(tr("Activating the standard map renderer instead."));
134                activateDefault();
135            } else {
136                Class<? extends AbstractMapRenderer> renderer = c.asSubclass(AbstractMapRenderer.class);
137                if (! isRegistered(renderer)) {
138                    System.err.println(tr("Can''t activate map renderer class ''{0}'', because it isn''t registered as map renderer.", rendererClassName));
139                    System.err.println(tr("Activating the standard map renderer instead."));
140                    activateDefault();
141                } else {
142                    activate(renderer);
143                }
144            }
145        }
146    
147        private void registerDefaultRenderers() {
148            register(
149                    WireframeMapRenderer.class,
150                    tr("Wireframe Map Renderer"),
151                    tr("Renders the map as simple wire frame.")
152            );
153            register(
154                    StyledMapRenderer.class,
155                    tr("Styled Map Renderer"),
156                    tr("Renders the map using style rules in a set of style sheets.")
157            );
158        }
159    
160        /**
161         * <p>Replies true, if {@code Renderer} is already a registered map renderer
162         * class.</p>
163         * 
164         * @param renderer the map renderer class. Must not be null.
165         * @return true, if {@code Renderer} is already a registered map renderer
166         * class
167         * @throws IllegalArgumentException thrown if {@code renderer} is null
168         */
169        public boolean isRegistered(Class<? extends AbstractMapRenderer> renderer) throws IllegalArgumentException {
170            CheckParameterUtil.ensureParameterNotNull(renderer);
171            for (Descriptor d: descriptors) {
172                if (d.getRenderer().getName().equals(renderer.getName())) return true;
173            }
174            return false;
175        }
176    
177        /**
178         * <p>Registers a map renderer class.</p>
179         * 
180         * @param renderer the map renderer class. Must not be null.
181         * @param displayName the display name to be displayed in UIs (i.e. in the preference dialog)
182         * @param description the description
183         * @throws IllegalArgumentException thrown if {@code renderer} is null
184         * @throws IllegalStateException thrown if {@code renderer} is already registered
185         */
186        public void register(Class<? extends AbstractMapRenderer> renderer, String displayName, String description) throws IllegalArgumentException, IllegalStateException{
187            CheckParameterUtil.ensureParameterNotNull(renderer);
188            if (isRegistered(renderer))
189                throw new IllegalStateException(
190                        // no I18n - this is a technical message
191                        MessageFormat.format("Class ''{0}'' already registered a renderer", renderer.getName())
192                );
193            Descriptor d = new Descriptor(renderer, displayName, description);
194            descriptors.add(d);
195        }
196    
197    
198        /**
199         * <p>Unregisters a map renderer class.</p>
200         *
201         * <p>If the respective class is also the active renderer, the renderer is reset
202         * to the default renderer.</p>
203         * 
204         * @param renderer the map renderer class. Must not be null.
205         * 
206         */
207        public void unregister(Class<? extends AbstractMapRenderer> renderer) {
208            if (renderer == null) return;
209            if (!isRegistered(renderer)) return;
210            Iterator<Descriptor> it = descriptors.iterator();
211            while(it.hasNext()) {
212                Descriptor d = it.next();
213                if (d.getRenderer().getName().equals(renderer.getName())) {
214                    it.remove();
215                    break;
216                }
217            }
218            if (activeRenderer != null && activeRenderer.getName().equals(renderer.getName())) {
219                activateDefault();
220            }
221        }
222    
223        /**
224         * <p>Activates a map renderer class.</p>
225         *
226         * <p>The renderer class must already be registered.</p>
227         * 
228         * @param renderer the map renderer class. Must not be null.
229         * @throws IllegalArgumentException thrown if {@code renderer} is null
230         * @throws IllegalStateException thrown if {@code renderer} isn't registered yet
231         * 
232         */
233        public void activate(Class<? extends AbstractMapRenderer> renderer) throws IllegalArgumentException, IllegalStateException{
234            CheckParameterUtil.ensureParameterNotNull(renderer);
235            if (!isRegistered(renderer))
236                throw new IllegalStateException(
237                        // no I18n required
238                        MessageFormat.format("Class ''{0}'' not registered as renderer. Can''t activate it.", renderer.getName())
239                );
240            this.activeRenderer = renderer;
241            Main.pref.put(PREF_KEY_RENDERER_CLASS_NAME, activeRenderer.getName());
242    
243        }
244    
245        /**
246         * <p>Activates the default map renderer.</p>
247         *
248         * @throws IllegalStateException thrown if the default renderer {@link StyledMapRenderer} isn't registered
249         * 
250         */
251        public void activateDefault() throws IllegalStateException{
252            Class<? extends AbstractMapRenderer> defaultRenderer = StyledMapRenderer.class;
253            if (!isRegistered(defaultRenderer))
254                throw new IllegalStateException(
255                        MessageFormat.format("Class ''{0}'' not registered as renderer. Can''t activate default renderer.", defaultRenderer.getName())
256                );
257            activate(defaultRenderer);
258        }
259    
260        /**
261         * <p>Creates an instance of the currently active renderer.</p>
262         *
263         * @throws MapRendererFactoryException thrown if creating an instance fails
264         * @see AbstractMapRenderer#AbstractMapRenderer(Graphics2D, NavigatableComponent, boolean)
265         */
266        public AbstractMapRenderer createActiveRenderer(Graphics2D g, NavigatableComponent viewport, boolean isInactiveMode) throws MapRendererFactoryException{
267            try {
268                Constructor<?> c = activeRenderer.getConstructor(new Class<?>[]{Graphics2D.class, NavigatableComponent.class, boolean.class});
269                return AbstractMapRenderer.class.cast(c.newInstance(g, viewport, isInactiveMode));
270            } catch(NoSuchMethodException e){
271                throw new MapRendererFactoryException(e);
272            } catch (IllegalArgumentException e) {
273                throw new MapRendererFactoryException(e);
274            } catch (InstantiationException e) {
275                throw new MapRendererFactoryException(e);
276            } catch (IllegalAccessException e) {
277                throw new MapRendererFactoryException(e);
278            } catch (InvocationTargetException e) {
279                throw new MapRendererFactoryException(e.getCause());
280            }
281        }
282    
283        /**
284         * <p>Replies the (unmodifiable) list of map renderer descriptors.</p>
285         * 
286         * @return the descriptors
287         */
288        public List<Descriptor> getMapRendererDescriptors() {
289            return Collections.unmodifiableList(descriptors);
290        }
291    
292        /**
293         * <p>Replies true, if currently the wireframe map renderer is active. Otherwise,
294         * false.</p>
295         * 
296         * <p>There is a specific method for {@link WireframeMapRenderer} for legacy support.
297         * Until 03/2011 there were only two possible map renderers in JOSM: the wireframe
298         * renderer and the styled renderer. For the time being there are still UI elements
299         * (menu entries, etc.) which toggle between these two renderers only.</p>
300         * 
301         * @return true, if currently the wireframe map renderer is active. Otherwise,
302         * false
303         */
304        public boolean isWireframeMapRendererActive() {
305            return activeRenderer != null && activeRenderer.getName().equals(WireframeMapRenderer.class.getName());
306        }
307    }