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,165 @@ 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 ();
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 () {
396+ Location instanceLoc = Platform .getInstanceLocation ();
397+ File lockFile = getLockFile (instanceLoc .getURL ());
398+ StringBuilder sb = new StringBuilder ();
399+ Properties props = new Properties ();
400+ try (FileInputStream is = new FileInputStream (lockFile )) {
401+ props .load (is );
402+ String prop = props .getProperty (USER );
403+ if (prop != null ) {
404+ sb .append ("user: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
405+ }
406+ prop = props .getProperty (HOST );
407+ if (prop != null ) {
408+ sb .append ("host: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
409+ }
410+ prop = props .getProperty (DISPLAY );
411+ if (prop != null ) {
412+ sb .append ("display: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
413+ }
414+ prop = props .getProperty (PROCESS_ID );
415+ if (prop != null ) {
416+ sb .append ("process-id: " ).append (prop ).append (System .lineSeparator ()); //$NON-NLS-1$
417+ }
418+ return sb .toString ();
419+ } catch (Exception e ) {
420+ IDEWorkbenchPlugin .log ("Could not read lock file: " , e ); //$NON-NLS-1$
421+ }
422+ return null ;
423+ }
424+
425+ /**
426+ * Write lock owner details onto workspace lock file. Data includes user, host,
427+ * display and current java process id.
428+ */
429+ private void writeWsLockInfo () {
430+ Location instanceLoc = Platform .getInstanceLocation ();
431+ if (instanceLoc == null || instanceLoc .isReadOnly ()) {
432+ return ;
433+ }
434+
435+ File lockFile = getLockFile (instanceLoc .getURL ());
436+ if (lockFile == null ) {
437+ return ;
438+ }
439+
440+ Properties props = new Properties ();
441+
442+ String user = System .getProperty (USER_NAME );
443+ if (user != null ) {
444+ props .setProperty (USER , user );
445+ }
446+ String host = getHostName ();
447+ if (host != null ) {
448+ props .setProperty (HOST , host );
449+ }
450+ String display = getDisplay ();
451+ if (display != null ) {
452+ props .setProperty (DISPLAY , display );
453+ }
454+ String pid = getProcessId ();
455+ if (pid != null ) {
456+ props .setProperty (PROCESS_ID , pid );
457+ }
458+
459+ try (OutputStream output = new FileOutputStream (lockFile )) {
460+ props .store (output , null );
461+ } catch (IOException e ) {
462+ IDEWorkbenchPlugin .log ("Could not modify lock file" , e ); //$NON-NLS-1$
463+ }
464+ }
465+
466+ private String getDisplay () {
467+ String displayEnv = null ;
468+ try {
469+ displayEnv = System .getenv (DISPLAY_VAR );
470+ } catch (Exception e ) {
471+ IDEWorkbenchPlugin .log ("Failed to read DISPLAY variable." , e ); //$NON-NLS-1$
472+ }
473+ return displayEnv ;
474+ }
475+
476+ private String getProcessId () {
477+ Long pid = null ;
478+ try {
479+ pid = ProcessHandle .current ().pid ();
480+ } catch (Exception e ) {
481+ IDEWorkbenchPlugin .log ("Failed to read Java process id." , e ); //$NON-NLS-1$
482+ }
483+ return pid != null ? pid .toString () : null ;
484+ }
485+
486+ private String getHostName () {
487+ String hostName = null ;
488+ try {
489+ hostName = InetAddress .getLocalHost ().getHostName ();
490+ } catch (UnknownHostException e ) {
491+ IDEWorkbenchPlugin .log ("Failed to read host name." , e ); //$NON-NLS-1$
492+ }
493+ return hostName ;
494+ }
495+
496+ private File getLockFile (URL workspaceUrl ) {
497+ if (workspaceUrl == null ) {
498+ return null ;
499+ }
500+
501+ // make sure the directory exists
502+ File metaDir = new File (workspaceUrl .getPath (), METADATA_FOLDER );
503+ if (!metaDir .exists ()) {
504+ return null ;
505+ }
506+
507+ // make sure the file exists
508+ File lockFile = new File (metaDir , LOCK_FILENAME );
509+ if (!lockFile .exists ()) {
510+ return null ;
511+ }
512+
513+ return lockFile ;
514+ }
515+
346516 @ SuppressWarnings ("rawtypes" )
347517 private static boolean isDevLaunchMode (Map args ) {
348518 // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode()
0 commit comments