private void addPlugins(Plugin[] plugs) throws PluginException {
		serviceMgr.setServiceAddedNotificationsOn(false);

		List<ServiceInterfaceImplementationPair> previousServices = serviceMgr.getAllServices();

		StringBuilder errMsg = new StringBuilder();
		MultipleCauses report = new MultipleCauses();

		int numOldPlugins = pluginList.size();
		for (Plugin newPluginToAdd : plugs) {
			pluginList.add(newPluginToAdd);

			// have each plugin publish its services
			newPluginToAdd.initServices();
		}

		// Find any unresolved dependencies in the plugins we are trying to load.
		// Delay throwing an exception so we can load the plugins that don't have problems.
		Map<Class<?>, PluginException> dependencyProblemResults = new HashMap<>();
		Set<PluginDependency> unresolvedDependencySet =
			resolveDependencies(dependencyProblemResults);
		if (!unresolvedDependencySet.isEmpty()) {
			for (PluginDependency pd : unresolvedDependencySet) {
				Class<?> dependency = pd.dependency();
				PluginException cause = dependencyProblemResults.get(dependency);
				String dependencyName = dependency.getName();
				String dependentName = pd.dependant().getName();
				String message = """
						Unresolved dependency: %s.  Used by plugin: %s
						""".formatted(dependencyName, dependentName);

				errMsg.append(message).append('\n');
				if (cause != null) {
					errMsg.append("Reason: ").append(cause.getMessage()).append('\n');
				}

				report.addCause(new PluginException(message, cause));
			}
			cleanupPluginsWithUnresolvedDependencies();
		}

		PluginEvent[] lastEvents = tool.getLastEvents();
		List<Plugin> sortedPlugins = getPluginsByServiceOrder(numOldPlugins);
		List<Plugin> badList = new ArrayList<>();
		for (Iterator<Plugin> it = sortedPlugins.iterator(); it.hasNext();) {
			Plugin p = it.next();
			try {
				p.init(); // allow each plugin to acquire services
			}
			catch (Throwable t) {
				Msg.error(this, "Unexpected Exception: " + t.getMessage(), t);
				errMsg.append("Initializing " + p.getName() + " failed: " + t + "\n");
				report.addCause(t);

				// NOTE: other plugins that depend on this failed plugin will still be
				// init()'d as well, which opens a small window of problem causing.
				badList.add(p); // remove plugin from the tool
				it.remove(); // remove the plugin from the iterator so we don't call it later
			}
		}

		if (badList.size() > 0) {
			try {
				removePlugins(badList);
			}
			catch (Throwable t) {
				log.debug("Exception unloading plugin", t);
			}
		}
		serviceMgr.setServiceAddedNotificationsOn(true);

		// notify the new plugins of all pre-existing services.
		for (Plugin p : sortedPlugins) {
			notifyServices(p, previousServices);
		}

		for (Plugin p : sortedPlugins) {
			p.processLastEvents(lastEvents);
		}

		if (errMsg.length() > 0) {
			throw new PluginException(errMsg.toString(), report);
		}
	}
Identify plugin which will accept specified URL.  If no plugin accepts URL it will be
rejected and false returned. If a plugin can accept the specified URL it will attempt to
process and return true if successful.
The user may be prompted if connecting to the URL requires user authentication.
@param url read-only resource URL
@return true if URL accepted and processed else false
Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java