diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/Literal.java b/src/main/java/fr/inria/corese/core/next/data/api/term/Literal.java
index cf37609da..e3a1d23fd 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/Literal.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/Literal.java
@@ -22,6 +22,7 @@ public interface Literal extends Value {
/**
* @return the lexical value of the literal
*/
+ @Override
String getLabel();
/**
@@ -52,6 +53,7 @@ public interface Literal extends Value {
/**
* @return the value of the literal as an int if possible
*/
+ @Override
int intValue();
/**
@@ -77,6 +79,7 @@ public interface Literal extends Value {
/**
* @return the value of the literal as a double if possible
*/
+ @Override
double doubleValue();
/**
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java
index 5b53b944e..5b1997eba 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDate.java
@@ -23,7 +23,7 @@ public final class SimpleDate extends AbstractTemporalPointLiteral {
private final XMLGregorianCalendar calendar;
public SimpleDate(String lexicalValue) {
- this(lexicalValue, XSDDatatype.DATE.getIRI(), XSDDatatype.DATE);
+ this(lexicalValue, XSDDatatype.DATE.getIRI());
}
public SimpleDate(XMLGregorianCalendar calendar) {
@@ -33,11 +33,6 @@ public SimpleDate(XMLGregorianCalendar calendar) {
}
public SimpleDate(String lexicalValue, IRI datatype) {
- this(lexicalValue, datatype, null);
- }
-
- @SuppressWarnings("java:S1172") // Kept for source compatibility with the temporal literal API.
- public SimpleDate(String lexicalValue, IRI datatype, CoreDatatype coreDatatype) {
super(datatype == null ? XSDDatatype.DATE.getIRI() : datatype);
this.label = Objects.requireNonNull(lexicalValue, "lexicalValue");
this.calendar = DatatypeFactory.newDefaultInstance().newXMLGregorianCalendar(lexicalValue);
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java
index 5a1c0c84c..957ff29fa 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDateTime.java
@@ -23,7 +23,7 @@ public final class SimpleDateTime extends AbstractTemporalPointLiteral {
private final XMLGregorianCalendar calendar;
public SimpleDateTime(String lexicalValue) {
- this(lexicalValue, XSDDatatype.DATETIME.getIRI(), XSDDatatype.DATETIME);
+ this(lexicalValue, XSDDatatype.DATETIME.getIRI());
}
public SimpleDateTime(XMLGregorianCalendar calendar) {
@@ -33,11 +33,6 @@ public SimpleDateTime(XMLGregorianCalendar calendar) {
}
public SimpleDateTime(String lexicalValue, IRI datatype) {
- this(lexicalValue, datatype, null);
- }
-
- @SuppressWarnings("java:S1172") // Kept for source compatibility with the temporal literal API.
- public SimpleDateTime(String lexicalValue, IRI datatype, CoreDatatype coreDatatype) {
super(datatype == null ? XSDDatatype.DATETIME.getIRI() : datatype);
this.label = Objects.requireNonNull(lexicalValue, "lexicalValue");
this.calendar = DatatypeFactory.newDefaultInstance().newXMLGregorianCalendar(lexicalValue);
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDuration.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDuration.java
index 65328f2a7..4855eb280 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDuration.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleDuration.java
@@ -43,7 +43,7 @@ private void readObject(ObjectInputStream input) throws IOException, ClassNotFou
}
public SimpleDuration(String lexicalValue) {
- this(lexicalValue, XSDDatatype.DURATION.getIRI(), XSDDatatype.DURATION);
+ this(lexicalValue, XSDDatatype.DURATION.getIRI());
}
public SimpleDuration(TemporalAmount temporalAmount) {
@@ -53,12 +53,7 @@ public SimpleDuration(TemporalAmount temporalAmount) {
}
public SimpleDuration(String lexicalValue, IRI datatype) {
- this(lexicalValue, datatype, null);
- }
-
- @SuppressWarnings("java:S1172") // Kept for source compatibility with the temporal literal API.
- public SimpleDuration(String lexicalValue, IRI datatype, CoreDatatype coreDatatype) {
- super();
+ super(datatype);
this.label = Objects.requireNonNull(lexicalValue, "lexicalValue");
this.temporalAmount = parseTemporalAmount(lexicalValue);
}
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java
index d4ab34fbe..cfa0701df 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTime.java
@@ -23,7 +23,7 @@ public final class SimpleTime extends AbstractTemporalPointLiteral {
private final XMLGregorianCalendar calendar;
public SimpleTime(String lexicalValue) {
- this(lexicalValue, XSDDatatype.TIME.getIRI(), XSDDatatype.TIME);
+ this(lexicalValue, XSDDatatype.TIME.getIRI());
}
public SimpleTime(XMLGregorianCalendar calendar) {
@@ -33,11 +33,6 @@ public SimpleTime(XMLGregorianCalendar calendar) {
}
public SimpleTime(String lexicalValue, IRI datatype) {
- this(lexicalValue, datatype, null);
- }
-
- @SuppressWarnings("java:S1172") // Kept for source compatibility with the temporal literal API.
- public SimpleTime(String lexicalValue, IRI datatype, CoreDatatype coreDatatype) {
super(datatype == null ? XSDDatatype.TIME.getIRI() : datatype);
this.label = Objects.requireNonNull(lexicalValue, "lexicalValue");
this.calendar = DatatypeFactory.newDefaultInstance().newXMLGregorianCalendar(lexicalValue);
diff --git a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTriple.java b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTriple.java
index f03ddcb4f..4f7403b81 100644
--- a/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTriple.java
+++ b/src/main/java/fr/inria/corese/core/next/data/api/term/SimpleTriple.java
@@ -39,7 +39,6 @@ public boolean equals(Object other) {
}
@Override
- @SuppressWarnings("NullableProblems")
public String toString() {
return "<<(" + subject + " " + predicate + " " + object + ")>>";
}
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/jsonld/package-info.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/jsonld/package-info.java
deleted file mode 100644
index df9025a34..000000000
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/jsonld/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Configuration shared by the JSON-LD parser and serializer implementations.
- */
-package fr.inria.corese.core.next.data.impl.io.jsonld;
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java
index ef086a53d..dd02d636d 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParser.java
@@ -172,10 +172,10 @@ private void clearAllCharactersBuffers() {
}
}
- /*
- * The algorithm in W3C recommendation is based on DOM processing, but this implementation is made in SAX.
+ /**
+ * The algorithm in W3C recommendation is based on DOM processing, but this implementation is made in SAX.
* To reconcile both approaches, the "local values" are stored in a pile of ProcessingContext. The IRI mapping are shared independently.
- * All operations except the ones that create literals are done ine this function.
+ * All operations except the ones that create literals are done in this function.
*/
@SuppressWarnings({"java:S3776", "java:S3398", "java:S125"})
private void startProcessElement(String qName, Attributes attrs) {
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfxml/RDFXMLStatementEmitter.java b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfxml/RDFXMLStatementEmitter.java
index 1b86b0361..6981fed3c 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfxml/RDFXMLStatementEmitter.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/io/parser/rdfxml/RDFXMLStatementEmitter.java
@@ -11,6 +11,7 @@
import org.xml.sax.Attributes;
import java.util.Optional;
+import java.util.regex.Pattern;
import static fr.inria.corese.core.next.data.impl.io.parser.rdfxml.RDFXMLUtils.*;
@@ -22,6 +23,8 @@ public class RDFXMLStatementEmitter {
private static final Logger logger = LoggerFactory.getLogger(RDFXMLStatementEmitter.class);
+ private static final Pattern CONTAINER_MEMBERSHIP_PATTERN = Pattern.compile("^_\\d+$");
+
private final Model model;
private final ValueFactory factory;
@@ -138,7 +141,7 @@ public void emitPropertyAttribute(Resource subject, Attributes attrs) {
"rdf:li cannot be used as property attribute. " +
"It can only be used as property element inside containers.");
}
- if (attrLocal.matches("^_\\d+$")) {
+ if (CONTAINER_MEMBERSHIP_PATTERN.matcher(attrLocal).matches()) {
throw new ParsingException(
"rdf:" + attrLocal + " cannot be used as property attribute. " +
"Container membership properties can only be used as property elements.");
diff --git a/src/main/java/fr/inria/corese/core/next/data/impl/model/EmptyModel.java b/src/main/java/fr/inria/corese/core/next/data/impl/model/EmptyModel.java
index 00b302f99..702979984 100644
--- a/src/main/java/fr/inria/corese/core/next/data/impl/model/EmptyModel.java
+++ b/src/main/java/fr/inria/corese/core/next/data/impl/model/EmptyModel.java
@@ -62,7 +62,6 @@ public Optional removeNamespace(String prefix) {
// -- Statement operations: read-only and always empty --
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return emptySet.iterator();
}
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractModel.java b/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractModel.java
index 5180eee40..e7b952e37 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractModel.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/model/AbstractModel.java
@@ -263,7 +263,6 @@ private Statement findNext() {
}
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return new ValueSetIterator(AbstractModel.this.iterator());
}
@@ -308,7 +307,6 @@ public boolean removeAll(Collection> collection) {
}
@Override
- @SuppressWarnings("NullableProblems")
public Object[] toArray() {
Iterator iterator = AbstractModel.this.iterator();
try {
@@ -323,7 +321,6 @@ public Object[] toArray() {
}
@Override
- @SuppressWarnings("NullableProblems")
public T[] toArray(T[] array) {
Iterator iterator = AbstractModel.this.iterator();
try {
@@ -371,7 +368,6 @@ public boolean addAll(Collection extends V> collection) {
}
@Override
- @SuppressWarnings("NullableProblems")
public boolean retainAll(Collection> collection) {
Iterator iterator = iterator();
try {
@@ -431,7 +427,6 @@ public boolean contains(Object object) {
}
@Override
- @SuppressWarnings("NullableProblems")
public Object[] toArray() {
Iterator iterator = iterator();
try {
@@ -448,7 +443,6 @@ public Object[] toArray() {
}
@Override
- @SuppressWarnings("NullableProblems")
public T[] toArray(T[] array) {
Iterator iterator = iterator();
try {
@@ -523,7 +517,6 @@ public boolean addAll(Collection extends Statement> collection) {
}
@Override
- @SuppressWarnings("NullableProblems")
public boolean retainAll(Collection> collection) {
boolean modified = false;
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/model/ReadOnlyModel.java b/src/main/java/fr/inria/corese/core/next/data/spi/model/ReadOnlyModel.java
index 2d4a8ed86..236dcae3e 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/model/ReadOnlyModel.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/model/ReadOnlyModel.java
@@ -107,7 +107,6 @@ public Model filter(Resource subject, IRI predicate, Value object, Resource... c
* Returns an unmodifiable iterator over the statements in the model.
*/
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return Collections.unmodifiableSet(delegate).iterator();
}
diff --git a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java
index 7b3aa4cb1..10ff2fa7e 100644
--- a/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java
+++ b/src/main/java/fr/inria/corese/core/next/data/spi/term/literal/AbstractDuration.java
@@ -11,6 +11,7 @@
import fr.inria.corese.core.next.data.api.literal.CoreDatatype;
import fr.inria.corese.core.next.data.api.literal.XSDDatatype;
+import fr.inria.corese.core.next.data.api.term.IRI;
/**
* Abstract class representing a duration literal in RDF.
@@ -21,7 +22,16 @@ public abstract class AbstractDuration extends AbstractLiteral implements Compar
* Constructor for AbstractDuration.
*/
protected AbstractDuration() {
- super(XSDDatatype.DURATION.getIRI());
+ this(XSDDatatype.DURATION.getIRI());
+ }
+
+ /**
+ * Constructor for AbstractDuration with explicit datatype.
+ *
+ * @param datatype the datatype IRI, or null for default duration
+ */
+ protected AbstractDuration(IRI datatype) {
+ super(datatype == null ? XSDDatatype.DURATION.getIRI() : datatype);
}
@Override
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java
index d168e387e..b52f1ebfc 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryEvaluationException.java
@@ -3,6 +3,7 @@
/**
* Thrown when a syntactically valid query fails during evaluation (execution).
*/
+@SuppressWarnings("java:S110")
public class QueryEvaluationException extends QueryException {
/**
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java
index f97a137db..0cc4162c6 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QuerySyntaxException.java
@@ -3,6 +3,7 @@
/**
* Thrown when a SPARQL query or update string contains invalid syntax.
*/
+@SuppressWarnings("java:S110")
public class QuerySyntaxException extends QueryException {
private final int line;
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java
index a9ad2cee0..d5d75c31a 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/QueryTimeoutException.java
@@ -3,6 +3,7 @@
/**
* Thrown when a query evaluation exceeds its configured timeout limit.
*/
+@SuppressWarnings("java:S110")
public class QueryTimeoutException extends QueryEvaluationException {
/**
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java b/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java
index 2b5c73091..30296af26 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/exception/RepositoryException.java
@@ -7,6 +7,7 @@
* Thrown when an operation on a {@link Repository}
* or {@link RepositoryConnection} fails.
*/
+@SuppressWarnings("java:S110")
public class RepositoryException extends QueryException {
/**
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/repository/MaterializedResults.java b/src/main/java/fr/inria/corese/core/next/query/api/repository/MaterializedResults.java
index 45bedbd27..7f2cb8c9d 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/repository/MaterializedResults.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/repository/MaterializedResults.java
@@ -134,7 +134,6 @@ public Value getValue(String name) {
}
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return values.entrySet().stream()
.map(entry -> new ImmutableBinding(entry.getKey(), entry.getValue()))
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/result/StatementResult.java b/src/main/java/fr/inria/corese/core/next/query/api/result/StatementResult.java
index c2eeacf9f..43da14775 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/result/StatementResult.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/result/StatementResult.java
@@ -30,7 +30,6 @@ public interface StatementResult extends Closeable, Iterable {
Statement next();
@Override
- @SuppressWarnings("NullableProblems")
default Iterator iterator() {
return new Iterator<>() {
@Override
diff --git a/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java b/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java
index 6b6cef4d6..e01f3012e 100644
--- a/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java
+++ b/src/main/java/fr/inria/corese/core/next/query/api/result/TupleQueryResult.java
@@ -56,7 +56,6 @@ public interface TupleQueryResult extends Closeable, Iterable {
* @return an iterator over the binding sets in this result
*/
@Override
- @SuppressWarnings("NullableProblems")
default Iterator iterator() {
return new Iterator<>() {
@Override
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ResultsImpl.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ResultsImpl.java
index 0eef3ecbc..fa490c658 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ResultsImpl.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/ResultsImpl.java
@@ -22,7 +22,6 @@ public List getSelect() {
}
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
Iterator it = maps.iterator();
return new Iterator<>() {
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java
index aad20fe07..c3ab90590 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/KgramEventDispatcher.java
@@ -17,7 +17,6 @@ public class KgramEventDispatcher implements Iterable {
List observers = new ArrayList<>();
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return observers.iterator();
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java
index 163e9bc46..c94694f9b 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/PathMappingBuffer.java
@@ -58,7 +58,6 @@ public synchronized void put(Mapping val, boolean next) {
}
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return this;
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/query/AbstractCoreseQuery.java b/src/main/java/fr/inria/corese/core/next/query/impl/query/AbstractCoreseQuery.java
index f99104dc8..abc1dae48 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/query/AbstractCoreseQuery.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/query/AbstractCoreseQuery.java
@@ -135,7 +135,6 @@ public Value getValue(String name) {
}
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return map.entrySet().stream()
.map(e -> new CoreseBinding(e.getKey(), e.getValue()))
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java
index 385887ba6..9d9004b39 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java
@@ -20,11 +20,7 @@ public static DatasetClauseAst none() {
@Override
public void accept(AstVisitor visitor) {
visitor.visit(this);
- this.graphs.forEach(iriAst -> {
- iriAst.accept(visitor);
- });
- this.namedGraphs.forEach(iriAst -> {
- iriAst.accept(visitor);
- });
+ this.graphs.forEach(iriAst -> iriAst.accept(visitor));
+ this.namedGraphs.forEach(iriAst -> iriAst.accept(visitor));
}
}
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java
index cb6a5f872..efb7d12e2 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java
@@ -82,9 +82,7 @@ public void accept(AstVisitor visitor) {
visitor.visit(this);
this.prologue.accept(visitor);
this.datasetClause.accept(visitor);
- this.described.forEach(termAst -> {
- termAst.accept(visitor);
- });
+ this.described.forEach(termAst -> termAst.accept(visitor));
this.whereClause.accept(visitor);
this.valuesClause.accept(visitor);
this.solutionModifier.accept(visitor);
diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/PrefixDeclarationAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/PrefixDeclarationAst.java
index ed29d202a..45e621475 100644
--- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/PrefixDeclarationAst.java
+++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/PrefixDeclarationAst.java
@@ -17,7 +17,7 @@ public record PrefixDeclarationAst(String prefix, IriAst namespace) implements V
throw new IllegalArgumentException("prefix must be non-null");
}
prefix = stripTrailingColon(prefix);
- namespace = Objects.requireNonNull(namespace, "namespace");
+ Objects.requireNonNull(namespace, "namespace");
if(! namespace.raw().isEmpty()) {
namespace = new IriAst(stripAngleBrackets(namespace.raw()));
}
diff --git a/src/main/java/fr/inria/corese/core/next/storage/impl/model/StorageModel.java b/src/main/java/fr/inria/corese/core/next/storage/impl/model/StorageModel.java
index d842077f7..877e264e8 100644
--- a/src/main/java/fr/inria/corese/core/next/storage/impl/model/StorageModel.java
+++ b/src/main/java/fr/inria/corese/core/next/storage/impl/model/StorageModel.java
@@ -208,7 +208,6 @@ public Model filter(Resource subject, IRI predicate, Value object, Resource... c
return new FilteredModel(this, subject, predicate, object, contexts) {
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return StorageModel.this.getFilterIterator(subject, predicate, object, contexts);
}
@@ -245,7 +244,6 @@ public Optional removeNamespace(String prefix) {
}
@Override
- @SuppressWarnings("NullableProblems")
public Iterator iterator() {
return getFilterIterator(null, null, null);
}
diff --git a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTest.java b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTest.java
index 977fb25d8..cbc4f24b8 100644
--- a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTest.java
@@ -15,7 +15,6 @@
import org.junit.jupiter.params.provider.ValueSource;
import fr.inria.corese.core.next.data.api.exception.IncorrectOperationException;
-import fr.inria.corese.core.next.data.api.literal.CoreDatatype;
import fr.inria.corese.core.next.data.api.literal.XSDDatatype;
class SimpleDateTest {
@@ -50,13 +49,10 @@ void constructsFromCalendar() {
@Test
void handlesDatatypeConstructors() {
assertEquals(new SimpleDate("2024-02-29Z"), new SimpleDate("2024-02-29Z", null));
- assertEquals(new SimpleDate("2024-02-29Z"), new SimpleDate("2024-02-29Z", null, null));
IRI custom = new SimpleIRI("urn:custom:temporal");
SimpleDate literal = new SimpleDate("2024-02-29Z", custom);
assertEquals(custom, literal.getDatatype());
assertEquals(XSDDatatype.DATE, literal.getCoreDatatype());
- assertEquals(XSDDatatype.DATE, new SimpleDate("2024-02-29Z", custom, CoreDatatype.NONE).getCoreDatatype());
- assertEquals(custom, new SimpleDate("2024-02-29Z", custom, XSDDatatype.STRING).getDatatype());
}
@Test
@@ -114,7 +110,6 @@ void rejectsNullLexicalValues() {
assertEquals("lexicalValue", assertThrows(NullPointerException.class,
() -> new SimpleDate((String) null)).getMessage());
assertThrows(NullPointerException.class, () -> new SimpleDate(null, null));
- assertThrows(NullPointerException.class, () -> new SimpleDate(null, null, null));
}
@ParameterizedTest
diff --git a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTimeTest.java b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTimeTest.java
index be1a74668..d2cc97081 100644
--- a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTimeTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDateTimeTest.java
@@ -15,7 +15,6 @@
import org.junit.jupiter.params.provider.ValueSource;
import fr.inria.corese.core.next.data.api.exception.IncorrectOperationException;
-import fr.inria.corese.core.next.data.api.literal.CoreDatatype;
import fr.inria.corese.core.next.data.api.literal.XSDDatatype;
class SimpleDateTimeTest {
@@ -50,13 +49,10 @@ void constructsFromCalendar() {
@Test
void handlesDatatypeConstructors() {
assertEquals(new SimpleDateTime("2024-02-29T12:30:45Z"), new SimpleDateTime("2024-02-29T12:30:45Z", null));
- assertEquals(new SimpleDateTime("2024-02-29T12:30:45Z"), new SimpleDateTime("2024-02-29T12:30:45Z", null, null));
IRI custom = new SimpleIRI("urn:custom:temporal");
SimpleDateTime literal = new SimpleDateTime("2024-02-29T12:30:45Z", custom);
assertEquals(custom, literal.getDatatype());
assertEquals(XSDDatatype.DATETIME, literal.getCoreDatatype());
- assertEquals(XSDDatatype.DATETIME, new SimpleDateTime("2024-02-29T12:30:45Z", custom, CoreDatatype.NONE).getCoreDatatype());
- assertEquals(custom, new SimpleDateTime("2024-02-29T12:30:45Z", custom, XSDDatatype.STRING).getDatatype());
}
@Test
@@ -114,7 +110,6 @@ void rejectsNullLexicalValues() {
assertEquals("lexicalValue", assertThrows(NullPointerException.class,
() -> new SimpleDateTime((String) null)).getMessage());
assertThrows(NullPointerException.class, () -> new SimpleDateTime(null, null));
- assertThrows(NullPointerException.class, () -> new SimpleDateTime(null, null, null));
}
@ParameterizedTest
diff --git a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDurationTest.java b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDurationTest.java
index 9009f7b76..07bd6e8f9 100644
--- a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDurationTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleDurationTest.java
@@ -19,7 +19,6 @@
import org.junit.jupiter.params.provider.ValueSource;
import fr.inria.corese.core.next.data.api.exception.IncorrectOperationException;
-import fr.inria.corese.core.next.data.api.literal.CoreDatatype;
import fr.inria.corese.core.next.data.api.literal.XSDDatatype;
class SimpleDurationTest {
@@ -69,15 +68,14 @@ void constructsFromTemporalAmounts() {
}
@Test
- void retainsDurationDatatypeForAllConstructors() {
+ void handlesDatatypeConstructors() {
SimpleDuration expected = new SimpleDuration("PT1S");
assertEquals(expected, new SimpleDuration("PT1S", null));
- assertEquals(expected, new SimpleDuration("PT1S", null, null));
assertEquals(expected, new SimpleDuration("PT1S", XSDDatatype.DURATION.getIRI()));
- assertEquals(expected, new SimpleDuration("PT1S", new SimpleIRI("urn:custom:duration")));
- assertEquals(expected, new SimpleDuration("PT1S", XSDDatatype.STRING.getIRI(), CoreDatatype.NONE));
- assertEquals(XSDDatatype.DURATION,
- new SimpleDuration("PT1S", null, XSDDatatype.STRING).getCoreDatatype());
+ IRI custom = new SimpleIRI("urn:custom:duration");
+ SimpleDuration literal = new SimpleDuration("PT1S", custom);
+ assertEquals(custom, literal.getDatatype());
+ assertEquals(XSDDatatype.DURATION, literal.getCoreDatatype());
}
@ParameterizedTest
@@ -159,7 +157,6 @@ void rejectsNullLexicalValues() {
assertEquals("lexicalValue", assertThrows(NullPointerException.class,
() -> new SimpleDuration((String) null)).getMessage());
assertThrows(NullPointerException.class, () -> new SimpleDuration(null, null));
- assertThrows(NullPointerException.class, () -> new SimpleDuration(null, null, null));
}
@ParameterizedTest
diff --git a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleTimeTest.java b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleTimeTest.java
index d40979151..d778dc130 100644
--- a/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleTimeTest.java
+++ b/src/test/java/fr/inria/corese/core/next/data/api/term/SimpleTimeTest.java
@@ -15,7 +15,6 @@
import org.junit.jupiter.params.provider.ValueSource;
import fr.inria.corese.core.next.data.api.exception.IncorrectOperationException;
-import fr.inria.corese.core.next.data.api.literal.CoreDatatype;
import fr.inria.corese.core.next.data.api.literal.XSDDatatype;
class SimpleTimeTest {
@@ -50,13 +49,10 @@ void constructsFromCalendar() {
@Test
void handlesDatatypeConstructors() {
assertEquals(new SimpleTime("12:30:45Z"), new SimpleTime("12:30:45Z", null));
- assertEquals(new SimpleTime("12:30:45Z"), new SimpleTime("12:30:45Z", null, null));
IRI custom = new SimpleIRI("urn:custom:temporal");
SimpleTime literal = new SimpleTime("12:30:45Z", custom);
assertEquals(custom, literal.getDatatype());
assertEquals(XSDDatatype.TIME, literal.getCoreDatatype());
- assertEquals(XSDDatatype.TIME, new SimpleTime("12:30:45Z", custom, CoreDatatype.NONE).getCoreDatatype());
- assertEquals(custom, new SimpleTime("12:30:45Z", custom, XSDDatatype.STRING).getDatatype());
}
@Test
@@ -116,7 +112,6 @@ void rejectsNullLexicalValues() {
assertEquals("lexicalValue", assertThrows(NullPointerException.class,
() -> new SimpleTime((String) null)).getMessage());
assertThrows(NullPointerException.class, () -> new SimpleTime(null, null));
- assertThrows(NullPointerException.class, () -> new SimpleTime(null, null, null));
}
@ParameterizedTest
diff --git a/tools/extract_sonar_alerts.py b/tools/extract_sonar_alerts.py
new file mode 100755
index 000000000..d981cc758
--- /dev/null
+++ b/tools/extract_sonar_alerts.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+"""
+Extract SonarLint alerts and diagnostics directly from the VS Code SonarLint storage.
+Connects to the H2 database maintained by the SonarLint language server in VS Code.
+"""
+
+import argparse
+import json
+import os
+import subprocess
+import sys
+from collections import Counter
+from pathlib import Path
+
+SONARLINT_JAR = Path.home() / ".vscode/extensions/sonarsource.sonarlint-vscode-5.9.1-linux-x64/server/sonarlint-ls.jar"
+DB_URL = f"jdbc:h2:{Path.home()}/.sonarlint/storage/h2/sq-ide;AUTO_SERVER=TRUE"
+
+
+def query_findings(prefix="src/main/java/fr/inria/corese/core/next"):
+ sql = f"""SELECT IDE_RELATIVE_FILE_PATH, RULE_KEY, START_LINE, MESSAGE, FINDING_TYPE
+ FROM KNOWN_FINDINGS
+ WHERE IDE_RELATIVE_FILE_PATH LIKE '{prefix}%'
+ ORDER BY IDE_RELATIVE_FILE_PATH, START_LINE;"""
+
+ cmd = [
+ "java", "-cp", str(SONARLINT_JAR),
+ "org.h2.tools.Shell",
+ "-url", DB_URL,
+ "-user", "sa", "-password", "",
+ "-sql", sql
+ ]
+ res = subprocess.run(cmd, capture_output=True, text=True, check=True)
+
+ findings = []
+ for line in res.stdout.splitlines():
+ parts = [p.strip() for p in line.split("|")]
+ if len(parts) >= 5 and parts[0].startswith(prefix):
+ rel_path = parts[0]
+ if os.path.exists(rel_path):
+ findings.append({
+ "file": rel_path,
+ "rule": parts[1],
+ "line": int(parts[2]) if parts[2].isdigit() else 0,
+ "message": parts[3],
+ "type": parts[4]
+ })
+ return findings
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Extract active SonarLint alerts from VS Code H2 database.")
+ parser.add_argument("--path", default="src/main/java/fr/inria/corese/core/next", help="Path prefix to filter files")
+ parser.add_argument("--json", dest="json_out", help="Path to write JSON output")
+ parser.add_argument("--summary", action="store_true", help="Print summary by rule and by file")
+ args = parser.parse_args()
+
+ findings = query_findings(args.path)
+
+ files_map = {}
+ rule_counter = Counter()
+ for f in findings:
+ files_map.setdefault(f["file"], []).append(f)
+ rule_counter[f["rule"]] += 1
+
+ print(f"SonarLint Analysis Report for: {args.path}")
+ print(f"Total findings on active files: {len(findings)} across {len(files_map)} files")
+
+ if args.summary or not args.json_out:
+ print("\n--- Summary by Rule ---")
+ for rule, count in rule_counter.most_common():
+ print(f" {rule:12s}: {count:3d}")
+
+ print("\n--- Files with Most Findings ---")
+ sorted_files = sorted(files_map.items(), key=lambda kv: len(kv[1]), reverse=True)
+ for filepath, file_findings in sorted_files[:25]:
+ print(f" {len(file_findings):3d} issues: {filepath}")
+
+ if args.json_out:
+ out_data = {
+ "total_findings": len(findings),
+ "files_count": len(files_map),
+ "by_rule": dict(rule_counter),
+ "findings": findings
+ }
+ Path(args.json_out).write_text(json.dumps(out_data, indent=2), encoding="utf-8")
+ print(f"\nWrote full JSON findings to {args.json_out}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/sonarlint_extract.py b/tools/sonarlint_extract.py
new file mode 100644
index 000000000..089f40484
--- /dev/null
+++ b/tools/sonarlint_extract.py
@@ -0,0 +1,557 @@
+#!/usr/bin/env python3
+"""
+Extract SonarLint diagnostics for Java files by driving the SonarLint
+Language Server (the same `sonarlint-ls.jar` bundled with the VS Code
+SonarLint extension) directly over stdio, using the Language Server
+Protocol (LSP) - no VS Code UI required.
+
+How it works
+------------
+The VS Code SonarLint extension does not do the Java analysis itself:
+it starts `sonarlint-ls.jar` (a standalone LSP server, class
+`org.sonarsource.sonarlint.ls.ServerMain`) as a child process and talks
+to it over stdio using standard LSP JSON-RPC messages. The extension's
+Java support is provided by the `sonarjava.jar` analyzer plugin that
+ships in the extension's `analyzers/` folder and is passed to the
+server on the command line.
+
+We can reproduce exactly that handshake ourselves:
+
+ 1. Launch:
+ java -jar sonarlint-ls.jar -stdio -analyzers ...
+ (`-stdio` tells it to use stdio transport instead of a TCP port;
+ `-analyzers` is a space separated list of analyzer plugin jars,
+ confirmed by inspecting the jar's picocli `@Option` annotations in
+ `org.sonarsource.sonarlint.ls.ServerMain`.)
+
+ 2. Send the LSP `initialize` request. SonarLint expects some
+ SonarLint-specific keys inside `initializationOptions`
+ (`productKey`, `productName`, `productVersion`, `workspaceName`,
+ `disableTelemetry`, ... - discovered the same way, by inspecting
+ the strings/constant pool of the server's compiled classes).
+
+ 3. Send `initialized` notification.
+
+ 4. For each Java file, send `textDocument/didOpen` with the file
+ content. SonarLint analyzes the file asynchronously and reports
+ results back to us as `textDocument/publishDiagnostics`
+ notifications (standard LSP) - we just listen for those.
+
+ 5. Collect all `publishDiagnostics` notifications, wait for the
+ server to go quiet (or hit a timeout), then shut down cleanly
+ with `shutdown` + `exit`.
+
+This script implements a minimal, dependency-free LSP client
+(Content-Length framed JSON-RPC over the subprocess's stdin/stdout)
+sufficient to do exactly this.
+
+Usage
+-----
+ python3 sonarlint_extract.py \
+ --src-root src/main/java/fr/inria/corese/core/next \
+ --output sonarlint_report.json
+
+Requirements
+------------
+ - Java (the JRE bundled inside the VS Code extension is reused by
+ default; override with --java to point elsewhere).
+ - sonarlint-ls.jar and analyzers/sonarjava.jar from the SonarLint
+ VS Code extension (paths below have working defaults, override
+ with --sonarlint-ls / --analyzer-jar if your extension version
+ or install location differs).
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import subprocess
+import sys
+import threading
+import time
+from pathlib import Path
+from typing import Any, Dict, List, Optional
+
+EXT_DIR = Path.home() / ".vscode/extensions/sonarsource.sonarlint-vscode-5.9.1-linux-x64"
+DEFAULT_SONARLINT_LS = EXT_DIR / "server/sonarlint-ls.jar"
+DEFAULT_ANALYZER_JARS = [
+ EXT_DIR / "analyzers/sonarjava.jar",
+ EXT_DIR / "analyzers/sonarjavasymbolicexecution.jar",
+]
+DEFAULT_JAVA = EXT_DIR / "jre/21.0.12.1-linux-x86_64.tar/bin/java"
+
+# How long (seconds) to wait for diagnostics after opening the LAST file
+# before we assume analysis has settled and it's safe to shut down.
+DEFAULT_QUIET_PERIOD = 8.0
+# Hard cap on total analysis wait time, in case something never quiets down.
+DEFAULT_MAX_WAIT = 600.0
+
+
+class LspClient:
+ """A minimal LSP client that speaks Content-Length-framed JSON-RPC
+ over a subprocess's stdin/stdout, similar to what VS Code itself
+ does when talking to sonarlint-ls."""
+
+ def __init__(self, proc: subprocess.Popen, java_config: Optional[dict] = None):
+ self.proc = proc
+ self.java_config = java_config
+ self._id = 0
+ self._lock = threading.Lock()
+ self._pending: Dict[int, threading.Event] = {}
+ self._results: Dict[int, Any] = {}
+ self.diagnostics: Dict[str, List[dict]] = {}
+ self._diag_lock = threading.Lock()
+ self._last_diagnostic_time = time.time()
+ self._activity_lock = threading.Lock()
+ self.last_activity_time = time.time()
+ self._stop_reader = False
+ self._reader_thread = threading.Thread(target=self._read_loop, daemon=True)
+ self._reader_thread.start()
+ self._stderr_thread = threading.Thread(target=self._drain_stderr, daemon=True)
+ self._stderr_thread.start()
+
+ # ---- low level framing -------------------------------------------------
+ def _write(self, payload: dict) -> None:
+ body = json.dumps(payload).encode("utf-8")
+ header = f"Content-Length: {len(body)}\r\n\r\n".encode("ascii")
+ assert self.proc.stdin is not None
+ self.proc.stdin.write(header + body)
+ self.proc.stdin.flush()
+
+ def _drain_stderr(self) -> None:
+ assert self.proc.stderr is not None
+ debug = os.environ.get("SONARLINT_DEBUG")
+ for line in self.proc.stderr:
+ if debug:
+ sys.stderr.write("[sonarlint-ls] " + line.decode(errors="replace"))
+
+ def _read_loop(self) -> None:
+ stdout = self.proc.stdout
+ assert stdout is not None
+ while not self._stop_reader:
+ line = stdout.readline()
+ if not line:
+ break
+ if line.strip() == b"":
+ continue
+ if line.lower().startswith(b"content-length:"):
+ length = int(line.split(b":", 1)[1].strip())
+ # consume remaining headers until blank line
+ while True:
+ hline = stdout.readline()
+ if hline in (b"\r\n", b"\n", b""):
+ break
+ body = stdout.read(length)
+ try:
+ msg = json.loads(body.decode("utf-8"))
+ except json.JSONDecodeError:
+ continue
+ self._handle_message(msg)
+
+ def _handle_message(self, msg: dict) -> None:
+ # Any message received from the server (log, diagnostics, progress,
+ # requests, responses, ...) counts as activity, so the quiet-period
+ # detection in the main loop can rely on a single, reliable signal
+ # instead of guessing from specific message contents.
+ with self._activity_lock:
+ self.last_activity_time = time.time()
+
+ if "id" in msg and ("result" in msg or "error" in msg):
+ # response to one of our requests
+ msg_id = msg["id"]
+ with self._lock:
+ ev = self._pending.get(msg_id)
+ self._results[msg_id] = msg
+ if ev:
+ ev.set()
+ return
+
+ method = msg.get("method")
+ if method == "textDocument/publishDiagnostics":
+ params = msg.get("params", {})
+ uri = params.get("uri", "")
+ diags = params.get("diagnostics", [])
+ with self._diag_lock:
+ self.diagnostics[uri] = diags
+ self._last_diagnostic_time = time.time()
+ sys.stderr.write(f"[diagnostics] {uri}: {len(diags)} issues\n")
+ elif method and msg.get("id") is not None:
+ req_id = msg["id"]
+ if method == "workspace/configuration":
+ items = msg.get("params", {}).get("items", [])
+ self._write({"jsonrpc": "2.0", "id": req_id, "result": [{}] * len(items)})
+ elif method == "sonarlint/isOpenInEditor":
+ self._write({"jsonrpc": "2.0", "id": req_id, "result": True})
+ elif method == "sonarlint/listFilesInFolder":
+ self._write({"jsonrpc": "2.0", "id": req_id, "result": {"foundFiles": []}})
+ elif method == "sonarlint/filterOutExcludedFiles":
+ params = msg.get("params", {})
+ uris = params.get("fileUris", []) if isinstance(params, dict) else params
+ self._write({"jsonrpc": "2.0", "id": req_id, "result": {"fileUris": uris}})
+ elif method == "sonarlint/hasJoinedIdeLabs":
+ self._write({"jsonrpc": "2.0", "id": req_id, "result": False})
+ elif method == "sonarlint/getJavaConfig":
+ self._write({"jsonrpc": "2.0", "id": req_id, "result": self.java_config})
+ else:
+ self._write({"jsonrpc": "2.0", "id": req_id, "result": None})
+ elif method == "window/logMessage":
+ msg_text = msg.get("params", {}).get("message", "")
+ sys.stderr.write(f"[log] {msg_text}\n")
+ # Note: we no longer treat a single "Analysis detected" (or similar)
+ # log line as a reliable "analysis complete" signal - SonarLint can
+ # emit several of these across batches. Completion is instead
+ # decided by the quiet-period check on `last_activity_time` in the
+ # main loop, which is updated above for every message received.
+ # Otherwise: ignore other notifications (progress, telemetry, etc.) -
+ # `last_activity_time` was already refreshed above regardless of type.
+
+ # ---- JSON-RPC helpers ---------------------------------------------------
+ def request(self, method: str, params: Optional[dict] = None, timeout: float = 30.0) -> dict:
+ with self._lock:
+ self._id += 1
+ msg_id = self._id
+ ev = threading.Event()
+ self._pending[msg_id] = ev
+ self._write({"jsonrpc": "2.0", "id": msg_id, "method": method, "params": params or {}})
+ if not ev.wait(timeout):
+ raise TimeoutError(f"Timed out waiting for response to {method!r}")
+ with self._lock:
+ result = self._results.pop(msg_id)
+ self._pending.pop(msg_id, None)
+ if "error" in result:
+ raise RuntimeError(f"{method} failed: {result['error']}")
+ return result.get("result")
+
+ def notify(self, method: str, params: Optional[dict] = None) -> None:
+ self._write({"jsonrpc": "2.0", "method": method, "params": params or {}})
+
+ def seconds_since_last_diagnostic(self) -> float:
+ with self._diag_lock:
+ return time.time() - self._last_diagnostic_time
+
+ def close(self) -> None:
+ self._stop_reader = True
+
+
+def start_server(java: Path, sonarlint_ls: Path, analyzer_jars: List[Path], work_dir: Path) -> subprocess.Popen:
+ cmd = [str(java), "-jar", str(sonarlint_ls), "-stdio", "-analyzers"] + [str(j) for j in analyzer_jars]
+ return subprocess.Popen(
+ cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=str(work_dir),
+ )
+
+
+def to_file_uri(path: Path) -> str:
+ return path.resolve().as_uri()
+
+
+def load_java_config(project_root: Path) -> Optional[Dict[str, Any]]:
+ """Build the `sonarlint/getJavaConfig` response from the compile
+ classpath resolved by the `sonarResolver` Gradle task.
+
+ Gradle writes a JSON document (despite the plain `properties`
+ filename) to `build/sonar-resolver/properties` containing a
+ `compileClasspath` list. If that file doesn't exist yet, run
+ `./gradlew sonarResolver` to generate it. Returns None (and lets
+ the server fall back to its own project detection) if the
+ classpath still can't be determined.
+ """
+ resolver_file = project_root / "build" / "sonar-resolver" / "properties"
+ if not resolver_file.exists():
+ print(f"{resolver_file} not found; running './gradlew sonarResolver' to generate it...",
+ file=sys.stderr)
+ try:
+ subprocess.run(
+ ["./gradlew", "sonarResolver"],
+ cwd=str(project_root),
+ check=True,
+ )
+ except (OSError, subprocess.CalledProcessError) as e:
+ print(f"warning: failed to run './gradlew sonarResolver': {e}", file=sys.stderr)
+ return None
+
+ if not resolver_file.exists():
+ print(f"warning: {resolver_file} still not found after running sonarResolver",
+ file=sys.stderr)
+ return None
+
+ try:
+ data = json.loads(resolver_file.read_text(encoding="utf-8"))
+ except (OSError, json.JSONDecodeError) as e:
+ print(f"warning: could not parse {resolver_file}: {e}", file=sys.stderr)
+ return None
+
+ compile_classpath = data.get("compileClasspath") or []
+ classes_dir = project_root / "build" / "classes" / "java" / "main"
+ classpath = [str(classes_dir)] + list(compile_classpath)
+
+ return {
+ "projectRoot": str(project_root),
+ "sourceLevel": "21",
+ "classpath": classpath,
+ "isTest": False,
+ "vmLocation": "",
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="Extract SonarLint diagnostics for Java files via the SonarLint Language Server (LSP over stdio).")
+ parser.add_argument("--src-root", default="src/main/java/fr/inria/corese/core/next",
+ help="Directory tree to scan for *.java files (default: %(default)s)")
+ parser.add_argument("--project-root", default=".",
+ help="Project/workspace root passed to SonarLint as the workspace folder (default: current directory)")
+ parser.add_argument("--files", nargs="*", default=None,
+ help="Specific .java files to scan instead of an entire tree")
+ parser.add_argument("--output", default="sonarlint_report.json", help="Where to write the JSON report")
+ parser.add_argument("--sonarlint-ls", default=str(DEFAULT_SONARLINT_LS), help="Path to sonarlint-ls.jar")
+ parser.add_argument("--analyzer-jar", action="append", default=None,
+ help="Analyzer jar to load (repeatable). Defaults to sonarjava.jar + symbolic execution plugin.")
+ parser.add_argument("--java", default=str(DEFAULT_JAVA), help="Path to java executable")
+ parser.add_argument("--quiet-period", type=float, default=DEFAULT_QUIET_PERIOD,
+ help="Seconds of no new diagnostics before considering analysis complete (default: %(default)s)")
+ parser.add_argument("--max-wait", type=float, default=DEFAULT_MAX_WAIT,
+ help="Absolute cap in seconds on total time spent waiting for diagnostics")
+ parser.add_argument("--batch-size", type=int, default=25,
+ help="Number of files to open concurrently per batch (avoids opening 700 files at once)")
+ args = parser.parse_args()
+
+ project_root = Path(args.project_root).resolve()
+ src_root = Path(args.src_root)
+ if not src_root.is_absolute():
+ src_root = project_root / src_root
+ if not src_root.is_dir():
+ print(f"error: src root not found: {src_root}", file=sys.stderr)
+ return 1
+
+ java = Path(args.java)
+ if not java.exists():
+ java = Path("java") # fall back to whatever is on PATH
+
+ sonarlint_ls = Path(args.sonarlint_ls)
+ if not sonarlint_ls.exists():
+ print(f"error: sonarlint-ls.jar not found at {sonarlint_ls}", file=sys.stderr)
+ return 1
+
+ analyzer_jars = [Path(p) for p in (args.analyzer_jar or DEFAULT_ANALYZER_JARS)]
+ for jar in analyzer_jars:
+ if not jar.exists():
+ print(f"error: analyzer jar not found: {jar}", file=sys.stderr)
+ return 1
+
+ if args.files:
+ java_files = [Path(f).resolve() for f in args.files]
+ else:
+ java_files = sorted(src_root.rglob("*.java"))
+ if not java_files:
+ print(f"error: no .java files found", file=sys.stderr)
+ return 1
+ print(f"Found {len(java_files)} Java files to analyze")
+
+ java_config = load_java_config(project_root)
+
+ extra_properties: Dict[str, str] = {}
+ if java_config and java_config.get("classpath"):
+ extra_properties["sonar.java.binaries"] = str(project_root / "build" / "classes" / "java" / "main")
+
+ print(f"Starting sonarlint-ls: {java} -jar {sonarlint_ls} -stdio -analyzers "
+ f"{' '.join(str(j) for j in analyzer_jars)}")
+ proc = start_server(java, sonarlint_ls, analyzer_jars, project_root)
+ client = LspClient(proc, java_config=java_config)
+
+ try:
+ init_result = client.request("initialize", {
+ "processId": os.getpid(),
+ "rootUri": to_file_uri(project_root),
+ "capabilities": {
+ "textDocument": {
+ "publishDiagnostics": {
+ "relatedInformation": True,
+ "versionSupport": False,
+ "tagSupport": {"valueSet": [1, 2]},
+ "codeDescriptionSupport": True,
+ "dataSupport": True,
+ }
+ },
+ "workspace": {
+ "configuration": True,
+ }
+ },
+ "workspaceFolders": [
+ {"uri": to_file_uri(project_root), "name": project_root.name}
+ ],
+ "initializationOptions": {
+ "productKey": "standalone",
+ "productName": "SonarLint CLI Extractor",
+ "productVersion": "1.0.0",
+ "workspaceName": project_root.name,
+ "firstSecretDetected": True,
+ "showVerboseLogs": True,
+ "additionalAttributes": {},
+ "disableTelemetry": True,
+ "automaticAnalysis": True,
+ "connections": {"sonarqube": [], "sonarcloud": []},
+ "rules": {},
+ "extraProperties": extra_properties,
+ },
+ }, timeout=60)
+ print("Server initialized:",
+ init_result.get("serverInfo") if isinstance(init_result, dict) else init_result)
+ client.notify("initialized", {})
+
+ # Give the server a moment to finish standalone-engine bootstrap
+ # (it lazily starts the Java analysis engine on first use).
+ time.sleep(1.0)
+
+ opened_uris: List[str] = []
+ batch_size = max(1, args.batch_size)
+ for start in range(0, len(java_files), batch_size):
+ batch = java_files[start:start + batch_size]
+ for f in batch:
+ uri = to_file_uri(f)
+ try:
+ text = f.read_text(encoding="utf-8", errors="replace")
+ except OSError as e:
+ print(f"warning: could not read {f}: {e}", file=sys.stderr)
+ continue
+ client.notify("textDocument/didOpen", {
+ "textDocument": {
+ "uri": uri,
+ "languageId": "java",
+ "version": 1,
+ "text": text,
+ }
+ })
+ opened_uris.append(uri)
+ print(f"Opened {min(start + batch_size, len(java_files))}/{len(java_files)} files...")
+ # Small pause between batches so we don't overwhelm the analyzer queue.
+ time.sleep(0.2)
+
+ print("All files opened. Waiting for SonarLint analysis to settle "
+ f"(quiet period: {args.quiet_period}s, max wait: {args.max_wait}s)...")
+ wait_start = time.time()
+ last_progress_print = 0.0
+ progress_interval = 5.0
+ while True:
+ now = time.time()
+ with client._activity_lock:
+ idle_for = now - client.last_activity_time
+ elapsed = now - wait_start
+
+ if now - last_progress_print >= progress_interval:
+ files_with_diagnostics = len(client.diagnostics)
+ print(f" ...{elapsed:.0f}s elapsed, {files_with_diagnostics}/{len(opened_uris)} "
+ f"files have received diagnostics, idle for {idle_for:.1f}s")
+ last_progress_print = now
+
+ files_with_diagnostics = len(client.diagnostics)
+ required_quiet = args.quiet_period if files_with_diagnostics >= len(opened_uris) else max(args.quiet_period, 15.0)
+ if idle_for >= required_quiet:
+ print(f"No server activity for {idle_for:.1f}s (>= required quiet period of "
+ f"{required_quiet:.1f}s). Assuming analysis has settled.")
+ break
+ if elapsed >= args.max_wait:
+ print(f"Reached max wait of {args.max_wait}s. Proceeding with whatever "
+ "diagnostics have been received so far.")
+ break
+ time.sleep(0.5)
+
+ # Some files may never receive a publishDiagnostics message if
+ # SonarLint found zero issues in them (LSP servers are not
+ # required to publish an empty diagnostics list). Fill those
+ # in explicitly so the report always reflects every scanned file.
+ for uri in opened_uris:
+ client.diagnostics.setdefault(uri, [])
+
+ report = build_report(client.diagnostics, project_root)
+ Path(args.output).write_text(json.dumps(report, indent=2), encoding="utf-8")
+ print(f"Wrote report with {report['summary']['total_issues']} issue(s) "
+ f"across {report['summary']['files_with_issues']} file(s) to {args.output}")
+ print_summary(report)
+ return 0
+ finally:
+ try:
+ client.request("shutdown", {}, timeout=10)
+ client.notify("exit", {})
+ except Exception:
+ pass
+ client.close()
+ try:
+ proc.wait(timeout=10)
+ except Exception:
+ proc.kill()
+
+
+def build_report(diagnostics: Dict[str, List[dict]], project_root: Path) -> dict:
+ files = []
+ total_issues = 0
+ files_with_issues = 0
+ severity_counts: Dict[str, int] = {}
+ rule_counts: Dict[str, int] = {}
+
+ for uri, diags in sorted(diagnostics.items()):
+ rel_path = uri
+ if uri.startswith("file://"):
+ abs_path = Path(uri[len("file://"):])
+ try:
+ rel_path = str(abs_path.relative_to(project_root))
+ except ValueError:
+ rel_path = str(abs_path)
+
+ issues = []
+ for d in diags:
+ severity = d.get("severity")
+ code = d.get("code")
+ rule = code if isinstance(code, str) else (code.get("value") if isinstance(code, dict) else code)
+ rng = d.get("range", {})
+ issues.append({
+ "rule": rule,
+ "message": d.get("message"),
+ "severity": severity,
+ "start_line": rng.get("start", {}).get("line", 0) + 1,
+ "start_character": rng.get("start", {}).get("character", 0),
+ "end_line": rng.get("end", {}).get("line", 0) + 1,
+ "end_character": rng.get("end", {}).get("character", 0),
+ "source": d.get("source"),
+ })
+ total_issues += 1
+ if rule:
+ rule_counts[str(rule)] = rule_counts.get(str(rule), 0) + 1
+ severity_counts[str(severity)] = severity_counts.get(str(severity), 0) + 1
+
+ if issues:
+ files_with_issues += 1
+ files.append({"file": rel_path, "issue_count": len(issues), "issues": issues})
+
+ return {
+ "summary": {
+ "total_files_scanned": len(files),
+ "files_with_issues": files_with_issues,
+ "total_issues": total_issues,
+ "by_severity": severity_counts,
+ "by_rule": dict(sorted(rule_counts.items(), key=lambda kv: -kv[1])),
+ },
+ "files": files,
+ }
+
+
+def print_summary(report: dict) -> None:
+ top_rules = list(report["summary"]["by_rule"].items())[:15]
+ if top_rules:
+ print("\nTop rules triggered:")
+ for rule, count in top_rules:
+ print(f" {count:4d} {rule}")
+ top_files = sorted(report["files"], key=lambda f: -f["issue_count"])[:15]
+ if top_files and top_files[0]["issue_count"] > 0:
+ print("\nFiles with the most issues:")
+ for f in top_files:
+ if f["issue_count"] == 0:
+ break
+ print(f" {f['issue_count']:4d} {f['file']}")
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())