/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtext.conversion.impl;

import java.util.Set;

import org.antlr.runtime.Token;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.IGrammarAccess;
import org.eclipse.xtext.conversion.ValueConverterException;
import org.eclipse.xtext.nodemodel.INode;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;

/**
 * Abstract implementation of a value converter that escapes a string
 * based on a set of values. The strategy to compute this values and how the 
 * the string is found in this set is the responsibility of the concrete 
 * implementation. A common use-case is to collect the keywords in a grammar
 * and match the string against these keywords or to use some ignore-case matching
 * algorithm against the list of normalized keywords.
 *  
 * @author Sebastian Zarnekow - Initial contribution and API
 */
@ImplementedBy(IDValueConverter.class)
public abstract class AbstractIDValueConverter extends AbstractLexerBasedConverter<String> {

	@Inject
	private IGrammarAccess grammarAccess;
	
	private Set<String> valuesToEscape;

	protected AbstractIDValueConverter() {
		super();
	}
	
	protected abstract Set<String> computeValuesToEscape(Grammar grammar);
	
	protected Set<String> getValuesToEscape() {
		if (valuesToEscape == null)
			valuesToEscape = computeValuesToEscape(grammarAccess.getGrammar());
		return valuesToEscape;
	}

	protected abstract boolean mustEscape(String value);
	
	@Override
	protected String toEscapedString(String value) {
		if (mustEscape(value))
			return "^" + value;
		return value;
	}
	
	@Override
	protected void assertValidValue(String value) {
		super.assertValidValue(value);
		if (value.length() == 0) {
			throw new ValueConverterException(getRuleName() + " may not be empty.", null, null);
		}
	}
	
	@Override
	protected ValueConverterException createTokenContentMismatchException(String value, String escapedString, Token token) {
		Set<Character> invalidChars = collectInvalidCharacters(value);
		if (invalidChars != null) {
			String chars = Joiner.on(", ").join(Iterables.transform(invalidChars, new Function<Character, String>() {
				public String apply(Character from) {
					return "'" + from + "' (0x" + Integer.toHexString(from) + ")";
				}
			}));
			return new ValueConverterException("ID '" + value + "' contains invalid characters: " + chars, null, null);
		}
		return super.createTokenContentMismatchException(value, escapedString, token);
	}

	protected Set<Character> collectInvalidCharacters(String value) {
		Set<Character> result = null;
		for (int i = 0; i < value.length(); i++) {
			char c = value.charAt(i);
			if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
				continue;
			if (i != 0 && c >= '0' && c <= '9')
				continue;
			if (c == '_')
				continue;
			if (result == null)
				result = Sets.newHashSet();
			result.add(c);
		}
		return result;
	}
	
	public String toValue(String string, INode node) {
		if (string == null)
			return null;
		return string.startsWith("^") ? string.substring(1) : string;
	}
	
	protected IGrammarAccess getGrammarAccess() {
		return grammarAccess;
	}
	
	public void setGrammarAccess(IGrammarAccess grammarAccess) {
		this.grammarAccess = grammarAccess;
	}
}
@@ -2652,16 +2652,296 @@
 ull) %7B%0A%09
+%09%09return new ValueConverterException(getInvalidCharactersMessage(value, invalidChars), null, null);%0A%09%09%7D%0A%09%09return super.createTokenContentMismatchException(value, escapedString, token);%0A%09%7D%0A%0A%09protected String getInvalidCharactersMessage(String value, Set%3CCharacter%3E invalidChars) %7B%0A
 %09%09String
@@ -3043,17 +3043,16 @@
 () %7B%0A%09%09%09
-%09
 public S
@@ -3081,17 +3081,16 @@
 from) %7B%0A
-%09
 %09%09%09%09retu
@@ -3152,23 +3152,20 @@
 )%22;%0A
-%09
 %09%09%09%7D%0A%09%09
-%09
 %7D));%0A
-%09
 %09%09re
@@ -3173,36 +3173,8 @@
 urn 
-new ValueConverterException(
 %22ID 
@@ -3231,111 +3231,14 @@
 hars
-, null, null)
 ;%0A
-%09
 %09%7D%0A%09
-%09return super.createTokenContentMismatchException(value, escapedString, token);%0A%09%7D%0A
 %0A%09pr

Enable the full range of Java identifiers for Xbase https://bugs.eclipse.org/bugs/show_bug.cgi?id=326044