All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.bnd.build.ProjectLauncher Maven / Gradle / Ivy

The newest version!
package aQute.bnd.build;

import static java.util.Objects.requireNonNull;
import static org.osgi.framework.Constants.FRAMEWORK_BEGINNING_STARTLEVEL;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.osgi.framework.launch.FrameworkFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.help.instructions.BuilderInstructions;
import aQute.bnd.help.instructions.LauncherInstructions;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Domain;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.Verifier;
import aQute.bnd.service.Strategy;
import aQute.lib.io.IO;
import aQute.lib.startlevel.StartLevelRuntimeHandler;
import aQute.lib.strings.Strings;
import aQute.lib.watcher.FileWatcher;
import aQute.lib.watcher.FileWatcher.Builder;
import aQute.libg.command.Command;
import aQute.libg.generics.Create;

/**
 * A Project Launcher is a base class to be extended by launchers. Launchers are
 * JARs that launch a framework and install a number of bundles and then run the
 * framework. A launcher jar must specify a Launcher-Class manifest header. This
 * class is instantiated and cast to a LauncherPlugin. This plug in is then
 * asked to provide a ProjectLauncher. This project launcher is then used by the
 * project to run the code. Launchers must extend this class.
 */
public abstract class ProjectLauncher extends Processor {
	public static final String			EMBEDDED_ACTIVATOR	= "Embedded-Activator";

	final static Logger					logger				= LoggerFactory.getLogger(ProjectLauncher.class);
	private final Project				project;
	private final List		onUpdate			= new ArrayList<>();
	private long						timeout				= 0L;
	private final List			classpath			= new ArrayList<>();
	private List				runbundles			= Create.list();

	private final List			runvm				= new ArrayList<>();
	private final List			runprogramargs		= new ArrayList<>();
	private boolean						runframeworkrestart;
	private Map			runproperties;
	private Command						java;
	private Parameters					runsystempackages;
	private Parameters					runsystemcapabilities;
	private final List			activators			= Create.list();
	private File						storageDir;
	protected BuilderInstructions		builderInstrs;
	protected LauncherInstructions		launcherInstrs;

	private boolean						trace;
	private boolean						keep;
	private int							framework;
	private File						cwd;
	private Collection			agents				= new ArrayList<>();
	private Set	listeners			= Collections.newSetFromMap(new IdentityHashMap<>());

	protected Appendable				out					= System.out;
	protected Appendable				err					= System.err;
	protected InputStream				in					= System.in;

	public final static int				SERVICES			= 10111;
	public final static int				NONE				= 20123;

	// MUST BE ALIGNED WITH LAUNCHER
	public final static int				OK					= 0;
	public final static int				WARNING				= 126 - 1;
	public final static int				ERROR				= 126 - 2;
	public final static int				TIMEDOUT			= 126 - 3;
	public final static int				UPDATE_NEEDED		= 126 - 4;
	public final static int				CANCELED			= 126 - 5;
	public final static int				DUPLICATE_BUNDLE	= 126 - 6;
	public final static int				RESOLVE_ERROR		= 126 - 7;
	public final static int				ACTIVATOR_ERROR		= 126 - 8;
	public final static int				STOPPED				= 126 - 9;

	public ProjectLauncher(Project project) throws Exception {
		this.project = project;
		this.setBase(project.getBase());
		builderInstrs = project.getInstructions(BuilderInstructions.class);
		launcherInstrs = project.getInstructions(LauncherInstructions.class);

		validate();
	}

	/**
	 * Validate some settings
	 */
	protected void validate() {
		Collection runvm = getRunVM();
		if (runvm.size() == 1)
			try {
				for (String r : runvm) {
					if (Verifier.isSpaceSeparated(r)) {
						SetLocation location = project.warning(
							"%s is a comma (,) separated instruction, it looks like you separate its values with spaces? If you need spaces, please quote them: %s",
							Constants.RUNVM, runvm);
						project.getHeader(Constants.RUNVM)
							.set(location);
					}
				}
			} catch (Exception e) {
				// ignore
			}
	}

