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