HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component pal.Assembly by barry
expand copy to clipboardexpand
uses pal.Perception
uses Service
uses pal.ProxyInfo

data ConfigOption {
	Composition c
	char cflat[]
}

data ActiveIntercept {
	char cmp[]
	IDC com
	char reqIntf[]
	}

//wiring graph of the current live system
data Interface {
	//name of the required interface
	char name[]
	//path of the component to which this required interface is currently connected, if any
	char currentWiring[]
	//proxy info associated with this wiring, if any (sometimes necessary to differentiate two wiring targets that might otherwise look the same, if they use the same proxy component but with different proxy params)
	char wiringProxyInfo[]
	//list of interceptors active on this required interface (currently we only support one)
	ActiveIntercept intercept
	}

data Component {
	char path[]
	IDC com

	int completeWirings
	bool serviceStarted

	//the set of required interfaces of this component
	Interface interfaces[]

	bool used

	bool proxy
	char proxyTag[]
	char proxyParams[]
	char proxyInterface[]

	Component next
	}

data Interceptor {
	char intf[]
	char cmp[]
	
	char proIntf[]
	char reqIntf[]
	}

component provides Assembly requires io.Output out, data.StringUtil stringUtil, data.IntUtil iu, composition.OptionBuilder, Loader loader, NativeLoader nLoader, composition.RecursiveLoader rLoader, composition.Adapt adaptAPI, composition.Intercept interceptAPI, System system, os.SystemInfo systemInfo, data.query.Search search, io.FileSystem fileSystem, data.adt.List {

	bool verbose

	//the list of loaded components and their current wirings
	Component components
	Component mainComponent

	Composition compositions[]
	ConfigOption configs[]
	int currentConfig

	Mutex configLock = new Mutex()

	IDC perception

	App myApp
	AppParam subParams[]
	bool appStarted
	bool appFinished
	bool exitCode

	OptionBuilder builder

	String searchPaths[]

	//the list of registered interceptors
	Interceptor interceptors[]

	//disabled exceptions
	String exceptionsDisabled[]

	Assembly:Assembly(char main[], store AppParam params[], store IDC perceptionModule, opt store String interfaceExcludeList[], store String componentExcludeList[])
		{
		searchPaths = getStandardSearchPaths(main)

		builder = new OptionBuilder(main, searchPaths, new String[](new String("pal.Perception"), interfaceExcludeList), componentExcludeList)

		Composition options[] = builder.getCompositions()

		subParams  = params

		perception = perceptionModule

		ConfigOption newConfigs[] = new ConfigOption[options.arrayLength]
		configs = newConfigs

		for (int i = 0; i < newConfigs.arrayLength; i++)
			{
			newConfigs[i] = new ConfigOption(options[i], flattenComposition(options[i]))
			}
		
		compositions = options
		}
	
	char[] normalisePath(char path[])
		{
		path = clone path
		
		for (int i = 0; i < path.arrayLength; i++)
			{
			if (path[i] == "\\") path[i] = "/"
			}
		
		return path
		}

	String[] getStandardSearchPaths(char main[])
		{
		//local
		String result[] = new String[](new String("./"))
		
		main = normalisePath(main)
		
		if (stringUtil.rfind(main, "/") != StringUtil.NOT_FOUND)
			{
			main = stringUtil.subString(main, 0, stringUtil.rfind(main, "/"))
			result = new String[](result, new String(main))
			}
		
		//System search paths
		result = new String[](result, system.getSearchPaths())
		
		//stdlib
		result = new String[](result, new String(new char[](system.getDanaHome(), "/components/")))
		
		//pre-process strings into a common format
		for (int i = 0; i < result.arrayLength; i++)
			{
			result[i] = new String(normalisePath(result[i].string))
			if (result[i].string.arrayLength > 0 && result[i].string[result[i].string.arrayLength-1] != "/")
				result[i].string = new char[](result[i].string, "/")
			}
		
		//remove duplicates
		String cd[] = result
		result = null
		for (int i = 0; i < cd.arrayLength; i++)
			{
			if (result.findFirst(String.[string], cd[i]) == null)
				{
				result = new String[](result, cd[i])
				}
			}
		
		return result
		}
	
	int findFirstIndex(Data list[], TypeField field, Data template)
		{
		for (int i = 0; i < list.arrayLength; i++)
			{
			if (list[i]:.field == template:.field)
				{
				return i
				}
			}
		
		return INT_MAX
		}
	
	char[] flattenComposition(Composition c)
		{
		//here we flatten the given composition into a specific wiring diagram string, by resolving required interfaces and their implied destinations in the cache

		//first build our list of components, with syntax for proxies; note a component may appear more than once in c.options, where it implements more than one interface
		String comps[]
		for (int i = 0; i < c.options.arrayLength; i++)
			{
			char compStr[] = c.options[i].comp

			if (c.options[i].proxyTag != null)
				{
				compStr = "{$compStr&$(c.options[i].proxyTag)&$(c.options[i].proxyParams)}"
				}

			if (comps.findFirst(String.[string], new String(compStr)) == null)
				{
				comps = new String[](comps, new String(compStr))
				}
			}
		
		//build the list of wirings, but ignore those that don't have anything to resolve against in comps (either they're native or an auto-bind)
		String wirings[]
		for (int i = 0; i < c.options.arrayLength; i++)
			{
			for (int j = 0; j < c.options[i].req.arrayLength; j++)
				{
				if (!c.options[i].req[j].isNative)
					{
					int fromIndex = findFirstIndex(comps, String.[string], new String(c.options[i].comp))
					int toIndex = findFirstIndex(c.options, InterfaceActual.[intf], new InterfaceActual(intf = c.options[i].req[j].intf))

					if (toIndex != INT_MAX)
						{
						//convert this to an index in comps, which might be different where we have repeated components entries in c.options
						char compStr[] = c.options[toIndex].comp
						
						if (c.options[toIndex].proxyTag != null)
							{
							compStr = "{$compStr&$(c.options[toIndex].proxyTag)&$(c.options[toIndex].proxyParams)}"
							}

						toIndex = findFirstIndex(comps, String.[string], new String(compStr))
						wirings = new String[](wirings, new String("$fromIndex:$toIndex:$(c.options[i].req[j].intf)"))
						}
					}
				}
			}

		return new char[](comps.implode(","), "|", wirings.implode(","))
		}
	
	bool isAnyOf(char c, char tokens[])
		{
		for (int i = 0; i < tokens.arrayLength; i ++)
			{
			if (c == tokens[i])
				return true
			}
		
		return false
		}
	
	//this version of "explode" does not parse inside braces, allowing proxy param strings to be unrestricted in their character set
	String[] explodeXB(char str[], char tokens[])
		{
		int depth = 0

		List lst = new List()
		String res[]
		String th
		
		for (int i = 0; i < str.arrayLength; i++)
			{
			if (depth == 0 && isAnyOf(str[i], tokens))
				{
				if (th != null) lst.add(th)
				th = null
				}
				else
				{
				if (th == null)
					th = new String()
				
				th.string = new char[](th.string, str[i])

				if (str[i] == "{")
					depth ++
					else if (str[i] == "}")
					depth --
				}
			}
		
		if (th != null) lst.add(th)
		
		if (lst.getLength() != 0)
			{
			int i = 0
			res = new String[lst.getLength()]
			for (String s = lst.getFirst(); s != null; s = lst.getNext())
				{
				res[i] = s
				i ++
				}
			}
		
		return res
		}
	
	/*
	{ "@description" : "Gets a list of all valid assemblies of the software system being managed, where each assembly is identified with a unique string." }
	*/
	String[] Assembly:getConfigs()
		{
		String cfg[] = new String[configs.arrayLength]
		for (int i = 0; i < cfg.arrayLength; i++)
			{
			cfg[i] = new String(configs[i].cflat)
			}
		return cfg
		}

	Interface getInterface(Component c, char name[])
		{
		for (int i = 0; i < c.interfaces.arrayLength; i++)
			{
			if (c.interfaces[i].name == name) return c.interfaces[i]
			}
		
		return null
		}
	
	Component componentLoaded(Component list, char comp[])
		{
		//check if it's a proxy
		bool proxy = false
		char proxyTag[] = null
		char proxyParams[] = null

		if (comp[0] == "{")
			{
			//we use lsplit here to minimise the character constraints on the proxy param string
			String parts[] = dana.sub(comp, 1, comp.arrayLength-2).lsplit("&")

			proxy = true
			comp = parts[0].string
			parts = parts[1].string.lsplit("&")
			proxyTag = parts[0].string
			proxyParams = parts[1].string
			}

		//find the component
		Component cw = list
		while (cw != null)
			{
			if (cw.path == comp && cw.proxy == proxy && cw.proxyTag == proxyTag && cw.proxyParams == proxyParams)
				{
				return cw
				}
			
			cw = cw.next
			}
		
		return null
		}
	
	bool isAutoBinding(char package[])
		{
		String interfaces[] = system.getAutoBindings()
		
		for (int i = 0; i < interfaces.arrayLength; i++)
			{
			if (interfaces[i].string == package) return true
			}
		
		if (package == "pal.Perception") return true
		
		return false
		}
	
	ReqIntf[] getRequiredInterfaces(Composition forc, char comp[])
		{
		InterfaceActual iact = forc.options.findFirst(InterfaceActual.[comp], new InterfaceActual(comp = comp))

		return iact.req
		}
	
	bool nativeExceptionsDisabled(char path[])
		{
		PlatformInfo pi = system.getPlatform()
		for (int j = 0; j < exceptionsDisabled.arrayLength; j++)
			{
			if (exceptionsDisabled[j].string.find("resources-ext") != StringUtil.NOT_FOUND)
				{
				for (int i = 0; i < searchPaths.arrayLength; i++)
					{
					char testPath[] = "$(searchPaths[i].string)resources-ext/$path"

					if (testPath == exceptionsDisabled[j].string)
						return true
					
					testPath = "$(searchPaths[i].string)resources-ext/$path[$(pi.osCode).$(pi.chipCode)].dnl"

					if (testPath == exceptionsDisabled[j].string)
						return true
					}
				}
			}
		
		return false
		}

	Component loadComponent(Composition forc, char comp[], ProxyLoaderInfo ploaders[])
		{
		//check if it's a proxy
		bool proxy = false
		char proxyTag[] = null
		char proxyParams[] = null

		if (comp[0] == "{")
			{
			//we use lsplit here to minimise the character constraints on the proxy param string
			String parts[] = dana.sub(comp, 1, comp.arrayLength-2).lsplit("&")

			proxy = true
			comp = parts[0].string
			parts = parts[1].string.lsplit("&")
			proxyTag = parts[0].string
			proxyParams = parts[1].string
			}
		
		Component c = new Component(comp)
		c.proxy = proxy
		c.proxyTag = proxyTag
		c.proxyParams = proxyParams
		
		if (proxy)
			{
			//this is a proxy - loading is fully delegated to a ProxyLoader from ploaders
			for (int i = 0; i < ploaders.arrayLength; i++)
				{
				if (ploaders[i].tag == proxyTag)
					{
					ProxyInstance pinstance = ploaders[i].loader.loadProxy(comp, proxyTag, proxyParams)

					if (pinstance != null)
						{
						c.com = pinstance.com
						c.proxyInterface = pinstance.providedInterface
						break
						}
						else
						{
						throw new Exception("failed to load proxy component '$comp'")
						}
					}
				}
			}
			else
			{
			//not a proxy - load it using our standard load approach
			c.com = loader.load(comp)

			if (exceptionsDisabled.findFirst(String.[string], new String(comp)) != null)
				{
				//out.println("disable exceptions on $(comp)")
				c.com.enableExceptions(false)
				}

			if (c.com == null)
				{
				throw new Exception("failed to load component '$comp'")
				}
			
			//c.lastModified = fileSystem.getInfo(comp).modified
			
			ReqIntf ris[] = getRequiredInterfaces(forc, comp)
			for (int i = 0; i < ris.arrayLength; i++)
				{
				Interface iq = new Interface(ris[i].intf)
				c.interfaces = new Interface[](c.interfaces, iq)
				
				if (isAutoBinding(ris[i].intf))
					{
					c.completeWirings ++
					}
				
				if (ris[i].isNative)
					{
					IDC ncom = nLoader.load(ris[i].intf)
					if (nativeExceptionsDisabled(ris[i].intf))
						{
						//out.println("disable exceptions on $(ris[i].intf)")
						ncom.enableExceptions(false)
						}
					c.com.wire(ris[i].intf, ncom, ris[i].intf)
					c.completeWirings ++
					}
				
				if (ris[i].intf == "pal.Perception")
					{
					c.com.wire("pal.Perception", perception, "pal.Perception")
					}
				}
			
			if (c.completeWirings == c.interfaces.arrayLength)
				{
				//service start?
				if (c.com.hasProvides("Service"))
					{
					Service svc = new Service() from c.com
					svc.start()
					}
				}
			}
		
		return c
		}
	
	//this function wires up a component's dependency, but makes sure that dependency itself is fully wired first
	int forwardWire(String comps[], String wirings[], int index)
		{
		int wiringsChanged = 0

		//out.println("fwd wire $(wirings[index].string)")
		
		String parts[] = wirings[index].string.lsplit(":")
		int fromIndex = parts[0].string.intFromString()
		parts = parts[1].string.lsplit(":")
		int toIndex = parts[0].string.intFromString()
		char intfName[] = parts[1].string
		
		wirings[index] = null
		
		// - wire everything *ahead* of this first
		for (int i = 0; i < wirings.arrayLength; i++)
			{
			if (wirings[i] != null)
				{
				parts = wirings[i].string.lsplit(":")
				int fromIndexB = parts[0].string.intFromString()
				parts = parts[1].string.lsplit(":")
				int toIndexB = parts[0].string.intFromString()
				char intfNameB[] = parts[1].string

				if (fromIndexB == toIndex)
					wiringsChanged += forwardWire(comps, wirings, i)
				}
			}
		
		// - and now do this wiring
		Component fromComponent = componentLoaded(components, comps[fromIndex].string)
		Component toComponent = componentLoaded(components, comps[toIndex].string)
		
		Interface iq = getInterface(fromComponent, intfName)

		char toIntfName[] = intfName
		if (toComponent.proxyInterface != null) toIntfName = toComponent.proxyInterface

		if (iq == null)
			{
			out.println("null IQ: $(comps[fromIndex].string) / $intfName")
			}
		
		if (iq.currentWiring == null)
			{
			if (verbose) out.println(" -- wiring $(fromComponent.path) :: $intfName >> $(toComponent.path)")

			//it's new, so just connect it
			fromComponent.com.wire(intfName, toComponent.com, toIntfName)
			fromComponent.completeWirings ++

			//check if we need to inject a proxy from an intercept rule
			Interceptor nic = interceptors.findFirst(Interceptor.[intf], new Interceptor(intfName))
			
			if (nic != null)
				{
				ActiveIntercept prxInstance = loadIntercept(nic, fromComponent.com, toComponent.com, intfName, fromComponent.path)
				iq.intercept = prxInstance
				}
			
			wiringsChanged ++
			
			if (fromComponent.completeWirings == fromComponent.interfaces.arrayLength)
				{
				//service start?
				if (fromComponent.com.hasProvides("Service"))
					{
					//out.println(" -- starting service on '$(fromComponent.path)'")
					Service svc = new Service() from fromComponent.com
					svc.start()
					}
				}
			}
			else if (iq.currentWiring != toComponent.path || iq.wiringProxyInfo != "$(toComponent.proxyTag):$(toComponent.proxyParams)")
			{
			//it's already wired to something (and something different), so adapt it
			
			if (verbose) out.println(" -- adapting $(fromComponent.path) :: $intfName >> $(toComponent.path)")

			//check if there's a proxy on this interface, and adapt the proxy's RI if so
			if (iq.intercept == null)
				adaptAPI.adaptRequiredInterface(fromComponent.com, intfName, toComponent.com, toIntfName)
				else
				adaptAPI.adaptRequiredInterface(iq.intercept.com, iq.intercept.reqIntf, toComponent.com, intfName)
			
			wiringsChanged ++
			}
		
		iq.currentWiring = toComponent.path
		iq.wiringProxyInfo = "$(toComponent.proxyTag):$(toComponent.proxyParams)"
		
		return wiringsChanged
		}
	
	void removeUnusedComponents(ProxyLoaderInfo loaders[])
		{
		Component prev = null
		Component cw = components
		while (cw != null)
			{
			if (!cw.used)
				{
				Component rem = cw
				if (prev == null)
					{
					components = cw.next
					}
					else
					{
					prev.next = cw.next
					}
				
				if (rem.proxy)
					{
					for (int i = 0; i < loaders.arrayLength; i++)
						{
						if (loaders[i].tag == rem.proxyTag)
							{
							loaders[i].loader.unloadProxy(rem.com, rem.path, rem.proxyTag, rem.proxyParams)
							break
							}
						}
					}
				}
				else
				{
				prev = cw
				}
			
			cw = cw.next
			}
		}
	
	void setComponentUse(Component lst, bool val)
		{
		Component cw = lst
		while (cw != null)
			{
			cw.used = val
			cw = cw.next
			}
		}
	
	bool Assembly:setConfig(char config[], opt ProxyLoaderInfo loaders[])
		{
		//check if app has exited
		if (appFinished) throw new Exception("Hosted application has completed execution")

		bool adaptOK = true

		int foundConfig = 0
		
		mutex(configLock)
			{
			//validate configuration
			bool found
			for (int i = 0; i < configs.arrayLength; i++)
				{
				if (config == configs[i].cflat)
					{
					found = true
					foundConfig = i
					break
					}
				}
			
			if (!found) throw new Exception("Configuration not known")
			
			//adapt to the configuration
			//if (verbose) out.println(" [setting config to '$config']")
			
			// - walk through all wirings of the new configuration and apply them (using forwardWire to satisfy forward dependencies first)
			String els[] = stringUtil.rsplit(config, "|")
			String comps[] = explodeXB(els[0].string, ",")
			String wirings[] = null
			if (els.arrayLength == 2)
				wirings = clone stringUtil.explode(els[1].string, ",")
			
			//mark all loaded components as not-used
			setComponentUse(components, false)
			
			//load all components that are not yet loaded
			bool loadFail = false
			Component newComponents = null
			Component end = null
			
			for (int j = 0; j < comps.arrayLength; j++)
				{
				Component ldc = null
				if ((ldc = componentLoaded(components, comps[j].string)) == null)
					{
					if (componentLoaded(newComponents, comps[j].string) == null)
						{
						Component c = loadComponent(configs[foundConfig].c, comps[j].string, loaders)

						if (c != null)
							{
							c.used = true
							
							if (end == null) end = c
							
							c.next = newComponents
							newComponents = c
							}
							else
							{
							loadFail = true
							break
							}
						}
					}
					else
					{
					ldc.used = true
					}
				}
			
			if (mainComponent == null)
				{
				mainComponent = end
				}
			
			//if we experienced a failure during loading, mark all original components as used, and all new components as not-used
			if (loadFail)
				{
				setComponentUse(components, true)
				setComponentUse(newComponents, false)
				
				adaptOK = false
				}
			
			//add new components into the in-use list
			// (note, this list can be empty if we're setting to the same config)
			if (end != null)
				{
				end.next = components
				components = newComponents
				}
			
			//perform the adaptation, if everything loaded OK
			if (!loadFail)
				{
				currentConfig = foundConfig

				//update wirings
				for (int i = 0; i < wirings.arrayLength; i++)
					{
					if (wirings[i] != null)
						{
						forwardWire(comps, wirings, i)
						}
					}
				}
			
			//remove all components that aren't used
			removeUnusedComponents(loaders)
			}
		
		return adaptOK
		}
	
	char[] Assembly:getConfig()
		{
		return configs[currentConfig].cflat
		}
	
	bool updConfig(char newConfig[], ConfigOption coption, char updComponent[], opt ProxyLoaderInfo loaders[])
		{
		mutex(configLock)
			{
			//adapt to the configuration
			//if (verbose) out.println(" [setting config to '$config']")
			
			// - walk through all wirings of the new configuration and apply them (using forwardWire to satisfy forward dependencies first)
			String els[] = stringUtil.rsplit(newConfig, "|")
			String comps[] = explodeXB(els[0].string, ",")
			String wirings[] = null
			if (els.arrayLength == 2)
				wirings = clone stringUtil.explode(els[1].string, ",")
			
			//mark all loaded components as not-used
			Component cw = components
			while (cw != null)
				{
				cw.used = false
				cw = cw.next
				}
			
			//load all components that are not yet loaded
			Component newComponents = null
			Component end = null

			IDC oldVersion = null
			
			for (int j = 0; j < comps.arrayLength; j++)
				{
				Component ldc = null
				if ((ldc = componentLoaded(components, comps[j].string)) == null)
					{
					if (componentLoaded(newComponents, comps[j].string) == null)
						{
						Component c = loadComponent(coption.c, comps[j].string, loaders)
						c.used = true
						
						if (end == null) end = c
						
						c.next = newComponents
						newComponents = c
						}
					}
					else
					{
					if (ldc.path == updComponent)
						{
						//load the new version, clear its wirings, and save the old version's IDC for later
						oldVersion = ldc.com

						Component ndc = loadComponent(coption.c, comps[j].string, loaders)

						ldc.com = ndc.com
						ldc.interfaces = ndc.interfaces
						ldc.completeWirings = ndc.completeWirings
						ldc.serviceStarted = ndc.serviceStarted
						}

					ldc.used = true
					}
				}
			
			if (mainComponent == null)
				{
				mainComponent = end
				}
			
			//add new components into the in-use list
			// (note, this list can be empty if we're setting to the same config)
			if (end != null)
				{
				end.next = components
				components = newComponents
				}
			
			//update wirings
			// - at this point we need to be using a slightly different "comps" list, where updComponent has a re-loaded version
			// - so we save the old version in a tmp variable above, so the comps list here has the new thing (but that thing has an empty set of wirings)
			// - after this, we need to work through wirings to find anything that points at the original updComponent's IDC, and adapt those wirings to updComponent's new IDC
			String adaptWirings[] = clone wirings
			for (int i = 0; i < wirings.arrayLength; i++)
				{
				if (wirings[i] != null)
					{
					forwardWire(comps, wirings, i)
					}
				}
			
			//now adapt all wirings that went into this component, since they'll still be pointing at the old one
			for (int i = 0; i < adaptWirings.arrayLength; i++)
				{
				String parts[] = adaptWirings[i].string.lsplit(":")
				int fromIndex = parts[0].string.intFromString()
				parts = parts[1].string.lsplit(":")
				int toIndex = parts[0].string.intFromString()
				char intfName[] = parts[1].string

				Component fromComponent = componentLoaded(components, comps[fromIndex].string)
				Component toComponent = componentLoaded(components, comps[toIndex].string)

				if (toComponent.path == updComponent)
					{
					//adapt this to our new version (could do an extra check to see if it's already pointing at our new version, but it doesn't really matter)
					adaptAPI.adaptRequiredInterface(fromComponent.com, intfName, toComponent.com)
					}
				}
			
			//remove all components that aren't used
			removeUnusedComponents(loaders)
			}
		
		return true
		}
	
	void launchMain(IDC class, char launchPath[])
		{
		myApp = new App() from class
		myApp.setSourcePath(launchPath)
		exitCode = myApp.main(subParams)
		appFinished = true
		appStarted = false
		}

	/*
	{ "@description" : "Launches the main method of the program, blocking until that function completes." }
	*/
	bool Assembly:runApp(char launchPath[])
		{
		mutex(configLock)
			{
			if (mainComponent == null)
				{
				throw new Exception("no main component loaded - use setConfig first")
				}
			
			if (!appStarted)
				{
				appStarted = true
				}
				else
				{
				throw new Exception("app is already running")
				}
			}
		
		launchMain(mainComponent.com, launchPath)
		
		return true
		}
	
	String[] addCompositions(Composition newOpt[])
		{
		ConfigOption newConfigs[] = new ConfigOption[newOpt.arrayLength]

		String result[] = new String[newOpt.arrayLength]

		for (int i = 0; i < newConfigs.arrayLength; i++)
			{
			newConfigs[i] = new ConfigOption(newOpt[i], flattenComposition(newOpt[i]))
			result[i] = new String(newConfigs[i].cflat)
			}
		
		configs = new ConfigOption[](configs, newConfigs)
		compositions = new Composition[](compositions, newOpt)

		return result
		}
	
	String[] remCompositions(Composition toRemove[])
		{
		Composition newCompositions[] = new Composition[compositions.arrayLength - toRemove.arrayLength]
		ConfigOption newConfigs[] = new ConfigOption[configs.arrayLength - toRemove.arrayLength]

		String result[] = new String[toRemove.arrayLength]

		int j = 0
		int k = 0
		for (int i = 0; i < configs.arrayLength; i++)
			{
			if (!isRefIn(configs[i].c, toRemove))
				{
				newConfigs[j] = configs[i]
				newCompositions[j] = configs[i].c
				j ++
				}
				else
				{
				result[k] = new String(flattenComposition(toRemove[k]))
				k ++
				}
			}
		
		configs = newConfigs
		compositions = newCompositions
		
		return result
		}

	String[] Assembly:addComponent(char path[])
		{
		Composition newOpt[] = builder.addComponent(path)

		return addCompositions(newOpt)
		}
	
	ConfigOption getBestConfigMatch(char matchWith[], opt char withComponent[])
		{
		//find the best match for "matchWith" in "fromList"
		// - if withComponent has been provided, the indicated component must appear within the matched config

		int comDelta = 0
		//int wiringDelta = 0
		int bestMatchIndex = 0

		String compsA[] = explodeXB(matchWith.rsplit("|")[0].string, ",")

		ConfigOption fromList[] = configs

		for (int i = 0; i < fromList.arrayLength; i++)
			{
			//count how many components don't match (how many wirings don't match??)
			
			String compsB[] = explodeXB(fromList[i].cflat.rsplit("|")[0].string, ",")

			int thisComDelta = 0

			for (int j = 0; j < compsA.arrayLength; j++)
				{
				if (compsB.findFirst(String.[string], compsA[j]) == null)
					{
					thisComDelta ++
					}
				}
			
			for (int j = 0; j < compsB.arrayLength; j++)
				{
				if (compsA.findFirst(String.[string], compsB[j]) == null)
					{
					thisComDelta ++
					}
				}
			
			if (thisComDelta < comDelta)
				{
				comDelta = thisComDelta
				bestMatchIndex = i
				}
			}

		return fromList[bestMatchIndex]
		}
	
	UpdateInfo Assembly:updComponent(char path[], opt ProxyLoaderInfo loaders[])
		{
		//call updComponent in optionBuilder; this will tell us if there are any changes to the composition list
		// - if there are no changes, check if our current composition uses this component, and "adapt" to it if so
		// - otherwise, if changes, check if our current composition uses this component, and locate the nearest-matching new composition to adapt to
		
		bool inUse = false

		Composition c = configs[currentConfig].c

		for (int i = 0; i < c.options.arrayLength; i++)
			{
			if (c.options[i].comp == path)
				{
				inUse = true
				break
				}
			}
		
		CompositionUpdate cupdate = builder.updComponent(compositions, path)
		
		if (inUse)
			{
			//here we need to adapt our current in-use composition to use the new version of the component
			// - we may ALSO need to adapt the entire composition to a closest-match if there's a structural change (we detect a structure change by scanning through cupdate.remOptions to check if you current composition is listed there)

			//we use a custom function here, updConfig(), to handle the process of changing to the new config while also hot-swapping the existing component
			// - it'll still do the "load components" step, but then with forward-wire anything not in the current config, and will load a copy of the component to be updated and forward-wire that
			// - then we'll do the hot-swap for anything that wires into the component to be updated
			// - then we switch that component IDC into its cache record, and update our current config...

			char configNow[] = configs[currentConfig].cflat

			String remList[] = remCompositions(cupdate.remOptions)
			String addList[] = addCompositions(cupdate.addOptions)

			char x[] = null
			ConfigOption xConfig = null

			if (cupdate.remOptions != null)
				{
				//our current config may no longer be available, so find the next-best match within addOptions
				ConfigOption bestMatch = getBestConfigMatch(configNow, path)

				x = bestMatch.cflat
				xConfig = bestMatch

				//update currentConfig by finding the index of cflat in our new list
				for (int i = 0; i < configs.arrayLength; i++)
					{
					if (x == configs[i].cflat)
						{
						currentConfig = i
						break
						}
					}
				}
				else
				{
				//our current config must still be available
				x = configs[currentConfig].cflat
				xConfig = configs[currentConfig]
				}

			updConfig(x, xConfig, path, loaders)

			//return the two list deltas, plus report the composition we're now in
			return new UpdateInfo(x, remList, addList)
			}
			else
			{
			//just update our composition list
			String remList[] = remCompositions(cupdate.remOptions)
			String addList[] = addCompositions(cupdate.addOptions)

			//return the two list deltas, plus report that we're still in the same composition
			return new UpdateInfo(configs[currentConfig].cflat, remList, addList)
			}

		return null
		}
	
	bool isRefIn(Data ref, Data array[])
		{
		for (int i = 0; i < array.arrayLength; i++)
			{
			if (array[i] === ref) return true
			}
		
		return false
		}
	
	String[] Assembly:remComponent(char path[])
		{
		//check if the currently-selected composition is using the component (and fail if so)
		// - otherwise just remove it, and report the removed compositions
		Composition c = configs[currentConfig].c

		for (int i = 0; i < c.options.arrayLength; i++)
			{
			if (c.options[i].comp == path)
				throw new Exception("attempt to remove a component that is currently in-use")
			}
		
		Composition toRemove[] = builder.remComponent(compositions, path)

		return remCompositions(toRemove)
		}

	String[] Assembly:addProxy(char forInterface[], char proxyComponent[], char tag[], char parameters[])
		{
		Composition newOpt[] = builder.addProxy(forInterface, proxyComponent, tag, parameters)

		return addCompositions(newOpt)
		}
	
	String[] Assembly:remProxy(char forInterface[], char proxyComponent[], char tag[], char parameters[])
		{
		//check if the currently-selected composition is using the proxy (and fail if so)
		// - otherwise just remove it, and report the removed compositions

		Composition c = configs[currentConfig].c

		for (int i = 0; i < c.options.arrayLength; i++)
			{
			if (c.options[i].intf == forInterface && c.options[i].comp == proxyComponent && c.options[i].proxyTag == tag && c.options[i].proxyParams == parameters)
				throw new Exception("attempt to remove a component that is currently in-use")
			}
		
		Composition toRemove[] = builder.remProxy(compositions, forInterface, proxyComponent, tag, parameters)

		return remCompositions(toRemove)
		}
	
	ActiveIntercept loadIntercept(Interceptor nic, IDC wireFrom, IDC wireTo, char wireToIntf[], char site[])
		{
		IDC prx = rLoader.load(nic.cmp, new String[](new String("lang.Morph"), new String("pal.Perception"))).mainComponent
		//IDC prx = loader.load(nic.cmp)
		prx.wire(nic.reqIntf, wireTo, wireToIntf)
		interceptAPI.insertIntercept(wireFrom, wireToIntf, prx, nic.proIntf, nic.reqIntf)
		
		//check if the proxy component is expecting additional info, via the ProxyInfo interface
		if (prx.hasProvides("pal.ProxyInfo"))
			{
			ProxyInfo pinfo = new ProxyInfo() from prx
			pinfo.setInjectSite(site, wireToIntf)
			}
		
		//check if the proxy component is expected to be connected to Perception
		if (prx.hasRequires("pal.Perception"))
			{
			prx.wire("pal.Perception", perception, "pal.Perception")
			}
		
		return new ActiveIntercept(nic.cmp, prx, nic.reqIntf)
		}

	bool Assembly:addIntercept(char intf[], char cmp[])
		{
		//load copies of the given interceptor cmp at each location required interface "intf" appears
		// - if interceptors already exist at that location, this one is added to the end of the chain
		
		mutex(configLock)
			{
			//this implementation currently supports only a single interceptor per interface
			if (interceptors.findFirst(Interceptor.[intf], new Interceptor(intf, cmp)) != null)
				throw new Exception("an interceptor on $intf is already registered")
			
			if (!fileSystem.exists(cmp))
				{
				throw new Exception("intercept component not found at '$cmp'")
				}
			
			Interceptor nic = new Interceptor(intf, cmp)
			
			//detect which interfaces the proxy has (generic lang.Proxy/Morph, or specific)
			IDC prxCheck = loader.load(cmp)
			
			if (prxCheck.hasProvides("lang.Proxy"))
				nic.proIntf = "lang.Proxy"
				else if (prxCheck.hasProvides(intf))
				nic.proIntf = intf
				else
				throw new Exception("intercept component does not provide a proxy-able interface for '$intf'")
			
			if (prxCheck.hasRequires("lang.Morph"))
				nic.reqIntf = "lang.Morph"
				else if (prxCheck.hasRequires(intf))
				nic.reqIntf = intf
				else
				throw new Exception("intercept component does not require a proxy-able interface for '$intf'")
			
			interceptors = new Interceptor[](interceptors, nic)
			
			//add this intercept to any interfaces that match
			for (Component cw = components; cw != null; cw = cw.next)
				{
				for (int i = 0; i < cw.interfaces.arrayLength; i++)
					{
					if (cw.interfaces[i].name == intf)
						{
						//load a copy of the interceptor cmp, and inject it
						Component wireTo = componentLoaded(components, cw.interfaces[i].currentWiring)
						ActiveIntercept prxInst = loadIntercept(nic, cw.com, wireTo.com, cw.interfaces[i].name, cw.path)
						cw.interfaces[i].intercept = prxInst
						}
					}
				}
			}
		
		return true
		}
	
	bool Assembly:remIntercept(char intf[], char cmp[])
		{
		//remove the given interceptor cmp at each location that the required interface "intf" appears
		// - if multiple interceptors exist, the one closest to the end of the chain is removed
		
		mutex(configLock)
			{
			//remove the intercept rule
			Interceptor nic = interceptors.findFirst(Interceptor.[intf], new Interceptor(intf, cmp))
			
			if (nic == null)
				throw new Exception("interceptor $intf/$cmp is not registered")
			
			Interceptor nArray[] = new Interceptor[interceptors.arrayLength-1]
			int j = 0
			for (int i = 0; i < interceptors.arrayLength; i++)
				{
				if (interceptors[i] !== nic)
					{
					nArray[j] = interceptors[i]
					j ++
					}
				}
			
			interceptors = nArray
			
			//remove each specific intercept
			for (Component cw = components; cw != null; cw = cw.next)
				{
				for (int i = 0; i < cw.interfaces.arrayLength; i++)
					{
					if (cw.interfaces[i].name == intf && cw.interfaces[i].intercept != null)
						{
						Component wireTo = componentLoaded(components, cw.interfaces[i].currentWiring)
						interceptAPI.removeIntercept(cw.com, cw.interfaces[i].name, wireTo.com, intf, nic.reqIntf)
						cw.interfaces[i].intercept = null
						}
					}
				}
			}
		
		return false
		}
	
	InterceptInfo[] Assembly:getIntercepts()
		{
		InterceptInfo result[] = new InterceptInfo[interceptors.arrayLength]
		
		for (int i = 0; i < result.arrayLength; i++)
			{
			result[i] = new InterceptInfo(interceptors[i].intf, interceptors[i].cmp)
			}
		
		return result
		}
	
	void Assembly:disableExceptions(String paths[])
		{
		exceptionsDisabled = paths
		}

	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n pal.Assembly -m "reason for update" -u yourUsername
Version 1 (this version) by barry
Notes for this version: Standard Library Initialisation