	/**
	 * Collect all the aspect from the project and set the local fields from
	 * them. Should be called after constructor has been called.
	 *
	 * @throws Exception
	 */
	protected void updateFromProject() throws Exception {
		setCwd(getProject().getBase());

		// pkr: could not use this because this is killing the runtests.
		// getProject().refresh();
		runbundles.clear();
		classpath.clear();

		Collection run = getProject().getRunbundles();

		for (Container container : run) {
			File file = container.getFile();
			if (file != null && (file.isFile() || file.isDirectory())) {
				addRunBundle(IO.absolutePath(file));
			} else {
				getProject().error("Bundle file \"%s\" does not exist, given error is %s", file, container.getError());
			}
		}

		if (getProject().getRunBuilds()) {
			File[] builds = getProject().getBuildFiles(true);
			if (builds != null)
				for (File file : builds)
					addRunBundle(IO.absolutePath(file));
		}

		runsystempackages = new Parameters(getProject().mergeProperties(Constants.RUNSYSTEMPACKAGES), getProject());
		runsystemcapabilities = new Parameters(getProject().mergeProperties(Constants.RUNSYSTEMCAPABILITIES),
			getProject());
		setRunFramework(getRunframework(getProject().getProperty(Constants.RUNFRAMEWORK)));

		setTimeout(Processor.getDuration(getProject().getProperty(Constants.RUNTIMEOUT), 0L), TimeUnit.MILLISECONDS);
		setTrace(getProject().isRunTrace());

		Collection runpath = getProject().getRunpath();
		runpath.addAll(getProject().getRunFw());
		for (Container c : runpath) {
			addClasspath(c);
		}

		getProject().getRunVM()
			.forEach(this::addRunVM);
		getProject().getRunProgramArgs()
			.forEach(this::addRunProgramArgs);
		runproperties = null; // set in getRunProperties
		runframeworkrestart = getProject().is(Constants.RUNFRAMEWORKRESTART);
		storageDir = getProject().getRunStorage();

		setKeep(getProject().getRunKeep());
	}

	private int getRunframework(String property) {
		if (Constants.RUNFRAMEWORK_NONE.equalsIgnoreCase(property))
			return NONE;
		if (Constants.RUNFRAMEWORK_SERVICES.equalsIgnoreCase(property))
			return SERVICES;
		return SERVICES;
	}

	public void addClasspath(Container container) throws Exception {
		addClasspath(container, classpath);
	}

	protected void addClasspath(Container container, List pathlist) throws Exception {
		if (container.getError() != null) {
			getProject().error("Cannot launch because %s has reported %s", container.getProject(),
				container.getError());
		} else {
			Collection members = container.getMembers();
			for (Container m : members) {
				String path = IO.absolutePath(m.getFile());
				if (!pathlist.contains(path)) {
					Manifest manifest = m.getManifest();
					if (manifest != null) {
						// We are looking for any agents, used if
						// -javaagent=true is set
						String agentClassName = manifest.getMainAttributes()
							.getValue("Premain-Class");
						if (agentClassName != null) {
							String agent = path;
							if (container.getAttributes()
								.get("agent") != null) {
								agent += "=" + container.getAttributes()
									.get("agent");
							}
							agents.add(agent);
						}

						Parameters exports = getProject().parseHeader(manifest.getMainAttributes()
							.getValue(Constants.EXPORT_PACKAGE));
						for (Entry e : exports.entrySet()) {
							if (!runsystempackages.containsKey(e.getKey()))
								runsystempackages.put(e.getKey(), e.getValue());
						}

						// Allow activators on the runpath. They are called
						// after the framework is completely initialized
						// with the system context.
						String activator = manifest.getMainAttributes()
							.getValue(EMBEDDED_ACTIVATOR);
						if (activator != null)
							activators.add(activator);
					}
					pathlist.add(path);
				}
			}
		}
	}

	protected void addClasspath(Collection path) throws Exception {
		for (Container c : Container.flatten(path)) {
			addClasspath(c);
		}
	}

	public void addRunBundle(String path) {
		path = IO.normalizePath(path);
		if (!runbundles.contains(path)) {
			runbundles.add(path);
		}
	}

	public Collection getRunBundles() {
		return runbundles;
	}

	public void addRunVM(String arg) {
		runvm.add(arg);
	}

	public void addRunProgramArgs(String arg) {
		runprogramargs.add(arg);
	}

	public List getRunpath() {
		return classpath;
	}

	public Collection getClasspath() {
		return classpath;
	}

