diff --git a/.gitignore b/.gitignore index 8e09288..dc416eb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ ConceptClassIds.txt DomainIds.txt VocabularyIds.txt vocabularyVersion.txt +authorName.txt local_files/ diff --git a/src/org/ohdsi/usagi/Concept.java b/src/org/ohdsi/usagi/Concept.java index 8f97f8e..0056590 100644 --- a/src/org/ohdsi/usagi/Concept.java +++ b/src/org/ohdsi/usagi/Concept.java @@ -20,6 +20,8 @@ import com.sleepycat.persist.model.Entity; import com.sleepycat.persist.model.PrimaryKey; +import java.util.Objects; + /** * Class for holding information about a single (target) concept in the Vocabulary */ @@ -77,4 +79,18 @@ public static Concept createEmptyConcept() { public Concept() { } + + @Override + public boolean equals(Object o) { + // Only compare conceptId + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Concept concept = (Concept) o; + return conceptId == concept.conceptId; + } + + @Override + public int hashCode() { + return Objects.hash(conceptId); + } } diff --git a/src/org/ohdsi/usagi/MappingTarget.java b/src/org/ohdsi/usagi/MappingTarget.java index 5341dc6..5883130 100644 --- a/src/org/ohdsi/usagi/MappingTarget.java +++ b/src/org/ohdsi/usagi/MappingTarget.java @@ -15,12 +15,24 @@ ******************************************************************************/ package org.ohdsi.usagi; +import java.util.Objects; + /** * Class for holding information about a single (target) concept in the Vocabulary */ public class MappingTarget{ public enum Type { - MAPS_TO, MAPS_TO_VALUE, MAPS_TO_UNIT + MAPS_TO, MAPS_TO_VALUE, MAPS_TO_UNIT, MAPS_TO_OPERATOR, MAPS_TO_TYPE, MAPS_TO_NUMBER; + + public static Type valueOfCompat(String value) { + // For backwards compatibility with old types + switch (value) { + case "EVENT": return MAPS_TO; + case "VALUE": return MAPS_TO_VALUE; + case "UNIT": return MAPS_TO_UNIT; + default: return valueOf(value); + } + } } private final Concept concept; @@ -72,4 +84,18 @@ public Type getMappingType() { public void setMappingType(Type mappingType) { this.mappingType = mappingType; } + + @Override + public boolean equals(Object o) { + // Only compares target concept and mappingType. + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MappingTarget that = (MappingTarget) o; + return Objects.equals(concept, that.concept) && mappingType == that.mappingType; + } + + @Override + public int hashCode() { + return Objects.hash(concept, mappingType); + } } diff --git a/src/org/ohdsi/usagi/ReadCodeMappingsFromFile.java b/src/org/ohdsi/usagi/ReadCodeMappingsFromFile.java index f514fdd..c4611e2 100644 --- a/src/org/ohdsi/usagi/ReadCodeMappingsFromFile.java +++ b/src/org/ohdsi/usagi/ReadCodeMappingsFromFile.java @@ -57,7 +57,7 @@ private void readNext() { buffer = null; } else { buffer = new CodeMapping(new SourceCode(row)); - buffer.setMatchScore(row.getDouble("matchScore")); + buffer.setMatchScore(row.getDouble("matchScore", "0")); buffer.setMappingStatus(MappingStatus.valueOf(row.get("mappingStatus"))); // Status provenance and review need a default as these fields might not be available in older Usagi files @@ -77,14 +77,9 @@ && new SourceCode(row).sourceName.equals(buffer.getSourceCode().sourceName)) { buffer.setMappingStatus(MappingStatus.INVALID_TARGET); buffer.setComment("Invalid existing target: " + row.get("conceptId")); } else { - // Type and provenance might not be available in older Usagi files MappingTarget mappingTarget = new MappingTarget( concept, - MappingTarget.Type.valueOf(row.get("mappingType", "MAPS_TO") - .replace("EVENT", "MAPS_TO") - .replace("VALUE", "MAPS_TO_VALUE") - .replace("UNIT", "MAPS_TO_UNIT") - ), + MappingTarget.Type.valueOfCompat(row.get("mappingType", "MAPS_TO")), row.get("createdBy", ""), row.getLong("createdOn", "0") ); diff --git a/src/org/ohdsi/usagi/WriteCodeMappingsToFile.java b/src/org/ohdsi/usagi/WriteCodeMappingsToFile.java index 2e62d92..9329b78 100644 --- a/src/org/ohdsi/usagi/WriteCodeMappingsToFile.java +++ b/src/org/ohdsi/usagi/WriteCodeMappingsToFile.java @@ -51,7 +51,8 @@ public void write(CodeMapping codeMapping) { row.add("statusSetBy", codeMapping.getStatusSetBy()); row.add("statusSetOn", codeMapping.getStatusSetOn()); row.add("conceptId", targetConcept.getConcept().conceptId); - row.add("conceptName", targetConcept.getConcept().conceptName); // Never read in. + row.add("conceptName", targetConcept.getConcept().conceptName); // Redundant, not read in + row.add("domainId", targetConcept.getConcept().domainId); // Redundant, not read in row.add("mappingType", targetConcept.getMappingType().toString()); row.add("comment", codeMapping.getComment()); row.add("createdBy", targetConcept.getCreatedBy()); diff --git a/src/org/ohdsi/usagi/ui/AuthorDialog.java b/src/org/ohdsi/usagi/ui/AuthorDialog.java index 6b83d35..ee77288 100644 --- a/src/org/ohdsi/usagi/ui/AuthorDialog.java +++ b/src/org/ohdsi/usagi/ui/AuthorDialog.java @@ -15,15 +15,18 @@ ******************************************************************************/ package org.ohdsi.usagi.ui; +import org.ohdsi.utilities.files.WriteTextFile; + import javax.swing.*; import java.awt.*; public class AuthorDialog extends JDialog { - private static final long serialVersionUID = 8239922540117895957L; + private static final long serialVersionUID = 8239922540117895957L; + private String authorFileName; public AuthorDialog() { - setTitle("Author"); + setTitle("Usagi v" + UsagiMain.version); setLayout(new GridBagLayout()); GridBagConstraints g = new GridBagConstraints(); g.fill = GridBagConstraints.BOTH; @@ -32,7 +35,7 @@ public AuthorDialog() { g.gridx = 0; g.gridy = 0; - add(new JLabel("Author:"), g); + add(new JLabel(" Author:"), g); g.gridx = 1; g.gridy = 0; @@ -41,6 +44,12 @@ public AuthorDialog() { authorField.setPreferredSize(new Dimension(100, 10)); add(authorField, g); + g.gridx = 0; + g.gridy = 1; + g.gridwidth = 2; + JCheckBox saveBox = new JCheckBox("Remember me?"); + add(saveBox, g); + g.gridx = 0; g.gridy = 2; g.gridwidth = 2; @@ -52,6 +61,11 @@ public AuthorDialog() { saveButton.addActionListener(event -> { Global.author = authorField.getText(); setVisible(false); + if (saveBox.isSelected() && authorFileName != null) { + WriteTextFile out = new WriteTextFile(authorFileName); + out.writeln(authorField.getText()); + out.close(); + } }); buttonPanel.add(saveButton); add(buttonPanel, g); @@ -60,4 +74,9 @@ public AuthorDialog() { setModal(true); setLocationRelativeTo(Global.frame); } + + public void setAuthorFileName(String authorFileName) { + this.authorFileName = authorFileName; + } + } diff --git a/src/org/ohdsi/usagi/ui/ImportDialog.java b/src/org/ohdsi/usagi/ui/ImportDialog.java index 054dc81..998e1e6 100644 --- a/src/org/ohdsi/usagi/ui/ImportDialog.java +++ b/src/org/ohdsi/usagi/ui/ImportDialog.java @@ -101,10 +101,15 @@ private void loadData(String filename) { if (!iterator.hasNext()) throw new RuntimeException("File contains no data"); columnNames = iterator.next(); - Set uniqueNames = new HashSet(); - for (String columnName : columnNames) - if (!uniqueNames.add(columnName)) + Set uniqueNames = new HashSet<>(); + for (String columnName : columnNames) { + if (columnName.isEmpty()) { + continue; + } + if (!uniqueNames.add(columnName)) { throw new RuntimeException("Found duplicate column name '" + columnName + "', duplicates are not allowed."); + } + } comboBoxOptions = new String[columnNames.size() + 1]; comboBoxOptions[0] = ""; for (int i = 0; i < columnNames.size(); i++) diff --git a/src/org/ohdsi/usagi/ui/Mapping.java b/src/org/ohdsi/usagi/ui/Mapping.java index d760ce0..deb8c62 100644 --- a/src/org/ohdsi/usagi/ui/Mapping.java +++ b/src/org/ohdsi/usagi/ui/Mapping.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import javax.swing.JOptionPane; @@ -24,6 +25,7 @@ import org.ohdsi.usagi.ReadCodeMappingsFromFile; import org.ohdsi.usagi.SourceCode; import org.ohdsi.usagi.WriteCodeMappingsToFile; +import org.ohdsi.utilities.collections.Pair; import static org.ohdsi.usagi.ui.DataChangeEvent.*; @@ -83,9 +85,15 @@ public void saveToFile(String filename) { } public List getSourceCodes() { - List sourceCodes = new ArrayList(size()); - for (CodeMapping codeMapping : this) - sourceCodes.add(codeMapping.getSourceCode()); - return sourceCodes; + return this.stream() + .map(CodeMapping::getSourceCode) + .collect(Collectors.toList()); + } + + public List getAdditionalColumnNames() { + CodeMapping codeMapping = Global.mapping.get(0); + return codeMapping.getSourceCode().sourceAdditionalInfo.stream() + .map(Pair::getItem1) + .collect(Collectors.toList()); } } diff --git a/src/org/ohdsi/usagi/ui/MappingDetailPanel.java b/src/org/ohdsi/usagi/ui/MappingDetailPanel.java index 5b479ea..f3b3049 100644 --- a/src/org/ohdsi/usagi/ui/MappingDetailPanel.java +++ b/src/org/ohdsi/usagi/ui/MappingDetailPanel.java @@ -53,7 +53,8 @@ public class MappingDetailPanel extends JPanel implements CodeSelectedListener, private JComboBox equivalenceOptionChooser; private JTextField commentField; private JButton removeButton; - private JComboBox typesChooser; + private JComboBox targetMappingTypesChooser; + private JComboBox addMappingsTypesChooser; private JButton replaceButton; private List addButtons; private JRadioButton autoQueryCodeButton; @@ -179,9 +180,11 @@ private Component createSearchResultsPanel() { if (viewRow == -1 || codeMapping.getMappingStatus() == MappingStatus.APPROVED) { addButtons.forEach(x -> x.setEnabled(false)); replaceButton.setEnabled(false); + addMappingsTypesChooser.setEnabled(false); } else { addButtons.forEach(x -> x.setEnabled(true)); replaceButton.setEnabled(true); + addMappingsTypesChooser.setEnabled(true); int modelRow = searchTable.convertRowIndexToModel(viewRow); Global.conceptInfoAction.setEnabled(true); Global.conceptInformationDialog.setActiveConcept(searchTableModel.getConcept(modelRow)); @@ -211,28 +214,23 @@ private Component createSearchResultsPanel() { buttonPanel.add(replaceButton); JButton button; - addButtons = new ArrayList<>(); - for (MappingTarget.Type mappingType : MappingTarget.Type.values()) { - if (mappingType.equals(MappingTarget.Type.MAPS_TO)) { - button = new JButton("Add concept"); - } else { - button = new JButton(String.format("Add %s", mappingType)); - } - button.setToolTipText(String.format("Add selected concept as %s", mappingType)); - button.addActionListener(e -> { - int viewRow = searchTable.getSelectedRow(); - int modelRow = searchTable.convertRowIndexToModel(viewRow); - addConcept(searchTableModel.getConcept(modelRow), mappingType); - }); - button.setEnabled(false); - addButtons.add(button); - // Add Maps_to button on the right, the other on the left. - if (mappingType.equals(MappingTarget.Type.MAPS_TO)) { - buttonPanel.add(button); - } else { - buttonPanel.add(button, 0); - } - } + button = new JButton("Add concept"); + button.setToolTipText(String.format("Add selected concept")); + button.addActionListener(e -> { + int viewRow = searchTable.getSelectedRow(); + int modelRow = searchTable.convertRowIndexToModel(viewRow); + addConcept(searchTableModel.getConcept(modelRow), (MappingTarget.Type) addMappingsTypesChooser.getSelectedItem()); + }); + button.setEnabled(false); + buttonPanel.add(button); + addButtons = new ArrayList<>(); // There used to be an add button for each mapping type + addButtons.add(button); + + addMappingsTypesChooser = new JComboBox<>(MappingTarget.Type.values()); + addMappingsTypesChooser.setToolTipText("Set type of the mapping to be added"); + addMappingsTypesChooser.setMaximumSize(addMappingsTypesChooser.getPreferredSize()); + addMappingsTypesChooser.setEnabled(false); + buttonPanel.add(addMappingsTypesChooser, 0); panel.add(buttonPanel); @@ -322,13 +320,13 @@ private JPanel createTargetConceptsPanel() { int viewRow = targetConceptTable.getSelectedRow(); if (viewRow == -1 || codeMapping.getMappingStatus() == MappingStatus.APPROVED) { removeButton.setEnabled(false); - typesChooser.setEnabled(false); + targetMappingTypesChooser.setEnabled(false); } else { removeButton.setEnabled(true); - typesChooser.setEnabled(true); + targetMappingTypesChooser.setEnabled(true); int modelRow = targetConceptTable.convertRowIndexToModel(viewRow); MappingTarget mappingTarget = targetConceptTableModel.getMappingTarget(modelRow); - typesChooser.setSelectedItem(mappingTarget.getMappingType()); + targetMappingTypesChooser.setSelectedItem(mappingTarget.getMappingType()); Global.conceptInfoAction.setEnabled(true); Global.conceptInformationDialog.setActiveConcept(mappingTarget.getConcept()); Global.athenaAction.setEnabled(true); @@ -342,22 +340,22 @@ private JPanel createTargetConceptsPanel() { JScrollPane pane = new JScrollPane(targetConceptTable); pane.setBorder(BorderFactory.createEmptyBorder()); - pane.setMinimumSize(new Dimension(500, 50)); - pane.setPreferredSize(new Dimension(500, 50)); + pane.setMinimumSize(new Dimension(500, 75)); + pane.setPreferredSize(new Dimension(500, 75)); panel.add(pane); JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); - typesChooser = new JComboBox<>(MappingTarget.Type.values()); - typesChooser.setToolTipText("Set type of the mapping"); - typesChooser.addActionListener(e -> { + targetMappingTypesChooser = new JComboBox<>(MappingTarget.Type.values()); + targetMappingTypesChooser.setToolTipText("Set type of the mapping"); + targetMappingTypesChooser.addActionListener(e -> { if (((JComboBox)e.getSource()).hasFocus()) changeTargetType(); }); - typesChooser.setMaximumSize(typesChooser.getPreferredSize()); - typesChooser.setEnabled(false); - buttonPanel.add(typesChooser); + targetMappingTypesChooser.setMaximumSize(targetMappingTypesChooser.getPreferredSize()); + targetMappingTypesChooser.setEnabled(false); + buttonPanel.add(targetMappingTypesChooser); buttonPanel.add(Box.createHorizontalGlue()); @@ -448,6 +446,7 @@ public void uncheckSelected() { if (searchTable.getSelectedRow() != -1) { replaceButton.setEnabled(true); addButtons.forEach(x -> x.setEnabled(true)); + addMappingsTypesChooser.setEnabled(true); } } @@ -523,7 +522,7 @@ private void remove() { private void changeTargetType() { for (int row : targetConceptTable.getSelectedRows()) { MappingTarget mappingTarget = codeMapping.getTargetConcepts().get(row); - mappingTarget.setMappingType((MappingTarget.Type) typesChooser.getSelectedItem()); + mappingTarget.setMappingType((MappingTarget.Type) targetMappingTypesChooser.getSelectedItem()); } targetConceptTableModel.fireTableDataChanged(); diff --git a/src/org/ohdsi/usagi/ui/MappingTablePanel.java b/src/org/ohdsi/usagi/ui/MappingTablePanel.java index 416750e..005d7f4 100644 --- a/src/org/ohdsi/usagi/ui/MappingTablePanel.java +++ b/src/org/ohdsi/usagi/ui/MappingTablePanel.java @@ -32,6 +32,7 @@ import javax.swing.table.TableRowSorter; import org.ohdsi.usagi.CodeMapping; +import org.ohdsi.usagi.CodeMapping.Equivalence; import org.ohdsi.usagi.CodeMapping.MappingStatus; import org.ohdsi.usagi.Concept; @@ -127,14 +128,14 @@ public void restructure() { columnNames = defaultColumnNames; addInfoColCount = 0; if (Global.mapping.size() != 0) { - CodeMapping codeMapping = Global.mapping.get(0); - addInfoColCount = codeMapping.getSourceCode().sourceAdditionalInfo.size(); + List additionalColumns = Global.mapping.getAdditionalColumnNames(); + addInfoColCount = additionalColumns.size(); columnNames = new String[defaultColumnNames.length + addInfoColCount]; for (int i = 0; i < ADD_INFO_START_COL; i++) columnNames[i] = defaultColumnNames[i]; for (int i = 0; i < addInfoColCount; i++) - columnNames[i + ADD_INFO_START_COL] = codeMapping.getSourceCode().sourceAdditionalInfo.get(i).getItem1(); + columnNames[i + ADD_INFO_START_COL] = additionalColumns.get(i); for (int i = ADD_INFO_START_COL; i < defaultColumnNames.length; i++) columnNames[i + addInfoColCount] = defaultColumnNames[i]; @@ -222,22 +223,19 @@ public Class getColumnClass(int col) { if (col >= ADD_INFO_START_COL && col < ADD_INFO_START_COL + addInfoColCount) { return String.class; } else { - col = resolveColumnIndex(col); - switch (col) { + switch (resolveColumnIndex(col)) { case 0: return MappingStatus.class; case 3: - return Integer.class; - case 4: - return Double.class; case 5: - return Integer.class; case 7: - return Integer.class; case 15: - return Integer.class; case 16: return Integer.class; + case 4: + return Double.class; + case 18: + return Equivalence.class; default: return String.class; } diff --git a/src/org/ohdsi/usagi/ui/UsagiMain.java b/src/org/ohdsi/usagi/ui/UsagiMain.java index 4519742..96b76f0 100644 --- a/src/org/ohdsi/usagi/ui/UsagiMain.java +++ b/src/org/ohdsi/usagi/ui/UsagiMain.java @@ -41,7 +41,7 @@ */ public class UsagiMain implements ActionListener { - public static String version = "1.4.2"; + public static String version = "1.4.3"; public static void main(String[] args) { new UsagiMain(args); @@ -64,7 +64,8 @@ public UsagiMain(String[] args) { Global.dbEngine.openForReading(); } - Global.vocabularyVersion = loadVocabularyVersion(Global.folder); + loadAuthor(Global.folder); + loadVocabularyVersion(Global.folder); Global.conceptClassIds = loadVectorFromFile(Global.folder + "/ConceptClassIds.txt"); Global.vocabularyIds = loadVectorFromFile(Global.folder + "/VocabularyIds.txt"); Global.domainIds = loadVectorFromFile(Global.folder + "/DomainIds.txt"); @@ -106,7 +107,7 @@ public UsagiMain(String[] args) { frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { - if (UsagiDialogs.askBeforeExit()) { + if (UsagiDialogs.askBeforeClose()) { Global.dbEngine.shutdown(); System.exit(0); } @@ -142,19 +143,31 @@ public void windowClosing(WindowEvent e) { if (args.length > 1 && args[0].equals("--file")) { OpenAction.open(new File(args[1])); } - - AuthorDialog authorDialog = new AuthorDialog(); - authorDialog.setVisible(true); } - private String loadVocabularyVersion(String folder) { + private void loadVocabularyVersion(String folder) { String versionFileName = folder + "/vocabularyVersion.txt"; - String version = "Unknown"; + Global.vocabularyVersion = "Unknown"; if (new File(versionFileName).exists()) { - for (String line : new ReadTextFile(versionFileName)) - version = line; + for (String line : new ReadTextFile(versionFileName)) { + Global.vocabularyVersion = line; + } + } + } + + private void loadAuthor(String folder) { + String authorFileName = folder + "/authorName.txt"; + if (new File(authorFileName).exists()) { + // Read from file + for (String line : new ReadTextFile(authorFileName)) { + Global.author = line; + } + } else { + // Dialog to ask user to input name + AuthorDialog authorDialog = new AuthorDialog(); + authorDialog.setAuthorFileName(authorFileName); + authorDialog.setVisible(true); } - return version; } @Override diff --git a/src/org/ohdsi/usagi/ui/UsagiStatusBar.java b/src/org/ohdsi/usagi/ui/UsagiStatusBar.java index 6d0ec04..ec1b30e 100644 --- a/src/org/ohdsi/usagi/ui/UsagiStatusBar.java +++ b/src/org/ohdsi/usagi/ui/UsagiStatusBar.java @@ -32,7 +32,7 @@ public class UsagiStatusBar extends JPanel implements DataChangeListener { private static final long serialVersionUID = 4406343348570974587L; private JLabel countLabel; private JLabel percentLabel; - private JLabel reviewPercentLabel; + private JLabel authorLabel; private JLabel searchLabel; private DecimalFormat percentFormatter = new DecimalFormat("##0.0"); @@ -67,6 +67,11 @@ public UsagiStatusBar() { add(Box.createHorizontalGlue()); + authorLabel = new JLabel("Author: " + (Global.author == null ? "" : Global.author)); + add(authorLabel); + + add(Box.createHorizontalStrut(15)); + JLabel versionLabel = new JLabel("Vocabulary version: " + Global.vocabularyVersion); add(versionLabel); Global.mapping.addListener(this); diff --git a/src/org/ohdsi/usagi/ui/UsagiTable.java b/src/org/ohdsi/usagi/ui/UsagiTable.java index 529e693..2800edb 100644 --- a/src/org/ohdsi/usagi/ui/UsagiTable.java +++ b/src/org/ohdsi/usagi/ui/UsagiTable.java @@ -31,6 +31,8 @@ import javax.swing.table.TableModel; import org.ohdsi.usagi.CodeMapping.MappingStatus; +import org.ohdsi.usagi.CodeMapping.Equivalence; + public class UsagiTable extends JTable { @@ -46,6 +48,7 @@ public UsagiTable(TableModel tableModel) { setDefaultRenderer(Double.class, new UsagiCellRenderer()); setDefaultRenderer(Integer.class, new UsagiCellRenderer()); setDefaultRenderer(MappingStatus.class, new UsagiCellRenderer()); + setDefaultRenderer(Equivalence.class, new UsagiCellRenderer()); setFillsViewportHeight(true); addMouseListener(new java.awt.event.MouseAdapter() { diff --git a/src/org/ohdsi/usagi/ui/actions/ApplyPreviousMappingAction.java b/src/org/ohdsi/usagi/ui/actions/ApplyPreviousMappingAction.java index 9c72b83..3acfcec 100644 --- a/src/org/ohdsi/usagi/ui/actions/ApplyPreviousMappingAction.java +++ b/src/org/ohdsi/usagi/ui/actions/ApplyPreviousMappingAction.java @@ -17,8 +17,11 @@ import java.awt.event.ActionEvent; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.List; +import java.util.stream.Collectors; import javax.swing.AbstractAction; import javax.swing.Action; @@ -30,6 +33,7 @@ import org.ohdsi.usagi.CodeMapping; import org.ohdsi.usagi.ui.Global; import org.ohdsi.usagi.ui.Mapping; +import org.ohdsi.utilities.collections.Pair; import static org.ohdsi.usagi.ui.DataChangeEvent.*; @@ -62,23 +66,36 @@ public void actionPerformed(ActionEvent arg0) { Mapping mappingToBeApplied = new Mapping(); mappingToBeApplied.loadFromFile(file.getAbsolutePath()); + // Additional columns + List existingAdditionalColumnNames = Global.mapping.getAdditionalColumnNames(); + // Apply mapping. Add mappings not currently present + ApplyPreviousChangeSummary summary = new ApplyPreviousChangeSummary(); for (CodeMapping codeMappingToBeApplied : mappingToBeApplied) { CodeMapping existingMapping = codeToMapping.get(codeMappingToBeApplied.getSourceCode().sourceCode); if (existingMapping != null) { + summary.compare(existingMapping, codeMappingToBeApplied); existingMapping.getSourceCode().sourceName = codeMappingToBeApplied.getSourceCode().sourceName; existingMapping.setTargetConcepts(codeMappingToBeApplied.getTargetConcepts()); existingMapping.setMappingStatus(codeMappingToBeApplied.getMappingStatus()); + existingMapping.setAssignedReviewer(codeMappingToBeApplied.getAssignedReviewer()); + existingMapping.setEquivalence(codeMappingToBeApplied.getEquivalence()); existingMapping.setComment(codeMappingToBeApplied.getComment()); + existingMapping.setStatusSetBy(codeMappingToBeApplied.getStatusSetBy()); + existingMapping.setStatusSetOn(codeMappingToBeApplied.getStatusSetOn()); mappingsApplied++; } else { + // Add empty additional columns if these existed in existing mapping + codeMappingToBeApplied.getSourceCode().sourceAdditionalInfo = existingAdditionalColumnNames.stream() + .map(x -> new Pair<>(x, "")) + .collect(Collectors.toList()); Global.mapping.add(codeMappingToBeApplied); mappingsAdded++; } } String message = "The applied mapping contained " + mappingToBeApplied.size() + " mappings of which " + mappingsApplied - + " were applied to the current mapping and " + mappingsAdded + " were newly added."; + + " were applied to the current mapping and " + mappingsAdded + " were newly added.\n\n" + summary.createReport(); Global.mappingTablePanel.updateUI(); Global.mappingDetailPanel.updateUI(); Global.mapping.fireDataChanged(APPROVE_EVENT); // To update the footer @@ -87,7 +104,48 @@ public void actionPerformed(ActionEvent arg0) { Global.usagiSearchEngine.createDerivedIndex(Global.mapping.getSourceCodes(), Global.frame); Global.mappingDetailPanel.doSearch(); } - JOptionPane.showMessageDialog(Global.frame, message); + JOptionPane.showMessageDialog(Global.frame, message, "Summary", JOptionPane.INFORMATION_MESSAGE); + } + } + + private static class ApplyPreviousChangeSummary { + private int nChanged = 0; + private int nSourceNameChanged = 0; + private int nTargetConceptsChanged = 0; + private int nMappingStatusChanged = 0; + private int nEquivalenceChanged = 0; + + private void compare(CodeMapping A, CodeMapping B) { + boolean hasChanged = false; + if (!A.getSourceCode().sourceName.equals(B.getSourceCode().sourceName)) { + nSourceNameChanged++; + hasChanged = true; + } + if (!A.getTargetConcepts().equals(B.getTargetConcepts())) { + nTargetConceptsChanged++; // This could be target concept, size OR type + hasChanged = true; + } + if (!A.getMappingStatus().equals(B.getMappingStatus())) { + nMappingStatusChanged++; + hasChanged = true; + } + if (!A.getEquivalence().equals(B.getEquivalence())) { + nEquivalenceChanged++; + hasChanged = true; + } + if (hasChanged) { + nChanged++; + } + } + + private String createReport() { + StringBuilder report = new StringBuilder(); + report.append("Of the applied mappings, " + nChanged + " mappings changed."); + report.append("\n\tSource name: " + nSourceNameChanged); + report.append("\n\tTarget concept: " + nTargetConceptsChanged + "\t(changed target concept, target type AND/OR number of targets)"); + report.append("\n\tMapping status: " + nMappingStatusChanged); + report.append("\n\tMapping equivalence: " + nEquivalenceChanged); + return report.toString(); } } } diff --git a/src/org/ohdsi/usagi/ui/actions/ExitAction.java b/src/org/ohdsi/usagi/ui/actions/ExitAction.java index 123179a..c7c0d1d 100644 --- a/src/org/ohdsi/usagi/ui/actions/ExitAction.java +++ b/src/org/ohdsi/usagi/ui/actions/ExitAction.java @@ -32,7 +32,7 @@ public ExitAction() { @Override public void actionPerformed(ActionEvent arg0) { - if (UsagiDialogs.askBeforeExit()) { + if (UsagiDialogs.askBeforeClose()) { Global.dbEngine.shutdown(); System.exit(0); } diff --git a/src/org/ohdsi/usagi/ui/actions/ImportAction.java b/src/org/ohdsi/usagi/ui/actions/ImportAction.java index d153d9b..2dc35e2 100644 --- a/src/org/ohdsi/usagi/ui/actions/ImportAction.java +++ b/src/org/ohdsi/usagi/ui/actions/ImportAction.java @@ -43,6 +43,11 @@ public ImportAction() { @Override public void actionPerformed(ActionEvent arg0) { + if (!Global.mapping.isEmpty()) { + if (!UsagiDialogs.askBeforeClose()) { + return; + } + } JFileChooser fileChooser = new JFileChooser(Global.folder); FileFilter csvFilter = new FileNameExtensionFilter("CSV files", "csv", "txt"); fileChooser.addChoosableFileFilter(csvFilter); diff --git a/src/org/ohdsi/usagi/ui/actions/OpenAction.java b/src/org/ohdsi/usagi/ui/actions/OpenAction.java index 37e9aba..a5b05ab 100644 --- a/src/org/ohdsi/usagi/ui/actions/OpenAction.java +++ b/src/org/ohdsi/usagi/ui/actions/OpenAction.java @@ -42,6 +42,11 @@ public OpenAction() { @Override public void actionPerformed(ActionEvent arg0) { + if (!Global.mapping.isEmpty()) { + if (!UsagiDialogs.askBeforeClose()) { + return; + } + } JFileChooser fileChooser = new JFileChooser(Global.folder); FileFilter csvFilter = new FileNameExtensionFilter("CSV files", "csv"); fileChooser.setFileFilter(csvFilter); diff --git a/src/org/ohdsi/usagi/ui/actions/UsagiDialogs.java b/src/org/ohdsi/usagi/ui/actions/UsagiDialogs.java index 1de882d..4ef9030 100644 --- a/src/org/ohdsi/usagi/ui/actions/UsagiDialogs.java +++ b/src/org/ohdsi/usagi/ui/actions/UsagiDialogs.java @@ -38,11 +38,11 @@ public static void warningNoApprovedToExport() { ); } - public static boolean askBeforeExit() { + public static boolean askBeforeClose() { String[] objButtons = {"Yes","No"}; int PromptResult = JOptionPane.showOptionDialog( Global.frame, - "Do you want to exit?\nPlease make sure that any work is saved", + "Do you want to close the current file?\nPlease make sure that any work is saved", "Usagi", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,