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