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 .net .UnknownHostException ;
3032import java .util .LinkedHashMap ;
3133import java .util .Map ;
3234import java .util .Properties ;
4951import org .eclipse .osgi .service .datalocation .Location ;
5052import org .eclipse .osgi .util .NLS ;
5153import org .eclipse .swt .SWT ;
54+ import org .eclipse .swt .layout .FillLayout ;
55+ import org .eclipse .swt .widgets .Composite ;
56+ import org .eclipse .swt .widgets .Control ;
5257import org .eclipse .swt .widgets .Display ;
58+ import org .eclipse .swt .widgets .Label ;
5359import org .eclipse .swt .widgets .Shell ;
5460import org .eclipse .ui .IWorkbench ;
5561import org .eclipse .ui .PlatformUI ;
@@ -79,6 +85,20 @@ public class IDEApplication implements IApplication, IExecutableExtension {
7985
8086 private static final String VERSION_FILENAME = "version.ini" ; //$NON-NLS-1$
8187
88+ private static final String LOCK_FILENAME = ".lock" ; //$NON-NLS-1$
89+
90+ private static final String DISPLAY_VAR = "DISPLAY" ; //$NON-NLS-1$
91+
92+ private static final String PROCESS_ID = "process-id" ; //$NON-NLS-1$
93+
94+ private static final String DISPLAY = "display" ; //$NON-NLS-1$
95+
96+ private static final String HOST = "host" ; //$NON-NLS-1$
97+
98+ private static final String USER = "user" ; //$NON-NLS-1$
99+
100+ private static final String USER_NAME = "user.name" ; //$NON-NLS-1$
101+
82102 // Use the branding plug-in of the platform feature since this is most likely
83103 // to change on an update of the IDE.
84104 private static final String WORKSPACE_CHECK_REFERENCE_BUNDLE_NAME = "org.eclipse.platform" ; //$NON-NLS-1$
@@ -225,6 +245,7 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) {
225245 try {
226246 if (instanceLoc .lock ()) {
227247 writeWorkspaceVersion ();
248+ writeWsLockInfo ();
228249 return null ;
229250 }
230251
@@ -313,6 +334,7 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) {
313334 if (instanceLoc .set (workspaceUrl , true )) {
314335 launchData .writePersistedData ();
315336 writeWorkspaceVersion ();
337+ writeWsLockInfo ();
316338 return null ;
317339 }
318340 } catch (IllegalStateException e ) {
@@ -332,17 +354,164 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) {
332354
333355 // by this point it has been determined that the workspace is
334356 // already in use -- force the user to choose again
357+
358+ String lockInfo = getWorkspaceLockInfo (workspaceUrl );
359+
335360 MessageDialog dialog = new MessageDialog (null , IDEWorkbenchMessages .IDEApplication_workspaceInUseTitle ,
336361 null , NLS .bind (IDEWorkbenchMessages .IDEApplication_workspaceInUseMessage , workspaceUrl .getFile ()),
337362 MessageDialog .ERROR , 1 , IDEWorkbenchMessages .IDEApplication_workspaceInUse_Retry ,
338- IDEWorkbenchMessages .IDEApplication_workspaceInUse_Choose );
363+ IDEWorkbenchMessages .IDEApplication_workspaceInUse_Choose ) {
364+ @ Override
365+ protected Control createCustomArea (Composite parent ) {
366+ if (lockInfo == null || lockInfo .isBlank ()) {
367+ return null ;
368+ }
369+
370+ String displayText = "Workspace lock is held by the following owner:\n " .concat (lockInfo ); //$NON-NLS-1$
371+
372+ Composite container = new Composite (parent , SWT .NONE );
373+ container .setLayout (new FillLayout ());
374+
375+ Label multiLineText = new Label (container , SWT .NONE );
376+ multiLineText .setText (displayText );
377+
378+ return container ;
379+ }
380+ };
339381 // the return value influences the next loop's iteration
340382 returnValue = dialog .open ();
341383 // Remember the locked workspace as recent workspace
342384 launchData .writePersistedData ();
343385 }
344386 }
345387
388+ /**
389+ * Read workspace lock file and parse all the properties present. Based on the
390+ * eclipse version and operating system some or all the properties may not
391+ * present. In such scenario it will return empty string.
392+ *
393+ * @return Previous lock owner details.
394+ */
395+ private String getWorkspaceLockInfo (URL workspaceUrl ) {
396+ File lockFile = getLockFile (workspaceUrl );
397+ StringBuilder sb = new StringBuilder ();
398+ Properties props = new Properties ();
399+ try (FileInputStream is = new FileInputStream (lockFile )) {
400+ props .load (is );
401+ String prop = props .getProperty (USER );
402+ if (prop != null ) {
403+ sb .append ("user: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
404+ }
405+ prop = props .getProperty (HOST );
406+ if (prop != null ) {
407+ sb .append ("host: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
408+ }
409+ prop = props .getProperty (DISPLAY );
410+ if (prop != null ) {
411+ sb .append ("display: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
412+ }
413+ prop = props .getProperty (PROCESS_ID );
414+ if (prop != null ) {
415+ sb .append ("process-id: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
416+ }
417+ return sb .toString ();
418+ } catch (Exception e ) {
419+ IDEWorkbenchPlugin .log ("Could not read lock file: " , e ); //$NON-NLS-1$
420+ }
421+ return null ;
422+ }
423+
424+ /**
425+ * Write lock owner details onto workspace lock file. Data includes user, host,
426+ * display and current java process id.
427+ */
428+ private void writeWsLockInfo () {
429+ Location instanceLoc = Platform .getInstanceLocation ();
430+ if (instanceLoc == null || instanceLoc .isReadOnly ()) {
431+ return ;
432+ }
433+
434+ File lockFile = getLockFile (instanceLoc .getURL ());
435+ if (lockFile == null ) {
436+ return ;
437+ }
438+
439+ Properties props = new Properties ();
440+
441+ String user = System .getProperty (USER_NAME );
442+ if (user != null ) {
443+ props .setProperty (USER , user );
444+ }
445+ String host = getHostName ();
446+ if (host != null ) {
447+ props .setProperty (HOST , host );
448+ }
449+ String display = getDisplay ();
450+ if (display != null ) {
451+ props .setProperty (DISPLAY , display );
452+ }
453+ String pid = getProcessId ();
454+ if (pid != null ) {
455+ props .setProperty (PROCESS_ID , pid );
456+ }
457+
458+ try (OutputStream output = new FileOutputStream (lockFile )) {
459+ props .store (output , null );
460+ } catch (IOException e ) {
461+ IDEWorkbenchPlugin .log ("Could not modify lock file" , e ); //$NON-NLS-1$
462+ }
463+ }
464+
465+ private String getDisplay () {
466+ String displayEnv = null ;
467+ try {
468+ displayEnv = System .getenv (DISPLAY_VAR );
469+ } catch (Exception e ) {
470+ IDEWorkbenchPlugin .log ("Failed to read DISPLAY variable." , e ); //$NON-NLS-1$
471+ }
472+ return displayEnv ;
473+ }
474+
475+ private String getProcessId () {
476+ Long pid = null ;
477+ try {
478+ pid = ProcessHandle .current ().pid ();
479+ } catch (Exception e ) {
480+ IDEWorkbenchPlugin .log ("Failed to read Java process id." , e ); //$NON-NLS-1$
481+ }
482+ return pid != null ? pid .toString () : null ;
483+ }
484+
485+ private String getHostName () {
486+ String hostName = null ;
487+ try {
488+ hostName = InetAddress .getLocalHost ().getHostName ();
489+ } catch (UnknownHostException e ) {
490+ IDEWorkbenchPlugin .log ("Failed to read host name." , e ); //$NON-NLS-1$
491+ }
492+ return hostName ;
493+ }
494+
495+ private File getLockFile (URL workspaceUrl ) {
496+ if (workspaceUrl == null ) {
497+ return null ;
498+ }
499+
500+ // make sure the directory exists
501+ File metaDir = new File (workspaceUrl .getPath (), METADATA_FOLDER );
502+ if (!metaDir .exists ()) {
503+ return null ;
504+ }
505+
506+ // make sure the file exists
507+ File lockFile = new File (metaDir , LOCK_FILENAME );
508+ if (!lockFile .exists ()) {
509+ return null ;
510+ }
511+
512+ return lockFile ;
513+ }
514+
346515 @ SuppressWarnings ("rawtypes" )
347516 private static boolean isDevLaunchMode (Map args ) {
348517 // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode()
0 commit comments