Changeset 11627 in josm


Ignore:
Timestamp:
2017-02-26T00:59:32+01:00 (20 months ago)
Author:
Don-vip
Message:

fix #3346 - improve drastically the performance of fixing duplicate nodes by:

  • caching data sources area computation
  • moving layer invalidation from UndoRedoHandler.addNoRedraw to UndoRedoHandler.add
  • avoiding any EDT call when building tag conflict dialog if it's not meant to be displayed
Location:
trunk/src/org/openstreetmap/josm
Files:
16 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/UpdateDataAction.java

    r10382 r11627  
    5151
    5252        List<Area> areas = new ArrayList<>();
    53         for (DataSource ds : editLayer.data.dataSources) {
     53        for (DataSource ds : editLayer.data.getDataSources()) {
    5454            areas.add(new Area(ds.bounds.asRect()));
    5555        }
  • trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java

    r11553 r11627  
    343343                }
    344344                // need to synthesize a download bounds lest the visual indication of downloaded area doesn't work
    345                 dataSet.dataSources.add(new DataSource(currentBounds != null ? currentBounds :
     345                dataSet.addDataSource(new DataSource(currentBounds != null ? currentBounds :
    346346                    new Bounds(LatLon.ZERO), "OpenStreetMap server"));
    347347            }
  • trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java

    r11553 r11627  
    5252        CheckParameterUtil.ensureParameterNotNull(c, "c");
    5353        c.executeCommand();
    54         c.invalidateAffectedLayers();
    5554        commands.add(c);
    5655        // Limit the number of commands in the undo list.
     
    8180        }
    8281        addNoRedraw(c);
     82        c.invalidateAffectedLayers();
    8383        afterAdd();
    8484
  • trunk/src/org/openstreetmap/josm/data/osm/DataSet.java

    r11440 r11627  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.geom.Area;
    67import java.util.ArrayList;
    78import java.util.Arrays;
     
    2526
    2627import org.openstreetmap.josm.Main;
     28import org.openstreetmap.josm.data.Bounds;
    2729import org.openstreetmap.josm.data.Data;
    2830import org.openstreetmap.josm.data.DataSource;
     
    127129    private final Object selectionLock = new Object();
    128130
     131    private Area cachedDataSourceArea;
     132    private List<Bounds> cachedDataSourceBounds;
     133
    129134    /**
    130135     * Constructs a new {@code DataSet}.
     
    184189            copyFrom.getReadLock().unlock();
    185190        }
     191    }
     192
     193    /**
     194     * Adds a new data source.
     195     * @param source data source to add
     196     * @return {@code true} if the collection changed as a result of the call
     197     * @since 11626
     198     */
     199    public synchronized boolean addDataSource(DataSource source) {
     200        return addDataSources(Collections.singleton(source));
     201    }
     202
     203    /**
     204     * Adds new data sources.
     205     * @param sources data sources to add
     206     * @return {@code true} if the collection changed as a result of the call
     207     * @since 11626
     208     */
     209    public synchronized boolean addDataSources(Collection<DataSource> sources) {
     210        boolean changed = dataSources.addAll(sources);
     211        if (changed) {
     212            cachedDataSourceArea = null;
     213            cachedDataSourceBounds = null;
     214        }
     215        return changed;
    186216    }
    187217
     
    917947            fireSelectionChanged();
    918948        }
     949    }
     950
     951    @Override
     952    public synchronized Area getDataSourceArea() {
     953        if (cachedDataSourceArea == null) {
     954            cachedDataSourceArea = Data.super.getDataSourceArea();
     955        }
     956        return cachedDataSourceArea;
     957    }
     958
     959    @Override
     960    public synchronized List<Bounds> getDataSourceBounds() {
     961        if (cachedDataSourceBounds == null) {
     962            cachedDataSourceBounds = Data.super.getDataSourceBounds();
     963        }
     964        return Collections.unmodifiableList(cachedDataSourceBounds);
    919965    }
    920966
     
    13341380     * @param progressMonitor The progress monitor
    13351381     */
    1336     public void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
     1382    public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
    13371383        if (from != null) {
    13381384            new DataSetMerger(this, from).merge(progressMonitor);
    1339             dataSources.addAll(from.dataSources);
    1340             from.dataSources.clear();
     1385            if (!from.dataSources.isEmpty()) {
     1386                if (dataSources.addAll(from.dataSources)) {
     1387                    cachedDataSourceArea = null;
     1388                    cachedDataSourceBounds = null;
     1389                }
     1390                from.dataSources.clear();
     1391                from.cachedDataSourceArea = null;
     1392                from.cachedDataSourceBounds = null;
     1393            }
    13411394        }
    13421395    }
  • trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java

    r11590 r11627  
    309309    @Override
    310310    public Command fixError(TestError testError) {
    311         if (!isFixable(testError)) return null;
    312311        Collection<OsmPrimitive> sel = new LinkedList<>(testError.getPrimitives());
    313312        Set<Node> nodes = new LinkedHashSet<>(OsmPrimitive.getFilteredList(sel, Node.class));
  • trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java

    r11452 r11627  
    293293        }
    294294
    295         //Delete all relations in the list
     295        // Delete all relations in the list
    296296        relFix.remove(relationToKeep);
    297297        commands.add(new DeleteCommand(relFix));
     
    306306        // We fix it only if there is no more than one relation that is relation member.
    307307        Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
    308         Set<Relation> relations = new HashSet<>();
     308        Set<Relation> rels = new HashSet<>();
    309309
    310310        for (OsmPrimitive osm : sel) {
    311311            if (osm instanceof Relation) {
    312                 relations.add((Relation) osm);
    313             }
    314         }
    315 
    316         if (relations.size() < 2)
     312                rels.add((Relation) osm);
     313            }
     314        }
     315
     316        if (rels.size() < 2)
    317317            return false;
    318318
    319319        int relationsWithRelations = 0;
    320         for (Relation w : relations) {
     320        for (Relation w : rels) {
    321321            List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
    322322            if (!rel.isEmpty()) {
  • trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java

    r11129 r11627  
    249249    public Command fixError(TestError testError) {
    250250        Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
    251         Set<Way> ways = new HashSet<>();
     251        Set<Way> wayz = new HashSet<>();
    252252
    253253        for (OsmPrimitive osm : sel) {
    254254            if (osm instanceof Way && !osm.isDeleted()) {
    255                 ways.add((Way) osm);
    256             }
    257         }
    258 
    259         if (ways.size() < 2)
     255                wayz.add((Way) osm);
     256            }
     257        }
     258
     259        if (wayz.size() < 2)
    260260            return null;
    261261
    262262        long idToKeep = 0;
    263         Way wayToKeep = ways.iterator().next();
     263        Way wayToKeep = wayz.iterator().next();
    264264        // Find the way that is member of one or more relations. (If any)
    265265        Way wayWithRelations = null;
    266266        List<Relation> relations = null;
    267         for (Way w : ways) {
     267        for (Way w : wayz) {
    268268            List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
    269269            if (!rel.isEmpty()) {
     
    284284
    285285        // Fix relations.
    286         if (wayWithRelations != null && wayToKeep != wayWithRelations) {
     286        if (wayWithRelations != null && relations != null && wayToKeep != wayWithRelations) {
    287287            for (Relation rel : relations) {
    288288                Relation newRel = new Relation(rel);
     
    297297        }
    298298
    299         //Delete all ways in the list
    300         //Note: nodes are not deleted, these can be detected and deleted at next pass
    301         ways.remove(wayToKeep);
    302         commands.add(new DeleteCommand(ways));
     299        // Delete all ways in the list
     300        // Note: nodes are not deleted, these can be detected and deleted at next pass
     301        wayz.remove(wayToKeep);
     302        commands.add(new DeleteCommand(wayz));
    303303        return new SequenceCommand(tr("Delete duplicate ways"), commands);
    304304    }
     
    309309            return false;
    310310
    311         //Do not automatically fix same ways with different tags
     311        // Do not automatically fix same ways with different tags
    312312        if (testError.getCode() != DUPLICATE_WAY) return false;
    313313
    314314        // We fix it only if there is no more than one way that is relation member.
    315315        Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
    316         Set<Way> ways = new HashSet<>();
     316        Set<Way> wayz = new HashSet<>();
    317317
    318318        for (OsmPrimitive osm : sel) {
    319319            if (osm instanceof Way) {
    320                 ways.add((Way) osm);
    321             }
    322         }
    323 
    324         if (ways.size() < 2)
     320                wayz.add((Way) osm);
     321            }
     322        }
     323
     324        if (wayz.size() < 2)
    325325            return false;
    326326
    327327        int waysWithRelations = 0;
    328         for (Way w : ways) {
     328        for (Way w : wayz) {
    329329            List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
    330330            if (!rel.isEmpty()) {
  • trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java

    r11606 r11627  
    132132     */
    133133    public void setTargetPrimitive(final OsmPrimitive primitive) {
     134        setTargetPrimitive(primitive, true);
     135    }
     136
     137    /**
     138     * Sets the primitive the collection of primitives is merged or combined to.
     139     *
     140     * @param primitive the target primitive
     141     * @param updateTitle {@code true} to call {@link #updateTitle} in EDT (can be a slow operation)
     142     * @since 11626
     143     */
     144    private void setTargetPrimitive(final OsmPrimitive primitive, boolean updateTitle) {
    134145        this.targetPrimitive = primitive;
    135         GuiHelper.runInEDTAndWait(() -> {
    136             updateTitle();
    137             if (primitive instanceof Way) {
    138                 pnlRelationMemberConflictResolver.initForWayCombining();
    139             } else if (primitive instanceof Node) {
    140                 pnlRelationMemberConflictResolver.initForNodeMerging();
    141             }
    142         });
     146        if (updateTitle) {
     147            GuiHelper.runInEDTAndWait(this::updateTitle);
     148        }
    143149    }
    144150
     
    153159            helpAction.setHelpTopic(ht("/Action/CombineWay#ResolvingConflicts"));
    154160            getRootPane().putClientProperty("help", ht("/Action/CombineWay#ResolvingConflicts"));
     161            pnlRelationMemberConflictResolver.initForWayCombining();
    155162        } else if (targetPrimitive instanceof Node) {
    156163            setTitle(tr("Conflicts when merging nodes - target node is ''{0}''", targetPrimitive
     
    158165            helpAction.setHelpTopic(ht("/Action/MergeNodes#ResolvingConflicts"));
    159166            getRootPane().putClientProperty("help", ht("/Action/MergeNodes#ResolvingConflicts"));
     167            pnlRelationMemberConflictResolver.initForNodeMerging();
    160168        }
    161169    }
     
    295303     */
    296304    public void prepareDefaultDecisions() {
    297         getTagConflictResolverModel().prepareDefaultTagDecisions();
    298         getRelationMemberConflictResolverModel().prepareDefaultRelationDecisions();
     305        prepareDefaultDecisions(true);
     306    }
     307
     308    /**
     309     * Prepares the default decisions for populated tag and relation membership conflicts.
     310     * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
     311     * @since 11626
     312     */
     313    private void prepareDefaultDecisions(boolean fireEvent) {
     314        getTagConflictResolverModel().prepareDefaultTagDecisions(fireEvent);
     315        getRelationMemberConflictResolverModel().prepareDefaultRelationDecisions(fireEvent);
    299316    }
    300317
     
    501518            final CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
    502519
    503             dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
    504             dialog.getRelationMemberConflictResolverModel().populate(parentRelations, primitives);
    505             dialog.prepareDefaultDecisions();
     520            dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues(), false);
     521            dialog.getRelationMemberConflictResolverModel().populate(parentRelations, primitives, false);
     522            dialog.prepareDefaultDecisions(false);
    506523
    507524            // Ensure a proper title is displayed instead of a previous target (fix #7925)
    508525            if (targetPrimitives.size() == 1) {
    509                 dialog.setTargetPrimitive(targetPrimitives.iterator().next());
     526                dialog.setTargetPrimitive(targetPrimitives.iterator().next(), false);
    510527            } else {
    511                 dialog.setTargetPrimitive(null);
     528                dialog.setTargetPrimitive(null, false);
    512529            }
    513530
    514531            // Resolve tag conflicts if necessary
    515532            if (!dialog.isResolvedCompletely()) {
     533                GuiHelper.runInEDTAndWait(() -> {
     534                    dialog.getTagConflictResolverModel().fireTableDataChanged();
     535                    dialog.getRelationMemberConflictResolverModel().fireTableDataChanged();
     536                    dialog.updateTitle();
     537                });
    516538                dialog.setVisible(true);
    517539                if (!dialog.isApplied()) {
     
    520542            }
    521543            for (OsmPrimitive i : targetPrimitives) {
    522                 dialog.setTargetPrimitive(i);
     544                dialog.setTargetPrimitive(i, false);
    523545                cmds.addAll(dialog.buildResolutionCommands());
    524546            }
  • trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java

    r11601 r11627  
    129129        case 4: /* decision */
    130130            d.decide((RelationMemberConflictDecisionType) value);
    131             refresh();
     131            refresh(false);
    132132            break;
    133133        default: // Do nothing
     
    159159     */
    160160    public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives) {
     161        populate(relations, memberPrimitives, true);
     162    }
     163
     164    /**
     165     * Populates the model with the relation members belonging to one of the relations in <code>relations</code>
     166     * and referring to one of the primitives in <code>memberPrimitives</code>.
     167     *
     168     * @param relations  the parent relations. Empty list assumed if null.
     169     * @param memberPrimitives the child primitives. Empty list assumed if null.
     170     * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
     171     * @since 11626
     172     */
     173    void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives, boolean fireEvent) {
    161174        decisions.clear();
    162175        relations = relations == null ? Collections.<Relation>emptyList() : relations;
     
    169182        this.relations = relations;
    170183        this.primitives = memberPrimitives;
    171         refresh();
     184        refresh(fireEvent);
    172185    }
    173186
     
    199212     */
    200213    public void prepareDefaultRelationDecisions() {
    201 
     214        prepareDefaultRelationDecisions(true);
     215    }
     216
     217    /**
     218     * Prepare the default decisions for the current model.
     219     *
     220     * Keep/delete decisions are made if every member has the same role and the members are in consecutive order within the relation.
     221     * For multiple occurrences those conditions are tested stepwise for each occurrence.
     222     *
     223     * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
     224     * @since 11626
     225     */
     226    void prepareDefaultRelationDecisions(boolean fireEvent) {
    202227        if (primitives.stream().allMatch(Node.class::isInstance)) {
    203228            final Collection<OsmPrimitive> primitivesInDecisions = new HashSet<>();
     
    256281        }
    257282
    258         refresh();
     283        refresh(fireEvent);
    259284    }
    260285
     
    300325     */
    301326    public void refresh() {
     327        refresh(true);
     328    }
     329
     330    /**
     331     * Refreshes the model state. Invoke this method to trigger necessary change
     332     * events after an update of the model data.
     333     * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
     334     * @since 11626
     335     */
     336    void refresh(boolean fireEvent) {
    302337        updateNumConflicts();
    303         GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
     338        if (fireEvent) {
     339            GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
     340        }
    304341    }
    305342
  • trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java

    r10939 r11627  
    7373     */
    7474    public void rebuild() {
     75        rebuild(true);
     76    }
     77
     78    /**
     79     * initializes the model from the current tags
     80     * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
     81     * @since 11626
     82     */
     83    void rebuild(boolean fireEvent) {
    7584        if (tags == null) return;
    7685        for (String key: tags.getKeys()) {
     
    102111        refreshNumConflicts();
    103112        sort();
    104         GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
     113        if (fireEvent) {
     114            GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
     115        }
    105116    }
    106117
     
    113124     */
    114125    public void populate(TagCollection tags, Set<String> keysWithConflicts) {
     126        populate(tags, keysWithConflicts, true);
     127    }
     128
     129    /**
     130     * Populates the model with the tags for which conflicts are to be resolved.
     131     *
     132     * @param tags  the tag collection with the tags. Must not be null.
     133     * @param keysWithConflicts the set of tag keys with conflicts
     134     * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
     135     * @throws IllegalArgumentException if tags is null
     136     * @since 11626
     137     */
     138    void populate(TagCollection tags, Set<String> keysWithConflicts, boolean fireEvent) {
    115139        CheckParameterUtil.ensureParameterNotNull(tags, "tags");
    116140        this.tags = tags;
     
    120144        }
    121145        decisions = new HashMap<>();
    122         rebuild();
     146        rebuild(fireEvent);
    123147    }
    124148
     
    250274     */
    251275    public void prepareDefaultTagDecisions() {
     276        prepareDefaultTagDecisions(true);
     277    }
     278
     279    /**
     280     * Prepare the default decisions for the current model
     281     * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
     282     * @since 11626
     283     */
     284    void prepareDefaultTagDecisions(boolean fireEvent) {
    252285        for (MultiValueResolutionDecision decision: decisions.values()) {
    253286            List<String> values = decision.getValues();
     
    260293            // else: Do not suggest to keep all values in order to reduce the wrong usage of semicolon values, see #9104!
    261294        }
    262         rebuild();
     295        rebuild(fireEvent);
    263296    }
    264297
  • trunk/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java

    r11374 r11627  
    5757import org.openstreetmap.josm.tools.JosmRuntimeException;
    5858import org.openstreetmap.josm.tools.Shortcut;
     59import org.openstreetmap.josm.tools.Utils;
    5960import org.xml.sax.SAXException;
    6061
     
    595596        protected void fixError(TestError error) throws InterruptedException, InvocationTargetException {
    596597            if (error.isFixable()) {
     598                long start = System.currentTimeMillis();
    597599                final Command fixCommand = error.getFix();
     600                long phase1 = System.currentTimeMillis() - start;
     601                start = System.currentTimeMillis();
    598602                if (fixCommand != null) {
    599603                    SwingUtilities.invokeAndWait(() -> Main.main.undoRedo.addNoRedraw(fixCommand));
     604                    if (Main.isDebugEnabled()) {
     605                        long phase2 = System.currentTimeMillis() - start;
     606                        Main.debug(String.format("%s fix: %s + %s",
     607                                error.getTester().getClass().getSimpleName(), Utils.getDurationString(phase1),
     608                                Utils.getDurationString(phase2)));
     609                    }
    600610                }
    601611                // It is wanted to ignore an error if it said fixable, even if fixCommand was null
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ParentRelationLoadingTask.java

    r11553 r11627  
    161161
    162162                // copy the merged layer's data source info
    163                 getLayer().data.dataSources.addAll(referrers.dataSources);
     163                getLayer().data.addDataSources(referrers.getDataSources());
    164164                // FIXME: this is necessary because there are dialogs listening
    165165                // for DataChangeEvents which manipulate Swing components on this thread.
  • trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

    r11608 r11627  
    406406        // draw the hatched area for non-downloaded region. only draw if we're the active
    407407        // and bounds are defined; don't draw for inactive layers or loaded GPX files etc
    408         if (active && Main.pref.getBoolean("draw.data.downloaded_area", true) && !data.dataSources.isEmpty()) {
     408        if (active && Main.pref.getBoolean("draw.data.downloaded_area", true) && !data.getDataSources().isEmpty()) {
    409409            // initialize area with current viewport
    410410            Rectangle b = mv.getBounds();
     
    496496        // copy the merged layer's data source info.
    497497        // only add source rectangles if they are not contained in the layer already.
    498         for (DataSource src : from.dataSources) {
     498        for (DataSource src : from.getDataSources()) {
    499499            if (a == null || !a.contains(src.bounds.asRect())) {
    500                 data.dataSources.add(src);
     500                data.addDataSource(src);
    501501            }
    502502        }
     
    846846        // we'll assume that if this has no data sources
    847847        // that it also has no borders
    848         if (this.data.dataSources.isEmpty())
     848        if (this.data.getDataSources().isEmpty())
    849849            return true;
    850850
    851851        boolean layerBoundsPoint = false;
    852         for (DataSource src : this.data.dataSources) {
     852        for (DataSource src : this.data.getDataSources()) {
    853853            if (src.bounds.contains(coor)) {
    854854                layerBoundsPoint = true;
  • trunk/src/org/openstreetmap/josm/io/OsmReader.java

    r11435 r11627  
    198198            }
    199199            DataSource src = new DataSource(bounds, origin);
    200             ds.dataSources.add(src);
     200            ds.addDataSource(src);
    201201        } else {
    202202            throwException(tr("Missing mandatory attributes on element ''bounds''. " +
  • trunk/src/org/openstreetmap/josm/io/OsmWriter.java

    r10619 r11627  
    174174
    175175    public void writeDataSources(DataSet ds) {
    176         for (DataSource s : ds.dataSources) {
     176        for (DataSource s : ds.getDataSources()) {
    177177            out.println("  <bounds minlat='"
    178178                    + s.bounds.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES)
  • trunk/src/org/openstreetmap/josm/io/OverpassDownloadReader.java

    r11560 r11627  
    169169
    170170        // add bounds if necessary (note that Overpass API does not return bounds in the response XML)
    171         if (ds != null && ds.dataSources.isEmpty() && overpassQuery.contains("{{bbox}}")) {
     171        if (ds != null && ds.getDataSources().isEmpty() && overpassQuery.contains("{{bbox}}")) {
    172172            if (crosses180th) {
    173173                Bounds bounds = new Bounds(lat1, lon1, lat2, 180.0);
    174174                DataSource src = new DataSource(bounds, getBaseUrl());
    175                 ds.dataSources.add(src);
     175                ds.addDataSource(src);
    176176
    177177                bounds = new Bounds(lat1, -180.0, lat2, lon2);
    178178                src = new DataSource(bounds, getBaseUrl());
    179                 ds.dataSources.add(src);
     179                ds.addDataSource(src);
    180180            } else {
    181181                Bounds bounds = new Bounds(lat1, lon1, lat2, lon2);
    182182                DataSource src = new DataSource(bounds, getBaseUrl());
    183                 ds.dataSources.add(src);
     183                ds.addDataSource(src);
    184184            }
    185185        }
Note: See TracChangeset for help on using the changeset viewer.