001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.history;
003
004import static org.openstreetmap.josm.tools.I18n.marktr;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.Component;
008import java.io.IOException;
009import java.text.MessageFormat;
010import java.util.Collection;
011import java.util.HashSet;
012import java.util.List;
013import java.util.Set;
014
015import org.openstreetmap.josm.data.osm.Changeset;
016import org.openstreetmap.josm.data.osm.OsmPrimitive;
017import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
018import org.openstreetmap.josm.data.osm.PrimitiveId;
019import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
020import org.openstreetmap.josm.data.osm.history.History;
021import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
022import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
023import org.openstreetmap.josm.gui.ExceptionDialogUtil;
024import org.openstreetmap.josm.gui.PleaseWaitRunnable;
025import org.openstreetmap.josm.io.ChangesetQuery;
026import org.openstreetmap.josm.io.OsmServerChangesetReader;
027import org.openstreetmap.josm.io.OsmServerHistoryReader;
028import org.openstreetmap.josm.io.OsmTransferException;
029import org.openstreetmap.josm.tools.CheckParameterUtil;
030import org.xml.sax.SAXException;
031
032/**
033 * Loads the object history of an collection of objects from the
034 * server.
035 *
036 * It provides a fluent API for configuration.
037 *
038 * Sample usage:
039 *
040 * <pre>
041 *   HistoryLoadTask task  = new HistoryLoadTask()
042 *      .add(1, OsmPrimitiveType.NODE)
043 *      .add(1233, OsmPrimitiveType.WAY)
044 *      .add(37234, OsmPrimitveType.RELATION)
045 *      .add(aHistoryItem);
046 *
047 *   Main.worker.execute(task);
048 *
049 * </pre>
050 */
051public class HistoryLoadTask extends PleaseWaitRunnable {
052
053    private boolean canceled = false;
054    private Exception lastException  = null;
055    private Set<PrimitiveId> toLoad;
056    private HistoryDataSet loadedData;
057    private OsmServerHistoryReader reader = null;
058
059    public HistoryLoadTask() {
060        super(tr("Load history"), true);
061        toLoad = new HashSet<PrimitiveId>();
062    }
063
064    /**
065     * Creates a new task
066     *
067     * @param parent the component to be used as reference to find the
068     * parent for {@link org.openstreetmap.josm.gui.PleaseWaitDialog}.
069     * Must not be <code>null</code>.
070     * @throws IllegalArgumentException thrown if parent is <code>null</code>
071     */
072    public HistoryLoadTask(Component parent) {
073        super(parent, tr("Load history"), true);
074        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
075        toLoad = new HashSet<PrimitiveId>();
076    }
077
078    /**
079     * Adds an object whose history is to be loaded.
080     *
081     * @param id the object id
082     * @param type the object type
083     * @return this task
084     */
085    public HistoryLoadTask add(long id, OsmPrimitiveType type) throws IllegalArgumentException {
086        if (id <= 0)
087            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got {1}.", "id", id));
088        CheckParameterUtil.ensureParameterNotNull(type, "type");
089        SimplePrimitiveId pid = new SimplePrimitiveId(id, type);
090        toLoad.add(pid);
091        return this;
092    }
093
094    /**
095     * Adds an object whose history is to be loaded.
096     *
097     * @param pid  the primitive id. Must not be null. Id > 0 required.
098     * @return this task
099     */
100    public HistoryLoadTask add(PrimitiveId pid) {
101        CheckParameterUtil.ensureValidPrimitiveId(pid, "pid");
102        toLoad.add(pid);
103        return this;
104    }
105
106    /**
107     * Adds an object to be loaded, the object is specified by a history item.
108     *
109     * @param primitive the history item
110     * @return this task
111     * @throws IllegalArgumentException thrown if primitive is null
112     */
113    public HistoryLoadTask add(HistoryOsmPrimitive primitive) {
114        CheckParameterUtil.ensureParameterNotNull(primitive, "primitive");
115        toLoad.add(primitive.getPrimitiveId());
116        return this;
117    }
118
119    /**
120     * Adds an object to be loaded, the object is specified by an already loaded object history.
121     *
122     * @param history the history. Must not be null.
123     * @return this task
124     * @throws IllegalArgumentException thrown if history is null
125     */
126    public HistoryLoadTask add(History history) {
127        CheckParameterUtil.ensureParameterNotNull(history, "history");
128        toLoad.add(history.getPrimitiveId());
129        return this;
130    }
131
132    /**
133     * Adds an object to be loaded, the object is specified by an OSM primitive.
134     *
135     * @param primitive the OSM primitive. Must not be null. primitive.getId() > 0 required.
136     * @return this task
137     * @throws IllegalArgumentException thrown if the primitive is null
138     * @throws IllegalArgumentException thrown if primitive.getId() <= 0
139     */
140    public HistoryLoadTask add(OsmPrimitive primitive) {
141        CheckParameterUtil.ensureValidPrimitiveId(primitive, "primitive");
142        toLoad.add(primitive.getPrimitiveId());
143        return this;
144    }
145
146    /**
147     * Adds a collection of objects to loaded, specified by a collection of OSM primitives.
148     *
149     * @param primitives the OSM primitives. Must not be <code>null</code>.
150     * <code>primitive.getId() > 0</code> required.
151     * @return this task
152     * @throws IllegalArgumentException thrown if primitives is <code>null</code>
153     * @throws IllegalArgumentException thrown if one of the ids in the collection &lt;= 0
154     */
155    public HistoryLoadTask add(Collection<? extends OsmPrimitive> primitives) {
156        CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
157        for (OsmPrimitive primitive: primitives) {
158            if (primitive == null) {
159                continue;
160            }
161            add(primitive);
162        }
163        return this;
164    }
165
166    @Override
167    protected void cancel() {
168        if (reader != null) {
169            reader.cancel();
170        }
171        canceled = true;
172    }
173
174    @Override
175    protected void finish() {
176        if (isCanceled())
177            return;
178        if (lastException != null) {
179            ExceptionDialogUtil.explainException(lastException);
180            return;
181        }
182        HistoryDataSet.getInstance().mergeInto(loadedData);
183    }
184
185    @Override
186    protected void realRun() throws SAXException, IOException, OsmTransferException {
187        loadedData = new HistoryDataSet();
188        try {
189            progressMonitor.setTicksCount(toLoad.size());
190            for(PrimitiveId pid: toLoad) {
191                if (canceled) {
192                    break;
193                }
194                String msg = "";
195                switch(pid.getType()) {
196                case NODE: msg = marktr("Loading history for node {0}"); break;
197                case WAY: msg = marktr("Loading history for way {0}"); break;
198                case RELATION: msg = marktr("Loading history for relation {0}"); break;
199                }
200                progressMonitor.indeterminateSubTask(tr(msg,
201                        Long.toString(pid.getUniqueId())));
202                reader = null;
203                HistoryDataSet ds = null;
204                try {
205                    reader = new OsmServerHistoryReader(pid.getType(), pid.getUniqueId());
206                    ds = reader.parseHistory(progressMonitor.createSubTaskMonitor(1, false));
207                    // load corresponding changesets (mostly for changeset comment)
208                    for (final Changeset i : new OsmServerChangesetReader().queryChangesets(
209                            new ChangesetQuery().forChangesetIds(ds.getChangesetIds()), progressMonitor.createSubTaskMonitor(1, false))) {
210                        ds.putChangeset(i);
211                    }
212                } catch(OsmTransferException e) {
213                    if (canceled)
214                        return;
215                    throw e;
216                }
217                loadedData.mergeInto(ds);
218            }
219        } catch(OsmTransferException e) {
220            lastException = e;
221            return;
222        }
223    }
224
225    public boolean isCanceled() {
226        return canceled;
227    }
228
229    public Exception getLastException() {
230        return lastException;
231    }
232}