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 ();
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 ();
316350 return null ;
317351 }
318352 } catch (IllegalStateException e ) {
@@ -332,17 +366,196 @@ 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+ private String getWorkspaceLockInfo (URL workspaceUrl ) {
406+ File lockFile = getLockInfoFile (workspaceUrl , false );
407+ if (lockFile == null ) {
408+ return null ;
409+ }
410+ StringBuilder sb = new StringBuilder ();
411+ Properties props = new Properties ();
412+ try (FileInputStream is = new FileInputStream (lockFile )) {
413+ props .load (is );
414+ String prop = props .getProperty (USER );
415+ if (prop != null ) {
416+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_User , prop ));
417+ }
418+ prop = props .getProperty (HOST );
419+ if (prop != null ) {
420+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_Host , prop ));
421+ }
422+ prop = props .getProperty (DISPLAY );
423+ if (prop != null ) {
424+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_Disp , prop ));
425+ }
426+ prop = props .getProperty (PROCESS_ID );
427+ if (prop != null ) {
428+ sb .append (NLS .bind (IDEWorkbenchMessages .IDEApplication_Ws_Lock_Owner_P_Id , prop ));
429+ }
430+ return sb .toString ();
431+ } catch (Exception e ) {
432+ IDEWorkbenchPlugin .log ("Could not read lock file: " , e ); //$NON-NLS-1$
433+ }
434+ return null ;
435+ }
436+
437+ /**
438+ * Write lock owner details onto workspace lock file. Data includes user, host,
439+ * display and current java process id.
440+ */
441+ protected void writeWsLockInfo () {
442+ Properties props = new Properties ();
443+
444+ String user = System .getProperty (USER_NAME );
445+ if (user != null ) {
446+ props .setProperty (USER , user );
447+ }
448+ String host = getHostName ();
449+ if (host != null ) {
450+ props .setProperty (HOST , host );
451+ }
452+ String display = getDisplay ();
453+ if (display != null ) {
454+ props .setProperty (DISPLAY , display );
455+ }
456+ String pid = getProcessId ();
457+ if (pid != null ) {
458+ props .setProperty (PROCESS_ID , pid );
459+ }
460+
461+ if (props .isEmpty ()) {
462+ return ;
463+ }
464+
465+ Location instanceLoc = Platform .getInstanceLocation ();
466+ if (instanceLoc == null || instanceLoc .isReadOnly ()) {
467+ return ;
468+ }
469+
470+ File lockFile = getLockInfoFile (instanceLoc .getURL (), true );
471+ if (lockFile == null ) {
472+ return ;
473+ }
474+
475+ if (lockFile == null || !lockFile .exists ()) {
476+ return ;
477+ }
478+
479+ try (OutputStream output = new FileOutputStream (lockFile )) {
480+ props .store (output , null );
481+ } catch (IOException e ) {
482+ IDEWorkbenchPlugin .log ("Could not modify lock file" , e ); //$NON-NLS-1$
483+ }
484+ }
485+
486+ private String getDisplay () {
487+ String displayEnv = null ;
488+ try {
489+ displayEnv = System .getenv (DISPLAY_VAR );
490+ } catch (Exception e ) {
491+ IDEWorkbenchPlugin .log ("Failed to read DISPLAY variable." , e ); //$NON-NLS-1$
492+ }
493+ return displayEnv ;
494+ }
495+
496+ private String getProcessId () {
497+ Long pid = null ;
498+ try {
499+ pid = ProcessHandle .current ().pid ();
500+ } catch (Exception e ) {
501+ IDEWorkbenchPlugin .log ("Failed to read Java process id." , e ); //$NON-NLS-1$
502+ }
503+ return pid != null ? pid .toString () : null ;
504+ }
505+
506+ private String getHostName () {
507+ String hostName = null ;
508+
509+ // Try fast approach first. Some OS(Like Linux) has HOSTNAME environment
510+ // variable set.
511+ try {
512+ hostName = System .getenv (HOST_NAME_VAR );
513+ if (hostName != null && !hostName .isEmpty ()) {
514+ return hostName ;
515+ }
516+ } catch (Exception e ) {
517+ // Ignore here because we will try another method in the next step.
518+ }
519+
520+ try {
521+ hostName = InetAddress .getLocalHost ().getHostName ();
522+ } catch (Exception e ) {
523+ IDEWorkbenchPlugin .log ("Failed to read host name." , e ); //$NON-NLS-1$
524+ }
525+ return hostName ;
526+ }
527+
528+ /**
529+ * Returns the .lock_info file if it is present or it will create new file.
530+ *
531+ * @param workspaceUrl
532+ * @return Create .lock file if it does not present and return.
533+ */
534+ private File getLockInfoFile (URL workspaceUrl , boolean create ) {
535+ if (workspaceUrl == null ) {
536+ return null ;
537+ }
538+ Path lockInfoPath = Path .of (workspaceUrl .getPath (), METADATA_FOLDER , LOCK_INFO_FILENAME );
539+
540+ if (Files .exists (lockInfoPath )) {
541+ return lockInfoPath .toFile ();
542+ }
543+
544+ try {
545+ if (create ) {
546+ Path createdPath = Files .createFile (lockInfoPath );
547+ if (createdPath != null ) {
548+ return createdPath .toFile ();
549+ }
550+ }
551+ } catch (IOException e ) {
552+ IDEWorkbenchPlugin .log ("Failed to create workspace lock file." , e ); //$NON-NLS-1$
553+ return null ;
554+ }
555+
556+ return null ;
557+ }
558+
346559 @ SuppressWarnings ("rawtypes" )
347560 private static boolean isDevLaunchMode (Map args ) {
348561 // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode()
0 commit comments