[Git][java-team/libbeam-java][upstream] New upstream version 1.4

Andrius Merkys (@merkys) gitlab at salsa.debian.org
Wed Sep 9 06:05:28 BST 2026



Andrius Merkys pushed to branch upstream at Debian Java Maintainers / libbeam-java


Commits:
aca58b0f by Andrius Merkys at 2026-09-08T09:30:15-04:00
New upstream version 1.4
- - - - -


14 changed files:

- .github/workflows/build.yml
- core/pom.xml
- core/src/main/java/uk/ac/ebi/beam/Graph.java
- + core/src/main/java/uk/ac/ebi/beam/Mode.java
- core/src/main/java/uk/ac/ebi/beam/Parser.java
- core/src/test/java/uk/ac/ebi/beam/GraphTest.java
- core/src/test/java/uk/ac/ebi/beam/ParserTest.java
- core/src/test/java/uk/ac/ebi/beam/ParsingAtomClassTest.java
- core/src/test/java/uk/ac/ebi/beam/ParsingBracketAtomTest.java
- + core/src/test/java/uk/ac/ebi/beam/RelaxedParsingTest.java
- exec/pom.xml
- func/pom.xml
- func/src/main/java/uk/ac/ebi/beam/NormaliseDirectionalLabels.java
- pom.xml


Changes:

=====================================
.github/workflows/build.yml
=====================================
@@ -7,30 +7,29 @@ on:
     types: [opened, synchronize, reopened]
 jobs:
   build:
-    name: Build
+    name: Build sonarcloud
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     steps:
-      - uses: actions/checkout at v2
+      - uses: actions/checkout at v6
         with:
           fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
-      - name: Set up JDK 11
-        uses: actions/setup-java at v1
+      - name: Set up JDK 17
+        uses: actions/setup-java at v5
         with:
-          java-version: 11
+          distribution: 'temurin'
+          java-version: 17
+          cache: maven
       - name: Cache SonarCloud packages
-        uses: actions/cache at v1
+        uses: actions/cache at v5
         with:
           path: ~/.sonar/cache
           key: ${{ runner.os }}-sonar
           restore-keys: ${{ runner.os }}-sonar
-      - name: Cache Maven packages
-        uses: actions/cache at v1
-        with:
-          path: ~/.m2
-          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
-          restore-keys: ${{ runner.os }}-m2
       - name: Build and analyze
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information, if any
           SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+          MAVEN_OPTS: -Xss16m  -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3
         run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=johnmay_beam -Pcoverage
\ No newline at end of file


=====================================
core/pom.xml
=====================================
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>beam</artifactId>
         <groupId>uk.ac.ebi.beam</groupId>
-        <version>1.3.9</version>
+        <version>1.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 


