001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.beans.PropertyChangeListener; 007import java.beans.PropertyChangeSupport; 008import java.lang.reflect.Constructor; 009import java.lang.reflect.Method; 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.List; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.RelationMember; 017import org.openstreetmap.josm.gui.ExtendedDialog; 018import org.openstreetmap.josm.gui.layer.OsmDataLayer; 019import org.openstreetmap.josm.tools.CheckParameterUtil; 020 021public abstract class RelationEditor extends ExtendedDialog { 022 /** the property name for the current relation. 023 * @see #setRelation(Relation) 024 * @see #getRelation() 025 */ 026 public static final String RELATION_PROP = RelationEditor.class.getName() + ".relation"; 027 028 /** the property name for the current relation snapshot 029 * @see #getRelationSnapshot() 030 */ 031 public static final String RELATION_SNAPSHOT_PROP = RelationEditor.class.getName() + ".relationSnapshot"; 032 033 /** the list of registered relation editor classes */ 034 private static List<Class<RelationEditor>> editors = new ArrayList<>(); 035 036 /** 037 * Registers a relation editor class. Depending on the type of relation to be edited 038 * {@link #getEditor(OsmDataLayer, Relation, Collection)} will create an instance of 039 * this class. 040 * 041 * @param clazz the class 042 */ 043 public void registerRelationEditor(Class<RelationEditor> clazz) { 044 if (clazz == null) return; 045 if (!editors.contains(clazz)) { 046 editors.add(clazz); 047 } 048 } 049 050 /** 051 * The relation that this editor is working on. 052 */ 053 private transient Relation relation; 054 055 /** 056 * The version of the relation when editing is started. This is 057 * null if a new relation is created. */ 058 private transient Relation relationSnapshot; 059 060 /** the data layer the relation belongs to */ 061 private final transient OsmDataLayer layer; 062 063 /** 064 * This is a factory method that creates an appropriate RelationEditor 065 * instance suitable for editing the relation that was passed in as an 066 * argument. 067 * 068 * This method is guaranteed to return a working RelationEditor. If no 069 * specific editor has been registered for the type of relation, then 070 * a generic editor will be returned. 071 * 072 * Editors can be registered by adding their class to the static list "editors" 073 * in the RelationEditor class. When it comes to editing a relation, all 074 * registered editors are queried via their static "canEdit" method whether they 075 * feel responsible for that kind of relation, and if they return true 076 * then an instance of that class will be used. 077 * 078 * @param layer the data layer the relation is a member of 079 * @param r the relation to be edited 080 * @param selectedMembers a collection of relation members which shall be selected when the 081 * editor is first launched 082 * @return an instance of RelationEditor suitable for editing that kind of relation 083 */ 084 public static RelationEditor getEditor(OsmDataLayer layer, Relation r, Collection<RelationMember> selectedMembers) { 085 for (Class<RelationEditor> e : editors) { 086 try { 087 Method m = e.getMethod("canEdit", Relation.class); 088 Boolean canEdit = (Boolean) m.invoke(null, r); 089 if (canEdit) { 090 Constructor<RelationEditor> con = e.getConstructor(Relation.class, Collection.class); 091 return con.newInstance(layer, r, selectedMembers); 092 } 093 } catch (Exception ex) { 094 Main.warn(ex); 095 } 096 } 097 if (RelationDialogManager.getRelationDialogManager().isOpenInEditor(layer, r)) 098 return RelationDialogManager.getRelationDialogManager().getEditorForRelation(layer, r); 099 else { 100 RelationEditor editor = new GenericRelationEditor(layer, r, selectedMembers); 101 RelationDialogManager.getRelationDialogManager().positionOnScreen(editor); 102 RelationDialogManager.getRelationDialogManager().register(layer, r, editor); 103 return editor; 104 } 105 } 106 107 /** 108 * Creates a new relation editor 109 * 110 * @param layer the {@link OsmDataLayer} in whose context a relation is edited. Must not be null. 111 * @param relation the relation. Can be null if a new relation is to be edited. 112 * @param selectedMembers a collection of members in <code>relation</code> which the editor 113 * should display selected when the editor is first displayed on screen 114 * @throws IllegalArgumentException if layer is null 115 */ 116 protected RelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) { 117 super(Main.parent, 118 "", 119 new String[] {tr("Apply Changes"), tr("Cancel")}, 120 false, 121 false 122 ); 123 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 124 this.layer = layer; 125 setRelation(relation); 126 } 127 128 /** 129 * updates the title of the relation editor 130 */ 131 protected void updateTitle() { 132 if (getRelation() == null) { 133 setTitle(tr("Create new relation in layer ''{0}''", layer.getName())); 134 } else if (getRelation().isNew()) { 135 setTitle(tr("Edit new relation in layer ''{0}''", layer.getName())); 136 } else { 137 setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.getId(), layer.getName())); 138 } 139 } 140 141 /** 142 * Replies the currently edited relation 143 * 144 * @return the currently edited relation 145 */ 146 protected Relation getRelation() { 147 return relation; 148 } 149 150 /** 151 * Sets the currently edited relation. Creates a snapshot of the current 152 * state of the relation. See {@link #getRelationSnapshot()} 153 * 154 * @param relation the relation 155 */ 156 protected void setRelation(Relation relation) { 157 setRelationSnapshot((relation == null) ? null : new Relation(relation)); 158 Relation oldValue = this.relation; 159 this.relation = relation; 160 if (this.relation != oldValue) { 161 support.firePropertyChange(RELATION_PROP, oldValue, this.relation); 162 } 163 updateTitle(); 164 } 165 166 /** 167 * Replies the {@link OsmDataLayer} in whose context this relation editor is 168 * open 169 * 170 * @return the {@link OsmDataLayer} in whose context this relation editor is 171 * open 172 */ 173 protected OsmDataLayer getLayer() { 174 return layer; 175 } 176 177 /** 178 * Replies the state of the edited relation when the editor has been launched 179 * 180 * @return the state of the edited relation when the editor has been launched 181 */ 182 protected Relation getRelationSnapshot() { 183 return relationSnapshot; 184 } 185 186 protected void setRelationSnapshot(Relation snapshot) { 187 Relation oldValue = relationSnapshot; 188 relationSnapshot = snapshot; 189 if (relationSnapshot != oldValue) { 190 support.firePropertyChange(RELATION_SNAPSHOT_PROP, oldValue, relationSnapshot); 191 } 192 } 193 194 /** 195 * Replies true if the currently edited relation has been changed elsewhere. 196 * 197 * In this case a relation editor can't apply updates to the relation directly. Rather, 198 * it has to create a conflict. 199 * 200 * @return true if the currently edited relation has been changed elsewhere. 201 */ 202 protected boolean isDirtyRelation() { 203 return !relation.hasEqualSemanticAttributes(relationSnapshot); 204 } 205 206 /* ----------------------------------------------------------------------- */ 207 /* property change support */ 208 /* ----------------------------------------------------------------------- */ 209 private final PropertyChangeSupport support = new PropertyChangeSupport(this); 210 211 @Override 212 public void addPropertyChangeListener(PropertyChangeListener listener) { 213 this.support.addPropertyChangeListener(listener); 214 } 215 216 @Override 217 public void removePropertyChangeListener(PropertyChangeListener listener) { 218 this.support.removePropertyChangeListener(listener); 219 } 220}