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
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                    Constructor<RelationEditor> con = e.getConstructor(Relation.class, Collection.class);
108                    return con.newInstance(layer, r, selectedMembers);
109                }
110            } catch (Exception ex) {
111                Main.warn(ex);
112            }
113        }
114        if (RelationDialogManager.getRelationDialogManager().isOpenInEditor(layer, r))
115            return RelationDialogManager.getRelationDialogManager().getEditorForRelation(layer, r);
116        else {
117            RelationEditor editor = new GenericRelationEditor(layer, r, selectedMembers);
118            RelationDialogManager.getRelationDialogManager().positionOnScreen(editor);
119            RelationDialogManager.getRelationDialogManager().register(layer, r, editor);
120            return editor;
121        }
122    }
123
124    /**
125     * updates the title of the relation editor
126     */
127    protected void updateTitle() {
128        if (getRelation() == null) {
129            setTitle(tr("Create new relation in layer ''{0}''", layer.getName()));
130        } else if (getRelation().isNew()) {
131            setTitle(tr("Edit new relation in layer ''{0}''", layer.getName()));
132        } else {
133            setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.getId(), layer.getName()));
134        }
135    }
136
137    @Override
138    public final Relation getRelation() {
139        return relation;
140    }
141
142    @Override
143    public final void setRelation(Relation relation) {
144        setRelationSnapshot((relation == null) ? null : new Relation(relation));
145        Relation oldValue = this.relation;
146        this.relation = relation;
147        if (this.relation != oldValue) {
148            support.firePropertyChange(RELATION_PROP, oldValue, this.relation);
149        }
150        updateTitle();
151    }
152
153    /**
154     * Replies the {@link OsmDataLayer} in whose context this relation editor is open
155     *
156     * @return the {@link OsmDataLayer} in whose context this relation editor is open
157     */
158    protected final OsmDataLayer getLayer() {
159        return layer;
160    }
161
162    @Override
163    public final Relation getRelationSnapshot() {
164        return relationSnapshot;
165    }
166
167    protected final void setRelationSnapshot(Relation snapshot) {
168        Relation oldValue = relationSnapshot;
169        relationSnapshot = snapshot;
170        if (relationSnapshot != oldValue) {
171            support.firePropertyChange(RELATION_SNAPSHOT_PROP, oldValue, relationSnapshot);
172        }
173    }
174
175    @Override
176    public final boolean isDirtyRelation() {
177        return !relation.hasEqualSemanticAttributes(relationSnapshot);
178    }
179
180    /* ----------------------------------------------------------------------- */
181    /* property change support                                                 */
182    /* ----------------------------------------------------------------------- */
183
184    @Override
185    public final void addPropertyChangeListener(PropertyChangeListener listener) {
186        this.support.addPropertyChangeListener(listener);
187    }
188
189    @Override
190    public final void removePropertyChangeListener(PropertyChangeListener listener) {
191        this.support.removePropertyChangeListener(listener);
192    }
193
194    @Override
195    public void dispose() {
196        layer.setRecentRelation(relation);
197        super.dispose();
198    }
199}