2525import java .io .FileOutputStream ;
2626import java .io .IOException ;
2727import java .io .OutputStream ;
28+ import java .net .InetAddress ;
2829import java .net .MalformedURLException ;
2930import java .net .URL ;
31+ import java .nio .file .Files ;
32+ import java .nio .file .Path ;
3033import java .util .LinkedHashMap ;
3134import java .util .Map ;
3235import java .util .Properties ;
4952import org .eclipse .osgi .service .datalocation .Location ;
5053import org .eclipse .osgi .util .NLS ;
5154import org .eclipse .swt .SWT ;
55+ import org .eclipse .swt .layout .FillLayout ;
56+ import org .eclipse .swt .widgets .Composite ;
57+ import org .eclipse .swt .widgets .Control ;
5258import org .eclipse .swt .widgets .Display ;
59+ import org .eclipse .swt .widgets .Label ;
5360import org .eclipse .swt .widgets .Shell ;
5461import org .eclipse .ui .IWorkbench ;
5562import org .eclipse .ui .PlatformUI ;
@@ -79,6 +86,22 @@ public class IDEApplication implements IApplication, IExecutableExtension {
7986
8087 private static final String VERSION_FILENAME = "version.ini" ; //$NON-NLS-1$
8188
89+ private static final String LOCK_INFO_FILENAME = ".lock_info" ; //$NON-NLS-1$
90+
91+ private static final String DISPLAY_VAR = "DISPLAY" ; //$NON-NLS-1$
92+
93+ private static final String HOST_NAME_VAR = "HOSTNAME" ; //$NON-NLS-1$
94+
95+ private static final String PROCESS_ID = "process-id" ; //$NON-NLS-1$
96+
97+ private static final String DISPLAY = "display" ; //$NON-NLS-1$
98+
99+ private static final String HOST = "host" ; //$NON-NLS-1$
100+
101+ private static final String USER = "user" ; //$NON-NLS-1$
102+
103+ private static final String USER_NAME = "user.name" ; //$NON-NLS-1$
104+
82105 // Use the branding plug-in of the platform feature since this is most likely
83106 // to change on an update of the IDE.
84107 private static final String WORKSPACE_CHECK_REFERENCE_BUNDLE_NAME = "org.eclipse.platform" ; //$NON-NLS-1$
@@ -225,6 +248,7 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) {
225248 try {
226249 if (instanceLoc .lock ()) {
227250 writeWorkspaceVersion ();
251+ writeWsLockInfo (instanceLoc .getURL ());
228252 return null ;
229253 }
230254
@@ -237,10 +261,19 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) {
237261 if (isDevLaunchMode (applicationArguments )) {
238262 return EXIT_WORKSPACE_LOCKED ;
239263 }
264+
265+ String wsLockedError = NLS .bind (IDEWorkbenchMessages .IDEApplication_workspaceCannotLockMessage ,
266+ workspaceDirectory .getAbsolutePath ());
267+ // check if there is a lock info then append it to error message.
268+ String lockInfo = getWorkspaceLockInfo (instanceLoc .getURL ());
269+ if (lockInfo != null && !lockInfo .isBlank ()) {
270+ wsLockedError = wsLockedError + System .lineSeparator () + System .lineSeparator ()
271+ + NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_Message , lockInfo );
272+ }
240273 MessageDialog .openError (
241274 shell ,
242275 IDEWorkbenchMessages .IDEApplication_workspaceCannotLockTitle ,
243- NLS . bind ( IDEWorkbenchMessages . IDEApplication_workspaceCannotLockMessage , workspaceDirectory . getAbsolutePath ()) );
276+ wsLockedError );
244277 } else {
245278 MessageDialog .openError (
246279 shell ,
@@ -313,6 +346,7 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) {
313346 if (instanceLoc .set (workspaceUrl , true )) {
314347 launchData .writePersistedData ();
315348 writeWorkspaceVersion ();
349+ writeWsLockInfo (instanceLoc .getURL ());
316350 return null ;
317351 }
318352 } catch (IllegalStateException e ) {
@@ -332,17 +366,187 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) {
332366
333367 // by this point it has been determined that the workspace is
334368 // already in use -- force the user to choose again
369+
370+ String lockInfo = getWorkspaceLockInfo (workspaceUrl );
371+
335372 MessageDialog dialog = new MessageDialog (null , IDEWorkbenchMessages .IDEApplication_workspaceInUseTitle ,
336373 null , NLS .bind (IDEWorkbenchMessages .IDEApplication_workspaceInUseMessage , workspaceUrl .getFile ()),
337374 MessageDialog .ERROR , 1 , IDEWorkbenchMessages .IDEApplication_workspaceInUse_Retry ,
338- IDEWorkbenchMessages .IDEApplication_workspaceInUse_Choose );
375+ IDEWorkbenchMessages .IDEApplication_workspaceInUse_Choose ) {
376+ @ Override
377+ protected Control createCustomArea (Composite parent ) {
378+ if (lockInfo == null || lockInfo .isBlank ()) {
379+ return null ;
380+ }
381+
382+ Composite container = new Composite (parent , SWT .NONE );
383+ container .setLayout (new FillLayout ());
384+
385+ Label multiLineText = new Label (container , SWT .NONE );
386+ multiLineText .setText (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_Message , lockInfo ));
387+
388+ return container ;
389+ }
390+ };
339391 // the return value influences the next loop's iteration
340392 returnValue = dialog .open ();
341393 // Remember the locked workspace as recent workspace
342394 launchData .writePersistedData ();
343395 }
344396 }
345397
398+ /**
399+ * Read workspace lock file and parse all the properties present. Based on the
400+ * eclipse version and operating system some or all the properties may not
401+ * present. In such scenario it will return empty string.
402+ *
403+ * @return Previous lock owner details.
404+ */
405+ protected String getWorkspaceLockInfo (URL workspaceUrl ) {
406+ try {
407+ File lockFile = getLockInfoFile (workspaceUrl );
408+ if (!lockFile .exists ()) {
409+ return null ;
410+ }
411+
412+ StringBuilder sb = new StringBuilder ();
413+ Properties props = new Properties ();
414+ try (FileInputStream is = new FileInputStream (lockFile )) {
415+ props .load (is );
416+ String prop = props .getProperty (USER );
417+ if (prop != null ) {
418+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_User , prop ));
419+ }
420+ prop = props .getProperty (HOST );
421+ if (prop != null ) {
422+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_Host , prop ));
423+ }
424+ prop = props .getProperty (DISPLAY );
425+ if (prop != null ) {
426+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_Disp , prop ));
427+ }
428+ prop = props .getProperty (PROCESS_ID );
429+ if (prop != null ) {
430+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_P_Id , prop ));
431+ }
432+ return sb .toString ();
433+ }
434+ } catch (Exception e ) {
435+ IDEWorkbenchPlugin .log ("Could not read lock info file: " , e ); //$NON-NLS-1$
436+
437+ }
438+ return null ;
439+ }
440+
441+ /**
442+ * Write lock owner details onto workspace lock file. Data includes user, host,
443+ * display and current java process id.
444+ *
445+ * @param instanceLoc
446+ */
447+ protected void writeWsLockInfo (URL workspaceUrl ) {
448+ Properties props = new Properties ();
449+
450+ String user = System .getProperty (USER_NAME );
451+ if (user != null ) {
452+ props .setProperty (USER , user );
453+ }
454+ String host = getHostName ();
455+ if (host != null ) {
456+ props .setProperty (HOST , host );
457+ }
458+ String display = getDisplay ();
459+ if (display != null ) {
460+ props .setProperty (DISPLAY , display );
461+ }
462+ String pid = getProcessId ();
463+ if (pid != null ) {
464+ props .setProperty (PROCESS_ID , pid );
465+ }
466+
467+ if (props .isEmpty ()) {
468+ return ;
469+ }
470+
471+ try (OutputStream output = new FileOutputStream (createLockInfoFile (workspaceUrl ))) {
472+ props .store (output , null );
473+ } catch (Exception e ) {
474+ IDEWorkbenchPlugin .log ("Could not write lock info file" , e ); //$NON-NLS-1$
475+ }
476+ }
477+
478+ private String getDisplay () {
479+ String displayEnv = null ;
480+ try {
481+ displayEnv = System .getenv (DISPLAY_VAR );
482+ } catch (Exception e ) {
483+ IDEWorkbenchPlugin .log ("Failed to read DISPLAY variable." , e ); //$NON-NLS-1$
484+ }
485+ return displayEnv ;
486+ }
487+
488+ private String getProcessId () {
489+ Long pid = null ;
490+ try {
491+ pid = ProcessHandle .current ().pid ();
492+ } catch (Exception e ) {
493+ IDEWorkbenchPlugin .log ("Failed to read Java process id." , e ); //$NON-NLS-1$
494+ }
495+ return pid != null ? pid .toString () : null ;
496+ }
497+
498+ private String getHostName () {
499+ String hostName = null ;
500+
501+ // Try fast approach first. Some OS(Like Linux) has HOSTNAME environment
502+ // variable set.
503+ try {
504+ hostName = System .getenv (HOST_NAME_VAR );
505+ if (hostName != null && !hostName .isEmpty ()) {
506+ return hostName ;
507+ }
508+ } catch (Exception e ) {
509+ // Ignore here because we will try another method in the next step.
510+ }
511+
512+ try {
513+ hostName = InetAddress .getLocalHost ().getHostName ();
514+ } catch (Exception e ) {
515+ IDEWorkbenchPlugin .log ("Failed to read host name." , e ); //$NON-NLS-1$
516+ }
517+ return hostName ;
518+ }
519+
520+ /**
521+ * Returns the .lock_info file. Does not check if it exists.
522+ *
523+ * @param workspaceUrl
524+ * @return .lock_info file.
525+ */
526+ private File getLockInfoFile (URL workspaceUrl ) {
527+ Path lockInfoPath = Path .of (workspaceUrl .getPath (), METADATA_FOLDER , LOCK_INFO_FILENAME );
528+ return lockInfoPath .toFile ();
529+ }
530+
531+ /**
532+ * Creates the .lock_info file if it does not exist.
533+ *
534+ * @param workspaceUrl
535+ * @return .lock_info file.
536+ */
537+ private File createLockInfoFile (URL workspaceUrl ) throws Exception {
538+ File lockInfoFile = getLockInfoFile (workspaceUrl );
539+
540+ if (lockInfoFile .exists ())
541+ return lockInfoFile ;
542+
543+ Path createdPath = Files .createFile (lockInfoFile .toPath ());
544+ if (createdPath != null ) {
545+ return createdPath .toFile ();
546+ }
547+ return null ;
548+ }
549+
346550 @ SuppressWarnings ("rawtypes" )
347551 private static boolean isDevLaunchMode (Map args ) {
348552 // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode()
0 commit comments