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 }