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