1010package org .elasticsearch .gradle .internal .transport ;
1111
1212import org .gradle .api .DefaultTask ;
13- import org .gradle .api .file .FileCollection ;
13+ import org .gradle .api .file .ConfigurableFileCollection ;
1414import org .gradle .api .file .RegularFileProperty ;
15- import org .gradle .api .provider . Property ;
15+ import org .gradle .api .tasks . Classpath ;
1616import org .gradle .api .tasks .InputFiles ;
1717import org .gradle .api .tasks .OutputFile ;
1818import org .gradle .api .tasks .TaskAction ;
1919import org .objectweb .asm .ClassReader ;
2020import org .objectweb .asm .ClassVisitor ;
21+ import org .objectweb .asm .Label ;
2122import org .objectweb .asm .MethodVisitor ;
2223import org .objectweb .asm .Opcodes ;
2324import org .objectweb .asm .tree .LdcInsnNode ;
2425import org .objectweb .asm .tree .MethodNode ;
2526
26- import java .io .File ;
27- import java .io .FileInputStream ;
28- import java .io .FileWriter ;
2927import java .io .IOException ;
3028import java .io .InputStream ;
31- import java .util .ArrayList ;
32- import java .util .Arrays ;
33- import java .util .Collection ;
29+ import java .nio .file .FileVisitResult ;
30+ import java .nio .file .Files ;
31+ import java .nio .file .Path ;
32+ import java .nio .file .SimpleFileVisitor ;
33+ import java .nio .file .attribute .BasicFileAttributes ;
3434import java .util .HashSet ;
35- import java .util .List ;
3635import java .util .Set ;
36+ import java .util .jar .JarInputStream ;
37+ import java .util .zip .ZipEntry ;
3738
3839/**
3940 * This task locates all method invocations of org.elasticsearch.TransportVersion#fromName(java.lang.String) in the
4041 * provided directory, and then records the value of string literals passed as arguments. It then records each
41- * string on a newline in the provided output file.
42+ * string on a newline along with path and line number in the provided output file.
4243 */
4344public abstract class CollectTransportVersionNamesTask extends DefaultTask {
4445 public static final String TRANSPORT_VERSION_SET_CLASS = "org/elasticsearch/TransportVersion" ;
@@ -50,7 +51,8 @@ public abstract class CollectTransportVersionNamesTask extends DefaultTask {
5051 * The directory to scan for method invocations.
5152 */
5253 @ InputFiles
53- public abstract Property <FileCollection > getClassDirs ();
54+ @ Classpath
55+ public abstract ConfigurableFileCollection getClassPath ();
5456
5557 /**
5658 * The output file, with each newline containing the string literal argument of each method
@@ -60,72 +62,89 @@ public abstract class CollectTransportVersionNamesTask extends DefaultTask {
6062 public abstract RegularFileProperty getOutputFile ();
6163
6264 @ TaskAction
63- public void checkTransportVersion () {
64- var classFiles = findJavaClassFiles (getClassDirs ().get ().getFiles ());
65- var tvNames = getTVDeclarationNames (classFiles );
65+ public void checkTransportVersion () throws IOException {
66+ var results = new HashSet <TransportVersionUtils .TransportVersionReference >();
6667
67- File file = getOutputFile ().get ().getAsFile ();
68- try (FileWriter writer = new FileWriter (file )) {
69- for (String tvName : tvNames ) {
70- writer .write (tvName + "\n " );
68+ for (var cpElement : getClassPath ()) {
69+ Path file = cpElement .toPath ();
70+ if (Files .isDirectory (file )) {
71+ addNamesFromClassesDirectory (results , file );
72+ } else {
73+ assert file .getFileName ().toString ().endsWith (".jar" );
74+ addNamesFromJar (results , file );
7175 }
72- } catch (IOException e ) {
73- throw new RuntimeException (e );
7476 }
77+
78+ Path outputFile = getOutputFile ().get ().getAsFile ().toPath ();
79+ Files .writeString (outputFile , String .join ("\n " , results .stream ().map (Object ::toString ).sorted ().toList ()));
7580 }
7681
77- public static Set <String > getTVDeclarationNames (Collection <File > classfiles ) {
78- var results = new HashSet <String >();
79- for (File javaFile : classfiles ) {
80- try (InputStream inputStream = new FileInputStream (javaFile )) {
81- ClassVisitor classVisitor = new ClassVisitor (Opcodes .ASM9 ) {
82- @ Override
83- public MethodVisitor visitMethod (int access , String name , String descriptor , String signature , String [] exceptions ) {
84- return new MethodNode (Opcodes .ASM9 , access , name , descriptor , signature , exceptions ) {
85- @ Override
86- public void visitMethodInsn (int opcode , String owner , String name , String descriptor , boolean isInterface ) {
87- if (owner .equals (TRANSPORT_VERSION_SET_CLASS ) && name .equals (TRANSPORT_VERSION_SET_METHOD_NAME )) {
88- var abstractInstruction = this .instructions .getLast ();
89- if (abstractInstruction instanceof LdcInsnNode ldcInsnNode
90- && ldcInsnNode .cst instanceof String tvName
91- && tvName .isEmpty () == false ) {
92- results .add (tvName );
93- } else {
94- // The instruction is not a LDC with a String constant (or an empty String),
95- // which is not allowed.
96- throw new RuntimeException (
97- "Transport Versions must be declared with a constant and non-empty String. "
98- + "file: "
99- + javaFile .getPath ()
100- );
101- }
102- }
103- super .visitMethodInsn (opcode , owner , name , descriptor , isInterface );
104- }
105- };
82+ private void addNamesFromClassesDirectory (Set <TransportVersionUtils .TransportVersionReference > results , Path file ) throws IOException {
83+ Files .walkFileTree (file , new SimpleFileVisitor <>() {
84+ @ Override
85+ public FileVisitResult visitFile (Path file , BasicFileAttributes attrs ) throws IOException {
86+ String filename = file .getFileName ().toString ();
87+ if (filename .endsWith (CLASS_EXTENSION ) && filename .endsWith (MODULE_INFO ) == false ) {
88+ try (var inputStream = Files .newInputStream (file )) {
89+ addNamesFromClass (results , inputStream , classname (file .toString ()));
10690 }
107- };
108- ClassReader classReader = new ClassReader (inputStream );
109- classReader .accept (classVisitor , 0 );
110- } catch (IOException e ) {
111- throw new RuntimeException (e );
91+ }
92+ return FileVisitResult .CONTINUE ;
11293 }
113- }
114- return results ;
94+ });
11595 }
11696
117- private static List < File > findJavaClassFiles ( Collection < File > files ) {
118- List < File > classFiles = new ArrayList <>();
119- for ( File file : files ) {
120- if ( file . isDirectory () ) {
121- File [] subFiles = file . listFiles ();
122- if (subFiles != null ) {
123- classFiles . addAll ( findJavaClassFiles ( Arrays . asList ( subFiles )));
97+ private void addNamesFromJar ( Set < TransportVersionUtils . TransportVersionReference > results , Path file ) throws IOException {
98+ try ( var jar = new JarInputStream ( Files . newInputStream ( file ))) {
99+ ZipEntry entry ;
100+ while (( entry = jar . getNextEntry ()) != null ) {
101+ String filename = entry . getName ();
102+ if (filename . endsWith ( CLASS_EXTENSION ) && filename . endsWith ( MODULE_INFO ) == false ) {
103+ addNamesFromClass ( results , jar , classname ( entry . toString ( )));
124104 }
125- } else if (file .getName ().endsWith (CLASS_EXTENSION ) && file .getName ().endsWith (MODULE_INFO ) == false ) {
126- classFiles .add (file );
127105 }
128106 }
129- return classFiles ;
107+ }
108+
109+ private void addNamesFromClass (Set <TransportVersionUtils .TransportVersionReference > results , InputStream classBytes , String classname )
110+ throws IOException {
111+ ClassVisitor classVisitor = new ClassVisitor (Opcodes .ASM9 ) {
112+ @ Override
113+ public MethodVisitor visitMethod (int access , String name , String descriptor , String signature , String [] exceptions ) {
114+ return new MethodNode (Opcodes .ASM9 , access , name , descriptor , signature , exceptions ) {
115+ int lineNumber = -1 ;
116+
117+ @ Override
118+ public void visitLineNumber (int line , Label start ) {
119+ lineNumber = line ;
120+ }
121+
122+ @ Override
123+ public void visitMethodInsn (int opcode , String owner , String name , String descriptor , boolean isInterface ) {
124+ if (owner .equals (TRANSPORT_VERSION_SET_CLASS ) && name .equals (TRANSPORT_VERSION_SET_METHOD_NAME )) {
125+ var abstractInstruction = this .instructions .getLast ();
126+ String location = classname + " line " + lineNumber ;
127+ if (abstractInstruction instanceof LdcInsnNode ldcInsnNode
128+ && ldcInsnNode .cst instanceof String tvName
129+ && tvName .isEmpty () == false ) {
130+ results .add (new TransportVersionUtils .TransportVersionReference (tvName , location ));
131+ } else {
132+ // The instruction is not a LDC with a String constant (or an empty String), which is not allowed.
133+ throw new RuntimeException (
134+ "TransportVersion.fromName must be called with a non-empty String literal. " + "See " + location + "."
135+ );
136+ }
137+ }
138+ super .visitMethodInsn (opcode , owner , name , descriptor , isInterface );
139+ }
140+ };
141+ }
142+ };
143+ ClassReader classReader = new ClassReader (classBytes );
144+ classReader .accept (classVisitor , 0 );
145+ }
146+
147+ private static String classname (String filename ) {
148+ return filename .substring (0 , filename .length () - CLASS_EXTENSION .length ()).replace ('/' , '.' );
130149 }
131150}
0 commit comments