	public Collection getRunVM() {
		return runvm;
	}

	public Collection getRunProgramArgs() {
		return runprogramargs;
	}

	public Map getRunProperties() {
		Map properties = runproperties;
		if (properties != null) {
			return properties;
		}
		properties = getProject().getRunProperties();
		try {
			calculatedProperties(properties);
		} catch (Exception e) {
			throw Exceptions.duck(e);
		}
		return runproperties = properties;
	}

	public File getStorageDir() {
		return storageDir;
	}

	public abstract String getMainTypeName();

	public void update() throws Exception {
		getProject().refresh();
		updateFromProject();
		for (Runnable update : onUpdate) {
			update.run();
		}
	}

	public void onUpdate(Runnable update) {
		onUpdate.add(update);
	}

	@Override
	public String getJavaExecutable(String java) {
		return getProject().getJavaExecutable(java);
	}

	public int launch() throws Exception {
		prepare();
		java = new Command();

		//
		// Handle the environment
		//

		getRunEnv().forEach(java::var);

		java.add(getJavaExecutable("java"));
		if (getProject().is(Constants.JAVAAGENT)) {
			for (String agent : agents) {
				java.add("-javaagent:" + agent);
			}
		}

		String jdb = getRunJdb();
		if (jdb != null) {
			Matcher matcher = RUNJDB_P.matcher(jdb);
			String address;
			String suspend;
			if (matcher.matches()) {
				address = matcher.group("address");
				suspend = "-".equals(matcher.group("sign")) ? "n" : "y";
			} else {
				address = "1044";
				suspend = "y";
			}
			java.add("-agentlib:jdwp=transport=dt_socket,server=y,address=" + address + ",suspend=" + suspend);
		}

		java.addAll(split(System.getenv("JAVA_OPTS"), "\\s+"));

		java.add("-cp");
		java.add(join(getClasspath(), File.pathSeparator));
		java.addAll(getRunVM());
		java.add(getMainTypeName());
		java.addAll(getRunProgramArgs());
		if (getTimeout() != 0) {
			java.setTimeout(getTimeout() + 1000, TimeUnit.MILLISECONDS);
		}

		File cwd = getCwd();
		if (cwd != null)
			java.setCwd(cwd);

		logger.debug("cmd line {}", java);
		try {
			int result = java.execute(in, out, err);
			if (result == Integer.MIN_VALUE)
				return TIMEDOUT;
			reportResult(result);
			return result;
		} finally {
			cleanup();
			listeners.clear();
		}
	}

	/**
	 * launch a framework internally. I.e. do not start a separate process.
	 */
	final static Pattern IGNORE = Pattern.compile("org[./]osgi[./]resource.*");

	public int start(ClassLoader parent) throws Exception {
		// FIXME This seems kinda broken. I think ProjectLauncherImpl will need
		// to implement this since only it will know the main class name of the
		// non-pre jar

		prepare();

		//
		// Intermediate class loader to not load osgi framework packages
		// from bnd's loader. Unfortunately, bnd uses some osgi classes
		// itself that would unnecessarily constrain the framework.
		//

		ClassLoader fcl = new ClassLoader(parent) {
			@Override
			protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
				if (IGNORE.matcher(name)
					.matches())
					throw new ClassNotFoundException();

				return super.loadClass(name, resolve);
			}
		};

		//
		// Load the class that would have gone to the class path
		// i.e. the framework etc.
		//

		List cp = new ArrayList<>();
		for (String path : getRunpath()) {
			cp.add(new File(path).toURI()
				.toURL());
		}
		@SuppressWarnings("resource")
		URLClassLoader cl = new URLClassLoader(cp.toArray(new URL[0]), fcl) {
			@Override
			public void addURL(URL url) {
				super.addURL(url);
			}
		};

		String[] args = getRunProgramArgs().toArray(new String[0]);

