Skip to content

Commit dc90043

Browse files
committed
Added the feature of IcoRanking
1 parent d74e686 commit dc90043

File tree

12 files changed

+436
-1
lines changed

12 files changed

+436
-1
lines changed

jabgui/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
requires java.sql.rowset;
1212

1313
// region JavaFX
14+
requires javafx.swing;
1415
requires javafx.base;
1516
requires javafx.controls;
1617
requires javafx.fxml;

jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ public static FieldEditorFX getForField(final Field field,
9090
MonthEditorViewModel(field, suggestionProvider, databaseContext.getMode(), fieldCheckers, undoManager));
9191
} else if (fieldProperties.contains(FieldProperty.LANGUAGE)) {
9292
return new OptionEditor<>(new LanguageEditorViewModel(field, suggestionProvider, databaseContext.getMode(), fieldCheckers, undoManager));
93+
} else if (field == StandardField.ICORANKING) {
94+
return new ICoreRankingEditor(field);
9395
} else if (field == StandardField.GENDER) {
9496
return new OptionEditor<>(new GenderEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager));
9597
} else if (fieldProperties.contains(FieldProperty.EDITOR_TYPE)) {
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package org.jabref.gui.fieldeditors;
2+
3+
import java.util.Optional;
4+
5+
import javafx.beans.property.StringProperty;
6+
import javafx.scene.Node;
7+
import javafx.scene.Parent;
8+
import javafx.scene.control.Button;
9+
import javafx.scene.control.TextField;
10+
import javafx.scene.control.TextInputControl;
11+
import javafx.scene.layout.HBox;
12+
import org.jabref.gui.keyboard.KeyBindingRepository;
13+
import org.jabref.gui.undo.RedoAction;
14+
import org.jabref.gui.undo.UndoAction;
15+
import org.jabref.logic.icore.ConferenceRankingEntry;
16+
import org.jabref.logic.icore.ICoreRankingRepository;
17+
import org.jabref.logic.util.ConferenceUtil;
18+
import org.jabref.model.entry.BibEntry;
19+
import org.jabref.model.entry.field.Field;
20+
import org.jabref.model.entry.field.StandardField;
21+
22+
public class ICoreRankingEditor extends HBox implements FieldEditorFX {
23+
24+
private final Field field;
25+
private final TextField textField;
26+
private BibEntry currentEntry;
27+
private final ICoreRankingRepository repo;
28+
29+
public ICoreRankingEditor(Field field) {
30+
this.field = field;
31+
this.textField = new TextField();
32+
this.repo = new ICoreRankingRepository(); // Load once
33+
34+
this.textField.setPromptText("Enter or lookup ICORE rank");
35+
36+
Button lookupButton = new Button("Lookup Rank");
37+
lookupButton.setOnAction(event -> lookupRank());
38+
39+
this.getChildren().addAll(textField, lookupButton);
40+
this.setSpacing(10);
41+
}
42+
43+
// Deprecated method it only shows rank by puttin the acronym in the text field
44+
// private void lookupRank() {
45+
// if (currentEntry == null) {
46+
//// System.out.println("No entry is currently bound.");
47+
// return;
48+
// }
49+
//
50+
// // 1. Try icoreranking field first
51+
// Optional<String> icoreField = currentEntry.getField(StandardField.ICORANKING);
52+
// if (icoreField.isPresent()) {
53+
// String raw = icoreField.get();
54+
//// System.out.println("Using ICORE field: " + raw);
55+
//
56+
// Optional<String> acronymRank = repo.getRankingFor(raw.toLowerCase());
57+
// String result = acronymRank.orElse("Not ranked");
58+
// textField.setText(result);
59+
//// System.out.println("Lookup result from ICORE: " + result);
60+
// return;
61+
// }
62+
//
63+
// // 2. Then fallback to booktitle
64+
// Optional<String> bookTitle = currentEntry.getFieldOrAlias(StandardField.BOOKTITLE);
65+
// // 3. Then fallback to journal
66+
// if (bookTitle.isEmpty()) {
67+
// bookTitle = currentEntry.getField(StandardField.JOURNAL);
68+
// }
69+
//
70+
// if (bookTitle.isEmpty()) {
71+
//// System.out.println("No usable field found.");
72+
// textField.setText("Not ranked");
73+
// return;
74+
// }
75+
//
76+
// String rawInput = bookTitle.get();
77+
//// System.out.println("Detected Title: '" + rawInput + "'");
78+
//
79+
// Optional<String> acronym = ConferenceUtil.extractAcronym(rawInput);
80+
//// acronym.ifPresent(acr -> System.out.println("Extracted acronym: " + acr));
81+
//
82+
// Optional<String> rank = acronym.flatMap(repo::getRankingFor)
83+
// .or(() -> repo.getRankingFor(rawInput));
84+
//
85+
// String result = rank.orElse("Not ranked");
86+
// textField.setText(result);
87+
//// System.out.println("Lookup result: " + result);
88+
// }
89+
90+
// By providing the acronym fetches the data from csv
91+
private void lookupRank() {
92+
if (currentEntry == null) {
93+
return;
94+
}
95+
96+
Optional<String> icoreField = currentEntry.getField(StandardField.ICORANKING);
97+
Optional<String> bookTitle = currentEntry.getFieldOrAlias(StandardField.BOOKTITLE);
98+
if (bookTitle.isEmpty()) {
99+
bookTitle = currentEntry.getField(StandardField.JOURNAL);
100+
}
101+
102+
Optional<String> finalBookTitle = bookTitle;
103+
String rawInput = icoreField.orElseGet(() -> finalBookTitle.orElse("Unknown"));
104+
105+
Optional<String> acronym = ConferenceUtil.extractAcronym(rawInput); // Extracting the acronym from our input field
106+
Optional<ConferenceRankingEntry> result = acronym.flatMap(repo::getFullEntry)
107+
.or(() -> repo.getFullEntry(rawInput)); // Finding if any matching entry present in csv file
108+
// If present then print the info
109+
110+
if (result.isPresent()) {
111+
ConferenceRankingEntry entry = result.get();
112+
113+
// Show in new dialog
114+
javafx.scene.control.Alert alert = new javafx.scene.control.Alert(javafx.scene.control.Alert.AlertType.INFORMATION);
115+
alert.setTitle("ICORE Ranking Info");
116+
alert.setHeaderText("Found Conference Details");
117+
alert.setContentText(entry.toString());
118+
alert.setResizable(true);
119+
alert.getDialogPane().setPrefSize(600, 400);
120+
alert.showAndWait();
121+
122+
textField.setText(entry.rank); // still show rank in the field
123+
} else {
124+
textField.setText("Not ranked");
125+
}
126+
}
127+
128+
@Override
129+
public void bindToEntry(BibEntry entry) {
130+
this.currentEntry = entry;
131+
132+
// System.out.println("ENTRY booktitle = " + entry.getField(StandardField.BOOKTITLE).orElse("none"));
133+
// System.out.println("ENTRY journal = " + entry.getField(StandardField.JOURNAL).orElse("none"));
134+
// System.out.println("ENTRY title = " + entry.getField(StandardField.TITLE).orElse("none"));
135+
136+
entry.getField(field).ifPresent(textField::setText);
137+
138+
textField.textProperty().addListener((obs, oldVal, newVal) -> {
139+
entry.setField(field, newVal);
140+
});
141+
}
142+
143+
@Override
144+
public void establishBinding(TextInputControl textInputControl, StringProperty viewModelTextProperty,
145+
KeyBindingRepository keyBindingRepository, UndoAction undoAction, RedoAction redoAction) {
146+
FieldEditorFX.super.establishBinding(textInputControl, viewModelTextProperty, keyBindingRepository, undoAction, redoAction);
147+
}
148+
149+
@Override
150+
public Parent getNode() {
151+
return this;
152+
}
153+
154+
@Override
155+
public void focus() {
156+
FieldEditorFX.super.focus();
157+
}
158+
159+
@Override
160+
public double getWeight() {
161+
return FieldEditorFX.super.getWeight();
162+
}
163+
164+
@Override
165+
public void requestFocus() {
166+
textField.requestFocus();
167+
}
168+
169+
@Override
170+
public Node getStyleableNode() {
171+
return super.getStyleableNode();
172+
}
173+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.jabref.gui.fieldeditors;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
7+
public class FieldEditorsTest {
8+
9+
@Test
10+
void testEditorCreationDoesNotCrash() {
11+
assertNotNull(FieldEditors.class);
12+
}
13+
}

jablib/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
open module org.jabref.jablib {
2+
exports org.jabref.logic.icore;
23
exports org.jabref.model;
34
exports org.jabref.logic;
45

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.jabref.logic.icore;
2+
3+
public class ConferenceRankingEntry {
4+
public final String title;
5+
public final String acronym;
6+
public final String source;
7+
public final String rank;
8+
public final String note;
9+
public final String dblp;
10+
public final String primaryFor;
11+
public final String comments;
12+
public final String averageRating;
13+
14+
public ConferenceRankingEntry(String title, String acronym, String source, String rank,
15+
String note, String dblp, String primaryFor,
16+
String comments, String averageRating) {
17+
this.title = title;
18+
this.acronym = acronym;
19+
this.source = source;
20+
this.rank = rank;
21+
this.note = note;
22+
this.dblp = dblp;
23+
this.primaryFor = primaryFor;
24+
this.comments = comments;
25+
this.averageRating = averageRating;
26+
}
27+
// Printing the data of of the csv we got based on the acronym lookup in a orderky format in a new window
28+
29+
@Override
30+
public String toString() {
31+
return String.format("""
32+
Title: %s
33+
Acronym: %s
34+
Source: %s
35+
Rank: %s
36+
Note: %s
37+
DBLP: %s
38+
Primary FoR: %s
39+
Comments: %s
40+
Average Rating: %s
41+
""", title, acronym, source, rank, note, dblp, primaryFor, comments, averageRating);
42+
}
43+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.jabref.logic.icore;
2+
3+
import java.io.BufferedReader;
4+
import java.io.InputStream;
5+
import java.io.InputStreamReader;
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
11+
import org.jabref.logic.util.strings.StringSimilarity;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
public class ICoreRankingRepository {
16+
17+
final Map<String, String> acronymToRank = new HashMap<>();
18+
private final Map<String, String> nameToRank = new HashMap<>();
19+
private final Map<String, ConferenceRankingEntry> acronymMap = new HashMap<>();
20+
private final Map<String, ConferenceRankingEntry> nameMap = new HashMap<>();
21+
private final StringSimilarity similarity = new StringSimilarity();
22+
private static final Logger LOGGER = LoggerFactory.getLogger(ICoreRankingRepository.class);
23+
24+
public ICoreRankingRepository() {
25+
InputStream inputStream = getClass().getResourceAsStream("/ICORE.csv");
26+
if (inputStream == null) {
27+
LOGGER.error("ICORE.csv not found in resources.");
28+
return;
29+
}
30+
31+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
32+
reader.lines().skip(1).forEach(line -> {
33+
String[] parts = line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
34+
if (parts.length >= 9) {
35+
String name = parts[0].trim().toLowerCase();
36+
String acronym = parts[1].trim().toLowerCase();
37+
String rank = parts[3].trim();
38+
acronymToRank.put(acronym, rank);
39+
nameToRank.put(name, rank);
40+
}
41+
ConferenceRankingEntry entry = new ConferenceRankingEntry(
42+
parts[0].trim(), // title
43+
parts[1].trim(), // acronym
44+
parts[2].trim(), // source
45+
parts[3].trim(), // rank
46+
parts[4].trim(), // note
47+
parts[5].trim(), // dblp
48+
parts[6].trim(), // primaryFor
49+
parts[7].trim(), // comments
50+
parts[8].trim() // averageRating
51+
);
52+
acronymMap.put(entry.acronym.toLowerCase(), entry);
53+
nameMap.put(entry.title.toLowerCase(), entry);
54+
});
55+
56+
// System.out.println("Loaded entries:");
57+
// acronymToRank.forEach((key, val) -> System.out.println("Acronym: " + key + " -> " + val));
58+
// nameToRank.forEach((key, val) -> System.out.println("Name: " + key + " -> " + val));
59+
} catch (Exception e) {
60+
LOGGER.error("Failed to load ICORE ranking data", e);
61+
}
62+
}
63+
64+
public Optional<String> getRankingFor(String acronymOrName) {
65+
String key = acronymOrName.trim().toLowerCase();
66+
67+
// 1. Try exact acronym match
68+
if (acronymToRank.containsKey(key)) {
69+
return Optional.of(acronymToRank.get(key));
70+
}
71+
72+
// 2. Try exact name match
73+
if (nameToRank.containsKey(key)) {
74+
return Optional.of(nameToRank.get(key));
75+
}
76+
77+
// 3. Skip fuzzy matching for short strings (e.g., "icse")
78+
if (key.length() < 6) {
79+
LOGGER.info("Skipped fuzzy fallback for short string: " + key);
80+
return Optional.empty();
81+
}
82+
83+
// 4. Fallback: fuzzy match with strict threshold
84+
LOGGER.info("Fuzzy match fallback triggered for: " + key);
85+
return nameToRank.entrySet().stream()
86+
.filter(e -> similarity.editDistanceIgnoreCase(e.getKey(), key) < 0.01)
87+
.peek(e -> LOGGER.info("Fuzzy match candidate: " + e.getKey()))
88+
.map(Map.Entry::getValue)
89+
.findFirst();
90+
}
91+
92+
public Optional<ConferenceRankingEntry> getFullEntry(String acronymOrName) {
93+
String key = acronymOrName.trim().toLowerCase();
94+
if (acronymMap.containsKey(key)) {
95+
return Optional.of(acronymMap.get(key));
96+
}
97+
if (nameMap.containsKey(key)) {
98+
return Optional.of(nameMap.get(key));
99+
}
100+
return Optional.empty();
101+
}
102+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.jabref.logic.util;
2+
3+
import java.util.Optional;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
6+
7+
public class ConferenceUtil {
8+
public static Optional<String> extractAcronym(String title) {
9+
Matcher matcher = Pattern.compile("\\((.*?)\\)").matcher(title);
10+
if (matcher.find()) {
11+
return Optional.of(matcher.group(1));
12+
}
13+
return Optional.empty();
14+
15+
}
16+
}

jablib/src/main/java/org/jabref/model/entry/field/StandardField.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ public enum StandardField implements Field {
138138
OWNER("owner"),
139139
TIMESTAMP("timestamp", FieldProperty.DATE),
140140
CREATIONDATE("creationdate", FieldProperty.DATE),
141-
MODIFICATIONDATE("modificationdate", FieldProperty.DATE);
141+
142+
MODIFICATIONDATE("modificationdate", FieldProperty.DATE),
143+
ICORANKING("icoranking");
142144

143145
public static final Set<Field> AUTOMATIC_FIELDS = Set.of(OWNER, TIMESTAMP, CREATIONDATE, MODIFICATIONDATE);
144146

jablib/src/main/resources/ICORE.csv

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Title,Acronym,Source,Rank,Note,DBLP,Primary FoR,Comments,Average Rating
2+
ACIS Conference on Software Engineering Research,SERA,CORE2023,C,none,view,4612,1,4.0
3+
AAAI Conference on Human Computation and Crowdsourcing,HCOMP,CORE2023,B,none,view,4608,0,N/A
4+
TUGboat Journal,TUGBOAT,CORE2023,C,none,view,9999,1,3.5
5+
International Conference on Software Engineering,ICSE,CORE2023,A*,none,view,1001,2,4.8
6+
European Conference on Object-Oriented Programming,ECOOP,CORE2023,A,none,view,1002,2,4.2

0 commit comments

Comments
 (0)