private ChannelNode eatChannelReference(boolean tapAllowed) {
		Token firstToken = nextToken();
		if (!firstToken.isIdentifier() || !isLegalChannelPrefix(firstToken.data)) {
			raiseException(firstToken.startpos,
					tapAllowed ? XDDSLMessages.EXPECTED_CHANNEL_PREFIX_QUEUE_TOPIC_TAP
							: XDDSLMessages.EXPECTED_CHANNEL_PREFIX_QUEUE_TOPIC,
					toString(firstToken));
		}
		List<Token> channelScopeComponents = new ArrayList<Token>();
		channelScopeComponents.add(firstToken);
		while (peekToken(TokenKind.COLON)) {
			if (!isNextTokenAdjacent()) {
				raiseException(peekToken().startpos, XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION);
			}
			nextToken(); // skip colon
			if (!isNextTokenAdjacent()) {
				raiseException(peekToken().startpos, XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION);
			}
			channelScopeComponents.add(eatToken(TokenKind.IDENTIFIER));
		}
		List<Token> channelReferenceComponents = new ArrayList<Token>();
		if (tapAllowed && firstToken.data.equalsIgnoreCase("tap")) {
			if (peekToken(TokenKind.DOT)) {
				if (channelScopeComponents.size() < 3) {
					raiseException(firstToken.startpos, XDDSLMessages.TAP_NEEDS_THREE_COMPONENTS);
				}
				String tokenData = channelScopeComponents.get(1).data;
				// for Stream, tap:stream:XXX - the channel name is always indexed
				// for Job, tap:job:XXX - the channel name can have "." in case of job notification channels
				if (!tokenData.equalsIgnoreCase("stream") && !tokenData.equalsIgnoreCase("job")) {
					raiseException(peekToken().startpos, XDDSLMessages.ONLY_A_TAP_ON_A_STREAM_OR_JOB_CAN_BE_INDEXED);
				}
			}
			while (peekToken(TokenKind.DOT)) {
				if (!isNextTokenAdjacent()) {
					raiseException(peekToken().startpos, XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION);
				}
				nextToken(); // skip dot
				if (!isNextTokenAdjacent()) {
					raiseException(peekToken().startpos, XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION);
				}
				channelReferenceComponents.add(eatToken(TokenKind.IDENTIFIER));
			}
		}
		else if (peekToken(TokenKind.DOT)) {
			if (tapAllowed) {
				raiseException(peekToken().startpos, XDDSLMessages.ONLY_A_TAP_ON_A_STREAM_OR_JOB_CAN_BE_INDEXED);
			}
			else {
				raiseException(peekToken().startpos, XDDSLMessages.CHANNEL_INDEXING_NOT_ALLOWED);
			}
		}
		// Verify the structure:
		ChannelType channelType = null;
		if (firstToken.data.equalsIgnoreCase("tap")) {
			// tap:stream:XXX.YYY
			// tap:job:XXX
			// tap:queue:XXX
			// tap:topic:XXX
			if (channelScopeComponents.size() < 3) {
				raiseException(firstToken.startpos, XDDSLMessages.TAP_NEEDS_THREE_COMPONENTS);
			}
			Token tappingToken = channelScopeComponents.get(1);
			String tapping = tappingToken.data.toLowerCase();
			channelScopeComponents.remove(0); // remove 'tap'
			if (tapping.equals("stream")) {
				channelType = ChannelType.TAP_STREAM;
			}
			else if (tapping.equals("job")) {
				channelType = ChannelType.TAP_JOB;
			}
			else if (tapping.equals("queue")) {
				channelType = ChannelType.TAP_QUEUE;
			}
			else if (tapping.equals("topic")) {
				channelType = ChannelType.TAP_TOPIC;
			}
			else {
				raiseException(tappingToken.startpos, XDDSLMessages.NOT_ALLOWED_TO_TAP_THAT, tappingToken.data);
			}
		}
		else {
			// queue:XXX
			// topic:XXX
			if (firstToken.data.equalsIgnoreCase("queue")) {
				channelType = ChannelType.QUEUE;
			}
			else if (firstToken.data.equalsIgnoreCase("topic")) {
				channelType = ChannelType.TOPIC;
			}
			// TODO: DT not sure if this is the best way to handle
			// StreamConfigParserTests.substreamsWithSourceChannels()
			if (channelScopeComponents.size() >= 3) {
				channelScopeComponents.remove(0);
			}
		}
		int endpos = channelScopeComponents.get(channelScopeComponents.size() - 1).endpos;
		if (!channelReferenceComponents.isEmpty()) {
			endpos = channelReferenceComponents.get(channelReferenceComponents.size() - 1).endpos;
		}
		return new ChannelNode(channelType, firstToken.startpos, endpos, tokenListToStringList(channelScopeComponents),
				tokenListToStringList(channelReferenceComponents));
	}
Parse a stream definition.

@return the AST for the parsed stream
@throws StreamDefinitionException
spring-xd-dirt/src/main/java/org/springframework/xd/dirt/stream/dsl/StreamConfigParser.java