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}