		Class main = cl.loadClass(getMainTypeName());
		return invoke(main, args);
	}

	protected int invoke(Class main, String args[]) throws Exception {
		throw new UnsupportedOperationException();
	}

	/**
	 * Is called after the process exists. Can you be used to cleanup the
	 * properties file.
	 */

	public void cleanup() {
		// do nothing by default
	}

	protected void reportResult(int result) {
		switch (result) {
			case OK :
				logger.debug("Command terminated normal {}", java);
				break;
			case TIMEDOUT :
				getProject().error("Launch timedout: %s", java);
				break;

			case ERROR :
				getProject().error("Launch errored: %s", java);
				break;

			case WARNING :
				getProject().warning("Launch had a warning %s", java);
				break;
			default :
				getProject().error("Exit code remote process %d: %s", result, java);
				break;
		}
	}

	public void setTimeout(long timeout, TimeUnit unit) {
		this.timeout = unit.convert(timeout, TimeUnit.MILLISECONDS);
	}

	public long getTimeout() {
		return this.timeout;
	}

	public void cancel() throws Exception {
		java.cancel();
	}

	public Map> getSystemPackages() {
		return runsystempackages.asMapMap();
	}

	public String getSystemCapabilities() {
		return runsystemcapabilities.isEmpty() ? null : runsystemcapabilities.toString();
	}

	public Parameters getSystemCapabilitiesParameters() {
		return runsystemcapabilities;
	}

	public void setKeep(boolean keep) {
		this.keep = keep;
	}

	public boolean isKeep() {
		return keep;
	}

	@Override
	public void setTrace(boolean level) {
		this.trace = level;
	}

	public boolean getTrace() {
		return this.trace;
	}

	/**
	 * Should be called when all the changes to the launchers are set. Will
	 * calculate whatever is necessary for the launcher.
	 *
	 * @throws Exception
	 */
	public void prepare() throws Exception {
		// noop
	}

	public Project getProject() {
		return project;
	}

	public boolean addActivator(String e) {
		return activators.add(e);
	}

	public Collection getActivators() {
		return Collections.unmodifiableCollection(activators);
	}

	/**
	 * Either NONE or SERVICES to indicate how the remote end launches. NONE
	 * means it should not use the classpath to run a framework. This likely
	 * requires some dummy framework support. SERVICES means it should load the
	 * framework from the claspath.
	 */
	public int getRunFramework() {
		return framework;
	}

	public void setRunFramework(int n) {
		assert n == NONE || n == SERVICES;
		this.framework = n;
	}

	/**
	 * Add the specification for a set of bundles the runpath if it does not
	 * already is included. This can be used by subclasses to ensure the proper
	 * jars are on the classpath.
	 *
	 * @param defaultSpec The default spec for default jars
	 */
	public void addDefault(String defaultSpec) throws Exception {
		Collection deflts = getProject().getBundles(Strategy.HIGHEST, defaultSpec, null);
		for (Container c : deflts)
			addClasspath(c);
	}

	/**
	 * Create a self executable.
	 */

	public Jar executable() throws Exception {
		throw new UnsupportedOperationException();
	}

	public File getCwd() {
		return cwd;
	}

	public void setCwd(File cwd) {
		this.cwd = cwd;
	}

	private static final Pattern RUNJDB_P = Pattern
		.compile("^\\s*(?[-+])?(?
(?:\\S+:)?\\d{1,5})\\s*$"); public String getRunJdb() { return getProject().getProperty(Constants.RUNJDB); } public Map getRunEnv() { String runenv = getProject().getProperty(Constants.RUNENV); if (runenv != null) { return OSGiHeader.parseProperties(runenv); } return Collections.emptyMap(); } public interface NotificationListener { void notify(NotificationType type, String notification); } public enum NotificationType { ERROR, WARNING, INFO; } public void registerForNotifications(NotificationListener listener) { listeners.add(listener); } public Set getNotificationListeners() { return Collections.unmodifiableSet(listeners); } /** * Set the stderr and stdout streams for the output process. The debugged * process must append its output (i.e. write operation in the process under * debug) to the given appendables. * * @param out std out * @param err std err */ public void setStreams(Appendable out, Appendable err) { this.out = out; this.err = err; } /** * Write text to the debugged process as if it came from stdin. * * @param text the text to write * @throws Exception */ public void write(String text) throws Exception { } /** * Get the run sessions. If this return null, then launch on this object * should be used, otherwise each returned object provides a remote session. * * @throws Exception */ public List getRunSessions() throws Exception { return null; } /** * Utility to calculate the final framework properties from settings */ public void calculatedProperties(Map properties) throws Exception { setupStartlevels(properties); } /** * Calculate the start level properties. This code is matched to the * aQute.lib class {@link StartLevelRuntimeHandler} that handles the runtime * details. *

