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 static public final String RELATION_PROP = RelationEditor.class.getName() + ".relation"; 027 028 /** the property name for the current relation snapshot 029 * @see #getRelationSnapshot() 030 */ 031 static public 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<Class<RelationEditor>>(); 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 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 Relation relationSnapshot; 059 060 /** the data layer the relation belongs to */ 061 private 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 RelationEditor editor = con.newInstance(layer, r, selectedMembers); 092 return editor; 093 } 094 } catch (Exception ex) { 095 // plod on 096 } 097 } 098 if (RelationDialogManager.getRelationDialogManager().isOpenInEditor(layer, r)) 099 return RelationDialogManager.getRelationDialogManager().getEditorForRelation(layer, r); 100 else { 101 RelationEditor editor = new GenericRelationEditor(layer, r, selectedMembers); 102 RelationDialogManager.getRelationDialogManager().positionOnScreen(editor); 103 RelationDialogManager.getRelationDialogManager().register(layer, r, editor); 104 return editor; 105 } 106 } 107 108 /** 109 * Creates a new relation editor 110 * 111 * @param layer the {@link OsmDataLayer} in whose context a relation is edited. Must not be null. 112 * @param relation the relation. Can be null if a new relation is to be edited. 113 * @param selectedMembers a collection of members in <code>relation</code> which the editor 114 * should display selected when the editor is first displayed on screen 115 * @throws IllegalArgumentException thrown if layer is null 116 */ 117 protected RelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers) throws IllegalArgumentException{ 118 // Initalizes ExtendedDialog 119 super(Main.parent, 120 "", 121 new String[] { tr("Apply Changes"), tr("Cancel")}, 122 false, 123 false 124 ); 125 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 126 this.layer = layer; 127 setRelation(relation); 128 } 129 130 /** 131 * updates the title of the relation editor 132 */ 133 protected void updateTitle() { 134 if (getRelation() == null) { 135 setTitle(tr("Create new relation in layer ''{0}''", layer.getName())); 136 } else if (getRelation().isNew()) { 137 setTitle(tr("Edit new relation in layer ''{0}''", layer.getName())); 138 } else { 139 setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.getId(), layer.getName())); 140 } 141 } 142 /** 143 * Replies the currently edited relation 144 * 145 * @return the currently edited relation 146 */ 147 protected Relation getRelation() { 148 return relation; 149 } 150 151 /** 152 * Sets the currently edited relation. Creates a snapshot of the current 153 * state of the relation. See {@link #getRelationSnapshot()} 154 * 155 * @param relation the relation 156 */ 157 protected void setRelation(Relation relation) { 158 setRelationSnapshot((relation == null) ? null : new Relation(relation)); 159 Relation oldValue = this.relation; 160 this.relation = relation; 161 if (this.relation != oldValue) { 162 support.firePropertyChange(RELATION_PROP, oldValue, this.relation); 163 } 164 updateTitle(); 165 } 166 167 /** 168 * Replies the {@link OsmDataLayer} in whose context this relation editor is 169 * open 170 * 171 * @return the {@link OsmDataLayer} in whose context this relation editor is 172 * open 173 */ 174 protected OsmDataLayer getLayer() { 175 return layer; 176 } 177 178 /** 179 * Replies the state of the edited relation when the editor has been launched 180 * 181 * @return the state of the edited relation when the editor has been launched 182 */ 183 protected Relation getRelationSnapshot() { 184 return relationSnapshot; 185 } 186 187 protected void setRelationSnapshot(Relation snapshot) { 188 Relation oldValue = relationSnapshot; 189 relationSnapshot = snapshot; 190 if (relationSnapshot != oldValue) { 191 support.firePropertyChange(RELATION_SNAPSHOT_PROP, oldValue, relationSnapshot); 192 } 193 } 194 195 /** 196 * Replies true if the currently edited relation has been changed elsewhere. 197 * 198 * In this case a relation editor can't apply updates to the relation directly. Rather, 199 * it has to create a conflict. 200 * 201 * @return true if the currently edited relation has been changed elsewhere. 202 */ 203 protected boolean isDirtyRelation() { 204 return ! relation.hasEqualSemanticAttributes(relationSnapshot); 205 } 206 207 /* ----------------------------------------------------------------------- */ 208 /* property change support */ 209 /* ----------------------------------------------------------------------- */ 210 final private PropertyChangeSupport support = new PropertyChangeSupport(this); 211 212 @Override 213 public void addPropertyChangeListener(PropertyChangeListener listener) { 214 this.support.addPropertyChangeListener(listener); 215 } 216 217 @Override 218 public void removePropertyChangeListener(PropertyChangeListener listener) { 219 this.support.removePropertyChangeListener(listener); 220 } 221}