From 1d9985ca45b64c2849030efc107541560e086fb4 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 25 Nov 2020 11:09:19 -0500 Subject: [PATCH] updates for most recent and most common --- .../datamodel/WhereUsedSummary.java | 360 ++++-------------- .../datasourcesummary/ui/Bundle.properties | 9 +- .../ui/Bundle.properties-MERGED | 12 +- .../datasourcesummary/ui/WhereUsedPanel.form | 158 +++++++- .../datasourcesummary/ui/WhereUsedPanel.java | 275 ++++++++----- 5 files changed, 419 insertions(+), 395 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/WhereUsedSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/WhereUsedSummary.java index 7f5928a4ca..b7ff0b9856 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/WhereUsedSummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/WhereUsedSummary.java @@ -18,32 +18,22 @@ */ package org.sleuthkit.autopsy.datasourcesummary.datamodel; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor; -import org.sleuthkit.autopsy.geolocation.KdTree.XYZPoint; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; @@ -97,152 +87,39 @@ public class WhereUsedSummary implements DefaultArtifactUpdateGovernor { } /** - * A record for a particular city including country and location. + * Returned data providing counts of most common cities seen and most recent + * cities seen. */ - public static class CityRecord extends XYZPoint { + public static class CityData { - private final String cityName; - private final String country; + private final CityCountsList mostCommon; + private final CityCountsList mostRecent; + private final Long mostRecentSeen; /** * Main constructor. * - * @param cityName The name of the city. - * @param country The country of that city. - * @param latitude Latitude for the city. - * @param longitude Longitude for the city. + * @param mostCommon The list of most common cities seen. + * @param mostRecent The list of most recent cities seen. + * @param mostRecentSeen */ - CityRecord(String cityName, String country, double latitude, double longitude) { - super(latitude, longitude); - this.cityName = cityName; - this.country = country; + CityData(CityCountsList mostCommon, CityCountsList mostRecent, Long mostRecentSeen) { + this.mostCommon = mostCommon; + this.mostRecent = mostRecent; + this.mostRecentSeen = mostRecentSeen; } - /** - * @return The name of the city. - */ - public String getCityName() { - return cityName; + public CityCountsList getMostCommon() { + return mostCommon; } - /** - * @return The country of that city. - */ - public String getCountry() { - return country; + public CityCountsList getMostRecent() { + return mostRecent; } - /** - * @return Latitude for the city. - */ - public double getLatitude() { - return getX(); + public Long getMostRecentSeen() { + return mostRecentSeen; } - - /** - * @return Longitude for the city. - */ - public double getLongitude() { - return getY(); - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 37 * hash + Objects.hashCode(this.cityName); - hash = 37 * hash + Objects.hashCode(this.country); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final CityRecord other = (CityRecord) obj; - if (!Objects.equals(this.cityName, other.cityName)) { - return false; - } - if (!Objects.equals(this.country, other.country)) { - return false; - } - - return super.equals(obj); - } - - @Override - public String toString() { - return "CityRecord{" + "cityName=" + cityName + ", country=" + country + ", lat=" + getX() + ", lng=" + getY() + '}'; - } - } - - // taken from GeoFilterPanel: all of the GPS artifact types. - @SuppressWarnings("deprecation") - private static final List GPS_ARTIFACT_TYPES = Arrays.asList( - BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK, - BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION, - BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE, - BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH, - BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK, - BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT, - BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF - ); - - // all GPS types - private static final Set GPS_ARTIFACT_TYPE_IDS = GPS_ARTIFACT_TYPES.stream() - .map(artifactType -> artifactType.getTypeID()) - .collect(Collectors.toSet()); - - private static WhereUsedSummary instance = null; - - /** - * @return The singleton instance of this class. - */ - public static WhereUsedSummary getInstance() { - if (instance == null) { - instance = new WhereUsedSummary(); - } - - return instance; - } - - private final SleuthkitCaseProvider provider; - private LatLngMap latLngMap = null; - private final java.util.logging.Logger logger; - - /** - * Main constructor. - */ - private WhereUsedSummary() { - this(SleuthkitCaseProvider.DEFAULT, Logger.getLogger(WhereUsedSummary.class.getName())); - } - - /** - * Main constructor. - * - * @param provider The means of obtaining a sleuthkit case. - */ - public WhereUsedSummary(SleuthkitCaseProvider provider, java.util.logging.Logger logger) { - this.provider = provider; - this.logger = logger; - } - - /** - * @return Returns all the geolocation artifact types. - */ - public List getGeoTypes() { - return GPS_ARTIFACT_TYPES; - } - - @Override - public Set getArtifactTypeIdsForRefresh() { - return GPS_ARTIFACT_TYPE_IDS; } public static class CityCountsList { @@ -264,29 +141,55 @@ public class WhereUsedSummary implements DefaultArtifactUpdateGovernor { } } - public static class CityData { + // taken from GeoFilterPanel: all of the GPS artifact types. + @SuppressWarnings("deprecation") + private static final List GPS_ARTIFACT_TYPES = Arrays.asList( + BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK, + BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION, + BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE, + BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH, + BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK, + BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT, + BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF + ); - private final CityCountsList mostCommon; - private final CityCountsList mostRecent; - private final Long mostRecentSeen; + // all GPS types + private static final Set GPS_ARTIFACT_TYPE_IDS = GPS_ARTIFACT_TYPES.stream() + .map(artifactType -> artifactType.getTypeID()) + .collect(Collectors.toSet()); - public CityData(CityCountsList mostCommon, CityCountsList mostRecent, Long mostRecentSeen) { - this.mostCommon = mostCommon; - this.mostRecent = mostRecent; - this.mostRecentSeen = mostRecentSeen; - } + private final SleuthkitCaseProvider provider; + private final java.util.logging.Logger logger; + private final SupplierWithException cityMapper; - public CityCountsList getMostCommon() { - return mostCommon; - } + public interface SupplierWithException { - public CityCountsList getMostRecent() { - return mostRecent; - } + T get() throws E; + } - public Long getMostRecentSeen() { - return mostRecentSeen; - } + /** + * Main constructor. + */ + public WhereUsedSummary() { + this(() -> ClosestCityMapper.getInstance(), SleuthkitCaseProvider.DEFAULT, Logger.getLogger(WhereUsedSummary.class.getName())); + } + + public WhereUsedSummary(SupplierWithException cityMapper, SleuthkitCaseProvider provider, java.util.logging.Logger logger) { + this.cityMapper = cityMapper; + this.provider = provider; + this.logger = logger; + } + + /** + * @return Returns all the geolocation artifact types. + */ + public List getGeoTypes() { + return GPS_ARTIFACT_TYPES; + } + + @Override + public Set getArtifactTypeIdsForRefresh() { + return GPS_ARTIFACT_TYPE_IDS; } private boolean greaterThanOrEqual(Long minTime, Long time) { @@ -308,7 +211,7 @@ public class WhereUsedSummary implements DefaultArtifactUpdateGovernor { return points.stream().reduce( EMPTY_COUNT, - (Waypoint w) -> Pair.of(1, greaterThanOrEqual(minTime, w.getTimestamp()) ? 1 : 0), + (total, w) -> Pair.of(total.getLeft() + 1, total.getRight() + (greaterThanOrEqual(minTime, w.getTimestamp()) ? 1 : 0)), (pair1, pair2) -> Pair.of(pair1.getLeft() + pair2.getLeft(), pair1.getRight() + pair2.getRight())); } @@ -322,10 +225,10 @@ public class WhereUsedSummary implements DefaultArtifactUpdateGovernor { * @throws GeoLocationDataException * @throws InterruptedException */ - public CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount) throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException { - if (this.latLngMap == null) { - throw new IllegalStateException("City data hasn't been loaded"); - } + public CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount) + throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException, IOException { + + ClosestCityMapper closestCityMapper = ClosestCityMapper.getInstance(); List dataSourcePoints = getPoints(dataSource); @@ -334,7 +237,7 @@ public class WhereUsedSummary implements DefaultArtifactUpdateGovernor { Long mostRecent = null; for (Waypoint pt : dataSourcePoints) { - CityRecord city = latLngMap.findClosest(new CityRecord(null, null, pt.getLatitude(), pt.getLongitude())); + CityRecord city = closestCityMapper.findClosest(new CityRecord(null, null, pt.getLatitude(), pt.getLongitude())); Long curTime = pt.getTimestamp(); if (curTime != null && (mostRecent == null || curTime > mostRecent)) { mostRecent = curTime; @@ -370,15 +273,14 @@ public class WhereUsedSummary implements DefaultArtifactUpdateGovernor { .sorted((a, b) -> -Integer.compare(a.getCount(), b.getCount())) .limit(maxCount) .collect(Collectors.toList()); - + Pair otherCounts = getCounts(others, mostRecentTime); int otherMostCommonCount = otherCounts.getLeft(); int otherMostRecentCount = otherCounts.getRight(); - - + return new CityData( - new CityCountsList(mostCommonCounts, otherMostCommonCount), - new CityCountsList(mostRecentCounts, otherMostRecentCount), + new CityCountsList(mostCommonCounts, otherMostCommonCount), + new CityCountsList(mostRecentCounts, otherMostRecentCount), mostRecentTime); } @@ -422,120 +324,4 @@ public class WhereUsedSummary implements DefaultArtifactUpdateGovernor { return Collections.emptyList(); } } - - /** - * Pre-loads city data. - */ - public void load() throws IOException { - latLngMap = new LatLngMap(parseCsvLines(WhereUsedSummary.class - .getResourceAsStream("worldcities.csv"), true)); - } - - private static final int CITY_NAME_IDX = 0; - private static final int COUNTRY_NAME_IDX = 4; - private static final int LAT_IDX = 2; - private static final int LONG_IDX = 3; - - private static final int MAX_IDX = Stream.of(CITY_NAME_IDX, COUNTRY_NAME_IDX, LAT_IDX, LONG_IDX) - .max(Integer::compare) - .get(); - - private static Double tryParse(String s) { - if (s == null) { - return null; - } - - try { - return Double.parseDouble(s); - } catch (NumberFormatException ex) { - return null; - } - } - - private String parseCountryName(String orig, int lineNum) { - if (StringUtils.isBlank(orig)) { - logger.log(Level.WARNING, String.format("No country name determined for line %d.", lineNum)); - return null; - } - - Matcher m = COUNTRY_WITH_COMMA.matcher(orig); - if (m.find()) { - return String.format("%s %s", m.group(1), m.group(2)); - } - - return orig; - } - - private CityRecord getCsvCityRecord(List csvRow, int lineNum) { - if (csvRow == null || csvRow.size() <= MAX_IDX) { - logger.log(Level.WARNING, String.format("Row at line number %d is required to have at least %d elements and does not.", lineNum, (MAX_IDX + 1))); - } - - String cityName = csvRow.get(CITY_NAME_IDX); - if (StringUtils.isBlank(cityName)) { - logger.log(Level.WARNING, String.format("No city name determined for line %d.", lineNum)); - return null; - } - - String countryName = parseCountryName(csvRow.get(COUNTRY_NAME_IDX), lineNum); - - Double lattitude = tryParse(csvRow.get(LAT_IDX)); - if (lattitude == null) { - logger.log(Level.WARNING, String.format("No lattitude determined for line %d.", lineNum)); - return null; - } - - Double longitude = tryParse(csvRow.get(LONG_IDX)); - if (longitude == null) { - logger.log(Level.WARNING, String.format("No longitude determined for line %d.", lineNum)); - return null; - } - - return new CityRecord(cityName, countryName, lattitude, longitude); - } - - private static Pattern CSV_NAIVE_REGEX = Pattern.compile("\"\\s*(([^\"]+?)?)\\s*\""); - private static Pattern COUNTRY_WITH_COMMA = Pattern.compile("^\\s*([^,]*)\\s*,\\s*([^,]*)\\s*$"); - - private List parseCsvLine(String line, int lineNum) { - if (line == null || line.length() <= 0) { - logger.log(Level.INFO, String.format("Line at %d had no content", lineNum)); - return null; - } - - List allMatches = new ArrayList(); - Matcher m = CSV_NAIVE_REGEX.matcher(line); - while (m.find()) { - allMatches.add(m.group(1)); - } - - return allMatches; - } - - private List parseCsvLines(InputStream csvInputStream, boolean ignoreHeaderRow) throws IOException { - List cityRecords = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(csvInputStream, "UTF-8"))) { - int lineNum = 1; - String line = reader.readLine(); - - if (line != null && ignoreHeaderRow) { - line = reader.readLine(); - lineNum++; - } - - while (line != null) { - // read next line - List rowElements = parseCsvLine(line, lineNum); - - if (rowElements != null) { - cityRecords.add(getCsvCityRecord(rowElements, lineNum)); - } - - line = reader.readLine(); - lineNum++; - } - } - - return cityRecords; - } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties index 7339a32a0a..7d4f2caead 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties @@ -42,7 +42,10 @@ RecentFilesPanel.attachmentLabel.text=Recent Attachments PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. -WhereUsedPanel.cityCountsLabel.text=Cities -WhereUsedPanel.viewInGeolocationBtn.text=View In Geolocation TimelinePanel.activityRangeLabel.text=Activity Range -WhereUsedPanel.withinDistanceLabel.text=Points must be within 150 Km to be considered +WhereUsedPanel.withinDistanceLabel.text=Locations further than 150Km from a city are not included +WhereUsedPanel.mostRecentLabel.text=Recent Cities from Geolocation Artifacts +WhereUsedPanel.withinDistanceLabel1.text=Locations further than 150Km from a city are not included +WhereUsedPanel.mostCommonLabel.text=Most Common Cities from Geolocation Artifacts +WhereUsedPanel.recentViewInGeolocationBtn.text=View In Geolocation +WhereUsedPanel.commonViewInGeolocationBtn.text=View In Geolocation diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 7a0683673e..ab0ffc3e35 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -103,6 +103,7 @@ RecentFilesPanel.attachmentLabel.text=Recent Attachments PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. +TimelinePanel.activityRangeLabel.text=Activity Range UserActivityPanel_noDataExists=No communication data exists UserActivityPanel_tab_title=User Activity UserActivityPanel_TopAccountTableModel_accountType_header=Account Type @@ -121,10 +122,13 @@ UserActivityPanel_TopWebSearchTableModel_dateAccessed_header=Date Accessed UserActivityPanel_TopWebSearchTableModel_searchString_header=Search String UserActivityPanel_TopWebSearchTableModel_translatedResult_header=Translated ViewSummaryInformationAction.name.text=View Summary Information -WhereUsedPanel.cityCountsLabel.text=Cities -WhereUsedPanel.viewInGeolocationBtn.text=View In Geolocation -TimelinePanel.activityRangeLabel.text=Activity Range -WhereUsedPanel.withinDistanceLabel.text=Points must be within 150 Km to be considered +WhereUsedPanel.withinDistanceLabel.text=Locations further than 150Km from a city are not included +WhereUsedPanel.mostRecentLabel.text=Recent Cities from Geolocation Artifacts +WhereUsedPanel.withinDistanceLabel1.text=Locations further than 150Km from a city are not included +WhereUsedPanel.mostCommonLabel.text=Most Common Cities from Geolocation Artifacts +WhereUsedPanel.recentViewInGeolocationBtn.text=View In Geolocation +WhereUsedPanel.commonViewInGeolocationBtn.text=View In Geolocation WhereUsedPanel_cityColumn_title=Closest City WhereUsedPanel_countColumn_title=Count WhereUsedPanel_onNoCrIngest_message=No results will be shown because the GPX Parser was not run. +WhereUsedPanel_unknownRow_title=Unknown diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.form index 4d0feedee4..91d08674e2 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.form +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.form @@ -21,7 +21,7 @@ - + @@ -72,10 +72,10 @@ - + - + @@ -107,21 +107,21 @@ - + - + - + - + - + @@ -161,13 +161,13 @@ - + - + - + @@ -177,15 +177,145 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.java index a31ab9af5a..885b0b106b 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/WhereUsedPanel.java @@ -18,17 +18,24 @@ */ package org.sleuthkit.autopsy.datasourcesummary.ui; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.swing.JButton; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.openide.util.NbBundle.Messages; import org.openide.util.actions.CallableSystemAction; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.datasourcesummary.datamodel.WhereUsedSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.WhereUsedSummary.CityCountsList; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.WhereUsedSummary.CityData; import org.sleuthkit.autopsy.datasourcesummary.datamodel.WhereUsedSummary.CityRecordCount; -import org.sleuthkit.autopsy.datasourcesummary.datamodel.WhereUsedSummary.CityRecord; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.CityRecord; import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; @@ -39,7 +46,6 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel; import org.sleuthkit.autopsy.geolocation.GeoFilter; import org.sleuthkit.autopsy.geolocation.GeolocationTopComponent; import org.sleuthkit.autopsy.geolocation.OpenGeolocationAction; -import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.DataSource; /** @@ -49,13 +55,72 @@ import org.sleuthkit.datamodel.DataSource; @Messages({ "WhereUsedPanel_cityColumn_title=Closest City", "WhereUsedPanel_countColumn_title=Count", - "WhereUsedPanel_onNoCrIngest_message=No results will be shown because the GPX Parser was not run." -}) + "WhereUsedPanel_onNoCrIngest_message=No results will be shown because the GPX Parser was not run.", + "WhereUsedPanel_unknownRow_title=Unknown",}) public class WhereUsedPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; private static final String GPX_FACTORY = "org.python.proxies.GPX_Parser_Module$GPXParserFileIngestModuleFactory"; private static final String GPX_NAME = "GPX Parser"; + private static final int DAYS_COUNT = 30; + private static final int MAX_COUNT = 10; + + private static final ColumnModel> CITY_COL = new ColumnModel<>( + Bundle.WhereUsedPanel_cityColumn_title(), + (pair) -> new DefaultCellModel(pair.getLeft()), + 300 + ); + + private static final ColumnModel> COUNT_COL = new ColumnModel<>( + Bundle.WhereUsedPanel_countColumn_title(), + (pair) -> new DefaultCellModel(Integer.toString(pair.getRight())), + 100 + ); + + // tables displaying city and number of hits for that city + private final JTablePanel> mostCommonTable = JTablePanel.getJTablePanel(Arrays.asList(CITY_COL, COUNT_COL)) + .setKeyFunction((pair) -> pair.getLeft()); + + private final JTablePanel> mostRecentTable = JTablePanel.getJTablePanel(Arrays.asList(CITY_COL, COUNT_COL)) + .setKeyFunction((pair) -> pair.getLeft()); + + // loadable components on this tab + private final List> tables = Arrays.asList(mostCommonTable, mostRecentTable); + + // means of fetching and displaying data + private final List> dataFetchComponents; + + private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel(); + + private final WhereUsedSummary whereUsedData; + + /** + * Main constructor. + */ + public WhereUsedPanel() { + this(new WhereUsedSummary()); + } + + /** + * Main constructor. + * + * @param whereUsedData The GeolocationSummary instance to use. + */ + public WhereUsedPanel(WhereUsedSummary whereUsedData) { + this.whereUsedData = whereUsedData; + // set up data acquisition methods + dataFetchComponents = Arrays.asList( + new DataFetchWorker.DataFetchComponents<>( + (dataSource) -> whereUsedData.getCityCounts(dataSource, DAYS_COUNT, MAX_COUNT), + (result) -> handleData(result))); + + initComponents(); + } + + private void handleData(DataFetchResult result) { + showCityContent(DataFetchResult.getSubResult(result, (dr) -> dr.getMostCommon()), mostCommonTable, commonViewInGeolocationBtn); + showCityContent(DataFetchResult.getSubResult(result, (dr) -> dr.getMostRecent()), mostRecentTable, recentViewInGeolocationBtn); + } /** * Retrieves the city name to display from the record. @@ -75,89 +140,79 @@ public class WhereUsedPanel extends BaseDataSourceSummaryPanel { return String.format("%s, %s", record.getCityName(), record.getCountry()); } - private static final ColumnModel CITY_COL = new ColumnModel<>( - Bundle.WhereUsedPanel_cityColumn_title(), - (cityCount) -> new DefaultCellModel(getCityName(cityCount.getCityRecord())), - 300 - ); - - private static final ColumnModel COUNT_COL = new ColumnModel<>( - Bundle.WhereUsedPanel_countColumn_title(), - (cityCount) -> new DefaultCellModel(Integer.toString(cityCount.getCount())), - 100 - ); - - // table displaying city and number of hits for that city - private final JTablePanel cityCountsTable = JTablePanel.getJTablePanel(Arrays.asList(CITY_COL, COUNT_COL)) - .setKeyFunction((cityCount) -> cityCount.getCityRecord()); - - // loadable components on this tab - private final List> tables = Arrays.asList( - cityCountsTable - ); - - // means of fetching and displaying data - private final List> dataFetchComponents; - - private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel(); - - private final WhereUsedSummary whereUsedData; - - /** - * Main constructor. - */ - public WhereUsedPanel() { - this(WhereUsedSummary.getInstance()); - } - - /** - * Main constructor. - * - * @param whereUsedData The GeolocationSummary instance to use. - */ - public WhereUsedPanel(WhereUsedSummary whereUsedData) { - this.whereUsedData = whereUsedData; - // set up data acquisition methods - dataFetchComponents = Arrays.asList( - new DataFetchWorker.DataFetchComponents<>( - (dataSource) -> whereUsedData.getCityCounts(dataSource), - (result) -> handleData(result))); - - initComponents(); - } - - private void handleData(DataFetchResult> result) { - if (result != null && result.getResultType() == DataFetchResult.ResultType.SUCCESS && CollectionUtils.isNotEmpty(result.getData())) { - viewInGeolocationBtn.setEnabled(true); + private Pair convert(CityRecordCount cityCount) { + if (cityCount == null) { + return null; } - showResultWithModuleCheck(cityCountsTable, result, GPX_FACTORY, GPX_NAME); + String cityName = getCityName(cityCount.getCityRecord()); + int count = cityCount.getCount(); + return Pair.of(cityName, count); } - private void openGeolocationWindow(DataSource dataSource) { + private List> convert(CityCountsList countsList) { + if (countsList == null) { + return null; + } + + Stream countsStream = ((countsList.getCounts() == null) + ? new ArrayList() + : countsList.getCounts()).stream(); + + Stream> pairStream = countsStream.map((r) -> convert(r)); + + Pair unknownRecord = Pair.of(Bundle.WhereUsedPanel_unknownRow_title(), countsList.getOtherCount()); + + return Stream.concat(pairStream, Stream.of(unknownRecord)) + .filter((p) -> p != null && p.getRight() != null && p.getRight() > 0) + .sorted((a, b) -> -Integer.compare(a.getRight(), b.getRight())) + .limit(MAX_COUNT) + .collect(Collectors.toList()); + } + + private void showCityContent(DataFetchResult result, JTablePanel> table, JButton goToGeolocation) { + DataFetchResult>> convertedData = DataFetchResult.getSubResult(result, (countsList) -> convert(countsList)); + if (convertedData != null && convertedData.getResultType() == DataFetchResult.ResultType.SUCCESS && CollectionUtils.isNotEmpty(convertedData.getData())) { + goToGeolocation.setEnabled(true); + } + + showResultWithModuleCheck(table, convertedData, GPX_FACTORY, GPX_NAME); + } + + private void openGeolocationWindow(DataSource dataSource, Integer daysLimit) { // open the window OpenGeolocationAction geoAction = CallableSystemAction.get(OpenGeolocationAction.class); if (geoAction != null) { geoAction.performAction(); } - + // set the filter TopComponent topComponent = WindowManager.getDefault().findTopComponent(GeolocationTopComponent.class.getSimpleName()); if (topComponent instanceof GeolocationTopComponent) { GeolocationTopComponent geoComponent = (GeolocationTopComponent) topComponent; - geoComponent.fetchAndShowWaypoints(new GeoFilter(true, false, 0, Arrays.asList(dataSource), whereUsedData.getGeoTypes())); + + GeoFilter filter = (daysLimit == null) + ? new GeoFilter(true, false, 0, Arrays.asList(dataSource), whereUsedData.getGeoTypes()) + : new GeoFilter(false, false, DAYS_COUNT, Arrays.asList(dataSource), whereUsedData.getGeoTypes()); + + geoComponent.fetchAndShowWaypoints(filter); } } - + + private void disableNavButtons() { + commonViewInGeolocationBtn.setEnabled(false); + recentViewInGeolocationBtn.setEnabled(false); + } + @Override protected void fetchInformation(DataSource dataSource) { - viewInGeolocationBtn.setEnabled(false); + disableNavButtons(); fetchInformation(dataFetchComponents, dataSource); } @Override protected void onNewDataSource(DataSource dataSource) { - viewInGeolocationBtn.setEnabled(false); + disableNavButtons(); onNewDataSource(dataFetchComponents, tables, dataSource); } @@ -179,13 +234,21 @@ public class WhereUsedPanel extends BaseDataSourceSummaryPanel { javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane(); javax.swing.JPanel mainContentPanel = new javax.swing.JPanel(); javax.swing.JPanel ingestRunningPanel = ingestRunningLabel; - javax.swing.JLabel cityCountsLabel = new javax.swing.JLabel(); + javax.swing.JLabel mostRecentLabel = new javax.swing.JLabel(); javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); - javax.swing.JPanel cityCountsPanel = cityCountsTable; + javax.swing.JPanel mostRecentPanel = mostRecentTable; javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); javax.swing.JLabel withinDistanceLabel = new javax.swing.JLabel(); - javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 10), new java.awt.Dimension(0, 10), new java.awt.Dimension(0, 10)); - viewInGeolocationBtn = new javax.swing.JButton(); + javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 5), new java.awt.Dimension(0, 5), new java.awt.Dimension(0, 5)); + recentViewInGeolocationBtn = new javax.swing.JButton(); + javax.swing.Box.Filler filler8 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); + javax.swing.JLabel mostCommonLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler7 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JPanel mostCommonPanel = mostCommonTable; + javax.swing.Box.Filler filler6 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JLabel withinDistanceLabel1 = new javax.swing.JLabel(); + javax.swing.Box.Filler filler4 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 5), new java.awt.Dimension(0, 5), new java.awt.Dimension(0, 5)); + commonViewInGeolocationBtn = new javax.swing.JButton(); javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); @@ -197,18 +260,18 @@ public class WhereUsedPanel extends BaseDataSourceSummaryPanel { ingestRunningPanel.setPreferredSize(new java.awt.Dimension(10, 25)); mainContentPanel.add(ingestRunningPanel); - org.openide.awt.Mnemonics.setLocalizedText(cityCountsLabel, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "WhereUsedPanel.cityCountsLabel.text")); // NOI18N - mainContentPanel.add(cityCountsLabel); - cityCountsLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(mostRecentLabel, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "WhereUsedPanel.mostRecentLabel.text")); // NOI18N + mainContentPanel.add(mostRecentLabel); + mostRecentLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N filler1.setAlignmentX(0.0F); mainContentPanel.add(filler1); - cityCountsPanel.setAlignmentX(0.0F); - cityCountsPanel.setMaximumSize(new java.awt.Dimension(32767, 212)); - cityCountsPanel.setMinimumSize(new java.awt.Dimension(100, 212)); - cityCountsPanel.setPreferredSize(new java.awt.Dimension(100, 212)); - mainContentPanel.add(cityCountsPanel); + mostRecentPanel.setAlignmentX(0.0F); + mostRecentPanel.setMaximumSize(new java.awt.Dimension(32767, 187)); + mostRecentPanel.setMinimumSize(new java.awt.Dimension(100, 187)); + mostRecentPanel.setPreferredSize(new java.awt.Dimension(100, 187)); + mainContentPanel.add(mostRecentPanel); filler2.setAlignmentX(0.0F); mainContentPanel.add(filler2); @@ -219,14 +282,47 @@ public class WhereUsedPanel extends BaseDataSourceSummaryPanel { filler3.setAlignmentX(0.0F); mainContentPanel.add(filler3); - org.openide.awt.Mnemonics.setLocalizedText(viewInGeolocationBtn, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "WhereUsedPanel.viewInGeolocationBtn.text")); // NOI18N - viewInGeolocationBtn.setEnabled(false); - viewInGeolocationBtn.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(recentViewInGeolocationBtn, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "WhereUsedPanel.recentViewInGeolocationBtn.text")); // NOI18N + recentViewInGeolocationBtn.setEnabled(false); + recentViewInGeolocationBtn.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - viewInGeolocationBtnActionPerformed(evt); + recentViewInGeolocationBtnActionPerformed(evt); } }); - mainContentPanel.add(viewInGeolocationBtn); + mainContentPanel.add(recentViewInGeolocationBtn); + + filler8.setAlignmentX(0.0F); + mainContentPanel.add(filler8); + + org.openide.awt.Mnemonics.setLocalizedText(mostCommonLabel, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "WhereUsedPanel.mostCommonLabel.text")); // NOI18N + mainContentPanel.add(mostCommonLabel); + + filler7.setAlignmentX(0.0F); + mainContentPanel.add(filler7); + + mostCommonPanel.setAlignmentX(0.0F); + mostCommonPanel.setMaximumSize(new java.awt.Dimension(32767, 187)); + mostCommonPanel.setMinimumSize(new java.awt.Dimension(100, 187)); + mostCommonPanel.setPreferredSize(new java.awt.Dimension(100, 187)); + mainContentPanel.add(mostCommonPanel); + + filler6.setAlignmentX(0.0F); + mainContentPanel.add(filler6); + + org.openide.awt.Mnemonics.setLocalizedText(withinDistanceLabel1, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "WhereUsedPanel.withinDistanceLabel1.text")); // NOI18N + mainContentPanel.add(withinDistanceLabel1); + + filler4.setAlignmentX(0.0F); + mainContentPanel.add(filler4); + + org.openide.awt.Mnemonics.setLocalizedText(commonViewInGeolocationBtn, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "WhereUsedPanel.commonViewInGeolocationBtn.text")); // NOI18N + commonViewInGeolocationBtn.setEnabled(false); + commonViewInGeolocationBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + commonViewInGeolocationBtnActionPerformed(evt); + } + }); + mainContentPanel.add(commonViewInGeolocationBtn); filler5.setAlignmentX(0.0F); mainContentPanel.add(filler5); @@ -241,16 +337,21 @@ public class WhereUsedPanel extends BaseDataSourceSummaryPanel { ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 746, Short.MAX_VALUE) ); }// //GEN-END:initComponents - private void viewInGeolocationBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewInGeolocationBtnActionPerformed - openGeolocationWindow(getDataSource()); - }//GEN-LAST:event_viewInGeolocationBtnActionPerformed + private void recentViewInGeolocationBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_recentViewInGeolocationBtnActionPerformed + openGeolocationWindow(getDataSource(), DAYS_COUNT); + }//GEN-LAST:event_recentViewInGeolocationBtnActionPerformed + + private void commonViewInGeolocationBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_commonViewInGeolocationBtnActionPerformed + openGeolocationWindow(getDataSource(), null); + }//GEN-LAST:event_commonViewInGeolocationBtnActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton viewInGeolocationBtn; + private javax.swing.JButton commonViewInGeolocationBtn; + private javax.swing.JButton recentViewInGeolocationBtn; // End of variables declaration//GEN-END:variables }