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