001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.projection; 003 004import java.lang.ref.WeakReference; 005import java.util.List; 006import java.util.Objects; 007import java.util.concurrent.CopyOnWriteArrayList; 008 009import org.openstreetmap.josm.data.Bounds; 010import org.openstreetmap.josm.tools.CheckParameterUtil; 011 012/** 013 * Registry for a single, global projection instance. 014 * @since 14120 015 */ 016public final class ProjectionRegistry { 017 018 /** 019 * The projection method used. 020 * Use {@link #getProjection()} and {@link #setProjection(Projection)} for access. 021 * Use {@link #setProjection(Projection)} in order to trigger a projection change event. 022 */ 023 private static volatile Projection proj; 024 025 private static ProjectionBoundsProvider boundsProvider; 026 027 /* 028 * Keep WeakReferences to the listeners. This relieves clients from the burden of 029 * explicitly removing the listeners and allows us to transparently register every 030 * created dataset as projection change listener. 031 */ 032 private static final List<WeakReference<ProjectionChangeListener>> listeners = new CopyOnWriteArrayList<>(); 033 034 private ProjectionRegistry() { 035 // hide constructor 036 } 037 038 /** 039 * Replies the current projection. 040 * 041 * @return the currently active projection 042 */ 043 public static Projection getProjection() { 044 return proj; 045 } 046 047 /** 048 * Sets the current projection 049 * 050 * @param p the projection 051 */ 052 public static void setProjection(Projection p) { 053 CheckParameterUtil.ensureParameterNotNull(p); 054 Projection oldValue = proj; 055 Bounds b = boundsProvider != null ? boundsProvider.getRealBounds() : null; 056 proj = p; 057 fireProjectionChanged(oldValue, proj, b); 058 } 059 060 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) { 061 if ((newValue == null ^ oldValue == null) 062 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) { 063 listeners.removeIf(x -> x.get() == null); 064 listeners.stream().map(WeakReference::get).filter(Objects::nonNull).forEach(x -> x.projectionChanged(oldValue, newValue)); 065 if (newValue != null && oldBounds != null && boundsProvider != null) { 066 boundsProvider.restoreOldBounds(oldBounds); 067 } 068 /* TODO - remove layers with fixed projection */ 069 } 070 } 071 072 /** 073 * Register a projection change listener. 074 * The listener is registered to be weak, so keep a reference of it if you want it to be preserved. 075 * 076 * @param listener the listener. Ignored if <code>null</code>. 077 */ 078 public static void addProjectionChangeListener(ProjectionChangeListener listener) { 079 if (listener == null) return; 080 for (WeakReference<ProjectionChangeListener> wr : listeners) { 081 // already registered ? => abort 082 if (wr.get() == listener) return; 083 } 084 listeners.add(new WeakReference<>(listener)); 085 } 086 087 /** 088 * Removes a projection change listener. 089 * 090 * @param listener the listener. Ignored if <code>null</code>. 091 */ 092 public static void removeProjectionChangeListener(ProjectionChangeListener listener) { 093 if (listener == null) return; 094 // remove the listener - and any other listener which got garbage collected in the meantime 095 listeners.removeIf(wr -> wr.get() == null || wr.get() == listener); 096 } 097 098 /** 099 * Remove all projection change listeners. For testing purposes only. 100 */ 101 public static void clearProjectionChangeListeners() { 102 listeners.clear(); 103 } 104 105 /** 106 * Returns the bounds provider called in projection events. 107 * @return the bounds provider 108 */ 109 public static ProjectionBoundsProvider getBoundsProvider() { 110 return boundsProvider; 111 } 112 113 /** 114 * Sets the bounds provider called in projection events. Must not be null 115 * @param provider the bounds provider 116 */ 117 public static void setboundsProvider(ProjectionBoundsProvider provider) { 118 boundsProvider = Objects.requireNonNull(provider); 119 } 120}