1+ package org .eclipse .pde .core .tests .internal .classpath ;
2+
3+ import static org .junit .jupiter .api .Assertions .assertTrue ;
4+
5+ import java .io .File ;
6+ import java .io .FileOutputStream ;
7+ import java .io .IOException ;
8+ import java .nio .file .Files ;
9+ import java .util .List ;
10+ import java .util .jar .Attributes ;
11+ import java .util .jar .JarOutputStream ;
12+ import java .util .jar .Manifest ;
13+ import java .util .zip .ZipEntry ;
14+
15+ import org .eclipse .core .resources .IProject ;
16+ import org .eclipse .core .resources .IWorkspaceDescription ;
17+ import org .eclipse .core .resources .IncrementalProjectBuilder ;
18+ import org .eclipse .core .resources .ResourcesPlugin ;
19+ import org .eclipse .core .runtime .NullProgressMonitor ;
20+ import org .eclipse .core .runtime .jobs .Job ;
21+ import org .eclipse .jdt .core .IClasspathEntry ;
22+ import org .eclipse .jdt .core .JavaCore ;
23+ import org .eclipse .pde .core .plugin .IPluginModelBase ;
24+ import org .eclipse .pde .core .plugin .PluginRegistry ;
25+ import org .eclipse .pde .core .project .IBundleProjectDescription ;
26+ import org .eclipse .pde .core .project .IBundleProjectService ;
27+ import org .eclipse .pde .core .project .IRequiredBundleDescription ;
28+ import org .eclipse .pde .core .target .ITargetDefinition ;
29+ import org .eclipse .pde .core .target .ITargetLocation ;
30+ import org .eclipse .pde .core .target .ITargetPlatformService ;
31+ import org .eclipse .pde .internal .core .PDECore ;
32+ import org .eclipse .pde .internal .ui .wizards .tools .UpdateClasspathJob ;
33+ import org .eclipse .pde .ui .tests .runtime .TestUtils ;
34+ import org .eclipse .pde .ui .tests .util .ProjectUtils ;
35+ import org .eclipse .pde .ui .tests .util .TargetPlatformUtil ;
36+ import org .eclipse .swt .widgets .Display ;
37+ import org .junit .jupiter .api .AfterAll ;
38+ import org .junit .jupiter .api .AfterEach ;
39+ import org .junit .jupiter .api .BeforeAll ;
40+ import org .junit .jupiter .api .BeforeEach ;
41+ import org .junit .jupiter .api .Test ;
42+ import org .osgi .framework .Version ;
43+
44+ public class ChainedReexportPerformanceTest {
45+
46+ @ BeforeAll
47+ public static void beforeAll () throws Exception {
48+ ProjectUtils .deleteAllWorkspaceProjects ();
49+ }
50+
51+ @ AfterAll
52+ public static void afterAll () throws Exception {
53+ // ProjectUtils.deleteAllWorkspaceProjects();
54+ }
55+
56+ private static final String CHAIN_PREFIX = "Chain_" ;
57+ private static final int PACKAGE_COUNT = 1000 ;
58+ private static final int BUNDLE_CHAIN_DEPTH = 5 ;
59+ private static final boolean DEBUG = false ;
60+ private File targetDir ;
61+
62+ @ BeforeEach
63+ public void setUp () throws Exception {
64+ // Disable auto-building
65+ IWorkspaceDescription desc = ResourcesPlugin .getWorkspace ().getDescription ();
66+ desc .setAutoBuilding (false );
67+ ResourcesPlugin .getWorkspace ().setDescription (desc );
68+
69+ targetDir = Files .createTempDirectory ("pde_chain_perf_target" ).toFile ();
70+ System .out .println ("Target Platform Location: " + targetDir .getAbsolutePath ());
71+ createChainedTargetPlatform ();
72+ }
73+
74+ @ AfterEach
75+ public void tearDown () throws Exception {
76+ // Restore auto-building
77+ IWorkspaceDescription desc = ResourcesPlugin .getWorkspace ().getDescription ();
78+ desc .setAutoBuilding (true );
79+ ResourcesPlugin .getWorkspace ().setDescription (desc );
80+ }
81+
82+ private void createChainedTargetPlatform () throws Exception {
83+ // Create a chain of bundles: B_0 -> B_1 -> ... -> B_N (all re-exporting)
84+ for (int i = 0 ; i < BUNDLE_CHAIN_DEPTH ; i ++) {
85+ String name = CHAIN_PREFIX + i ;
86+ String exports = createPackageExports (name );
87+ String requires = (i > 0 ) ? (CHAIN_PREFIX + (i - 1 ) + ";visibility:=reexport" ) : null ;
88+ createBundle (targetDir , name , exports , requires );
89+ }
90+
91+ // Set Target Platform
92+ ITargetPlatformService tps = PDECore .getDefault ().acquireService (ITargetPlatformService .class );
93+ ITargetDefinition target = tps .newTarget ();
94+ target .setTargetLocations (new ITargetLocation [] { tps .newDirectoryLocation (targetDir .getAbsolutePath ()) });
95+ TargetPlatformUtil .loadAndSetTarget (target );
96+ }
97+
98+ private String createPackageExports (String bundleName ) {
99+ StringBuilder sb = new StringBuilder ();
100+ for (int i = 0 ; i < PACKAGE_COUNT ; i ++) {
101+ if (sb .length () > 0 ) {
102+ sb .append ("," );
103+ }
104+ sb .append (bundleName ).append (".pkg." ).append (i );
105+ }
106+ return sb .toString ();
107+ }
108+
109+ private void createBundle (File dir , String name , String exports , String requires ) throws IOException {
110+ File jarFile = new File (dir , name + ".jar" );
111+ try (JarOutputStream jos = new JarOutputStream (new FileOutputStream (jarFile ))) {
112+ Manifest manifest = new Manifest ();
113+ Attributes main = manifest .getMainAttributes ();
114+ main .put (Attributes .Name .MANIFEST_VERSION , "1.0" );
115+ main .put (new Attributes .Name ("Bundle-ManifestVersion" ), "2" );
116+ main .put (new Attributes .Name ("Bundle-SymbolicName" ), name );
117+ main .put (new Attributes .Name ("Bundle-Version" ), "1.0.0" );
118+ if (exports != null ) {
119+ main .put (new Attributes .Name ("Export-Package" ), exports );
120+ }
121+ if (requires != null ) {
122+ main .put (new Attributes .Name ("Require-Bundle" ), requires );
123+ }
124+
125+ ZipEntry entry = new ZipEntry ("META-INF/MANIFEST.MF" );
126+ jos .putNextEntry (entry );
127+ manifest .write (jos );
128+ jos .closeEntry ();
129+ }
130+ }
131+
132+ @ Test
133+ public void testChainedReexportPerformance () throws Exception {
134+ IBundleProjectService service = PDECore .getDefault ().acquireService (IBundleProjectService .class );
135+
136+ String consumerName = "ConsumerBundle" ;
137+ IProject consumerProj = ResourcesPlugin .getWorkspace ().getRoot ().getProject (consumerName );
138+ consumerProj .create (null );
139+ consumerProj .open (null );
140+
141+ IBundleProjectDescription consumerDesc = service .getDescription (consumerProj );
142+ consumerDesc .setSymbolicName (consumerName );
143+ consumerDesc .setBundleVersion (new Version ("1.0.0" ));
144+
145+ // Require the last bundle in the chain with re-export
146+ IRequiredBundleDescription mainReq = service .newRequiredBundle (CHAIN_PREFIX + (BUNDLE_CHAIN_DEPTH - 1 ), null , false , true );
147+ consumerDesc .setRequiredBundles (new IRequiredBundleDescription [] { mainReq });
148+
149+ consumerDesc .setNatureIds (new String [] { JavaCore .NATURE_ID , IBundleProjectDescription .PLUGIN_NATURE });
150+ consumerDesc .apply (null );
151+
152+ // Build to ensure models are ready
153+ ResourcesPlugin .getWorkspace ().build (IncrementalProjectBuilder .FULL_BUILD , new NullProgressMonitor ());
154+ TestUtils .waitForJobs ("Init" , 500 , 5000 );
155+
156+ IPluginModelBase consumerModel = PluginRegistry .findModel (consumerProj );
157+ if (consumerModel == null ) {
158+ throw new IllegalStateException ("Consumer model not found" );
159+ }
160+
161+ long start = System .currentTimeMillis ();
162+
163+ // This triggers the computation
164+ UpdateClasspathJob .scheduleFor (List .of (consumerModel ), false );
165+ waitForJobsAndUI (60000 );
166+
167+ long elapsed = System .currentTimeMillis () - start ;
168+ System .out .println ("Classpath computation took: " + elapsed + "ms for chained re-exports." );
169+
170+ if (elapsed > 10000 ) {
171+ throw new AssertionError ("Performance regression: Classpath computation took too long (" + elapsed + "ms)" );
172+ }
173+
174+ IClasspathEntry [] resolvedClasspath = JavaCore .create (consumerProj ).getRawClasspath ();
175+ assertTrue (resolvedClasspath .length > 0 , "Classpath should not be empty" );
176+
177+ if (DEBUG ) {
178+ long endTime = System .currentTimeMillis () + 6000000 ;
179+ while (System .currentTimeMillis () < endTime ) {
180+ if (!Display .getDefault ().readAndDispatch ()) {
181+ Display .getDefault ().sleep ();
182+ }
183+ }
184+ }
185+ }
186+
187+ private void waitForJobsAndUI (long timeoutMillis ) {
188+ long start = System .currentTimeMillis ();
189+
190+ while (System .currentTimeMillis () - start < timeoutMillis ) {
191+ // Process UI events
192+ while (Display .getDefault ().readAndDispatch ()) {
193+ // Keep processing
194+ }
195+
196+ // Check if all jobs are done
197+ if (Job .getJobManager ().isIdle ()) {
198+ // Process any final UI events
199+ while (Display .getDefault ().readAndDispatch ()) {
200+ // Keep processing
201+ }
202+ return ;
203+ }
204+
205+ try {
206+ Thread .sleep (50 );
207+ } catch (InterruptedException e ) {
208+ Thread .currentThread ().interrupt ();
209+ return ;
210+ }
211+ }
212+
213+ throw new AssertionError ("Timeout waiting for jobs to complete" );
214+ }
215+ }
0 commit comments