001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.util.Arrays;
008import java.util.List;
009
010import org.openstreetmap.josm.Main;
011import org.openstreetmap.josm.data.conflict.Conflict;
012import org.openstreetmap.josm.data.coor.EastNorth;
013import org.openstreetmap.josm.data.coor.ILatLon;
014import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
015import org.openstreetmap.josm.data.osm.BBox;
016import org.openstreetmap.josm.data.osm.DataSet;
017import org.openstreetmap.josm.data.osm.Node;
018import org.openstreetmap.josm.data.osm.OsmPrimitive;
019import org.openstreetmap.josm.data.osm.Relation;
020import org.openstreetmap.josm.data.osm.RelationMember;
021import org.openstreetmap.josm.data.osm.Way;
022import org.openstreetmap.josm.data.projection.proj.TransverseMercator;
023import org.openstreetmap.josm.data.projection.proj.TransverseMercator.Hemisphere;
024import org.openstreetmap.josm.tools.Geometry;
025import org.openstreetmap.josm.tools.Pair;
026import org.openstreetmap.josm.tools.Utils;
027import org.openstreetmap.josm.tools.date.DateUtils;
028
029/**
030 * Textual representation of primitive contents, used in {@code InspectPrimitiveDialog}.
031 * @since 10198
032 */
033public class InspectPrimitiveDataText {
034    private static final String INDENT = "  ";
035    private static final char NL = '\n';
036
037    private final StringBuilder s = new StringBuilder();
038    private final DataSet ds;
039
040    InspectPrimitiveDataText(DataSet ds) {
041        this.ds = ds;
042    }
043
044    private InspectPrimitiveDataText add(String title, String... values) {
045        s.append(INDENT).append(title);
046        for (String v : values) {
047            s.append(v);
048        }
049        s.append(NL);
050        return this;
051    }
052
053    private static String getNameAndId(String name, long id) {
054        if (name != null) {
055            return name + tr(" ({0})", /* sic to avoid thousand seperators */ Long.toString(id));
056        } else {
057            return Long.toString(id);
058        }
059    }
060
061    /**
062     * Adds a new OSM primitive.
063     * @param o primitive to add
064     */
065    public void addPrimitive(OsmPrimitive o) {
066
067        addHeadline(o);
068
069        if (!(o.getDataSet() != null && o.getDataSet().getPrimitiveById(o) != null)) {
070            s.append(NL).append(INDENT).append(tr("not in data set")).append(NL);
071            return;
072        }
073        if (o.isIncomplete()) {
074            s.append(NL).append(INDENT).append(tr("incomplete")).append(NL);
075            return;
076        }
077        s.append(NL);
078
079        addState(o);
080        addCommon(o);
081        addAttributes(o);
082        addSpecial(o);
083        addReferrers(s, o);
084        addConflicts(o);
085        s.append(NL);
086    }
087
088    void addHeadline(OsmPrimitive o) {
089        addType(o);
090        addNameAndId(o);
091    }
092
093    void addType(OsmPrimitive o) {
094        if (o instanceof Node) {
095            s.append(tr("Node: "));
096        } else if (o instanceof Way) {
097            s.append(tr("Way: "));
098        } else if (o instanceof Relation) {
099            s.append(tr("Relation: "));
100        }
101    }
102
103    void addNameAndId(OsmPrimitive o) {
104        String name = o.get("name");
105        if (name == null) {
106            s.append(o.getUniqueId());
107        } else {
108            s.append(getNameAndId(name, o.getUniqueId()));
109        }
110    }
111
112    void addState(OsmPrimitive o) {
113        StringBuilder sb = new StringBuilder(INDENT);
114        /* selected state is left out: not interesting as it is always selected */
115        if (o.isDeleted()) {
116            sb.append(tr("deleted")).append(INDENT);
117        }
118        if (!o.isVisible()) {
119            sb.append(tr("deleted-on-server")).append(INDENT);
120        }
121        if (o.isModified()) {
122            sb.append(tr("modified")).append(INDENT);
123        }
124        if (o.isDisabledAndHidden()) {
125            sb.append(tr("filtered/hidden")).append(INDENT);
126        }
127        if (o.isDisabled()) {
128            sb.append(tr("filtered/disabled")).append(INDENT);
129        }
130        if (o.hasDirectionKeys()) {
131            if (o.reversedDirection()) {
132                sb.append(tr("has direction keys (reversed)")).append(INDENT);
133            } else {
134                sb.append(tr("has direction keys")).append(INDENT);
135            }
136        }
137        String state = sb.toString().trim();
138        if (!state.isEmpty()) {
139            add(tr("State: "), sb.toString().trim());
140        }
141    }
142
143    void addCommon(OsmPrimitive o) {
144        add(tr("Data Set: "), Integer.toHexString(o.getDataSet().hashCode()));
145        add(tr("Edited at: "), o.isTimestampEmpty() ? tr("<new object>")
146                : DateUtils.fromTimestamp(o.getRawTimestamp()));
147        add(tr("Edited by: "), o.getUser() == null ? tr("<new object>")
148                : getNameAndId(o.getUser().getName(), o.getUser().getId()));
149        add(tr("Version: "), Integer.toString(o.getVersion()));
150        add(tr("In changeset: "), Integer.toString(o.getChangesetId()));
151    }
152
153    void addAttributes(OsmPrimitive o) {
154        if (o.hasKeys()) {
155            add(tr("Tags: "));
156            for (String key : o.keySet()) {
157                s.append(INDENT).append(INDENT);
158                s.append(String.format("\"%s\"=\"%s\"%n", key, o.get(key)));
159            }
160        }
161    }
162
163    void addSpecial(OsmPrimitive o) {
164        if (o instanceof Node) {
165            addCoordinates((Node) o);
166        } else if (o instanceof Way) {
167            addBbox(o);
168            add(tr("Centroid: "),
169                    toStringCSV(", ", Main.getProjection().eastNorth2latlon(
170                            Geometry.getCentroid(((Way) o).getNodes()))));
171            addWayNodes((Way) o);
172        } else if (o instanceof Relation) {
173            addBbox(o);
174            addRelationMembers((Relation) o);
175        }
176    }
177
178    void addRelationMembers(Relation r) {
179        add(trn("{0} Member: ", "{0} Members: ", r.getMembersCount(), r.getMembersCount()));
180        for (RelationMember m : r.getMembers()) {
181            s.append(INDENT).append(INDENT);
182            addHeadline(m.getMember());
183            s.append(tr(" as \"{0}\"", m.getRole()));
184            s.append(NL);
185        }
186    }
187
188    void addWayNodes(Way w) {
189        add(tr("{0} Nodes: ", w.getNodesCount()));
190        for (Node n : w.getNodes()) {
191            s.append(INDENT).append(INDENT);
192            addNameAndId(n);
193            s.append(NL);
194        }
195    }
196
197    void addBbox(OsmPrimitive o) {
198        BBox bbox = o.getBBox();
199        if (bbox != null) {
200            add(tr("Bounding box: "), bbox.toStringCSV(", "));
201            EastNorth bottomRigth = bbox.getBottomRight().getEastNorth(Main.getProjection());
202            EastNorth topLeft = bbox.getTopLeft().getEastNorth(Main.getProjection());
203            add(tr("Bounding box (projected): "),
204                    Double.toString(topLeft.east()), ", ",
205                    Double.toString(bottomRigth.north()), ", ",
206                    Double.toString(bottomRigth.east()), ", ",
207                    Double.toString(topLeft.north()));
208            add(tr("Center of bounding box: "), toStringCSV(", ", bbox.getCenter()));
209        }
210    }
211
212    void addCoordinates(Node n) {
213        if (n.isLatLonKnown()) {
214            add(tr("Coordinates: "),
215                    Double.toString(n.lat()), ", ",
216                    Double.toString(n.lon()));
217            EastNorth en = n.getEastNorth();
218            add(tr("Coordinates (projected): "),
219                    Double.toString(en.east()), ", ",
220                    Double.toString(en.north()));
221            Pair<Integer, Hemisphere> utmZone = TransverseMercator.locateUtmZone(n.getCoor());
222            String utmLabel = tr("UTM Zone");
223            add(utmLabel, utmLabel.endsWith(":") ? " " : ": ", Integer.toString(utmZone.a), utmZone.b.name().substring(0, 1));
224        }
225    }
226
227    void addReferrers(StringBuilder s, OsmPrimitive o) {
228        List<OsmPrimitive> refs = o.getReferrers();
229        if (!refs.isEmpty()) {
230            add(tr("Part of: "));
231            for (OsmPrimitive p : refs) {
232                s.append(INDENT).append(INDENT);
233                addHeadline(p);
234                s.append(NL);
235            }
236        }
237    }
238
239    void addConflicts(OsmPrimitive o) {
240        Conflict<?> c = ds.getConflicts().getConflictForMy(o);
241        if (c != null) {
242            add(tr("In conflict with: "));
243            addNameAndId(c.getTheir());
244        }
245    }
246
247    /**
248     * Returns lat/lon coordinate in human-readable format separated by {@code separator}.
249     * @param separator values separator
250     * @param ll the lat/lon
251     * @return String in the format {@code "1.23456[separator]2.34567"}
252     */
253    private static String toStringCSV(String separator, ILatLon ll) {
254        return Utils.join(separator, Arrays.asList(
255                DecimalDegreesCoordinateFormat.INSTANCE.latToString(ll),
256                DecimalDegreesCoordinateFormat.INSTANCE.lonToString(ll)
257        ));
258    }
259
260    @Override
261    public String toString() {
262        return s.toString();
263    }
264}