@@ -16,6 +16,17 @@ public AnnotationConstantPool(ClassportInfo annotationInfo) {
1616 }
1717
1818 public record ConstantPoolData (int entryCount , byte [] data ) {}
19+
20+ private record ClassportInfoIndices (
21+ int classportInfoIndex ,
22+ int groupIndex ,
23+ int versionIndex ,
24+ int idIndex ,
25+ int sourceProjectIdIndex ,
26+ int childIdsIndex ,
27+ int artefactIndex ,
28+ int isDirectDependencyIndex
29+ ) {}
1930
2031 public ConstantPoolData getNewEntries () {
2132 ByteArrayOutputStream baos = new ByteArrayOutputStream ();
@@ -110,6 +121,19 @@ public byte[] injectAnnotation(byte[] originalBytes, ConstantPoolData cpd) {
110121 int rvaUtf8Index = findUtf8InConstantPool (existingCpData , originalConstantPoolCount - 1 , "RuntimeVisibleAnnotations" );
111122 boolean rvaExists = rvaUtf8Index > 0 ;
112123
124+ // check if ClassportInfo is already in the constant pool (all in one iteration)
125+ // This is a hack because there can be a case where all these constants are present in the constant pool
126+ // but the ClassportInfo is not present
127+ ClassportInfoIndices existingClassportIndices = findClassportInfoIndices (existingCpData , originalConstantPoolCount - 1 );
128+
129+ if (existingClassportIndices .classportInfoIndex > 0 && existingClassportIndices .groupIndex > 0 && existingClassportIndices .versionIndex > 0 &&
130+ existingClassportIndices .idIndex > 0 && existingClassportIndices .sourceProjectIdIndex > 0 && existingClassportIndices .childIdsIndex > 0 &&
131+ existingClassportIndices .artefactIndex > 0 && existingClassportIndices .isDirectDependencyIndex > 0 ) {
132+ // we don't want duplicate classport info entries
133+ // so we return the original bytes
134+ return originalBytes ;
135+ }
136+
113137 int newConstantPoolCount = originalConstantPoolCount + cpd .entryCount ();
114138 if (!rvaExists ) {
115139 newConstantPoolCount ++; // add one for "RuntimeVisibleAnnotations"
@@ -153,7 +177,7 @@ public byte[] injectAnnotation(byte[] originalBytes, ConstantPoolData cpd) {
153177 byte [] attrData = new byte [attrLen ];
154178 originalBuffer .get (attrData );
155179
156- if (i == rvaIndex && ! hasClassportAnnotation ( originalBytes , attrData ) ) {
180+ if (i == rvaIndex ) {
157181 // Merge annotation
158182 dos .writeShort (nameIndex );
159183 byte [] merged = mergeAnnotation (attrData , indices );
@@ -275,61 +299,6 @@ private void writeStringElementValue(DataOutputStream out, int nameIndex, int va
275299 out .writeShort (valueIndex );
276300 }
277301
278- private boolean hasClassportAnnotation (byte [] classBytes , byte [] rvaAttrData ) {
279- ByteBuffer buffer = ByteBuffer .wrap (rvaAttrData );
280- int annotationCount = buffer .getShort () & 0xFFFF ;
281-
282- for (int i = 0 ; i < annotationCount ; i ++) {
283- int typeIndex = buffer .getShort () & 0xFFFF ;
284- String descriptor = getCPString (classBytes , typeIndex );
285- if (CLASSPORT_DESCRIPTOR .equals (descriptor )) {
286- return true ;
287- }
288- skipAnnotation (buffer );
289- }
290- return false ;
291- }
292-
293- private void skipAnnotation (ByteBuffer buffer ) {
294- if (buffer .remaining () < 2 ) return ;
295- int numPairs = buffer .getShort () & 0xFFFF ;
296- for (int j = 0 ; j < numPairs ; j ++) {
297- if (buffer .remaining () < 2 ) return ;
298- buffer .getShort (); // skip element_name_index
299- skipElementValue (buffer ); // skip element_value
300- }
301- }
302-
303- private void skipElementValue (ByteBuffer buffer ) {
304- if (buffer .remaining () < 1 ) return ;
305- byte tag = buffer .get ();
306- switch (tag ) {
307- case 'B' : case 'C' : case 'D' : case 'F' : case 'I' : case 'J' : case 'S' : case 'Z' : case 's' : // primitive or string
308- if (buffer .remaining () < 2 ) return ;
309- buffer .getShort (); // const_value_index
310- break ;
311- case 'e' : // enum
312- if (buffer .remaining () < 4 ) return ;
313- buffer .getShort (); // type_name_index
314- buffer .getShort (); // const_name_index
315- break ;
316- case 'c' : // class
317- if (buffer .remaining () < 2 ) return ;
318- buffer .getShort (); // class_info_index
319- break ;
320- case '@' : // annotation (recursive)
321- skipAnnotation (buffer );
322- break ;
323- case '[' : // array
324- if (buffer .remaining () < 2 ) return ;
325- int numValues = buffer .getShort () & 0xFFFF ;
326- for (int k = 0 ; k < numValues ; k ++) {
327- skipElementValue (buffer );
328- }
329- break ;
330- }
331- }
332-
333302 private int findRuntimeVisibleAnnotationsAttribute (byte [] classBytes , int attrStartPos , int attrCount ) {
334303 ByteBuffer buffer = ByteBuffer .wrap (classBytes );
335304 buffer .position (attrStartPos );
@@ -418,6 +387,92 @@ private void copyMembers(ByteBuffer buffer, DataOutputStream out) throws IOExcep
418387 }
419388 }
420389
390+ private ClassportInfoIndices findClassportInfoIndices (byte [] cpData , int cpCount ) {
391+ ByteBuffer buffer = ByteBuffer .wrap (cpData );
392+ int currentIndex = 1 ; // Constant pool indices start at 1
393+
394+ int classportInfoIndex = -1 ;
395+ int groupIndex = -1 ;
396+ int versionIndex = -1 ;
397+ int idIndex = -1 ;
398+ int sourceProjectIdIndex = -1 ;
399+ int childIdsIndex = -1 ;
400+ int artefactIndex = -1 ;
401+ int isDirectDependencyIndex = -1 ;
402+
403+ // Check if we've found all strings to potentially exit early
404+ boolean allFound = false ;
405+
406+ for (int i = 0 ; i < cpCount && !allFound ; i ++) {
407+ int tag = buffer .get () & 0xFF ;
408+
409+ switch (tag ) {
410+ case 1 : // UTF8
411+ int len = buffer .getShort () & 0xFFFF ;
412+ byte [] utf8Bytes = new byte [len ];
413+ buffer .get (utf8Bytes );
414+ String utf8String = new String (utf8Bytes , java .nio .charset .StandardCharsets .UTF_8 );
415+
416+ // Check against all target strings
417+ if (utf8String .equals (CLASSPORT_DESCRIPTOR )) {
418+ classportInfoIndex = currentIndex ;
419+ } else if (utf8String .equals ("group" )) {
420+ groupIndex = currentIndex ;
421+ } else if (utf8String .equals ("version" )) {
422+ versionIndex = currentIndex ;
423+ } else if (utf8String .equals ("id" )) {
424+ idIndex = currentIndex ;
425+ } else if (utf8String .equals ("sourceProjectId" )) {
426+ sourceProjectIdIndex = currentIndex ;
427+ } else if (utf8String .equals ("childIds" )) {
428+ childIdsIndex = currentIndex ;
429+ } else if (utf8String .equals ("artefact" )) {
430+ artefactIndex = currentIndex ;
431+ } else if (utf8String .equals ("isDirectDependency" )) {
432+ isDirectDependencyIndex = currentIndex ;
433+ }
434+
435+ // Early exit if all found
436+ if (classportInfoIndex > 0 && groupIndex > 0 && versionIndex > 0 &&
437+ idIndex > 0 && sourceProjectIdIndex > 0 && childIdsIndex > 0 &&
438+ artefactIndex > 0 && isDirectDependencyIndex > 0 ) {
439+ allFound = true ;
440+ }
441+ break ;
442+ case 3 : case 4 : // Integer, Float
443+ buffer .getInt ();
444+ break ;
445+ case 5 : case 6 : // Long, Double
446+ buffer .getLong ();
447+ i ++; // takes 2 slots
448+ currentIndex ++; // Long and Double take 2 constant pool slots
449+ break ;
450+ case 7 : case 8 : case 16 : case 19 : case 20 : // Class, String, etc.
451+ buffer .getShort ();
452+ break ;
453+ case 9 : case 10 : case 11 : case 12 : case 17 : case 18 : // Fieldref, etc.
454+ buffer .getInt ();
455+ break ;
456+ case 15 : // MethodHandle
457+ buffer .get ();
458+ buffer .getShort ();
459+ break ;
460+ }
461+ currentIndex ++;
462+ }
463+
464+ return new ClassportInfoIndices (
465+ classportInfoIndex ,
466+ groupIndex ,
467+ versionIndex ,
468+ idIndex ,
469+ sourceProjectIdIndex ,
470+ childIdsIndex ,
471+ artefactIndex ,
472+ isDirectDependencyIndex
473+ );
474+ }
475+
421476 private int findUtf8InConstantPool (byte [] cpData , int cpCount , String target ) {
422477 ByteBuffer buffer = ByteBuffer .wrap (cpData );
423478 int currentIndex = 1 ; // Constant pool indices start at 1
0 commit comments