* The -runbundles instruction can carry a `startlevel` attribute. If any * bundle has this start level attribute we control the startlevel process. * If no bundle has this attribute, then the start level handling is not * doing anything. The remaining section assumes that there is at least 1 * bundle with a set startlevel attribute. *

* The {@link StartLevelRuntimeHandler#LAUNCH_STARTLEVEL_DEFAULT} is then * set to the maximum startlevel + 1. This signals that the * {@link StartLevelRuntimeHandler} class handles the runtime aspects. *

* The {@link Constants#RUNSTARTLEVEL_BEGIN} controls the beginning start * level of the framework after the framework itself is started. The user * can set this or else it is set to the maxmum startlevel+2. *

* During runtime, the handler must be created with * {@link StartLevelRuntimeHandler#create(aQute.lib.startlevel.Trace, Map)} * before the framework is created since it may change the properties. I.e. * the properties given to the {@link FrameworkFactory} must be the same * object as given to the create method. One thing is that it will set the * {@link Constants#RUNSTARTLEVEL_BEGIN} to ensure that all bundles are * installed at level 1. *

* After the framework is created, the runtime handler * {@link StartLevelRuntimeHandler#beforeStart(org.osgi.framework.launch.Framework)} * must be called. This will prepare that bundles will get their proper * start level when installed. *

* After the set of bundles is installed, the * {@link StartLevelRuntimeHandler#afterStart()} is called to raise the * start level to the desired level. Either the set * {@link Constants#RUNSTARTLEVEL_BEGIN} or the maximum level + 2. */ private void setupStartlevels(Map properties) throws Exception { Parameters runbundles = new Parameters(); int maxLevel = -1; for (Container c : project.getRunbundles()) { Map attrs = c.getAttributes(); if (attrs == null) continue; if (c.getBundleSymbolicName() == null) continue; if (c.getError() != null) continue; if (c.getFile() == null || !c.getFile() .isFile()) continue; Attrs runtimeAttrs; if (attrs instanceof Attrs a) { runtimeAttrs = new Attrs(a); } else { runtimeAttrs = new Attrs(attrs); } String startLevelString = attrs.get(Constants.RUNBUNDLES_STARTLEVEL_ATTRIBUTE); if (startLevelString == null) continue; int startlevel = -1; if (!Verifier.isNumber(startLevelString)) { error("Invalid start level on -runbundles. bsn=%s, version=%s, startlevel=%s, not a number", c.getBundleSymbolicName(), c.getVersion(), startLevelString); continue; } else { startlevel = Integer.parseInt(startLevelString); if (startlevel > 0) { if (startlevel > maxLevel) maxLevel = startlevel; } Domain domain = Domain.domain(c.getFile()); String bsn = domain.getBundleSymbolicName() .getKey(); String bundleVersion = Strings.trim(domain.getBundleVersion()); if (!Verifier.isVersion(bundleVersion)) { error("Invalid version on -runbundles. bsn=%s, version=%s", c.getBundleSymbolicName(), c.getVersion(), startLevelString); continue; } else { runtimeAttrs.put("version", bundleVersion); } runbundles.put(bsn, runtimeAttrs); } } boolean areStartlevelsEnabled = maxLevel > 0; String beginningLevelString = properties.get(FRAMEWORK_BEGINNING_STARTLEVEL); if (!runbundles.isEmpty()) { properties.put(Constants.LAUNCH_RUNBUNDLES_ATTRS, runbundles.toString()); if (areStartlevelsEnabled) { int defaultLevel = maxLevel + 1; int beginningLevel = maxLevel + 2; if (!properties.containsKey(LAUNCH_STARTLEVEL_DEFAULT)) { switch (project.instructions.launcher() .manage()) { default : case all : properties.put(LAUNCH_STARTLEVEL_DEFAULT, Integer.toString(defaultLevel)); break; case narrow : properties.put(LAUNCH_STARTLEVEL_DEFAULT, Integer.toString(-defaultLevel)); break; case none : properties.put(LAUNCH_STARTLEVEL_DEFAULT, "0"); break; } } if (beginningLevelString == null) { properties.put(FRAMEWORK_BEGINNING_STARTLEVEL, Integer.toString(beginningLevel)); } } } if (beginningLevelString != null) { if (!Verifier.isNumber(beginningLevelString)) { error("%s set to %s, not a valid startlevel (is not a number)", beginningLevelString); } else { int beginningStartLevel = Integer.parseInt(beginningLevelString); if (beginningStartLevel < 1) { error("%s set to %s, must be > 0", beginningLevelString); } } } } public LiveCoding liveCoding(Executor executor, ScheduledExecutorService scheduledExecutor) throws Exception { return new LiveCoding(executor, scheduledExecutor); } public class LiveCoding implements Closeable { private final Semaphore semaphore = new Semaphore(1); private final AtomicBoolean propertiesChanged = new AtomicBoolean(false); private final Executor executor; private final ScheduledExecutorService scheduledExecutor; private volatile FileWatcher fw; LiveCoding(Executor executor, ScheduledExecutorService scheduledExecutor) throws Exception { this.executor = requireNonNull(executor); this.scheduledExecutor = requireNonNull(scheduledExecutor); watch(); } @Override public void close() { FileWatcher old = fw; if (old != null) { old.close(); } } private void watch() throws IOException { Builder builder = new FileWatcher.Builder().executor(executor) .changed(this::changed) .file(getProject().getPropertiesFile()) .files(getProject().getIncluded()); for (String runpath : getRunpath()) { builder.file(new File(runpath)); } for (String runbundle : getRunBundles()) { builder.file(new File(runbundle)); } FileWatcher old = fw; fw = builder.build(); if (old != null) { old.close(); } logger.debug("[LiveCoding] Watching for changes..."); } private void changed(File file, String kind) { logger.info("[LiveCoding] Detected change to {}.", file); propertiesChanged.compareAndSet(false, getProject().getPropertiesFile() .equals(file) || getProject().getIncluded() .contains(file)); if (semaphore.tryAcquire()) { scheduledExecutor.schedule(() -> { try { logger.info("[LiveCoding] Updating ProjectLauncher."); update(); } catch (Exception e) { logger.error("[LiveCoding] Error on ProjectLauncher update", e); } finally { semaphore.release(); if (propertiesChanged.compareAndSet(true, false)) { logger.info("[LiveCoding] Detected changes to bnd properties file. Replacing watcher."); try { watch(); } catch (IOException e) { logger.error("[LiveCoding] Error replacing watcher {}", e); } } } }, 600, TimeUnit.MILLISECONDS); } } } public boolean isRunFrameworkRestart() { return runframeworkrestart; } static final String[] STRINGARRAY = new String[0]; public static String renderArguments(Collection arguments) { return renderArguments(arguments.toArray(STRINGARRAY)); } public static String renderArguments(Collection arguments, boolean isWin32) { return renderArguments(arguments.toArray(STRINGARRAY), isWin32); } public static String renderArguments(String[] arguments) { return renderArguments(arguments, IO.isWindows()); } public static String renderArguments(String[] arguments, boolean isWin32) { StringBuilder buf = new StringBuilder(); int count = arguments.length; for (int i = 0; i < count; i++) { if (i > 0) { buf.append(' '); } boolean containsSpace = false; char[] characters = arguments[i].toCharArray(); for (char ch : characters) { if (ch == ' ' || ch == '\t') { containsSpace = true; buf.append('"'); break; } } int backslashes = 0; for (int j = 0; j < characters.length; j++) { char ch = characters[j]; if (ch == '"') { if (isWin32) { if (j == 0 && characters.length == 2 && characters[1] == '"') { // empty string on windows platform, see bug 130767. // Bug in constructor of JDK's // java.lang.ProcessImpl. buf.append("\"\""); //$NON-NLS-1$ break; } if (backslashes > 0) { // Feature in Windows: need to double-escape // backslashes in front of double quote. for (; backslashes > 0; backslashes--) { buf.append('\\'); } } } buf.append('\\'); } else if (ch == '\\') { if (isWin32) { backslashes++; } else { buf.append('\\'); } } else if (isWin32) { backslashes = 0; // FIX for Eclipse code } buf.append(ch); } if (containsSpace) { buf.append('"'); } else if (characters.length == 0) { buf.append("\"\""); //$NON-NLS-1$ } } return buf.toString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy