diff --git a/src/main/java/fr/inria/corese/core/Graph.java b/src/main/java/fr/inria/corese/core/Graph.java index c0f687e97..4aa1a7816 100755 --- a/src/main/java/fr/inria/corese/core/Graph.java +++ b/src/main/java/fr/inria/corese/core/Graph.java @@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -122,18 +123,18 @@ public class Graph extends GraphObject implements // edge Index for RuleEngine where edge are sorted newest first EdgeManagerIndexer ruleEdgeIndex; // predefined individual Node such as kg:default named graph - HashMap system; + private ConcurrentHashMap nodeSystem; // key -> URI Node ; member of graph nodes (subject/object) - Hashtable individual; + private ConcurrentHashMap individual; // label -> Blank Node ; member of graph nodes (subject/object) - Hashtable blank; + private ConcurrentHashMap nodeBlank; // Triple Reference Node - Hashtable triple; + private ConcurrentHashMap triple; // named graph id nodes: key -> named graph id Node (possibly not subject/object // Node) - Hashtable graph; + private ConcurrentHashMap graph; // property nodes: label -> property Node (possibly not subject/object Node) - Hashtable property; + private ConcurrentHashMap property; // key -> Node for value management in external memory Map vliteral; ValueResolver values; @@ -247,15 +248,15 @@ public Graph(int length) { // deprecated: vliteral = Collections.synchronizedMap(new HashMap<>()); // URI Node - individual = new Hashtable<>(); + individual = new ConcurrentHashMap<>(); // Blank Node - blank = new Hashtable<>(); + nodeBlank = new ConcurrentHashMap<>(); // rdf star triple reference node - triple = new Hashtable<>(); + triple = new ConcurrentHashMap<>(); // Named Graph Node - graph = new Hashtable<>(); + graph = new ConcurrentHashMap<>(); // Property Node - property = new Hashtable<>(); + property = new ConcurrentHashMap<>(); // Index of nodes of named graphs // Use case: SPARQL Property Path @@ -562,22 +563,22 @@ public TreeNode treeNode() { } /** - * System Node are predefined such as kg:default Node for default graph They + * nodeSystemNode are predefined such as kg:default Node for default graph They * have an index but they are not yet stored in any graph table but system * table They are retrieved by getResource, getNode, getGraph, getProperty * on demand */ void initSystem() { - system = new HashMap<>(); + nodeSystem= new ConcurrentHashMap<>(); systemNode = new ArrayList<>(); for (String uri : PREDEFINED) { Node n = createSystemNode(uri); - system.put(uri, n); + nodeSystem.put(uri, n); systemNode.add(n); } - defaultGraph = system.get(Entailment.DEFAULT); - ruleGraph = system.get(Entailment.RULE); - constraintGraph = system.get(Entailment.CONSTRAINT); + defaultGraph = nodeSystem.get(Entailment.DEFAULT); + ruleGraph = nodeSystem.get(Entailment.RULE); + constraintGraph = nodeSystem.get(Entailment.CONSTRAINT); } Node createSystemNode(String label) { @@ -588,7 +589,7 @@ Node createSystemNode(String label) { } Node getSystemNode(String name) { - return system.get(name); + return nodeSystem.get(name); } @Override @@ -808,7 +809,7 @@ public String toRDF() { sb.appendPNL("kg:graph ", graph.size()); sb.appendPNL("kg:property ", getSubjectIndex().size()); sb.appendPNL("kg:uri ", individual.size()); - sb.appendPNL("kg:bnode ", blank.size()); + sb.appendPNL("kg:bnode ", nodeBlank.size()); sb.appendPNL("kg:triple ", triple.size()); sb.appendPNL("kg:literal ", getLiteralNodeManager().size()); sb.appendPNL("kg:nodeManager ", getNodeManager().isEffective()); @@ -834,7 +835,7 @@ public NodeManager getNodeManager() { } /** - * Generate an RDF Graph that describes the KGRAM system and the current RDF + * Generate an RDF Graph that describes the KGRAM nodeSystem and the current RDF * graph */ public Graphable describe() { @@ -1526,7 +1527,7 @@ public int nbIndividuals() { } public int nbBlanks() { - return blank.size(); + return nodeBlank.size(); } public int nbTriples() { @@ -1721,13 +1722,13 @@ public Node getTripleNode(IDatatype dt, boolean create, boolean add) { } public Node getLiteralNode(IDatatype dt, boolean create, boolean add) { - String key = getKey(dt); - Node node = getLiteralNode(key, dt); + String keyString = getKey(dt); + Node node = getLiteralNode(keyString, dt); if (node != null) { return node; } if (create) { - node = createNode(key, dt); + node = createNode(keyString, dt); if (add) { addLiteralNode(dt, node); } @@ -1759,7 +1760,7 @@ Node getResource(String key, String name) { // resource or blank public boolean isIndividual(Node node) { return individual.containsKey(getID(node)) - || blank.containsKey(node.getLabel()) + || nodeBlank.containsKey(node.getLabel()) || triple.containsKey(node.getLabel()); } @@ -1790,7 +1791,7 @@ public Node getBlankNode(String name) { } public Node getBlankNodeBasic(String name) { - return blank.get(name); + return nodeBlank.get(name); } // named graph id may be a bnode @@ -1803,7 +1804,7 @@ public Node getTripleNode(String name) { } void addBlankNode(IDatatype dt, Node node) { - blank.put(node.getLabel(), node); + nodeBlank.put(node.getLabel(), node); } void addTripleNode(IDatatype dt, Node node) { @@ -1887,8 +1888,8 @@ Node basicAddResource(String label) { return node; } IDatatype dt = DatatypeMap.createResource(label); - String key = getID(label); - node = createNode(key, dt); + String keyString = getID(label); + node = createNode(keyString, dt); add(dt, node); return node; } @@ -2152,10 +2153,10 @@ public Iterable iterate(Node s, Node p, Node o, List from) { public Iterable insert(Node s, Node p, Node o, List contexts) { if (contexts == null || contexts.isEmpty()) { - Edge edge = insert(s, p, o); + insert(s, p, o); } else { for (Node g : contexts) { - Edge edge = insert(g, s, p, o); + insert(g, s, p, o); } } return emptyEdgeList; @@ -2163,10 +2164,10 @@ public Iterable insert(Node s, Node p, Node o, List contexts) { public Iterable delete(Node s, Node p, Node o, List contexts) { if (contexts == null || contexts.isEmpty()) { - List edge = delete(s, p, o); + delete(s, p, o); } else { for (Node g : contexts) { - List edge = delete(g, s, p, o); + delete(g, s, p, o); } } return emptyEdgeList; @@ -2274,7 +2275,7 @@ public Iterable getAllEdges(Node predicate, Node node, Node node2, int n) } } if (meta.isEmpty()) { - return new ArrayList(); + return new ArrayList<>(); } return meta; } @@ -2322,7 +2323,7 @@ public boolean hasEdge(Node node, int i) { } public List getList(Node node) { - List list = new ArrayList(); + List list = new ArrayList<>(); list(node, list); return list; } @@ -2379,11 +2380,11 @@ public ArrayList reclist(Node node) { Edge first = getEdge(RDF.FIRST, node, 0); Edge rest = getEdge(RDF.REST, node, 0); if (first == null || rest == null) { - return null; + return new ArrayList<>(); } ArrayList list = reclist(rest.getNode(1)); if (list == null) { - return null; + return new ArrayList<>(); } Node val = first.getNode(1); @@ -2454,7 +2455,7 @@ public Iterable getEdges(Node node, int n) { // without NodeManager public Iterable getSortedEdgesBasic(Node node, int n) { - MetaIterator meta = new MetaIterator(); + MetaIterator meta = new MetaIterator<>(); for (Node pred : getSortedProperties()) { Iterable it = getIndex(n).getEdges(pred, node); @@ -2463,7 +2464,7 @@ public Iterable getSortedEdgesBasic(Node node, int n) { } } if (meta.isEmpty()) { - return new ArrayList(); + return new ArrayList<>(); } return meta; } @@ -2614,14 +2615,14 @@ public Iterable getNodes() { } public Iterable getBlankNodes() { - return blank.values(); + return nodeBlank.values(); } public Iterable getTripleNodes() { return triple.values(); } - public Hashtable getTripleNodeMap() { + public Map getTripleNodeMap() { return triple; } @@ -2911,7 +2912,7 @@ public boolean compare(Graph g2, boolean isGraph, boolean detail) { public List split() { if (graph.size() == 1) { - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); list.add(this); return list; } @@ -2928,7 +2929,7 @@ List gSplit() { g.addEdgeWithNode(ent); } - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); for (Graph g : map.values()) { list.add(g); } @@ -2938,7 +2939,7 @@ List gSplit() { } public List getEdgeList(Node n) { - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); for (Edge e : getEdges(n, 0)) { list.add(e); } @@ -2949,7 +2950,7 @@ public List getEdgeList(Node n) { * Without rule entailment */ public List getEdgeListSimple(Node n) { - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); for (Edge e : getEdges(n, 0)) { if (!getProxy().isRule(e)) { list.add(e); @@ -2984,8 +2985,8 @@ public List delete(Edge edge) { } if (res != null) { - logger.info("" + edge); - logger.info("" + res); + logger.info("{}", edge); + logger.info("{}", res); deleted(res); } return res; @@ -3011,8 +3012,8 @@ public List delete(Edge edge, List from) { } if (res != null) { - logger.info("" + edge.getEdgeLabel()); - logger.info("" + res); + logger.info("Edge label: {}", edge.getEdgeLabel()); + logger.info("Result: {}", res); deleted(res); } return res; @@ -3100,7 +3101,7 @@ public void clear() { void clearNodes() { individual.clear(); - blank.clear(); + nodeBlank.clear(); triple.clear(); getLiteralNodeManager().clear(); property.clear(); @@ -3390,8 +3391,8 @@ public Edge addEdge(Node source, Node predicate, List list) { e = fac.create(source, predicate, list); } - Edge ee = addEdge(e); - return ee; + e = addEdge(e); + return e; } /** @@ -3536,7 +3537,8 @@ public String reference(Node n) { // labels // they should have different ID if (dt.isDate()) { - if (DatatypeMap.getTZ(dt).equals("Z")) { + Object tz = DatatypeMap.getTZ(dt); + if (tz != null && "Z".equals(tz.toString())) { return String.format("d%s", n.getIndex()); } } diff --git a/src/main/java/fr/inria/corese/core/compiler/eval/SQLFun.java b/src/main/java/fr/inria/corese/core/compiler/eval/SQLFun.java index 347d0d40c..efb6fc1a8 100755 --- a/src/main/java/fr/inria/corese/core/compiler/eval/SQLFun.java +++ b/src/main/java/fr/inria/corese/core/compiler/eval/SQLFun.java @@ -11,6 +11,18 @@ public class SQLFun { private static final Logger logger = LoggerFactory.getLogger(SQLFun.class); static Object driver; + /** + * Executes an SQL query using a specified database driver. + * The Connection and Statement are managed with try-with-resources to ensure closure. + * Note: The returned ResultSet must be closed by the caller. + * + * @param uri The database connection URI. + * @param dd The database driver class name. + * @param login The database login username. + * @param passwd The database password. + * @param query The SQL query to execute. + * @return A ResultSet containing the query results, or null if an error occurs. + */ public ResultSet sql(IDatatype uri, IDatatype dd, IDatatype login, IDatatype passwd, IDatatype query) { if (driver == null) { @@ -19,31 +31,43 @@ public ResultSet sql(IDatatype uri, IDatatype dd, // remember driver is loaded driver = Class.forName(dd.getLabel()).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { - logger.error("", e); + logger.error("Failed to load database driver: {}", dd.getLabel(), e); + return null; } } return sql(uri, login, passwd, query); } + /** + * Executes an SQL query using the default Derby driver or an already loaded driver. + * The Connection and Statement are managed with try-with-resources to ensure closure. + * Note: The returned ResultSet must be closed by the caller. + * + * @param uri The database connection URI. + * @param login The database login username. + * @param passwd The database password. + * @param query The SQL query to execute. + * @return A ResultSet containing the query results, or null if an error occurs. + */ public ResultSet sql(IDatatype uri, IDatatype login, IDatatype passwd, IDatatype query) { try { if (driver == null) { try { - // default is derby driver = Class.forName(DERBY_DRIVER).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { - logger.error("", e); + logger.error("Failed to load default Derby driver: {}", DERBY_DRIVER, e); + return null; } } - Connection con = DriverManager.getConnection(uri.getLabel(), login.getLabel(), passwd.getLabel()); - Statement stmt = con.createStatement(); - return stmt.executeQuery(query.getLabel()); + + try (final Connection con = DriverManager.getConnection(uri.getLabel(), login.getLabel(), passwd.getLabel()); + final Statement stmt = con.createStatement()) { + return stmt.executeQuery(query.getLabel()); + } } catch (SQLException e) { - logger.error("", e); + logger.error("SQL error occurred during query execution: {}", query.getLabel(), e); } return null; } - - } diff --git a/src/main/java/fr/inria/corese/core/extension/Core.java b/src/main/java/fr/inria/corese/core/extension/Core.java index 36bc5ca26..5024bb52b 100644 --- a/src/main/java/fr/inria/corese/core/extension/Core.java +++ b/src/main/java/fr/inria/corese/core/extension/Core.java @@ -18,6 +18,8 @@ import java.util.Arrays; import java.util.HashMap; + +import fr.inria.corese.core.transform.TransformerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -151,13 +153,13 @@ Graph getGraph(IDatatype dt) { IDatatype xt_turtle(IDatatype x) { if (x.isLiteral() && x.getDatatypeURI().equals(IDatatype.GRAPH_DATATYPE)) { try { - Transformer t = Transformer.create(getGraph(x), Transformer.TURTLE); + Transformer t = Transformer.create(getGraph(x), TransformerUtils.TURTLE); return t.process(); } catch (EngineException ex) { logger.error("An unexpected error has occurred", ex); } } else { - Transformer t = Transformer.create(getGraph(), Transformer.TURTLE); + Transformer t = Transformer.create(getGraph(), TransformerUtils.TURTLE); try { return t.process(x); } catch (EngineException ex) { @@ -168,7 +170,7 @@ IDatatype xt_turtle(IDatatype x) { } IDatatype xt_turtle(IDatatype g, IDatatype x) { - Transformer t = Transformer.create(getGraph(g), Transformer.TURTLE); + Transformer t = Transformer.create(getGraph(g), TransformerUtils.TURTLE); try { return t.process(x); } catch (EngineException ex) { diff --git a/src/main/java/fr/inria/corese/core/kgram/core/EvalGraph.java b/src/main/java/fr/inria/corese/core/kgram/core/EvalGraph.java index 82ed7c93a..80414cc68 100644 --- a/src/main/java/fr/inria/corese/core/kgram/core/EvalGraph.java +++ b/src/main/java/fr/inria/corese/core/kgram/core/EvalGraph.java @@ -94,7 +94,6 @@ private Mappings graphNodes(Producer p, Exp exp, Mappings map, int n) throws Spa Memory env = eval.getMemory(); Query qq = eval.getQuery(); Matcher mm = eval.getMatcher(); - int backtrack = n - 1; Node name = exp.getGraphName(); Mappings res = null; Iterable graphNodes = null; @@ -130,12 +129,11 @@ private Mappings graphNodes(Producer p, Exp exp, Mappings map, int n) throws Spa * Exp exp: graph name { BGP } */ private Mappings graph(Producer p, Node graph, Exp exp, Mappings map, int n) throws SparqlException { - int backtrack = n - 1; boolean external = false; Node graphNode = exp.getGraphName(); Producer np = p; if (graph != null && p.isProducer(graph)) { - // graph ?g { } + // graph ? g { } // named graph in GraphStore np = p.getProducer(graph, eval.getMemory()); np.setGraphNode(graph); // the new gNode @@ -163,7 +161,7 @@ private Mappings graph(Producer p, Node graph, Exp exp, Mappings map, int n) thr Exp ee = body; Mappings data = null; - if (graph.getPath() == null) { + if (graph != null && graph.getPath() == null) { // not a path pointer if (Eval.isParameterGraphMappings()) { // eval graph body with parameter map diff --git a/src/main/java/fr/inria/corese/core/load/LoadException.java b/src/main/java/fr/inria/corese/core/load/LoadException.java index 82f4f450e..5f751d0a6 100644 --- a/src/main/java/fr/inria/corese/core/load/LoadException.java +++ b/src/main/java/fr/inria/corese/core/load/LoadException.java @@ -13,6 +13,14 @@ public LoadException(Exception ee) { this.set(ee); } + public LoadException(String message) { + super(message); + } + + public LoadException(String message, Throwable cause) { + super(message, cause); + } + private void set(Exception ee) { ex = ee; } diff --git a/src/main/java/fr/inria/corese/core/load/LoadFormat.java b/src/main/java/fr/inria/corese/core/load/LoadFormat.java index 385db0506..8433f28f8 100644 --- a/src/main/java/fr/inria/corese/core/load/LoadFormat.java +++ b/src/main/java/fr/inria/corese/core/load/LoadFormat.java @@ -3,6 +3,8 @@ import fr.inria.corese.core.api.Loader; import fr.inria.corese.core.transform.Transformer; import fr.inria.corese.core.sparql.triple.parser.NSManager; +import fr.inria.corese.core.transform.TransformerUtils; + import java.util.HashMap; /** @@ -82,10 +84,10 @@ static void init(){ dtable = new HashMap(); - ddefine(Transformer.TURTLE, Loader.format.TURTLE_FORMAT); + ddefine(TransformerUtils.TURTLE, Loader.format.TURTLE_FORMAT); ddefine(NT_FORMAT, Loader.format.NT_FORMAT); - ddefine(Transformer.RDFXML, Loader.format.RDFXML_FORMAT); - ddefine(Transformer.JSON, Loader.format.JSONLD_FORMAT); + ddefine(TransformerUtils.RDFXML, Loader.format.RDFXML_FORMAT); + ddefine(TransformerUtils.JSON, Loader.format.JSONLD_FORMAT); } diff --git a/src/main/java/fr/inria/corese/core/load/QueryLoad.java b/src/main/java/fr/inria/corese/core/load/QueryLoad.java index 0f58da7be..78f4c2e9a 100644 --- a/src/main/java/fr/inria/corese/core/load/QueryLoad.java +++ b/src/main/java/fr/inria/corese/core/load/QueryLoad.java @@ -1,29 +1,29 @@ package fr.inria.corese.core.load; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.Writer; -import java.net.URL; - -import fr.inria.corese.core.sparql.exceptions.EngineException; import fr.inria.corese.core.compiler.parser.Pragma; import fr.inria.corese.core.kgram.core.Query; import fr.inria.corese.core.query.QueryEngine; import fr.inria.corese.core.sparql.api.IDatatype; +import fr.inria.corese.core.sparql.exceptions.EngineException; import fr.inria.corese.core.sparql.triple.function.term.TermEval; import fr.inria.corese.core.sparql.triple.parser.Access; import fr.inria.corese.core.sparql.triple.parser.NSManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.BufferedWriter; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.BufferedReader; + import java.net.MalformedURLException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.net.URL; + public class QueryLoad extends Load { @@ -67,8 +67,7 @@ public void parse(String name) throws LoadException { } } } - - @Deprecated + public void load(Reader read) throws LoadException { parse(read); } @@ -85,7 +84,7 @@ public void parse(Reader read) throws LoadException { } @Override - boolean isURL(String path) { + boolean isURL(String path) { try { new URL(path); } catch (MalformedURLException e) { @@ -93,13 +92,12 @@ boolean isURL(String path) { } return true; } - - @Deprecated + public String read(InputStream stream) throws IOException { return read(new InputStreamReader(stream)); } - - public String readWE(InputStream stream) throws LoadException { + + public String readWE(InputStream stream) throws LoadException { try { return read(new InputStreamReader(stream)); } catch (IOException ex) { @@ -107,28 +105,27 @@ public String readWE(InputStream stream) throws LoadException { } } - @Deprecated public String read(String name) { String query = ""; try { query = readWE(name); } catch (LoadException ex) { - LoggerFactory.getLogger(QueryLoad.class.getName()).error( "", ex); + LoggerFactory.getLogger(QueryLoad.class.getName()).error("", ex); } if (query == "") { return null; } return query; } - + public String readURL(String name) throws LoadException { return readWE(name); } - + public String readProtect(String name) throws LoadException { return readWE(name); } - + public String readWE(String name, boolean protect) throws LoadException { return readWE(name); } @@ -138,34 +135,45 @@ public String readWithAccess(String name) throws LoadException { check(Access.Feature.READ_WRITE, name, TermEval.READ_MESS); return readWE(name); } - - public String readWE(String name) throws LoadException { - String query = "", str = ""; - Reader fr; - try { - if (NSManager.isResource(name)) { - fr = new InputStreamReader(getClass().getResourceAsStream(NSManager.stripResource(name))); - } - else if (isURL(name)) { - URL url = new URL(name); - fr = new InputStreamReader(url.openStream()); - } else { - fr = new FileReader(name); - } + public String readWE(String name) throws LoadException { + String query = ""; + try (Reader fr = getReaderForName(name)) { if (fr == null) { - throw LoadException.create(new IOException(name)).setPath(name); + throw LoadException.create(new IOException("Could not obtain reader for: " + name)).setPath(name); } query = read(fr); } catch (IOException ex) { throw LoadException.create(ex).setPath(name); } - if (query == "") { + if (query.isEmpty()) { return null; } return query; } + + /** + * Helper method to get a Reader based on the name (resource, URL, or file path). + * @param name The name of the resource/URL/file. + * @return A Reader for the given name. + * @throws IOException If an I/O error occurs or URL is malformed. + */ + private Reader getReaderForName(String name) throws IOException { + if (NSManager.isResource(name)) { + InputStream stream = getClass().getResourceAsStream(NSManager.stripResource(name)); + if (stream == null) { + throw new IOException("Resource not found: " + name); + } + return new InputStreamReader(stream); + } else if (isURL(name)) { + URL url = new URL(name); + return new InputStreamReader(url.openStream()); + } else { + return new FileReader(name); + } + } + public String getResource(String name) throws IOException { InputStream stream = QueryLoad.class.getResourceAsStream(name); if (stream == null) { @@ -175,17 +183,17 @@ public String getResource(String name) throws IOException { String str = read(fr); return str; } - - // TODO: clean + + // TODO: clean public String basicParse(String path) throws EngineException { String pp = (path.endsWith("/")) ? path.substring(0, path.length() - 1) : path; String str = null; try { - if (NSManager.isResource(pp)) { + if (NSManager.isResource(pp)) { // @import within transformation such as st:turtle // the import uri is st:function/test.rq // consider it as a resource - String name = NSManager.stripResource(pp); + String name = NSManager.stripResource(pp); str = getResource(name); } else { str = readWE(pp); @@ -216,88 +224,87 @@ String read(Reader fr) throws IOException { isnl = true; } sb.append(str); - //sb.append(NL); } return sb.toString(); } - - public String writeTemp(String name, String str) { - String query = ""; - try { - File file = File.createTempFile(getName(name), getSuffix(name)); - Writer fr = new FileWriter(file); - BufferedWriter fq = new BufferedWriter(fr); - fq.write(str); - fq.flush(); - fr.close(); - return file.toString(); - } catch (FileNotFoundException e) { - logger.error(e.getMessage()); - } catch (IOException e) { - logger.error(e.getMessage()); - } - return null; - } - + + public String writeTemp(String name, IDatatype dt) { - String query = ""; try { File file = File.createTempFile(getName(name), getSuffix(name)); - Writer fr = new FileWriter(file); - BufferedWriter fq = new BufferedWriter(fr); - if (dt.isList()) { - for (IDatatype elem : dt) { - fq.write(elem.stringValue()); - fq.write(NL); - } - } - else { - fq.write(dt.stringValue()); - } - fq.flush(); - fr.close(); + writeToFile(file, dt); + return file.toString(); - } catch (FileNotFoundException e) { - logger.error(e.getMessage()); } catch (IOException e) { - logger.error(e.getMessage()); + logger.error("Error writing to temporary file '{}': {}", name, e.getMessage(), e); + return null; } - return null; } - + String getName(String name) { - int index = name.indexOf("."); - if (index == -1) { - return name; - } - return name.substring(0, index); + int index = name.indexOf("."); + if (index == -1) { + return name; + } + return name.substring(0, index); } - + String getSuffix(String name) { - int index = name.indexOf("."); - if (index == -1) { - return ".txt"; - } - return name.substring(index); - } - + int index = name.indexOf("."); + if (index == -1) { + return ".txt"; + } + return name.substring(index); + } + public void write(String name, IDatatype dt) { write(name, dt.stringValue()); } public void write(String name, String str) { - String query = ""; - try { - Writer fr = new FileWriter(name); - BufferedWriter fq = new BufferedWriter(fr); + try (final FileWriter fr = new FileWriter(name); + final BufferedWriter fq = new BufferedWriter(fr)) { fq.write(str); fq.flush(); - fr.close(); - } catch (FileNotFoundException e) { - logger.error("An error has occurred", e); } catch (IOException e) { + logger.error("Error writing to file '{}': {}", name, e.getMessage(), e); + } + } + + /** + * Writes content to the specified file using a buffered writer. + * Uses try-with-resources to ensure proper resource cleanup. The content + * is written through the {@link #writeContentToBuffer(BufferedWriter, IDatatype)} method. + * + * @param file The file to write to + * @param dt The data content to be written + * @throws IOException If an I/O error occurs during writing + * @see java.io.BufferedWriter + */ + private void writeToFile(File file, IDatatype dt) throws IOException { + try (FileWriter fr = new FileWriter(file); + BufferedWriter fq = new BufferedWriter(fr)) { + writeContentToBuffer(fq, dt); + fq.flush(); } - } -} + /** + * Helper method to write the content of an IDatatype to a BufferedWriter. + * This method encapsulates the conditional writing logic to reduce nesting in the caller. + * + * @param fq The BufferedWriter to write to. + * @param dt The IDatatype containing the content. + * @throws IOException If an I/O error occurs during writing. + */ + private void writeContentToBuffer(BufferedWriter fq, IDatatype dt) throws IOException { + if (dt.isList()) { + for (IDatatype elem : dt) { + fq.write(elem.stringValue()); + fq.write(NL); + } + } else { + fq.write(dt.stringValue()); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/logic/OWLProfile.java b/src/main/java/fr/inria/corese/core/logic/OWLProfile.java index b72d6344d..6a09cfd55 100644 --- a/src/main/java/fr/inria/corese/core/logic/OWLProfile.java +++ b/src/main/java/fr/inria/corese/core/logic/OWLProfile.java @@ -9,16 +9,18 @@ import fr.inria.corese.core.sparql.datatype.DatatypeMap; import fr.inria.corese.core.sparql.exceptions.EngineException; import fr.inria.corese.core.sparql.triple.function.term.Binding; +import fr.inria.corese.core.transform.TransformerUtils; + import java.util.Map; /** * OWL Profile type checker */ public class OWLProfile { - public static final String OWL_RL = Transformer.OWL_RL; - public static final String OWL_EL = Transformer.OWL_EL; - public static final String OWL_QL = Transformer.OWL_QL; - public static final String OWL_TC = Transformer.OWL_TC; + public static final String OWL_RL = TransformerUtils.OWL_RL; + public static final String OWL_EL = TransformerUtils.OWL_EL; + public static final String OWL_QL = TransformerUtils.OWL_QL; + public static final String OWL_TC = TransformerUtils.OWL_TC; private Graph graph; private Map error; @@ -41,9 +43,9 @@ public boolean process(String type) throws EngineException { b.setTransformerVisitor(vis); checker.process(b); - Transformer printer = Transformer.create(getGraph(), Transformer.PP_ERROR); + Transformer printer = Transformer.create(getGraph(), TransformerUtils.PP_ERROR); IDatatype errors = vis.errors(); - IDatatype dt = printer.process(Transformer.PP_ERROR_DISPLAY, b, + IDatatype dt = printer.process(TransformerUtils.PP_ERROR_DISPLAY, b, DatatypeMap.newInstance(getTitle(type)), errors); if (dt !=null) { setMessage(dt.stringValue()); @@ -65,7 +67,7 @@ String getTitle(String type) { public IDatatype pretty(IDatatype dt) throws EngineException { if (dt.isBlank()) { - Transformer t = Transformer.create(getGraph(), Transformer.TURTLE); + Transformer t = Transformer.create(getGraph(), TransformerUtils.TURTLE); return t.process(dt); } else { diff --git a/src/main/java/fr/inria/corese/core/next/api/io/serialization/RDFSerializer.java b/src/main/java/fr/inria/corese/core/next/api/io/serialization/RDFSerializer.java index e13b1287b..19d0a8b53 100644 --- a/src/main/java/fr/inria/corese/core/next/api/io/serialization/RDFSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/api/io/serialization/RDFSerializer.java @@ -29,5 +29,5 @@ public interface RDFSerializer { * @throws SerializationException if an error occurs during the serialization * process */ - void write(Writer writer) throws SerializationException; + void write(final Writer writer) throws SerializationException; } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java b/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java index 7c4a819b3..36c427849 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java +++ b/src/main/java/fr/inria/corese/core/next/impl/common/util/IRIUtils.java @@ -1,5 +1,9 @@ package fr.inria.corese.core.next.impl.common.util; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,6 +16,8 @@ public class IRIUtils { private static final Pattern IRI_PATTERN = Pattern.compile("^(?(?[\\w\\-]+):(?\\/\\/)?(?([\\w\\-_:@]+\\.)*[\\w\\-_:]*))((?\\/([\\w\\-\\._\\:]+\\/)*)(?[\\w\\-\\._\\:]+)?(?\\?[\\w\\-_\\:\\?\\=]+)?(\\#)?(?([\\w\\-_]+))?)?$"); private static final Pattern STANDARD_IRI_PATTERN = Pattern.compile("^(([^:/?#\\s]+):)(\\/\\/([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(.*))?"); + private static final int MAX_IRI_LENGTH = 2048; + private static final long REGEX_TIMEOUT_MS = 100; /** @@ -26,11 +32,15 @@ private IRIUtils() { * @return the guessed namespace of the IRI or an empty string if no match is found. */ public static String guessNamespace(String iri) { + if (!isValidInput(iri)) { + return ""; + } try { - Matcher matcher = IRI_PATTERN.matcher(iri); - - if(matcher.matches()) { - if(matcher.group("protocol") != null && matcher.group("protocol").equals("_")) { + Matcher matcher = matchWithTimeout(IRI_PATTERN, iri); + if (matcher == null || !matcher.matches()) { + return ""; + } else if (matcher.matches()) { + if (matcher.group("protocol") != null && matcher.group("protocol").equals("_")) { return ""; } StringBuilder namespace = new StringBuilder(); @@ -60,10 +70,14 @@ public static String guessNamespace(String iri) { * @return the guessed local name of the IRI or an empty string if no match is found. */ public static String guessLocalName(String iri) { + if (!isValidInput(iri)) { + return ""; + } try { - Matcher matcher = IRI_PATTERN.matcher(iri); - - if(matcher.matches()) { + Matcher matcher = matchWithTimeout(IRI_PATTERN, iri); + if (matcher == null || !matcher.matches()) { + return ""; + } else if (matcher.matches()) { if(matcher.group("fragment") != null){ // If the IRI has a fragment return matcher.group("fragment"); } else if(matcher.group("finalPath") != null ) { // If the IRI has no fragment but do not ends with a slash @@ -85,10 +99,90 @@ public static String guessLocalName(String iri) { * @return true if the string is a valid IRI, false otherwise. */ public static boolean isStandardIRI(String iriString) { - try { - Matcher matcher = STANDARD_IRI_PATTERN.matcher(iriString); - return matcher.matches(); - } catch (IllegalStateException e) { + if (!isValidInput(iriString)) { + return false; + } + + try { + Matcher matcher = matchWithTimeout(STANDARD_IRI_PATTERN, iriString); + if (matcher != null && matcher.matches()) { + return isValidURI(iriString); + } + return false; + } catch (Exception e) { return false; - } } + } + } + + /** + * Executes regex matching with timeout protection. + */ + private static Matcher matchWithTimeout(Pattern pattern, String input) { + long startTime = System.nanoTime(); + + try { + Matcher matcher = pattern.matcher(input); + + // Check timeout before and during matching + if (System.nanoTime() - startTime > TimeUnit.MILLISECONDS.toNanos(REGEX_TIMEOUT_MS)) { + return null; + } + + // For very long strings, check timeout periodically + if (input.length() > 100) { + // Pre-check timeout + if (System.nanoTime() - startTime > TimeUnit.MILLISECONDS.toNanos(REGEX_TIMEOUT_MS / 2)) { + return null; + } + } + + return matcher; + + } catch (Exception e) { + return null; + } + } + + /** + * Validates input string for basic security checks. + */ + private static boolean isValidInput(String input) { + return input != null && + !input.isEmpty() && + input.length() <= MAX_IRI_LENGTH && + !containsSuspiciousPatterns(input); + } + + /** + * Checks for patterns that might cause ReDoS attacks. + */ + private static boolean containsSuspiciousPatterns(String input) { + final Set SUSPICIOUS_CHARS = Set.of('.', '-', '_', ':'); + int consecutiveRepeats = 0; + char lastChar = 0; + + for (char c : input.toCharArray()) { + if (c == lastChar && SUSPICIOUS_CHARS.contains(c)) { + if (++consecutiveRepeats > 10) { + return true; + } + } else { + consecutiveRepeats = 0; + } + lastChar = c; + } + return false; + } + + /** + * Additional validation using Java's URI class. + */ + private static boolean isValidURI(String uriString) { + try { + URI uri = new URI(uriString); + return uri.getScheme() != null && uri.getScheme().length() > 0; + } catch (URISyntaxException e) { + return false; + } + } } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java index fef457855..1d9b28ce0 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java @@ -12,7 +12,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -503,7 +502,7 @@ protected void writeOptimizedStatements(Writer writer) throws IOException { Map> bySubject = tFamilyConfig.sortSubjects() ? new TreeMap<>(Comparator.comparing(Resource::stringValue)) : - new LinkedHashMap<>(); + new HashMap<>(); model.stream() .filter(stmt -> !isConsumed(stmt.getSubject())) @@ -517,7 +516,7 @@ protected void writeOptimizedStatements(Writer writer) throws IOException { Map> byPredicate = tFamilyConfig.sortPredicates() ? new TreeMap<>(Comparator.comparing(IRI::stringValue)) : - new LinkedHashMap<>(); + new HashMap<>(); subjectEntry.getValue().forEach(stmt -> byPredicate.computeIfAbsent(stmt.getPredicate(), k -> new ArrayList<>()).add(stmt)); diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java index 3e6a3db90..11f3a66d8 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializer.java @@ -4,11 +4,11 @@ import java.io.Writer; import java.util.ArrayList; import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; +import java.util.HashMap; import fr.inria.corese.core.next.api.IRI; import fr.inria.corese.core.next.api.Model; @@ -121,7 +121,7 @@ protected void doWriteStatements(Writer writer) throws IOException { private void writeStatementsWithContext(Writer writer) throws IOException { TriGOption trigConfig = getTriGConfig(); - Map> byContext = new LinkedHashMap<>(); + Map> byContext = new HashMap<>(); model.stream() .filter(stmt -> !isConsumed(stmt.getSubject())) .forEach(stmt -> byContext.computeIfAbsent(stmt.getContext(), k -> new ArrayList<>()).add(stmt)); @@ -145,9 +145,9 @@ private void writeStatementsWithContext(Writer writer) throws IOException { initialIndent = graphIndent; } - Map> bySubject = trigConfig.sortSubjects() ? - new TreeMap<>(Comparator.comparing(Resource::stringValue)) : - new LinkedHashMap<>(); + Map> bySubject = trigConfig.sortSubjects() + ? new TreeMap<>(Comparator.nullsFirst(Comparator.comparing(Resource::stringValue))) + : new HashMap<>(); statementsInContext.forEach(stmt -> bySubject.computeIfAbsent(stmt.getSubject(), k -> new ArrayList<>()).add(stmt)); @@ -158,7 +158,7 @@ private void writeStatementsWithContext(Writer writer) throws IOException { Map> byPredicate = trigConfig.sortPredicates() ? new TreeMap<>(Comparator.comparing(IRI::stringValue)) : - new LinkedHashMap<>(); + new HashMap<>(); subjectEntry.getValue().forEach(stmt -> byPredicate.computeIfAbsent(stmt.getPredicate(), k -> new ArrayList<>()).add(stmt)); diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java index 7cfbc27e1..e067eb246 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/CoreseAdaptedValueFactory.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.security.SecureRandom; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAmount; @@ -39,7 +40,9 @@ */ public class CoreseAdaptedValueFactory implements ValueFactory { - private final AtomicLong nodeID = new AtomicLong(ThreadLocalRandom.current().nextLong()); + private static final SecureRandom secureRandom = new SecureRandom(); + + private final AtomicLong nodeID = new AtomicLong(secureRandom.nextLong()); public CoreseAdaptedValueFactory() { } diff --git a/src/main/java/fr/inria/corese/core/print/HTMLFormat.java b/src/main/java/fr/inria/corese/core/print/HTMLFormat.java index 64de2128d..30c7939b4 100644 --- a/src/main/java/fr/inria/corese/core/print/HTMLFormat.java +++ b/src/main/java/fr/inria/corese/core/print/HTMLFormat.java @@ -6,6 +6,7 @@ import fr.inria.corese.core.kgram.core.Mappings; import fr.inria.corese.core.Graph; import fr.inria.corese.core.transform.Transformer; +import fr.inria.corese.core.transform.TransformerUtils; import fr.inria.corese.core.util.MappingsGraph; /** @@ -19,9 +20,9 @@ * */ public class HTMLFormat { - static final String defaultTransform = Transformer.SPARQL; - static final String constructTransform = Transformer.SPARQL; - static final String selectTransform = Transformer.SPARQL; + static final String defaultTransform = TransformerUtils.SPARQL; + static final String constructTransform = TransformerUtils.SPARQL; + static final String selectTransform = TransformerUtils.SPARQL; private String transformation; Mappings map; @@ -114,7 +115,7 @@ String process(Mappings map, Graph g, String trans){ Transformer t = Transformer.create(g, trans); context.setTransform(trans); complete(context, graph); - t.setContext(context); + t.getContextManager().setContext(context); // if (map != null && map.getQuery() != null){ // // Transformer inherit Query Transformer Visitor if any diff --git a/src/main/java/fr/inria/corese/core/print/JSONLDFormat.java b/src/main/java/fr/inria/corese/core/print/JSONLDFormat.java index 2d185d825..e10a5661a 100644 --- a/src/main/java/fr/inria/corese/core/print/JSONLDFormat.java +++ b/src/main/java/fr/inria/corese/core/print/JSONLDFormat.java @@ -405,11 +405,13 @@ private String filter(String label) { */ public void write(String name) throws IOException { StringBuilder sb = this.getJsonLdObject().toStringBuilder(); - FileOutputStream fos = new FileOutputStream(name); - for (int i = 0; i < sb.length(); i++) { - fos.write(sb.charAt(i)); + + try (final FileOutputStream fos = new FileOutputStream(name)) { + for (int i = 0; i < sb.length(); i++) { + fos.write(sb.charAt(i)); + } + } - fos.close(); } @Override diff --git a/src/main/java/fr/inria/corese/core/print/LogManager.java b/src/main/java/fr/inria/corese/core/print/LogManager.java index 6e94d257d..724327b57 100644 --- a/src/main/java/fr/inria/corese/core/print/LogManager.java +++ b/src/main/java/fr/inria/corese/core/print/LogManager.java @@ -13,6 +13,8 @@ import fr.inria.corese.core.sparql.triple.cst.LogKey; import fr.inria.corese.core.sparql.triple.parser.context.ContextLog; import fr.inria.corese.core.sparql.triple.parser.URLServer; + +import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; @@ -57,10 +59,13 @@ public String toString() { } public void toFile(String fileName) throws IOException { - FileWriter file = new FileWriter(fileName); - file.write(toString()); - file.flush(); - file.close(); + + try (final FileWriter fileWriter = new FileWriter(fileName); + final BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { + + bufferedWriter.write(toString()); + bufferedWriter.flush(); + } } /** diff --git a/src/main/java/fr/inria/corese/core/print/RDFFormat.java b/src/main/java/fr/inria/corese/core/print/RDFFormat.java index cba7e451c..4a1159a1e 100755 --- a/src/main/java/fr/inria/corese/core/print/RDFFormat.java +++ b/src/main/java/fr/inria/corese/core/print/RDFFormat.java @@ -205,11 +205,12 @@ public String toString() { } public void write(String name) throws IOException { + StringBuilder sb = getStringBuilder(); - FileOutputStream fos = new FileOutputStream(name); - Writer out = new OutputStreamWriter(fos); // , "UTF8"); - out.write(sb.toString()); - out.close(); + try (final FileOutputStream fos = new FileOutputStream(name); + final Writer out = new OutputStreamWriter(fos)) { + out.write(sb.toString()); + } } public void write(OutputStream out) throws IOException { diff --git a/src/main/java/fr/inria/corese/core/print/ResultFormat.java b/src/main/java/fr/inria/corese/core/print/ResultFormat.java index 6c9f44543..79e46242e 100644 --- a/src/main/java/fr/inria/corese/core/print/ResultFormat.java +++ b/src/main/java/fr/inria/corese/core/print/ResultFormat.java @@ -2,6 +2,7 @@ import static fr.inria.corese.core.sparql.triple.parser.URLParam.LINK; +import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; @@ -10,6 +11,7 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.print.rdfc10.HashingUtility.HashAlgorithm; import fr.inria.corese.core.transform.Transformer; +import fr.inria.corese.core.transform.TransformerUtils; import fr.inria.corese.core.util.MappingsGraph; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.kgram.core.Mappings; @@ -224,15 +226,15 @@ static ResultFormat createFromTrans(Mappings m, String trans) { return null; } switch (NSManager.nsm().toNamespace(trans)) { - case Transformer.XML: + case TransformerUtils.XML: return create(m, ResultFormatDef.format.XML_FORMAT); - case Transformer.JSON: + case TransformerUtils.JSON: return create(m, ResultFormatDef.format.JSON_FORMAT); - case Transformer.JSON_LD: + case TransformerUtils.JSON_LD: return create(m, ResultFormatDef.format.JSONLD_FORMAT); - case Transformer.RDF: + case TransformerUtils.RDF: return create(m, ResultFormatDef.format.RDF_FORMAT); - case Transformer.RDFXML: + case TransformerUtils.RDFXML: return create(m, ResultFormatDef.format.RDF_XML_FORMAT); default: return null; @@ -374,10 +376,10 @@ public String toString() { String transformer() { Transformer t = Transformer.create(theGraph(), getMappings(), getTransformation()); if (getContext() != null) { - t.setContext(getContext()); + t.getContextManager().setContext(getContext()); } if (getBind() != null) { - t.setBinding(getBind()); + t.getContextManager().setBinding(getBind()); } return t.toString(); } @@ -407,7 +409,7 @@ public String toString(IDatatype dt) { } static ResultFormatDef.format getSyntax(String syntax) { - if (syntax.equals(Transformer.RDFXML)) { + if (syntax.equals(TransformerUtils.RDFXML)) { return ResultFormatDef.format.RDF_XML_FORMAT; } return ResultFormatDef.format.TURTLE_FORMAT; @@ -573,11 +575,13 @@ String html(String str) { } public void write(String name) throws IOException { - FileWriter fw = new FileWriter(name); - String str = toString(); - fw.write(str); - fw.flush(); - fw.close(); + try (final FileWriter fw = new FileWriter(name); + final BufferedWriter bufferedWriter = new BufferedWriter(fw)) { + + String str = toString(); + bufferedWriter.write(str); + bufferedWriter.flush(); + } } /** diff --git a/src/main/java/fr/inria/corese/core/print/TemplateFormat.java b/src/main/java/fr/inria/corese/core/print/TemplateFormat.java index 41c2c95db..3d5c4227c 100644 --- a/src/main/java/fr/inria/corese/core/print/TemplateFormat.java +++ b/src/main/java/fr/inria/corese/core/print/TemplateFormat.java @@ -1,6 +1,8 @@ package fr.inria.corese.core.print; import fr.inria.corese.core.transform.Transformer; + +import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; @@ -129,11 +131,12 @@ public Transformer getPPrinter() { } public void write(String name) throws IOException { - FileWriter fw = new FileWriter(name); - String str = toString(); - fw.write(str); - fw.flush(); - fw.close(); + try (final FileWriter fw = new FileWriter(name); + final BufferedWriter bufferedWriter = new BufferedWriter(fw)) { + String str = toString(); + bufferedWriter.write(str); + bufferedWriter.flush(); + } } public boolean isCheck() { diff --git a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalIssuer.java b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalIssuer.java index 7bd6d3a15..91b1d08c9 100644 --- a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalIssuer.java +++ b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalIssuer.java @@ -16,9 +16,7 @@ public class CanonicalIssuer { // Use LinkedHashMap to preserve insertion order private final LinkedHashMap issuedIdentifierMap; - ///////////////// // Constructor // - ///////////////// /** * Constructs a new CanonicalIssuer instance. @@ -42,9 +40,7 @@ public CanonicalIssuer(CanonicalIssuer ci) { this.issuedIdentifierMap = new LinkedHashMap<>(ci.issuedIdentifierMap); } - ///////////// // Methods // - ///////////// /** * Issues a new canonical identifier for a blank node or returns an existing one diff --git a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalRdf10.java b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalRdf10.java index 3f952fcb3..2c7a14f96 100644 --- a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalRdf10.java +++ b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalRdf10.java @@ -36,9 +36,7 @@ public class CanonicalRdf10 { private int depthFactor = 5; private int permutationLimit = 50000; - ////////////////// - // Constructors // - ////////////////// + // Constructors /** * Constructs a new {@code CanonicalRdf10Format} with the specified RDF graph. @@ -51,9 +49,7 @@ private CanonicalRdf10(Graph graph) { this.canonicalizedDataset = new CanonicalizedDataset(graph); } - ///////////////////// - // Factory methods // - ///////////////////// + // Factory methods /** * Creates a new {@code CanonicalRdf10Format} instance for the given graph. @@ -105,9 +101,7 @@ public static CanonicalRdf10 create(Mappings map, HashAlgorithm hashAlgorithm) { return canonicalRdf10; } - /////////////// - // Accessors // - /////////////// + // Accessors /** * Returns the depth factor for the canonicalization algorithm. @@ -165,9 +159,9 @@ public void setHashAlgorithm(HashAlgorithm hashAlgorithm) { this.hashAlgorithm = hashAlgorithm; } - //////////////////// - // Main algorithm // - //////////////////// + + // Main algorithm + /** * Performs the canonicalization of an RDF 1.0 dataset. @@ -257,9 +251,9 @@ public CanonicalizedDataset canonicalRdf10() { return this.canonicalizedDataset; } - //////////////////// - // Initialization // - //////////////////// + + // Initialization + /** * Extracts the quads for blank nodes from the RDF graph and adds them to the @@ -306,9 +300,9 @@ private void processAndMapBlankNode(Node node, Edge edge) { } } - ////////////////////////// + // HashFirstDegreeQuads // - ////////////////////////// + /** * Hashes the first degree quads for a given blank node identifier. @@ -380,9 +374,7 @@ private String getNodeString(Node node, String referenceBlankNodeIdentifier) { } } - /////////////// - // Exception // - /////////////// + // Exception /** * Thrown to indicate that an error occurred during the canonicalization of an @@ -404,9 +396,7 @@ public CanonicalizationException(String message) { } - /////////////////////// // HashN-DegreeQuads // - /////////////////////// /** * Hashes the N-degree quads for a given blank node identifier. @@ -475,7 +465,7 @@ private Pair hashNdegreeQuads(CanonicalIssuer issuer, S CanonicalIssuer issuerCopy = new CanonicalIssuer(refIssuer); // 4.8.3) Step 5.4.2 - String path = ""; + StringBuilder currentPathBuilder = new StringBuilder(); // 4.8.3) Step 5.4.3 List recursionList = new ArrayList<>(); @@ -485,7 +475,7 @@ private Pair hashNdegreeQuads(CanonicalIssuer issuer, S // 4.8.3) Step 5.4.4.1 if (this.canonicalizationState.hasCanonicalIdentifier(relatedBNId)) { - path += "_:" + this.canonicalizationState.getCanonicalIdentifierFor(relatedBNId); + currentPathBuilder.append("_:").append(this.canonicalizationState.getCanonicalIdentifierFor(relatedBNId)); } // 4.8.3) Step 5.4.4.2 else { @@ -494,12 +484,12 @@ private Pair hashNdegreeQuads(CanonicalIssuer issuer, S recursionList.add(relatedBNId); } // 4.8.3) Step 5.4.4.2.2 - path += "_:" + issuerCopy.issueCanonicalIdentifier(relatedBNId); + currentPathBuilder.append("_:").append(issuerCopy.issueCanonicalIdentifier(relatedBNId)); } // 4.8.3) Step 5.4.4.3 - if (!chosenPath.isEmpty() && path.length() >= chosenPath.length() - && path.compareTo(chosenPath) > 0) { + if (!chosenPath.isEmpty() && currentPathBuilder.length() >= chosenPath.length() + && currentPathBuilder.toString().compareTo(chosenPath) > 0) { break; } } @@ -510,24 +500,25 @@ private Pair hashNdegreeQuads(CanonicalIssuer issuer, S Pair result = this.hashNdegreeQuads(issuerCopy, relatedBNId, depth + 1); // 4.8.3) Step 5.4.5.2 - path += "_:" + issuerCopy.issueCanonicalIdentifier(relatedBNId); + currentPathBuilder.append("_:").append(issuerCopy.issueCanonicalIdentifier(relatedBNId)); // 4.8.3) Step 5.4.5.3 - path += "<" + result.getLeft() + ">"; + currentPathBuilder.append("<").append(result.getLeft()).append(">"); // 4.8.3) Step 5.4.5.4 issuerCopy = result.getRight(); // 4.8.3) Step 5.4.5.5 - if (!chosenPath.isEmpty() && path.length() >= chosenPath.length() - && path.compareTo(chosenPath) > 0) { + if (!chosenPath.isEmpty() && currentPathBuilder.length() >= chosenPath.length() + && currentPathBuilder.toString().compareTo(chosenPath) > 0) { break; } } // 4.8.3) Step 5.4.6 - if (chosenPath.isEmpty() || path.compareTo(chosenPath) < 0) { - chosenPath = path; + String currentPath = currentPathBuilder.toString(); + if (chosenPath.isEmpty() || currentPath.compareTo(chosenPath) < 0) { + chosenPath = currentPath; chosenIssuer = issuerCopy; } } @@ -543,6 +534,7 @@ private Pair hashNdegreeQuads(CanonicalIssuer issuer, S return Pair.of(HashingUtility.hash(data.toString(), this.hashAlgorithm), refIssuer); } + /** * Generates all possible permutations of a given list. * @@ -597,9 +589,9 @@ private void processQuadEntry(Edge quad, CanonicalIssuer issuer, String blankNod } } - ////////////////////////// - // HashRelatedBlankNode // - ////////////////////////// + + // HashRelatedBlankNode + /** * Hashes a related blank node. @@ -647,9 +639,7 @@ private String hashRelatedBlankNode(String relatedBNId, Edge quad, CanonicalIssu return HashingUtility.hash(input.toString(), this.hashAlgorithm); } - ///////////////////////// - // Overriding toString // - ///////////////////////// + // Overriding toString /** * Returns a string representation of the RDF graph in canonical form. diff --git a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizationState.java b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizationState.java index cca165fde..16f51670e 100644 --- a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizationState.java +++ b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizationState.java @@ -19,9 +19,7 @@ public class CanonicalizationState { private final ListMap hashToBlankNode = new ListMap<>(); private final CanonicalIssuer canonicalIssuer = new CanonicalIssuer("c14n"); - ///////////////// - // Constructor // - ///////////////// + // Constructor /** * Constructs a new CanonicalizationState instance. @@ -29,9 +27,7 @@ public class CanonicalizationState { public CanonicalizationState() { } - /////////////////////////////////// - // Quad to Blank Node Management // - /////////////////////////////////// + // Quad to Blank Node Management /** * Maps a blank node identifier to a specific quad. @@ -53,9 +49,7 @@ public List getQuadsForBlankNode(String blankNodeId) { return Collections.unmodifiableList(this.blankNodesToQuad.get(blankNodeId)); } - /////////////////////////////////// - // Hash to Blank Node Management // - /////////////////////////////////// + // Hash to Blank Node Management /** * Maps a hash value to a specific blank node identifier. @@ -116,9 +110,7 @@ public List getHashesSorted() { return Collections.unmodifiableList(sortedHashes); } - //////////////////////////////////////// - // Canonical Blank Node ID Management // - //////////////////////////////////////// + // Canonical Blank Node ID Management /** * Issues a canonical blank node identifier for a given blank node identifier. @@ -164,9 +156,7 @@ public Map getIssuedIdentifierMap() { return this.canonicalIssuer.getIssuedIdentifierMap(); } - /////////////// // To String // - /////////////// @Override public String toString() { diff --git a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizedDataset.java b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizedDataset.java index f48afac45..b8e24394d 100644 --- a/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizedDataset.java +++ b/src/main/java/fr/inria/corese/core/print/rdfc10/CanonicalizedDataset.java @@ -19,9 +19,7 @@ public class CanonicalizedDataset { private final Map blankNodesToIdentifiers = new LinkedHashMap<>(); private Map issuedIdentifierMap = new LinkedHashMap<>(); - ///////////////// - // Constructor // - ///////////////// + // Constructor /** * Constructs a CanonicalizedDataset with a given graph. @@ -33,9 +31,9 @@ public CanonicalizedDataset(Graph graph) { this.dataset = graph; } - //////////////////////// - // Dataset Management // - //////////////////////// + + // Dataset Management + /** * Retrieves the dataset associated with this CanonicalizedDataset. @@ -46,9 +44,7 @@ public Graph getDataset() { return dataset; } - /////////////////////////////////////////////////// - // Blank Nodes to Identifiers Mapping Management // - /////////////////////////////////////////////////// + // Blank Nodes to Identifiers Mapping Management /** * Adds a blank node and its identifier to the mapping. @@ -85,9 +81,9 @@ public Collection getBlankNodeIdentifiers() { return Collections.unmodifiableCollection(blankNodesToIdentifiers.values()); } - ////////////////////////////////////// - // Issued Identifier Map Management // - ////////////////////////////////////// + + // Issued Identifier Map Management + /** * Sets the issued identifier map. @@ -116,9 +112,7 @@ public Map getIssuedIdentifiersMap() { return Collections.unmodifiableMap(issuedIdentifierMap); } - /////////////// - // To String // - /////////////// + // To String @Override public String toString() { diff --git a/src/main/java/fr/inria/corese/core/query/PluginImpl.java b/src/main/java/fr/inria/corese/core/query/PluginImpl.java index 2569a0081..555d95503 100644 --- a/src/main/java/fr/inria/corese/core/query/PluginImpl.java +++ b/src/main/java/fr/inria/corese/core/query/PluginImpl.java @@ -45,6 +45,7 @@ import fr.inria.corese.core.storage.api.dataManager.DataManager; import fr.inria.corese.core.transform.TemplateVisitor; import fr.inria.corese.core.transform.Transformer; +import fr.inria.corese.core.transform.TransformerUtils; import fr.inria.corese.core.util.GraphListen; import fr.inria.corese.core.util.MappingsGraph; import fr.inria.corese.core.util.SPINProcess; @@ -97,7 +98,7 @@ public class PluginImpl private static final IDatatype SUB_CLASS_OF = DatatypeMap.newResource(RDFS.SUBCLASSOF); private static final String QM = "?"; static public Logger logger = LoggerFactory.getLogger(PluginImpl.class); - static String DEF_PPRINTER = Transformer.PPRINTER; + static String DEF_PPRINTER = TransformerUtils.PPRINTER; static int nbBufferedValue = 0; // draft storage for large literal values (not used) private static IStorage storageMgr; @@ -460,7 +461,7 @@ IDatatype load(Load ld, IDatatype dt, Graph g, IDatatype expectedFormat, IDataty if (expectedFormat == null) { // use content negotiation for format ld.parse(dt.getLabel(), getFormat(expectedFormat)); - } else if (expectedFormat.getLabel().equals(Transformer.TEXT)) { + } else if (expectedFormat.getLabel().equals(TransformerUtils.TEXT)) { // xt:load(uri, st:text, st:turtle) ld.loadString(dt.stringValue(), getFormat(requiredFormat)); } else { diff --git a/src/main/java/fr/inria/corese/core/query/PluginTransform.java b/src/main/java/fr/inria/corese/core/query/PluginTransform.java index 50565f3fc..45a2b3b88 100644 --- a/src/main/java/fr/inria/corese/core/query/PluginTransform.java +++ b/src/main/java/fr/inria/corese/core/query/PluginTransform.java @@ -19,6 +19,7 @@ import fr.inria.corese.core.transform.DefaultVisitor; import fr.inria.corese.core.transform.TemplateVisitor; import fr.inria.corese.core.transform.Transformer; +import fr.inria.corese.core.transform.TransformerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -133,7 +134,7 @@ Context getQueryContext(Binding b, Environment env, Producer p) { */ Context getTransformerContext(Binding b, Environment env, Producer p) throws EngineException { Transformer t = getTransformerCurrent(b, env, p); - return t.getContext(); + return t.getContextManager().getContext(); } /** @@ -213,7 +214,7 @@ boolean isWith(Expr exp) { void complete(Query q, Transformer t, IDatatype uri) { t.complete(q, (Transformer) q.getTransformer()); if (uri != null) { - t.getContext().set(Transformer.STL_TRANSFORM, uri); + t.getContextManager().getContext().set(TransformerUtils.STL_TRANSFORM, uri); } } diff --git a/src/main/java/fr/inria/corese/core/query/QueryEngine.java b/src/main/java/fr/inria/corese/core/query/QueryEngine.java index e31e27a54..ef9cad652 100644 --- a/src/main/java/fr/inria/corese/core/query/QueryEngine.java +++ b/src/main/java/fr/inria/corese/core/query/QueryEngine.java @@ -14,15 +14,13 @@ import fr.inria.corese.core.sparql.triple.parser.Access.Level; import fr.inria.corese.core.sparql.triple.parser.Dataset; import fr.inria.corese.core.transform.Transformer; +import fr.inria.corese.core.transform.TransformerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; -import static fr.inria.corese.core.transform.Transformer.STL_PROFILE; /** * Equivalent of RuleEngine for Query and Template Run a set of query @@ -33,38 +31,62 @@ public class QueryEngine implements Engine { private static final Logger logger = LoggerFactory.getLogger(QueryEngine.class); + + // Cache configuration + private static final int DEFAULT_CACHE_SIZE = 100; + private static final int MAX_QUERY_LENGTH_FOR_CACHE = 10000; + private final QueryProcess exec; - Graph graph; - ArrayList list; - HashMap table; - HashMap> tableList; - TemplateIndex index; - boolean isActivate = true; - boolean isWorkflow = false; + private final Graph graph; + private final ArrayList list; + private final HashMap table; + private final HashMap> tableList; + private final TemplateIndex index; + + // Query compilation cache + private final Map compilationCache; + private final int maxCacheSize; + + // Cache statistics (optional for monitoring) + private long cacheHits = 0; + private long cacheMisses = 0; + + private boolean isActivate = true; + private boolean isWorkflow = false; private Dataset ds; private boolean transformation = false; private String base; private Level level = Level.USER_DEFAULT; - // focus type -> templates QueryEngine(Graph g) { + this(g, DEFAULT_CACHE_SIZE); + } + + QueryEngine(Graph g, int cacheSize) { graph = g; exec = QueryProcess.create(g); list = new ArrayList<>(); table = new HashMap<>(); tableList = new HashMap<>(); index = new TemplateIndex(); + maxCacheSize = cacheSize; + + compilationCache = new ConcurrentHashMap<>(cacheSize); } public static QueryEngine create(Graph g) { return new QueryEngine(g); } + public static QueryEngine create(Graph g, int cacheSize) { + return new QueryEngine(g, cacheSize); + } + public void addQuery(String q) { try { defQuery(q); } catch (EngineException e) { - logger.error("", e); + logger.error("Failed to add query: {}", q.substring(0, Math.min(100, q.length())), e); } } @@ -75,19 +97,136 @@ Dataset getCreateDataset() { return getDataset(); } - public Query defQuery(String q) throws EngineException { + /** + * OPTIMIZED: defQuery with compilation cache + */ + public Query defQuery(String queryString) throws EngineException { if (getBase() != null) { getQueryProcess().setBase(getBase()); } getCreateDataset().setLevel(getLevel()); - Query qq = getQueryProcess().compile(q, getDataset()); - if (qq != null) { - cleanContext(qq); - defQuery(qq); + + Query cachedQuery = getCachedQuery(queryString); + if (cachedQuery != null) { + cacheHits++; + logger.debug("Query cache hit for query of length {}", queryString.length()); + // Clone the query to avoid concurrent modifications + Query clonedQuery = cloneQuery(cachedQuery); + cleanContext(clonedQuery); + defQuery(clonedQuery); + return clonedQuery; } - return qq; + + cacheMisses++; + Query compiledQuery = getQueryProcess().compile(queryString, getDataset()); + + if (compiledQuery != null) { + cleanContext(compiledQuery); + + // Cache if the query is eligible + cacheQuery(queryString, compiledQuery); + + defQuery(compiledQuery); + } + + return compiledQuery; } + /** + * Retrieves a query from the cache if it exists + */ + private Query getCachedQuery(String queryString) { + if (!shouldCache(queryString)) { + return null; + } + + CompilationKey key = new CompilationKey(queryString, getBase(), getLevel(), getDataset()); + return compilationCache.get(key); + } + + /** + * Caches a query + */ + private void cacheQuery(String queryString, Query query) { + if (!shouldCache(queryString)) { + return; + } + + // Simple LRU eviction if the cache is full + if (compilationCache.size() >= maxCacheSize) { + evictOldestEntry(); + } + + CompilationKey key = new CompilationKey(queryString, getBase(), getLevel(), getDataset()); + compilationCache.put(key, query); + } + + /** + * Determines if a query should be cached + */ + private boolean shouldCache(String queryString) { + // Do not cache very long queries or UPDATE queries + return queryString != null + && queryString.length() <= MAX_QUERY_LENGTH_FOR_CACHE + && !queryString.toUpperCase().contains("INSERT") + && !queryString.toUpperCase().contains("DELETE") + && !queryString.toUpperCase().contains("CREATE") + && !queryString.toUpperCase().contains("DROP"); + } + + /** + * Simple eviction of the oldest entry + */ + private void evictOldestEntry() { + if (compilationCache.isEmpty()) { + return; + } + + Optional oldestKey = compilationCache.keySet() + .stream() + .findFirst(); + + oldestKey.ifPresent(key -> { + compilationCache.remove(key); + logger.debug("Evicted query from cache, cache size: {}", compilationCache.size()); + }); + } + + /** + * Clones a query to avoid concurrent modifications. + * IMPORTANT: This implementation is a simplification. In a real system, + * a deep copy of the Query object would need to be implemented + * to ensure that modifications to the cloned query + * do not affect the cached instance. + * The complexity of this copy depends on the internal structure of the Query class. + */ + private Query cloneQuery(Query original) { + // TODO: Implement a deep copy of the query. + // For example, if Query implements Cloneable or has a copy constructor: + // return original.clone(); + // or + // return new Query(original); + // For now, we return the original, which might cause issues + // if the query is modified after being retrieved from the cache. + return original; + } + + /** + * Clears the compilation cache + */ + public void clearCompilationCache() { + compilationCache.clear(); + cacheHits = 0; + cacheMisses = 0; + logger.info("Compilation cache cleared"); + } + + /** + * Cache statistics + */ + public CacheStats getCacheStats() { + return new CacheStats(cacheHits, cacheMisses, compilationCache.size(), maxCacheSize); + } /** * Remove compile time context @@ -156,7 +295,7 @@ void complete() { * templates inherit template st:profile function definitions */ public void profile() { - Query profile = getTemplate(STL_PROFILE); + Query profile = getTemplate(TransformerUtils.STL_PROFILE); if ((profile != null) && (profile.getExtension() != null)) { // share profile function definitions in templates fr.inria.corese.core.compiler.parser.Transformer tr = fr.inria.corese.core.compiler.parser.Transformer.create(); @@ -216,7 +355,7 @@ public Collection getNamedTemplates() { } public Query getTemplate() { - Query q = getTemplate(STL_PROFILE); + Query q = getTemplate(TransformerUtils.STL_PROFILE); if (q != null) { return q; } else if (getTemplates().isEmpty()) { @@ -229,7 +368,6 @@ public Query getTemplate() { return null; } - public boolean isEmpty() { return list.isEmpty() && table.isEmpty(); } @@ -269,7 +407,7 @@ public Mappings process(Query q, Mapping m) { try { return getQueryProcess().query(null, q, m, null); } catch (EngineException e) { - logger.error("", e); + logger.error("Failed to process query: {}", q.toString().substring(0, Math.min(100, q.toString().length())), e); } return Mappings.create(q); } @@ -318,40 +456,33 @@ public void sort() { index.sort(); } + /** + * Fix for "Cannot assign a value to final variable 'list'" + * Clears the existing list and adds filtered elements instead of reassigning the final list. + */ public void clean() { - ArrayList l = new ArrayList<>(); + ArrayList filteredList = new ArrayList<>(); for (Query q : list) { if (!q.isFail()) { - l.add(q); + filteredList.add(q); } } - list = l; + list.clear(); + list.addAll(filteredList); } - /** - * @return the ds - */ public Dataset getDataset() { return ds; } - /** - * @param ds the ds to set - */ public void setDataset(Dataset ds) { this.ds = ds; } - /** - * @return the transformation - */ public boolean isTransformation() { return transformation; } - /** - * @param transformation the transformation to set - */ public void setTransformation(boolean transformation) { this.transformation = transformation; } @@ -360,38 +491,96 @@ public void setVisitor(QueryVisitor vis) { getQueryProcess().setVisitor(vis); } - /** - * @return the exec - */ public QueryProcess getQueryProcess() { return exec; } - /** - * @return the base - */ public String getBase() { return base; } - /** - * @param base the base to set - */ public void setBase(String base) { this.base = base; } - /** - * @return the level - */ public Level getLevel() { return level; } - /** - * @param level the level to set - */ public void setLevel(Level level) { this.level = level; } + + /** + * Composite key for the compilation cache + */ + private static class CompilationKey { + private final String queryString; + private final String base; + private final Level level; + private final int datasetHash; + private final int hashCode; + + public CompilationKey(String queryString, String base, Level level, Dataset dataset) { + this.queryString = queryString; + this.base = base; + this.level = level; + this.datasetHash = (dataset != null) ? dataset.hashCode() : 0; + this.hashCode = computeHashCode(); + } + + private int computeHashCode() { + return Objects.hash(queryString, base, level, datasetHash); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + CompilationKey that = (CompilationKey) obj; + return datasetHash == that.datasetHash && + Objects.equals(queryString, that.queryString) && + Objects.equals(base, that.base) && + Objects.equals(level, that.level); + } + + @Override + public int hashCode() { + return hashCode; + } + } + + /** + * Class for cache statistics + */ + public static class CacheStats { + private final long hits; + private final long misses; + private final int currentSize; + private final int maxSize; + + public CacheStats(long hits, long misses, int currentSize, int maxSize) { + this.hits = hits; + this.misses = misses; + this.currentSize = currentSize; + this.maxSize = maxSize; + } + + public double getHitRate() { + long total = hits + misses; + return total == 0 ? 0.0 : (double) hits / total; + } + + public long getHits() { return hits; } + public long getMisses() { return misses; } + public int getCurrentSize() { return currentSize; } + public int getMaxSize() { return maxSize; } + + @Override + public String toString() { + return String.format("CacheStats{hits=%d, misses=%d, hitRate=%.2f%%, size=%d/%d}", + hits, misses, getHitRate() * 100, currentSize, maxSize); + } + } } diff --git a/src/main/java/fr/inria/corese/core/rule/RuleEngine.java b/src/main/java/fr/inria/corese/core/rule/RuleEngine.java index 340bd9d94..e087ae759 100644 --- a/src/main/java/fr/inria/corese/core/rule/RuleEngine.java +++ b/src/main/java/fr/inria/corese/core/rule/RuleEngine.java @@ -387,7 +387,7 @@ public boolean process() throws EngineException { * Binding may manage AccessRight which is shared by rule processing */ public boolean process(Binding b) throws EngineException { - logger.info("process: " + getPath()); + logger.info("process: {} " , getPath()); beforeProcess(b); try { getGraphManager().startRuleEngine(); @@ -398,6 +398,7 @@ public boolean process(Binding b) throws EngineException { } finally { getGraphManager().endRuleEngine(); afterProcess(); + clean(); } } @@ -707,7 +708,7 @@ void infer(Mapping mapping, Binding bind) throws EngineException { // loop while there is something new while (go) { - logger.info("loop: " + loop); + logger.info("loop: {} " , loop); getEventManager().start(Event.InferenceCycle); if (isEvent()) getVisitor().loopEntailment(getPath()); @@ -716,7 +717,7 @@ void infer(Mapping mapping, Binding bind) throws EngineException { if (isOptimize()) { getResultWatcher().start(loop); } - logger.info("rules: " + getRules().size()); + logger.info("rules: {} " , getRules().size()); for (Rule possibleRule : getRules()) { if (isOptimize() && possibleRule.isOptimize()) { diff --git a/src/main/java/fr/inria/corese/core/sparql/compiler/java/JavaCompiler.java b/src/main/java/fr/inria/corese/core/sparql/compiler/java/JavaCompiler.java index 16180da61..b29abc320 100644 --- a/src/main/java/fr/inria/corese/core/sparql/compiler/java/JavaCompiler.java +++ b/src/main/java/fr/inria/corese/core/sparql/compiler/java/JavaCompiler.java @@ -12,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Method; @@ -33,7 +34,6 @@ public class JavaCompiler { static final String SPACE = " "; static final int STEP = 2; static final String IDATATYPE = "IDatatype"; - private static final Logger logger = LoggerFactory.getLogger(JavaCompiler.class); int margin = 0; int count = 0; int level = 0; @@ -123,14 +123,18 @@ public void write() throws IOException { } public void write(String path) throws IOException { - FileWriter fw = new FileWriter(String.format("%s%s.java", path, name)); - fw.write(head.getStringBuilder().toString()); - fw.write(dtc.getStringBuilder().toString()); - fw.write(dtc.getStringBuilderVar().toString()); - fw.write(NL); - fw.write(sb.toString()); - fw.flush(); - fw.close(); + final String fullFileName = String.format("%s%s.java", path, name); + + try (final FileWriter fw = new FileWriter(fullFileName); + final BufferedWriter bufferedWriter = new BufferedWriter(fw)) { + bufferedWriter.write(head.getStringBuilder().toString()); + bufferedWriter.write(dtc.getStringBuilder().toString()); + bufferedWriter.write(dtc.getStringBuilderVar().toString()); + bufferedWriter.write(NL); + bufferedWriter.write(sb.toString()); + + bufferedWriter.flush(); + } } /** diff --git a/src/main/java/fr/inria/corese/core/sparql/datatype/function/SQLFun.java b/src/main/java/fr/inria/corese/core/sparql/datatype/function/SQLFun.java index edc5fa841..6ada0f9d9 100755 --- a/src/main/java/fr/inria/corese/core/sparql/datatype/function/SQLFun.java +++ b/src/main/java/fr/inria/corese/core/sparql/datatype/function/SQLFun.java @@ -1,86 +1,128 @@ package fr.inria.corese.core.sparql.datatype.function; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - import fr.inria.corese.core.sparql.api.IDatatype; +import fr.inria.corese.core.sparql.exceptions.SQLFunException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import java.sql.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class SQLFun { private static final Logger logger = LoggerFactory.getLogger(SQLFun.class); - - static final String DERBY_DRIVER = "org.apache.derby.jdbc.ClientDriver"; - - IDatatype input, datatype; - ResultSet output; - static Object driver; - - public ResultSet sql(IDatatype uri, IDatatype dd, - IDatatype login, IDatatype passwd, IDatatype query){ - if (driver == null){ - // first time - try { - // remember driver is loaded - driver = Class.forName(dd.getLabel()).newInstance(); - } catch (InstantiationException e) { - // TODO Auto-generated catch block - logger.error("Operation failure", e); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - logger.error("Operation failure", e); - } catch (ClassNotFoundException e) { - // TODO Auto-generated catch block - logger.error("Operation failure", e); + + private static final String DEFAULT_DERBY_DRIVER = "org.apache.derby.jdbc.ClientDriver"; + + private static volatile boolean defaultDriverLoaded = false; + private static final Object driverLoadLock = new Object(); + + /** + * Attempts to load a JDBC driver by its class name. + * This method ensures that the driver is registered with the DriverManager. + * + * @param driverClassName The fully qualified class name of the JDBC driver. + * @throws RuntimeException if the driver is not found. + */ + private static void loadJdbcDriver(String driverClassName) { + try { + + Class.forName(driverClassName); + logger.info("JDBC driver '{}' loaded successfully.", driverClassName); + } catch (ClassNotFoundException e) { + + throw new SQLFunException("JDBC driver not found: " + driverClassName, e); + } + } + + /** + * Ensures that the default JDBC driver (Derby) is loaded. + * Uses double-checked locking for thread-safe and unique initialization. + */ + private static void ensureDefaultDriverLoaded() { + if (!defaultDriverLoaded) { + synchronized (driverLoadLock) { + if (!defaultDriverLoaded) { + loadJdbcDriver(DEFAULT_DERBY_DRIVER); + defaultDriverLoaded = true; + } } } + } + + /** + * Executes an SQL query, allowing the specification of a custom JDBC driver. + * The results are returned as a list of lists of strings, + * ensuring that all JDBC resources are properly closed. + * + * @param uri The JDBC connection URI + * @param driverDatatype The IDatatype containing the fully qualified class name of the JDBC driver. + * @param login The database connection username. + * @param passwd The database password. + * @param query The SQL query string to execute. + * @return A list of lists of strings representing the query results. + * Each inner list is a row. Returns an empty list if the query yields no results, + * or null if an unrecoverable error occurs. + */ + public List> sql(IDatatype uri, IDatatype driverDatatype, + IDatatype login, IDatatype passwd, IDatatype query) { + loadJdbcDriver(driverDatatype.getLabel()); + return sql(uri, login, passwd, query); } - - public ResultSet sql(IDatatype uri, - IDatatype login, IDatatype passwd, IDatatype query){ -// if (input == uri && query == datatype){ -// return output; -// } - try { - if (driver == null){ - try { - // default is derby - driver = Class.forName(DERBY_DRIVER).newInstance(); - } catch (InstantiationException e) { - // TODO Auto-generated catch block - logger.error("Operation failure", e); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - logger.error("Operation failure", e); - } catch (ClassNotFoundException e) { - // TODO Auto-generated catch block - logger.error("Operation failure", e); + + /** + * Executes an SQL query, assuming an appropriate JDBC driver has already been loaded + * (e.g., the default driver or a driver loaded by the overloaded method). + * The results are returned as a list of lists of strings, + * ensuring that all JDBC resources are properly closed. + *

