001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.dialogs.relation;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.beans.PropertyChangeListener;
007    import java.beans.PropertyChangeSupport;
008    import java.lang.reflect.Constructor;
009    import java.lang.reflect.Method;
010    import java.util.ArrayList;
011    import java.util.Collection;
012    
013    import org.openstreetmap.josm.Main;
014    import org.openstreetmap.josm.data.osm.Relation;
015    import org.openstreetmap.josm.data.osm.RelationMember;
016    import org.openstreetmap.josm.gui.ExtendedDialog;
017    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
018    import org.openstreetmap.josm.tools.CheckParameterUtil;
019    
020    public abstract class RelationEditor extends ExtendedDialog {
021        /** the property name for the current relation.
022         * @see #setRelation(Relation)
023         * @see #getRelation()
024         */
025        static public final String RELATION_PROP = RelationEditor.class.getName() + ".relation";
026    
027        /** the property name for the current relation snapshot
028         * @see #getRelationSnapshot()
029         */
030        static public final String RELATION_SNAPSHOT_PROP = RelationEditor.class.getName() + ".relationSnapshot";
031    
032        /** the list of registered relation editor classes */
033        private static ArrayList<Class<RelationEditor>> editors = new ArrayList<Class<RelationEditor>>();
034    
035        /**
036         * Registers a relation editor class. Depending on the type of relation to be edited
037         * {@link #getEditor(OsmDataLayer, Relation, Collection)} will create an instance of
038         * this class.
039         *
040         * @param clazz the class
041         */
042        public void registerRelationEditor(Class<RelationEditor> clazz) {
043            if (clazz == null) return;
044            if (!editors.contains(clazz)) {
045                editors.add(clazz);
046            }
047        }
048    
049        /**
050         * The relation that this editor is working on.
051         */
052        private Relation relation;
053    
054        /**
055         * The version of the relation when editing is started.  This is
056         * null if a new relation is created. */
057        private Relation relationSnapshot;
058    
059        /** the data layer the relation belongs to */
060        private OsmDataLayer layer;
061    
062        /**
063         * This is a factory method that creates an appropriate RelationEditor
064         * instance suitable for editing the relation that was passed in as an
065         * argument.
066         *
067         * This method is guaranteed to return a working RelationEditor. If no
068         * specific editor has been registered for the type of relation, then
069         * a generic editor will be returned.
070         *
071         * Editors can be registered by adding their class to the static list "editors"
072         * in the RelationEditor class. When it comes to editing a relation, all
073         * registered editors are queried via their static "canEdit" method whether they
074         * feel responsible for that kind of relation, and if they return true
075         * then an instance of that class will be used.
076         *
077         * @param layer the data layer the relation is a member of
078         * @param r the relation to be edited
079         * @param selectedMembers a collection of relation members which shall be selected when the
080         * editor is first launched
081         * @return an instance of RelationEditor suitable for editing that kind of relation
082         */
083        public static RelationEditor getEditor(OsmDataLayer layer, Relation r, Collection<RelationMember> selectedMembers) {
084            for (Class<RelationEditor> e : editors) {
085                try {
086                    Method m = e.getMethod("canEdit", Relation.class);
087                    Boolean canEdit = (Boolean) m.invoke(null, r);
088                    if (canEdit) {
089                        Constructor<RelationEditor> con = e.getConstructor(Relation.class, Collection.class);
090                        RelationEditor editor = con.newInstance(layer, r, selectedMembers);
091                        return editor;
092                    }
093                } catch (Exception ex) {
094                    // plod on
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 thrown if layer is null
115         */
116        protected RelationEditor(OsmDataLayer layer, Relation relation, Collection<RelationMember> selectedMembers)  throws IllegalArgumentException{
117            // Initalizes ExtendedDialog
118            super(Main.parent,
119                    "",
120                    new String[] { tr("Apply Changes"), tr("Cancel")},
121                    false,
122                    false
123            );
124            CheckParameterUtil.ensureParameterNotNull(layer, "layer");
125            this.layer = layer;
126            setRelation(relation);
127        }
128    
129        /**
130         * updates the title of the relation editor
131         */
132        protected void updateTitle() {
133            if (getRelation() == null) {
134                setTitle(tr("Create new relation in layer ''{0}''", layer.getName()));
135            } else if (getRelation().isNew()) {
136                setTitle(tr("Edit new relation in layer ''{0}''", layer.getName()));
137            } else {
138                setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.getId(), layer.getName()));
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        final private 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    }