Ignore:
Timestamp:
23.06.2009 22:03:37 (3 years ago)
Author:
Gubaer
Message:

new: MultiFetchServerObjectReader using APIs Multi Fetch method
update: now uses Multi Fetch to check for deleted primitives on the server
update: now uses Multi Fetch to update the selected primitives with the state from the server
fixed: cleaned up merging in MergeVisitor
new: conflict resolution dialog; now resolves conflicts due to different visibilities
new: replacement for realEqual() on OsmPrimitive and derived classes; realEqual now @deprecated
fixed: cleaning up OsmReader
fixed: progress indication in OsmApi

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMergeModel.java

    r1669 r1690  
    33 
    44import static org.openstreetmap.josm.gui.conflict.MergeDecisionType.UNDECIDED; 
     5import static org.openstreetmap.josm.tools.I18n.tr; 
    56 
    67import java.beans.PropertyChangeListener; 
    78import java.beans.PropertyChangeSupport; 
     9import java.io.IOException; 
     10import java.net.HttpURLConnection; 
    811import java.util.ArrayList; 
     12import java.util.HashMap; 
    913import java.util.List; 
    1014import java.util.Observable; 
    1115 
     16import javax.swing.JOptionPane; 
     17import javax.swing.text.html.HTML; 
     18 
     19import org.openstreetmap.josm.Main; 
    1220import org.openstreetmap.josm.command.Command; 
    1321import org.openstreetmap.josm.command.CoordinateConflictResolveCommand; 
    1422import org.openstreetmap.josm.command.DeletedStateConflictResolveCommand; 
     23import org.openstreetmap.josm.command.PurgePrimitivesCommand; 
     24import org.openstreetmap.josm.command.UndeletePrimitivesCommand; 
    1525import org.openstreetmap.josm.data.coor.LatLon; 
     26import org.openstreetmap.josm.data.osm.DataSet; 
    1627import org.openstreetmap.josm.data.osm.Node; 
    1728import org.openstreetmap.josm.data.osm.OsmPrimitive; 
     29import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 
     30import org.openstreetmap.josm.data.osm.Relation; 
     31import org.openstreetmap.josm.data.osm.RelationMember; 
     32import org.openstreetmap.josm.data.osm.Way; 
     33import org.openstreetmap.josm.gui.PleaseWaitRunnable; 
    1834import org.openstreetmap.josm.gui.conflict.MergeDecisionType; 
     35import org.openstreetmap.josm.gui.conflict.properties.PropertiesMerger.KeepMyVisibleStateAction; 
     36import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 
     37import org.openstreetmap.josm.io.OsmApi; 
     38import org.openstreetmap.josm.io.OsmApiException; 
     39import org.openstreetmap.josm.io.OsmServerObjectReader; 
     40import org.openstreetmap.josm.io.OsmTransferException; 
     41import org.xml.sax.SAXException; 
    1942 
    2043/** 
    2144 * This is the model for resolving conflicts in the properties of the 
    2245 * {@see OsmPrimitive}s. In particular, it represents conflicts in the coordiates of {@see Node}s and 
    23  * the deleted state of {@see OsmPrimitive}s. 
     46 * the deleted or visible state of {@see OsmPrimitive}s. 
    2447 * 
    2548 * This model is an {@see Observable}. It notifies registered {@see Observer}s whenever the 
     
    3154 * @see Node#getCoor() 
    3255 * @see OsmPrimitive#deleted 
     56 * @see OsmPrimitive#visible 
    3357 * 
    3458 */ 
     
    3761    static public final String RESOLVED_COMPLETELY_PROP = PropertiesMergeModel.class.getName() + ".resolvedCompletely"; 
    3862 
     63    private OsmPrimitive my; 
     64 
    3965    private LatLon myCoords; 
    4066    private LatLon theirCoords; 
     
    4369    private boolean myDeletedState; 
    4470    private boolean theirDeletedState; 
     71    private boolean myVisibleState; 
     72    private boolean theirVisibleState; 
    4573    private MergeDecisionType deletedMergeDecision; 
     74    private MergeDecisionType visibleMergeDecision; 
    4675    private final PropertyChangeSupport support; 
    4776    private boolean resolvedCompletely; 
     
    91120 
    92121    /** 
     122     * replies true if there is a  conflict in the visible state and if this conflict is 
     123     * resolved 
     124     * 
     125     * @return true if there is a conflict in the visible state and if this conflict is 
     126     * resolved; false, otherwise 
     127     */ 
     128    public boolean isDecidedVisibleState() { 
     129        return ! visibleMergeDecision.equals(UNDECIDED); 
     130    } 
     131 
     132    /** 
    93133     * replies true if the current decision for the coordinate conflict is <code>decision</code> 
    94134     * 
     
    111151 
    112152    /** 
     153     * replies true if the current decision for the visible state conflict is <code>decision</code> 
     154     * 
     155     * @return true if the current decision for the visible state conflict is <code>decision</code>; 
     156     *  false, otherwise 
     157     */ 
     158    public boolean isVisibleStateDecision(MergeDecisionType decision) { 
     159        return visibleMergeDecision.equals(decision); 
     160    } 
     161    /** 
    113162     * populates the model with the differences between my and their version 
    114163     * 
     
    117166     */ 
    118167    public void populate(OsmPrimitive my, OsmPrimitive their) { 
     168        this.my = my; 
    119169        if (my instanceof Node) { 
    120170            myCoords = ((Node)my).getCoor(); 
     
    128178        theirDeletedState = their.deleted; 
    129179 
     180        myVisibleState = my.visible; 
     181        theirVisibleState = their.visible; 
     182 
    130183        coordMergeDecision = UNDECIDED; 
    131184        deletedMergeDecision = UNDECIDED; 
     185        visibleMergeDecision = UNDECIDED; 
    132186        setChanged(); 
    133187        notifyObservers(); 
     
    209263    } 
    210264 
    211     public void decideDeletedStateConflict(MergeDecisionType decision) { 
     265 
     266    /** 
     267     * replies my visible state, 
     268     * @return my visible state 
     269     */ 
     270    public Boolean getMyVisibleState() { 
     271        return myVisibleState; 
     272    } 
     273 
     274    /** 
     275     * replies their visible state, 
     276     * @return their visible state 
     277     */ 
     278    public  Boolean getTheirVisibleState() { 
     279        return theirVisibleState; 
     280    } 
     281 
     282    /** 
     283     * replies the merged visible state; null, if the merge decision is 
     284     * {@see MergeDecisionType#UNDECIDED}. 
     285     *  
     286     * @return the merged visible state 
     287     */ 
     288    public Boolean getMergedVisibleState() { 
     289        switch(visibleMergeDecision) { 
     290        case KEEP_MINE: return myVisibleState; 
     291        case KEEP_THEIR: return theirVisibleState; 
     292        case UNDECIDED: return null; 
     293        } 
     294        // should not happen 
     295        return null; 
     296    } 
     297 
     298    /** 
     299     * decides the conflict between two deleted states 
     300     * @param decision the decision (must not be null) 
     301     *  
     302     * @throws IllegalArgumentException thrown, if decision is null 
     303     */ 
     304    public void decideDeletedStateConflict(MergeDecisionType decision) throws IllegalArgumentException{ 
     305        if (decision == null) 
     306            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision")); 
    212307        this.deletedMergeDecision = decision; 
     308        setChanged(); 
     309        notifyObservers(); 
     310        fireCompletelyResolved(); 
     311    } 
     312 
     313    /** 
     314     * decides the conflict between two visible states 
     315     * @param decision the decision (must not be null) 
     316     *  
     317     * @throws IllegalArgumentException thrown, if decision is null 
     318     */ 
     319    public void decideVisibleStateConflict(MergeDecisionType decision) throws IllegalArgumentException { 
     320        if (decision == null) 
     321            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision")); 
     322        this.visibleMergeDecision = decision; 
    213323        setChanged(); 
    214324        notifyObservers(); 
     
    242352 
    243353    /** 
     354     * replies true if my and their primitive have a conflict between 
     355     * their visible states 
     356     * 
     357     * @return true if my and their primitive have a conflict between 
     358     * their visible states 
     359     */ 
     360    public boolean hasVisibleStateConflict() { 
     361        return myVisibleState != theirVisibleState; 
     362    } 
     363 
     364    /** 
    244365     * replies true if all conflict in this model are resolved 
    245366     * 
     
    254375            ret = ret && ! deletedMergeDecision.equals(UNDECIDED); 
    255376        } 
     377        if (hasVisibleStateConflict()) { 
     378            ret = ret && ! visibleMergeDecision.equals(UNDECIDED); 
     379        } 
    256380        return ret; 
    257381    } 
     
    264388     * @return the list of commands 
    265389     */ 
    266     public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) { 
     390    public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) throws OperationCancelledException{ 
    267391        ArrayList<Command> cmds = new ArrayList<Command>(); 
     392        if (hasVisibleStateConflict() && isDecidedVisibleState()) { 
     393            if (isVisibleStateDecision(MergeDecisionType.KEEP_MINE)) { 
     394                try { 
     395                    UndeletePrimitivesCommand cmd = createUndeletePrimitiveCommand(my); 
     396                    if (cmd == null) 
     397                        throw new OperationCancelledException(); 
     398                    cmds.add(cmd); 
     399                } catch(OsmTransferException e) { 
     400                    handleExceptionWhileBuildingCommand(e); 
     401                    throw new OperationCancelledException(e); 
     402                } 
     403            } else if (isVisibleStateDecision(MergeDecisionType.KEEP_THEIR)) { 
     404                cmds.add(new PurgePrimitivesCommand(my)); 
     405            } 
     406        } 
    268407        if (hasCoordConflict() && isDecidedCoord()) { 
    269408            cmds.add(new CoordinateConflictResolveCommand((Node)my, (Node)their, coordMergeDecision)); 
     
    274413        return cmds; 
    275414    } 
     415 
     416    public OsmPrimitive getMyPrimitive() { 
     417        return my; 
     418    } 
     419 
     420    /** 
     421     *  
     422     * @param id 
     423     */ 
     424    protected void handleExceptionWhileBuildingCommand(Exception e) { 
     425        e.printStackTrace(); 
     426        String msg = e.getMessage() != null ? e.getMessage() : e.toString(); 
     427        msg = msg.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"); 
     428        JOptionPane.showMessageDialog( 
     429                Main.parent, 
     430                tr("<html>An error occurred while communicating with the server<br>" 
     431                        + "Details: {0}</html>", 
     432                        msg 
     433                ), 
     434                tr("Communication with server failed"), 
     435                JOptionPane.ERROR_MESSAGE 
     436        ); 
     437    } 
     438 
     439    /** 
     440     * User has decided to keep his local version of a primitive which had been deleted 
     441     * on the server 
     442     *  
     443     * @param id the primitive id 
     444     */ 
     445    protected UndeletePrimitivesCommand createUndeletePrimitiveCommand(OsmPrimitive my) throws OsmTransferException { 
     446        if (my instanceof Node) 
     447            return createUndeleteNodeCommand((Node)my); 
     448        else if (my instanceof Way) 
     449            return createUndeleteWayCommand((Way)my); 
     450        else if (my instanceof Relation) 
     451            return createUndeleteRelationCommand((Relation)my); 
     452        return null; 
     453    } 
     454    /** 
     455     * Undelete a node which is already deleted on the server. The API 
     456     * doesn't offer a call for "undeleting" a node. We therefore create 
     457     * a clone of the node which we flag as new. On the next upload the 
     458     * server will assign the node a new id. 
     459     *  
     460     * @param node the node to undelete 
     461     */ 
     462    protected UndeletePrimitivesCommand  createUndeleteNodeCommand(Node node) { 
     463        return new UndeletePrimitivesCommand(node); 
     464    } 
     465 
     466    /** 
     467     * displays a confirmation message. The user has to confirm that additional dependent 
     468     * nodes should be undeleted too. 
     469     *  
     470     * @param way  the way 
     471     * @param dependent a list of dependent nodes which have to be undelete too 
     472     * @return true, if the user confirms; false, otherwise 
     473     */ 
     474    protected boolean confirmUndeleteDependentPrimitives(Way way, ArrayList<OsmPrimitive> dependent) { 
     475        String [] options = { 
     476                tr("Yes, undelete them too"), 
     477                tr("No, cancel operation") 
     478        }; 
     479        int ret = JOptionPane.showOptionDialog( 
     480                Main.parent, 
     481                tr("<html>There are {0} additional nodes used by way {1}<br>" 
     482                        + "which are deleted on the server.<br>" 
     483                        + "<br>" 
     484                        + "Do you want to undelete these nodes too?</html>", 
     485                        Long.toString(dependent.size()), Long.toString(way.id)), 
     486                        tr("Undelete additional nodes?"), 
     487                        JOptionPane.YES_NO_OPTION, 
     488                        JOptionPane.QUESTION_MESSAGE, 
     489                        null, 
     490                        options, 
     491                        options[0] 
     492        ); 
     493 
     494        switch(ret) { 
     495        case JOptionPane.CLOSED_OPTION: return false; 
     496        case JOptionPane.YES_OPTION: return true; 
     497        case JOptionPane.NO_OPTION: return false; 
     498        } 
     499        return false; 
     500 
     501    } 
     502 
     503    protected boolean confirmUndeleteDependentPrimitives(Relation r, ArrayList<OsmPrimitive> dependent) { 
     504        String [] options = { 
     505                tr("Yes, undelete them too"), 
     506                tr("No, cancel operation") 
     507        }; 
     508        int ret = JOptionPane.showOptionDialog( 
     509                Main.parent, 
     510                tr("<html>There are {0} additional primitives referred to by relation {1}<br>" 
     511                        + "which are deleted on the server.<br>" 
     512                        + "<br>" 
     513                        + "Do you want to undelete them too?</html>", 
     514                        Long.toString(dependent.size()), Long.toString(r.id)), 
     515                        tr("Undelete dependent primitives?"), 
     516                        JOptionPane.YES_NO_OPTION, 
     517                        JOptionPane.QUESTION_MESSAGE, 
     518                        null, 
     519                        options, 
     520                        options[0] 
     521        ); 
     522 
     523        switch(ret) { 
     524        case JOptionPane.CLOSED_OPTION: return false; 
     525        case JOptionPane.YES_OPTION: return true; 
     526        case JOptionPane.NO_OPTION: return false; 
     527        } 
     528        return false; 
     529 
     530    } 
     531 
     532    /** 
     533     * Creates the undelete command for a way which is already deleted on the server. 
     534     *  
     535     * This method also checks whether there are additional nodes referred to by 
     536     * this way which are deleted on the server too. 
     537     *  
     538     * @param way the way to undelete 
     539     * @return the undelete command 
     540     * @see #createUndeleteNodeCommand(Node) 
     541     */ 
     542    protected UndeletePrimitivesCommand createUndeleteWayCommand(final Way way) throws OsmTransferException { 
     543 
     544        HashMap<Long,OsmPrimitive> candidates = new HashMap<Long,OsmPrimitive>(); 
     545        for (Node n : way.nodes) { 
     546            if (n.id > 0 && ! candidates.values().contains(n)) { 
     547                candidates.put(n.id, n); 
     548            } 
     549        } 
     550        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 
     551        reader.append(candidates.values()); 
     552        DataSet ds = reader.parseOsm(); 
     553 
     554        ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>(); 
     555        for (OsmPrimitive their : ds.allPrimitives()) { 
     556            if (candidates.keySet().contains(their.id) && ! their.visible) { 
     557                toDelete.add(candidates.get(their.id)); 
     558            } 
     559        } 
     560        if (!toDelete.isEmpty()) { 
     561            if (! confirmUndeleteDependentPrimitives(way, toDelete)) 
     562                // FIXME: throw exception ? 
     563                return null; 
     564        } 
     565        toDelete.add(way); 
     566        return new UndeletePrimitivesCommand(toDelete); 
     567    } 
     568 
     569    /** 
     570     * Creates an undelete command for a relation which is already deleted on the server. 
     571     *  
     572     * This method  checks whether there are additional primitives referred to by 
     573     * this relation which are already deleted on the server. 
     574     *  
     575     * @param r the relation 
     576     * @return the undelete command 
     577     * @see #createUndeleteNodeCommand(Node) 
     578     */ 
     579    protected UndeletePrimitivesCommand createUndeleteRelationCommand(final Relation r) throws OsmTransferException { 
     580 
     581        HashMap<Long,OsmPrimitive> candidates = new HashMap<Long, OsmPrimitive>(); 
     582        for (RelationMember m : r.members) { 
     583            if (m.member.id > 0 && !candidates.values().contains(m.member)) { 
     584                candidates.put(m.member.id,m.member); 
     585            } 
     586        } 
     587 
     588        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 
     589        reader.append(candidates.values()); 
     590        DataSet ds = reader.parseOsm(); 
     591 
     592        ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>(); 
     593        for (OsmPrimitive their : ds.allPrimitives()) { 
     594            if (candidates.keySet().contains(their.id) && ! their.visible) { 
     595                toDelete.add(candidates.get(their.id)); 
     596            } 
     597        } 
     598        if (!toDelete.isEmpty()) { 
     599            if (! confirmUndeleteDependentPrimitives(r, toDelete)) 
     600                // FIXME: throw exception ? 
     601                return null; 
     602        } 
     603        toDelete.add(r); 
     604        return new UndeletePrimitivesCommand(toDelete); 
     605    } 
     606 
    276607} 
Note: See TracChangeset for help on using the changeset viewer.