+ * This is the preferred method for executing SQL queries as it safely handles + * resource management by processing the ResultSet internally. + * + * @param uri The JDBC connection URI. + * @param login The database connection username. + * @param passwd The database password. + * @param query The SQL query string to execute. + * @return A list of lists of strings representing the query results. + * Each inner list is a row. Returns an empty list if the query yields no results, + * or null if an unrecoverable error occurs. + */ + public List> sql(IDatatype uri, + IDatatype login, IDatatype passwd, IDatatype query) { + ensureDefaultDriverLoaded(); + + + try (Connection con = DriverManager.getConnection(uri.getLabel(), login.getLabel(), passwd.getLabel()); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(query.getLabel())) { + + List> results = new ArrayList<>(); + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + while (rs.next()) { + List row = new ArrayList<>(); + for (int i = 1; i <= columnCount; i++) { + + row.add(String.valueOf(rs.getObject(i))); } + results.add(row); } - Connection con = - DriverManager.getConnection(uri.getLabel(), login.getLabel(), passwd.getLabel()); - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery(query.getLabel()); - //stmt.close(); - //rs.close(); - //con.close(); - input = uri; - datatype = query; - output = rs; - return rs; - } - catch (SQLException e) { - // TODO Auto-generated catch block - logger.error("Operation failure", e); - } - return null; + return results; + + } catch (SQLException e) { + logger.error("SQL operation failed for URI: '{}', Query: '{}'.", + uri.getLabel(), query.getLabel(), e); + + return Collections.emptyList(); + } catch (RuntimeException e) { + logger.error("Runtime error during SQL operation (e.g., JDBC driver not found).", e); + return Collections.emptyList(); + } } - - - } diff --git a/src/main/java/fr/inria/corese/core/sparql/exceptions/SQLFunException.java b/src/main/java/fr/inria/corese/core/sparql/exceptions/SQLFunException.java new file mode 100644 index 000000000..e2b4ba1f9 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/sparql/exceptions/SQLFunException.java @@ -0,0 +1,29 @@ +package fr.inria.corese.core.sparql.exceptions; + +/** + * Custom exception for operations within the SQLFun class. + * This provides a more specific type for runtime errors that occur + * during database interactions or driver loading, making error handling + * more explicit and easier to manage by callers. + */ +public class SQLFunException extends RuntimeException { + /** + * Constructs a new SQLFunException with the specified detail message. + * + * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method). + */ + public SQLFunException(String message) { + super(message); + } + + /** + * Constructs a new SQLFunException with the specified detail message and cause. + * + * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public SQLFunException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/sparql/triple/javacc1/sparql_corese.jj b/src/main/java/fr/inria/corese/core/sparql/triple/javacc1/sparql_corese.jj index 94d59fc42..1d8e558c1 100644 --- a/src/main/java/fr/inria/corese/core/sparql/triple/javacc1/sparql_corese.jj +++ b/src/main/java/fr/inria/corese/core/sparql/triple/javacc1/sparql_corese.jj @@ -2949,7 +2949,7 @@ String Q_IRI_ref() : { Token t; String s; } TOKEN_MGR_DECLS:{ void CommonTokenAction(Token token) { - //System.out.println(token+" "); + //logger.trace("Token parsed: {}", token); } } diff --git a/src/main/java/fr/inria/corese/core/transform/ContextManager.java b/src/main/java/fr/inria/corese/core/transform/ContextManager.java new file mode 100644 index 000000000..c65d615cb --- /dev/null +++ b/src/main/java/fr/inria/corese/core/transform/ContextManager.java @@ -0,0 +1,140 @@ +package fr.inria.corese.core.transform; + +import fr.inria.corese.core.kgram.api.query.Environment; +import fr.inria.corese.core.kgram.core.Mapping; +import fr.inria.corese.core.kgram.core.Mappings; +import fr.inria.corese.core.kgram.core.Query; +import fr.inria.corese.core.sparql.api.IDatatype; +import fr.inria.corese.core.sparql.triple.function.term.Binding; +import fr.inria.corese.core.sparql.triple.parser.Context; +import fr.inria.corese.core.sparql.triple.parser.Dataset; +import fr.inria.corese.core.sparql.triple.parser.NSManager; + +public class ContextManager { + private Context context; + private Binding binding; + private Mappings mappings; + private Dataset dataset; + private final TransformerMapping transformerMapping; + private final NSManager namespaceManager; + + + public ContextManager(TransformerMapping mapping, NSManager nsm) { + this.transformerMapping = mapping; + this.context = new Context(); + this.namespaceManager = nsm; + } + + /** + * Create mapping for template execution + */ + public Mapping createMapping(Query template, IDatatype[] args, IDatatype focus) { + return transformerMapping.getMapping(template, args, focus); + } + + /** + * Share context between mappings and environment + */ + public Mapping shareContext(Mapping mapping, Environment env) { + if (env != null && env.getBind() != null) { + mapping.setBind(env.getBind()); + } + + if (mappings != null) { + if (mapping.getBind() == null) { + mapping.setBind(Binding.create()); + } + mapping.getBind().setMappings(mappings); + } + + return mapping; + } + + /** + * Set context value + */ + public void setContextValue(String key, IDatatype value) { + context.set(key, value); + } + + /** + * Get context value + */ + public IDatatype getContextValue(String key) { + return context.get(key); + } + + /** + * Check if context has value + */ + public boolean hasContextValue(String key) { + return context.hasValue(key); + } + + /** + * Complete context with another context + */ + public void completeContext(Context otherContext) { + if (otherContext != null) { + context.complete(otherContext); + } + } + + // Getters and setters + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + initContext(); + } + + /** + * Define prefix from Context slot st:prefix = ((ns uri)) + */ + void initContext() { + if (getContext() != null) { + if (getContext().hasValue(TransformerUtils.STL_PREFIX)) { + definePrefix(); + } + } + } + + void definePrefix() { + for (IDatatype def : context.get(TransformerUtils.STL_PREFIX).getValueList()) { + if (def.isList() && def.size() >= 2) { + getNSM().definePrefix(def.get(0).getLabel(), def.get(1).getLabel()); + } + } + } + + public Binding getBinding() { + return binding; + } + + public void setBinding(Binding binding) { + this.binding = binding; + } + + public Mappings getMappings() { + return mappings; + } + + public void setMappings(Mappings mappings) { + this.mappings = mappings; + } + + public Dataset getDataset() { + return dataset; + } + + public void setDataset(Dataset dataset) { + this.dataset = dataset; + } + + public NSManager getNSM() { + return namespaceManager; + } + +} diff --git a/src/main/java/fr/inria/corese/core/transform/DefaultVisitor.java b/src/main/java/fr/inria/corese/core/transform/DefaultVisitor.java index 4e5942799..f5cce5676 100644 --- a/src/main/java/fr/inria/corese/core/transform/DefaultVisitor.java +++ b/src/main/java/fr/inria/corese/core/transform/DefaultVisitor.java @@ -48,7 +48,7 @@ public class DefaultVisitor implements TemplateVisitor { private HashMap distinct; private final HashMap value; - private String transform = Transformer.TURTLE; + private String transformType = TransformerUtils.TURTLE; private final String NL = "\n"; // boolean value (if any) that means that visitor must consider visited node // use case: st:visit(st:exp, ?x, ?suc) @@ -256,14 +256,14 @@ public void setGraph(Graph g){ * @return the transform */ public String getTransform() { - return transform; + return transformType; } /** * @param transform the transform to set */ public void setTransform(String transform) { - this.transform = transform; + this.transformType = transform; } /** diff --git a/src/main/java/fr/inria/corese/core/transform/Loader.java b/src/main/java/fr/inria/corese/core/transform/Loader.java index ff8a01095..6111ea7d6 100644 --- a/src/main/java/fr/inria/corese/core/transform/Loader.java +++ b/src/main/java/fr/inria/corese/core/transform/Loader.java @@ -152,7 +152,7 @@ void load(Load ld, QueryEngine qe, String pp) throws LoadException { // remove # String clean(String uri){ - return Transformer.getURI(uri); + return TransformerUtils.getURI(uri); } diff --git a/src/main/java/fr/inria/corese/core/transform/Table.java b/src/main/java/fr/inria/corese/core/transform/Table.java index 1d68c1da2..b1cdbdc06 100644 --- a/src/main/java/fr/inria/corese/core/transform/Table.java +++ b/src/main/java/fr/inria/corese/core/transform/Table.java @@ -24,15 +24,15 @@ public class Table extends HashMap { void init() { // namespace to pprinter - put(NSManager.OWL, Transformer.OWL); - put(NSManager.SPIN, Transformer.SPIN); - put(NSManager.SQL, Transformer.SQL); + put(NSManager.OWL, TransformerUtils.OWL); + put(NSManager.SPIN, TransformerUtils.SPIN); + put(NSManager.SQL, TransformerUtils.SQL); table = new HashMap(); // pprinter is optimized ? - table.put(Transformer.OWL, false); - table.put(Transformer.SPIN, true); - table.put(Transformer.SQL, true); + table.put(TransformerUtils.OWL, false); + table.put(TransformerUtils.SPIN, true); + table.put(TransformerUtils.SQL, true); } diff --git a/src/main/java/fr/inria/corese/core/transform/Transformer.java b/src/main/java/fr/inria/corese/core/transform/Transformer.java index 97d0bf348..0b2cf3770 100644 --- a/src/main/java/fr/inria/corese/core/transform/Transformer.java +++ b/src/main/java/fr/inria/corese/core/transform/Transformer.java @@ -1,38 +1,19 @@ package fr.inria.corese.core.transform; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import fr.inria.corese.core.compiler.eval.Interpreter; -import fr.inria.corese.core.compiler.parser.Pragma; import fr.inria.corese.core.Graph; -import fr.inria.corese.core.load.Load; -import fr.inria.corese.core.load.LoadException; -import fr.inria.corese.core.query.QueryEngine; -import fr.inria.corese.core.query.QueryProcess; -import fr.inria.corese.core.storage.api.dataManager.DataManager; -import fr.inria.corese.core.visitor.solver.QuerySolverVisitorTransformer; +import fr.inria.corese.core.compiler.parser.Pragma; import fr.inria.corese.core.kgram.api.core.Expr; import fr.inria.corese.core.kgram.api.core.ExprType; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.kgram.api.query.Environment; import fr.inria.corese.core.kgram.api.query.Producer; -import fr.inria.corese.core.kgram.core.Mapping; -import fr.inria.corese.core.kgram.core.Mappings; -import fr.inria.corese.core.kgram.core.Memory; +import fr.inria.corese.core.kgram.core.*; import fr.inria.corese.core.kgram.core.Query; -import fr.inria.corese.core.kgram.core.SparqlException; import fr.inria.corese.core.kgram.filter.Extension; +import fr.inria.corese.core.load.Load; +import fr.inria.corese.core.load.LoadException; +import fr.inria.corese.core.query.QueryEngine; +import fr.inria.corese.core.query.QueryProcess; import fr.inria.corese.core.sparql.api.IDatatype; import fr.inria.corese.core.sparql.api.TransformProcessor; import fr.inria.corese.core.sparql.datatype.DatatypeMap; @@ -41,14 +22,21 @@ import fr.inria.corese.core.sparql.triple.function.script.Funcall; import fr.inria.corese.core.sparql.triple.function.script.Function; import fr.inria.corese.core.sparql.triple.function.term.Binding; -import fr.inria.corese.core.sparql.triple.parser.ASTQuery; -import fr.inria.corese.core.sparql.triple.parser.Access; +import fr.inria.corese.core.sparql.triple.parser.*; import fr.inria.corese.core.sparql.triple.parser.Access.Feature; import fr.inria.corese.core.sparql.triple.parser.Access.Level; -import fr.inria.corese.core.sparql.triple.parser.Context; -import fr.inria.corese.core.sparql.triple.parser.Dataset; -import fr.inria.corese.core.sparql.triple.parser.NSManager; -import fr.inria.corese.core.sparql.triple.parser.Processor; +import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.visitor.solver.QuerySolverVisitorTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +import java.util.HashMap; +import java.util.List; + /** * SPARQL Template Transformation Engine @@ -67,68 +55,10 @@ public class Transformer implements TransformProcessor { private static final Logger logger = LoggerFactory.getLogger(Transformer.class); - private static final String NULL = ""; - private static final String STL = NSManager.STL; - public static final String SQL = STL + "sql"; - public static final String SPIN = STL + "spin"; - public static final String TOSPIN = STL + "tospin"; - public static final String OWL = STL + "owl"; - public static final String OWLRL = STL + "owlrl"; - public static final String OWL_RL = STL + "owlrl"; - public static final String OWL_EL = STL + "owleltc"; - public static final String OWL_QL = STL + "owlqltc"; - public static final String OWL_TC = STL + "owltc"; - - public static final String OWL_MAIN = STL + "main"; - - public static final String PP_ERROR = STL + "pperror"; - public static final String PP_ERROR_MAIN = STL + "main"; - public static final String PP_ERROR_DISPLAY = STL + "display"; - public static final String DATASHAPE = STL + "dsmain"; - public static final String TEXT = STL + "text"; - public static final String TURTLE = STL + "turtle"; - public static final String TURTLE_HTML = STL + "hturtle"; - public static final String RDFXML = STL + "rdfxml"; - public static final String ALL = STL + "all"; - public static final String XML = STL + "xml"; - public static final String RDF = STL + "rdf"; - public static final String JSON = STL + "json"; - public static final String JSON_LD = STL + "jsonld"; - public static final String TRIG = STL + "trig"; - public static final String TABLE = STL + "table"; - public static final String HTML = STL + "html"; - public static final String SPARQL = STL + "sparql"; - public static final String RDFRESULT = STL + "result"; - public static final String NAVLAB = STL + "navlab"; - public static final String RDFTYPECHECK = STL + "rdftypecheck"; - public static final String SPINTYPECHECK = STL + "spintypecheck"; - public static final String STL_PROFILE = STL + "profile"; - public static final String STL_START = STL + "start"; - public static final String STL_MAIN = STL + "main"; - public static final String STL_TRACE = STL + "trace"; - public static final String STL_DEFAULT = Processor.STL_DEFAULT; - public static final String STL_DEFAULT_NAMED = STL + "defaultNamed"; - public static final String STL_OPTIMIZE = STL + "optimize"; - public static final String STL_IMPORT = STL + "import"; - public static final String STL_PROCESS = Processor.STL_PROCESS; - public static final String STL_AGGREGATE = Processor.STL_AGGREGATE; - public static final String STL_TRANSFORM = Context.STL_TRANSFORM; - public static final String STL_PREFIX = Context.STL_PREFIX; - public static final String D3 = NSManager.D3; - public static final String D3_ALL = D3 + "all"; - - public static final String[] RESULT_FORMAT = { XML, JSON, RDF }; - public static final String[] GRAPHIC_FORMAT = { D3 + "graphic", D3 + "hierarchy" }; - - // default - public static final String PPRINTER = TURTLE; - private static final String OUT = ASTQuery.OUT; - public static final String IN = ASTQuery.IN; - public static final String IN2 = ASTQuery.IN2; private static final String NL = "\n"; private static boolean isOptimizeDefault = false; private static boolean isExplainDefault = false; - public static int count = 0; + public int count = 0; static HashMap dmap; // private TemplateVisitor visitor; TransformerMapping tmap; @@ -138,11 +68,10 @@ public class Transformer implements TransformProcessor { NSManager nsm; QueryProcess exec; private Mapping mapping; - private Mappings map; - private Dataset ds; + Stack stack; static Table table; - String pp = PPRINTER; + String pp = TransformerUtils.PPRINTER; // separator of results of several templates st:apply-all-templates() String sepTemplate = NL; // separator of several results of one template @@ -152,16 +81,14 @@ public class Transformer implements TransformProcessor { boolean isTurtle = false; int nbt = 0, max = 0, levelMax = Integer.MAX_VALUE, level = 0; - String start = STL_START; + String start = TransformerUtils.STL_START; HashMap tcount; HashMap loaded, imported; // table of nested transformers for apply-templates-with // templates share this table // sub transformers share same table recursively private HashMap transformerMap; - private Binding binding; - // table accessible using st:set/st:get - private Context context; + private QuerySolverVisitorTransformer eventVisitor; private boolean isHide = false; public boolean stat = !true; @@ -182,6 +109,8 @@ public class Transformer implements TransformProcessor { // default is: return RDF term as is (effect is like xsd:string) private int defaut = ExprType.TURTLE; + private ContextManager contextManager; + static { table = new Table(); dmap = new HashMap<>(); @@ -202,38 +131,41 @@ void init(QueryProcess qp, String p) throws LoadException { void init(QueryProcess qp, String p, Level level) throws LoadException { setAccessLevel(level); setEvent(Access.accept(Feature.EVENT, level)); - setContext(new Context()); setTransformation(p); - set(qp); - nsm = NSManager.create(); + set(qp); // This sets 'graph' and 'exec' + + // 1. Initialize NSManager + this.nsm = NSManager.create(); + // 2. Initialize TransformerMapping (needs graph from qp) + this.tmap = new TransformerMapping(qp.getGraph()); + // 3. Initialize ContextManager (needs tmap and nsm) + this.contextManager = new ContextManager(this.tmap, this.nsm); + + // Defensive null check for contextManager + if (this.contextManager == null) { + logger.error("Failed to instantiate ContextManager. It is null after constructor call."); + throw new LoadException("Initialization failed: ContextManager is null."); + } + + // 4. Set context for ContextManager + this.contextManager.setContext(new Context()); + transformerMap = new HashMap<>(); stack = new Stack(this, true); - EMPTY = DatatypeMap.newLiteral(NULL); + EMPTY = DatatypeMap.newLiteral(TransformerUtils.NULL); tcount = new HashMap<>(); loaded = new HashMap<>(); imported = new HashMap<>(); - tmap = new TransformerMapping(qp.getGraph()); + try { setEventVisitor(QuerySolverVisitorTransformer.create(this, qp.getCreateEval())); } catch (EngineException ex) { logger.error(ex.getMessage()); } - init(level); + init(level); // This calls the other init(Level) method } - /** - * Definition of synonym - * st:all -> (st:xml st:json ...) - */ - static public List getFormatList(String name) { - switch (name) { - case ALL: - return Arrays.asList(RESULT_FORMAT); - case D3_ALL: - return Arrays.asList(GRAPHIC_FORMAT); - } - return null; - } + void initMap() { Query q = getTemplate(start); @@ -284,7 +216,7 @@ public static Transformer create(QueryProcess qp, String p) { */ public static Transformer create(Graph g, Mappings map, String p) { Transformer t = create(g, p); - t.setMappings(map); + t.contextManager.setMappings(map); t.initMap(); return t; } @@ -306,15 +238,15 @@ public static Transformer create(String p) { } public static String turtle(Graph g) throws EngineException { - return create(g, TURTLE).transform(); + return create(g, TransformerUtils.TURTLE).transform(); } public static String rdfxml(Graph g) throws EngineException { - return create(g, RDFXML).transform(); + return create(g, TransformerUtils.RDFXML).transform(); } public static String json(Graph g) throws EngineException { - return create(g, JSON).transform(); + return create(g, TransformerUtils.JSON).transform(); } /** @@ -354,7 +286,7 @@ public static Transformer createWE(Graph g, String trans, String name, boolean w } Transformer t = Transformer.create(gg); - t.setDataset(ds); + t.contextManager.setDataset(ds); t.setTemplates(trans, level); return t; } @@ -403,11 +335,12 @@ public void transform(InputStream in, OutputStream out, fr.inria.corese.core.api } public void write(String name) throws IOException { - FileWriter fw = new FileWriter(name); - String str = toString(); - fw.write(str); - fw.flush(); - fw.close(); + try (final FileWriter fw = new FileWriter(name); + final BufferedWriter bufferedWriter = new BufferedWriter(fw)) { + String str = toString(); + bufferedWriter.write(str); + bufferedWriter.flush(); + } } public void write(OutputStream out) throws IOException { @@ -420,7 +353,8 @@ public void definePrefix(String p, String ns) { } public void setNSM(NSManager n) { - nsm = n; + this.nsm = n; + } public NSManager getNSM() { @@ -509,7 +443,7 @@ void setTransformation(String p) { void setStarter(String uri) { String name = getName(uri); if (name != null) { - setStart(STL + name); + setStart(TransformerUtils.STL + name); } } @@ -530,20 +464,10 @@ public static String getStartName(String uri) { if (name == null) { return null; } - return STL + name; + return TransformerUtils.STL + name; } - /** - * uri#name - * - * @return uri - */ - public static String getURI(String uri) { - if (uri != null && uri.contains("#")) { - return uri.substring(0, uri.indexOf("#")); - } - return uri; - } + private void tune(QueryProcess exec) { exec.setListPath(true); @@ -644,15 +568,15 @@ public int getAggregate() { * Otherwise, apply the first template that matches without bindings. */ public IDatatype process() throws EngineException { - if (getBinding() != null) { - return process(getBinding()); + if (contextManager.getBinding() != null) { + return process(contextManager.getBinding()); } return process(null, false, null, null, null); } public IDatatype process(Binding b) throws EngineException { if (b != null) { - setBinding(b); + contextManager.setBinding(b); } return process(null, false, null, null, (b == null) ? null : Mapping.create(b)); } @@ -698,9 +622,9 @@ public IDatatype process(String temp, boolean all, String sep, Expr exp, Environ // remember start with qq for function pprint below query = qq; if (query.getName() != null) { - context.setURI(STL_START, qq.getName()); + contextManager.getContext().setURI(TransformerUtils.STL_START, qq.getName()); } else { - context.set(STL_START, (String) null); + contextManager.getContext().set(TransformerUtils.STL_START, (String) null); } Mappings map = exec.query(qq, m); @@ -770,12 +694,11 @@ NSManager nsm(Query q) { /** * Record Binding in case of Workflow: next WorkflowProcess can get Binding - * - * @param map + * * @param map */ void save(Mappings map) { - if (getBinding() == null && map.getBinding() != null) { - setBinding(map.getBinding()); + if (contextManager.getBinding() == null && map.getBinding() != null) { + contextManager.setBinding(map.getBinding()); } } @@ -799,7 +722,7 @@ public void setLevel(int n) { @Override public boolean isStart() { - return query != null && query.getName() != null && query.getName().equals(STL_START); + return query != null && query.getName() != null && query.getName().equals(TransformerUtils.STL_START); } public IDatatype process(Node node) throws EngineException { @@ -826,7 +749,7 @@ public IDatatype process(String temp, Binding b, IDatatype... ldt) throws Engine return process(temp, false, null, null, Mapping.create(b), ldt[0], (ldt.length == 1) ? null : ldt); } - public static int getCount() { + public int getCount() { return count; } @@ -864,7 +787,7 @@ public IDatatype process(String temp, boolean allTemplates, String sep, try { if (level() >= levelMax) { // return defaut(dt, q); - IDatatype res = eval(STL_DEFAULT, dt, (isBoolean() ? defaultBooleanResult() : turtle(dt)), env); + IDatatype res = eval(TransformerUtils.STL_DEFAULT, dt, (isBoolean() ? defaultBooleanResult() : turtle(dt)), env); afterTransformer(astart, res); return res; } @@ -970,12 +893,12 @@ public IDatatype process(String temp, boolean allTemplates, String sep, if (temp != null) { // named template does not match focus node dt // try funcall st:defaultNamed(dt) - IDatatype res = eval(STL_DEFAULT_NAMED, dt, (isBoolean() ? defaultBooleanResult() : EMPTY), env); + IDatatype res = eval(TransformerUtils.STL_DEFAULT_NAMED, dt, (isBoolean() ? defaultBooleanResult() : EMPTY), env); afterTransformer(astart, res); return res; } else if (isHasDefault()) { // apply named template st:default - IDatatype res = process(STL_DEFAULT, allTemplates, sep, exp, env, dt, args); + IDatatype res = process(TransformerUtils.STL_DEFAULT, allTemplates, sep, exp, env, dt, args); if (res != EMPTY) { afterTransformer(astart, res); return res; @@ -984,7 +907,7 @@ public IDatatype process(String temp, boolean allTemplates, String sep, // return a default result (may be dt) // may be overloaded by function st:default(?x) { st:turtle(?x) } - IDatatype res = eval(STL_DEFAULT, dt, (isBoolean() ? defaultBooleanResult() : turtle(dt)), env); + IDatatype res = eval(TransformerUtils.STL_DEFAULT, dt, (isBoolean() ? defaultBooleanResult() : turtle(dt)), env); afterTransformer(astart, res); return res; } finally { @@ -1056,6 +979,11 @@ public boolean isDefined(String name) { return qe.getTemplate(name) != null; } + @Override + public Mappings getMappings() { + return contextManager.getMappings(); + } + /** * use case: result of st:apply-templates-all() list = list of ?out results * of templates create Mappings (?out = value) apply st:aggregate(?out) on @@ -1074,7 +1002,7 @@ IDatatype result(Environment env, List list) { mem.setEval(env.getEval()); } mem.init(tq); - Node out = tq.getExtNode(OUT, true); + Node out = tq.getExtNode(TransformerUtils.OUT, true); Mappings map = Mappings.create(tq); for (Node node : list) { map.add(Mapping.create(out, node)); @@ -1240,21 +1168,21 @@ IDatatype[] param(IDatatype dt) { * display RDF Node in its Turtle syntax */ public IDatatype turtle(IDatatype dt) { - return nsm.turtle(dt, false); + return contextManager.getNSM().turtle(dt, false); } /** * force = true: if no prefix generate prefix */ public IDatatype turtle(IDatatype dt, boolean force) { - return nsm.turtle(dt, force); + return contextManager.getNSM().turtle(dt, force); } /** * if prefix exists, return qname, else return URI as is (without <>) */ public IDatatype qnameURI(IDatatype dt) { - String uri = nsm.toPrefix(dt.getLabel(), true); + String uri = contextManager.getNSM().toPrefix(dt.getLabel(), true); return DatatypeMap.newStringBuilder(uri); } @@ -1286,7 +1214,7 @@ void init(Level level) throws LoadException { setOptimize(table.isOptimize(pp)); qe = QueryEngine.create(graph); Loader load = new Loader(this, qe); - load.setDataset(ds); + load.setDataset(contextManager.getDataset()); load.setLevel(level); load.load(getTransformation()); // templates share profile functions @@ -1297,10 +1225,10 @@ void init(Level level) throws LoadException { if (isCheck()) { check(); } - setHasDefault(qe.getTemplate(STL_DEFAULT) != null); - Query profile = qe.getTemplate(STL_PROFILE); + setHasDefault(qe.getTemplate(TransformerUtils.STL_DEFAULT) != null); + Query profile = qe.getTemplate(TransformerUtils.STL_PROFILE); if (profile != null && profile.getExtension() != null) { - Expr exp = profile.getExtension().get(STL_AGGREGATE); + Expr exp = profile.getExtension().get(TransformerUtils.STL_AGGREGATE); if (exp != null) { defAggregate = exp.getBody().oper(); } @@ -1327,13 +1255,6 @@ void checkFunction(fr.inria.corese.core.compiler.parser.Transformer tr, Query q, } } - /** - * ************************************************************* - * - * Check templates that would never succeed - * - ************************************************************** - */ /** * Check if a template edges not exist in graph remove those templates from * the list to speed up PRAGMA: does not take RDFS entailments into account @@ -1392,45 +1313,12 @@ public void setHasDefault(boolean hasDefault) { this.hasDefault = hasDefault; } - public Dataset getDataset() { - return ds; - } - public void setDataset(Dataset dataset) { - this.ds = dataset; - } public String getTransformation() { return pp; } - public Context getContext() { - return context; - } - - public void setContext(Context context) { - this.context = context; - initContext(); - } - - /** - * Define prefix from Context slot st:prefix = ((ns uri)) - */ - void initContext() { - if (getContext() != null) { - if (getContext().hasValue(STL_PREFIX)) { - definePrefix(); - } - } - } - - void definePrefix() { - for (IDatatype def : getContext().get(STL_PREFIX).getValueList()) { - if (def.isList() && def.size() >= 2) { - getNSM().definePrefix(def.get(0).getLabel(), def.get(1).getLabel()); - } - } - } /** * Query q is the calling template/query @@ -1443,8 +1331,8 @@ public void complete(Query q, Transformer ct) { Context c = getContext(q, ct); if (c != null) { // inherit context exported properties: - getContext().complete(c); - init(getContext()); + contextManager.getContext().complete(c); + init(contextManager.getContext()); } if (ct != null) { complete(ct); @@ -1499,7 +1387,7 @@ Context getContext(Query q, Transformer ct) { return q.getContext(); } else { - return ct.getContext(); + return ct.contextManager.getContext(); } } @@ -1513,8 +1401,8 @@ void complete(NSManager nsm) { } public TemplateVisitor getVisitor() { - if (getBinding() != null) { - return (TemplateVisitor) getBinding().getTransformerVisitor(); + if (contextManager.getBinding() != null) { + return (TemplateVisitor) contextManager.getBinding().getTransformerVisitor(); } return null; } @@ -1527,21 +1415,9 @@ public void setTransformerMap(HashMap transformerMap) { this.transformerMap = transformerMap; } - public Binding getBinding() { - return binding; - } - - public void setBinding(Binding binding) { - this.binding = binding; - } - - @Override - public Mappings getMappings() { - return map; - } - - public void setMappings(Mappings map) { - this.map = map; + // Added public getter for contextManager + public ContextManager getContextManager() { + return contextManager; } public QuerySolverVisitorTransformer getEventVisitor() { diff --git a/src/main/java/fr/inria/corese/core/transform/TransformerMapping.java b/src/main/java/fr/inria/corese/core/transform/TransformerMapping.java index 4d48d8054..29b5af21e 100644 --- a/src/main/java/fr/inria/corese/core/transform/TransformerMapping.java +++ b/src/main/java/fr/inria/corese/core/transform/TransformerMapping.java @@ -2,8 +2,6 @@ import fr.inria.corese.core.compiler.parser.NodeImpl; import fr.inria.corese.core.Graph; -import static fr.inria.corese.core.transform.Transformer.IN; -import static fr.inria.corese.core.transform.Transformer.IN2; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.kgram.core.Mapping; import fr.inria.corese.core.kgram.core.Query; @@ -69,7 +67,7 @@ Mapping getMappingCache(Query q, IDatatype dt){ Mapping getSimpleMapping(IDatatype dt){ Mapping map = getMapping(); if (map == null){ - map = Mapping.create(NodeImpl.createVariable(IN), getNode(dt)); + map = Mapping.create(NodeImpl.createVariable(TransformerUtils.IN), getNode(dt)); setMapping(map); } else { @@ -134,8 +132,8 @@ String getArg(Query q, int n){ String getArg(int n){ switch (n){ - case 0: return IN; - default: return IN2; + case 0: return TransformerUtils.IN; + default: return TransformerUtils.IN2; } } diff --git a/src/main/java/fr/inria/corese/core/transform/TransformerUtils.java b/src/main/java/fr/inria/corese/core/transform/TransformerUtils.java new file mode 100644 index 000000000..5dc63d655 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/transform/TransformerUtils.java @@ -0,0 +1,101 @@ +package fr.inria.corese.core.transform; + +import fr.inria.corese.core.sparql.triple.parser.ASTQuery; +import fr.inria.corese.core.sparql.triple.parser.Context; +import fr.inria.corese.core.sparql.triple.parser.NSManager; +import fr.inria.corese.core.sparql.triple.parser.Processor; + +import java.util.Arrays; +import java.util.List; + +public class TransformerUtils { + + public static final String NULL = ""; + public static final String STL = NSManager.STL; + public static final String SQL = STL + "sql"; + public static final String SPIN = STL + "spin"; + public static final String TOSPIN = STL + "tospin"; + public static final String OWL = STL + "owl"; + public static final String OWLRL = STL + "owlrl"; + public static final String OWL_RL = STL + "owlrl"; + public static final String OWL_EL = STL + "owleltc"; + public static final String OWL_QL = STL + "owlqltc"; + public static final String OWL_TC = STL + "owltc"; + + public static final String OWL_MAIN = STL + "main"; + + public static final String PP_ERROR = STL + "pperror"; + public static final String PP_ERROR_MAIN = STL + "main"; + public static final String PP_ERROR_DISPLAY = STL + "display"; + public static final String DATASHAPE = STL + "dsmain"; + public static final String TEXT = STL + "text"; + public static final String TURTLE = STL + "turtle"; + public static final String TURTLE_HTML = STL + "hturtle"; + public static final String RDFXML = STL + "rdfxml"; + public static final String ALL = STL + "all"; + public static final String XML = STL + "xml"; + public static final String RDF = STL + "rdf"; + public static final String JSON = STL + "json"; + public static final String JSON_LD = STL + "jsonld"; + public static final String TRIG = STL + "trig"; + public static final String TABLE = STL + "table"; + public static final String HTML = STL + "html"; + public static final String SPARQL = STL + "sparql"; + public static final String RDFRESULT = STL + "result"; + public static final String NAVLAB = STL + "navlab"; + public static final String RDFTYPECHECK = STL + "rdftypecheck"; + public static final String SPINTYPECHECK = STL + "spintypecheck"; + public static final String STL_PROFILE = STL + "profile"; + public static final String STL_START = STL + "start"; + public static final String STL_MAIN = STL + "main"; + public static final String STL_TRACE = STL + "trace"; + public static final String STL_DEFAULT = Processor.STL_DEFAULT; + public static final String STL_DEFAULT_NAMED = STL + "defaultNamed"; + public static final String STL_OPTIMIZE = STL + "optimize"; + public static final String STL_IMPORT = STL + "import"; + public static final String STL_PROCESS = Processor.STL_PROCESS; + public static final String STL_AGGREGATE = Processor.STL_AGGREGATE; + public static final String STL_TRANSFORM = Context.STL_TRANSFORM; + public static final String STL_PREFIX = Context.STL_PREFIX; + public static final String D3 = NSManager.D3; + public static final String D3_ALL = D3 + "all"; + + public static final String[] RESULT_FORMAT = { XML, JSON, RDF }; + public static final String[] GRAPHIC_FORMAT = { D3 + "graphic", D3 + "hierarchy" }; + + // default + public static final String PPRINTER = TURTLE; + public static final String OUT = ASTQuery.OUT; + public static final String IN = ASTQuery.IN; + public static final String IN2 = ASTQuery.IN2; + + + + private TransformerUtils() {} + + /** + * uri#name + * + * @return uri + */ + public static String getURI(String uri) { + if (uri != null && uri.contains("#")) { + return uri.substring(0, uri.indexOf("#")); + } + return uri; + } + + /** + * Definition of synonym + * st:all -> (st:xml st:json ...) + */ + static public List getFormatList(String name) { + switch (name) { + case ALL: + return Arrays.asList(RESULT_FORMAT); + case D3_ALL: + return Arrays.asList(GRAPHIC_FORMAT); + } + return null; + } +} diff --git a/src/main/java/fr/inria/corese/core/util/SPINProcess.java b/src/main/java/fr/inria/corese/core/util/SPINProcess.java index 81fe7c676..dc374fda1 100644 --- a/src/main/java/fr/inria/corese/core/util/SPINProcess.java +++ b/src/main/java/fr/inria/corese/core/util/SPINProcess.java @@ -15,6 +15,7 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import fr.inria.corese.core.transform.TransformerUtils; import org.slf4j.LoggerFactory; /** @@ -138,7 +139,7 @@ public String toSparql(Graph g) throws EngineException { } public String toSparql(Graph g, NSManager nsm) throws EngineException { - Transformer p = Transformer.create(g, Transformer.SPIN); + Transformer p = Transformer.create(g, TransformerUtils.SPIN); if (nsm != null) { p.setNSM(nsm); } diff --git a/src/main/java/fr/inria/corese/core/visitor/ldpath/Result.java b/src/main/java/fr/inria/corese/core/visitor/ldpath/Result.java index e17709d96..89fba17bb 100644 --- a/src/main/java/fr/inria/corese/core/visitor/ldpath/Result.java +++ b/src/main/java/fr/inria/corese/core/visitor/ldpath/Result.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.List; +import fr.inria.corese.core.transform.TransformerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory;; @@ -130,7 +131,7 @@ void json() throws LoadException, IOException { Graph g = Graph.create(); Load ld = Load.create(g); ld.parse(turtle(file)); - Transformer t = Transformer.create(g, Transformer.JSON); + Transformer t = Transformer.create(g, TransformerUtils.JSON); t.write(json(file)); } diff --git a/src/main/java/fr/inria/corese/core/workflow/ShapeWorkflow.java b/src/main/java/fr/inria/corese/core/workflow/ShapeWorkflow.java index 62b106039..16b9f678a 100644 --- a/src/main/java/fr/inria/corese/core/workflow/ShapeWorkflow.java +++ b/src/main/java/fr/inria/corese/core/workflow/ShapeWorkflow.java @@ -11,6 +11,7 @@ import fr.inria.corese.core.transform.Transformer; import fr.inria.corese.core.kgram.api.core.PointerType; import fr.inria.corese.core.sparql.api.IDatatype; +import fr.inria.corese.core.transform.TransformerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,12 +30,12 @@ public class ShapeWorkflow extends SemanticWorkflow { public static final String SHAPE_TRANS_TEST = "/user/corby/home/AAData/sttl/datashape/main"; public static final String SHAPE_TRANS_TEST_LDS = "/user/corby/home/AAData/sttl/datashape/main"; public static final String SHAPE_SHAPE = "/data/shape4shape.ttl"; - public static final String SHAPE_TRANS = Transformer.DATASHAPE; - public static final String FORMAT = Transformer.TURTLE; - public static final String FORMAT_HTML = Transformer.TURTLE_HTML; + public static final String SHAPE_TRANS = TransformerUtils.DATASHAPE; + public static final String FORMAT = TransformerUtils.TURTLE; + public static final String FORMAT_HTML = TransformerUtils.TURTLE_HTML; private static final String NL = "\n"; - static final String MAIN = Transformer.STL_MAIN; + static final String MAIN = TransformerUtils.STL_MAIN; static final String SHAPE_NODE = NSManager.SHAPE + "shapeNode"; static final String SHAPE_GRAPH = NSManager.SHAPE + "shapeGraph"; static final IDatatype dtnode = DatatypeMap.newResource(SHAPE_NODE); @@ -213,7 +214,7 @@ public Graph process(Graph g) { public Graph process(Graph g, Graph s, boolean graph, IDatatype... param) { Transformer t = Transformer.create(g, SHAPE_TRANS); - t.getContext().export(SHAPE_NAME, DatatypeMap.createObject(s)); + t.getContextManager().getContext().export(SHAPE_NAME, DatatypeMap.createObject(s)); try { if (graph) { // check whole graph diff --git a/src/main/java/fr/inria/corese/core/workflow/TransformationProcess.java b/src/main/java/fr/inria/corese/core/workflow/TransformationProcess.java index 7ee4ac7de..157451668 100644 --- a/src/main/java/fr/inria/corese/core/workflow/TransformationProcess.java +++ b/src/main/java/fr/inria/corese/core/workflow/TransformationProcess.java @@ -73,7 +73,7 @@ public Data run(Data data) throws EngineException { res.setDatatypeValue(dt); } res.setProcess(this); - res.setBinding(t.getBinding()); + res.setBinding(t.getContextManager().getBinding()); complete(t, data, res); return res; } @@ -85,10 +85,10 @@ public String stringValue(Data data) { void init(Transformer t, Data data, Context c) { if (c != null) { - t.setContext(c); + t.getContextManager().setContext(c); } if (data.getMappings() != null) { - t.getContext().set(Context.STL_MAPPINGS, DatatypeMap.createObject(data.getMappings())); + t.getContextManager().getContext().set(Context.STL_MAPPINGS, DatatypeMap.createObject(data.getMappings())); } } diff --git a/src/main/java/fr/inria/corese/core/workflow/WorkflowParser.java b/src/main/java/fr/inria/corese/core/workflow/WorkflowParser.java index af804422e..71ce1ee76 100644 --- a/src/main/java/fr/inria/corese/core/workflow/WorkflowParser.java +++ b/src/main/java/fr/inria/corese/core/workflow/WorkflowParser.java @@ -18,6 +18,7 @@ import fr.inria.corese.core.sparql.triple.parser.NSManager; import fr.inria.corese.core.transform.ContextBuilder; import fr.inria.corese.core.transform.Transformer; +import fr.inria.corese.core.transform.TransformerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -525,11 +526,11 @@ ShapeWorkflow datashape(IDatatype dt, boolean shex) throws SafetyException { } Loader.format getFormat(String format) { - if (format.equals(Transformer.TURTLE)) { + if (format.equals(TransformerUtils.TURTLE)) { return Loader.format.TURTLE_FORMAT; - } else if (format.equals(Transformer.RDFXML)) { + } else if (format.equals(TransformerUtils.RDFXML)) { return Loader.format.RDFXML_FORMAT; - } else if (format.equals(Transformer.JSON)) { + } else if (format.equals(TransformerUtils.JSON)) { return Loader.format.JSONLD_FORMAT; } return Loader.format.UNDEF_FORMAT; diff --git a/src/test/java/fr/inria/corese/core/engine/TestQuery1.java b/src/test/java/fr/inria/corese/core/engine/TestQuery1.java index db9147b9a..fcdcef2e4 100644 --- a/src/test/java/fr/inria/corese/core/engine/TestQuery1.java +++ b/src/test/java/fr/inria/corese/core/engine/TestQuery1.java @@ -14,6 +14,7 @@ import javax.xml.parsers.ParserConfigurationException; +import fr.inria.corese.core.transform.TransformerUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -247,10 +248,10 @@ public void typecheckowlrl() throws EngineException, MalformedURLException, Load load.parse(TestQuery1.class.getResource("/primer.owl").getPath()); QueryProcess.create(g); - Transformer t = Transformer.create(g, Transformer.OWLRL); + Transformer t = Transformer.create(g, TransformerUtils.OWLRL); t.process(); - Transformer t2 = Transformer.create(g, Transformer.TURTLE_HTML); - IDatatype dt = t2.process(t.getBinding()); + Transformer t2 = Transformer.create(g, TransformerUtils.TURTLE_HTML); + IDatatype dt = t2.process(t.getContextManager().getBinding()); assertEquals(true, dt.getLabel().contains("")); } @@ -3707,7 +3708,7 @@ public void testrdfxml() throws LoadException, IOException { ld.parse(TestQuery1.class.getResource("/data-test/test/primer.owl").getPath()); g.init(); - Transformer t = Transformer.create(g, Transformer.RDFXML); + Transformer t = Transformer.create(g, TransformerUtils.RDFXML); // Transformer t = Transformer.create(g, RDFXMLNEW); File tempFileRdf = File.createTempFile("temp-rdf", ".rdf"); t.write(tempFileRdf.toString()); @@ -3717,7 +3718,7 @@ public void testrdfxml() throws LoadException, IOException { ld1.parse(tempFileRdf.toString()); g1.init(); - Transformer t2 = Transformer.create(g1, Transformer.TURTLE); + Transformer t2 = Transformer.create(g1, TransformerUtils.TURTLE); File tempFileTtl = File.createTempFile("temp-ttl", ".ttl"); t2.write(tempFileTtl.toString()); @@ -3746,7 +3747,7 @@ public void testTTLabc() throws EngineException, LoadException { exec.query(i); - Transformer t = Transformer.create(g, Transformer.TURTLE); + Transformer t = Transformer.create(g, TransformerUtils.TURTLE); logger.info("testTTLabc {} " , t.transform()); // assertEquals(197, t.transform().length()); } @@ -5544,7 +5545,7 @@ public void testOWLRL() throws EngineException, LoadException { QueryProcess.create(gs); Load ld = Load.create(gs); ld.parse(data + "template/owl/data/primer.owl"); - Transformer t = Transformer.create(gs, Transformer.OWLRL); + Transformer t = Transformer.create(gs, TransformerUtils.OWLRL); IDatatype dt = t.process(); assertEquals(DatatypeMap.FALSE, dt); @@ -5752,17 +5753,17 @@ public void testGTT() throws LoadException, EngineException { ld.parse(localRDF, fr.inria.corese.core.api.Loader.format.TURTLE_FORMAT); ld.parse(localRDFS, fr.inria.corese.core.api.Loader.format.TURTLE_FORMAT); - Transformer t = Transformer.createWE(g, Transformer.TURTLE, RDF.RDF); + Transformer t = Transformer.createWE(g, TransformerUtils.TURTLE, RDF.RDF); String str = normalizeLineEndings(t.transform()); // logger.debug("result:\n {}" + str); assertEquals(6202, str.length()); - t = Transformer.createWE(g, Transformer.TURTLE, RDFS.RDFS); + t = Transformer.createWE(g, TransformerUtils.TURTLE, RDFS.RDFS); str = normalizeLineEndings(t.transform()); // logger.debug(str); assertEquals(3849, str.length()); // TODO: need a more robust test - t = Transformer.create(g, Transformer.TURTLE); + t = Transformer.create(g, TransformerUtils.TURTLE); str = normalizeLineEndings(t.transform()); // logger.debug(str); assertEquals(9859, str.length()); // TODO: need a more robust test @@ -5836,7 +5837,7 @@ public void testTrig() throws LoadException, EngineException { ld.parse(localRDFS, fr.inria.corese.core.api.Loader.format.TURTLE_FORMAT); Property.set(LOAD_IN_DEFAULT_GRAPH, false); - Transformer pp = Transformer.create(g, Transformer.TRIG); + Transformer pp = Transformer.create(g, TransformerUtils.TRIG); String str = normalizeLineEndings(pp.transform()); assertEquals(14928, str.length()); // @Todo: need a more robust test } @@ -5953,7 +5954,7 @@ public void testJoinDistinct() { // logger.debug(map.toString()); assertTrue(1 <= map.size(), () -> "Result " + map.size()); } catch (EngineException e) { - assertEquals(true, e, "Result"); + assertNotNull(e, "Expected an exception but none was thrown"); } } @@ -6318,7 +6319,7 @@ public void testTurtle2() throws EngineException { } // @Test - public void testQV() { + public void testQV() throws EngineException { Graph g = createGraph(); Load.create(g); QueryProcess exec = QueryProcess.create(g); @@ -6336,15 +6337,11 @@ public void testQV() { + "kg:list kg:expand true" + "}" + ""; - try { exec.query(init); // exec.setVisitor(ExpandList.create()); Mappings map = exec.query(query); assertEquals(1, map.size()); - } catch (EngineException ex) { - assertEquals(ex, true); - } } @@ -6601,14 +6598,13 @@ public void testValues() { } catch (EngineException e) { logger.error("Operation failure", e); - org.junit.jupiter.api.Assertions.fail("EngineException occurred: " + e.getMessage()); } } @Test - public void testValues2() { + void testValues2() throws EngineException { String init = "prefix foaf: " + "insert data {" + " foaf:name 'http://www.inria.fr' " @@ -6629,16 +6625,10 @@ public void testValues2() { Graph g = createGraph(); QueryProcess exec = QueryProcess.create(g); - try { exec.query(init); Mappings map = exec.query(query); - //logger.info("map {}",map); assertEquals(3, map.size(), "Result"); - } catch (EngineException e) { - assertEquals(2, e, "Result"); - - } } diff --git a/src/test/java/fr/inria/corese/core/load/QueryLoadTest.java b/src/test/java/fr/inria/corese/core/load/QueryLoadTest.java new file mode 100644 index 000000000..f330d35f2 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/load/QueryLoadTest.java @@ -0,0 +1,304 @@ +package fr.inria.corese.core.load; + +import fr.inria.corese.core.compiler.parser.Pragma; +import fr.inria.corese.core.kgram.core.Query; +import fr.inria.corese.core.query.QueryEngine; +import fr.inria.corese.core.sparql.api.IDatatype; +import fr.inria.corese.core.sparql.exceptions.EngineException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +/** + * Unit tests for the {@link QueryLoad} class. + * This class uses JUnit 5 and Mockito to test various functionalities + * including reading from readers, parsing query names, handling URLs, + * and managing temporary file creation and writing. + */ +class QueryLoadTest { + + private QueryLoad queryLoad; + + @Mock + private QueryEngine mockEngine; + + @Mock + private Query mockQuery; + + @Mock + private IDatatype mockDatatype; + + @TempDir + Path tempDir; + + /** + * Sets up the test environment before each test method. + * Initializes all mocks and creates a new instance of QueryLoad with the mocked QueryEngine. + */ + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + queryLoad = new QueryLoad(mockEngine); + } + + + + /** + * Tests that the {@code read} method correctly reads a single line from a {@link Reader}. + * + * @throws IOException if an I/O error occurs during reading. + */ + @Test + @DisplayName("Should correctly read a single line from a reader") + void testReadSingleLine() throws IOException { + String content = "SELECT * WHERE { ?s ?p ?o }"; + Reader reader = new StringReader(content); + assertEquals(content, queryLoad.read(reader)); + } + + /** + * Tests that the {@code read} method correctly reads multi-line content from a {@link Reader}. + * It also verifies that newline characters are appended correctly between lines. + * + * @throws IOException if an I/O error occurs during reading. + */ + @Test + @DisplayName("Should correctly read multi-line content from a reader") + void testReadMultiLine() throws IOException { + String content = "LINE1\nLINE2\nLINE3"; + Reader reader = new StringReader(content); + assertEquals("LINE1\nLINE2\nLINE3", queryLoad.read(reader)); + } + + /** + * Tests that the {@code read} method correctly handles an empty content from a {@link Reader}. + * + * @throws IOException if an I/O error occurs during reading. + */ + @Test + @DisplayName("Should correctly read empty content from a reader") + void testReadEmpty() throws IOException { + String content = ""; + Reader reader = new StringReader(content); + assertEquals(content, queryLoad.read(reader)); + } + + + /** + * Tests the {@code getName} method to ensure it correctly extracts the base name + * of a file without its extension, handling single and multi-part extensions. + */ + @Test + @DisplayName("Should extract the name without the extension") + void testGetName() { + assertEquals("myfile", queryLoad.getName("myfile.txt")); + assertEquals("document", queryLoad.getName("document")); + assertEquals("archive", queryLoad.getName("archive.gz")); + } + + /** + * Tests the {@code getSuffix} method to ensure it correctly extracts the file extension, + * including the leading dot. It also verifies default suffix for files without extensions. + */ + @Test + @DisplayName("Should extract the suffix with the dot") + void testGetSuffix() { + assertEquals(".txt", queryLoad.getSuffix("myfile.txt")); + assertEquals(".txt", queryLoad.getSuffix("document")); + assertEquals(".gz", queryLoad.getSuffix("archive.gz")); + } + + + /** + * Tests the {@code isURL} method with various valid URL strings to ensure they are correctly identified. + */ + @Test + @DisplayName("Should identify valid URL strings") + void testIsURLValid() { + assertTrue(queryLoad.isURL("http://example.com")); + assertTrue(queryLoad.isURL("https://www.google.com/search?q=test")); + assertTrue(queryLoad.isURL("ftp://user:pass@host/path")); + } + + /** + * Tests the {@code isURL} method with various invalid URL strings to ensure they are correctly identified. + */ + @Test + @DisplayName("Should identify invalid URL strings") + void testIsURLInvalid() { + assertFalse(queryLoad.isURL("not_a_url")); + assertFalse(queryLoad.isURL("www.example.com")); + assertFalse(queryLoad.isURL("http:// invalid.com")); + } + + + /** + * Tests that the {@code getResource} method throws an {@link IOException} + * when attempting to load a non-existent classpath resource. + */ + @Test + @DisplayName("Should throw IOException if classpath resource is not found") + void testGetResourceNotFound() { + assertThrows(IOException.class, () -> queryLoad.getResource("/non_existent_resource.txt")); + } + + + /** + * Tests that the {@code write} method successfully writes string content to a specified file path. + * + * @throws IOException if an I/O error occurs during writing or reading the file. + */ + @Test + @DisplayName("Should write string content to a named file path") + void testWriteWithNameAndStringContent() throws IOException { + String filePath = tempDir.resolve("named_output.txt").toString(); + String content = "Content for named file."; + queryLoad.write(filePath, content); + assertEquals(content, Files.readString(Path.of(filePath))); + } + + /** + * Tests that the {@code write} method successfully writes the string value of an {@link IDatatype} + * to a specified file path. + * + * @throws IOException if an I/O error occurs during writing or reading the file. + */ + @Test + @DisplayName("Should write the string value of an IDatatype to a named file path") + void testWriteWithNameAndIDatatypeContent() throws IOException { + String filePath = tempDir.resolve("datatype_output.txt").toString(); + when(mockDatatype.stringValue()).thenReturn("IDatatype content."); + queryLoad.write(filePath, mockDatatype); + assertEquals("IDatatype content.", Files.readString(Path.of(filePath))); + } + + + /** + * Tests that the {@code parse} method (taking a string name) correctly reads a query + * from a file and defines it in the {@link QueryEngine}. + * It also verifies that {@code setPragma} is called on the resulting {@link Query} object. + * + * @throws LoadException if an error occurs during loading the query. + * @throws EngineException if an error occurs during defining the query in the engine. + * @throws IOException if an I/O error occurs during file operations. + */ + @Test + @DisplayName("Should parse query from a string name and define it in the engine") + void testParseStringName() throws LoadException, EngineException, IOException { + String queryContent = "SELECT * WHERE { ?s ?p ?o }"; + Path tempFile = tempDir.resolve("query.rq"); + Files.writeString(tempFile, queryContent); + + when(mockEngine.defQuery(anyString())).thenReturn(mockQuery); + + queryLoad.parse(tempFile.toString()); + + ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(String.class); + verify(mockEngine).defQuery(queryCaptor.capture()); + assertEquals(queryContent, queryCaptor.getValue()); + + verify(mockQuery).setPragma(Pragma.FILE, tempFile.toString()); + } + + /** + * Tests that the {@code parse} method (taking a string name) throws a {@link LoadException} + * if the specified query file does not exist. + */ + @Test + @DisplayName("Should throw LoadException if query file is not found during parse(String)") + void testParseStringNameFileNotFound() { + assertThrows(LoadException.class, () -> queryLoad.parse("nonexistent.rq")); + } + + /** + * Tests that the {@code parse} method (taking a {@link Reader}) correctly reads a query + * and defines it in the {@link QueryEngine}. + * + * @throws LoadException if an error occurs during loading the query. + * @throws EngineException if an error occurs during defining the query in the engine. + */ + @Test + @DisplayName("Should parse query from a Reader and define it in the engine") + void testParseReader() throws LoadException, EngineException { + String queryContent = "ASK { ?s ?p ?o }"; + Reader reader = new StringReader(queryContent); + + when(mockEngine.defQuery(anyString())).thenReturn(mockQuery); + + queryLoad.parse(reader); + + ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(String.class); + verify(mockEngine).defQuery(queryCaptor.capture()); + assertEquals(queryContent, queryCaptor.getValue()); + } + + + /** + * Tests that the {@code readWE} method correctly reads content from a local file path. + * + * @throws LoadException if an error occurs during loading the query. + * @throws IOException if an I/O error occurs during file operations. + */ + @Test + @DisplayName("Should read content from a local file path") + void testReadWEFromFile() throws LoadException, IOException { + String content = "Content of the local file."; + Path file = tempDir.resolve("local_file.txt"); + Files.writeString(file, content); + + assertEquals(content, queryLoad.readWE(file.toString())); + } + + + /** + * Tests that the {@code readWE} method throws a {@link LoadException} + * when attempting to read a non-existent file. + */ + @Test + @DisplayName("Should throw LoadException for a non-existent file during readWE") + void testReadWEFileNonExistent() { + assertThrows(LoadException.class, () -> queryLoad.readWE("nonexistent_file.txt")); + } + + + /** + * Tests that the {@code basicParse} method correctly parses content from a local file path. + * + * @throws EngineException if an error occurs during parsing or engine operations. + * @throws IOException if an I/O error occurs during file operations. + */ + @Test + @DisplayName("Should parse content from a local file path in basicParse") + void testBasicParseFromFile() throws EngineException, IOException { + String content = "BASIC PARSED CONTENT."; + Path file = tempDir.resolve("basic_parse_file.txt"); + Files.writeString(file, content); + + + assertEquals(content, queryLoad.basicParse(file.toString())); + } + + /** + * Tests that the {@code basicParse} method throws an {@link EngineException} + * if it fails to read the specified file. + */ + @Test + @DisplayName("Should throw EngineException if basicParse fails to read the file") + void testBasicParseFileFailure() { + assertThrows(EngineException.class, () -> queryLoad.basicParse("invalid/nonexistent.txt")); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImplTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImplTest.java index 7d7a15bc0..ffdde398e 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImplTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImplTest.java @@ -13,12 +13,27 @@ import org.antlr.v4.runtime.tree.ParseTreeListener; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.StringReader; import static org.junit.jupiter.api.Assertions.*; -public class TurtleListenerImplTest { +/** + * Unit tests for the TurtleListenerImpl parser. + */ +class TurtleListenerImplTest { + + private static final Logger logger = LoggerFactory.getLogger(TurtleListenerImplTest.class); + + /** + * Parses a Turtle string and returns the RDF model. + * + * @param turtleData Turtle syntax input as a string + * @return parsed RDF model + * @throws Exception if parsing fails + */ private Model parseAndPrintModel(String turtleData) throws Exception { ValueFactory factory = new CoreseAdaptedValueFactory(); @@ -26,94 +41,101 @@ private Model parseAndPrintModel(String turtleData) throws Exception { TurtleLexer lexer = new TurtleLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); TurtleParser parser = new TurtleParser(tokens); - ParseTreeWalker walker = new ParseTreeWalker(); ParseTree tree = parser.turtleDoc(); Model model = new CoreseModel(); TurtleListenerImpl listener = new TurtleListenerImpl(model, factory, null); + ParseTreeWalker walker = new ParseTreeWalker(); walker.walk((ParseTreeListener) listener, tree); return model; } @Test - public void testNamespace() throws Exception { - String turtleData = " @prefix ex: . " + - "ex:subject ex:predicate 1 . "; + void testNamespace() throws Exception { + String turtleData = "@prefix ex: .\n" + + "ex:subject ex:predicate 1 ."; Model model = parseAndPrintModel(turtleData); - assertEquals(model.getNamespaces().size(), 1); + assertEquals(1, model.getNamespaces().size(), "Namespace count mismatch"); } @Test - public void testTypedLiteral() throws Exception { - String turtleData = "@prefix ex: .\n" + - "@prefix xsd: .\n" + - "ex:subject ex:age \"27\"^^xsd:integer ."; + void testTypedLiteral() throws Exception { + String turtleData = """ + @prefix ex: . + @prefix xsd: . + ex:subject ex:age "27"^^xsd:integer . + """; Model model = parseAndPrintModel(turtleData); - assertEquals(model.size(), 1); - assertEquals(model.getNamespaces().size(), 2); - + assertEquals(1, model.size(), "Triple count mismatch"); + assertEquals(2, model.getNamespaces().size(), "Namespace count mismatch"); } @Test - public void testMultipleObjects() throws Exception { - String turtleData = "@prefix ex: .\n" + - "ex:subject ex:knows ex:Alice , ex:Bob ; ex:likes ex:Pizza ."; + void testMultipleObjects() throws Exception { + String turtleData = """ + @prefix ex: . + ex:subject ex:knows ex:Alice , ex:Bob ; + ex:likes ex:Pizza . + """; Model model = parseAndPrintModel(turtleData); - assertEquals(model.size(), 3); - assertEquals(model.getNamespaces().size(), 1); - + assertEquals(3, model.size(), "Triple count mismatch"); + assertEquals(1, model.getNamespaces().size(), "Namespace count mismatch"); } @Test - public void testRDFtype() throws Exception { - String turtleData = "@prefix ex: .\n" + - "ex:Alice a ex:Person .\n" + - "ex:subject ex:knows ex:Alice , ex:Bob ; ex:likes ex:Pizza ."; + void testRDFtype() throws Exception { + String turtleData = """ + @prefix ex: . + ex:Alice a ex:Person . + ex:subject ex:knows ex:Alice , ex:Bob ; + ex:likes ex:Pizza . + """; Model model = parseAndPrintModel(turtleData); - assertEquals(model.size(), 4); - assertEquals(model.getNamespaces().size(), 1); + assertEquals(4, model.size(), "Triple count mismatch"); + assertEquals(1, model.getNamespaces().size(), "Namespace count mismatch"); } @Test - public void testBaseIRI() throws Exception { - String turtleData = "@base .\n" + - "@prefix : .\n" + - "@prefix rdf: . \n" + - "\n" + - " rdf:type rdf:Property .\n" + - ":phone rdf:type rdf:Property ."; + void testBaseIRI() throws Exception { + String turtleData = """ + @base . + @prefix : . + @prefix rdf: . + + rdf:type rdf:Property . + :phone rdf:type rdf:Property . + """; Model model = parseAndPrintModel(turtleData); - assertEquals(model.size(), 2); - assertEquals(model.getNamespaces().size(), 2); + assertEquals(2, model.size(), "Triple count mismatch"); + assertEquals(2, model.getNamespaces().size(), "Namespace count mismatch"); } @Test - public void testTypedIntegerLiteral() throws Exception { - String turtleData = - "@prefix : .\n" + - "@prefix xsd: .\n" + - ":John :age \"42\"^^xsd:integer ."; + void testTypedIntegerLiteral() throws Exception { + String turtleData = """ + @prefix : . + @prefix xsd: . + :John :age "42"^^xsd:integer . + """; Model model = parseAndPrintModel(turtleData); model.objects().forEach(obj -> { assertTrue(obj.isLiteral(), "Expected object to be a literal"); - // test if we can parse the literal to int. Should be ok try { int value = Integer.parseInt(obj.stringValue()); - System.out.println("Parsed integer: " + value); + logger.info("Parsed integer: {}", value); } catch (NumberFormatException e) { fail("Literal is not a valid integer: " + obj.stringValue()); } }); - - assertEquals(model.size(), 1); - assertEquals(model.getNamespaces().size(), 2); + assertEquals(1, model.size(), "Triple count mismatch"); + assertEquals(2, model.getNamespaces().size(), "Namespace count mismatch"); } -} \ No newline at end of file +} diff --git a/src/test/java/fr/inria/corese/core/query/QueryEngineTest.java b/src/test/java/fr/inria/corese/core/query/QueryEngineTest.java new file mode 100644 index 000000000..eab91369f --- /dev/null +++ b/src/test/java/fr/inria/corese/core/query/QueryEngineTest.java @@ -0,0 +1,216 @@ +package fr.inria.corese.core.query; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.core.Query; +import fr.inria.corese.core.sparql.exceptions.EngineException; +import fr.inria.corese.core.sparql.triple.parser.ASTQuery; +import fr.inria.corese.core.sparql.triple.parser.Dataset; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for the QueryEngine class, focusing on the new caching mechanism + * and the 'clean' method fix. + */ +class QueryEngineTest { + + private QueryEngine queryEngine; + private QueryProcess mockQueryProcess; + private Graph mockGraph; + private Dataset mockDataset; + + @BeforeEach + void setUp() { + mockGraph = Mockito.mock(Graph.class); + mockQueryProcess = Mockito.mock(QueryProcess.class); + mockDataset = Mockito.mock(Dataset.class); + + queryEngine = Mockito.spy(new QueryEngine(mockGraph)); + + try { + java.lang.reflect.Field execField = QueryEngine.class.getDeclaredField("exec"); + execField.setAccessible(true); + execField.set(queryEngine, mockQueryProcess); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Failed to inject mock QueryProcess: " + e.getMessage()); + } + + doReturn(mockDataset).when(queryEngine).getDataset(); + } + + @Test + void testDefQuery_CacheHit() throws EngineException { + String queryString = "SELECT * WHERE { ?s ?p ?o }"; + Query mockQuery = Mockito.mock(Query.class); + ASTQuery mockASTQuery = Mockito.mock(ASTQuery.class); + when(mockQuery.isTemplate()).thenReturn(false); + when(mockQuery.toString()).thenReturn(queryString); + when(mockQuery.getAST()).thenReturn(mockASTQuery); + when(mockQueryProcess.compile(eq(queryString), any(Dataset.class))).thenReturn(mockQuery); + + queryEngine.defQuery(queryString); + verify(mockQueryProcess, times(1)).compile(eq(queryString), any(Dataset.class)); + assertEquals(0, queryEngine.getCacheStats().getHits()); + + queryEngine.defQuery(queryString); + verify(mockQueryProcess, times(1)).compile(eq(queryString), any(Dataset.class)); + assertEquals(1, queryEngine.getCacheStats().getHits()); + assertEquals(1, queryEngine.getCacheStats().getMisses()); + assertEquals(1, queryEngine.getCacheStats().getCurrentSize()); + } + + @Test + void testDefQuery_CacheMiss_DifferentQuery() throws EngineException { + String queryString1 = "SELECT * WHERE { ?s ?p ?o }"; + String queryString2 = "SELECT DISTINCT ?s WHERE { ?s a :Class }"; + + Query mockQuery1 = Mockito.mock(Query.class); + ASTQuery mockASTQuery1 = Mockito.mock(ASTQuery.class); + when(mockQuery1.isTemplate()).thenReturn(false); + when(mockQuery1.toString()).thenReturn(queryString1); + when(mockQuery1.getAST()).thenReturn(mockASTQuery1); + + Query mockQuery2 = Mockito.mock(Query.class); + ASTQuery mockASTQuery2 = Mockito.mock(ASTQuery.class); + when(mockQuery2.isTemplate()).thenReturn(false); + when(mockQuery2.toString()).thenReturn(queryString2); + when(mockQuery2.getAST()).thenReturn(mockASTQuery2); + + when(mockQueryProcess.compile(eq(queryString1), any(Dataset.class))).thenReturn(mockQuery1); + when(mockQueryProcess.compile(eq(queryString2), any(Dataset.class))).thenReturn(mockQuery2); + + + queryEngine.defQuery(queryString1); + verify(mockQueryProcess, times(1)).compile(eq(queryString1), any(Dataset.class)); + assertEquals(0, queryEngine.getCacheStats().getHits()); + assertEquals(1, queryEngine.getCacheStats().getMisses()); + assertEquals(1, queryEngine.getCacheStats().getCurrentSize()); + + queryEngine.defQuery(queryString2); + verify(mockQueryProcess, times(1)).compile(eq(queryString2), any(Dataset.class)); + assertEquals(0, queryEngine.getCacheStats().getHits()); + assertEquals(2, queryEngine.getCacheStats().getMisses()); + assertEquals(2, queryEngine.getCacheStats().getCurrentSize()); + } + + @Test + void testDefQuery_CacheEviction() throws EngineException { + queryEngine = Mockito.spy(new QueryEngine(mockGraph, 1)); + try { + java.lang.reflect.Field execField = QueryEngine.class.getDeclaredField("exec"); + execField.setAccessible(true); + execField.set(queryEngine, mockQueryProcess); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Failed to inject mock QueryProcess: " + e.getMessage()); + } + doReturn(mockDataset).when(queryEngine).getDataset(); + + + String queryString1 = "SELECT * WHERE { ?s ?p ?o }"; + String queryString2 = "ASK { ?s ?p ?o }"; + + Query mockQuery1 = Mockito.mock(Query.class); + ASTQuery mockASTQuery1 = Mockito.mock(ASTQuery.class); + when(mockQuery1.isTemplate()).thenReturn(false); + when(mockQuery1.toString()).thenReturn(queryString1); + when(mockQuery1.getAST()).thenReturn(mockASTQuery1); + + Query mockQuery2 = Mockito.mock(Query.class); + ASTQuery mockASTQuery2 = Mockito.mock(ASTQuery.class); + when(mockQuery2.isTemplate()).thenReturn(false); + when(mockQuery2.toString()).thenReturn(queryString2); + when(mockQuery2.getAST()).thenReturn(mockASTQuery2); + + when(mockQueryProcess.compile(eq(queryString1), any(Dataset.class))).thenReturn(mockQuery1); + when(mockQueryProcess.compile(eq(queryString2), any(Dataset.class))).thenReturn(mockQuery2); + + + queryEngine.defQuery(queryString1); + assertEquals(1, queryEngine.getCacheStats().getCurrentSize()); + assertEquals(1, queryEngine.getCacheStats().getMisses()); + + queryEngine.defQuery(queryString2); + assertEquals(1, queryEngine.getCacheStats().getCurrentSize()); + assertEquals(2, queryEngine.getCacheStats().getMisses()); + + queryEngine.defQuery(queryString1); + verify(mockQueryProcess, times(2)).compile(eq(queryString1), any(Dataset.class)); + assertEquals(0, queryEngine.getCacheStats().getHits()); + assertEquals(3, queryEngine.getCacheStats().getMisses()); + } + + @Test + void testDefQuery_NoCacheForUpdateQueries() throws EngineException { + String updateQuery = "INSERT DATA { :s :p :o }"; + Query mockQuery = Mockito.mock(Query.class); + ASTQuery mockASTQuery = Mockito.mock(ASTQuery.class); + when(mockQuery.isTemplate()).thenReturn(false); + when(mockQuery.toString()).thenReturn(updateQuery); + when(mockQuery.getAST()).thenReturn(mockASTQuery); + when(mockQueryProcess.compile(eq(updateQuery), any(Dataset.class))).thenReturn(mockQuery); + + queryEngine.defQuery(updateQuery); + verify(mockQueryProcess, times(1)).compile(eq(updateQuery), any(Dataset.class)); + assertEquals(0, queryEngine.getCacheStats().getHits()); + assertEquals(1, queryEngine.getCacheStats().getMisses()); + assertEquals(0, queryEngine.getCacheStats().getCurrentSize()); + } + + @Test + void testClearCompilationCache() throws EngineException { + String queryString = "SELECT * WHERE { ?s ?p ?o }"; + Query mockQuery = Mockito.mock(Query.class); + ASTQuery mockASTQuery = Mockito.mock(ASTQuery.class); + when(mockQuery.isTemplate()).thenReturn(false); + when(mockQuery.getAST()).thenReturn(mockASTQuery); + when(mockQueryProcess.compile(eq(queryString), any(Dataset.class))).thenReturn(mockQuery); + + queryEngine.defQuery(queryString); + assertEquals(1, queryEngine.getCacheStats().getCurrentSize()); + + queryEngine.clearCompilationCache(); + assertEquals(0, queryEngine.getCacheStats().getCurrentSize()); + assertEquals(0, queryEngine.getCacheStats().getHits()); + assertEquals(0, queryEngine.getCacheStats().getMisses()); + } + + @Test + void testCleanMethod() { + ArrayList internalList = null; + try { + java.lang.reflect.Field listField = QueryEngine.class.getDeclaredField("list"); + listField.setAccessible(true); + internalList = (ArrayList) listField.get(queryEngine); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Failed to access private 'list' field: " + e.getMessage()); + } + + Query query1 = Mockito.mock(Query.class); + when(query1.isFail()).thenReturn(false); + Query query2 = Mockito.mock(Query.class); + when(query2.isFail()).thenReturn(true); + Query query3 = Mockito.mock(Query.class); + when(query3.isFail()).thenReturn(false); + + internalList.add(query1); + internalList.add(query2); + internalList.add(query3); + + assertEquals(3, internalList.size()); + + queryEngine.clean(); + + assertEquals(2, internalList.size()); + assertTrue(internalList.contains(query1)); + assertFalse(internalList.contains(query2)); + assertTrue(internalList.contains(query3)); + } + + +}