From d864301c7f083aaafc0f86308dbfbdca17af5501 Mon Sep 17 00:00:00 2001 From: GoldenGnu Date: Wed, 18 Dec 2024 17:58:39 +0100 Subject: [PATCH 1/3] Bug Fix: Stockpile shopping list counted own items double if also in subpile Thanks to @mezoology for reporting the bug --- .../shared/table/EnumTableFormatAdaptor.java | 22 +---- .../gui/tabs/stockpile/Stockpile.java | 99 +++++++++++++++---- .../gui/tabs/stockpile/StockpileData.java | 8 +- .../StockpileShoppingListDialog.java | 81 +++------------ 4 files changed, 95 insertions(+), 115 deletions(-) diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/shared/table/EnumTableFormatAdaptor.java b/src/main/java/net/nikr/eve/jeveasset/gui/shared/table/EnumTableFormatAdaptor.java index 76038e69b..2e83767a8 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/shared/table/EnumTableFormatAdaptor.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/shared/table/EnumTableFormatAdaptor.java @@ -40,8 +40,6 @@ import net.nikr.eve.jeveasset.gui.shared.table.containers.NumberValue; import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.StockpileItem; import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.StockpileTotal; -import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.SubpileItem; -import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.SubpileStock; import net.nikr.eve.jeveasset.gui.tabs.stockpile.StockpileTableFormat; import net.nikr.eve.jeveasset.i18n.GuiShared; import org.slf4j.Logger; @@ -419,26 +417,8 @@ private Object eval(Formula formula, Q e) { return null; } StockpileTotal totalItem = (StockpileTotal) e; - Map map = new HashMap<>(); - //Items - for (StockpileItem item : totalItem.getStockpile().getItems()) { - if (item.isTotal()) { - continue; //Ignore Total - } - map.put(item.getItemTypeID(), item); - } - //SubpileItem (Overwrites StockpileItem items) - for (SubpileItem item : totalItem.getStockpile().getSubpileItems()) { - if (item instanceof SubpileStock) { - continue; - } - map.put(item.getItemTypeID(), item); - } double total = 0.0; - for (StockpileItem item : map.values()) { - if (item.isTotal()) { - continue; //Ignore Total - } + for (StockpileItem item : totalItem.getStockpile().getClaims()) { setVariables(formula, StockpileTableFormat.values(), item); BigDecimal value = safeEval(expression); if (value != null) { diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java index 8568e2818..32f70a86e 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java @@ -449,10 +449,19 @@ public Collection getItems() { } public List getClaims() { - List list = new ArrayList<>(); - list.addAll(items); - list.addAll(subpileAll); - return list; + Map map = new HashMap<>(); + //Items + for (StockpileItem item : items) { + if (item.isTotal()) { + continue; + } + map.put(item.getType(), item); + } + //SubpileItem (Overwrites StockpileItem items) + for (SubpileItem item : subpileItems) { + map.put(item.getType(), item); + } + return new ArrayList<>(map.values()); } @Override @@ -492,20 +501,8 @@ public double getPercentFull() { public void updateTotal() { totalItem.reset(); percentFull = Double.MAX_VALUE; - Map map = new HashMap<>(); - //Items - for (StockpileItem item : items) { - if (item.isTotal()) { - continue; //Ignore Total - } - map.put(item.getItemTypeID(), item); - } - //SubpileItem (Overwrites StockpileItem items) - for (SubpileItem item : subpileItems) { - map.put(item.getItemTypeID(), item); - } //For each item type - for (StockpileItem item : map.values()) { + for (StockpileItem item : getClaims()) { if (item.isTotal()) { continue; //Ignore Total } @@ -567,6 +564,7 @@ public static class StockpileItem implements Comparable, Location private Stockpile stockpile; private Item item; private int typeID; + private TypeIdentifier type; private double countMinimum; private boolean runs; private boolean ignoreMultiplier; @@ -621,6 +619,7 @@ public StockpileItem(final Stockpile stockpile, final Item item, final int typeI this.runs = runs; this.ignoreMultiplier = ignoreMultiplier; this.id = id; + this.type = new TypeIdentifier(typeID, runs); } void update(StockpileItem stockpileItem) { @@ -630,6 +629,7 @@ void update(StockpileItem stockpileItem) { this.countMinimum = stockpileItem.countMinimum; this.runs = stockpileItem.runs; this.ignoreMultiplier = stockpileItem.ignoreMultiplier; + this.type = new TypeIdentifier(typeID, runs); } @Override @@ -1318,6 +1318,10 @@ public Double getTransactionAveragePrice() { return transactionAveragePrice; } + public TypeIdentifier getType() { + return type; + } + public int getItemTypeID() { return typeID; } @@ -2140,4 +2144,65 @@ public void setCountMinimum(double subMultiplier) { public double getVolumeNeeded() { return 0; }; } + + public static class TypeIdentifier { + + private final int typeID; + private final boolean runs; + + public TypeIdentifier(StockpileItem stockpileItem) { + this.typeID = stockpileItem.typeID; + this.runs = stockpileItem.isRuns(); + } + + public TypeIdentifier(int typeID, boolean runs) { + this.typeID = typeID; + this.runs = runs; + } + + public boolean isEmpty() { + return typeID == 0; + } + + public boolean isBPC() { + return typeID < 0; + } + + public boolean isRuns() { + return runs; + } + + public int getTypeID() { + return typeID; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 59 * hash + this.typeID; + hash = 59 * hash + (this.runs ? 1 : 0); + 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 TypeIdentifier other = (TypeIdentifier) obj; + if (this.typeID != other.typeID) { + return false; + } + if (this.runs != other.runs) { + return false; + } + return true; + } + } } diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java index 45fa2a1ef..593273a32 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java @@ -272,7 +272,7 @@ public static Map> contractsMatchAll(ProfileDat for (MyContract contract : profileData.getContractList()) { foundIDs.put(contract, new HashSet<>()); foundItems.put(contract, new ArrayList<>()); - } + } //Update subpile claims if (updateClaims && !stockpile.getSubpiles().isEmpty()) { updateSubpileClaims(stockpile); @@ -280,9 +280,6 @@ public static Map> contractsMatchAll(ProfileDat //StockpileItem map lookup Map stockpileItems = new HashMap<>(); for (StockpileItem stockpileItem : stockpile.getClaims()) { - if (stockpileItem.isTotal()) { - continue; //Ignore Total - } typeIDs.add(stockpileItem.getItemTypeID()); stockpileItems.put(stockpileItem.getItemTypeID(), stockpileItem); } @@ -352,9 +349,6 @@ public static Map> assetsMatchAll(ProfileData profileData //StockpileItem map lookup Map stockpileItems = new HashMap<>(); for (StockpileItem stockpileItem : stockpile.getClaims()) { - if (stockpileItem.isTotal()) { - continue; //Ignore Total - } typeIDs.add(stockpileItem.getItemTypeID()); stockpileItems.put(stockpileItem.getItemTypeID(), stockpileItem); } diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileShoppingListDialog.java b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileShoppingListDialog.java index 714ab13c2..984f74016 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileShoppingListDialog.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileShoppingListDialog.java @@ -64,6 +64,7 @@ import net.nikr.eve.jeveasset.gui.shared.components.JIntegerField; import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.StockpileItem; import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.SubpileStock; +import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.TypeIdentifier; import net.nikr.eve.jeveasset.i18n.TabsStockpile; import net.nikr.eve.jeveasset.io.shared.ApiIdConverter; import org.slf4j.Logger; @@ -88,8 +89,8 @@ private enum StockpileShoppingListAction { private final JTextField jPercentIgnore; private final JComboBox jFormat; private final JComboBox jOutput; + private final Map outputData = new EnumMap<>(ShoppingListType.class); - private Map outputData = new EnumMap<>(ShoppingListType.class); private List stockpiles; private boolean updating = false; @@ -258,18 +259,18 @@ private void updateList() { } } + for (SubpileStock stockpileItem : stockpile.getSubpileStocks()) { + String key = stockpileItem.getName().trim(); + double value = subpiles.getOrDefault(key, 0.0); + subpiles.put(key, value + stockpileItem.getSubMultiplier()); + } for (StockpileItem stockpileItem : stockpile.getClaims()) { - if (stockpileItem instanceof SubpileStock) { - String key = stockpileItem.getName().trim(); - double value = subpiles.getOrDefault(key, 0.0); - subpiles.put(key, value + ((SubpileStock) stockpileItem).getSubMultiplier()); - } - TypeIdentifier typeID = new TypeIdentifier(stockpileItem); - if (!typeID.isEmpty()) { //Ignore Total - List claimList = claims.get(typeID); + TypeIdentifier type = stockpileItem.getType(); + if (!type.isEmpty()) { //Ignore Total + List claimList = claims.get(type); if (claimList == null) { claimList = new ArrayList<>(); - claims.put(typeID, claimList); + claims.put(type, claimList); } claimList.add(new StockClaim(stockpileItem, contractIDs, assetsIDs, percent)); } @@ -684,66 +685,6 @@ public boolean equals(Object obj) { } } - private static class TypeIdentifier { - private final int typeID; - private final boolean runs; - - public TypeIdentifier(StockpileItem stockpileItem) { - this.typeID = stockpileItem.getItemTypeID(); - this.runs = stockpileItem.isRuns(); - } - - public TypeIdentifier(int typeID, boolean runs) { - this.typeID = typeID; - this.runs = runs; - } - - public boolean isEmpty() { - return typeID == 0; - } - - public boolean isBPC() { - return typeID < 0; - } - - public boolean isRuns() { - return runs; - } - - public int getTypeID() { - return typeID; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 59 * hash + this.typeID; - hash = 59 * hash + (this.runs ? 1 : 0); - 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 TypeIdentifier other = (TypeIdentifier) obj; - if (this.typeID != other.typeID) { - return false; - } - if (this.runs != other.runs) { - return false; - } - return true; - } - } - private static enum ShoppingListType { SHOPPING_LIST(TabsStockpile.get().shoppingList(), Images.STOCKPILE_SHOPPING_LIST.getIcon()){ @Override From 2234a1541900d388ec61c505ac2f4d104e84ecf2 Mon Sep 17 00:00:00 2001 From: GoldenGnu Date: Wed, 18 Dec 2024 19:04:41 +0100 Subject: [PATCH 2/3] Bug Fix: Made stockpile match all differentiate between bpc and runs --- .../gui/tabs/stockpile/Stockpile.java | 6 +- .../gui/tabs/stockpile/StockpileData.java | 160 +++++++++++------- 2 files changed, 100 insertions(+), 66 deletions(-) diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java index 32f70a86e..b279423dd 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/Stockpile.java @@ -449,6 +449,10 @@ public Collection getItems() { } public List getClaims() { + return new ArrayList<>(getClaimsMap().values()); + } + + public Map getClaimsMap() { Map map = new HashMap<>(); //Items for (StockpileItem item : items) { @@ -461,7 +465,7 @@ public List getClaims() { for (SubpileItem item : subpileItems) { map.put(item.getType(), item); } - return new ArrayList<>(map.values()); + return map; } @Override diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java index 593273a32..0421e26f6 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/stockpile/StockpileData.java @@ -22,6 +22,7 @@ import ca.odell.glazedlists.EventList; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -48,6 +49,7 @@ import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.StockpileItem; import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.SubpileItem; import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.SubpileStock; +import net.nikr.eve.jeveasset.gui.tabs.stockpile.Stockpile.TypeIdentifier; import net.nikr.eve.jeveasset.io.shared.ApiIdConverter; @@ -265,61 +267,61 @@ private void updateStockpileItems(Stockpile stockpile, boolean updateClaims) { } public static Map> contractsMatchAll(ProfileData profileData, Stockpile stockpile, boolean updateClaims) { - Map> foundIDs = new HashMap<>(); + Map> foundIDs = new HashMap<>(); Map> foundItems = new HashMap<>(); - Set typeIDs = new HashSet<>(); //Init found maps for (MyContract contract : profileData.getContractList()) { foundIDs.put(contract, new HashSet<>()); foundItems.put(contract, new ArrayList<>()); - } + } //Update subpile claims if (updateClaims && !stockpile.getSubpiles().isEmpty()) { updateSubpileClaims(stockpile); } //StockpileItem map lookup - Map stockpileItems = new HashMap<>(); - for (StockpileItem stockpileItem : stockpile.getClaims()) { - typeIDs.add(stockpileItem.getItemTypeID()); - stockpileItems.put(stockpileItem.getItemTypeID(), stockpileItem); - } + Map stockpileItems = stockpile.getClaimsMap(); //Contract Items matching for (MyContractItem contractItem : profileData.getContractItemList()) { //Validate contract if (contractItem.getContract().isIgnoreContract()) { continue; } - Integer typeID = get(contractItem.getTypeID(), contractItem.isBPC()); - //Validate typeID - if (ignore(typeIDs, typeID)) { - foundItems.remove(contractItem.getContract()); //Contract have items not in the stockpile - continue; //Nothing left to do here - } - //Get items - List items = foundItems.get(contractItem.getContract()); - if (items == null) { - continue; //Happens when one or more typeIDs from the contract isn't in the stockpile - } - //Get contract typeIDs - Set ids = foundIDs.get(contractItem.getContract()); - if (ids == null) { - continue; //Should never happen, but, better safe than sorry - } - //Get StockpileItem - StockpileItem stockpileItem = stockpileItems.get(typeID); - if (stockpileItem == null) { - continue; //Should never happen, but, better safe than sorry + boolean found = false; + for (TypeIdentifier type : getTypes(contractItem.getTypeID(), contractItem.isBPC())) { + //Validate typeID + if (ignore(stockpileItems.keySet(), type)) { + continue; //Nothing left to do here + } + //Get items + List items = foundItems.get(contractItem.getContract()); + if (items == null) { + continue; //Happens when one or more typeIDs from the contract isn't in the stockpile + } + //Get contract typeIDs + Set ids = foundIDs.get(contractItem.getContract()); + if (ids == null) { + continue; //Should never happen, but, better safe than sorry + } + //Get StockpileItem + StockpileItem stockpileItem = stockpileItems.get(type); + if (stockpileItem == null) { + continue; //Should never happen, but, better safe than sorry + } + if (stockpileItem.matchesContract(contractItem)) { + items.add(contractItem); + ids.add(type); + break; //Can only match once + } } - if (stockpileItem.matchesContract(contractItem)) { - items.add(contractItem); - ids.add(typeID); + if (!found) { + foundItems.remove(contractItem.getContract()); //Contract have items not in the stockpile } } //Stockpile Items matching - for (Map.Entry> entry : foundIDs.entrySet()) { + for (Map.Entry> entry : foundIDs.entrySet()) { //Only compare the size of the sets, as both sets only contains valid and unique ids. //Therefore there should be no reason to compare the actualy IDs (which is really really slow) - if (entry.getValue().size() != typeIDs.size()) { //Stockpile have items not in the contract + if (entry.getValue().size() != stockpileItems.keySet().size()) { //Stockpile have items not in the contract foundItems.remove(entry.getKey()); } } @@ -327,10 +329,9 @@ public static Map> contractsMatchAll(ProfileDat } public static Map> assetsMatchAll(ProfileData profileData, Stockpile stockpile, boolean updateClaims) { - Map> foundIDs = new HashMap<>(); + Map> foundIDs = new HashMap<>(); Map> foundItems = new HashMap<>(); Map> parents = new HashMap<>(); - Set typeIDs = new HashSet<>(); //Init found maps for (MyAsset asset : profileData.getAssetsList()) { if (asset.getAssets().isEmpty()) { @@ -347,11 +348,7 @@ public static Map> assetsMatchAll(ProfileData profileData updateSubpileClaims(stockpile); } //StockpileItem map lookup - Map stockpileItems = new HashMap<>(); - for (StockpileItem stockpileItem : stockpile.getClaims()) { - typeIDs.add(stockpileItem.getItemTypeID()); - stockpileItems.put(stockpileItem.getItemTypeID(), stockpileItem); - } + Map stockpileItems = stockpile.getClaimsMap(); //Contract Items matching for (Map.Entry> entry : parents.entrySet()) { MyAsset parent = entry.getKey(); @@ -360,38 +357,45 @@ public static Map> assetsMatchAll(ProfileData profileData if (child.isGenerated()) { continue; } - Integer typeID = get(child.getTypeID(), child.isBPC()); - //Validate typeID - if (ignore(typeIDs, typeID)) { - foundItems.remove(parent); //Contract have items not in the stockpile - continue; //Nothing left to do here - } - //Get items - List items = foundItems.get(parent); - if (items == null) { - continue; //Happens when one or more typeIDs from the contract isn't in the stockpile - } - //Get contract typeIDs - Set ids = foundIDs.get(parent); - if (ids == null) { - continue; //Should never happen, but, better safe than sorry - } - //Get StockpileItem - StockpileItem stockpileItem = stockpileItems.get(typeID); - if (stockpileItem == null) { - continue; //Should never happen, but, better safe than sorry + boolean found = false; + for (TypeIdentifier type : getTypes(child.getTypeID(), child.isBPC())) { + //Validate typeID + if (ignore(stockpileItems.keySet(), type)) { + continue; //Nothing left to do here + } + + //Get items + List items = foundItems.get(parent); + if (items == null) { + continue; //Happens when one or more typeIDs from the contract isn't in the stockpile + } + //Get contract typeIDs + Set ids = foundIDs.get(parent); + if (ids == null) { + continue; //Should never happen, but, better safe than sorry + } + //Get StockpileItem + StockpileItem stockpileItem = stockpileItems.get(type); + if (stockpileItem == null) { + continue; //Should never happen, but, better safe than sorry + } + if (stockpileItem.matchesAsset(child)) { + items.add(child); + ids.add(type); + found = true; + break; //Can only match once + } } - if (stockpileItem.matchesAsset(child)) { - items.add(child); - ids.add(typeID); + if (!found) { + foundItems.remove(parent); //Contract have items not in the stockpile } } } //Stockpile Items matching - for (Map.Entry> entry : foundIDs.entrySet()) { + for (Map.Entry> entry : foundIDs.entrySet()) { //Only compare the size of the sets, as both sets only contains valid and unique ids. //Therefore there should be no reason to compare the actualy IDs (which is really really slow) - if (entry.getValue().size() != typeIDs.size()) { //Stockpile have items not in the contract + if (entry.getValue().size() != stockpileItems.keySet().size()) { //Stockpile have items not in the contract foundItems.remove(entry.getKey()); } } @@ -418,6 +422,23 @@ public static Integer get(Integer typeID, boolean bpc) { return typeID; } + public static List getTypes(Integer typeID, boolean bpc) { + //Ignore null + if (typeID == null) { + return Collections.emptyList(); + } + //BPC has negative value + if (bpc) { + typeID = -typeID; + List list = new ArrayList<>(); + list.add(new TypeIdentifier(typeID, true)); + list.add(new TypeIdentifier(typeID, false)); + return list; + } else { + return Collections.singletonList(new TypeIdentifier(typeID, false)); + } + } + private static boolean ignore(Set typeIDs, Integer typeID) { //Ignore null if (typeID == null) { @@ -427,6 +448,15 @@ private static boolean ignore(Set typeIDs, Integer typeID) { return !typeIDs.contains(typeID); } + private static boolean ignore(Set typeIDs, TypeIdentifier typeID) { + //Ignore null + if (typeID == null) { + return true; + } + //Ignore wrong typeID + return !typeIDs.contains(typeID); + } + private void add(Map> map, Integer typeID, T t) { if (typeID == null) { return; //Ignore null (should never happen: better safe than sorry) From 4f4af10fcbbac3448aca6ef60f1644f8a6752261 Mon Sep 17 00:00:00 2001 From: GoldenGnu Date: Wed, 18 Dec 2024 19:11:30 +0100 Subject: [PATCH 3/3] Bug Fix: Importing tracker data replaced existing data --- .../jeveasset/data/settings/TrackerData.java | 27 +++++++- .../gui/tabs/tracker/TrackerTab.java | 42 ++++++++++-- .../eve/jeveasset/gui/tabs/values/Value.java | 12 ++++ .../nikr/eve/jeveasset/i18n/TabsTracker.java | 8 ++- .../eve/jeveasset/i18n/TabsTracker.properties | 12 ++-- .../data/settings/TrackerDataTest.java | 64 +++++++++++++++++++ 6 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 src/test/java/net/nikr/eve/jeveasset/data/settings/TrackerDataTest.java diff --git a/src/main/java/net/nikr/eve/jeveasset/data/settings/TrackerData.java b/src/main/java/net/nikr/eve/jeveasset/data/settings/TrackerData.java index a948f27bf..9d34bc8c1 100644 --- a/src/main/java/net/nikr/eve/jeveasset/data/settings/TrackerData.java +++ b/src/main/java/net/nikr/eve/jeveasset/data/settings/TrackerData.java @@ -25,6 +25,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.locks.ReentrantReadWriteLock; import net.nikr.eve.jeveasset.CliOptions; import net.nikr.eve.jeveasset.gui.tabs.values.Value; @@ -37,7 +39,7 @@ public class TrackerData { private static final Logger LOG = LoggerFactory.getLogger(TrackerData.class); - private static final Map> TRACKER_DATA = new HashMap>(); //ownerID :: long + private static final Map> TRACKER_DATA = new HashMap<>(); //ownerID :: long private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); private static final Object SAVE_QUEUE_SYNC = new Object(); private static Integer SAVE_QUEUE = 0; @@ -116,10 +118,29 @@ public static void add(String owner, Value add) { } } - public static void addAll(Map> trackerData) { + public static void addAll(Map> trackerData, boolean overwrite) { try { LOCK.writeLock().lock(); - TRACKER_DATA.putAll(trackerData); + for (Map.Entry> entry : trackerData.entrySet()) { + //For each owner + String owner = entry.getKey(); + List list = TRACKER_DATA.get(owner); + if (list == null) { //Owner doesn't exist + list = new ArrayList<>(); + TRACKER_DATA.put(owner, list); + } + //Remove duplicates, while staying in order + Set set = new TreeSet<>(Value.DATE_COMPARATOR); + if (overwrite) { + set.addAll(entry.getValue()); //Add new data (First) + set.addAll(list); //Add old data (Second, only add if not already contains) + } else { + set.addAll(list); //Add old data (First) + set.addAll(entry.getValue()); //Add new data (Second, only add if not already contains) + } + list.clear(); //Clear old data + list.addAll(set); //Set new merged data + } } finally { LOCK.writeLock().unlock(); } diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/tracker/TrackerTab.java b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/tracker/TrackerTab.java index 774b29abd..96372b217 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/tracker/TrackerTab.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/tracker/TrackerTab.java @@ -152,6 +152,35 @@ private enum TrackerAction { FILTER_SKILL_POINTS } + public static enum ImportOptions { + KEEP() { + @Override + public String getName() { + return TabsTracker.get().importFileOptionsKeep(); + } + }, + OVERWRITE() { + @Override + public String getName() { + return TabsTracker.get().importFileOptionsOverwrite(); + } + }, + REPLACE() { + @Override + public String getName() { + return TabsTracker.get().importFileOptionsReplace(); + } + }; + + abstract public String getName(); + + @Override + public String toString() { + return getName(); + } + + } + private final Shape NO_FILTER = new Rectangle(-3, -3, 6, 6); private final Shape FILTER_AND_DEFAULT = new Ellipse2D.Float(-3.0f, -3.0f, 6.0f, 6.0f); private final int PANEL_WIDTH_MINIMUM = 160; @@ -1843,12 +1872,12 @@ public void task() { @Override public void hidden() { if (trackerData == null) { //Invalid file - JOptionPane.showMessageDialog(program.getMainWindow().getFrame(), TabsTracker.get().importFileInvalidMsg(), TabsTracker.get().importFileInvalidTitle(), JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(program.getMainWindow().getFrame(), TabsTracker.get().importFileInvalidMsg(), TabsTracker.get().importFileTitle(), JOptionPane.WARNING_MESSAGE); return; } //Overwrite? - int value = JOptionPane.showConfirmDialog(program.getMainWindow().getFrame(), TabsTracker.get().importFileOverwriteMsg(), TabsTracker.get().importFileOverwriteTitle(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); - if (value != JOptionPane.OK_OPTION) { + Object value = JOptionPane.showInputDialog(program.getMainWindow().getFrame(), TabsTracker.get().importFileOptionsMsg(), TabsTracker.get().importFileTitle(), JOptionPane.PLAIN_MESSAGE, null, ImportOptions.values(), ImportOptions.KEEP); + if (value == null) { return; //Cancel } SwingUtilities.invokeLater(new Runnable() { @@ -1857,10 +1886,13 @@ public void run() { jLockWindow.show(TabsTracker.get().importFileImport(), new LockWorkerAdaptor() { @Override public void task() { - TrackerData.addAll(trackerData); + if (value == ImportOptions.REPLACE) { + TrackerData.set(trackerData); + } else { + TrackerData.addAll(trackerData, value == ImportOptions.OVERWRITE); + } TrackerData.save("File Import", true); } - @Override public void gui() { updateData(); diff --git a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/values/Value.java b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/values/Value.java index c49ba6ada..a172e7c0a 100644 --- a/src/main/java/net/nikr/eve/jeveasset/gui/tabs/values/Value.java +++ b/src/main/java/net/nikr/eve/jeveasset/gui/tabs/values/Value.java @@ -21,6 +21,7 @@ package net.nikr.eve.jeveasset.gui.tabs.values; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -35,6 +36,8 @@ public class Value implements Comparable, LocationType { + + public static final Comparator DATE_COMPARATOR = new ValueDateComparator(); private final static long MINIMUM_SKILL_POINTS = 5000000; private final static double SKILL_EXTRACTOR_SIZE = 500000.0; private final String name; @@ -447,4 +450,13 @@ public boolean equals(Object obj) { public int compareTo(Value o) { return this.getName().compareToIgnoreCase(o.getName()); } + + private static class ValueDateComparator implements Comparator { + + @Override + public int compare(Value o1, Value o2) { + return Formatter.simpleDate(o1.date).compareTo(Formatter.simpleDate(o2.date)); + } + + } } diff --git a/src/main/java/net/nikr/eve/jeveasset/i18n/TabsTracker.java b/src/main/java/net/nikr/eve/jeveasset/i18n/TabsTracker.java index 69f778c61..e0697dd14 100644 --- a/src/main/java/net/nikr/eve/jeveasset/i18n/TabsTracker.java +++ b/src/main/java/net/nikr/eve/jeveasset/i18n/TabsTracker.java @@ -68,9 +68,11 @@ public TabsTracker(final Locale locale) { public abstract String importFile(); public abstract String importFileImport(); public abstract String importFileInvalidMsg(); - public abstract String importFileInvalidTitle(); - public abstract String importFileOverwriteMsg(); - public abstract String importFileOverwriteTitle(); + public abstract String importFileOptionsKeep(); + public abstract String importFileOptionsMsg(); + public abstract String importFileOptionsOverwrite(); + public abstract String importFileOptionsReplace(); + public abstract String importFileTitle(); public abstract String includeZero(); public abstract String invalid(); public abstract String invalidNumberMsg(); diff --git a/src/main/resources/net/nikr/eve/jeveasset/i18n/TabsTracker.properties b/src/main/resources/net/nikr/eve/jeveasset/i18n/TabsTracker.properties index be1d8ecc3..bc14ba0ba 100644 --- a/src/main/resources/net/nikr/eve/jeveasset/i18n/TabsTracker.properties +++ b/src/main/resources/net/nikr/eve/jeveasset/i18n/TabsTracker.properties @@ -40,11 +40,13 @@ helpNewData=Filterable importFile=Import file... importFileImport=Importing... importFileInvalidMsg=Not a valid tracker data file -importFileInvalidTitle=Tracker Import -importFileOverwriteMsg=Are you sure you want to overwrite?\n\ -Your existing tracker data will be replaced by the imported data.\n\ -This action can not be undone! -importFileOverwriteTitle=Tracker Import +importFileOptionsKeep=Merge: On duplicates use existing data +importFileOptionsMsg=Warning: This action can not be undone!\n\ +\n\ +Select how to import: +importFileOptionsOverwrite=Merge: On duplicates use imported data +importFileOptionsReplace=Delete: Delete all exisitng data and replaced with imported data +importFileTitle=Tracker Import includeZero=Always include zero invalid=Invalid input invalidNumberMsg=Not a valid number, try again... diff --git a/src/test/java/net/nikr/eve/jeveasset/data/settings/TrackerDataTest.java b/src/test/java/net/nikr/eve/jeveasset/data/settings/TrackerDataTest.java new file mode 100644 index 000000000..4c4c5558b --- /dev/null +++ b/src/test/java/net/nikr/eve/jeveasset/data/settings/TrackerDataTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2009-2024 Contributors (see credits.txt) + * + * This file is part of jEveAssets. + * + * jEveAssets is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * jEveAssets is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with jEveAssets; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +package net.nikr.eve.jeveasset.data.settings; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import net.nikr.eve.jeveasset.TestUtil; +import net.nikr.eve.jeveasset.gui.tabs.values.Value; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Test; + + +public class TrackerDataTest extends TestUtil { + + @Test + public void addAll() { + Map> values; + List list; + String owner = "owner"; + Date date = new Date(); + Value oldValue = new Value("Old", date); + Value newValue = new Value("New", date); + TrackerData.set(Collections.singletonMap(owner, new ArrayList<>(Collections.singletonList(oldValue)))); + TrackerData.addAll(Collections.singletonMap(owner, new ArrayList<>(Collections.singletonList(newValue))), true); + values = TrackerData.get(); + list = values.get(owner); + assertNotNull(list); + assertEquals(1, list.size()); + assertEquals(newValue.getName(), list.get(0).getName()); + TrackerData.set(Collections.emptyMap()); + + TrackerData.set(Collections.singletonMap(owner, new ArrayList<>(Collections.singletonList(oldValue)))); + TrackerData.addAll(Collections.singletonMap(owner, new ArrayList<>(Collections.singletonList(newValue))), false); + values = TrackerData.get(); + list = values.get(owner); + assertNotNull(list); + assertEquals(1, list.size()); + assertEquals(oldValue.getName(), list.get(0).getName()); + TrackerData.set(Collections.emptyMap()); + } + +}