Skip to content

Commit 7c53cb7

Browse files
committed
Intermediate checkin - working on external licenses
Signed-off-by: Gary O'Neall <gary@sourceauditor.com>
1 parent 51f6eb6 commit 7c53cb7

3 files changed

Lines changed: 252 additions & 76 deletions

File tree

src/main/java/org/spdx/storage/listedlicense/SpdxListedLicenseModelStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public static String objectUriToLicenseOrExceptionId(String objectUri) {
120120
}
121121

122122
public static String licenseOrExceptionIdToObjectUri(String id) {
123-
return LISTED_LICENSE_NAMESPACE + "/" + id;
123+
return LISTED_LICENSE_NAMESPACE + id;
124124
}
125125

126126
/**

src/main/java/org/spdx/utility/license/LicenseExpressionParser.java

Lines changed: 119 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import java.util.Optional;
2828
import java.util.Stack;
2929

30+
import javax.annotation.Nullable;
31+
32+
import org.spdx.core.DefaultModelStore;
3033
import org.spdx.core.IModelCopyManager;
3134
import org.spdx.core.InvalidSPDXAnalysisException;
3235
import org.spdx.library.LicenseInfoFactory;
@@ -45,20 +48,25 @@
4548
import org.spdx.library.model.v2.license.SpdxNoAssertionLicense;
4649
import org.spdx.library.model.v2.license.SpdxNoneLicense;
4750
import org.spdx.library.model.v2.license.WithExceptionOperator;
51+
import org.spdx.library.model.v3.ExternalCustomLicense;
52+
import org.spdx.library.model.v3.ExternalCustomLicenseAddition;
4853
import org.spdx.library.model.v3.SpdxConstantsV3;
54+
import org.spdx.library.model.v3.core.NamespaceMap;
55+
import org.spdx.library.model.v3.core.SpdxDocument;
4956
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingConjunctiveLicenseSet;
5057
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingCustomLicense;
58+
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingCustomLicenseAddition;
5159
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingDisjunctiveLicenseSet;
5260
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingExtendableLicense;
5361
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingLicense;
62+
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingLicenseAddition;
5463
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingListedLicense;
5564
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingListedLicenseException;
5665
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingNoAssertionLicense;
5766
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingNoneLicense;
5867
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingOrLaterOperator;
5968
import org.spdx.library.model.v3.expandedlicensing.ExpandedLicensingWithAdditionOperator;
6069
import org.spdx.library.model.v3.simplelicensing.SimpleLicensingAnyLicenseInfo;
61-
import org.spdx.storage.CompatibleModelStoreWrapper;
6270
import org.spdx.storage.IModelStore;
6371
import org.spdx.storage.IModelStore.IdType;
6472

@@ -98,24 +106,27 @@ enum Operator {
98106
* @param customLicenseUriPrefix Prefix for Object URI's created when appending custom license ID's or custom license additions. If any custom licenses or additions already exist, they will be used.
99107
* If none exist for an ID, they will be added. If null, the default model document URI will be used.
100108
* @param copyManager if non-null, allows for copying of any properties set which use other model stores or document URI's
109+
* @param spdxDocument enclosing SPDX document - required if there are any external custom licenses or additions
101110
* @return the parsed license expression
102111
* @throws InvalidSPDXAnalysisException
103112
*/
104113
public static SimpleLicensingAnyLicenseInfo parseLicenseExpression(String expression, IModelStore store,
105-
String customLicenseUriPrefix, IModelCopyManager copyManager) throws InvalidSPDXAnalysisException {
114+
String customLicenseUriPrefix, @Nullable IModelCopyManager copyManager, @Nullable SpdxDocument spdxDocument) throws InvalidSPDXAnalysisException {
106115
if (expression == null || expression.trim().isEmpty()) {
107116
throw new LicenseParserException("Empty license expression");
108117
}
109118
Objects.requireNonNull(store, "Model store can not be null");
110-
Objects.requireNonNull(customLicenseUriPrefix, "Document URI can not be null");
119+
if (Objects.isNull(customLicenseUriPrefix)) {
120+
customLicenseUriPrefix = DefaultModelStore.getDefaultDocumentUri() + "#";
121+
}
111122
String[] tokens = tokenizeExpression(expression);
112123
if (tokens.length == 1 && tokens[0].equals(SpdxConstantsCompatV2.NOASSERTION_VALUE)) {
113124
return new ExpandedLicensingNoAssertionLicense();
114125
} else if (tokens.length == 1 && tokens[0].equals(SpdxConstantsCompatV2.NONE_VALUE)) {
115126
return new ExpandedLicensingNoneLicense();
116127
} else {
117128
try {
118-
return parseLicenseExpression(tokens, store, customLicenseUriPrefix, copyManager);
129+
return parseLicenseExpression(tokens, store, customLicenseUriPrefix, copyManager, spdxDocument);
119130
} catch (LicenseParserException ex) {
120131
// Add the expression to the error message to provide additional information to the user
121132
throw new LicenseParserException(ex.getMessage()+" License expression: '"+expression+"'", ex);
@@ -203,11 +214,13 @@ private static void processPreToken(String preToken,
203214
* @param customLicenseUriPrefix Prefix for Object URI's created when appending custom license ID's or custom license additions. If any custom licenses or additions already exist, they will be used.
204215
* If none exist for an ID, they will be added. If null, the default model document URI will be used.
205216
* @param copyManager if non-null, allows for copying of any properties set which use other model stores or document URI's
206-
* @return
217+
* @param spdxDocument enclosing SPDX document - required if there are any external custom licenses or additions
218+
* @return a license info representing the fully parsed list of tokens
207219
* @throws InvalidSPDXAnalysisException
208220
*/
209221
private static SimpleLicensingAnyLicenseInfo parseLicenseExpression(String[] tokens, IModelStore store,
210-
String customLicenseUriPrefix, IModelCopyManager copyManager) throws InvalidSPDXAnalysisException {
222+
String customLicenseUriPrefix, @Nullable IModelCopyManager copyManager,
223+
@Nullable SpdxDocument spdxDocument) throws InvalidSPDXAnalysisException {
211224
if (tokens == null || tokens.length == 0) {
212225
throw new LicenseParserException("Expected license expression");
213226
}
@@ -224,10 +237,11 @@ private static SimpleLicensingAnyLicenseInfo parseLicenseExpression(String[] tok
224237
throw new LicenseParserException("Missing right parenthesis");
225238
}
226239
String[] nestedTokens = Arrays.copyOfRange(tokens, tokenIndex, rightParenIndex);
227-
operandStack.push(parseLicenseExpression(nestedTokens, store, customLicenseUriPrefix, copyManager));
240+
operandStack.push(parseLicenseExpression(nestedTokens, store, customLicenseUriPrefix,
241+
copyManager, spdxDocument));
228242
tokenIndex = rightParenIndex + 1;
229243
} else if (OPERATOR_MAP.get(token) == null) { // assumed to be a simple licensing type
230-
operandStack.push(parseSimpleLicenseToken(token, store, customLicenseUriPrefix, copyManager));
244+
operandStack.push(parseSimpleLicenseToken(token, store, customLicenseUriPrefix, copyManager, spdxDocument));
231245
} else {
232246
Operator operator = OPERATOR_MAP.get(token);
233247
if (operator == Operator.WITH) {
@@ -240,21 +254,8 @@ private static SimpleLicensingAnyLicenseInfo parseLicenseExpression(String[] tok
240254
throw new LicenseParserException("Missing exception clause");
241255
}
242256
token = tokens[tokenIndex++];
243-
//TODO Update this to handle custom additions
244-
ExpandedLicensingListedLicenseException licenseException = null;
245-
Optional<String> exceptionId = Optional.empty();
246-
if (LicenseInfoFactory.isSpdxListedExceptionId(token)) {
247-
exceptionId = LicenseInfoFactory.listedExceptionIdCaseSensitive(token);
248-
}
249-
if (exceptionId.isPresent()) {
250-
licenseException = LicenseInfoFactory.getListedExceptionById(exceptionId.get());
251-
} else if (token.startsWith(SpdxConstantsCompatV2.NON_STD_LICENSE_ID_PRENUM)) {
252-
throw new LicenseParserException("WITH must be followed by a license exception. "+token+" is a Listed License type.");
253-
} else {
254-
licenseException = new ExpandedLicensingListedLicenseException(store,
255-
CompatibleModelStoreWrapper.documentUriIdToUri(customLicenseUriPrefix, token, false),
256-
copyManager, true);
257-
}
257+
ExpandedLicensingLicenseAddition licenseAddition = parseSimpleLicenseAdditionToken(token,
258+
store, customLicenseUriPrefix, copyManager, spdxDocument);
258259
SimpleLicensingAnyLicenseInfo operand = operandStack.pop();
259260
if (operand == null) {
260261
throw new LicenseParserException("Missing license for with clause");
@@ -265,7 +266,7 @@ private static SimpleLicensingAnyLicenseInfo parseLicenseExpression(String[] tok
265266
ExpandedLicensingWithAdditionOperator weo = new ExpandedLicensingWithAdditionOperator(store,
266267
store.getNextId(IdType.Anonymous), copyManager, true);
267268
weo.setExpandedLicensingSubjectExtendableLicense((ExpandedLicensingExtendableLicense)operand);
268-
weo.setExpandedLicensingSubjectAddition(licenseException);
269+
weo.setExpandedLicensingSubjectAddition(licenseAddition);
269270
operandStack.push(weo);
270271
} else {
271272
// process in order of precedence using the shunting yard algorithm
@@ -403,25 +404,79 @@ private static int findMatchingParen(String[] tokens, int startToken) {
403404
}
404405
return -1;
405406
}
407+
408+
/**
409+
* Converts a string token into its equivalent license addition
410+
* checking for a listed license
411+
* @param token Token to translate to the equivalent license addition
412+
* @param store Store for the licenses
413+
* @param customLicenseUriPrefix Prefix to use for any created local licenses or additions
414+
* @param copyManager to use when copying from the listed license store
415+
* @param spdxDocument enclosing SPDX document - required if there are any external custom licenses or additions
416+
* @return a CustomLicenseAddition, ListedLicense, ListedLicenseException or CustomLicense depending on what is in the store
417+
* @throws InvalidSPDXAnalysisException
418+
*/
419+
private static ExpandedLicensingLicenseAddition parseSimpleLicenseAdditionToken(String token, IModelStore store, String customLicenseUriPrefix,
420+
@Nullable IModelCopyManager copyManager, @Nullable SpdxDocument spdxDocument) throws InvalidSPDXAnalysisException {
421+
Objects.requireNonNull(token, "Token can not be null");
422+
Objects.requireNonNull(store, "Model store can not be null");
423+
Objects.requireNonNull(customLicenseUriPrefix, "URI Prefix can not be null");
424+
if (token.contains(":")) {
425+
// External License Ref
426+
return new ExternalCustomLicenseAddition(store,
427+
convertToExternalObjectUri(token, spdxDocument), copyManager);
428+
}
429+
Optional<String> exceptionId = Optional.empty();
430+
if (LicenseInfoFactory.isSpdxListedExceptionId(token)) {
431+
// listed exception
432+
exceptionId = LicenseInfoFactory.listedExceptionIdCaseSensitive(token);
433+
}
434+
if (exceptionId.isPresent()) {
435+
ExpandedLicensingListedLicenseException listedException = LicenseInfoFactory.getListedExceptionById(exceptionId.get());
436+
if (!store.exists(SpdxConstantsCompatV2.LISTED_LICENSE_NAMESPACE_PREFIX + exceptionId.get())) {
437+
if (Objects.nonNull(copyManager)) {
438+
// copy to the local store
439+
copyManager.copy(store, listedException.getObjectUri(), listedException.getModelStore(),
440+
listedException.getObjectUri(), SpdxConstantsV3.EXPANDED_LICENSING_EXPANDED_LICENSING_LISTED_LICENSE,
441+
SpdxModelFactory.getLatestSpecVersion(), null);
442+
// copy to the local store
443+
}
444+
}
445+
return new ExpandedLicensingListedLicenseException(store, listedException.getObjectUri(), copyManager, true);
446+
} else {
447+
// custom addition
448+
String objectUri = customLicenseUriPrefix + token;
449+
ExpandedLicensingCustomLicenseAddition localAddition = null;
450+
if (store.exists(objectUri)) {
451+
localAddition = new ExpandedLicensingCustomLicenseAddition(store, objectUri, copyManager, false);
452+
} else {
453+
localAddition = new ExpandedLicensingCustomLicenseAddition(store, objectUri, copyManager, true);
454+
localAddition.setExpandedLicensingAdditionText(UNINITIALIZED_LICENSE_TEXT);
455+
}
456+
return localAddition;
457+
}
458+
}
406459

407460
/**
408461
* Converts a string token into its equivalent license
409462
* checking for a listed license
410-
* @param token
411-
* @param baseStore
412-
* @param customLicenseUriPrefix
413-
* @param copyManager
414-
* @return
463+
* @param token Token to translate to the equivalent license
464+
* @param store Store for the licenses
465+
* @param customLicenseUriPrefix Prefix to use for any created local licenses or additions
466+
* @param copyManager to use when copying from the listed license store
467+
* @param spdxDocument enclosing SPDX document - required if there are any external custom licenses or additions
468+
* @return a CustomLicenseAddition, ListedLicense, ListedLicenseException or CustomLicense depending on what is in the store
415469
* @throws InvalidSPDXAnalysisException
416470
*/
417471
private static SimpleLicensingAnyLicenseInfo parseSimpleLicenseToken(String token, IModelStore store, String customLicenseUriPrefix,
418-
IModelCopyManager copyManager) throws InvalidSPDXAnalysisException {
472+
@Nullable IModelCopyManager copyManager, @Nullable SpdxDocument spdxDocument) throws InvalidSPDXAnalysisException {
419473
Objects.requireNonNull(token, "Token can not be null");
420474
Objects.requireNonNull(store, "Model store can not be null");
421-
Objects.requireNonNull(customLicenseUriPrefix, "Document URI can not be null");
475+
Objects.requireNonNull(customLicenseUriPrefix, "URI Prefix can not be null");
422476
if (token.contains(":")) {
423-
// External License Ref
424-
return SpdxModelFactory.getExternalAnyLicenseInfo(store, customLicenseUriPrefix + token, copyManager, SpdxModelFactory.getLatestSpecVersion());
477+
// External Custom License
478+
return new ExternalCustomLicense(store,
479+
convertToExternalObjectUri(token, spdxDocument), copyManager);
425480
}
426481
Optional<String> licenseId = Optional.empty();
427482
if (LicenseInfoFactory.isSpdxListedLicenseId(token)) {
@@ -442,19 +497,44 @@ private static SimpleLicensingAnyLicenseInfo parseSimpleLicenseToken(String toke
442497
return new ExpandedLicensingListedLicense(store, listedLicense.getObjectUri(), copyManager, true);
443498
} else {
444499
// LicenseRef
445-
Optional<String> caseSensitiveId = store.getCaseSensisitiveId(customLicenseUriPrefix, token);
500+
String objectUri = customLicenseUriPrefix + token;
446501
ExpandedLicensingCustomLicense localLicense = null;
447-
if (caseSensitiveId.isPresent()) {
448-
localLicense = new ExpandedLicensingCustomLicense(store, customLicenseUriPrefix + caseSensitiveId.get(), copyManager, false);
449-
502+
if (store.exists(objectUri)) {
503+
localLicense = new ExpandedLicensingCustomLicense(store, objectUri, copyManager, false);
450504
} else {
451-
localLicense = new ExpandedLicensingCustomLicense(store, customLicenseUriPrefix + customLicenseUriPrefix, copyManager, true);
505+
localLicense = new ExpandedLicensingCustomLicense(store, objectUri, copyManager, true);
452506
localLicense.setSimpleLicensingLicenseText(UNINITIALIZED_LICENSE_TEXT);
453507
}
454508
return localLicense;
455509
}
456510
}
457511

512+
/**
513+
* @param externalReference String of the form [prefix]:[id] where [prefix] is a prefix in the spdxDocument and ID is the suffix of the object URI
514+
* @param spdxDocument document containing the license expression string and (importantly) the namespace map for any external URIs
515+
* @return the full object URI with the [prefix] replaced by the associated namespace
516+
* @throws InvalidSPDXAnalysisException
517+
*/
518+
private static String convertToExternalObjectUri(String externalReference, SpdxDocument spdxDocument) throws InvalidSPDXAnalysisException {
519+
if (Objects.isNull(spdxDocument)) {
520+
throw new LicenseParserException("References to external custom additions or external custom licenses must include the spdxDocument parameter");
521+
}
522+
String[] refParts = externalReference.split(":");
523+
if (refParts.length != 2 || refParts[0].isEmpty() || refParts[1].isEmpty()) {
524+
throw new LicenseParserException("Invalid external ID: "+externalReference);
525+
}
526+
String namespace = null;
527+
for (NamespaceMap nm:spdxDocument.getNamespaceMaps()) {
528+
if (refParts[0].equals(nm.getPrefix())) {
529+
namespace = nm.getNamespace();
530+
}
531+
}
532+
if (Objects.isNull(namespace)) {
533+
throw new InvalidSPDXAnalysisException("Prefix "+refParts[0]+" not found in SPDX document "+spdxDocument.toString());
534+
}
535+
return namespace + refParts[1];
536+
}
537+
458538
/**
459539
* Converts a string token into its equivalent license
460540
* checking for a listed license
@@ -469,7 +549,7 @@ private static AnyLicenseInfo parseSimpleLicenseTokenCompatV2(String token, IMod
469549
IModelCopyManager copyManager) throws InvalidSPDXAnalysisException {
470550
Objects.requireNonNull(token, "Token can not be null");
471551
Objects.requireNonNull(store, "Model store can not be null");
472-
Objects.requireNonNull(documentUri, "Document URI can not be null");
552+
Objects.requireNonNull(documentUri, "URI prefix can not be null");
473553
if (token.contains(":")) {
474554
// External License Ref
475555
return new ExternalExtractedLicenseInfo(store, documentUri, token, copyManager, true);

0 commit comments

Comments
 (0)