=====================================
core/src/main/java/uk/ac/ebi/beam/Graph.java
=====================================
@@ -113,7 +113,8 @@ public final class Graph {
         this.degrees    = new int[order];
         this.edges      = new Edge[order][];
         this.topologies = Arrays.copyOf(org.topologies, org.topologies.length);
-        
+        this.title      = org.title;
+
         for (int u = 0; u < order; u++) {
             final int deg = org.degrees[u];
             this.edges[u] = new Edge[deg];
@@ -428,7 +429,7 @@ public final class Graph {
                                                InvalidSmilesException {
         if (smi == null)
             throw new NullPointerException("no SMILES provided");
-        Parser parser = new Parser(CharBuffer.fromString(smi), false);
+        Parser parser = new Parser(CharBuffer.fromString(smi), Mode.Default);
         for (String warn : parser.getWarnings()) {
             for (String line : warn.split("\n"))
               System.err.println("SMILES Warning: " + line);
@@ -436,10 +437,10 @@ public final class Graph {
         return parser.molecule();
     }
 
-    public static Graph parse(String smi, boolean strict, Set<String> warnings) throws InvalidSmilesException {
+    public static Graph parse(String smi, Mode mode, Set<String> warnings) throws InvalidSmilesException {
         if (smi == null)
             throw new NullPointerException("no SMILES provided");
-        Parser parser = new Parser(CharBuffer.fromString(smi), strict);
+        Parser parser = new Parser(CharBuffer.fromString(smi), mode);
         warnings.addAll(parser.getWarnings());
         return parser.molecule();
     }


=====================================
core/src/main/java/uk/ac/ebi/beam/Mode.java
=====================================
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2026, John Mayfield
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those
+ * of the authors and should not be interpreted as representing official policies,
+ * either expressed or implied, of the FreeBSD Project.
+ */
+package uk.ac.ebi.beam;
+
+/**
+ * Parsing mode, this sets how strict the SMILES parser is.
+ */
+public enum Mode {
+    /* Fail (exception) on errors and warnings. */
+    Strict,
+    /* Fail (exception) on errors only. */
+    Default,
+    /* Treat recoverable errors as warnings. */
+    Relaxed
+}


=====================================
core/src/main/java/uk/ac/ebi/beam/Parser.java
=====================================
@@ -29,6 +29,7 @@
 
 package uk.ac.ebi.beam;
 
+import javax.swing.plaf.nimbus.AbstractRegionPainter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -103,9 +104,9 @@ final class Parser {
     private int openRings = 0;
 
     /**
-     * Strict parsing.
+     * Parsing mode
      */
-    private final boolean strict;
+    private Mode mode;
 
     private BitSet checkDirectionalBonds = new BitSet();
 
@@ -120,15 +121,16 @@ final class Parser {
      * Create a new parser for the specified buffer.
      *
      * @param buffer character buffer holding a SMILES string
+     * @param mode the mode
      * @throws InvalidSmilesException thrown if the SMILES could not be parsed
      */
-    Parser(CharBuffer buffer, boolean strict) throws InvalidSmilesException {
-        this.strict = strict;
+    Parser(CharBuffer buffer, Mode mode) throws InvalidSmilesException {
+        this.mode = mode;
         g = new Graph(1 + (2 * (buffer.length() / 3)));
         readSmiles(buffer);
-        if (openRings > 0)
+        if (openRings > 0 && mode != Mode.Relaxed)
             throw new InvalidSmilesException("Unclosed ring detected, SMILES may be truncated:", buffer);
-        if (stack.size() > 1)
+        if (stack.size() > 1 && mode != Mode.Relaxed)
             throw new InvalidSmilesException("Unclosed branch detected, SMILES may be truncated:", buffer);
         start.add(0); // always include first vertex as start
         if (g.getFlags(Graph.HAS_STRO) != 0) {
@@ -169,7 +171,7 @@ final class Parser {
      * @throws InvalidSmilesException thrown if the SMILES could not be parsed
      */
     Parser(String str) throws InvalidSmilesException {
-        this(CharBuffer.fromString(str), false);
+        this(CharBuffer.fromString(str), Mode.Default);
     }
 
     /**
@@ -181,7 +183,7 @@ final class Parser {
      * @throws InvalidSmilesException
      */
     static Graph strict(String str) throws InvalidSmilesException {
-        return new Parser(CharBuffer.fromString(str), true).molecule();
+        return new Parser(CharBuffer.fromString(str), Mode.Strict).molecule();
     }
 
     /**
@@ -194,8 +196,8 @@ final class Parser {
      * @return a graph created with the loose parser
      * @throws InvalidSmilesException
      */
-    static Graph losse(String str) throws InvalidSmilesException {
-        return new Parser(CharBuffer.fromString(str), false).molecule();
+    static Graph relaxed(String str) throws InvalidSmilesException {
+        return new Parser(CharBuffer.fromString(str), Mode.Relaxed).molecule();
     }
 
     /**
@@ -272,7 +274,7 @@ final class Parser {
                 String errorPos = InvalidSmilesException.display(buffer,
                                                                  offset1 - buffer.length(),
                                                                  offset2 - buffer.length());
-                if (strict)
+                if (mode == Mode.Strict)
                     throw new InvalidSmilesException("Ignored invalid Cis/Trans specification: " + errorPos);
                 else
                     warnings.add("Ignored invalid Cis/Trans specification: " + errorPos);
@@ -289,7 +291,7 @@ final class Parser {
                 String errorPos = InvalidSmilesException.display(buffer,
                                                                  offset1 - buffer.length(),
                                                                  offset2 - buffer.length());
-                if (strict)
+                if (mode == Mode.Strict)
                     throw new InvalidSmilesException("Ignored invalid Cis/Trans specification: " + errorPos);
                 else
                     warnings.add("Ignored invalid Cis/Trans specification: " + errorPos);
@@ -359,8 +361,10 @@ final class Parser {
         boolean begh = g.implHCount(beg) == 1;
         boolean endh = g.implHCount(end) == 1;
         List<Edge> begEdges = new ArrayList<>(getLocalEdges(beg));
-        if (begh)
+
+        if (begh || g.degree(beg) == 2)
             begEdges.add(start.contains(beg) ? 0 : 1, null);
+
         for (Edge bEdge : begEdges) {
             if (bEdge == null) {
                 carriers[i++] = beg;
@@ -370,8 +374,10 @@ final class Parser {
             if (bEdge.bond() == Bond.DOUBLE) {
                 // neighbors next to end
                 List<Edge> endEdges = new ArrayList<>(getLocalEdges(end));
-                if (endh)
+
+                if (endh || g.degree(end) == 2)
                     endEdges.add(1, null);
+
                 for (Edge eEdge : endEdges) {
                     if (eEdge == null)
                         carriers[i++] = end;
@@ -387,6 +393,36 @@ final class Parser {
         return carriers;
     }
 
+
+    private void warning(String mesg) throws InvalidSmilesException {
+        if (mode == Mode.Strict)
+            throw new InvalidSmilesException(mesg);
+        else
+            warnings.add(mesg);
+    }
+
+    private void warning(String mesg, CharBuffer input) throws InvalidSmilesException {
+        if (mode == Mode.Strict)
+            throw new InvalidSmilesException(mesg, input);
+        else
+            warnings.add(mesg);
+    }
+
+    private void error(String mesg) throws InvalidSmilesException {
+        if (mode != Mode.Relaxed)
+            throw new InvalidSmilesException(mesg);
+        else
+            warnings.add(mesg);
+    }
+
+    private void error(String mesg, CharBuffer input) throws InvalidSmilesException {
+        if (mode != Mode.Relaxed)
+            throw new InvalidSmilesException(mesg, input, input.position());
+        else
+            warnings.add(mesg);
+    }
+
+
     /**
      * Add a topology for vertex 'u' with configuration 'c'. If the atom 'u' was
      * involved in a ring closure the local arrangement is used instead of the
@@ -411,10 +447,7 @@ final class Parser {
             } else if (c.type() == Configuration.Type.ExtendedTetrahedral) {
                 g.addFlags(Graph.HAS_EXT_STRO);
                 if ((us = getAlleneCarriers(u)) == null) {
-                    if (strict)
-                        throw new InvalidSmilesException("Invalid Allene stereo");
-                    else
-                        warnings.add("Ignored invalid Allene stereochemistry");
+                    warning("Invalid Allene stereo");
                     return;
                 }
             } else if (input.type() == Configuration.Type.SquarePlanar) {
@@ -425,24 +458,15 @@ final class Parser {
                 us = insertMultipleImplicitRefs(u, us, 6);
             } else if (c.type() == Configuration.Type.SquarePlanar &&
                        us.length != 4) {
-                if (strict)
-                    throw new InvalidSmilesException("SquarePlanar without 4 explicit neighbours");
-                else
-                    warnings.add("SquarePlanar without 4 explicit neighbours");
+                warning("SquarePlanar without 4 explicit neighbours");
                 return;
             } else if (c.type() == Configuration.Type.TrigonalBipyramidal &&
                        us.length != 5) {
-                if (strict)
-                    throw new InvalidSmilesException("TrigonalBipyramidal without 5 explicit neighbours");
-                else
-                    warnings.add("SquarePlanar without 5 explicit neighbours");
+                warning("TrigonalBipyramidal without 5 explicit neighbours");
                 return;
             } else if (c.type() == Configuration.Type.Octahedral &&
                        us.length != 6) {
-                if (strict)
-                    throw new InvalidSmilesException("Octahedral without 6 explicit neighbours");
-                else
-                    warnings.add("SquarePlanar without 6 explicit neighbours");
+                warning("Octahedral without 6 explicit neighbours");
                 return;
             }
             g.addTopology(Topology.create(u, us, es, c));
@@ -468,32 +492,23 @@ final class Parser {
                 us = insertMultipleImplicitRefs(u, us, 6);
             } else if (c.type() == Configuration.Type.SquarePlanar &&
                        us.length != 4) {
-                if (strict)
-                    throw new InvalidSmilesException("SquarePlanar without 4 explicit neighbours");
-                else
-                    warnings.add("SquarePlanar without 4 explicit neighbours");
+                warning("SquarePlanar without 4 explicit neighbours");
                 return;
             } else if (c.type() == Configuration.Type.TrigonalBipyramidal &&
                        us.length != 5) {
-                if (strict)
-                    throw new InvalidSmilesException("TrigonalBipyramidal without 5 explicit neighbours");
-                else
-                    warnings.add("SquarePlanar without 5 explicit neighbours");
+                warning("TrigonalBipyramidal without 5 explicit neighbours");
                 return;
             } else if (c.type() == Configuration.Type.Octahedral &&
                        us.length != 6) {
-                if (strict)
-                    throw new InvalidSmilesException("Octahedral without 6 explicit neighbours");
-                else
-                    warnings.add("SquarePlanar without 6 explicit neighbours");
+                warning("Octahedral without 6 explicit neighbours");
                 return;
             }
+
             g.addTopology(Topology.create(u, us, es, c));
         }
     }
 
-    private int[] insertThImplicitRef(int u, int[] vs) throws
-            InvalidSmilesException {
+    private int[] insertThImplicitRef(int u, int[] vs) throws InvalidSmilesException {
         if (vs.length == 4)
             return vs;
         if (vs.length != 3)
@@ -529,7 +544,7 @@ final class Parser {
         if (vs.length == 3)
             return vs;
         if (vs.length != 2)
-            throw new InvalidSmilesException("Invaid number of verticies for DB1/DB2 stereo chemistry");
+            throw new InvalidSmilesException("Invalid number of vertices for DB1/DB2 stereo chemistry");
         if (start.contains(u))
             return new int[]{u, vs[0], vs[1]};
         else
@@ -651,21 +666,15 @@ final class Parser {
                 // says it's possible. The D and T here are automatic converted
                 // to [2H] and [3H].
                 case 'H':
-                    if (strict)
-                        throw new InvalidSmilesException("hydrogens should be specified in square brackets - '[H]'",
-                                                         buffer);
+                    warning("hydrogens should be specified in square brackets - '[H]'", buffer);
                     addAtom(AtomImpl.EXPLICIT_HYDROGEN, buffer);
                     break;
                 case 'D':
-                    if (strict)
-                        throw new InvalidSmilesException("deuterium should be specified as a hydrogen isotope - '[2H]'",
-                                                         buffer);
+                    warning("deuterium should be specified as a hydrogen isotope - '[2H]'", buffer);
                     addAtom(AtomImpl.DEUTERIUM, buffer);
                     break;
                 case 'T':
-                    if (strict)
-                        throw new InvalidSmilesException("tritium should be specified as a hydrogen isotope - '[3H]'",
-                                                         buffer);
+                    warning("tritium should be specified as a hydrogen isotope - '[3H]'", buffer);
                     addAtom(AtomImpl.TRITIUM, buffer);
                     break;
 
@@ -689,78 +698,83 @@ final class Parser {
                     break;
                 case '%':
                     int num = buffer.getNumber(2);
-                    if (num < 0)
-                        throw new InvalidSmilesException("a number (<digit>+) must follow '%':", buffer);
-                    if (strict && num < 10)
-                        throw new InvalidSmilesException("two digits must follow '%'", buffer);
+                    if (num < 0) {
+                        error("a number (<digit>+) must follow '%':", buffer);
+                        continue;
+                    }
+                    if (num < 10) {
+                        warning("two digits must follow '%'", buffer);
+                    }
                     ring(num, buffer);
                     lastBondPos = buffer.position();
                     break;
 
                 // bond/dot
                 case '-':
-                    if (bond != Bond.IMPLICIT)
-                        throw new InvalidSmilesException("Multiple bonds specified:", buffer);
+                    if (bond != Bond.IMPLICIT || stack.empty())
+                        error("Invalid bond:", buffer);
                     bond = Bond.SINGLE;
                     lastBondPos = buffer.position();
                     break;
                 case '=':
-                    if (bond != Bond.IMPLICIT)
-                        throw new InvalidSmilesException("Multiple bonds specified:", buffer);
+                    if (bond != Bond.IMPLICIT || stack.empty())
+                        error("Invalid bond:", buffer);
                     bond = Bond.DOUBLE;
                     lastBondPos = buffer.position();
                     break;
                 case '#':
-                    if (bond != Bond.IMPLICIT)
-                        throw new InvalidSmilesException("Multiple bonds specified:", buffer);
+                    if (bond != Bond.IMPLICIT || stack.empty())
+                        error("Invalid bond:", buffer);
                     bond = Bond.TRIPLE;
                     lastBondPos = buffer.position();
                     break;
                 case '$':
-                    if (bond != Bond.IMPLICIT)
-                        throw new InvalidSmilesException("Multiple bonds specified:", buffer);
+                    if (bond != Bond.IMPLICIT || stack.empty())
+                        error("Invalid bond:", buffer);
                     bond = Bond.QUADRUPLE;
                     lastBondPos = buffer.position();
                     break;
                 case ':':
-                    if (bond != Bond.IMPLICIT)
-                        throw new InvalidSmilesException("Multiple bonds specified:", buffer);
+                    if (bond != Bond.IMPLICIT || stack.empty())
+                        error("Invalid bond:", buffer);
                     g.addFlags(Graph.HAS_AROM);
                     bond = Bond.AROMATIC;
                     lastBondPos = buffer.position();
                     break;
                 case '/':
-                    if (bond != Bond.IMPLICIT)
-                        throw new InvalidSmilesException("Multiple bonds specified:", buffer);
+                    if (bond != Bond.IMPLICIT || stack.empty())
+                        error("Invalid bond:", buffer);
                     bond = Bond.UP;
                     lastBondPos = buffer.position();
                     g.addFlags(Graph.HAS_BND_STRO);
                     break;
                 case '\\':
                     // we allow C\\C=C/C since it could be an escaping error
-                    if (bond != Bond.IMPLICIT && bond != Bond.DOWN)
-                        throw new InvalidSmilesException("Multiple bonds specified:", buffer);
+                    if (bond != Bond.IMPLICIT && bond != Bond.DOWN || stack.empty())
+                        error("Invalid bond:", buffer);
                     bond = Bond.DOWN;
                     lastBondPos = buffer.position();
                     g.addFlags(Graph.HAS_BND_STRO);
                     break;
                 case '.':
                     if (bond != Bond.IMPLICIT)
-                        throw new InvalidSmilesException("Bond specified before disconnection:", buffer);
+                        error("Invalid disconnection:", buffer);
                     bond = Bond.DOT;
                     break;
 
                 // branching
                 case '(':
-                    if (stack.empty())
-                        throw new InvalidSmilesException("Cannot open branch at this position, SMILES may be truncated:",
-                                                         buffer);
+                    if (stack.empty()) {
+                        error("Cannot open branch at this position, SMILES may be truncated:", buffer);
+                        continue;
+                    }
                     stack.push(stack.peek());
                     break;
                 case ')':
-                    if (stack.size() < 2)
-                        throw new InvalidSmilesException("Closing of an unopened branch, SMILES may be truncated:",
-                                                         buffer);
+                    if (stack.size() < 2) {
+                        error("Closing of an unopened branch, SMILES may be truncated:", buffer);
+                        continue;
+                    }
                     stack.pop();
                     break;
 
@@ -782,6 +796,11 @@ final class Parser {
                 case '\r':
                     return;
 
+                case ']':
+                case '@':
+                case '+':
+                    error("unexpected character:", buffer);
+                    continue;
                 default:
                     throw new InvalidSmilesException("unexpected character:", buffer);
             }
@@ -809,8 +828,10 @@ final class Parser {
 
         boolean arbitraryLabel = false;
 
-        if (!buffer.hasRemaining())
-            throw new InvalidSmilesException("Unclosed bracket atom, SMILES may be truncated", buffer);
+        if (!buffer.hasRemaining()) {
+            error("Unclosed bracket atom, SMILES may be truncated", buffer);
+            return AtomImpl.AliphaticSubset.Any;
+        }
 
         final int isotope = buffer.getNumber();
         final boolean aromatic = buffer.next() >= 'a' && buffer.next() <= 'z';
@@ -818,15 +839,15 @@ final class Parser {
         if (element == Element.Unknown)
             hasAstrix = true;
 
-        if (strict && element == null)
-            throw new InvalidSmilesException("unrecognised element symbol, SMILES may be truncated: ", buffer);
+        if (mode == Mode.Strict && element == null)
+            warning("unrecognised element symbol, SMILES may be truncated: ", buffer);
 
         if (element != null && aromatic)
             g.addFlags(Graph.HAS_AROM);
 
         // element isn't aromatic as per the OpenSMILES specification
-        if (strict && aromatic && !element.aromatic(Element.AromaticSpecification.OpenSmiles))
-            throw new InvalidSmilesException("abnormal aromatic element", buffer);
+        if (mode == Mode.Strict && aromatic && !element.aromatic(Element.AromaticSpecification.OpenSmiles))
+            warning("abnormal aromatic element", buffer);
 
         if (element == null) {
             arbitraryLabel = true;
@@ -839,10 +860,12 @@ final class Parser {
         int atomClass = readClass(buffer);
 
         if (!arbitraryLabel && !buffer.getIf(']')) {
-            if (strict) {
+            if (mode == Mode.Strict) {
                 throw InvalidSmilesException.invalidBracketAtom(buffer);
-            } else {
+            } else if (buffer.hasRemaining()) {
                 arbitraryLabel = true;
+            } else {
+                error("Unclosed bracket atom!");
             }
         }
 
@@ -861,9 +884,7 @@ final class Parser {
                 end++;
             }
             if (depth != 0)
-                throw new InvalidSmilesException("unparsable label in bracket atom",
-                                                 buffer,
-                                                 buffer.position - 1);
+                error("Unclosed bracket atom", buffer);
             String label = buffer.substr(start, end);
             hasAstrix = true;
             return new AtomImpl.BracketAtom(label);
@@ -940,11 +961,11 @@ final class Parser {
      * @see <a href="http://www.opensmiles.org/opensmiles.html#atomclass">Atom
      * Class - OpenSMILES Specification</a>
      */
-    static int readClass(CharBuffer buffer) throws InvalidSmilesException {
+    int readClass(CharBuffer buffer) throws InvalidSmilesException {
         if (buffer.getIf(':')) {
             if (buffer.nextIsDigit())
                 return buffer.getNumber();
-            throw new InvalidSmilesException("invalid atom class, <digit>+ must follow ':'", buffer);
+            error("invalid atom class, <digit>+ must follow ':'", buffer);
         }
         return 0;
     }
@@ -956,14 +977,14 @@ final class Parser {
      * @throws InvalidSmilesException bond types did not match on ring closure
      */
     private void ring(int rnum, CharBuffer buffer) throws InvalidSmilesException {
-        if (bond == Bond.DOT)
-            throw new InvalidSmilesException("a ring bond can not be a 'dot':",
-                                             buffer,
-                                             buffer.position());
-        if (stack.empty())
-            throw new InvalidSmilesException("No previous atom for ring open!",
-                                             buffer,
-                                             buffer.position());
+        if (bond == Bond.DOT) {
+            error("a ring bond can not be a 'dot':", buffer);
+            return;
+        }
+        if (stack.empty()) {
+            error("No previous atom for ring open!", buffer);
+            return;
+        }
 
         if (rings.length <= rnum || rings[rnum] == null)
             openRing(rnum, buffer);
@@ -1030,9 +1051,11 @@ final class Parser {
             throw new InvalidSmilesException("Endpoints of ringbond are the same - loops are not allowed",
                                              buffer);
 
-        if (g.adjacent(u, v))
-            throw new InvalidSmilesException("Endpoints of ringbond are already connected - multi-edges are not allowed",
-                                             buffer);
+        if (g.adjacent(u, v)) {
+            error("Endpoints of ringbond are already connected - multi-edges are not allowed",
+                  buffer);
+            return;
+        }
 
         bond = decideBond(rbond.bond, bond.inverse(), rbond.pos, buffer);
 
@@ -1080,11 +1103,13 @@ final class Parser {
             return b;
         else if (b == Bond.IMPLICIT)
             return a;
-        if (strict || a.inverse() != b)
-            throw new InvalidSmilesException("Ring closure bonds did not match,  '" + a + "'!='" + b + "':" +
-                                             InvalidSmilesException.display(buffer,
-                                                                            pos - buffer.position,
-                                                                            lastBondPos - buffer.position));
+        if (mode == Mode.Strict || a.inverse() != b) {
+            error("Ring closure bonds did not match,  '" + a + "'!='" + b + "':" +
+                  InvalidSmilesException.display(buffer,
+                                                 pos - buffer.position,
+                                                 lastBondPos - buffer.position));
+            return b; // arbitrary closure takes priority
+        }
         warnings.add("Ignored invalid Cis/Trans on ring closure, should flip:" +
                      InvalidSmilesException.display(buffer, pos - buffer.position,
                                                     lastBondPos - buffer.position));


=====================================
core/src/test/java/uk/ac/ebi/beam/GraphTest.java
=====================================
@@ -546,6 +546,14 @@ public class GraphTest {
         Assert.assertThat(g.topologyOf(3).configuration(), is(Configuration.AL2));
     }
 
+
+    @Test public void extendedTetrahedralNitrogen() throws InvalidSmilesException {
+        Graph g = Graph.fromSmiles("CN=[C at AL1]=NC");
+        Assert.assertThat(g.topologyOf(2).configuration(), is(Configuration.AL1));
+        g = Graph.fromSmiles("CN=[C@]=NC");
+        Assert.assertThat(g.topologyOf(2).configuration(), is(Configuration.AL1));
+    }
+
     @Test public void testDegenerateOctahedral1() throws Exception {
         assertThat(Graph.fromSmiles("N[Co at OH1]N").toSmiles(),
                    containsString("N[Co at OH1]N"));


=====================================
core/src/test/java/uk/ac/ebi/beam/ParserTest.java
=====================================
@@ -108,7 +108,7 @@ public class ParserTest {
     }
 
     @Test public void hydrogen() throws IOException {
-        Graph g = Parser.losse("HH");
+        Graph g = Parser.parse("HH");
         assertThat(g.order(), is(2));
         assertThat(g.toSmiles(), is("[H][H]"));
     }
@@ -119,7 +119,7 @@ public class ParserTest {
     }
 
     @Test public void deuterium() throws IOException {
-        Graph g = Parser.losse("DD");
+        Graph g = Parser.parse("DD");
         assertThat(g.order(), is(2));
         assertThat(g.toSmiles(), is("[2H][2H]"));
     }
@@ -130,7 +130,7 @@ public class ParserTest {
     }
 
     @Test public void tritium() throws IOException {
-        Graph g = Parser.losse("TT");
+        Graph g = Parser.parse("TT");
         assertThat(g.order(), is(2));
         assertThat(g.toSmiles(), is("[3H][3H]"));
     }
@@ -148,7 +148,7 @@ public class ParserTest {
     }
 
     @Test public void tellurium() throws IOException {
-        Graph g = Parser.losse("[te]");
+        Graph g = Parser.relaxed("[te]");
         assertTrue(g.atom(0).aromatic());
         assertThat(g.atom(0).element(), is(Element.Tellurium));
     }


=====================================
core/src/test/java/uk/ac/ebi/beam/ParsingAtomClassTest.java
=====================================
@@ -80,6 +80,8 @@ public class ParsingAtomClassTest {
 
     private void verify(String str, int atomClass) throws
                                                    InvalidSmilesException {
-        assertThat(Parser.readClass(CharBuffer.fromString(str)), is(atomClass));
+        assertThat(new Parser(CharBuffer.fromString(""), Mode.Default)
+                           .readClass(CharBuffer.fromString(str)),
+                   is(atomClass));
     }
 }


=====================================
core/src/test/java/uk/ac/ebi/beam/ParsingBracketAtomTest.java
=====================================
@@ -134,7 +134,7 @@ public class ParsingBracketAtomTest {
 
     private Atom parse(String str) throws InvalidSmilesException {
         CharBuffer buffer = CharBuffer.fromString(str);
-        return new Parser(buffer, false).molecule().atom(0);
+        return new Parser(buffer, Mode.Relaxed).molecule().atom(0);
     }
 
     private Atom atom(Element e) {


=====================================
core/src/test/java/uk/ac/ebi/beam/RelaxedParsingTest.java
=====================================
@@ -0,0 +1,61 @@
+package uk.ac.ebi.beam;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class RelaxedParsingTest {
+
+    public static void assertRelaxed(String exp, String smi) throws IOException {
+        Assert.assertThrows(smi + " should throw and exception when not relaxed parsing",
+                            InvalidSmilesException.class, () -> {Parser.parse(smi);});
+        String act = Parser.relaxed(smi).toSmiles();
+        Assert.assertEquals(smi + " was not parsed expected", exp, act);
+    }
+
+    @Test
+    public void testRelaxedParsing_unclosedRings() throws IOException {
+        assertRelaxed("CCCC", "C1CCC");
+        assertRelaxed("CCCC", "C%CCC");
+        assertRelaxed("C.CCC", "C.1CCC");
+        assertRelaxed("CC1CCCC1", "CC1CCCC12");
+        assertRelaxed("CC1CCCC1", "CC12CCCC1");
+        assertRelaxed("CC1CCCC1", "CC12CCCC1");
+        assertRelaxed("CC", "C1C1");
+        assertRelaxed("C#1CCC1", "C=1CCC#1");
+        assertRelaxed("C=1CCC1", "C#1CCC=1");
+    }
+
+    @Test
+    public void testRelaxedParsing_unclosedBranches() throws IOException {
+        assertRelaxed("CCCC", "C(CCC");
+        assertRelaxed("CCCC", "C((CCC");
+        assertRelaxed("CCCC", "C)CCC");
+        assertRelaxed("CCCC", "C))CCC");
+        assertRelaxed("CCC(CO)O", "CCC(CO)O(");
+    }
+
+    @Test
+    public void testRelaxedParsing_bondTypes() throws IOException {
+        assertRelaxed("CC#CC", "CC-=#CC");
+        assertRelaxed("CC=CC", "CC-==CC");
+        assertRelaxed("CC=CC", "CC#=CC");
+        assertRelaxed("CC.CC", "CC..CC");
+        assertRelaxed("CC.CC", "CC..CC");
+        // assertRelaxed("CC", ".CC");
+        assertRelaxed("CC", "=CC");
+    }
+
+    @Test
+    public void testRelaxedParsing_atoms() throws IOException {
+        assertRelaxed("CC[NH2]", "CC[NH2");
+        assertRelaxed("CC[*]", "CC[NBoc");
+        assertRelaxed("CC*", "CC[");
+        assertRelaxed("CC", "]CC");
+        assertRelaxed("CC", "@]CC");
+        assertRelaxed("[H]CC", "@H]CC");
+        assertRelaxed("CC", "+]CC");
+    }
+
+}


=====================================
exec/pom.xml
=====================================
@@ -5,10 +5,10 @@
   <parent>
     <artifactId>beam</artifactId>
     <groupId>uk.ac.ebi.beam</groupId>
-    <version>1.3.9</version>
+    <version>1.4</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
-
+  <name>beam-exec</name>
   <artifactId>beam-exec</artifactId>
   
   <dependencies>


=====================================
func/pom.xml
=====================================
@@ -5,7 +5,7 @@
     <parent>
         <artifactId>beam</artifactId>
         <groupId>uk.ac.ebi.beam</groupId>
-        <version>1.3.9</version>
+        <version>1.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 


=====================================
func/src/main/java/uk/ac/ebi/beam/NormaliseDirectionalLabels.java
=====================================
@@ -86,7 +86,7 @@ final class NormaliseDirectionalLabels
                     if (cmp != 0) return cmp;
                     int max1 = Math.max(ordering[u1], ordering[v1]);
                     int max2 = Math.max(ordering[u2], ordering[v2]);
-                    return max1 - max2;
+                    return Integer.compare(max1, max2);
                 }
             });
 


=====================================
pom.xml
=====================================
@@ -7,7 +7,7 @@
     <description>SMILES parsing and generation library for cheminformatics</description>
     <url>http://www.github.com/johnmay/beam/</url>
     <packaging>pom</packaging>
-    <version>1.3.9</version>
+    <version>1.4</version>
     <modules>
         <module>core</module>
         <module>func</module>
@@ -24,12 +24,12 @@
     </issueManagement>
     <distributionManagement>
         <snapshotRepository>
-            <id>ossrh</id>
-            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+            <id>central</id>
+            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
         </snapshotRepository>
         <repository>
-            <id>ossrh</id>
-            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+            <id>central</id>
+            <url>https://central.sonatype.com/</url>
         </repository>
     </distributionManagement>
     <properties>
@@ -91,14 +91,14 @@
             <build>
                 <plugins>
                     <plugin>
-                        <groupId>org.sonatype.plugins</groupId>
-                        <artifactId>nexus-staging-maven-plugin</artifactId>
-                        <version>1.6.13</version>
+                        <groupId>org.sonatype.central</groupId>
+                        <artifactId>central-publishing-maven-plugin</artifactId>
+                        <version>0.8.0</version>
                         <extensions>true</extensions>
                         <configuration>
-                            <serverId>ossrh-beam</serverId>
-                            <nexusUrl>https://oss.sonatype.org/</nexusUrl>
-                            <autoReleaseAfterClose>true</autoReleaseAfterClose>
+                            <publishingServerId>central</publishingServerId>
+                            <autoPublish>true</autoPublish>
+                            <waitUntil>published</waitUntil>
                         </configuration>
                     </plugin>
                     <plugin>
@@ -159,7 +159,7 @@
                     <plugin>
                         <groupId>org.jacoco</groupId>
                         <artifactId>jacoco-maven-plugin</artifactId>
-                        <version>0.8.5</version>
+                        <version>0.8.14</version>
                         <executions>
                             <execution>
                                 <id>prepare-agent</id>



View it on GitLab: https://salsa.debian.org/java-team/libbeam-java/-/commit/aca58b0f1b3848e0cca2c731fe97fd3e9b2d58bb

-- 
View it on GitLab: https://salsa.debian.org/java-team/libbeam-java/-/commit/aca58b0f1b3848e0cca2c731fe97fd3e9b2d58bb
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20260909/4fd4a736/attachment.htm>


More information about the pkg-java-commits mailing list