private List<ModuleDescriptor> buildModuleDescriptors(String name, String rawDSL, ParsingContext parsingContext,
			StreamNode stream, List<Exception> errorAccumulator) {
		Deque<ModuleDescriptor.Builder> builders = new LinkedList<>();

		List<ModuleNode> moduleNodes = stream.getModuleNodes();
		for (int m = moduleNodes.size() - 1; m >= 0; m--) {
			ModuleNode moduleNode = moduleNodes.get(m);
			ModuleDescriptor.Builder builder =
					new ModuleDescriptor.Builder()
							.setGroup(name)
							.setModuleName(moduleNode.getName())
							.setModuleLabel(moduleNode.getLabelName())
							.setIndex(m);
			if (moduleNode.hasArguments()) {
				ArgumentNode[] arguments = moduleNode.getArguments();
				for (ArgumentNode argument : arguments) {
					builder.setParameter(argument.getName(), argument.getValue());
				}
			}
			builders.add(builder);
		}

		SourceChannelNode sourceChannel = stream.getSourceChannelNode();
		if (sourceChannel != null) {
			if (parsingContext.supportsNamedChannels()) {
				builders.getLast().setSourceChannelName(sourceChannel.getChannelName());
			}
			else {
				throw new StreamDefinitionException(rawDSL, sourceChannel.getStartPos(),
						NAMED_CHANNELS_UNSUPPORTED_HERE);
			}
		}

		SinkChannelNode sinkChannel = stream.getSinkChannelNode();
		if (sinkChannel != null) {
			if (parsingContext.supportsNamedChannels()) {
				builders.getFirst().setSinkChannelName(sinkChannel.getChannelName());
			}
			else {
				throw new StreamDefinitionException(rawDSL, sinkChannel.getChannelNode().getStartPos(),
						NAMED_CHANNELS_UNSUPPORTED_HERE);
			}
		}

		// Now that we know about source and sink channel names,
		// do a second pass to determine type. Also convert to composites.
		// And while we're at it (and type is known), validate module name and options
		List<ModuleDescriptor> result = new ArrayList<ModuleDescriptor>(builders.size());
		for (ModuleDescriptor.Builder builder : builders) {
			ModuleType moduleType;
			try {
				moduleType = determineType(builder, builders.size() - 1, parsingContext);
			}
			catch (NoSuchModuleException nsme) {
				if (errorAccumulator != null) {
					errorAccumulator.add(nsme);
					// 'processor' below effectively indicates 'unknown' - a caller passing an
					// error accumulator should be aware that this can happen (the accumulator
					// will contain the exception that indicates it did)
					moduleType = ModuleType.processor;
				}
				else {
					throw nsme;
				}
			}
			builder.setType(moduleType);

			ModuleDefinition moduleDefinition = moduleRegistry.findDefinition(builder.getModuleName(),
					builder.getType());
			if (moduleDefinition != null) {
				builder.setModuleDefinition(moduleDefinition);
				ModuleOptionsMetadata optionsMetadata = moduleOptionsMetadataResolver.resolve(moduleDefinition);
				if (parsingContext.shouldBindAndValidate()) {
					try {
						optionsMetadata.interpolate(builder.getParameters());
					}
					catch (BindException e) {
						ModuleConfigurationException mce = ModuleConfigurationException.fromBindException(
								builder.getModuleName(),
								builder.getType(), e);
						if (errorAccumulator != null) {
							errorAccumulator.add(mce);
						}
						else {
							throw mce;
						}
					}
				}
			}

			result.add(buildModuleDescriptor(builder));
		}
		return result;
	}
Build a list of ModuleDescriptors out of a parsed StreamNode. If an errorAccumulator
is passed then the method will not exit on the first exception that occurs, instead
it will record the problems in the accumulator and attempt to continue processing.
@param name the name of the definition unit
@param rawDSL the raw DSL text of the definition
@param parsingContext the context in which parsing happens
@param stream the AST construct representing the definition
@param errorAccumulator accumulates exceptions that occur during validation
spring-xd-dirt/src/main/java/org/springframework/xd/dirt/stream/XDStreamParser.java