11/*
2- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
@@ -102,10 +102,20 @@ of its enclosing class or interface, whether direct or inherited
102102that were later generified remain the same.
103103
104104usage: the checker is run from a module specific test
105- `@run main SinceChecker <moduleName> [--exclude package1,package2 | --exclude package1 package2]`
105+ `@run main SinceChecker <moduleName> [--ignoreSince <string1>,<string2>] [--exclude package1,package2 | --exclude package1 package2]`
106+
107+ To help long running projects still in development, that do not have a fixed version number that conforms
108+ to the OpenJDK release cycle, one may want to use token name instead of continuely updating the current version since tags.
109+ For example, `@since LongRunningProjectName`. The option `--ignoreSince` maybe used to
110+ ignore these tags (`-ignoreSince LongRunningProjectName`). Maybe be specified multiple times.
106111*/
107112
108113public class SinceChecker {
114+ private static final int JDK_CURRENT = Runtime .version ().feature ();
115+ // Ignored since tags
116+ private static final Set <String > IGNORE_LIST = new HashSet <>();
117+ // Simply replace ignored since tags with the latest version
118+ private static final Version IGNORE_VERSION = Version .parse (Integer .toString (JDK_CURRENT ));
109119 private final Map <String , Set <String >> LEGACY_PREVIEW_METHODS = new HashMap <>();
110120 private final Map <String , IntroducedIn > classDictionary = new HashMap <>();
111121 private final JavaCompiler tool ;
@@ -125,10 +135,16 @@ public static void main(String[] args) throws Exception {
125135 }
126136 String moduleName = args [0 ];
127137 boolean excludeFlag = false ;
138+ boolean ignoreFlag = false ;
128139
129140 for (int i = 1 ; i < args .length ; i ++) {
130- if ("--exclude" .equals (args [i ])) {
141+ if ("--ignoreSince" .equals (args [i ])) {
142+ ignoreFlag = true ;
143+ excludeFlag = false ;
144+ continue ;
145+ } else if ("--exclude" .equals (args [i ])) {
131146 excludeFlag = true ;
147+ ignoreFlag = false ;
132148 continue ;
133149 }
134150
@@ -139,6 +155,14 @@ public static void main(String[] args) throws Exception {
139155 EXCLUDE_LIST .add (args [i ]);
140156 }
141157 }
158+
159+ if (ignoreFlag ) {
160+ if (args [i ].contains ("," )) {
161+ IGNORE_LIST .addAll (Arrays .asList (args [i ].split ("," )));
162+ } else {
163+ IGNORE_LIST .add (args [i ]);
164+ }
165+ }
142166 }
143167
144168 SinceChecker sinceCheckerTestHelper = new SinceChecker (moduleName );
@@ -152,7 +176,7 @@ private void error(String message) {
152176
153177 private SinceChecker (String moduleName ) throws IOException {
154178 tool = ToolProvider .getSystemJavaCompiler ();
155- for (int i = 9 ; i <= Runtime . version (). feature () ; i ++) {
179+ for (int i = 9 ; i <= JDK_CURRENT ; i ++) {
156180 DiagnosticListener <? super JavaFileObject > noErrors = d -> {
157181 if (!d .getCode ().equals ("compiler.err.module.not.found" )) {
158182 error (d .getMessage (null ));
@@ -402,7 +426,7 @@ private boolean isNotCommonRecordMethod(TypeElement te, Element element, Types t
402426
403427 private void analyzeClassCheck (TypeElement te , String version , EffectiveSourceSinceHelper javadocHelper ,
404428 Types types , Elements elementUtils ) {
405- String currentjdkVersion = String .valueOf (Runtime . version (). feature () );
429+ String currentjdkVersion = String .valueOf (JDK_CURRENT );
406430 if (!isDocumented (te )) {
407431 return ;
408432 }
@@ -452,24 +476,31 @@ private void checkElement(Element explicitOwner, Element element, Types types,
452476 }
453477
454478 private Version extractSinceVersionFromText (String documentation ) {
455- Pattern pattern = Pattern .compile ("@since\\ s+(\\ d+(?:\\ .\\ d+)?)" );
456- Matcher matcher = pattern .matcher (documentation );
457- if (matcher .find ()) {
458- String versionString = matcher .group (1 );
459- try {
460- if (versionString .equals ("1.0" )) {
461- versionString = "1" ; //ended up being necessary
462- } else if (versionString .startsWith ("1." )) {
463- versionString = versionString .substring (2 );
464- }
465- return Version .parse (versionString );
466- } catch (NumberFormatException ex ) {
467- error ("`@since` value that cannot be parsed: " + versionString );
468- return null ;
469- }
470- } else {
479+ Matcher matcher = Pattern .compile ("@since\\ s+(\\ S+)" ).matcher (documentation );
480+ if (!matcher .find ()) {
481+ return null ;
482+ }
483+
484+ String versionString = matcher .group (1 );
485+ if (IGNORE_LIST .contains (versionString )) {
486+ return IGNORE_VERSION ;
487+ }
488+
489+ versionString = switch (versionString ) {
490+ case "1.0" -> "1" ;
491+ case String v when v .matches ("1\\ .\\ d+\\ .\\ d+" ) -> "1" ; // `1.x.x` -> `1`
492+ case String v when v .startsWith ("1." ) -> v .substring (2 ); // `1.x` -> `x`
493+ case String v when v .contains ("u" ) -> v .substring (0 , v .indexOf ('u' )); // 6u25 -> 6
494+ default -> versionString ;
495+ };
496+
497+ if (!versionString .matches ("\\ d+(?:\\ .\\ d+)?" )) {
498+ error ("Non-numeric `@since` value encountered: '" + versionString +
499+ "'; If this is intentional, consider using the --ignoreSince option." );
471500 return null ;
472501 }
502+
503+ return Version .parse (versionString );
473504 }
474505
475506 private void checkEquals (String prefix , String sinceVersion , String mappedVersion , String name ) {
0 commit comments