@@ -62,6 +62,52 @@ public final class PackageURL implements Serializable {
6262
6363 private static final char PERCENT_CHAR = '%' ;
6464
65+ /**
66+ * The PackageURL scheme constant
67+ */
68+ public static final String SCHEME = "pkg" ;
69+
70+ /**
71+ * The PackageURL scheme ({@code "pkg"}) constant followed by a colon ({@code ':'}).
72+ */
73+ private static final String SCHEME_PART = SCHEME + ':' ;
74+
75+ /**
76+ * The package "type" or package "protocol" such as maven, npm, nuget, gem, pypi, etc.
77+ * Required.
78+ */
79+ private String type ;
80+
81+ /**
82+ * The name prefix such as a Maven groupid, a Docker image owner, a GitHub user or organization.
83+ * Optional and type-specific.
84+ */
85+ private @ Nullable String namespace ;
86+
87+ /**
88+ * The name of the package.
89+ * Required.
90+ */
91+ private String name ;
92+
93+ /**
94+ * The version of the package.
95+ * Optional.
96+ */
97+ private @ Nullable String version ;
98+
99+ /**
100+ * Extra qualifying data for a package such as an OS, architecture, a distro, etc.
101+ * Optional and type-specific.
102+ */
103+ private @ Nullable Map <String , String > qualifiers ;
104+
105+ /**
106+ * Extra subpath within a package, relative to the package root.
107+ * Optional.
108+ */
109+ private @ Nullable String subpath ;
110+
65111 /**
66112 * Constructs a new PackageURL object by parsing the specified string.
67113 *
@@ -111,12 +157,12 @@ public PackageURL(
111157 final @ Nullable String subpath )
112158 throws MalformedPackageURLException {
113159 this .type = toLowerCase (validateType (requireNonNull (type , "type" )));
114- this .namespace = validateNamespace (namespace );
115- this .name = validateName (requireNonNull (name , "name" ));
160+ this .namespace = validateNamespace (this . type , namespace );
161+ this .name = validateName (this . type , requireNonNull (name , "name" ));
116162 this .version = validateVersion (type , version );
117163 this .qualifiers = parseQualifiers (qualifiers );
118164 this .subpath = validateSubpath (subpath );
119- verifyTypeConstraints (this .type , this .namespace , this .name );
165+ verifyTypeConstraints (this .type , this .namespace , this .name , this . version , this . qualifiers );
120166 }
121167
122168 /**
@@ -143,52 +189,6 @@ public PackageURL(
143189 this (type , namespace , name , version , (qualifiers != null ) ? new TreeMap <>(qualifiers ) : null , subpath );
144190 }
145191
146- /**
147- * The PackageURL scheme constant
148- */
149- public static final String SCHEME = "pkg" ;
150-
151- /**
152- * The PackageURL scheme ({@code "pkg"}) constant followed by a colon ({@code ':'}).
153- */
154- private static final String SCHEME_PART = SCHEME + ':' ;
155-
156- /**
157- * The package "type" or package "protocol" such as maven, npm, nuget, gem, pypi, etc.
158- * Required.
159- */
160- private String type ;
161-
162- /**
163- * The name prefix such as a Maven groupid, a Docker image owner, a GitHub user or organization.
164- * Optional and type-specific.
165- */
166- private @ Nullable String namespace ;
167-
168- /**
169- * The name of the package.
170- * Required.
171- */
172- private String name ;
173-
174- /**
175- * The version of the package.
176- * Optional.
177- */
178- private @ Nullable String version ;
179-
180- /**
181- * Extra qualifying data for a package such as an OS, architecture, a distro, etc.
182- * Optional and type-specific.
183- */
184- private @ Nullable Map <String , String > qualifiers ;
185-
186- /**
187- * Extra subpath within a package, relative to the package root.
188- * Optional.
189- */
190- private @ Nullable String subpath ;
191-
192192 /**
193193 * Converts this {@link PackageURL} to a {@link PackageURLBuilder}.
194194 *
@@ -276,7 +276,7 @@ public Map<String, String> getQualifiers() {
276276 return subpath ;
277277 }
278278
279- private void validateScheme (final String value ) throws MalformedPackageURLException {
279+ private static void validateScheme (final String value ) throws MalformedPackageURLException {
280280 if (!SCHEME .equals (value )) {
281281 throw new MalformedPackageURLException (
282282 "The PackageURL scheme '" + value + "' is invalid. It should be '" + SCHEME + "'" );
@@ -321,14 +321,16 @@ private static void validateChars(String value, IntPredicate predicate, String c
321321 }
322322 }
323323
324- private @ Nullable String validateNamespace (final @ Nullable String value ) throws MalformedPackageURLException {
324+ private static @ Nullable String validateNamespace (final String type , final @ Nullable String value )
325+ throws MalformedPackageURLException {
325326 if (isEmpty (value )) {
326327 return null ;
327328 }
328- return validateNamespace (value .split ("/" ));
329+ return validateNamespace (type , value .split ("/" ));
329330 }
330331
331- private @ Nullable String validateNamespace (final String [] values ) throws MalformedPackageURLException {
332+ private static @ Nullable String validateNamespace (final String type , final String [] values )
333+ throws MalformedPackageURLException {
332334 if (values .length == 0 ) {
333335 return null ;
334336 }
@@ -362,7 +364,7 @@ private static void validateChars(String value, IntPredicate predicate, String c
362364 return retVal ;
363365 }
364366
365- private String validateName (final String value ) throws MalformedPackageURLException {
367+ private static String validateName (final String type , final String value ) throws MalformedPackageURLException {
366368 if (value .isEmpty ()) {
367369 throw new MalformedPackageURLException ("The PackageURL name specified is invalid" );
368370 }
@@ -393,7 +395,7 @@ private String validateName(final String value) throws MalformedPackageURLExcept
393395 return temp ;
394396 }
395397
396- private @ Nullable String validateVersion (final String type , final @ Nullable String value ) {
398+ private static @ Nullable String validateVersion (final String type , final @ Nullable String value ) {
397399 if (value == null ) {
398400 return null ;
399401 }
@@ -408,7 +410,7 @@ private String validateName(final String value) throws MalformedPackageURLExcept
408410 }
409411 }
410412
411- private @ Nullable Map <String , String > validateQualifiers (final @ Nullable Map <String , String > values )
413+ private static @ Nullable Map <String , String > validateQualifiers (final @ Nullable Map <String , String > values )
412414 throws MalformedPackageURLException {
413415 if (values == null || values .isEmpty ()) {
414416 return null ;
@@ -438,7 +440,7 @@ private static void validateValue(final String key, final @Nullable String value
438440 }
439441 }
440442
441- private @ Nullable String validateSubpath (final @ Nullable String value ) throws MalformedPackageURLException {
443+ private static @ Nullable String validateSubpath (final @ Nullable String value ) throws MalformedPackageURLException {
442444 if (isEmpty (value )) {
443445 return null ;
444446 }
@@ -597,20 +599,13 @@ private static String toLowerCase(String s) {
597599 return new String (chars );
598600 }
599601
600- private static int indexOfPercentChar (final byte [] bytes , final int start ) {
601- return IntStream .range (start , bytes .length )
602+ private static int indexOfFirstPercentChar (final byte [] bytes ) {
603+ return IntStream .range (0 , bytes .length )
602604 .filter (i -> isPercent (bytes [i ]))
603605 .findFirst ()
604606 .orElse (-1 );
605607 }
606608
607- private static int indexOfUnsafeChar (final byte [] bytes , final int start ) {
608- return IntStream .range (start , bytes .length )
609- .filter (i -> shouldEncode (bytes [i ]))
610- .findFirst ()
611- .orElse (-1 );
612- }
613-
614609 private static byte percentDecode (final byte [] bytes , final int start ) {
615610 if (start + 2 >= bytes .length ) {
616611 throw new ValidationException ("Incomplete percent encoding at offset " + start + " with value '"
@@ -644,7 +639,7 @@ private static String percentDecode(final String source) {
644639 }
645640
646641 byte [] bytes = source .getBytes (StandardCharsets .UTF_8 );
647- int i = indexOfPercentChar (bytes , 0 );
642+ int i = indexOfFirstPercentChar (bytes );
648643
649644 if (i == -1 ) {
650645 return source ;
@@ -779,13 +774,13 @@ private void parse(final String purl) throws MalformedPackageURLException {
779774 // The 'remainder' should now consist of an optional namespace and the name
780775 index = remainder .lastIndexOf ('/' );
781776 if (index <= start ) {
782- this .name = validateName (percentDecode (remainder .substring (start )));
777+ this .name = validateName (this . type , percentDecode (remainder .substring (start )));
783778 } else {
784- this .name = validateName (percentDecode (remainder .substring (index + 1 )));
779+ this .name = validateName (this . type , percentDecode (remainder .substring (index + 1 )));
785780 remainder = remainder .substring (0 , index );
786- this .namespace = validateNamespace (parsePath (remainder .substring (start ), false ));
781+ this .namespace = validateNamespace (this . type , parsePath (remainder .substring (start ), false ));
787782 }
788- verifyTypeConstraints (this .type , this .namespace , this .name );
783+ verifyTypeConstraints (this .type , this .namespace , this .name , this . version , this . qualifiers );
789784 } catch (URISyntaxException e ) {
790785 throw new MalformedPackageURLException ("Invalid purl: " + e .getMessage (), e );
791786 }
@@ -797,7 +792,12 @@ private void parse(final String purl) throws MalformedPackageURLException {
797792 * @param namespace the purl namespace
798793 * @throws MalformedPackageURLException if constraints are not met
799794 */
800- private void verifyTypeConstraints (final String type , final String namespace , final String name )
795+ private void verifyTypeConstraints (
796+ final String type ,
797+ final @ Nullable String namespace ,
798+ final @ Nullable String name ,
799+ final @ Nullable String version ,
800+ final @ Nullable Map <String , String > qualifiers )
801801 throws MalformedPackageURLException {
802802 switch (type ) {
803803 case StandardTypes .CONAN :
@@ -843,6 +843,7 @@ private void verifyTypeConstraints(final String type, final String namespace, fi
843843 URL url = new URL (repositoryUrl );
844844 host = url .getHost ();
845845 if (host .matches (".*[.]?azuredatabricks.net$" )) {
846+ // TODO: Move this eventually
846847 this .name = name .toLowerCase ();
847848 }
848849 } catch (MalformedURLException e ) {
@@ -862,7 +863,7 @@ private void verifyTypeConstraints(final String type, final String namespace, fi
862863 }
863864 }
864865
865- private @ Nullable Map <String , String > parseQualifiers (final @ Nullable Map <String , String > qualifiers )
866+ private static @ Nullable Map <String , String > parseQualifiers (final @ Nullable Map <String , String > qualifiers )
866867 throws MalformedPackageURLException {
867868 if (qualifiers == null || qualifiers .isEmpty ()) {
868869 return null ;
@@ -882,7 +883,7 @@ private void verifyTypeConstraints(final String type, final String namespace, fi
882883 }
883884
884885 @ SuppressWarnings ("StringSplitter" ) // reason: surprising behavior is okay in this case
885- private @ Nullable Map <String , String > parseQualifiers (final String encodedString )
886+ private static @ Nullable Map <String , String > parseQualifiers (final String encodedString )
886887 throws MalformedPackageURLException {
887888 try {
888889 final TreeMap <String , String > results = Arrays .stream (encodedString .split ("&" ))
@@ -913,7 +914,7 @@ private static String[] parsePath(final String value, final boolean isSubpath) {
913914 .toArray (String []::new );
914915 }
915916
916- private String encodePath (final String path ) {
917+ private static String encodePath (final String path ) {
917918 return Arrays .stream (path .split ("/" )).map (PackageURL ::percentEncode ).collect (Collectors .joining ("/" ));
918919 }
919920
0 commit comments