2222import java .nio .charset .StandardCharsets ;
2323import java .text .MessageFormat ;
2424import java .util .ArrayList ;
25+ import java .util .Comparator ;
2526import java .util .HashMap ;
27+ import java .util .LinkedHashMap ;
2628import java .util .List ;
2729import java .util .Map ;
30+ import java .util .Optional ;
2831
2932import javax .xml .parsers .DocumentBuilder ;
3033import javax .xml .parsers .FactoryConfigurationError ;
@@ -170,6 +173,14 @@ public class DebugPlugin extends Plugin {
170173 */
171174 public static final String EXTENSION_POINT_PROCESS_FACTORIES = "processFactories" ; //$NON-NLS-1$
172175
176+ /**
177+ * Simple identifier constant (value <code>"execFactories"</code>) for the
178+ * exec factories extension point.
179+ *
180+ * @since 3.23
181+ */
182+ public static final String EXTENSION_POINT_EXEC_FACTORIES = "execFactories" ; //$NON-NLS-1$
183+
173184 /**
174185 * Simple identifier constant (value <code>"logicalStructureTypes"</code>) for the
175186 * logical structure types extension point.
@@ -438,6 +449,11 @@ public class DebugPlugin extends Plugin {
438449 */
439450 private HashMap <String , IConfigurationElement > fProcessFactories = null ;
440451
452+ /**
453+ * List of exec factories.
454+ */
455+ private List <ExecFactoryFacade > execFactories ;
456+
441457 /**
442458 * Service tracker for the workspace service
443459 */
@@ -841,10 +857,13 @@ public static IProcess newProcess(ILaunch launch, Process process, String label)
841857 */
842858 public static IProcess newProcess (ILaunch launch , Process process , String label , Map <String , String > attributes ) {
843859 ILaunchConfiguration config = launch .getLaunchConfiguration ();
844- String processFactoryID = null ;
860+ String processFactoryID = null ;
861+ // FIXME hack for demo purpose we need an UI for the user to decide if a
862+ // terminal
863+ // is wanted
845864 if (config != null ) {
846865 try {
847- processFactoryID = config .getAttribute (ATTR_PROCESS_FACTORY_ID , ( String ) null );
866+ processFactoryID = config .getAttribute (ATTR_PROCESS_FACTORY_ID , "org.eclipse.debug.terminal.processFactory.cdt" ); //$NON-NLS-1$
848867 } catch (CoreException e ) {
849868 }
850869 }
@@ -981,34 +1000,45 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
9811000 * @since 3.14
9821001 */
9831002 public static Process exec (String [] cmdLine , File workingDirectory , String [] envp , boolean mergeOutput ) throws CoreException {
984- Process p = null ;
1003+ List <ExecFactoryFacade > factories = DebugPlugin .getDefault ().getExecFactories ();
1004+ Optional <File > directory = shortenWindowsPath (workingDirectory );
1005+ Optional <Map <String , String >> envMap = Optional .ofNullable (envp ).map (array -> {
1006+ Map <String , String > map = new LinkedHashMap <>();
1007+ for (String e : array ) {
1008+ int index = e .indexOf ('=' );
1009+ if (index != -1 ) {
1010+ map .put (e .substring (0 , index ), e .substring (index + 1 ));
1011+ }
1012+ }
1013+ return Map .copyOf (map );
1014+ });
1015+ for (ExecFactoryFacade holder : factories ) {
1016+ Optional <Process > exec = holder .exec (cmdLine .clone (), directory , envMap , mergeOutput );
1017+ if (exec .isPresent ()) {
1018+ return exec .get ();
1019+ }
1020+ }
9851021 try {
9861022 // starting with and without merged output could be done with the
9871023 // same process builder approach but since the handling of
9881024 // environment variables is slightly different between
9891025 // ProcessBuilder and Runtime.exec only the new option uses process
9901026 // builder to not break existing caller of this method
1027+
9911028 if (mergeOutput ) {
9921029 ProcessBuilder pb = new ProcessBuilder (cmdLine );
993- if (workingDirectory != null ) {
994- pb .directory (shortenWindowsPath (workingDirectory ));
995- }
1030+ directory .ifPresent (pb ::directory );
9961031 pb .redirectErrorStream (mergeOutput );
997- if (envp != null ) {
1032+ if (envMap . isPresent () ) {
9981033 Map <String , String > env = pb .environment ();
9991034 env .clear ();
1000- for (String e : envp ) {
1001- int index = e .indexOf ('=' );
1002- if (index != -1 ) {
1003- env .put (e .substring (0 , index ), e .substring (index + 1 ));
1004- }
1005- }
1035+ env .putAll (envMap .get ());
10061036 }
1007- p = pb .start ();
1008- } else if (workingDirectory == null ) {
1009- p = Runtime .getRuntime ().exec (cmdLine , envp );
1037+ return pb .start ();
1038+ } else if (directory . isEmpty () ) {
1039+ return Runtime .getRuntime ().exec (cmdLine , envp );
10101040 } else {
1011- p = Runtime .getRuntime ().exec (cmdLine , envp , shortenWindowsPath ( workingDirectory ));
1041+ return Runtime .getRuntime ().exec (cmdLine , envp , directory . get ( ));
10121042 }
10131043 } catch (IOException e ) {
10141044 Status status = new Status (IStatus .ERROR , getUniqueIdentifier (), ERROR , DebugCoreMessages .DebugPlugin_0 , e );
@@ -1021,18 +1051,18 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
10211051 if (handler != null ) {
10221052 Object result = handler .handleStatus (status , null );
10231053 if (result instanceof Boolean resultValue && resultValue ) {
1024- p = exec (cmdLine , null );
1054+ return exec (cmdLine , null );
10251055 }
10261056 }
10271057 }
1028- return p ;
1058+ return null ;
10291059 }
10301060
10311061 // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
10321062 private static final int WINDOWS_MAX_PATH = 258 ;
10331063
1034- private static File shortenWindowsPath (File path ) {
1035- if (path .getPath ().length () > WINDOWS_MAX_PATH && Platform .OS .isWindows ()) {
1064+ private static Optional < File > shortenWindowsPath (File path ) {
1065+ if (path != null && path .getPath ().length () > WINDOWS_MAX_PATH && Platform .OS .isWindows ()) {
10361066 // When spawning new processes on Windows, there is no uniform way
10371067 // to use long working directory paths that exceed the default path
10381068 // length limit, like for example using the raw path prefix '\\?\'
@@ -1042,12 +1072,12 @@ private static File shortenWindowsPath(File path) {
10421072 @ SuppressWarnings ("restriction" )
10431073 String shortPath = org .eclipse .core .internal .filesystem .local .Win32Handler .getShortPathName (path .toString ());
10441074 if (shortPath != null ) {
1045- return new File (shortPath );
1075+ return Optional . of ( new File (shortPath ) );
10461076 } else {
10471077 log (Status .warning ("Working directory of process to create exceeds Window's MAX_PATH limit and shortening the path failed." )); //$NON-NLS-1$
10481078 }
10491079 }
1050- return path ;
1080+ return Optional . ofNullable ( path ) ;
10511081 }
10521082
10531083 /**
@@ -1189,6 +1219,39 @@ private void initializeProcessFactories() {
11891219 }
11901220 }
11911221
1222+ private synchronized List <ExecFactoryFacade > getExecFactories () {
1223+ if (execFactories == null ) {
1224+ IExtensionPoint extensionPoint = Platform .getExtensionRegistry ().getExtensionPoint (DebugPlugin .PI_DEBUG_CORE , EXTENSION_POINT_EXEC_FACTORIES );
1225+ IConfigurationElement [] infos = extensionPoint .getConfigurationElements ();
1226+ List <ExecFactoryFacade > list = new ArrayList <>();
1227+ for (IConfigurationElement configurationElement : infos ) {
1228+ String clz = configurationElement .getAttribute ("class" ); //$NON-NLS-1$
1229+ if (clz != null ) {
1230+ int priority ;
1231+ String attribute = configurationElement .getAttribute ("priority" ); //$NON-NLS-1$
1232+ if (attribute == null ) {
1233+ priority = 0 ;
1234+ }
1235+ try {
1236+ priority = Integer .parseInt (attribute );
1237+ } catch (NumberFormatException e ) {
1238+ log (new Status (IStatus .ERROR , DebugPlugin .PI_DEBUG_CORE , ERROR , MessageFormat .format (DebugCoreMessages .DebugPlugin_invalid_exec_factory , new Object [] {
1239+ configurationElement .getContributor ().getName () }), null ));
1240+ priority = 0 ;
1241+ }
1242+ list .add (new ExecFactoryFacade (configurationElement , priority ));
1243+ } else {
1244+ String badDefiner = configurationElement .getContributor ().getName ();
1245+ log (new Status (IStatus .ERROR , DebugPlugin .PI_DEBUG_CORE , ERROR , MessageFormat .format (DebugCoreMessages .DebugPlugin_invalid_exec_factory , new Object [] {
1246+ badDefiner }), null ));
1247+ }
1248+ }
1249+ list .sort (Comparator .comparingInt (ExecFactoryFacade ::getPriority ).reversed ());
1250+ execFactories = List .copyOf (list );
1251+ }
1252+ return execFactories ;
1253+ }
1254+
11921255 private void invalidStatusHandler (Exception e , String id ) {
11931256 log (new Status (IStatus .ERROR , DebugPlugin .PI_DEBUG_CORE , ERROR , MessageFormat .format (DebugCoreMessages .DebugPlugin_5 , new Object [] { id }), e ));
11941257 }
@@ -1221,6 +1284,34 @@ public boolean equals(Object obj) {
12211284 }
12221285 }
12231286
1287+ private class ExecFactoryFacade implements ExecFactory {
1288+
1289+ private IConfigurationElement element ;
1290+ private int priority ;
1291+
1292+ ExecFactoryFacade (IConfigurationElement element , int priority ) {
1293+ this .element = element ;
1294+ this .priority = priority ;
1295+ }
1296+
1297+ public int getPriority () {
1298+ return priority ;
1299+ }
1300+
1301+ @ Override
1302+ public Optional <Process > exec (String [] cmdLine , Optional <File > workingDirectory , Optional <Map <String , String >> environment , boolean mergeOutput ) throws CoreException {
1303+ ExecFactory extension ;
1304+ try {
1305+ extension = (ExecFactory ) element .createExecutableExtension (IConfigurationElementConstants .CLASS );
1306+ } catch (CoreException e ) {
1307+ log (e );
1308+ return Optional .empty ();
1309+ }
1310+ return extension .exec (cmdLine , workingDirectory , environment , mergeOutput );
1311+ }
1312+
1313+ }
1314+
12241315 /**
12251316 * Executes runnables after event dispatch is complete.
12261317 *
0 commit comments