/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.sarl.lang.mwe2.keywords;
import static org.eclipse.xtext.EcoreUtil2.eAllContentsAsList;
import static org.eclipse.xtext.EcoreUtil2.typeSelect;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import com.google.common.base.Objects;
import com.google.inject.Inject;
import com.google.inject.Injector;
import org.apache.log4j.Logger;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.EnumRule;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.xbase.compiler.JavaKeywords;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.ListExtensions;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment;
import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming;
import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions;
import org.eclipse.xtext.xtext.generator.model.FileAccessFactory;
import org.eclipse.xtext.xtext.generator.model.JavaFileAccess;
import org.eclipse.xtext.xtext.generator.model.TypeReference;
/**
* A {@link AbstractXtextGeneratorFragment} that enables to create a code builder for the generated language.
*
* <p>The generated builder could be used for helping to create Eobjects from scratch
* (in ui wizard for example).
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public class GrammarKeywordAccessFragment2 extends AbstractXtextGeneratorFragment {
private static final Logger LOG = Logger.getLogger(GrammarKeywordAccessFragment2.class);
@Inject
private FileAccessFactory fileAccessFactory;
@Inject
private XtextGeneratorNaming naming;
@Inject
private GrammarAccessExtensions grammarAccessExtensions;
@Inject
private GrammarKeywordAccessConfig configuration;
@Inject
private JavaKeywords javaKeywords;
/** Replies the language name.
*
* @return the language name.
*/
@Pure
public String getLanguageName() {
return Strings.toFirstUpper(GrammarUtil.getSimpleName(getGrammar()).toLowerCase());
}
/** Replies the base package for the language.
*
* @return the base package.
*/
@Pure
public String getBasePackage() {
final Grammar grammar = getGrammar();
final String basePackage = this.naming.getRuntimeBasePackage(grammar);
return basePackage + ".services"; //$NON-NLS-1$
}
/** Replies the type of the accessor.
*
* @return the accessor's type.
*/
@Pure
public TypeReference getAccessorType() {
return new TypeReference(getBasePackage() + "." //$NON-NLS-1$
+ getLanguageName().toUpperCase() + "GrammarKeywordAccess"); //$NON-NLS-1$
}
@Override
public void initialize(Injector injector) {
super.initialize(injector);
}
@Override
public void generate() {
LOG.info("Generating the grammar keyword access for " + getLanguageName()); //$NON-NLS-1$
final StringConcatenationClient content = new StringConcatenationClient() {
@SuppressWarnings("synthetic-access")
@Override
protected void appendTo(TargetStringConcatenation it) {
it.append("/** Set of SARL keywords that are not directly supported by the"); //$NON-NLS-1$
it.newLine();
it.append(" * {@link SARLGrammarAccess} or hardly accessible."); //$NON-NLS-1$
it.newLine();
it.append(" */"); //$NON-NLS-1$
it.newLine();
it.append("@"); //$NON-NLS-1$
it.append(SuppressWarnings.class);
it.append("(\"all\")"); //$NON-NLS-1$
it.newLine();
it.append("public class "); //$NON-NLS-1$
it.append(getAccessorType().getSimpleName());
it.append(" {"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\t@"); //$NON-NLS-1$
it.append(Inject.class);
it.newLine();
it.append("\tprivate "); //$NON-NLS-1$
it.append(GrammarKeywordAccessFragment2.this.grammarAccessExtensions.getGrammarAccess(getGrammar()));
it.append(" grammarAccess;"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
final Set<String> addedKeywords = new HashSet<>();
final Map<String, String> getters = new HashMap<>();
final LinkedList<Grammar> grammars = new LinkedList<>();
grammars.add(getGrammar());
while (!grammars.isEmpty()) {
final Grammar grammar = grammars.removeFirst();
if (isValidGrammar(grammar)) {
it.append(generateMembers(grammar, addedKeywords, getters));
if (GrammarKeywordAccessFragment2.this.configuration.getDependencyGrammarInheritance()) {
grammars.addAll(getEffectivelyUsedGrammars(grammar));
}
}
}
it.append(generateMembersFromConfig(addedKeywords, getters));
it.append(generateAccessors(addedKeywords, getters));
it.append("}"); //$NON-NLS-1$
it.newLine();
it.newLineIfNotEmpty();
it.newLine();
}
};
final JavaFileAccess javaFile = this.fileAccessFactory.createJavaFile(
getAccessorType(), content);
javaFile.writeTo(getProjectConfig().getRuntime().getSrcGen());
}
/** Replies if the given grammar may be treated.
*
* @param grammar the grammar to test.
* @return <code>true</code> if the grammar could be treated.
*/
@SuppressWarnings("static-method")
protected boolean isValidGrammar(Grammar grammar) {
return !Objects.equal(grammar.getName(), "org.eclipse.xtext.common.Terminals"); //$NON-NLS-1$
}
/** Generate the members of the accessors.
*
* @param addedKeywords the set of keywords that are added to the output.
* @param getters filled by this function with the getters' names.
* @return the content.
*/
protected StringConcatenationClient generateMembersFromConfig(Set<String> addedKeywords, Map<String, String> getters) {
final List<StringConcatenationClient> clients = new ArrayList<>();
for (final String keyword : this.configuration.getKeywords()) {
final String id = keyword.toLowerCase();
if (!addedKeywords.contains(id) && !this.configuration.getIgnoredKeywords().contains(keyword)) {
clients.add(generateKeyword(keyword, getGrammar().getName(), getters));
addedKeywords.add(id);
}
}
for (final String keyword : this.configuration.getLiterals()) {
final String id = keyword.toLowerCase();
if (!addedKeywords.contains(id) && !this.configuration.getIgnoredKeywords().contains(keyword)) {
clients.add(generateKeyword(keyword, getGrammar().getName(), getters));
addedKeywords.add(id);
}
}
return new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation it) {
for (final StringConcatenationClient client : clients) {
it.append(client);
}
}
};
}
/** Generate the members of the accessors.
*
* @param grammar the grammar for which the keyword accessors must be generated.
* @param addedKeywords the set of keywords that are added to the output.
* @param getters filled by this function with the getters' names.
* @return the content.
*/
protected StringConcatenationClient generateMembers(Grammar grammar, Set<String> addedKeywords,
Map<String, String> getters) {
final List<StringConcatenationClient> clients = new ArrayList<>();
for (final Keyword grammarKeyword : getAllKeywords(grammar)) {
final String keyword = grammarKeyword.getValue().trim();
if (!keyword.isEmpty()) {
final String id = keyword.toLowerCase();
if (!addedKeywords.contains(id) && !this.configuration.getIgnoredKeywords().contains(keyword)) {
clients.add(generateKeyword(grammarKeyword, grammar.getName(), getters));
addedKeywords.add(id);
}
}
}
return new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation it) {
for (final StringConcatenationClient client : clients) {
it.append(client);
}
}
};
}
/** Replies the keywords in the given grammar.
*
* @param grammar the grammar.
* @return the keywords.
*/
protected static Iterable<Keyword> getAllKeywords(Grammar grammar) {
final Map<String, Keyword> keywords = new HashMap<>();
final List<ParserRule> rules = GrammarUtil.allParserRules(grammar);
for (final ParserRule parserRule : rules) {
final List<Keyword> list = typeSelect(eAllContentsAsList(parserRule), Keyword.class);
for (final Keyword keyword : list) {
keywords.put(keyword.getValue(), keyword);
}
}
final List<EnumRule> enumRules = GrammarUtil.allEnumRules(grammar);
for (final EnumRule enumRule : enumRules) {
final List<Keyword> list = typeSelect(eAllContentsAsList(enumRule), Keyword.class);
for (final Keyword keyword : list) {
keywords.put(keyword.getValue(), keyword);
}
}
return keywords.values();
}
/** Generate a basic keyword.
*
* @param keyword the keyword to add.
* @param comment a comment for the javadoc.
* @param getters filled by this function with the getters' names.
* @return the content.
*/
protected StringConcatenationClient generateKeyword(final String keyword, final String comment, Map<String, String> getters) {
final String fieldName = keyword.toUpperCase().replaceAll("[^a-zA-Z0-9_]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
final String methodName = Strings.toFirstUpper(keyword.replaceAll("[^a-zA-Z0-9_]+", "_")) //$NON-NLS-1$ //$NON-NLS-2$
+ "Keyword"; //$NON-NLS-1$
if (getters != null) {
getters.put(methodName, keyword);
}
return new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation it) {
it.append("\tprivate static final String "); //$NON-NLS-1$
it.append(fieldName);
it.append(" = \""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(keyword));
it.append("\";"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\t/** Keyword: {@code "); //$NON-NLS-1$
it.append(protectCommentKeyword(keyword));
it.append("}."); //$NON-NLS-1$
it.newLine();
if (!Strings.isEmpty(comment)) {
it.append("\t * Source: "); //$NON-NLS-1$
it.append(comment);
it.newLine();
}
it.append("\t */"); //$NON-NLS-1$
it.newLine();
it.append("\tpublic String get"); //$NON-NLS-1$
it.append(methodName);
it.append("() {"); //$NON-NLS-1$
it.newLine();
it.append("\t\treturn "); //$NON-NLS-1$
it.append(fieldName);
it.append(";"); //$NON-NLS-1$
it.newLine();
it.append("\t}"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
}
};
}
/** Generate a grammar keyword.
*
* @param keyword the keyword to add.
* @param comment a comment for the javadoc.
* @param getters filled by this function with the getters' names.
* @return the content.
*/
protected StringConcatenationClient generateKeyword(final Keyword keyword, final String comment,
Map<String, String> getters) {
try {
final String methodName = getIdentifier(keyword);
final String accessor = GrammarKeywordAccessFragment2.this.grammarAccessExtensions.gaAccessor(keyword);
if (!Strings.isEmpty(methodName) && !Strings.isEmpty(accessor)) {
if (getters != null) {
getters.put(methodName, keyword.getValue());
}
return new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation it) {
it.append("\t/** Keyword: {@code "); //$NON-NLS-1$
it.append(protectCommentKeyword(keyword.getValue()));
it.append("}."); //$NON-NLS-1$
it.newLine();
if (!Strings.isEmpty(comment)) {
it.append("\t * Source: "); //$NON-NLS-1$
it.append(comment);
it.newLine();
}
it.append("\t */"); //$NON-NLS-1$
it.newLine();
it.append("\tpublic String get"); //$NON-NLS-1$
it.append(methodName);
it.append("() {"); //$NON-NLS-1$
it.newLine();
it.append("\t\treturn this.grammarAccess."); //$NON-NLS-1$
it.append(accessor);
it.append(".getValue();"); //$NON-NLS-1$
it.newLine();
it.append("\t}"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
}
};
}
} catch (Exception e) {
//
}
return null;
}
/** Replies the identifier for the given keyword.
*
* @param keyword the keyword.
* @return the identifier.
*/
protected String getIdentifier(Keyword keyword) {
return this.grammarAccessExtensions.gaElementIdentifier(keyword)
.replaceFirst("[0-9_]+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
/** Protect the keywword for Java comments.
*
* @param keyword the keyword to protect.
* @return the protected keyword.
*/
@SuppressWarnings("static-method")
protected String protectCommentKeyword(String keyword) {
if ("*/".equals(keyword)) { //$NON-NLS-1$
return "* /"; //$NON-NLS-1$
}
if ("/*".equals(keyword)) { //$NON-NLS-1$
return "/ *"; //$NON-NLS-1$
}
if ("//".equals(keyword)) { //$NON-NLS-1$
return "/ /"; //$NON-NLS-1$
}
return keyword;
}
/** Returns all grammars from the hierarchy that are used from rules of this grammar.
*
* @param grammar the grammar.
* @return the used grammars.
*/
protected static List<Grammar> getEffectivelyUsedGrammars(final Grammar grammar) {
final List<AbstractRule> allRules = GrammarUtil.allRules(grammar);
final List<Grammar> map = ListExtensions.<AbstractRule, Grammar>map(allRules, (it) -> GrammarUtil.getGrammar(it));
final Iterable<Grammar> filter = IterableExtensions.<Grammar>filter(map, (it) -> Boolean.valueOf(it != grammar));
final Set<Grammar> set = IterableExtensions.<Grammar>toSet(filter);
return IterableExtensions.<Grammar>toList(set);
}
/** Generate the members of the accessors.
*
* @param addedKeywords the set of keywords that are added to the output.
* @param getters filled by this function with the getters' names.
* @return the content.
*/
@SuppressWarnings("checkstyle:anoninnerlength")
protected StringConcatenationClient generateAccessors(Set<String> addedKeywords, Map<String, String> getters) {
return new StringConcatenationClient() {
@SuppressWarnings("synthetic-access")
@Override
protected void appendTo(TargetStringConcatenation it) {
it.append("\tprivate "); //$NON-NLS-1$
it.append(SoftReference.class);
it.append("<"); //$NON-NLS-1$
it.append(Set.class);
it.append("<String>> allKeywords;"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\t/** Replies the SARL keywords."); //$NON-NLS-1$
it.newLine();
it.append("\t * @return the SARL keywords."); //$NON-NLS-1$
it.newLine();
it.append("\t * @see #getPureKeywords()"); //$NON-NLS-1$
it.newLine();
it.append("\t */"); //$NON-NLS-1$
it.newLine();
it.append("\tpublic "); //$NON-NLS-1$
it.append(Set.class);
it.append("<String> getKeywords() {"); //$NON-NLS-1$
it.newLine();
it.append("\t\t"); //$NON-NLS-1$
it.append(Set.class);
it.append("<String> kws = this.allKeywords == null ? null : this.allKeywords.get();"); //$NON-NLS-1$
it.newLine();
it.append("\t\tif (kws == null) {"); //$NON-NLS-1$
it.newLine();
it.append("\t\t\tkws = new "); //$NON-NLS-1$
it.append(TreeSet.class);
it.append("<>();"); //$NON-NLS-1$
it.newLine();
final Pattern pattern = Pattern.compile("^[a-zA-Z_$]+$"); //$NON-NLS-1$
for (final Entry<String, String> getter : getters.entrySet()) {
if (pattern.matcher(getter.getValue()).matches()) {
it.append("\t\t\tkws.add(get"); //$NON-NLS-1$
it.append(getter.getKey());
it.append("());"); //$NON-NLS-1$
it.newLine();
}
}
it.append("\t\t\tthis.allKeywords = new "); //$NON-NLS-1$
it.append(SoftReference.class);
it.append("<>(kws);"); //$NON-NLS-1$
it.newLine();
it.append("\t\t}"); //$NON-NLS-1$
it.newLine();
it.append("\t\treturn "); //$NON-NLS-1$
it.append(Collections.class);
it.append(".unmodifiableSet(kws);"); //$NON-NLS-1$
it.newLine();
it.append("\t}"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\t/** Replies if the given string of characters is a SARL keyword."); //$NON-NLS-1$
it.newLine();
it.append("\t * @param str the string of characters."); //$NON-NLS-1$
it.newLine();
it.append("\t * @return <code>true</code> if the string of characters is a SARL keyword."); //$NON-NLS-1$
it.newLine();
it.append("\t */"); //$NON-NLS-1$
it.newLine();
it.append("\tpublic boolean isKeyword(String str) {"); //$NON-NLS-1$
it.newLine();
it.append("\t\tassert !"); //$NON-NLS-1$
it.append(Strings.class);
it.append(".isEmpty(str);"); //$NON-NLS-1$
it.newLine();
it.append("\t\treturn getKeywords().contains(str);"); //$NON-NLS-1$
it.newLine();
it.append("\t}"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\tprivate "); //$NON-NLS-1$
it.append(SoftReference.class);
it.append("<"); //$NON-NLS-1$
it.append(Set.class);
it.append("<String>> pureSarlKeywords;"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\t/** Replies the pure SARL keywords."); //$NON-NLS-1$
it.newLine();
it.append("\t * Pure SARL keywords are SARL keywords that are not Java keywords."); //$NON-NLS-1$
it.newLine();
it.append("\t * @return the pure SARL keywords."); //$NON-NLS-1$
it.newLine();
it.append("\t */"); //$NON-NLS-1$
it.newLine();
it.append("\tpublic "); //$NON-NLS-1$
it.append(Set.class);
it.append("<String> getPureKeywords() {"); //$NON-NLS-1$
it.newLine();
it.append("\t\t"); //$NON-NLS-1$
it.append(Set.class);
it.append("<String> kws = this.pureSarlKeywords == null ? null : this.pureSarlKeywords.get();"); //$NON-NLS-1$
it.newLine();
it.append("\t\tif (kws == null) {"); //$NON-NLS-1$
it.newLine();
it.append("\t\t\tkws = new "); //$NON-NLS-1$
it.append(HashSet.class);
it.append("<>();"); //$NON-NLS-1$
it.newLine();
for (final Entry<String, String> getter : getters.entrySet()) {
if (pattern.matcher(getter.getValue()).matches()
&& !GrammarKeywordAccessFragment2.this.javaKeywords.isJavaKeyword(getter.getValue())) {
it.append("\t\t\tkws.add(get"); //$NON-NLS-1$
it.append(getter.getKey());
it.append("());"); //$NON-NLS-1$
it.newLine();
}
}
it.append("\t\t\tthis.pureSarlKeywords = new "); //$NON-NLS-1$
it.append(SoftReference.class);
it.append("<>(kws);"); //$NON-NLS-1$
it.newLine();
it.append("\t\t}"); //$NON-NLS-1$
it.newLine();
it.append("\t\treturn "); //$NON-NLS-1$
it.append(Collections.class);
it.append(".unmodifiableSet(kws);"); //$NON-NLS-1$
it.newLine();
it.append("\t}"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\t/** Replies if the given string of characters is a pure SARL keyword."); //$NON-NLS-1$
it.newLine();
it.append("\t * Pure SARL keywords are SARL keywords that are not Java keywords."); //$NON-NLS-1$
it.newLine();
it.append("\t * @param str the string of characters."); //$NON-NLS-1$
it.newLine();
it.append("\t * @return <code>true</code> if the string of characters is a SARL keyword."); //$NON-NLS-1$
it.newLine();
it.append("\t */"); //$NON-NLS-1$
it.newLine();
it.append("\tpublic boolean isPureKeyword(String str) {"); //$NON-NLS-1$
it.newLine();
it.append("\t\tassert !"); //$NON-NLS-1$
it.append(Strings.class);
it.append(".isEmpty(str);"); //$NON-NLS-1$
it.newLine();
it.append("\t\treturn getPureKeywords().contains(str);"); //$NON-NLS-1$
it.newLine();
it.append("\t}"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
it.append("\t/** Protect the given text if it is a keyword."); //$NON-NLS-1$
it.newLine();
it.append("\t * @param text the text to protect."); //$NON-NLS-1$
it.newLine();
it.append("\t * @return the protected text."); //$NON-NLS-1$
it.newLine();
it.append("\t */"); //$NON-NLS-1$
it.newLine();
it.append("\tpublic String protectKeyword(String text) {"); //$NON-NLS-1$
it.newLine();
it.append("\t\tif (!"); //$NON-NLS-1$
it.append(Strings.class);
it.append(".isEmpty(text) && isKeyword(text)) {"); //$NON-NLS-1$
it.newLine();
it.append("\t\t\treturn \""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(GrammarKeywordAccessFragment2.this.configuration.getKeywordProtectionSymbol()));
it.append("\" + text;"); //$NON-NLS-1$
it.newLine();
it.append("\t\t}"); //$NON-NLS-1$
it.newLine();
it.append("\t\treturn text;"); //$NON-NLS-1$
it.newLine();
it.append("\t}"); //$NON-NLS-1$
it.newLineIfNotEmpty();
it.newLine();
}
};
}
}