-
-
Notifications
You must be signed in to change notification settings - Fork 578
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2400 from hborchardt/spdx-expressions
Implement SPDX expressions
- Loading branch information
Showing
14 changed files
with
854 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
src/main/java/org/dependencytrack/parser/spdx/expression/SpdxExpressionParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
* This file is part of Dependency-Track. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* Copyright (c) Steve Springett. All Rights Reserved. | ||
*/ | ||
package org.dependencytrack.parser.spdx.expression; | ||
|
||
import java.util.ArrayDeque; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
import org.dependencytrack.parser.spdx.expression.model.SpdxOperator; | ||
import org.dependencytrack.parser.spdx.expression.model.SpdxExpression; | ||
|
||
/** | ||
* This class parses SPDX expressions according to | ||
* https://spdx.github.io/spdx-spec/v2-draft/SPDX-license-expressions/ into a tree of | ||
* SpdxExpressions and SpdxExpressionOperations | ||
* | ||
* @author hborchardt | ||
* @since 4.8.0 | ||
*/ | ||
public class SpdxExpressionParser { | ||
|
||
/** | ||
* Reads in a SPDX expression and returns a parsed tree of SpdxExpressionOperators and license | ||
* ids. | ||
* | ||
* @param spdxExpression | ||
* spdx expression string | ||
* @return parsed SpdxExpression tree, or SpdxExpression.INVALID if an error has occurred during | ||
* parsing | ||
*/ | ||
public SpdxExpression parse(final String spdxExpression) { | ||
// operators are surrounded by spaces or brackets. Let's make our life easier and surround brackets by spaces. | ||
var _spdxExpression = spdxExpression.replace("(", " ( ").replace(")", " ) ").split(" "); | ||
if (_spdxExpression.length == 1) { | ||
return new SpdxExpression(spdxExpression); | ||
} | ||
|
||
// Shunting yard algorithm to convert SPDX expression to reverse polish notation | ||
// specify list of infix operators | ||
List<String> infixOperators = List.of(SpdxOperator.OR.getToken(), SpdxOperator.AND.getToken(), | ||
SpdxOperator.WITH.getToken()); | ||
|
||
ArrayDeque<String> operatorStack = new ArrayDeque<>(); | ||
ArrayDeque<String> outputQueue = new ArrayDeque<>(); | ||
Iterator<String> it = List.of(_spdxExpression).iterator(); | ||
while(it.hasNext()) { | ||
var token = it.next(); | ||
if (token.length() == 0) { | ||
continue; | ||
} | ||
if (infixOperators.contains(token)) { | ||
int opPrecedence = SpdxOperator.valueOf(token).getPrecedence(); | ||
for (String o2; (o2 = operatorStack.peek()) != null && !o2.equals("(") | ||
&& SpdxOperator.valueOf(o2).getPrecedence() > opPrecedence;) { | ||
outputQueue.push(operatorStack.pop()); | ||
} | ||
; | ||
operatorStack.push(token); | ||
} else if (token.equals("(")) { | ||
operatorStack.push(token); | ||
} else if (token.equals(")")) { | ||
for (String o2; (o2 = operatorStack.peek()) == null || !o2.equals("(");) { | ||
if (o2 == null) { | ||
// Mismatched parentheses | ||
return SpdxExpression.INVALID; | ||
} | ||
outputQueue.push(operatorStack.pop()); | ||
} | ||
; | ||
String leftParens = operatorStack.pop(); | ||
|
||
if (!"(".equals(leftParens)) { | ||
// Mismatched parentheses | ||
return SpdxExpression.INVALID; | ||
} | ||
// no function tokens implemented | ||
} else { | ||
outputQueue.push(token); | ||
} | ||
} | ||
for (String o2; (o2 = operatorStack.peek()) != null;) { | ||
if ("(".equals(o2)) { | ||
// Mismatched parentheses | ||
return SpdxExpression.INVALID; | ||
} | ||
outputQueue.push(operatorStack.pop()); | ||
} | ||
|
||
// convert RPN stack into tree | ||
// this is easy because all infix operators have two arguments | ||
ArrayDeque<SpdxExpression> expressions = new ArrayDeque<>(); | ||
SpdxExpression expr = null; | ||
while (!outputQueue.isEmpty()) { | ||
var token = outputQueue.pollLast(); | ||
if (infixOperators.contains(token)) { | ||
var rhs = expressions.pop(); | ||
var lhs = expressions.pop(); | ||
expr = new SpdxExpression(SpdxOperator.valueOf(token), List.of(lhs, rhs)); | ||
} else { | ||
if (token.endsWith("+")) { | ||
// trailing `+` is not a whitespace-delimited operator - process it separately | ||
expr = new SpdxExpression(SpdxOperator.PLUS, | ||
List.of(new SpdxExpression(token.substring(0, token.length() - 1)))); | ||
} else { | ||
expr = new SpdxExpression(token); | ||
} | ||
} | ||
expressions.push(expr); | ||
} | ||
return expr; | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
src/main/java/org/dependencytrack/parser/spdx/expression/model/SpdxExpression.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* This file is part of Dependency-Track. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* Copyright (c) Steve Springett. All Rights Reserved. | ||
*/ | ||
package org.dependencytrack.parser.spdx.expression.model; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* A node of an SPDX expression tree. If it is a leaf node, it contains a spdxLicenseId. If it is an | ||
* inner node, containss an operation. | ||
* | ||
* @author hborchardt | ||
* @since 4.8.0 | ||
*/ | ||
public class SpdxExpression { | ||
public static final SpdxExpression INVALID = new SpdxExpression(null); | ||
public SpdxExpression(String spdxLicenseId) { | ||
this.spdxLicenseId = spdxLicenseId; | ||
} | ||
|
||
public SpdxExpression(SpdxOperator operator, List<SpdxExpression> arguments) { | ||
this.operation = new SpdxExpressionOperation(operator, arguments); | ||
} | ||
|
||
private SpdxExpressionOperation operation; | ||
private String spdxLicenseId; | ||
|
||
public SpdxExpressionOperation getOperation() { | ||
return operation; | ||
} | ||
|
||
public void setOperation(SpdxExpressionOperation operation) { | ||
this.operation = operation; | ||
} | ||
|
||
public String getSpdxLicenseId() { | ||
return spdxLicenseId; | ||
} | ||
|
||
public void setSpdxLicenseId(String spdxLicenseId) { | ||
this.spdxLicenseId = spdxLicenseId; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
if (this == INVALID) { | ||
return "INVALID"; | ||
} | ||
if (spdxLicenseId != null) { | ||
return spdxLicenseId; | ||
} | ||
return operation.toString(); | ||
} | ||
} |
Oops, something went wrong.