11package com .codename1 .tools .translator ;
2+ import com .codename1 .tools .translator .bytecodes .ArrayLengthExpression ;
3+ import com .codename1 .tools .translator .bytecodes .ArrayLoadExpression ;
4+ import com .codename1 .tools .translator .bytecodes .AssignableExpression ;
5+ import com .codename1 .tools .translator .bytecodes .Instruction ;
6+ import com .codename1 .tools .translator .bytecodes .MultiArray ;
27import org .junit .jupiter .api .Test ;
8+ import org .objectweb .asm .AnnotationVisitor ;
9+ import org .objectweb .asm .Opcodes ;
310
411import javax .tools .JavaCompiler ;
512import javax .tools .ToolProvider ;
13+ import java .io .ByteArrayOutputStream ;
14+ import java .io .File ;
15+ import java .io .FilenameFilter ;
16+ import java .lang .reflect .Constructor ;
17+ import java .lang .reflect .Field ;
618import java .nio .charset .StandardCharsets ;
719import java .nio .file .Files ;
820import java .nio .file .Path ;
21+ import java .util .ArrayList ;
922import java .util .Arrays ;
23+ import java .util .HashSet ;
24+ import java .util .List ;
25+ import java .util .Set ;
26+ import java .util .TreeSet ;
27+ import java .util .concurrent .atomic .AtomicBoolean ;
1028import java .util .stream .Stream ;
1129
1230import static org .junit .jupiter .api .Assertions .*;
@@ -90,7 +108,197 @@ void translatesOptimizedBytecodeToLLVMExecutable() throws Exception {
90108
91109 Path executable = buildDir .resolve ("CustomBytecodeApp" );
92110 String output = CleanTargetIntegrationTest .runCommand (Arrays .asList (executable .toString ()), buildDir );
93- assertTrue (output .contains ("RESULT=54" ), "Compiled program should print the expected arithmetic result" );
111+ assertTrue (output .contains ("RESULT=293" ), "Compiled program should print the expected arithmetic result" );
112+ }
113+
114+ private Set <String > snapshotArrayTypes () throws Exception {
115+ Field arrayTypesField = ByteCodeClass .class .getDeclaredField ("arrayTypes" );
116+ arrayTypesField .setAccessible (true );
117+ return new TreeSet <>((Set <String >) arrayTypesField .get (null ));
118+ }
119+
120+ private void restoreArrayTypes (Set <String > snapshot ) throws Exception {
121+ Field arrayTypesField = ByteCodeClass .class .getDeclaredField ("arrayTypes" );
122+ arrayTypesField .setAccessible (true );
123+ arrayTypesField .set (null , new TreeSet <>(snapshot ));
124+ }
125+
126+ private static class StubAssignableExpression extends Instruction implements AssignableExpression {
127+ private final String expression ;
128+ int dependencyCalls ;
129+
130+ private StubAssignableExpression (int opcode , String expression ) {
131+ super (opcode );
132+ this .expression = expression ;
133+ }
134+
135+ @ Override
136+ public void addDependencies (List <String > dependencyList ) {
137+ dependencyCalls ++;
138+ dependencyList .add (expression );
139+ }
140+
141+ @ Override
142+ public void appendInstruction (StringBuilder b ) {
143+ b .append (expression );
144+ }
145+
146+ @ Override
147+ public boolean assignTo (String varName , StringBuilder sb ) {
148+ if (varName != null ) {
149+ sb .append (varName ).append (" = " ).append (expression ).append (";\n " );
150+ } else {
151+ sb .append (expression );
152+ }
153+ return true ;
154+ }
155+ }
156+
157+ @ Test
158+ void annotationVisitorWrapperDelegatesAndHandlesNullVisitor () {
159+ Parser parser = new Parser ();
160+
161+ Parser .AnnotationVisitorWrapper wrapperWithNull = parser .new AnnotationVisitorWrapper (null );
162+ assertNull (wrapperWithNull .visitArray ("values" ));
163+ assertNull (wrapperWithNull .visitAnnotation ("name" , "LExample;" ));
164+ assertDoesNotThrow (() -> wrapperWithNull .visit ("flag" , true ));
165+ assertDoesNotThrow (() -> wrapperWithNull .visitEnum ("choice" , "LExample;" , "VALUE" ));
166+
167+ AtomicBoolean delegated = new AtomicBoolean (false );
168+ AnnotationVisitor delegate = new AnnotationVisitor (Opcodes .ASM5 ) {
169+ @ Override
170+ public AnnotationVisitor visitArray (String name ) {
171+ delegated .set (true );
172+ return this ;
173+ }
174+ };
175+
176+ Parser .AnnotationVisitorWrapper wrapperWithDelegate = parser .new AnnotationVisitorWrapper (delegate );
177+ AnnotationVisitor result = wrapperWithDelegate .visitArray ("values" );
178+ assertSame (delegate , result );
179+ assertTrue (delegated .get (), "AnnotationVisitorWrapper should forward to the underlying visitor" );
180+ }
181+
182+ @ Test
183+ void byteCodeTranslatorFilenameFilterMatchesExpectedFiles () throws Exception {
184+ Class <?> filterClass = Class .forName ("com.codename1.tools.translator.ByteCodeTranslator$3" );
185+ Constructor <?> ctor = filterClass .getDeclaredConstructor ();
186+ ctor .setAccessible (true );
187+
188+ FilenameFilter filter = (FilenameFilter ) ctor .newInstance ();
189+ File directory = Files .createTempDirectory ("bytecode-filter" ).toFile ();
190+
191+ assertTrue (filter .accept (directory , "assets.bundle" ));
192+ assertTrue (filter .accept (directory , "model.xcdatamodeld" ));
193+ assertTrue (filter .accept (directory , "VisibleSource.m" ));
194+ assertFalse (filter .accept (directory , ".hidden" ));
195+ assertFalse (filter .accept (directory , "Images.xcassets" ));
196+ }
197+
198+ @ Test
199+ void concatenatingFileOutputStreamWritesShardedOutputs () throws Exception {
200+ Path outputDir = Files .createTempDirectory ("concatenating-output" );
201+ ByteCodeTranslator .OutputType original = ByteCodeTranslator .output ;
202+ ByteCodeTranslator .output = ByteCodeTranslator .OutputType .OUTPUT_TYPE_CLEAN ;
203+ try {
204+ ConcatenatingFileOutputStream stream = new ConcatenatingFileOutputStream (outputDir .toFile ());
205+ stream .beginNextFile ("first" );
206+ stream .write ("abc" .getBytes (StandardCharsets .UTF_8 ));
207+ stream .close ();
208+
209+ stream .beginNextFile ("second" );
210+ stream .write ("123" .getBytes (StandardCharsets .UTF_8 ));
211+ stream .close ();
212+
213+ Field destField = ConcatenatingFileOutputStream .class .getDeclaredField ("dest" );
214+ destField .setAccessible (true );
215+ ByteArrayOutputStream [] buffers = (ByteArrayOutputStream []) destField .get (stream );
216+ for (int i = 0 ; i < buffers .length ; i ++) {
217+ if (buffers [i ] == null ) {
218+ buffers [i ] = new ByteArrayOutputStream ();
219+ }
220+ }
221+ destField .set (stream , buffers );
222+
223+ stream .realClose ();
224+
225+ Path first = outputDir .resolve ("concatenated_" + Math .abs ("first" .hashCode () % ConcatenatingFileOutputStream .MODULO ) + ".c" );
226+ Path second = outputDir .resolve ("concatenated_" + Math .abs ("second" .hashCode () % ConcatenatingFileOutputStream .MODULO ) + ".c" );
227+
228+ assertTrue (Files .exists (first ));
229+ assertEquals ("abc\n " , new String (Files .readAllBytes (first ), StandardCharsets .UTF_8 ));
230+
231+ assertTrue (Files .exists (second ));
232+ assertEquals ("123\n " , new String (Files .readAllBytes (second ), StandardCharsets .UTF_8 ));
233+ } finally {
234+ ByteCodeTranslator .output = original ;
235+ }
236+ }
237+
238+ @ Test
239+ void multiArrayAddsDependenciesAndRegistersArrayTypes () throws Exception {
240+ List <String > dependencies = new ArrayList <>();
241+ MultiArray multiArray = new MultiArray ("[[Ljava/lang/String;" , 2 );
242+
243+ Set <String > snapshot = snapshotArrayTypes ();
244+ try {
245+ multiArray .addDependencies (dependencies );
246+
247+ assertTrue (dependencies .contains ("java_lang_String" ));
248+ assertTrue (snapshotArrayTypes ().contains ("2_java_lang_String" ));
249+ } finally {
250+ restoreArrayTypes (snapshot );
251+ }
252+ }
253+
254+ @ Test
255+ void arrayLengthExpressionReducesAndAssigns () throws Exception {
256+ List <Instruction > instructions = new ArrayList <>();
257+ StubAssignableExpression arrayRef = new StubAssignableExpression (Opcodes .ALOAD , "myArray" );
258+ Instruction arrayLength = new Instruction (Opcodes .ARRAYLENGTH ) { };
259+ instructions .add (arrayRef );
260+ instructions .add (arrayLength );
261+
262+ int reducedIndex = ArrayLengthExpression .tryReduce (instructions , 1 );
263+ assertEquals (0 , reducedIndex );
264+ assertEquals (1 , instructions .size ());
265+ ArrayLengthExpression reduced = (ArrayLengthExpression ) instructions .get (0 );
266+
267+ StringBuilder assignment = new StringBuilder ();
268+ assertTrue (reduced .assignTo ("len" , assignment ));
269+ assertEquals ("len = CN1_ARRAY_LENGTH(myArray);\n " , assignment .toString ());
270+
271+ List <String > deps = new ArrayList <>();
272+ reduced .addDependencies (deps );
273+ assertEquals (1 , arrayRef .dependencyCalls );
274+ assertEquals ("myArray" , deps .get (0 ));
275+ }
276+
277+ @ Test
278+ void arrayLoadExpressionReducesAndAssigns () {
279+ List <Instruction > instructions = new ArrayList <>();
280+ StubAssignableExpression arrayRef = new StubAssignableExpression (Opcodes .ALOAD , "items" );
281+ StubAssignableExpression index = new StubAssignableExpression (Opcodes .ILOAD , "index" );
282+ Instruction loadInstr = new Instruction (Opcodes .IALOAD ) { };
283+ instructions .add (arrayRef );
284+ instructions .add (index );
285+ instructions .add (loadInstr );
286+
287+ int reducedIndex = ArrayLoadExpression .tryReduce (instructions , 2 );
288+ assertEquals (0 , reducedIndex );
289+ assertEquals (1 , instructions .size ());
290+ ArrayLoadExpression reduced = (ArrayLoadExpression ) instructions .get (0 );
291+
292+ StringBuilder assignment = new StringBuilder ();
293+ assertTrue (reduced .assignTo ("value" , assignment ));
294+ assertEquals ("value=CN1_ARRAY_ELEMENT_INT(items, index);\n " , assignment .toString ());
295+
296+ List <String > deps = new ArrayList <>();
297+ reduced .addDependencies (deps );
298+ assertEquals (1 , arrayRef .dependencyCalls );
299+ assertEquals (1 , index .dependencyCalls );
300+ assertTrue (deps .contains ("items" ));
301+ assertTrue (deps .contains ("index" ));
94302 }
95303
96304 private Path findGeneratedSource (Path srcRoot ) throws Exception {
@@ -104,7 +312,14 @@ private Path findGeneratedSource(Path srcRoot) throws Exception {
104312
105313 private String appSource () {
106314 return "public class BytecodeInstructionApp {\n " +
315+ " private static final int STATIC_INCREMENT = 3;\n " +
107316 " private static native void report(int value);\n " +
317+ " private int instanceCounter = 7;\n " +
318+ " private int baseField = 11;\n " +
319+ " public BytecodeInstructionApp(int seed) {\n " +
320+ " instanceCounter = seed;\n " +
321+ " baseField = seed + 6;\n " +
322+ " }\n " +
108323 " private static int optimizedComputation(int a, int b) {\n " +
109324 " int counter = a;\n " +
110325 " counter++;\n " +
@@ -136,12 +351,73 @@ private String appSource() {
136351 " }\n " +
137352 " return result;\n " +
138353 " }\n " +
354+ " private int loopArrays(int base) {\n " +
355+ " int[] values = { base, base + 1, base + 2, STATIC_INCREMENT };\n " +
356+ " int total = 0;\n " +
357+ " for (int i = 0; i < values.length; i++) {\n " +
358+ " total += values[i] * (i + 1);\n " +
359+ " }\n " +
360+ " return total + values.length;\n " +
361+ " }\n " +
362+ " private int multiArrayUsage(int factor) {\n " +
363+ " int[][] grid = new int[2][3];\n " +
364+ " int v = factor;\n " +
365+ " for (int i = 0; i < grid.length; i++) {\n " +
366+ " for (int j = 0; j < grid[i].length; j++) {\n " +
367+ " grid[i][j] = v++;\n " +
368+ " }\n " +
369+ " }\n " +
370+ " int total = 0;\n " +
371+ " for (int[] row : grid) {\n " +
372+ " for (int cell : row) {\n " +
373+ " total += cell;\n " +
374+ " }\n " +
375+ " total += row.length;\n " +
376+ " }\n " +
377+ " String[][] labels = new String[][] { { \" a\" , \" b\" }, { \" c\" , \" d\" } };\n " +
378+ " int labelLength = 0;\n " +
379+ " for (int i = 0; i < labels.length; i++) {\n " +
380+ " labelLength += labels[i].length;\n " +
381+ " }\n " +
382+ " return total + labelLength;\n " +
383+ " }\n " +
384+ " private int useFieldsAndMethods(int offset) {\n " +
385+ " instanceCounter += offset;\n " +
386+ " int[] mix = new int[] { offset, offset + baseField, instanceCounter };\n " +
387+ " int altSum = 0;\n " +
388+ " for (int i = 0; i < mix.length; i++) {\n " +
389+ " altSum += mix[i] + i;\n " +
390+ " }\n " +
391+ " int nestedLoop = loopArrays(offset);\n " +
392+ " int nestedMulti = multiArrayUsage(offset);\n " +
393+ " report(instanceCounter);\n " +
394+ " report(baseField);\n " +
395+ " report(altSum);\n " +
396+ " report(nestedLoop);\n " +
397+ " report(nestedMulti);\n " +
398+ " return nestedLoop + nestedMulti + instanceCounter + altSum + baseField;\n " +
399+ " }\n " +
139400 " public static void main(String[] args) {\n " +
401+ " BytecodeInstructionApp app = new BytecodeInstructionApp(4);\n " +
140402 " int first = optimizedComputation(1, 3);\n " +
141403 " int second = optimizedComputation(5, 2);\n " +
142404 " int switched = switchComputation(first) + switchComputation(second);\n " +
143405 " int synchronizedValue = synchronizedIncrement(second);\n " +
144- " report(first + second + switched + synchronizedValue);\n " +
406+ " int arrays = app.loopArrays(2);\n " +
407+ " int multi = app.multiArrayUsage(3);\n " +
408+ " int arraysFive = app.loopArrays(5);\n " +
409+ " int multiFive = app.multiArrayUsage(5);\n " +
410+ " int fieldCalls = app.useFieldsAndMethods(5);\n " +
411+ " report(first);\n " +
412+ " report(second);\n " +
413+ " report(switched);\n " +
414+ " report(synchronizedValue);\n " +
415+ " report(arrays);\n " +
416+ " report(multi);\n " +
417+ " report(arraysFive);\n " +
418+ " report(multiFive);\n " +
419+ " report(fieldCalls);\n " +
420+ " report(first + second + switched + synchronizedValue + arrays + multi + fieldCalls);\n " +
145421 " }\n " +
146422 "}\n " ;
147423 }
0 commit comments