diff --git a/pom.xml b/pom.xml index 76a71366..b98c6720 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,7 @@ false Cytoscape Snapshots - http://code.cytoscape.org/nexus/content/repositories/snapshots/ + https://code.cytoscape.org/nexus/content/repositories/snapshots/ cytoscape_releases @@ -90,7 +90,7 @@ true Cytoscape Releases - http://code.cytoscape.org/nexus/content/repositories/releases/ + https://code.cytoscape.org/nexus/content/repositories/releases/ apache-repo-releases @@ -106,6 +106,7 @@ true + @@ -249,6 +250,39 @@ ${osgi.api.version} provided + + org.projectlombok + lombok + 1.18.34 + provided + + + + org.junit.jupiter + junit-jupiter-api + 5.8.2 + test + + + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + test + + + + org.junit.platform + junit-platform-runner + 1.8.2 + test + + + org.testng + testng + RELEASE + test + diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/CyActivator.java b/src/main/java/uk/ac/ebi/intact/app/internal/CyActivator.java index 31294209..5e40a014 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/CyActivator.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/CyActivator.java @@ -18,6 +18,7 @@ import uk.ac.ebi.intact.app.internal.tasks.query.NoGUIQueryTask; import uk.ac.ebi.intact.app.internal.tasks.query.factories.ExactQueryTaskFactory; import uk.ac.ebi.intact.app.internal.tasks.query.factories.FuzzySearchTaskFactory; +import uk.ac.ebi.intact.app.internal.tasks.query.factories.AdvancedSearchTaskFactory; import uk.ac.ebi.intact.app.internal.tasks.settings.SettingsTask; import uk.ac.ebi.intact.app.internal.tasks.version.factories.VersionTaskFactory; import uk.ac.ebi.intact.app.internal.tasks.view.extract.ExtractNetworkViewTaskFactory; @@ -222,6 +223,11 @@ public TaskIterator createTaskIterator() { Properties propsSearch = new Properties(); registerService(bc, intactSearch, NetworkSearchTaskFactory.class, propsSearch); } + { + AdvancedSearchTaskFactory advancedSearchTaskFactory = new AdvancedSearchTaskFactory(manager); + Properties propsSearch = new Properties(); + registerService(bc, advancedSearchTaskFactory, NetworkSearchTaskFactory.class, propsSearch); + } manager.utils.info("Intact App initialized"); } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/io/HttpUtils.java b/src/main/java/uk/ac/ebi/intact/app/internal/io/HttpUtils.java index 39e2822f..5d72892a 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/io/HttpUtils.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/io/HttpUtils.java @@ -1,11 +1,17 @@ package uk.ac.ebi.intact.app.internal.io; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.node.NullNode; import org.cytoscape.io.util.StreamUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import uk.ac.ebi.intact.app.internal.model.managers.Manager; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.Field; import java.io.*; import java.net.*; @@ -19,11 +25,53 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import static uk.ac.ebi.intact.app.internal.model.managers.Manager.INTACT_GRAPH_WS; + public class HttpUtils { + private static final HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .build(); + private static final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .header("Content-Type", "application/json") + .header("Accept", "application/json"); + + private static final ObjectMapper mapper = JsonMapper.builder() + .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true) + .build(); + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtils.class); + + public static JsonNode getJsonNetworkWithRequestBody(String query, int page) throws IOException, InterruptedException { + String baseUrl = INTACT_GRAPH_WS + "network/fromPagedInteractions"; + + Map params = Map.of( + "query", query, + "advancedSearch", isAdvancedSearch(query), + "page", page + ); + + HttpRequest request = requestBuilder + .uri(URI.create(baseUrl)) + .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(params))) + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); + + return mapper.readTree(response.body()); + } + + public static boolean isAdvancedSearch(String query) { + for (Field field : Field.values()) { + if (query.contains(field.getMiqlQuery())) { + return true; + } + } + return false; + } + public static JsonNode getJSON(String url, Map queryMap, Manager manager) { URL trueURL; try { @@ -48,7 +96,7 @@ public static JsonNode getJSON(String url, Map queryMap, Manager entityStream.close(); } catch (Exception e) { - e.printStackTrace(); + logError(e.getMessage()); } return jsonObject; } @@ -65,11 +113,11 @@ public static JsonNode postJSON(String url, Map data, Manager ma HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); if (response == null) { - manager.utils.error("No response from " + url + " with post data = " + data.toString()); + manager.utils.error("No response from " + url + " with post data = " + data); return null; } if (response.statusCode() != 200) { - manager.utils.error("Error " + response.statusCode() + " from " + url + " with post data = " + data.toString()); + manager.utils.error("Error " + response.statusCode() + " from " + url + " with post data = " + data); } return new ObjectMapper().readTree(response.body()); @@ -80,32 +128,31 @@ public static JsonNode postJSON(String url, Map data, Manager ma } } - public static JsonNode postJSON(String url, Map data, Manager manager, Supplier isCancelled) { + public static JsonNode postJSON(String url, Map body, Manager manager, Supplier isCancelled) { try { HttpRequest request = HttpRequest.newBuilder() - .POST(buildFormDataFromMap(data)) .uri(URI.create(url)) + .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body))) .setHeader("User-Agent", "Java 11 HttpClient Bot") // add request header - .header("Content-Type", "application/x-www-form-urlencoded") .header("accept", "application/json") .build(); Instant begin = Instant.now(); - CompletableFuture body = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + CompletableFuture responseBody = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(response -> { if (response.statusCode() != 200) { - manager.utils.error("Error " + response.statusCode() + " from " + url + " with post data = " + data.toString()); + manager.utils.error("Error " + response.statusCode() + " from " + url + " with post data = " + body); } return response; }).thenApply(HttpResponse::body); - while (!body.isDone()) { + while (!responseBody.isDone()) { if (isCancelled.get()) { - body.cancel(true); + responseBody.cancel(true); return null; } } System.out.println("Response received in " + Duration.between(begin, Instant.now()).toSeconds() + "s from " + url); - return new ObjectMapper().readTree(body.get()); + return new ObjectMapper().readTree(responseBody.get()); } catch (Exception e) { // e.printStackTrace(); @@ -114,7 +161,6 @@ public static JsonNode postJSON(String url, Map data, Manager ma } } - public static String getStringArguments(Map args) { StringBuilder s = null; try { @@ -125,26 +171,24 @@ public static String getStringArguments(Map args) { s.append("&").append(key).append("=").append(URLEncoder.encode(args.get(key), StandardCharsets.UTF_8.displayName())); } } catch (Exception e) { - e.printStackTrace(); + logError(e.getMessage()); } return s != null ? s.toString() : ""; } - public static String truncate(String str) { if (str.length() > 1000) return str.substring(0, 1000) + "..."; return str; } - private static URLConnection executeWithRedirect(Manager manager, String url, Map queryMap) throws Exception { // Get the connection from Cytoscape HttpURLConnection connection = (HttpURLConnection) manager.utils.getService(StreamUtil.class).getURLConnection(new URL(url)); // We want to write on the stream connection.setDoOutput(true); - // We want to deal with redirection ourself + // We want to deal with redirection ourselves connection.setInstanceFollowRedirects(false); // We write the POST arguments @@ -178,7 +222,7 @@ private static String readStream(InputStream stream) throws Exception { builder.append(line); // + "\r\n"(no need, json has no line breaks!) } } - System.out.println("JSON error response: " + builder.toString()); + System.out.println("JSON error response: " + builder); return builder.toString(); } @@ -194,7 +238,7 @@ public static String getRequestResultForUrl(String requestURL) { } jsonText = builder.toString(); } catch (IOException e) { - e.printStackTrace(); + logError(e.getMessage()); } return jsonText; @@ -206,7 +250,7 @@ public static JsonNode getJsonForUrl(String jsonQuery) { try { return new ObjectMapper().readTree(jsonText); } catch (JsonProcessingException e) { - e.printStackTrace(); + logError(e.getMessage()); } } return null; @@ -235,4 +279,7 @@ private static HttpRequest.BodyPublisher buildFormDataFromMap(Map sourceFeatureAcs; public final List targetFeatureAcs; + public final boolean isNegative; public static Edge createEdge(Network network, CyEdge edge) { @@ -57,6 +58,8 @@ public static Edge createEdge(Network network, CyEdge edge) { if (targetFeatureAcs != null) { targetFeatureAcs.removeIf(String::isBlank); } + + isNegative = IS_NEGATIVE_INTERACTION.getValue(edgeRow); } public abstract Map> getFeatures() ; diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/EvidenceEdge.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/EvidenceEdge.java index b1ebfbd6..0626e1f7 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/EvidenceEdge.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/EvidenceEdge.java @@ -13,10 +13,7 @@ import uk.ac.ebi.intact.app.internal.model.core.network.Network; import uk.ac.ebi.intact.app.internal.model.tables.fields.enums.FeatureFields; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static uk.ac.ebi.intact.app.internal.model.managers.Manager.INTACT_GRAPH_WS; import static uk.ac.ebi.intact.app.internal.model.tables.fields.enums.EdgeFields.*; @@ -36,6 +33,7 @@ public class EvidenceEdge extends Edge { public final CVTerm targetBiologicalRole; public final CVTerm targetExperimentalRole; private JsonNode detailsJSON; + public final Boolean isNegative; EvidenceEdge(Network network, CyEdge edge) { super(network, edge); @@ -55,6 +53,7 @@ public class EvidenceEdge extends Edge { targetExperimentalRole = new CVTerm(edgeRow, EXPERIMENTAL_ROLE.TARGET); pubMedId = PUBMED_ID.getValue(edgeRow); + isNegative = IS_NEGATIVE_INTERACTION.getValue(edgeRow); } @Override @@ -128,6 +127,7 @@ public EvidenceEdge cloneInto(Network network) { AFFECTED_BY_MUTATION.setValue(row, isAffectedByMutation()); PUBMED_ID.setValue(row, pubMedId); + return (EvidenceEdge) Edge.createEdge(network, edge); } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/SummaryEdge.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/SummaryEdge.java index bed74a7f..ae51d8d2 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/SummaryEdge.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/core/elements/edges/SummaryEdge.java @@ -28,6 +28,10 @@ public Map> getFeatures() { return features; } + public Boolean isNegative() { + return getSummarizedEdges().stream().anyMatch(edge -> edge.isNegative); + } + protected void buildFeatures(Map> features, List featureAcs, Node participant, Set edgesSUID) { ArrayList participantFeatures = new ArrayList<>(); features.put(participant, participantFeatures); @@ -64,6 +68,7 @@ public void updateSummary() { if (summarizedCyEdge == null) return false; return network.getEvidenceEdge(summarizedCyEdge) != null; }).count(); + EdgeFields.IS_NEGATIVE_INTERACTION.setValue(edgeRow, isNegative()); EdgeFields.SUMMARY_NB_EDGES.setValue(edgeRow, nbSummarizedEdges); } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/core/view/NetworkView.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/core/view/NetworkView.java index 5fb8f16c..bcdfaeff 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/model/core/view/NetworkView.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/core/view/NetworkView.java @@ -74,6 +74,7 @@ private void setupFilters(boolean loadData) { filters.add(new EdgeExpansionTypeFilter(this)); filters.add(new EdgeTypeFilter(this)); filters.add(new EdgeMutationFilter(this)); + filters.add(new EdgePositiveFilter(this)); filters.add(new OrphanNodeFilter(this)); // Must be after edge filters filters.add(new OrphanEdgeFilter(this)); diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/filters/edge/EdgePositiveFilter.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/filters/edge/EdgePositiveFilter.java new file mode 100644 index 00000000..095b3fed --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/filters/edge/EdgePositiveFilter.java @@ -0,0 +1,54 @@ +package uk.ac.ebi.intact.app.internal.model.filters.edge; + +import lombok.Getter; +import lombok.Setter; +import uk.ac.ebi.intact.app.internal.model.core.elements.edges.Edge; +import uk.ac.ebi.intact.app.internal.model.core.view.NetworkView; +import uk.ac.ebi.intact.app.internal.model.filters.BooleanFilter; + +public class EdgePositiveFilter extends BooleanFilter { + + @Setter + @Getter + private boolean isNegativeHidden = areTherePositiveInteractions() && areThereNegativeInteractions(); + @Setter + @Getter + private boolean isPositiveHidden = !areTherePositiveInteractions(); + + public EdgePositiveFilter(NetworkView networkView) { + super(networkView, + Edge.class, + "Positive Interaction", + "Positive and/or negative interactions only", + "Positive and/or negative interactions only"); + } + + @Override + public boolean isToHide(Edge element) { + if (element.isNegative){ + return isNegativeHidden; + } else { + return isPositiveHidden; + } + } + + @Override + public void reset() { + boolean anyPositive = areTherePositiveInteractions(); + boolean anyNegative = areThereNegativeInteractions(); + setNegativeHidden(anyPositive && anyNegative); + setPositiveHidden(!anyPositive); + status = anyPositive || anyNegative; + fireFilterUpdated(); + } + + public boolean areThereNegativeInteractions() { + NetworkView networkView = getNetworkView(); + return networkView.getNetwork().getEvidenceEdges().stream().anyMatch(edge -> edge.isNegative); + } + + public boolean areTherePositiveInteractions() { + NetworkView networkView = getNetworkView(); + return networkView.getNetwork().getEvidenceEdges().stream().anyMatch(edge -> !edge.isNegative); + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/managers/Manager.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/managers/Manager.java index eaf51be7..d981a4ce 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/model/managers/Manager.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/managers/Manager.java @@ -8,8 +8,8 @@ import java.util.Properties; public class Manager { - private static final String INTACT_WEBSERVICES = "https://www.ebi.ac.uk/intact/ws/"; -// private static final String INTACT_WEBSERVICES = "http://127.0.0.1:8081/intact/ws/"; +// private static final String INTACT_WEBSERVICES = "https://www.ebi.ac.uk/intact/ws/"; + private static final String INTACT_WEBSERVICES = "https://wwwdev.ebi.ac.uk/intact/ws/"; public static final String INTACT_GRAPH_WS = INTACT_WEBSERVICES + "graph/"; public static final String INTACT_INTERACTOR_WS = INTACT_WEBSERVICES + "interactor/"; diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/Style.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/Style.java index 88313e64..2dd57910 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/Style.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/Style.java @@ -1,6 +1,8 @@ package uk.ac.ebi.intact.app.internal.model.styles; +import lombok.Getter; import org.cytoscape.event.CyEventHelper; +import org.cytoscape.model.CyEdge; import org.cytoscape.model.CyNode; import org.cytoscape.view.model.CyNetworkView; import org.cytoscape.view.model.VisualLexicon; @@ -14,18 +16,22 @@ import org.cytoscape.view.vizmap.VisualStyleFactory; import org.cytoscape.view.vizmap.mappings.DiscreteMapping; import org.cytoscape.view.vizmap.mappings.PassthroughMapping; + import uk.ac.ebi.intact.app.internal.model.core.view.NetworkView; import uk.ac.ebi.intact.app.internal.model.managers.Manager; import uk.ac.ebi.intact.app.internal.model.styles.mapper.StyleMapper; +import uk.ac.ebi.intact.app.internal.model.tables.fields.enums.EdgeFields; import uk.ac.ebi.intact.app.internal.model.tables.fields.enums.NodeFields; import uk.ac.ebi.intact.app.internal.utils.TimeUtils; import java.awt.*; +import java.awt.geom.AffineTransform; import java.util.Map; public abstract class Style { public static final Color defaultNodeColor = new Color(157, 177, 128); + @Getter protected VisualStyle style; protected Manager manager; protected CyEventHelper eventHelper; @@ -101,11 +107,13 @@ private void createStyle() { setEdgeSourceShape(); setEdgeTargetShape(); setEdgeArrowColor(); + setEdgeLabel(); } @SuppressWarnings("unchecked") protected void loadStyle() { setNodeLabel(); + setEdgeLabel(); nodeTypeToShape = (DiscreteMapping) style.getVisualMappingFunction(BasicVisualLexicon.NODE_SHAPE); taxIdToNodeColor = (DiscreteMapping) style.getVisualMappingFunction(BasicVisualLexicon.NODE_FILL_COLOR); } @@ -146,7 +154,6 @@ public void setNodePaintStyle() { protected void setNodeBorderPaintStyle() { } - protected void setNodeBorderWidth() { style.setDefaultValue(BasicVisualLexicon.NODE_BORDER_WIDTH, 0d); } @@ -249,13 +256,27 @@ public String getStyleName(){ public abstract NetworkView.Type getStyleViewType(); - public VisualStyle getStyle() { - return style; - } - @Override public String toString() { return getStyleName(); } -} + protected void setEdgeLabel() { + DiscreteMapping edgeLabelMapping = + (DiscreteMapping) discreteFactory.createVisualMappingFunction( + EdgeFields.IS_NEGATIVE_INTERACTION.toString(), + Boolean.class, + BasicVisualLexicon.EDGE_LABEL + ); + + edgeLabelMapping.putMapValue(false, ""); + edgeLabelMapping.putMapValue(true, "X"); + + VisualLexicon lex = manager.utils.getService(RenderingEngineManager.class).getDefaultVisualLexicon(); + VisualProperty labelPosition = lex.lookup(CyEdge.class, "EDGE_LABEL_AUTOROTATE"); + style.addVisualMappingFunction(edgeLabelMapping); + style.setDefaultValue(BasicVisualLexicon.EDGE_LABEL_COLOR, Color.RED); + style.setDefaultValue(BasicVisualLexicon.EDGE_LABEL_FONT_SIZE, 30); + style.setDefaultValue(labelPosition, true); + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/mapper/StyleMapper.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/mapper/StyleMapper.java index 71f79e4c..251067c7 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/mapper/StyleMapper.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/styles/mapper/StyleMapper.java @@ -28,6 +28,8 @@ import static uk.ac.ebi.intact.app.internal.model.styles.mapper.definitions.Taxons.ARTIFICIAL; public class StyleMapper { + public static final String BASE_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=taxonomy&id="; + public static final int MAX_URL_LENGTH = 2_000; private static boolean taxIdsReady = false; private static boolean taxIdsWorking = false; private static boolean nodeTypesReady = false; @@ -173,18 +175,42 @@ public synchronized static Map harvestKingdomsOf(Set taxI return addedTaxIds; try { - String resultText = HttpUtils.getRequestResultForUrl("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=taxonomy&id=" + concatenateTaxIds(taxIdsToCheckAndAdd)); - JSONObject jObject = XML.toJSONObject(resultText); - JsonNode taxons = new ObjectMapper().readTree(jObject.toString()).get("TaxaSet").get("Taxon"); - if (taxons.isArray()) { - for (JsonNode taxon : taxons) { - addTaxon(addedTaxIds, taxon, setColor); + List queries = new ArrayList<>(); + StringBuilder url = new StringBuilder(BASE_URL); + boolean first = true; + + for (String taxId : taxIdsToCheckAndAdd) { + // Ignore custom taxids specific to IntAxt + if (taxId.startsWith("-")) continue; + // Pre-calculate url before modification in append exceeds limit + String completeUrl = url.toString(); + + if (!first) url.append(","); + first = false; + + url.append(taxId); + + if (url.length() > MAX_URL_LENGTH) { + queries.add(completeUrl); + url = new StringBuilder(BASE_URL).append(taxId); } - } else { - addTaxon(addedTaxIds, taxons, setColor); + } + queries.add(url.toString()); + for (String query : queries) { + String resultText = HttpUtils.getRequestResultForUrl(query); + JSONObject jObject = XML.toJSONObject(resultText); + JsonNode taxons = new ObjectMapper().readTree(jObject.toString()).get("TaxaSet").get("Taxon"); + if (taxons.isArray()) { + for (JsonNode taxon : taxons) { + addTaxon(addedTaxIds, taxon, setColor); + } + } else { + addTaxon(addedTaxIds, taxons, setColor); + } + } } catch (IOException e) { e.printStackTrace(); } @@ -331,10 +357,6 @@ private static void setChildrenValues(Map mapToFill, String paren } } - private static String concatenateTaxIds(Set taxIds) { - return taxIds.stream().filter(taxId -> !taxId.startsWith("-")).collect(toList()).toString().replaceAll("[\\[\\]]", "").replaceAll(" ", "%20"); - } - public static boolean speciesNotReady() { return !taxIdsReady; } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/model/tables/fields/enums/EdgeFields.java b/src/main/java/uk/ac/ebi/intact/app/internal/model/tables/fields/enums/EdgeFields.java index e977c7b9..5f61c71a 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/model/tables/fields/enums/EdgeFields.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/model/tables/fields/enums/EdgeFields.java @@ -39,4 +39,6 @@ public class EdgeFields { public static final ParticipantListField FEATURES = new ParticipantListField<>(fields, initializers, "features", String.class); public static final Field ID = new Field<>(fields, initializers, Field.Namespace.INTACT, "ID", "id", Long.class); + + public static final Field IS_NEGATIVE_INTERACTION = new Field<>(fields, initializers, Field.Namespace.SUMMARY, "negative interaction", "is_negative", Boolean.class, Boolean.FALSE); } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/AdvancedSearchTask.java b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/AdvancedSearchTask.java new file mode 100644 index 00000000..d535cb8a --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/AdvancedSearchTask.java @@ -0,0 +1,195 @@ +package uk.ac.ebi.intact.app.internal.tasks.query; + +import com.fasterxml.jackson.databind.JsonNode; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.cytoscape.model.CyNetwork; +import org.cytoscape.model.CyNetworkManager; +import org.cytoscape.model.CyTable; +import org.cytoscape.model.CyTableManager; +import org.cytoscape.view.model.CyNetworkView; +import org.cytoscape.work.*; + +import uk.ac.ebi.intact.app.internal.io.HttpUtils; +import uk.ac.ebi.intact.app.internal.model.core.network.Network; +import uk.ac.ebi.intact.app.internal.model.managers.Manager; +import uk.ac.ebi.intact.app.internal.utils.ModelUtils; +import uk.ac.ebi.intact.app.internal.utils.ViewUtils; + +import java.io.IOException; +import java.time.Instant; +import java.util.*; + +import static uk.ac.ebi.intact.app.internal.utils.ViewUtils.getLayoutTask; + +public class AdvancedSearchTask extends AbstractTask implements TaskObserver { + private final String query; + private final Manager manager; + private final boolean applyLayout; + private final Network network; + + public AdvancedSearchTask(Manager manager, String query, boolean applyLayout) { + this.query = query; + this.manager = manager; + this.network = new Network(manager); + this.applyLayout = applyLayout; + } + + @Override + public void run(TaskMonitor monitor) throws Exception { + Manager manager = network.manager; + this.setNetworkFromGraphApi(monitor); + + if (cancelled) return; + monitor.setTitle("Load IntAct Network"); + monitor.setTitle("Querying IntAct servers"); + monitor.setProgress(0.2); + monitor.showMessage(TaskMonitor.Level.INFO, "Querying IntAct servers"); + manager.utils.registerService(this, TaskObserver.class, new Properties()); + + monitor.setTitle("Parsing result data"); + monitor.showMessage(TaskMonitor.Level.INFO, "Parsing data"); + monitor.setProgress(0.4); + if (cancelled) return; + + CyNetwork cyNetwork = network.getCyNetwork(); + + monitor.setTitle("Create summary edges"); + monitor.showMessage(TaskMonitor.Level.INFO, "Create summary edges"); + monitor.setProgress(0.6); + manager.data.addNetwork(network, cyNetwork); + manager.data.fireIntactNetworkCreated(network); + + if (cancelled) { + destroyNetwork(manager, network); + return; + } + + monitor.setTitle("Register network"); + monitor.showMessage(TaskMonitor.Level.INFO, "Register network"); + monitor.setProgress(0.7); + manager.data.setCurrentNetwork(cyNetwork); + if (cancelled) { + manager.utils.getService(CyNetworkManager.class).destroyNetwork(cyNetwork); + destroyNetwork(manager, network); + return; + } + + monitor.setTitle("Create and register network view + Initialize filters"); + monitor.showMessage(TaskMonitor.Level.INFO, "Create and register network view + Initialize filters"); + monitor.setProgress(0.8); + CyNetworkView networkView = manager.data.createNetworkView(cyNetwork); + ViewUtils.registerView(manager, networkView); + + if (cancelled) { + destroyNetwork(manager, network); + return; + } + + if (applyLayout) { + TaskIterator taskIterator = getLayoutTask(monitor, manager, networkView); + insertTasksAfterCurrentTask(taskIterator); + } + + manager.utils.showResultsPanel(); + } + + private void setNetworkFromGraphApi(TaskMonitor monitor) { + JsonNode fetchedNetwork; + + monitor.setTitle("Fetch network from IntAct servers"); + monitor.setProgress(0); + + ObjectMapper mapper = new ObjectMapper(); + + + Map nodes = new HashMap<>(); + ArrayNode edgesArray = mapper.createArrayNode(); + + try { + int page = 0; + JsonNode pagedResult; + do { + pagedResult = HttpUtils.getJsonNetworkWithRequestBody(this.query, page++); + JsonNode network = pagedResult.get("content").get(0); + Number totalPages = pagedResult.get("totalPages").numberValue(); + + monitor.showMessage(TaskMonitor.Level.INFO, "Page " + page + " / " + totalPages); + monitor.setProgress(page / totalPages.doubleValue()); + if (cancelled) return; + + for (JsonNode node : network.get("nodes")) { + nodes.putIfAbsent(node.get("id").textValue(), node); + } + + edgesArray.addAll((ArrayNode) network.get("edges")); + + } while (!pagedResult.get("last").booleanValue()); + + ObjectNode combinedNetwork = mapper.createObjectNode(); + + ArrayNode nodesArray = mapper.createArrayNode().addAll(nodes.values()); + nodesArray.addAll(nodes.values()); + combinedNetwork.set("nodes", nodesArray); + combinedNetwork.set("edges", edgesArray); + fetchedNetwork = combinedNetwork; + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + + if (cancelled) return; + + CyNetwork cyNetwork = ModelUtils.createIntactNetworkFromJSON(network, fetchedNetwork, query, () -> cancelled); + network.setNetwork(cyNetwork); + } + + + private void destroyNetwork(Manager manager, Network network) { + CyNetwork cyNetwork = network.getCyNetwork(); + + CyNetworkManager networkManager = manager.utils.getService(CyNetworkManager.class); + CyTableManager tableManager = manager.utils.getService(CyTableManager.class); + + if (cyNetwork != null && networkManager.networkExists(cyNetwork.getSUID())) + networkManager.destroyNetwork(cyNetwork); + + CyTable featuresTable = network.getFeaturesTable(); + if (featuresTable != null) tableManager.deleteTable(featuresTable.getSUID()); + + CyTable identifiersTable = network.getIdentifiersTable(); + if (identifiersTable != null) tableManager.deleteTable(identifiersTable.getSUID()); + manager.data.removeNetwork(network); + } + + + @Override + public void taskFinished(ObservableTask task) { + + } + + @Override + public void allFinished(FinishStatus finishStatus) { + + } + + public static JsonNode mergeJsonNodes(ObjectMapper objectMapper, List jsonNodes) { + ObjectNode merged = objectMapper.createObjectNode(); + ArrayNode mergedNodes = objectMapper.createArrayNode(); + ArrayNode mergedEdges = objectMapper.createArrayNode(); + + for (JsonNode jsonNode : jsonNodes) { + if (jsonNode.has("nodes") && jsonNode.get("nodes").isArray()) { + mergedNodes.addAll((ArrayNode) jsonNode.get("nodes")); + } + if (jsonNode.has("edges") && jsonNode.get("edges").isArray()) { + mergedEdges.addAll((ArrayNode) jsonNode.get("edges")); + } + } + + merged.set("nodes", mergedNodes); + merged.set("edges", mergedEdges); + return merged; + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/CreateNetworkTask.java b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/CreateNetworkTask.java index e324195a..1c1057bb 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/CreateNetworkTask.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/CreateNetworkTask.java @@ -57,7 +57,7 @@ public void run(TaskMonitor monitor) { begin = Instant.now(); manager.utils.registerService(this, TaskObserver.class, new Properties()); - JsonNode results = HttpUtils.postJSON(Manager.INTACT_GRAPH_WS + "network/data", postData, manager, () -> cancelled); + JsonNode results = HttpUtils.postJSON(Manager.INTACT_GRAPH_WS + "network/fromInteractors", postData, manager, () -> cancelled); System.out.println(Duration.between(begin, Instant.now()).toSeconds()); // This may change... monitor.setTitle("Parsing result data"); diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/TermsResolvingTask.java b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/TermsResolvingTask.java index c7f5e8d4..070f755f 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/TermsResolvingTask.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/TermsResolvingTask.java @@ -41,9 +41,15 @@ public TermsResolvingTask(Network network, String terms, String panelTitle, bool @Override public void run(TaskMonitor monitor) { monitor.setTitle("Solving term ambiguity"); +// System.out.println("Terms: " + terms); +// System.out.println("Advanced search: " + advancedSearch); +// System.out.println("Interactors to resolve: " + interactorsToResolve); +// System.out.println("Exact query: " + exactQuery); + if (terms.isBlank()) { monitor.showMessage(TaskMonitor.Level.WARN, "Empty query"); } else { + interactorsToResolve = resolveTerms(terms, manager.option.MAX_INTERACTOR_PER_TERM.getValue()); if (showNoResults()) return; @@ -62,7 +68,7 @@ public void run(TaskMonitor monitor) { d.setVisible(true); }); } - if (interactorsToResolve == null || interactorsToResolve.size() == 0) { + if (interactorsToResolve == null || interactorsToResolve.isEmpty()) { monitor.showMessage(TaskMonitor.Level.ERROR, "Query returned no terms"); } } @@ -148,7 +154,7 @@ private static String buildQuery(List termsToComplete) { } public Map> getInteractorsToResolve() { - return interactorsToResolve; + return interactorsToResolve; //todo: is this why the solving term ambiguity takes so long? } public Map getTotalInteractors() { diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/AdvancedSearchTaskFactory.java b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/AdvancedSearchTaskFactory.java new file mode 100644 index 00000000..e74ee6ea --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/AdvancedSearchTaskFactory.java @@ -0,0 +1,65 @@ +package uk.ac.ebi.intact.app.internal.tasks.query.factories; + +import org.cytoscape.application.swing.search.AbstractNetworkSearchTaskFactory; +import org.cytoscape.work.TaskIterator; + +import uk.ac.ebi.intact.app.internal.model.core.network.Network; +import uk.ac.ebi.intact.app.internal.model.managers.Manager; +import uk.ac.ebi.intact.app.internal.model.managers.sub.managers.OptionManager; +import uk.ac.ebi.intact.app.internal.tasks.query.AdvancedSearchTask; +import uk.ac.ebi.intact.app.internal.ui.components.query.SearchQueryComponent; +import uk.ac.ebi.intact.app.internal.ui.panels.options.OptionsPanel; +import uk.ac.ebi.intact.app.internal.utils.IconUtils; + +import javax.swing.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.logging.Logger; + +public class AdvancedSearchTaskFactory extends AbstractNetworkSearchTaskFactory { + private static final Icon ICON = IconUtils.createImageIcon("/IntAct/DIGITAL/ICON_PNG/Cropped_Gradient790.png"); + private static final String INTACT_ID = "uk.ac.ebi.intact.advanced.search"; + private static final String INTACT_NAME = "IntAct Advanced Search"; + private static final String INTACT_DESC = "Make an advanced search query"; + private static final URL INTACT_URL = buildUrl(); + + private SearchQueryComponent queryComponent = null; + private Network network; + + Manager manager; + + private static final Logger LOGGER = Logger.getLogger(AdvancedSearchTaskFactory.class.getName()); + + private static URL buildUrl() { + try { + return new URL("https://www.ebi.ac.uk/intact/home#advanced-search"); + } catch (MalformedURLException e) { + LOGGER.warning(e.getMessage()); + } + return null; + } + + public AdvancedSearchTaskFactory(Manager manager) { + super(INTACT_ID, INTACT_NAME, INTACT_DESC, ICON, INTACT_URL); + this.manager = manager; + this.network = new Network(manager); + } + + public boolean isReady() { + return queryComponent.getQueryText() != null && !queryComponent.getQueryText().isEmpty(); + } + + public TaskIterator createTaskIterator() { + return new TaskIterator(new AdvancedSearchTask(manager, queryComponent.getQueryText(), true)); + } + + public JComponent getQueryComponent() { + if (queryComponent == null) queryComponent = new SearchQueryComponent(true); + return queryComponent; + } + + @Override + public JComponent getOptionsComponent() { + return new OptionsPanel(manager, OptionManager.Scope.SEARCH); + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/ExactQueryTaskFactory.java b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/ExactQueryTaskFactory.java index c45c6984..ca59bd57 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/ExactQueryTaskFactory.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/ExactQueryTaskFactory.java @@ -47,7 +47,7 @@ public TaskIterator createTaskIterator() { } public JComponent getQueryComponent() { - if (queryComponent == null) queryComponent = new SearchQueryComponent(); + if (queryComponent == null) queryComponent = new SearchQueryComponent(false); return queryComponent; } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/FuzzySearchTaskFactory.java b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/FuzzySearchTaskFactory.java index 6a8e18c8..45180a4c 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/FuzzySearchTaskFactory.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/tasks/query/factories/FuzzySearchTaskFactory.java @@ -48,7 +48,7 @@ public TaskIterator createTaskIterator() { public JComponent getQueryComponent() { - if (queryComponent == null) queryComponent = new SearchQueryComponent(); + if (queryComponent == null) queryComponent = new SearchQueryComponent(false); return queryComponent; } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/AdvancedSearchQueryComponent.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/AdvancedSearchQueryComponent.java new file mode 100644 index 00000000..8e120930 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/AdvancedSearchQueryComponent.java @@ -0,0 +1,251 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query; + +import lombok.Getter; + +import org.apache.log4j.Logger; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.*; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels.RulePanel; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels.RuleSetPanel; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components.Rule; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components.RuleSet; + +import static uk.ac.ebi.intact.app.internal.ui.components.query.advanced.AdvancedSearchUtils.*; + +import javax.swing.*; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +public class AdvancedSearchQueryComponent { + static int frameWidth = 2000; + private JFrame frame; + + @Getter + private final JTextPane queryTextField = new JTextPane(); + + public final JPanel rulesPanel = new JPanel(); + + @Getter + private final ArrayList panels = new ArrayList<>(); + + @Getter + private final JButton buildQueryButton = new JButton("Build query"); + + private final QueryOperators queryOperators = new QueryOperators(this, panels); + private final MIQLParser miqlParser = new MIQLParser(); + + public void getFrame(String input) { + queryTextField.setText(input); + frame = new JFrame("Advanced Search Query Builder"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.setSize(frameWidth, 1000); + frame.setLayout(new BorderLayout()); + + JPanel pageStartContainer = new JPanel(new GridLayout(2, 1)); + pageStartContainer.setSize(frameWidth, 50); + + JPanel queryContainer = new JPanel(new FlowLayout(FlowLayout.CENTER)); + queryContainer.add(getQueryInputField(), BorderLayout.LINE_START); + queryContainer.add(getBuildQueryButtonContainer(), BorderLayout.CENTER); + + JPanel buttonsContainer = new JPanel(new FlowLayout(FlowLayout.CENTER)); + buttonsContainer.add(queryOperators.getButtons(rulesPanel), BorderLayout.LINE_END); + + pageStartContainer.add(queryContainer); + pageStartContainer.add(buttonsContainer); + + frame.add(pageStartContainer, BorderLayout.PAGE_START); + frame.add(getRuleScrollPane(), BorderLayout.CENTER); + + frame.setVisible(true); + } + + private JPanel getBuildQueryButtonContainer(){ + JPanel buttonContainer = new JPanel(); + buttonContainer.setLayout(new FlowLayout(FlowLayout.CENTER)); + + setButtonIntactPurple(buildQueryButton); + buildQueryButton.addActionListener(e -> { + + String fullQuery = getFullQuery(); + queryTextField.setText(fullQuery); + highlightQuery(fullQuery); + + Action submitAction = queryTextField.getActionMap().get("submitQuery"); + if (submitAction != null) { + submitAction.actionPerformed(new ActionEvent(queryTextField, ActionEvent.ACTION_PERFORMED, null)); + } + + frame.dispose(); + }); + + + buttonContainer.add(buildQueryButton); + return buttonContainer; + } + + public String getFullQuery() { + StringBuilder fullQuery = new StringBuilder(); + for (int i = 0; i < panels.size(); i++) { + Object panel = panels.get(i); + if (panel instanceof RuleSetPanel) { + fullQuery.append(((RuleSetPanel) panel).getQuery()); + } + else if (panel instanceof RulePanel) { + fullQuery.append(((RulePanel) panel).getQuery()); + } + if (i < panels.size() - 1) { + fullQuery.append(" ").append(queryOperators.getRuleOperator()).append(" "); + } + } + return fullQuery.toString(); + } + + private JScrollPane getRuleScrollPane() { + JScrollPane scrollPane = new JScrollPane(); + + rulesPanel.setAutoscrolls(true); + rulesPanel.setLayout(new BoxLayout(rulesPanel, BoxLayout.Y_AXIS)); + + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollPane.setViewportView(rulesPanel); + + return scrollPane; + } + + private JPanel getQueryInputField(){ + JPanel queryInputFieldContainer = new JPanel(); + + queryTextField.setMinimumSize(new Dimension(frameWidth, 25)); + queryTextField.setPreferredSize(new Dimension(frameWidth/2, 25)); + queryTextField.setVisible(true); + + queryTextField.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "submitQuery"); + queryTextField.getActionMap().put("submitQuery", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + + String input = queryTextField.getText(); + highlightQuery(input); + + RuleSet parsedQuery = miqlParser.parseMIQL(input); + if (parsedQuery != null && parsedQuery.rules != null && !parsedQuery.rules.isEmpty()) { + queryOperators.setRuleOperator(parsedQuery.condition); + queryOperators.updateAndOrButtons(); + + modifyComboboxFromQuery(parsedQuery, 0); + String builtQuery = getFullQuery(); + highlightQuery(builtQuery); + } else { + JOptionPane.showMessageDialog(null, "Failed to parse query. Please check the syntax."); + } + } + }); + + queryInputFieldContainer.add(queryTextField); + return queryInputFieldContainer; + } + + private RuleSetPanel modifyComboboxFromQuery(RuleSet ruleSet, int indentLevel) { + if (indentLevel == 0) { + rulesPanel.removeAll(); + panels.clear(); + } + + RuleSetPanel currentRuleSetPanel = null; + + if (indentLevel != 0) { + currentRuleSetPanel = new RuleSetPanel(this); + currentRuleSetPanel.getPanels().clear(); + currentRuleSetPanel.getQueryOperators().setRuleOperator(ruleSet.condition); + currentRuleSetPanel.getQueryOperators().updateAndOrButtons(); + } + + for (Object ruleComponent : ruleSet.rules) { + if (ruleComponent instanceof RuleSet) { + RuleSet nestedRuleSet = (RuleSet) ruleComponent; + RuleSetPanel nestedPanel = modifyComboboxFromQuery(nestedRuleSet, indentLevel + 1); + + + if (nestedPanel != null) { + if (indentLevel == 0) { + rulesPanel.add(nestedPanel.getRuleSetPanel()); + panels.add(nestedPanel); + } else { + currentRuleSetPanel.addRuleSetPanel(nestedPanel); + } + } + + } else if (ruleComponent instanceof Rule) { + Rule rule = (Rule) ruleComponent; + RulePanel rulePanel = new RulePanel(this); + + rulePanel.entityComboBox.setSelectedItem(rule.getEntity()); + rulePanel.entityPropertiesCombobox.setSelectedItem(rule.getFieldName()); + rulePanel.operatorsComboBox.setSelectedItem(rule.getOperator()); + rulePanel.userInputProperty.setText(rule.getUserInput1()); + rulePanel.userInputProperty2.setText(rule.getUserInput2()); + + if (indentLevel == 0) { + rulesPanel.add(rulePanel.getOneRuleBuilderPanel()); + panels.add(rulePanel); + } else { + currentRuleSetPanel.addRulePanel(rulePanel); + } + } + } + + if (indentLevel == 0) { + rulesPanel.revalidate(); + rulesPanel.repaint(); + return null; + } else { + return currentRuleSetPanel; + } + } + + public void highlightQuery(String query) { + StyledDocument doc = queryTextField.getStyledDocument(); + doc.removeUndoableEditListener(null); + + queryTextField.setText(""); + + Style defaultStyle = queryTextField.addStyle("default", null); + StyleConstants.setForeground(defaultStyle, Color.BLACK); + + Style operatorStyle = queryTextField.addStyle("operator", null); + StyleConstants.setForeground(operatorStyle, new Color(229, 191, 86)); + + Style miQLStyle = queryTextField.addStyle("keyword", null); + StyleConstants.setForeground(miQLStyle, new Color(165, 87, 202)); + + Style ruleStyle = queryTextField.addStyle("rule", null); + StyleConstants.setForeground(ruleStyle, new Color(58, 132, 176)); + + String[] tokens = query.split("((?<=\\W)|(?=\\W))"); + + for (String token : tokens) { + Style styleToUse = defaultStyle; + + if (token.equals("AND") || token.equals("OR") || token.equals("NOT")) { + styleToUse = operatorStyle; + } else if (token.matches(Field.getMiQlRegex())) { + styleToUse = miQLStyle; + } else if (token.matches("[(:)]")) { + styleToUse = ruleStyle; + } + + try { + doc.insertString(doc.getLength(), token, styleToUse); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/SearchQueryComponent.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/SearchQueryComponent.java index a1444f5e..85ba08a4 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/SearchQueryComponent.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/SearchQueryComponent.java @@ -11,7 +11,7 @@ public class SearchQueryComponent extends JTextField { private static final long serialVersionUID = 1L; - private static final String DEF_SEARCH_TEXT = "← Change query type | Enter one term per line | Options →"; + private static final String DEF_SEARCH_TEXT = "← Change query type | Enter one term per line | Options →"; //todo: should it be changed for advanced search? final int vgap = 1; final int hgap = 5; final String tooltip; @@ -19,11 +19,14 @@ public class SearchQueryComponent extends JTextField { private JTextArea queryTextArea = null; private JScrollPane queryScroll = null; private JPopupMenu popup = null; + final String[] textFromQueryBuilder = {null}; + final boolean isAdvancedSearch; - public SearchQueryComponent() { + public SearchQueryComponent(boolean advancedSearch) { super(); init(); tooltip = "Press " + (LookAndFeelUtil.isMac() ? "Command" : "Ctrl") + "+ENTER to run the search"; + isAdvancedSearch = advancedSearch; } void init() { @@ -42,7 +45,7 @@ public void mousePressed(MouseEvent e) { // Since we provide our own search component, it should let Cytoscape know // when it has been updated by the user, so Cytoscape can give a better - // feedback to the user of whether or not the whole search component is ready + // feedback to the user of whether the whole search component is ready // (e.g. Cytoscape may enable or disable the search button) getDocument().addDocumentListener(new DocumentListener() { @Override @@ -87,6 +90,9 @@ public void paint(Graphics g) { } public String getQueryText() { + if (textFromQueryBuilder[0] != null) { + return textFromQueryBuilder[0]; + } if (queryTextArea == null) return ""; return queryTextArea.getText(); } @@ -108,11 +114,40 @@ private void showQueryPopup() { queryScroll.setPreferredSize(new Dimension(getSize().width, 200)); popup.setPreferredSize(queryScroll.getPreferredSize()); + if (isAdvancedSearch) { + JButton queryBuilderButton = getBuildQueryButton(); + popup.add(queryBuilderButton, BorderLayout.EAST); + } + popup.show(this, 0, 0); popup.requestFocus(); queryTextArea.requestFocusInWindow(); queryTextArea.setToolTipText(tooltip); + } + + private JButton getBuildQueryButton() { + JButton queryBuilder = new JButton("Query builder"); + queryBuilder.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + AdvancedSearchQueryComponent component = new AdvancedSearchQueryComponent(); + component.getFrame(queryTextArea.getText()); + + component.getBuildQueryButton().addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String builtQuery = component.getQueryTextField().getText(); + textFromQueryBuilder[0] = builtQuery; + queryTextArea.setText(builtQuery); + queryTextArea.revalidate(); + queryTextArea.repaint(); + updateQueryTextField(); + } + }); + } + }); + return queryBuilder; } private void updateQueryTextField() { @@ -144,7 +179,6 @@ private void createQueryScroll() { queryTextArea.getActionMap().put(ENTER_ACTION_KEY, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { - // System.out.println("\n\nENTER"); SearchQueryComponent.this.firePropertyChange(NetworkSearchTaskFactory.SEARCH_REQUESTED_PROPERTY, null, null); popup.setVisible(false); } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/AdvancedSearchUtils.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/AdvancedSearchUtils.java new file mode 100644 index 00000000..d25db2b0 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/AdvancedSearchUtils.java @@ -0,0 +1,103 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced; + +import uk.ac.ebi.intact.app.internal.ui.components.query.AdvancedSearchQueryComponent; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels.RulePanel; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels.RuleSetPanel; + +import javax.swing.*; + +import java.awt.*; +import java.util.ArrayList; + +public class AdvancedSearchUtils { + final static Color INTACT_PURPLE = new Color(104, 41, 124); + + public static void setCorrectDimensions(JComponent component) { + Dimension comboboxDimension = new Dimension(300, 20); + component.setPreferredSize(comboboxDimension); + component.setMinimumSize(comboboxDimension); + component.setMaximumSize(comboboxDimension); + } + + public static void setButtonIntactPurple(JButton button) { + button.setOpaque(true); + button.setBorderPainted(false); + button.setBackground(INTACT_PURPLE); + button.setForeground(Color.WHITE); + } + + public static void setButtonWhite(JButton button) { + button.setOpaque(true); + button.setBorderPainted(false); + button.setBackground(Color.WHITE); + button.setForeground(INTACT_PURPLE); + } + + public static String getQueriesFromRuleBuilders(ArrayList panels, String queryOperator) { + if (!panels.isEmpty()){ + ArrayList ruleBuilders = new ArrayList<>(); + for (Object panel : panels) { + if (panel instanceof RulePanel) { + RulePanel rulePanel = (RulePanel) panel; + ruleBuilders.add(rulePanel.getQuery()); + } else if (panel instanceof RuleSetPanel) { + RuleSetPanel ruleSetPanel = (RuleSetPanel) panel; + ruleBuilders.add(ruleSetPanel.getQuery()); + } + } + return String.join(" " + queryOperator + " ", ruleBuilders); + } else { + return null; + } + } + + public static JButton getDeletePanelButton(JPanel panelToDelete, AdvancedSearchQueryComponent advancedSearchQueryComponent) { + JButton deleteButton = new JButton("X"); + setButtonIntactPurple(deleteButton); + + deleteButton.addActionListener(e -> { + Container parent = panelToDelete.getParent(); + if (parent != null) { + parent.remove(panelToDelete); + parent.revalidate(); + parent.repaint(); + } + + Object toRemove = findPanel(panelToDelete, advancedSearchQueryComponent.getPanels()); + + if (toRemove != null) { + advancedSearchQueryComponent.getPanels().remove(toRemove); + } + + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + }); + + return deleteButton; + } + + private static Object findPanel(JPanel panelToDelete, ArrayList panels) { + for (Object obj : panels) { + if (obj instanceof RulePanel) { + if (((RulePanel) obj).getOneRuleBuilderPanel() == panelToDelete) { + return obj; + } + } else if (obj instanceof RuleSetPanel) { + RuleSetPanel ruleSetPanel = (RuleSetPanel) obj; + if (ruleSetPanel.getRuleSetPanel() == panelToDelete) { + return obj; + } + + Object nested = findPanel(panelToDelete, ruleSetPanel.getPanels()); + if (nested != null) { + ruleSetPanel.getPanels().remove(nested); + return null; + } + } + } + return null; + } + + + +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/Field.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/Field.java new file mode 100644 index 00000000..b33ec352 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/Field.java @@ -0,0 +1,146 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced; + +import lombok.Getter; + +import java.time.Year; +import java.util.*; + +@Getter +public enum Field { + P_ID("id","Identifiers", "string", "participant"), + P_ID_A("idA","Identifier", "string", "participantA"), + P_ID_B("idB","Identifier", "string", "participantB"), + P_IDENTIFIER("identifier","Identifiers, Alternatives, Aliases", "string", "participant"), + P_ALT_ID_A("altidA","Alternative id", "string", "participantA"), + P_ALT_ID_B("altidB","Alternative id", "string", "participantB"), + P_ALIAS("alias","Alias", "string", "participant"), + P_ALIAS_A("aliasA","Alias", "string", "participantA"), + P_ALIAS_B("aliasB","Alias", "string", "participantB"), + PUB_YEAR("pubyear","Publication year", "int-range", "publication", "[2000 TO " + Year.now().getValue() + "]", new String[]{"∈", "∉"}), + PUB_AUTHORS("pubauthors","Publication author(s)", "string", "publication"), + PUB_FIRST_AUTH("pubauth","Publication 1st author(s)", "string", "publication"), + PUB_ID("pubid","Publication Identifier(s)", "string", "publication"), + P_TAX_ID_A("taxidA","Taxon id or Species", "string", "participantA"), + P_TAX_ID_B("taxidB","Taxon id or Species", "string", "participantB"), + TAX_ID_HOST("taxidHost","Taxon id or Species of Host organism", "string", "interaction"), + P_SPECIES("species","Taxon id or Species", "string", "participant"), + I_TYPE("type","Interaction type(s)", "string", "interaction"), + I_DET_METHOD("detmethod","Interaction Detection method(s)", "string", "interaction"), + I_ID("interaction_id","Interaction identifier(s)", "string", "interaction"), + P_BIO_ROLE_A("pbioroleA","Biological role", "string", "participantA"), + P_BIO_ROLE_B("pbioroleB","Biological role", "string", "participantB"), + P_BIO_ROLE("pbiorole","Biological role", "string", "participant"), + P_TYPE("ptype","Interactor type", "string", "participant"), + P_TYPE_A("ptypeA","Interactor type", "string", "participantA"), + P_TYPE_B("ptypeB","Interactor type", "string", "participantB"), + P_XREF("pxref","Interactor xref", "string", "participant"), + P_XREF_A("pxrefA","Interactor xref", "string", "participantA"), + P_XREF_B("pxrefB","Interactor xref", "string", "participantB"), + INTACT_MI_SCORE("intact-miscore","IntAct MI Score", "float-range", "interaction", "[0 TO 1]", new String[]{"∈", "∉"}), + P_GENE_NAME("geneName","Gene Name", "string", "participant"), + P_GENE_NAME_A("geneNameA","Gene Name", "string", "participantA"), + P_GENE_NAME_B("geneNameB","Gene Name", "string", "participantB"), + I_XREF("xref","Interaction xref", "string", "interaction"), + I_ANNOTATION("annot","Interaction annotation(s)", "string", "interaction"), + R_DATE("rdate","Release date", "date-range", "curationMetadata", "[20030101 TO " + Year.now().getValue() + "1231]", new String[]{"∈", "∉"}), + U_DATE("udate","Update date", "date-range", "curationMetadata", "[20030101 TO " + Year.now().getValue() + "1231]", new String[]{"∈", "∉"}), + NEGATIVE("negative","Negative interaction", "boolean", "interaction", "TRUE", new String[]{"TRUE", "FALSE"}), + P_MUTATION_A("mutationA","Mutation of Interactor A", "boolean", "participantA", "TRUE", new String[]{"TRUE", "FALSE"}), + P_MUTATION_B("mutationB","Mutation of Interactor B", "boolean", "participantB", "TRUE", new String[]{"TRUE", "FALSE"}), + P_MUTATION("mutation","Mutation of Interactor", "boolean", "participant", "TRUE", new String[]{"TRUE", "FALSE"}), + COMPLEX("complex","Complex expansion", "category", "interaction", "-"), //TODO:add the spoke expansion + P_FEATURE_TYPE_A("ftypeA","Feature type", "string", "participantA"), + P_FEATURE_TYPE_B("ftypeB","Feature type", "string", "participantB"), + P_FEATURE_TYPE("ftype","Feature type", "string", "participant"), + P_IDENTIFICATION_METHOD("pmethod","Interactor identification method", "string", "participant"), + P_IDENTIFICATION_METHOD_A("pmethodA","Interactor identification method", "string", "participantA"), + P_IDENTIFICATION_METHOD_B("pmethodB","Interactor identification method", "string", "participantB"), + STOICHIOMETRY("stc","Stoichiometry", "string", "participant", "TRUE", new String[]{"TRUE", "FALSE"}), + I_PARAMETERS("param","Interaction parameters", "string", "interaction", "TRUE", new String[]{"TRUE", "FALSE"}), + SOURCE_DB("source","Source database", "string", "curationMetadata"); + + private final String miqlQuery; + private final String name; + private final String type; + private final String entity; + private final String defaultValue; + private final String[] operators; + + public static final Map FIELD_MAP = new HashMap<>(); + + static { + for (Field field : values()) { + FIELD_MAP.put(field.name(), field); + } + } + + Field(String miqlQuery, String name, String type, String entity) { + this(miqlQuery, name, type, entity, null, new String[]{"=", "≠", "in", "not in"}); + } + + Field(String miqlQuery, String name, String type, String entity, String defaultValue) { + this(miqlQuery, name, type, entity, defaultValue, new String[]{"=", "≠", "in", "not in"}); + } + + Field(String miqlQuery, String name, String type, String entity, String defaultValue, String[] operators) { + this.miqlQuery = miqlQuery; + this.name = name; + this.type = type; + this.entity = entity; + this.defaultValue = defaultValue; + this.operators = operators; + } + + public static Field getFieldFromNameAndEntity(String name, String entity) { + List matchingFields = getFieldsFromName(name); + + for (Field field : matchingFields) { + if (field.getEntity().equalsIgnoreCase(entity)) { + return field; + } + } + + return null; + } + + private static List getFieldsFromName(String name) { + List matchingFields = new ArrayList<>(); + for (Field field : FIELD_MAP.values()) { + if (field.getName().equalsIgnoreCase(name)) { + matchingFields.add(field); + } + } + return matchingFields; + } + + public static Field getFieldsFromMiQL(String miqlQuery) { + for (Field field : FIELD_MAP.values()) { + if (field.getMiqlQuery().equalsIgnoreCase(miqlQuery)) { + return field; + } + } + return null; + } + + public static String[] getEntities() { + Set uniqueEntities = new HashSet<>(); + for (Field field : FIELD_MAP.values()) { + uniqueEntities.add(field.getEntity()); + } + + List sortedEntities = new ArrayList<>(uniqueEntities); + Collections.sort(sortedEntities); + + return sortedEntities.toArray(new String[0]); + } + + public static String getMiQlRegex() { + Set uniqueMiqls = new HashSet<>(); + for (Field field : FIELD_MAP.values()) { + uniqueMiqls.add(field.getMiqlQuery()); + } + List sortedMiqls = new ArrayList<>(uniqueMiqls); + Collections.sort(sortedMiqls); + return String.join("|", sortedMiqls); + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/MIQLParser.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/MIQLParser.java new file mode 100644 index 00000000..36de98ec --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/MIQLParser.java @@ -0,0 +1,150 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced; + +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components.*; + +import java.util.*; + +public class MIQLParser { + + public RuleSet parseMIQL(String miql) { + miql = "(" + miql + ")"; + RuleSet out = null; + Deque stack = new ArrayDeque<>(); + int end, stackLevel; + String value; + Map> levelMap = new HashMap<>(); + + char[] array = miql.toCharArray(); + for (int index = 0; index < array.length; index++) { + char c = array[index]; + switch (c) { + case '(': + stack.push(new StackFrame(index, new RuleSet())); + break; + case ')': + StackFrame frame = stack.pop(); + RuleSet ruleSet = frame.getRuleSet(); + out = ruleSet; + end = index; + int start = frame.getStart(); + value = miql.substring(start + 1, end); + + if (start > 0 && array[start - 1] == ':') { + Rule rule = extractSetRule(miql, start, value); + if (!stack.isEmpty()) { + stack.peek().getRuleSet().rules.add(rule); + } + } else { + stackLevel = stack.size(); + Range range = new Range(start, end - 1); + setupLevelMap(levelMap, stackLevel, range); + String trimmedValue = removeSuperiorRules(value, start, end, stackLevel, levelMap); + fillRuleSet(ruleSet, trimmedValue); + if (!stack.isEmpty()) { + stack.peek().getRuleSet().rules.add(ruleSet); + } + } + } + } + return out; + } + + private void setupLevelMap(Map> levelMap, int stackLevel, Range range) { + levelMap.computeIfAbsent(stackLevel, k -> new ArrayList<>()).add(range); + } + + private String removeSuperiorRules(String value, int start, int end, int stackLevel, Map> levelMap) { + int deleted = start; + List superiorRanges = levelMap.get(stackLevel + 1); + + if (superiorRanges != null) { + for (Range superiorRange : superiorRanges) { + if (superiorRange.getStart() > start && superiorRange.getEnd() < end) { + value = value.substring(0, superiorRange.getStart() - deleted) + + value.substring(superiorRange.getEnd() - deleted); + deleted += superiorRange.getEnd() - superiorRange.getStart(); + } + } + } + return value; + } + + public void fillRuleSet(RuleSet ruleSet, String value) { + ruleSet.setCondition(value.contains("OR") ? "OR" : "AND"); + + List superiorRuleSets = ruleSet.getRules(); + int i = 0; + ruleSet.setRules(new ArrayList<>()); + + String[] ruleStrings = value.split("\\sAND\\s|\\sOR\\s"); + for (String ruleStr : ruleStrings) { + ruleStr = ruleStr.trim(); + + if (!ruleStr.isEmpty()) { + if ("()".equals(ruleStr)) { + ruleSet.getRules().add(superiorRuleSets.get(i++)); + } else { + ruleStr = ruleStr.trim(); + boolean different = ruleStr.toUpperCase().startsWith("NOT "); + String ruleOperator = different ? "≠" : "="; + + int indexOfColon = ruleStr.indexOf(":"); + if (indexOfColon == -1) indexOfColon = ruleStr.length(); + + String ruleFieldKeyword = ruleStr.substring(different ? 4 : 0, indexOfColon); + + Field ruleField = Field.getFieldsFromMiQL(ruleFieldKeyword); + + if (ruleField != null) { + String ruleValue = ruleStr.substring(indexOfColon + 1).trim(); + String operator = ruleValue.startsWith("[") ? (different ? "∉" : "∈") : ruleOperator; + + String userInput1 = ruleValue; + String userInput2 = null; + + if (ruleValue.startsWith("[") && ruleValue.endsWith("]") && ruleValue.contains("TO")) { + ruleValue = ruleValue.replace("[", "").replace("]", ""); + String[] userInputs = ruleValue.split("TO"); + userInput1 = userInputs[0].trim(); + userInput2 = userInputs[1].trim(); + } + + + if (ruleValue.startsWith("(")) { + ruleSet.getRules().add(superiorRuleSets.remove(superiorRuleSets.size() - 1)); + } else if ("undefined".equals(ruleValue)) { + ruleSet.getRules().add(new Rule(ruleFieldKeyword, operator, ruleField.getEntity(), userInput1, userInput2, ruleField.getName())); + } else { + ruleSet.getRules().add(new Rule(ruleFieldKeyword, operator, ruleField.getEntity(), userInput1, userInput2, ruleField.getName())); + } + } + } + } + } + } + + private Rule extractSetRule(String input, int start, String value) { + int previousSpaceIndex = input.lastIndexOf(" ", start - 2); + if (input.length() > previousSpaceIndex + 1 && input.charAt(previousSpaceIndex + 1) == '(') { + previousSpaceIndex++; + } + + if (value.startsWith("(") && value.endsWith(")")) { + value = value.substring(1, value.length() - 1); + } + + String potentialNot = input.substring(Math.max(previousSpaceIndex - 3, 0), previousSpaceIndex); + String operator = potentialNot.equals("NOT") || potentialNot.equals("not") ? "not in" : "in"; + + String field = input.substring(previousSpaceIndex + 1, start - 1); + Field parsedField = Field.getFieldsFromMiQL(field); + String entity = parsedField != null ? parsedField.getEntity() : null; + String fieldName = parsedField != null ? parsedField.getName() : field; + if (entity != null) { + return new Rule(field, operator, entity, value, null, fieldName); + } + return null; + } + +} + diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/QueryComponents.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/QueryComponents.java new file mode 100644 index 00000000..5daa425f --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/QueryComponents.java @@ -0,0 +1,32 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Setter +@Getter +public class QueryComponents { + private String entity; + private String operator; + private String name; + private String userInput; + private String userInput2; + private boolean negated; + private boolean isRuleSet; + private QueryComponents parent; + private List children; + + public QueryComponents() { + this.children = new ArrayList<>(); + } + + public void addChild(QueryComponents child) { + if (child != null) { + child.setParent(this); + this.children.add(child); + } + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/QueryOperators.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/QueryOperators.java new file mode 100644 index 00000000..ac2b2f96 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/QueryOperators.java @@ -0,0 +1,132 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced; + +import lombok.Getter; +import lombok.Setter; + +import uk.ac.ebi.intact.app.internal.ui.components.query.AdvancedSearchQueryComponent; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels.RulePanel; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels.RuleSetPanel; + +import javax.swing.*; + +import java.awt.*; +import java.util.ArrayList; + +import static uk.ac.ebi.intact.app.internal.ui.components.query.advanced.AdvancedSearchUtils.*; + +public class QueryOperators { + + @Getter + @Setter + private String ruleOperator = "AND"; + + private final JButton andButton = new JButton("AND"); + private final JButton orButton = new JButton("OR"); + + AdvancedSearchQueryComponent advancedSearchQueryComponent; + + @Getter + ArrayList panels; + + + public QueryOperators(AdvancedSearchQueryComponent advancedSearchQueryComponent, + ArrayList panels) { + this.advancedSearchQueryComponent = advancedSearchQueryComponent; + this.panels = panels; + } + + public JPanel getAndOrButton() { + JPanel buttonContainer = new JPanel(); + + + setButtonIntactPurple(andButton); + + + setButtonWhite(orButton); + + andButton.addActionListener(e -> { + setButtonIntactPurple(andButton); + setButtonWhite(orButton); + ruleOperator = "AND"; + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + }); + + orButton.addActionListener(e -> { + setButtonWhite(andButton); + setButtonIntactPurple(orButton); + ruleOperator = "OR"; + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + }); + + buttonContainer.add(andButton); + buttonContainer.add(orButton); + + return buttonContainer; + } + + public JPanel getRuleAndRuleSetButton(JPanel parentContainer) { + JPanel buttonContainer = new JPanel(); + + JButton addRuleButton = new JButton("+ Rule"); + setButtonIntactPurple(addRuleButton); + + JButton ruleSetButton = new JButton("+ Ruleset"); + setButtonIntactPurple(ruleSetButton); + + + addRuleButton.addActionListener(e -> { + RulePanel rule = new RulePanel(advancedSearchQueryComponent); + + parentContainer.add(rule.getOneRuleBuilderPanel()); + rule.setEntityComboboxSelected(); //triggers the actionListener to set up the other comboBoxes + + panels.add(rule); + + parentContainer.revalidate(); + parentContainer.repaint(); + + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + }); + + ruleSetButton.addActionListener(e -> { + RuleSetPanel ruleSet = new RuleSetPanel( + advancedSearchQueryComponent); + + parentContainer.add(ruleSet.getRuleSetPanel()); + + panels.add(ruleSet); + parentContainer.revalidate(); + parentContainer.repaint(); + + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + }); + + buttonContainer.add(addRuleButton); + buttonContainer.add(ruleSetButton); + + return buttonContainer; + + } + + public JPanel getButtons(JPanel parentContainer) { + JPanel container = new JPanel(); + container.setLayout(new GridLayout(1,2)); + container.add(getAndOrButton()); + container.add(getRuleAndRuleSetButton(parentContainer)); + return container; + } + + public void updateAndOrButtons() { + if (ruleOperator.equals("AND")) { + setButtonIntactPurple(andButton); + setButtonWhite(orButton); + } else { + setButtonWhite(andButton); + setButtonIntactPurple(orButton); + } + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/panels/RulePanel.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/panels/RulePanel.java new file mode 100644 index 00000000..e4e60d70 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/panels/RulePanel.java @@ -0,0 +1,236 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels; + +import uk.ac.ebi.intact.app.internal.ui.components.query.AdvancedSearchQueryComponent; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.Field; + +import static uk.ac.ebi.intact.app.internal.ui.components.query.advanced.AdvancedSearchUtils.*; + +import javax.swing.*; + +import java.awt.*; +import java.util.Objects; + +public class RulePanel { + + JPanel oneRule = new JPanel(); + + public JComboBox entityComboBox = new JComboBox<>(Field.getEntities()); + public JComboBox entityPropertiesCombobox = new JComboBox<>(); + public JComboBox operatorsComboBox = new JComboBox<>(); + + + public JTextArea firstBracket = new JTextArea("["); + public JTextField userInputProperty = new JTextField(); + public JTextArea textTO = new JTextArea(" TO "); + public JTextField userInputProperty2 = new JTextField(); + public JTextArea lastBracket = new JTextArea("]"); + + AdvancedSearchQueryComponent advancedSearchQueryComponent; + + public RulePanel(AdvancedSearchQueryComponent advancedSearchQueryComponent) { + this.advancedSearchQueryComponent = advancedSearchQueryComponent; + getOneRuleBuilderPanel(); + oneRule.add(getDeletePanelButton(oneRule, advancedSearchQueryComponent)); + } + + public JPanel getOneRuleBuilderPanel() { + oneRule.setBorder(BorderFactory.createTitledBorder("Rule")); + oneRule.setLayout(new BoxLayout(oneRule, BoxLayout.X_AXIS)); + oneRule.setVisible(true); + setUpDisplay(); + + oneRule.add(getEntityComboBox()); + oneRule.add(getEntityPropertiesComboBox()); + oneRule.add(getOperatorsComboBox()); + oneRule.add(firstBracket); + oneRule.add(getUserInputProperty()); + oneRule.add(textTO); + oneRule.add(getUserInputProperty2()); + oneRule.add(lastBracket); + + oneRule.revalidate(); + oneRule.repaint(); + + return oneRule; + } + + private JComboBox getEntityComboBox() { + setCorrectDimensions(entityComboBox); + + entityComboBox.addActionListener(e -> { + setUpEntityPropertiesCombobox((String) entityComboBox.getSelectedItem()); + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + }); + + return entityComboBox; + } + + private JComboBox getEntityPropertiesComboBox() { + setCorrectDimensions(entityPropertiesCombobox); + + entityPropertiesCombobox.addItemListener(e -> { + setUpOperatorsCombobox((String) entityPropertiesCombobox.getSelectedItem(), + (String) entityComboBox.getSelectedItem()); + }); + return entityPropertiesCombobox; + } + + private JComboBox getOperatorsComboBox() { + setCorrectDimensions(operatorsComboBox); + + operatorsComboBox.addActionListener(e -> { + setUserInput2Visible(); + userInputProperty.setVisible(operatorsComboBox.getSelectedItem() != null && isUserInputNeeded()); + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + }); + + return operatorsComboBox; + } + + private void setUserInput2Visible(){ + boolean visible = operatorsComboBox.getSelectedItem() != null && isUserInput2needed(); + userInputProperty2.setVisible(visible); + firstBracket.setVisible(visible); + lastBracket.setVisible(visible); + textTO.setVisible(visible); + } + + private void setUpDisplay(){ + firstBracket.setMaximumSize(new Dimension(10,20)); + lastBracket.setMaximumSize(new Dimension(10,20)); + textTO.setMaximumSize(new Dimension(10,20)); + + firstBracket.setEditable(false); + lastBracket.setEditable(false); + textTO.setEditable(false); + + firstBracket.setBackground(UIManager.getColor("Panel.background")); + lastBracket.setBackground(UIManager.getColor("Panel.background")); + textTO.setBackground(UIManager.getColor("Panel.background")); + } + + + private JTextField getUserInputProperty() { + setCorrectDimensions(userInputProperty); + userInputProperty.addActionListener(e -> { + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + } + ); + return userInputProperty; + } + + private boolean isUserInputNeeded() { + String operatorSelected = (String) operatorsComboBox.getSelectedItem(); + return operatorSelected != null && !(operatorSelected.equals("TRUE") || operatorSelected.equals("FALSE")); + } + + private JTextField getUserInputProperty2() { + setCorrectDimensions(userInputProperty2); + userInputProperty2.addActionListener(e -> { + advancedSearchQueryComponent.getQueryTextField().setText(advancedSearchQueryComponent.getFullQuery()); + advancedSearchQueryComponent.highlightQuery(advancedSearchQueryComponent.getQueryTextField().getText()); + } + ); + return userInputProperty2; + } + + private boolean isUserInput2needed() { + String operatorSelected = (String) operatorsComboBox.getSelectedItem(); + return operatorSelected != null && (operatorSelected.equals("∈") || operatorSelected.equals("∉")); + } + + private void setUpEntityPropertiesCombobox(String entitySelected) { + entityPropertiesCombobox.removeAllItems(); + + for (Field field : Field.values()) { + if (entitySelected != null && field.getEntity().equalsIgnoreCase(entitySelected.trim())) { + entityPropertiesCombobox.addItem(field.getName()); + } + } + + if (entityPropertiesCombobox.getItemCount() > 0) { + entityPropertiesCombobox.setSelectedIndex(0); + } + + setUpOperatorsCombobox((String) entityPropertiesCombobox.getSelectedItem(), entitySelected); + + entityPropertiesCombobox.revalidate(); + entityPropertiesCombobox.repaint(); + } + + private void setUpOperatorsCombobox(String entityPropertySelected, String entitySelected) { + operatorsComboBox.removeAllItems(); + if (entityPropertySelected == null || entitySelected == null) return; + + Field field = Field.getFieldFromNameAndEntity(entityPropertySelected, entitySelected); + if (field == null) return; + + if (field.getOperators() != null) { + for (String operator : field.getOperators()) { + operatorsComboBox.addItem(operator); + } + } + + if (operatorsComboBox.getItemCount() > 0) { + operatorsComboBox.setSelectedIndex(0); + } + + operatorsComboBox.revalidate(); + operatorsComboBox.repaint(); + } + + public void setEntityComboboxSelected(){ + entityComboBox.setSelectedIndex(0); + } + + public String getQuery() { + + String entityPropertySelected = entityPropertiesCombobox.getSelectedItem() != null ? (String) entityPropertiesCombobox.getSelectedItem() : ""; + String entitySelected = entityComboBox.getSelectedItem() != null ? (String) entityComboBox.getSelectedItem() : ""; + String operatorSelected = operatorsComboBox.getSelectedItem() != null ? (String) operatorsComboBox.getSelectedItem() : ""; + String userInput = userInputProperty.getText() != null ? userInputProperty.getText() : ""; + String userInput2 = userInputProperty2.getText() != null ? userInputProperty2.getText() : ""; + + entitySelected = Field.getFieldFromNameAndEntity(entityPropertySelected, entitySelected) != null + ? Objects.requireNonNull(Field.getFieldFromNameAndEntity(entityPropertySelected, entitySelected)).getMiqlQuery() + : ""; + + String query = ""; + + if (operatorSelected != null) { + switch (operatorSelected) { + case "=": + query = entitySelected + ":" + userInput; + break; + case "≠": + query = "NOT " + entitySelected + ":" + userInput; + break; + case "in": + query = entitySelected + ":(" + userInput + ")"; + break; + case "not in": + query = "NOT " + entitySelected + ":(" + userInput + ")"; + break; + case "∈": + query = entitySelected + ":[" + userInput + " TO " + userInput2 + "]"; + break; + case "∉": + query = "NOT " + entitySelected + ":[" + userInput + " TO " + userInput2 + "]"; + break; + case "TRUE": + query = entitySelected + ":TRUE"; + break; + case "FALSE": + query = entitySelected + ":FALSE"; + break; + default: + return ""; + } + } + return query; + } + +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/panels/RuleSetPanel.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/panels/RuleSetPanel.java new file mode 100644 index 00000000..328299f1 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/panels/RuleSetPanel.java @@ -0,0 +1,79 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.panels; + +import lombok.Getter; +import lombok.Setter; +import uk.ac.ebi.intact.app.internal.ui.components.query.AdvancedSearchQueryComponent; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.QueryOperators; + +import static uk.ac.ebi.intact.app.internal.ui.components.query.advanced.AdvancedSearchUtils.*; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.stream.Collectors; + +@Getter +public class RuleSetPanel { + + private final JPanel ruleSetPanel = new JPanel(); + + @Setter + private QueryOperators queryOperators; + + @Getter + private final ArrayList panels = new ArrayList<>(); + + public RuleSetPanel(AdvancedSearchQueryComponent advancedSearchQueryComponent) { + queryOperators = new QueryOperators(advancedSearchQueryComponent, panels); + ruleSetPanel.setBorder(BorderFactory.createTitledBorder("Rule Set")); + ruleSetPanel.setLayout(new BoxLayout(ruleSetPanel, BoxLayout.Y_AXIS)); + ruleSetPanel.add(queryOperators.getButtons(ruleSetPanel)); + ruleSetPanel.add(getDeletePanelButton(ruleSetPanel, advancedSearchQueryComponent)); + } + + public String getQuery(){ + String queryFromSubRuleSets = getQueryFromSubRuleSets(); + if (queryFromSubRuleSets != null) { + return "(" + getQueryFromRules() + " " + this.queryOperators.getRuleOperator() + " " + getQueryFromSubRuleSets() + ")"; + } else { + return "(" + getQueryFromRules() + ")"; + } + } + + private String getQueryFromRules() { + ArrayList rules = panels.stream() + .filter(panel -> panel instanceof RulePanel) + .map(panel -> (RulePanel) panel) + .collect(Collectors.toCollection(ArrayList::new)); + + return getQueriesFromRuleBuilders(rules, this.queryOperators.getRuleOperator()); + } + + private String getQueryFromSubRuleSets(){ + ArrayList subRuleSets = panels.stream() + .filter(panel -> panel instanceof RuleSetPanel) + .map(panel -> (RuleSetPanel) panel) + .collect(Collectors.toCollection(ArrayList::new)); + + if (!subRuleSets.isEmpty()) { + StringBuilder subQuery = new StringBuilder(); + for (RuleSetPanel subRuleSet : subRuleSets) { + subQuery.append(subRuleSet.getQuery()); + } + return subQuery.toString(); + } + + return null; + } + + public void addRulePanel(RulePanel rulePanel) { + panels.add(rulePanel); + ruleSetPanel.add(rulePanel.getOneRuleBuilderPanel()); + } + + public void addRuleSetPanel(RuleSetPanel ruleSetPanelToAdd) { + panels.add(ruleSetPanelToAdd); + ruleSetPanel.add(ruleSetPanelToAdd.getRuleSetPanel()); + ruleSetPanel.revalidate(); + ruleSetPanel.repaint(); + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/Range.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/Range.java new file mode 100644 index 00000000..33a88ea4 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/Range.java @@ -0,0 +1,15 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Range { + int start, end; + + public Range(int start, int end) { + this.start = start; + this.end = end; + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/Rule.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/Rule.java new file mode 100644 index 00000000..8e44a5f1 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/Rule.java @@ -0,0 +1,29 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Rule implements RuleComponent { + String miql; + String operator; + String entity; + String userInput1; + String userInput2; + String fieldName; + + public Rule(String miql, String operator, String entity, String userInput1, String userInput2, String fieldName) { + this.miql = miql; + this.operator = operator; + this.entity = entity; + this.userInput1 = userInput1; + this.userInput2 = userInput2; + this.fieldName = fieldName; + } + + @Override + public Rule getRule(int i) { + return this; + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/RuleComponent.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/RuleComponent.java new file mode 100644 index 00000000..7d8104c9 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/RuleComponent.java @@ -0,0 +1,8 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components; + +public interface RuleComponent { + Rule getRule(int i); +} + + + diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/RuleSet.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/RuleSet.java new file mode 100644 index 00000000..b0ae17ea --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/RuleSet.java @@ -0,0 +1,24 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +public class RuleSet implements RuleComponent { + public String condition; + public List rules; + + public RuleSet() { + this.condition = "AND"; + this.rules = new ArrayList<>(); + } + + @Override + public Rule getRule(int i) { + return rules.get(i).getRule(i); + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/StackFrame.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/StackFrame.java new file mode 100644 index 00000000..94b395b0 --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/components/query/advanced/parser/components/StackFrame.java @@ -0,0 +1,16 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class StackFrame { + int start; + RuleSet ruleSet; + + public StackFrame(int start, RuleSet ruleSet) { + this.start = start; + this.ruleSet = ruleSet; + } +} diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/detail/sub/panels/edge/attributes/EdgeParticipants.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/detail/sub/panels/edge/attributes/EdgeParticipants.java index 52343b42..6eb3c68e 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/detail/sub/panels/edge/attributes/EdgeParticipants.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/detail/sub/panels/edge/attributes/EdgeParticipants.java @@ -69,7 +69,7 @@ protected void fillSummaryEdgeContent(SummaryEdge edge) { int thickness = edge.getNbSummarizedEdges() + 2; thickness = Integer.min(thickness, 25); - content.add(new EdgeDiagram(SummaryStyle.getColor(edge.miScore), thickness, false)); + content.add(new EdgeDiagram(SummaryStyle.getColor(edge.miScore), thickness, false, edge.isNegative())); } @Override @@ -103,7 +103,7 @@ protected void fillEvidenceEdgeContent(EvidenceEdge edge) { nodePanel.add(Box.createVerticalGlue(), layoutHelper.down().expandVert()); } - content.add(new EdgeDiagram(StyleMapper.edgeTypeToPaint.get(edge.type.value), 4, edge.expansionType != null && !edge.expansionType.isBlank())); + content.add(new EdgeDiagram(StyleMapper.edgeTypeToPaint.get(edge.type.value), 4, edge.expansionType != null && !edge.expansionType.isBlank(), edge.isNegative)); } private class ParticipantInfoPanel extends LinePanel { @@ -226,20 +226,25 @@ private class EdgeDiagram extends JComponent { Paint color; boolean dashed; int thickness; + Boolean isNegative; - public EdgeDiagram(Paint paint, int thickness, boolean dashed) { + public EdgeDiagram(Paint paint, int thickness, boolean dashed, Boolean isNegative) { this.color = paint; this.dashed = dashed; this.thickness = thickness; + this.isNegative = isNegative; } @Override public void paint(Graphics g) { super.paint(g); Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setPaint(color); + if (dashed) { - g2.setStroke(new BasicStroke(thickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, new float[]{6.0f, 5.0f}, 0)); + g2.setStroke(new BasicStroke(thickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{6.0f, 5.0f}, 0)); } else { g2.setStroke(new BasicStroke(thickness)); } @@ -247,7 +252,27 @@ public void paint(Graphics g) { Rectangle sourceBounds = sourceDiagram.getBounds(); Rectangle targetBounds = targetDiagram.getBounds(); int centerX = (int) sourceBounds.getCenterX(); - g2.drawLine(centerX, (int) sourceBounds.getCenterY() + 15, centerX, (int) targetBounds.getCenterY() - 15); + int y1 = (int) sourceBounds.getCenterY() + 15; + int y2 = (int) targetBounds.getCenterY() - 15; + + g2.drawLine(centerX, y1, centerX, y2); + + if (isNegative) { + String negation = "X"; + + int midY = (y1 + y2) / 2; + + FontMetrics fm = g2.getFontMetrics(); + int stringWidth = fm.stringWidth(negation); + int stringHeight = fm.getAscent(); + + int textX = centerX - stringWidth / 2; + int textY = midY + stringHeight / 2; + + g2.setPaint(Color.RED); + g2.drawString(negation, textX, textY); + } } + } } diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/filters/FilterPanel.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/filters/FilterPanel.java index 9092aa3b..c9c7bd9c 100644 --- a/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/filters/FilterPanel.java +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/filters/FilterPanel.java @@ -7,6 +7,7 @@ import uk.ac.ebi.intact.app.internal.model.filters.DiscreteFilter; import uk.ac.ebi.intact.app.internal.model.filters.ContinuousFilter; import uk.ac.ebi.intact.app.internal.model.filters.Filter; +import uk.ac.ebi.intact.app.internal.model.filters.edge.EdgePositiveFilter; import uk.ac.ebi.intact.app.internal.model.managers.Manager; import uk.ac.ebi.intact.app.internal.ui.components.buttons.HelpButton; import uk.ac.ebi.intact.app.internal.ui.components.panels.CollapsablePanel; @@ -61,6 +62,9 @@ public static FilterPanel createFilterPanel(Filter fil } else if (filter instanceof DiscreteFilter) { return new DiscreteFilterPanel<>(manager, (DiscreteFilter) filter); } else if (filter instanceof BooleanFilter) { + if (filter instanceof EdgePositiveFilter) { + return new ToggleFilterPanel<>(manager, (EdgePositiveFilter) filter); + } return new BooleanFilterPanel<>(manager, (BooleanFilter) filter); } return null; diff --git a/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/filters/ToggleFilterPanel.java b/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/filters/ToggleFilterPanel.java new file mode 100644 index 00000000..c1000eed --- /dev/null +++ b/src/main/java/uk/ac/ebi/intact/app/internal/ui/panels/filters/ToggleFilterPanel.java @@ -0,0 +1,99 @@ +package uk.ac.ebi.intact.app.internal.ui.panels.filters; + +import uk.ac.ebi.intact.app.internal.model.core.elements.Element; +import uk.ac.ebi.intact.app.internal.model.filters.BooleanFilter; +import uk.ac.ebi.intact.app.internal.model.filters.edge.EdgePositiveFilter; +import uk.ac.ebi.intact.app.internal.model.managers.Manager; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; + +public class ToggleFilterPanel extends FilterPanel> implements ChangeListener { + private final ButtonGroup group = new ButtonGroup(); + private final JToggleButton positiveButton = new JToggleButton("✔"); + private final JToggleButton negativeButton = new JToggleButton("❌"); + private final JToggleButton bothButton = new JToggleButton("✔/❌"); + + private final JLabel label = new JLabel(""); + + public ToggleFilterPanel(Manager manager, BooleanFilter filter) { + super(manager, filter); + buildButtons(); + + positiveButton.addChangeListener(this); + negativeButton.addChangeListener(this); + bothButton.addChangeListener(this); + + updateFilterUI(filter); + } + + private void buildButtons() { + group.add(positiveButton); + group.add(negativeButton); + group.add(bothButton); + setButtonsEnabled(); + + JPanel buttonPanel = getButtonPanel(); + content.add(buttonPanel, layoutHelper.down().expandHoriz()); + + positiveButton.addActionListener(e -> updateFilter(true, false)); + negativeButton.addActionListener(e -> updateFilter(false, true)); + bothButton.addActionListener(e -> updateFilter(false, false)); + } + + private JPanel getButtonPanel() { + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + buttonPanel.add(positiveButton); + buttonPanel.add(bothButton); + buttonPanel.add(negativeButton); + + buttonPanel.setBackground(new Color(251, 251, 251)); + + return buttonPanel; + } + + private void setButtonsEnabled() { + boolean isThereNegativeInteractions = ((EdgePositiveFilter) filter).areThereNegativeInteractions(); + boolean isTherePositiveInteractions = ((EdgePositiveFilter) filter).areTherePositiveInteractions(); + + negativeButton.setEnabled(isThereNegativeInteractions); + positiveButton.setEnabled(isTherePositiveInteractions); + bothButton.setEnabled(isThereNegativeInteractions && isTherePositiveInteractions); + } + + private void updateFilter(boolean showOnlyPositive, boolean showOnlyNegative) { + ((EdgePositiveFilter) filter).setPositiveHidden(showOnlyNegative); + ((EdgePositiveFilter) filter).setNegativeHidden(showOnlyPositive); + + boolean shouldEnableFilter = showOnlyPositive || showOnlyNegative; + filter.setStatus(shouldEnableFilter); + + positiveButton.setSelected(showOnlyPositive && !showOnlyNegative); + negativeButton.setSelected(!showOnlyPositive && showOnlyNegative); + bothButton.setSelected(!showOnlyPositive && !showOnlyNegative); + } + + @Override + protected void updateFilterUI(BooleanFilter filter) { + label.setText(filter.description); + setButtonsEnabled(); + + boolean isThereNegativeInteractions = ((EdgePositiveFilter) filter).areThereNegativeInteractions(); + boolean isTherePositiveInteractions = ((EdgePositiveFilter) filter).areTherePositiveInteractions(); + + negativeButton.setSelected(isThereNegativeInteractions && !isTherePositiveInteractions); + positiveButton.setSelected(isTherePositiveInteractions); + } + + @Override + public void stateChanged(ChangeEvent e) { + setListening(false); + boolean shouldEnableFilter = ((EdgePositiveFilter) filter).isPositiveHidden() || ((EdgePositiveFilter) filter).isNegativeHidden(); + filter.setStatus(shouldEnableFilter); + setListening(true); + } + +} diff --git a/src/tests/java/uk.ac.ebi.intact.app.internal/MIQLParserTest.java b/src/tests/java/uk.ac.ebi.intact.app.internal/MIQLParserTest.java new file mode 100644 index 00000000..7df2b10a --- /dev/null +++ b/src/tests/java/uk.ac.ebi.intact.app.internal/MIQLParserTest.java @@ -0,0 +1,144 @@ +package uk.ac.ebi.intact.app.internal.ui.components.query.advanced.tests; + +import org.testng.Assert; +import org.testng.annotations.Test; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.Field; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.MIQLParser; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components.Rule; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components.RuleComponent; +import uk.ac.ebi.intact.app.internal.ui.components.query.advanced.parser.components.RuleSet; + +import java.util.Objects; + +public class MIQLParserTest { + + MIQLParser parser = new MIQLParser(); + + @Test + public void testParseSimpleQuery() { + String TEST_STRING = "idA:1234 AND idB:5678"; + RuleSet parsedResult = parser.parseMIQL(TEST_STRING); + for (RuleComponent rule : parsedResult.getRules()) { + Assert.assertTrue(rule instanceof Rule); + Rule parsedRule = (Rule) rule; + + String entity = parsedRule.getEntity(); + String operator = parsedRule.getOperator(); + String miql = parsedRule.getMiql(); + String userInput1 = parsedRule.getUserInput1(); + + Assert.assertTrue(Objects.equals(userInput1, "1234") + || Objects.equals(userInput1, "5678")); + + Assert.assertTrue(Objects.equals(miql, Field.P_ID_A.getMiqlQuery()) + || Objects.equals(miql, Field.P_ID_B.getMiqlQuery())); + + Assert.assertTrue(Objects.equals(entity, Field.P_ID_A.getEntity()) + || Objects.equals(entity, Field.P_ID_B.getEntity())); + + Assert.assertEquals(operator, "="); + + } + Assert.assertEquals(parsedResult.rules.size(), 2); + } + + @Test + public void testParseQueryWithMultipleUserInputs() { + String TEST_STRING = "rdate:[2000 TO 2025]"; + RuleSet parsedResult = parser.parseMIQL(TEST_STRING); + for (RuleComponent rule : parsedResult.getRules()) { + Assert.assertTrue(rule instanceof Rule); + Rule parsedRule = (Rule) rule; + + String entity = parsedRule.getEntity(); + String operator = parsedRule.getOperator(); + String miql = parsedRule.getMiql(); + String userInput1 = parsedRule.getUserInput1(); + String userInput2 = parsedRule.getUserInput2(); + + Assert.assertTrue(Objects.equals(userInput1, "2000") && Objects.equals(userInput2, "2025")); + + Assert.assertEquals(Field.R_DATE.getMiqlQuery(), miql); + + Assert.assertEquals(Field.R_DATE.getEntity(), entity); + + Assert.assertEquals(operator, "∈"); + + } + Assert.assertEquals(parsedResult.rules.size(), 1); + } + + @Test + public void testParseQueryWithRuleSet() { + final String TEST_STRING = "rdate:[2000 TO 2025] AND (taxidHost:12345)"; + RuleSet parsedResult = parser.parseMIQL(TEST_STRING); + Assert.assertEquals(parsedResult.rules.size(), 2); + } + + @Test + public void testParseQueryWithNegation() { + final String TEST_STRING = "NOT idA:1234 AND idB:5678"; + RuleSet parsedResult = parser.parseMIQL(TEST_STRING); + + Rule negatedRule = parsedResult.getRule(0); + Assert.assertEquals(negatedRule.getOperator(), "≠"); + Assert.assertEquals(parsedResult.rules.size(), 2); + } + + @Test + public void testParseQueryWithIn() { + final String TEST_STRING = "idB:(5678)"; + RuleSet parsedResult = parser.parseMIQL(TEST_STRING); + + Rule negatedRule = parsedResult.getRule(0); + Assert.assertEquals(negatedRule.getOperator(), "in"); + Assert.assertEquals(parsedResult.rules.size(), 1); + } + + @Test + public void testParseQueryWithNotIn() { + final String TEST_STRING = "NOT idB:(5678)"; + RuleSet parsedResult = parser.parseMIQL(TEST_STRING); + + Rule negatedRule = parsedResult.getRule(0); + Assert.assertEquals(negatedRule.getOperator(), "not in"); + Assert.assertEquals(parsedResult.rules.size(), 1); + } + + @Test + public void testParseQueryWithNegationAndRuleset() { + String TEST_STRING = "NOT idA:IDA AND (interaction_id:(ID) OR pubid:PUBMEDID OR source:DATABASE)"; + RuleSet parsedResult = parser.parseMIQL(TEST_STRING); + Assert.assertEquals(parsedResult.rules.size(), 2); + } + + @Test + public void testParseQueryWithNestedRuleset() { + String TEST_STRING = "idA:IDA AND (interaction_id:ID OR pubid:PUBMEDID OR (source:DATABASE))"; + RuleSet fullQuery = parser.parseMIQL(TEST_STRING); + Assert.assertEquals(fullQuery.rules.size(), 2); + + RuleComponent parentRuleSet = fullQuery.getRules().get(1); + Assert.assertTrue(parentRuleSet instanceof RuleSet); + + Assert.assertEquals(((RuleSet) parentRuleSet).rules.size(), 3); + + RuleComponent childRuleSet = ((RuleSet) parentRuleSet).rules.get(2); + Assert.assertTrue(childRuleSet instanceof RuleSet); + } + + @Test + public void testParseQueryWithRuleSetAndIn(){ + String TEST_STRING = "idA:IDA AND (interaction_id:(ID) OR pubid:PUBMEDID OR (source:DATABASE))"; + RuleSet fullQuery = parser.parseMIQL(TEST_STRING); + Assert.assertEquals(fullQuery.rules.size(), 2); + + RuleComponent parentRuleSet = fullQuery.getRules().get(1); + Assert.assertTrue(parentRuleSet instanceof RuleSet); + + Assert.assertEquals(((RuleSet) parentRuleSet).rules.size(), 3); + + Assert.assertEquals(((RuleSet) parentRuleSet).rules.get(2).getRule(0).getOperator(), "in"); + } + +}