Merge pull request #1532 from eclipse/ade-refactor-xtend-to-java

[eclipse/xtext#1777] Translate more code to java.
This commit is contained in:
Arne Deutsch 2020-07-03 15:28:36 +02:00 committed by GitHub
commit 097ad57191
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1052 additions and 2059 deletions

View file

@ -6,12 +6,6 @@
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry including="**/*.java|**/*.xtend" kind="src" output="bin/main" path="xtend-gen">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/main"/>

View file

@ -2,7 +2,6 @@ bin.includes = .,\
META-INF/,\
plugin.properties,\
about.html
source.. = src/,\
xtend-gen/
source.. = src/
output.. = bin/main/
src.includes = about.html

View file

@ -0,0 +1,276 @@
/**
* Copyright (c) 2018 TypeFox and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.ide.server.semanticHighlight;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SemanticHighlightingCapabilities;
import org.eclipse.lsp4j.SemanticHighlightingInformation;
import org.eclipse.lsp4j.SemanticHighlightingParams;
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.util.SemanticHighlightingTokens;
import org.eclipse.lsp4j.util.SemanticHighlightingTokens.Token;
import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator;
import org.eclipse.xtext.ide.editor.syntaxcoloring.MergingHighlightedPositionAcceptor;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.ide.server.ILanguageServerAccess.Context;
import org.eclipse.xtext.ide.server.UriExtensions;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.ListExtensions;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import com.google.inject.Inject;
/**
* Shared semantic highlighting manager per language server. Responsible for converting the semantic highlighted ranges
* into the LSP standard by producing a compact, {@code base64} encoded token string.
*/
public class SemanticHighlightingRegistry {
/**
* Reserved TextMate scope identifier for styles that cannot be handled.
*/
public static final String UNKNOWN_SCOPE = "unknown.xtext";
/**
* TextMate scopes indicating an unhandled style ID to TextMate scope mapping.
*/
public static final List<String> UNKNOWN_SCOPES = Collections
.singletonList(SemanticHighlightingRegistry.UNKNOWN_SCOPE);
@Inject
private UriExtensions uriExtensions;
/**
* Lookup table for all known TextMate scopes.
*/
protected BiMap<Integer, List<String>> scopes;
protected LanguageClient client;
public void initialize(Iterable<? extends IResourceServiceProvider> allLanguages, ClientCapabilities capabilities,
LanguageClient client) {
Preconditions.checkState(this.client == null, "Already initialized.");
TextDocumentClientCapabilities textDocument = capabilities == null ? null : capabilities.getTextDocument();
SemanticHighlightingCapabilities semanticHighlightingCapabilities = textDocument == null ? null
: textDocument.getSemanticHighlightingCapabilities();
boolean semanticHighlighting = semanticHighlightingCapabilities == null ? false
: semanticHighlightingCapabilities.getSemanticHighlighting();
ImmutableBiMap.Builder<Integer, List<String>> builder = ImmutableBiMap.builder();
if (semanticHighlighting) {
Set<List<String>> allScopes = Streams.stream(allLanguages)
.map(l -> l.get(ISemanticHighlightingStyleToTokenMapper.class)).filter(m -> m != null)
.flatMap(mapper -> mapper.getAllStyleIds().stream().map(id -> mapper.toScopes(id)))
.filter(l -> l != null && !l.isEmpty()).collect(Collectors.toSet());
int i = 0;
for (List<String> scopes : allScopes)
builder.put(i++, scopes);
}
this.scopes = builder.build();
this.client = client;
}
/**
* Returns with a list of TextMate scopes for the internal scope index. Returns the
* {@link SemanticHighlightingRegistry#UNKNOWN_SCOPES unknown scopes} if no scopes are registered for the argument.
*/
public List<String> getScopes(int scopeIndex) {
checkInitialized();
return scopes.getOrDefault(scopeIndex, SemanticHighlightingRegistry.UNKNOWN_SCOPES);
}
/**
* Returns with the internal scope index for the argument. Returns {@code -1} if the scopes argument is
* <code>null</code>, the {@link SemanticHighlightingRegistry#UNKNOWN_SCOPES unknown scopes} or is not registered to
* this manager.
*/
public int getIndex(List<String> scopes) {
checkInitialized();
if (isNullOrUnknown(scopes))
return -1;
Integer index = this.scopes.inverse().get(scopes);
return index == null ? -1 : index;
}
/**
* Returns with a view of all scopes known by this manager.
*/
public List<List<String>> getAllScopes() {
checkInitialized();
ImmutableList.Builder<List<String>> builder = ImmutableList.builder();
scopes.keySet().forEach(it -> builder
.add(Preconditions.checkNotNull(scopes.get(it), "No scopes are available for index: " + it)));
return builder.build();
}
public void update(Context context) {
checkInitialized();
if (!(context.getResource() instanceof XtextResource))
return;
if (!context.isDocumentOpen())
return;
XtextResource resource = (XtextResource) context.getResource();
IResourceServiceProvider resourceServiceProvider = resource.getResourceServiceProvider();
ISemanticHighlightingCalculator calculator = resourceServiceProvider == null ? null
: resourceServiceProvider.get(ISemanticHighlightingCalculator.class);
ISemanticHighlightingStyleToTokenMapper mapper = resourceServiceProvider == null ? null
: resourceServiceProvider.get(ISemanticHighlightingStyleToTokenMapper.class);
if (calculator == null || isIgnoredMapper(mapper))
return;
Document document = context.getDocument();
MergingHighlightedPositionAcceptor acceptor = new MergingHighlightedPositionAcceptor(calculator);
calculator.provideHighlightingFor(resource, acceptor, CancelIndicator.NullImpl);
Iterable<SemanticHighlightingRegistry.HighlightedRange> ranges = Iterables.concat(ListExtensions
.map(acceptor.getPositions(), position -> ListExtensions.map(Arrays.asList(position.getIds()), id -> {
Position start = document.getPosition(position.getOffset());
Position end = document.getPosition(position.getOffset() + position.getLength());
int scope = getIndex(mapper.toScopes(id));
return new SemanticHighlightingRegistry.HighlightedRange(start, end, scope);
})));
notifyClient(new SemanticHighlightingParams(toVersionedTextDocumentIdentifier(context),
toSemanticHighlightingInformation(ranges, document)));
}
/**
* {@code true} if the argument is an ignored mapper. Otherwise, {@code false}. If a mapper is ignored, no semantic
* highlighting information will be calculated. Clients won't be notified at all.
*
* By default, the argument is ignored if {@code null}, or instance of the
* {@link ISemanticHighlightingStyleToTokenMapper.Noop NOOP mapper}.
*/
protected boolean isIgnoredMapper(ISemanticHighlightingStyleToTokenMapper mapper) {
return mapper instanceof ISemanticHighlightingStyleToTokenMapper.Noop;
}
protected List<SemanticHighlightingInformation> toSemanticHighlightingInformation(
Iterable<? extends HighlightedRange> ranges, Document document) {
ImmutableMultimap.Builder<Integer, Token> builder = ImmutableMultimap.builder();
Iterables.filter(ranges, it -> !Objects.equals(it.getStart(), it.getEnd())).forEach((HighlightedRange it) -> {
int startLine = it.getStart().getLine();
int endLine = it.getEnd().getLine();
if (startLine == endLine) {
int length = it.getEnd().getCharacter() - it.getStart().getCharacter();
builder.put(startLine, new Token(it.getStart().getCharacter(), length, it.scope));
} else {
String startLineContent = document.getLineContent(startLine);
int startLength = startLineContent.length() - it.getStart().getCharacter();
builder.put(startLine, new Token(it.getStart().getCharacter(), startLength, it.scope));
for (int line = (startLine + 1); (line < endLine); line++) {
String lineContent = document.getLineContent(line);
builder.put(line, new Token(0, lineContent.length(), it.scope));
}
builder.put(endLine, new Token(0, it.getEnd().getCharacter(), it.scope));
}
});
return appendEmptyLineTokens(IterableExtensions.toList(IterableExtensions
.map(builder.build().asMap().entrySet(), it -> new SemanticHighlightingInformation(it.getKey(),
SemanticHighlightingTokens.encode(it.getValue())))),
document);
}
protected List<SemanticHighlightingInformation> appendEmptyLineTokens(List<SemanticHighlightingInformation> infos,
Document document) {
int lineCount = document.getLineCount();
Map<Integer, SemanticHighlightingInformation> tokens = new HashMap<>(
Maps.uniqueIndex(infos, it -> it.getLine()));
for (int i = 0; i < lineCount; i++)
tokens.putIfAbsent(i, new SemanticHighlightingInformation(i, null));
return new ArrayList<>(tokens.values());
}
protected VersionedTextDocumentIdentifier toVersionedTextDocumentIdentifier(Context context) {
VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier();
id.setUri(uriExtensions.toUriString(context.getResource().getURI()));
id.setVersion(context.getDocument().getVersion());
return id;
}
protected void notifyClient(SemanticHighlightingParams params) {
client.semanticHighlighting(params);
}
protected void checkInitialized() {
Preconditions.checkState(client != null, "Not initialized.");
}
protected boolean isNullOrUnknown(List<String> nullable) {
return SemanticHighlightingRegistry.UNKNOWN_SCOPES.equals(nullable);
}
/**
* A highlighted range with additional <a href="https://manual.macromates.com/en/language_grammars">TextMate
* scopes</a> information.
*/
public static class HighlightedRange extends Range {
/**
* The internal index of the corresponding TextMate scope.
*/
private final int scope;
public HighlightedRange(Position start, Position end, int scope) {
super(start, end);
this.scope = scope;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + scope;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
if (!super.equals(obj))
return false;
SemanticHighlightingRegistry.HighlightedRange other = (SemanticHighlightingRegistry.HighlightedRange) obj;
if (other.scope != scope)
return false;
return true;
}
@Override
public String toString() {
return new ToStringBuilder(this).addAllFields().toString();
}
public int getScope() {
return scope;
}
}
}

View file

@ -1,242 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 TypeFox and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.ide.server.semanticHighlight
import com.google.common.base.Preconditions
import com.google.common.collect.BiMap
import com.google.common.collect.ImmutableBiMap
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMultimap
import com.google.common.collect.Maps
import com.google.inject.Inject
import java.util.List
import org.eclipse.lsp4j.ClientCapabilities
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import org.eclipse.lsp4j.SemanticHighlightingInformation
import org.eclipse.lsp4j.SemanticHighlightingParams
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier
import org.eclipse.lsp4j.services.LanguageClient
import org.eclipse.lsp4j.util.SemanticHighlightingTokens
import org.eclipse.xtend.lib.annotations.Data
import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator
import org.eclipse.xtext.ide.editor.syntaxcoloring.MergingHighlightedPositionAcceptor
import org.eclipse.xtext.ide.server.Document
import org.eclipse.xtext.ide.server.ILanguageServerAccess
import org.eclipse.xtext.ide.server.UriExtensions
import org.eclipse.xtext.resource.IResourceServiceProvider
import org.eclipse.xtext.resource.XtextResource
import org.eclipse.xtext.util.CancelIndicator
import static extension org.eclipse.lsp4j.util.SemanticHighlightingTokens.encode
/**
* Shared semantic highlighting manager per language server.
* Responsible for converting the semantic highlighted ranges into the LSP standard by producing a compact,
* {@code base64} encoded token string.
*
*/
class SemanticHighlightingRegistry {
/**
* Reserved TextMate scope identifier for styles that cannot be handled.
*/
public static val UNKNOWN_SCOPE = 'unknown.xtext';
/**
* TextMate scopes indicating an unhandled style ID to TextMate scope mapping.
*/
public static val UNKNOWN_SCOPES = #[UNKNOWN_SCOPE];
/**
* A highlighted range with additional <a href="https://manual.macromates.com/en/language_grammars">TextMate scopes</a> information.
*/
@Data
static class HighlightedRange extends Range {
/**
* The internal index of the corresponding TextMate scope.
*/
val int scope;
new(Position start, Position end, int scope) {
super(start, end);
this.scope = scope;
}
}
@Inject
extension UriExtensions;
/**
* Lookup table for all known TextMate scopes.
*/
protected BiMap<Integer, List<String>> scopes;
protected LanguageClient client;
def void initialize(Iterable<? extends IResourceServiceProvider> allLanguages, ClientCapabilities capabilities, LanguageClient client) {
Preconditions.checkState(this.client === null, 'Already initialized.');
val enabled = capabilities?.textDocument?.semanticHighlightingCapabilities?.semanticHighlighting ?: false;
val builder = ImmutableBiMap.builder;
if (enabled) {
allLanguages
.map[get(ISemanticHighlightingStyleToTokenMapper)]
.filterNull
.map[mapper|mapper.allStyleIds.map[styleId|mapper.toScopes(styleId)]]
.flatten
.filter[!nullOrEmpty]
.toSet
.forEach [ scope, index |
builder.put(index, scope)
];
}
scopes = builder.build;
this.client = client;
}
/**
* Returns with a list of TextMate scopes for the internal scope index. Returns the
* {@link SemanticHighlightingRegistry#UNKNOWN_SCOPES unknown scopes} if no scopes are registered for the argument.
*/
def List<String> getScopes(int scopeIndex) {
checkInitialized;
return scopes.getOrDefault(scopeIndex, SemanticHighlightingRegistry.UNKNOWN_SCOPES);
}
/**
* Returns with the internal scope index for the argument. Returns {@code -1} if the scopes
* argument is <code>null</code>, the {@link SemanticHighlightingRegistry#UNKNOWN_SCOPES unknown scopes}
* or is not registered to this manager.
*/
def int getIndex(List<String> scopes) {
checkInitialized;
if (scopes.nullOrUnknown) {
return -1;
}
val index = this.scopes.inverse.get(scopes);
return if(index === null) -1 else index;
}
/**
* Returns with a view of all scopes known by this manager.
*/
def List<List<String>> getAllScopes() {
checkInitialized;
val builder = ImmutableList.builder;
scopes.keySet.forEach [
builder.add(Preconditions.checkNotNull(scopes.get(it), '''No scopes are available for index: «it»'''))
];
return builder.build;
}
def void update(ILanguageServerAccess.Context context) {
checkInitialized;
if (!(context.resource instanceof XtextResource)) {
return;
}
if (!context.documentOpen) {
return;
}
val resource = context.resource as XtextResource;
val calculator = resource.resourceServiceProvider?.get(ISemanticHighlightingCalculator);
val mapper = resource.resourceServiceProvider?.get(ISemanticHighlightingStyleToTokenMapper);
if (calculator === null || mapper.isIgnoredMapper) {
return;
}
val document = context.document;
val acceptor = new MergingHighlightedPositionAcceptor(calculator);
calculator.provideHighlightingFor(resource, acceptor, CancelIndicator.NullImpl);
val ranges = acceptor.positions.map [ position |
position.ids.map [ id |
val start = document.getPosition(position.offset);
val end = document.getPosition(position.offset + position.length);
val scope = getIndex(mapper.toScopes(id));
return new HighlightedRange(start, end, scope);
]
].flatten;
val lines = ranges.toSemanticHighlightingInformation(document);
val textDocument = context.toVersionedTextDocumentIdentifier;
notifyClient(new SemanticHighlightingParams(textDocument, lines));
}
/**
* {@code true} if the argument is an ignored mapper. Otherwise, {@code false}.
* If a mapper is ignored, no semantic highlighting information will be calculated. Clients won't be notified at all.
*
* By default, the argument is ignored if {@code null}, or instance of the {@link ISemanticHighlightingStyleToTokenMapper.Noop NOOP mapper}.
*/
protected def boolean isIgnoredMapper(ISemanticHighlightingStyleToTokenMapper mapper) {
return mapper === null || mapper instanceof ISemanticHighlightingStyleToTokenMapper.Noop;
}
protected def List<SemanticHighlightingInformation> toSemanticHighlightingInformation(
Iterable<? extends HighlightedRange> ranges, Document document) {
val builder = ImmutableMultimap.builder;
ranges.filter[start != end].forEach [
val startLine = start.line;
val endLine = end.line;
if (startLine === endLine) {
val length = end.character - start.character;
builder.put(startLine, new SemanticHighlightingTokens.Token(start.character, length, scope));
} else {
val startLineContent = document.getLineContent(startLine);
val startLength = startLineContent.length - start.character;
builder.put(startLine, new SemanticHighlightingTokens.Token(start.character, startLength, scope));
for (var line = startLine + 1; line < endLine; line++) {
val lineContent = document.getLineContent(line);
builder.put(line, new SemanticHighlightingTokens.Token(0, lineContent.length, scope));
}
builder.put(endLine, new SemanticHighlightingTokens.Token(0, end.character, scope));
}
];
return builder.build.asMap.entrySet.map [
val line = key;
val tokens = value;
new SemanticHighlightingInformation(line, tokens.encode);
].toList.appendEmptyLineTokens(document);
}
protected def List<SemanticHighlightingInformation> appendEmptyLineTokens(
List<SemanticHighlightingInformation> infos, Document document) {
val lineCount = document.lineCount;
val tokens = Maps.newHashMap(Maps.uniqueIndex(infos, [line]));
for (i : 0 ..< lineCount) {
if (!tokens.containsKey(i)) {
tokens.put(i, new SemanticHighlightingInformation(i, null))
}
}
return tokens.values.toList;
}
protected def VersionedTextDocumentIdentifier toVersionedTextDocumentIdentifier(ILanguageServerAccess.Context context) {
return new VersionedTextDocumentIdentifier => [
uri = context.resource.URI.toUriString;
version = context.document.version;
];
}
protected def void notifyClient(SemanticHighlightingParams params) {
client.semanticHighlighting(params);
}
protected def void checkInitialized() {
Preconditions.checkState(client !== null, 'Not initialized.');
}
protected def boolean isNullOrUnknown(List<String> nullable) {
return nullable === null || nullable == UNKNOWN_SCOPES;
}
}

View file

@ -1,386 +0,0 @@
/**
* Copyright (c) 2018 TypeFox and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.ide.server.semanticHighlight;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SemanticHighlightingCapabilities;
import org.eclipse.lsp4j.SemanticHighlightingInformation;
import org.eclipse.lsp4j.SemanticHighlightingParams;
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.util.SemanticHighlightingTokens;
import org.eclipse.xtend.lib.annotations.Data;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator;
import org.eclipse.xtext.ide.editor.syntaxcoloring.LightweightPosition;
import org.eclipse.xtext.ide.editor.syntaxcoloring.MergingHighlightedPositionAcceptor;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.ide.server.ILanguageServerAccess;
import org.eclipse.xtext.ide.server.UriExtensions;
import org.eclipse.xtext.ide.server.semanticHighlight.ISemanticHighlightingStyleToTokenMapper;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.ExclusiveRange;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.ListExtensions;
import org.eclipse.xtext.xbase.lib.ObjectExtensions;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure2;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
/**
* Shared semantic highlighting manager per language server.
* Responsible for converting the semantic highlighted ranges into the LSP standard by producing a compact,
* {@code base64} encoded token string.
*/
@SuppressWarnings("all")
public class SemanticHighlightingRegistry {
/**
* A highlighted range with additional <a href="https://manual.macromates.com/en/language_grammars">TextMate scopes</a> information.
*/
@Data
public static class HighlightedRange extends Range {
/**
* The internal index of the corresponding TextMate scope.
*/
private final int scope;
public HighlightedRange(final Position start, final Position end, final int scope) {
super(start, end);
this.scope = scope;
}
@Override
@Pure
public int hashCode() {
return 31 * super.hashCode() + this.scope;
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
if (!super.equals(obj))
return false;
SemanticHighlightingRegistry.HighlightedRange other = (SemanticHighlightingRegistry.HighlightedRange) obj;
if (other.scope != this.scope)
return false;
return true;
}
@Override
@Pure
public String toString() {
return new ToStringBuilder(this)
.addAllFields()
.toString();
}
@Pure
public int getScope() {
return this.scope;
}
}
/**
* Reserved TextMate scope identifier for styles that cannot be handled.
*/
public static final String UNKNOWN_SCOPE = "unknown.xtext";
/**
* TextMate scopes indicating an unhandled style ID to TextMate scope mapping.
*/
public static final List<String> UNKNOWN_SCOPES = Collections.<String>unmodifiableList(CollectionLiterals.<String>newArrayList(SemanticHighlightingRegistry.UNKNOWN_SCOPE));
@Inject
@Extension
private UriExtensions _uriExtensions;
/**
* Lookup table for all known TextMate scopes.
*/
protected BiMap<Integer, List<String>> scopes;
protected LanguageClient client;
public void initialize(final Iterable<? extends IResourceServiceProvider> allLanguages, final ClientCapabilities capabilities, final LanguageClient client) {
Preconditions.checkState((this.client == null), "Already initialized.");
Boolean _elvis = null;
TextDocumentClientCapabilities _textDocument = null;
if (capabilities!=null) {
_textDocument=capabilities.getTextDocument();
}
SemanticHighlightingCapabilities _semanticHighlightingCapabilities = null;
if (_textDocument!=null) {
_semanticHighlightingCapabilities=_textDocument.getSemanticHighlightingCapabilities();
}
Boolean _semanticHighlighting = null;
if (_semanticHighlightingCapabilities!=null) {
_semanticHighlighting=_semanticHighlightingCapabilities.getSemanticHighlighting();
}
if (_semanticHighlighting != null) {
_elvis = _semanticHighlighting;
} else {
_elvis = Boolean.valueOf(false);
}
final Boolean enabled = _elvis;
final ImmutableBiMap.Builder<Integer, List<String>> builder = ImmutableBiMap.<Integer, List<String>>builder();
if ((enabled).booleanValue()) {
final Function1<IResourceServiceProvider, ISemanticHighlightingStyleToTokenMapper> _function = (IResourceServiceProvider it) -> {
return it.<ISemanticHighlightingStyleToTokenMapper>get(ISemanticHighlightingStyleToTokenMapper.class);
};
final Function1<ISemanticHighlightingStyleToTokenMapper, Iterable<List<String>>> _function_1 = (ISemanticHighlightingStyleToTokenMapper mapper) -> {
final Function1<String, List<String>> _function_2 = (String styleId) -> {
return mapper.toScopes(styleId);
};
return IterableExtensions.<String, List<String>>map(mapper.getAllStyleIds(), _function_2);
};
final Function1<List<String>, Boolean> _function_2 = (List<String> it) -> {
boolean _isNullOrEmpty = IterableExtensions.isNullOrEmpty(it);
return Boolean.valueOf((!_isNullOrEmpty));
};
final Procedure2<List<String>, Integer> _function_3 = (List<String> scope, Integer index) -> {
builder.put(index, scope);
};
IterableExtensions.<List<String>>forEach(IterableExtensions.<List<String>>toSet(IterableExtensions.<List<String>>filter(Iterables.<List<String>>concat(IterableExtensions.<ISemanticHighlightingStyleToTokenMapper, Iterable<List<String>>>map(IterableExtensions.<ISemanticHighlightingStyleToTokenMapper>filterNull(IterableExtensions.map(allLanguages, _function)), _function_1)), _function_2)), _function_3);
}
this.scopes = builder.build();
this.client = client;
}
/**
* Returns with a list of TextMate scopes for the internal scope index. Returns the
* {@link SemanticHighlightingRegistry#UNKNOWN_SCOPES unknown scopes} if no scopes are registered for the argument.
*/
public List<String> getScopes(final int scopeIndex) {
this.checkInitialized();
return this.scopes.getOrDefault(Integer.valueOf(scopeIndex), SemanticHighlightingRegistry.UNKNOWN_SCOPES);
}
/**
* Returns with the internal scope index for the argument. Returns {@code -1} if the scopes
* argument is <code>null</code>, the {@link SemanticHighlightingRegistry#UNKNOWN_SCOPES unknown scopes}
* or is not registered to this manager.
*/
public int getIndex(final List<String> scopes) {
this.checkInitialized();
boolean _isNullOrUnknown = this.isNullOrUnknown(scopes);
if (_isNullOrUnknown) {
return (-1);
}
final Integer index = this.scopes.inverse().get(scopes);
Integer _xifexpression = null;
if ((index == null)) {
_xifexpression = Integer.valueOf((-1));
} else {
_xifexpression = index;
}
return (_xifexpression).intValue();
}
/**
* Returns with a view of all scopes known by this manager.
*/
public List<List<String>> getAllScopes() {
this.checkInitialized();
final ImmutableList.Builder<List<String>> builder = ImmutableList.<List<String>>builder();
final Consumer<Integer> _function = (Integer it) -> {
List<String> _get = this.scopes.get(it);
StringConcatenation _builder = new StringConcatenation();
_builder.append("No scopes are available for index: ");
_builder.append(it);
builder.add(Preconditions.<List<String>>checkNotNull(_get, _builder));
};
this.scopes.keySet().forEach(_function);
return builder.build();
}
public void update(final ILanguageServerAccess.Context context) {
this.checkInitialized();
Resource _resource = context.getResource();
boolean _not = (!(_resource instanceof XtextResource));
if (_not) {
return;
}
boolean _isDocumentOpen = context.isDocumentOpen();
boolean _not_1 = (!_isDocumentOpen);
if (_not_1) {
return;
}
Resource _resource_1 = context.getResource();
final XtextResource resource = ((XtextResource) _resource_1);
IResourceServiceProvider _resourceServiceProvider = resource.getResourceServiceProvider();
ISemanticHighlightingCalculator _get = null;
if (_resourceServiceProvider!=null) {
_get=_resourceServiceProvider.<ISemanticHighlightingCalculator>get(ISemanticHighlightingCalculator.class);
}
final ISemanticHighlightingCalculator calculator = _get;
IResourceServiceProvider _resourceServiceProvider_1 = resource.getResourceServiceProvider();
ISemanticHighlightingStyleToTokenMapper _get_1 = null;
if (_resourceServiceProvider_1!=null) {
_get_1=_resourceServiceProvider_1.<ISemanticHighlightingStyleToTokenMapper>get(ISemanticHighlightingStyleToTokenMapper.class);
}
final ISemanticHighlightingStyleToTokenMapper mapper = _get_1;
if (((calculator == null) || this.isIgnoredMapper(mapper))) {
return;
}
final Document document = context.getDocument();
final MergingHighlightedPositionAcceptor acceptor = new MergingHighlightedPositionAcceptor(calculator);
calculator.provideHighlightingFor(resource, acceptor, CancelIndicator.NullImpl);
final Function1<LightweightPosition, List<SemanticHighlightingRegistry.HighlightedRange>> _function = (LightweightPosition position) -> {
final Function1<String, SemanticHighlightingRegistry.HighlightedRange> _function_1 = (String id) -> {
final Position start = document.getPosition(position.getOffset());
int _offset = position.getOffset();
int _length = position.getLength();
int _plus = (_offset + _length);
final Position end = document.getPosition(_plus);
final int scope = this.getIndex(mapper.toScopes(id));
return new SemanticHighlightingRegistry.HighlightedRange(start, end, scope);
};
return ListExtensions.<String, SemanticHighlightingRegistry.HighlightedRange>map(((List<String>)Conversions.doWrapArray(position.getIds())), _function_1);
};
final Iterable<SemanticHighlightingRegistry.HighlightedRange> ranges = Iterables.<SemanticHighlightingRegistry.HighlightedRange>concat(ListExtensions.<LightweightPosition, List<SemanticHighlightingRegistry.HighlightedRange>>map(acceptor.getPositions(), _function));
final List<SemanticHighlightingInformation> lines = this.toSemanticHighlightingInformation(ranges, document);
final VersionedTextDocumentIdentifier textDocument = this.toVersionedTextDocumentIdentifier(context);
SemanticHighlightingParams _semanticHighlightingParams = new SemanticHighlightingParams(textDocument, lines);
this.notifyClient(_semanticHighlightingParams);
}
/**
* {@code true} if the argument is an ignored mapper. Otherwise, {@code false}.
* If a mapper is ignored, no semantic highlighting information will be calculated. Clients won't be notified at all.
*
* By default, the argument is ignored if {@code null}, or instance of the {@link ISemanticHighlightingStyleToTokenMapper.Noop NOOP mapper}.
*/
protected boolean isIgnoredMapper(final ISemanticHighlightingStyleToTokenMapper mapper) {
return ((mapper == null) || (mapper instanceof ISemanticHighlightingStyleToTokenMapper.Noop));
}
protected List<SemanticHighlightingInformation> toSemanticHighlightingInformation(final Iterable<? extends SemanticHighlightingRegistry.HighlightedRange> ranges, final Document document) {
final ImmutableMultimap.Builder<Integer, SemanticHighlightingTokens.Token> builder = ImmutableMultimap.<Integer, SemanticHighlightingTokens.Token>builder();
final Function1<SemanticHighlightingRegistry.HighlightedRange, Boolean> _function = (SemanticHighlightingRegistry.HighlightedRange it) -> {
Position _start = it.getStart();
Position _end = it.getEnd();
return Boolean.valueOf((!Objects.equal(_start, _end)));
};
final Consumer<SemanticHighlightingRegistry.HighlightedRange> _function_1 = (SemanticHighlightingRegistry.HighlightedRange it) -> {
final int startLine = it.getStart().getLine();
final int endLine = it.getEnd().getLine();
if ((startLine == endLine)) {
int _character = it.getEnd().getCharacter();
int _character_1 = it.getStart().getCharacter();
final int length = (_character - _character_1);
int _character_2 = it.getStart().getCharacter();
SemanticHighlightingTokens.Token _token = new SemanticHighlightingTokens.Token(_character_2, length, it.scope);
builder.put(Integer.valueOf(startLine), _token);
} else {
final String startLineContent = document.getLineContent(startLine);
int _length = startLineContent.length();
int _character_3 = it.getStart().getCharacter();
final int startLength = (_length - _character_3);
int _character_4 = it.getStart().getCharacter();
SemanticHighlightingTokens.Token _token_1 = new SemanticHighlightingTokens.Token(_character_4, startLength, it.scope);
builder.put(Integer.valueOf(startLine), _token_1);
for (int line = (startLine + 1); (line < endLine); line++) {
{
final String lineContent = document.getLineContent(line);
int _length_1 = lineContent.length();
SemanticHighlightingTokens.Token _token_2 = new SemanticHighlightingTokens.Token(0, _length_1, it.scope);
builder.put(Integer.valueOf(line), _token_2);
}
}
int _character_5 = it.getEnd().getCharacter();
SemanticHighlightingTokens.Token _token_2 = new SemanticHighlightingTokens.Token(0, _character_5, it.scope);
builder.put(Integer.valueOf(endLine), _token_2);
}
};
IterableExtensions.filter(ranges, _function).forEach(_function_1);
final Function1<Map.Entry<Integer, Collection<SemanticHighlightingTokens.Token>>, SemanticHighlightingInformation> _function_2 = (Map.Entry<Integer, Collection<SemanticHighlightingTokens.Token>> it) -> {
SemanticHighlightingInformation _xblockexpression = null;
{
final Integer line = it.getKey();
final Collection<SemanticHighlightingTokens.Token> tokens = it.getValue();
String _encode = SemanticHighlightingTokens.encode(tokens);
_xblockexpression = new SemanticHighlightingInformation((line).intValue(), _encode);
}
return _xblockexpression;
};
return this.appendEmptyLineTokens(IterableExtensions.<SemanticHighlightingInformation>toList(IterableExtensions.<Map.Entry<Integer, Collection<SemanticHighlightingTokens.Token>>, SemanticHighlightingInformation>map(builder.build().asMap().entrySet(), _function_2)), document);
}
protected List<SemanticHighlightingInformation> appendEmptyLineTokens(final List<SemanticHighlightingInformation> infos, final Document document) {
final int lineCount = document.getLineCount();
final Function<SemanticHighlightingInformation, Integer> _function = (SemanticHighlightingInformation it) -> {
return Integer.valueOf(it.getLine());
};
final HashMap<Integer, SemanticHighlightingInformation> tokens = Maps.<Integer, SemanticHighlightingInformation>newHashMap(Maps.<Integer, SemanticHighlightingInformation>uniqueIndex(infos, _function));
ExclusiveRange _doubleDotLessThan = new ExclusiveRange(0, lineCount, true);
for (final Integer i : _doubleDotLessThan) {
boolean _containsKey = tokens.containsKey(i);
boolean _not = (!_containsKey);
if (_not) {
SemanticHighlightingInformation _semanticHighlightingInformation = new SemanticHighlightingInformation((i).intValue(), null);
tokens.put(i, _semanticHighlightingInformation);
}
}
return IterableExtensions.<SemanticHighlightingInformation>toList(tokens.values());
}
protected VersionedTextDocumentIdentifier toVersionedTextDocumentIdentifier(final ILanguageServerAccess.Context context) {
VersionedTextDocumentIdentifier _versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier();
final Procedure1<VersionedTextDocumentIdentifier> _function = (VersionedTextDocumentIdentifier it) -> {
it.setUri(this._uriExtensions.toUriString(context.getResource().getURI()));
it.setVersion(context.getDocument().getVersion());
};
return ObjectExtensions.<VersionedTextDocumentIdentifier>operator_doubleArrow(_versionedTextDocumentIdentifier, _function);
}
protected void notifyClient(final SemanticHighlightingParams params) {
this.client.semanticHighlighting(params);
}
protected void checkInitialized() {
Preconditions.checkState((this.client != null), "Not initialized.");
}
protected boolean isNullOrUnknown(final List<String> nullable) {
return ((nullable == null) || Objects.equal(nullable, SemanticHighlightingRegistry.UNKNOWN_SCOPES));
}
}

View file

@ -0,0 +1,337 @@
/**
* Copyright (c) 2015, 2020 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.testing.logging;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggingEvent;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
import org.junit.Assert;
import com.google.common.annotations.Beta;
import com.google.common.primitives.Longs;
@Beta
public class LoggingTester {
private static final Comparator<LogEntry> TEMPORAL_ORDER = ($0, $1) -> Longs.compare($0.timeStamp, $1.timeStamp);
public static LogCapture captureLogging(Level level, Class<?> source, Runnable action) {
Logger logger = Logger.getLogger(source);
QueueAppender appender = new QueueAppender();
Level oldLevel = logger.getLevel();
List<Appender> allAppenders = appenderHierarchy(logger);
SourceFilter filter = new SourceFilter(logger);
try {
allAppenders.forEach(it -> it.addFilter(filter));
logger.addAppender(appender);
logger.setLevel(level);
action.run();
List<LogEntry> events = IterableExtensions.sortWith(IterableExtensions.toList(appender.events),
LoggingTester.TEMPORAL_ORDER);
return new LoggingTester.LogCapture(events);
} finally {
logger.removeAppender(appender);
allAppenders.forEach(it -> LoggingTester.removeFilter(it, filter));
logger.setLevel(oldLevel);
}
}
@SuppressWarnings("unchecked")
private static List<Appender> appenderHierarchy(Logger logger) {
List<Appender> appenders = new ArrayList<>();
for (Category current = logger; current != null; current = current.getParent())
appenders.addAll(Collections.list(current.getAllAppenders()));
return appenders;
}
private static void removeFilter(Appender appender, Filter filter) {
if (Objects.equals(appender.getFilter(), filter)) {
appender.clearFilters();
appender.addFilter(filter.getNext());
} else {
for (Filter current = appender.getFilter(); current != null; current = current.getNext()) {
if (Objects.equals(current.getNext(), filter)) {
current.setNext(filter.getNext());
return;
}
}
}
}
public static class LogCapture {
private final List<LogEntry> logEntries;
public void assertNoLogEntries() {
assertNumberOfLogEntries(0);
}
public void assertLogEntry(String... messageParts) {
assertNumberOfLogEntries(1, messageParts);
}
public void assertLogEntry(Level level, String... messageParts) {
assertNumberOfLogEntries(1, level, messageParts);
}
public void assertNumberOfLogEntries(int number) {
assertNumberOfLogEntries(number, new String[] {});
}
public void assertNumberOfLogEntries(int number, String... messageParts) {
assertNumberOfLogEntries(number, null, messageParts);
}
public void assertNumberOfLogEntries(int number, Level level, String... messageParts) {
@SuppressWarnings("unchecked")
Iterable<LogEntry> passed = IterableExtensions
.filter(logEntries,
log -> ((level == null || Objects.equals(log.level, level)) && IterableExtensions.forall(
((Iterable<String>) Conversions.doWrapArray(messageParts)),
it -> log.message.contains(it))));
if (IterableExtensions.size(passed) != number) {
StringConcatenation builder = new StringConcatenation();
if (number == 0) {
builder.append("Expected no log entries");
builder.newLine();
} else {
if (number == 1) {
builder.append("Expected a log entry");
builder.newLine();
} else {
builder.append("Expected ");
builder.append(number);
builder.append(" log entries");
builder.newLineIfNotEmpty();
}
}
if (level != null) {
builder.append("with ");
builder.append(level);
builder.append(" level");
builder.newLineIfNotEmpty();
}
builder.append("containing the phrases ");
builder.append(Stream.of(messageParts).map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
builder.newLineIfNotEmpty();
builder.append("but got");
builder.newLine();
builder.append(this.logEntries);
builder.newLineIfNotEmpty();
Assert.fail(builder.toString());
}
}
public LogCapture(List<LogEntry> logEntries) {
super();
this.logEntries = logEntries;
}
@Override
@Pure
public int hashCode() {
return 31 * 1 + ((logEntries == null) ? 0 : logEntries.hashCode());
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LogCapture other = (LogCapture) obj;
if (logEntries == null) {
if (other.logEntries != null)
return false;
} else if (!logEntries.equals(other.logEntries))
return false;
return true;
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("logEntries", logEntries);
return b.toString();
}
@Pure
public List<LogEntry> getLogEntries() {
return logEntries;
}
}
public static class LogEntry {
private final String message;
private final String source;
private final long timeStamp;
private final Level level;
public LogEntry(String message, String source, long timeStamp, Level level) {
this.message = message;
this.source = source;
this.timeStamp = timeStamp;
this.level = level;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (message == null ? 0 : message.hashCode());
result = prime * result + (source == null ? 0 : source.hashCode());
result = prime * result + (int) (timeStamp ^ (timeStamp >>> 32));
return prime * result + (level == null ? 0 : level.hashCode());
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LogEntry other = (LogEntry) obj;
if (message == null) {
if (other.message != null)
return false;
} else if (!message.equals(other.message))
return false;
if (source == null) {
if (other.source != null)
return false;
} else if (!source.equals(other.source))
return false;
if (other.timeStamp != timeStamp)
return false;
if (level == null) {
if (other.level != null)
return false;
} else if (!level.equals(other.level))
return false;
return true;
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("message", message);
b.add("source", source);
b.add("timeStamp", timeStamp);
b.add("level", level);
return b.toString();
}
public String getMessage() {
return message;
}
public String getSource() {
return source;
}
public long getTimeStamp() {
return timeStamp;
}
public Level getLevel() {
return level;
}
}
private static class QueueAppender extends AppenderSkeleton {
private final Queue<LogEntry> events = new ConcurrentLinkedQueue<>();
@Override
public boolean requiresLayout() {
return false;
}
@Override
public void close() {
}
@Override
protected void append(LoggingEvent event) {
events.add(new LogEntry(event.getRenderedMessage(), event.getLoggerName(), event.getTimeStamp(),
event.getLevel()));
}
@Pure
public Queue<LoggingTester.LogEntry> getEvents() {
return events;
}
}
private static class SourceFilter extends Filter {
private final Logger source;
@Override
public int decide(LoggingEvent event) {
return Objects.equals(event.getLoggerName(), source.getName()) ? Filter.DENY : Filter.NEUTRAL;
}
public SourceFilter(Logger source) {
this.source = source;
}
@Override
public int hashCode() {
return 31 * 1 + (source == null ? 0 : source.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SourceFilter other = (SourceFilter) obj;
if (source == null) {
if (other.source != null)
return false;
} else if (!source.equals(other.source))
return false;
return true;
}
@Override
public String toString() {
return new ToStringBuilder(this).addAllFields().toString();
}
}
}

View file

@ -1,159 +0,0 @@
/*******************************************************************************
* Copyright (c) 2015, 2016 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.testing.logging
import com.google.common.annotations.Beta
import com.google.common.primitives.Longs
import java.util.Collections
import java.util.Comparator
import java.util.List
import java.util.Queue
import java.util.concurrent.ConcurrentLinkedQueue
import org.apache.log4j.Appender
import org.apache.log4j.AppenderSkeleton
import org.apache.log4j.Category
import org.apache.log4j.Level
import org.apache.log4j.Logger
import org.apache.log4j.spi.Filter
import org.apache.log4j.spi.LoggingEvent
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtend.lib.annotations.Data
import org.junit.Assert
@Beta class LoggingTester {
@Data
static class LogCapture {
List<LogEntry> logEntries
def assertNoLogEntries() {
assertNumberOfLogEntries(0)
}
def assertLogEntry(String... messageParts) {
assertNumberOfLogEntries(1, messageParts)
}
def assertLogEntry(Level level, String... messageParts) {
assertNumberOfLogEntries(1, level, messageParts)
}
def assertNumberOfLogEntries(int number) {
assertNumberOfLogEntries(number, #[])
}
def assertNumberOfLogEntries(int number, String... messageParts) {
assertNumberOfLogEntries(number, null, messageParts)
}
def assertNumberOfLogEntries(int number, Level level, String... messageParts) {
val passed = logEntries.filter [ log |
(level === null || log.level == level) && messageParts.forall[log.message.contains(it)]
]
if (passed.size != number) {
Assert.fail(
'''
«IF number == 0»
Expected no log entries
«ELSEIF number == 1»
Expected a log entry
«ELSE»
Expected «number» log entries
«ENDIF»
«IF level !== null»
with «level» level
«ENDIF»
containing the phrases «messageParts.join(", ")['"' + it + '"']»
but got
«logEntries»
'''
)
}
}
}
@Data
static class LogEntry {
String message
String source
long timeStamp
Level level
}
def static captureLogging(Level level, Class<?> source, Runnable action) {
val logger = Logger.getLogger(source)
val appender = new QueueAppender
val oldLevel = logger.level
val allAppenders = logger.appenderHierarchy
val filter = new SourceFilter(logger)
try {
allAppenders.forEach[addFilter(filter)]
logger.addAppender(appender)
logger.level = level
action.run
val events = appender.events.toList.sortWith(TEMPORAL_ORDER)
return new LogCapture(events)
} finally {
logger.removeAppender(appender)
allAppenders.forEach[removeFilter(filter)]
logger.level = oldLevel
}
}
private static def appenderHierarchy(Logger logger) {
val appenders = newArrayList
for (var Category current = logger; current !== null; current = current.parent) {
appenders.addAll(Collections.<Appender>list(current.allAppenders))
}
appenders
}
private static def removeFilter(Appender appender, Filter filter) {
if (appender.filter == filter) {
appender.clearFilters
appender.addFilter(filter.getNext)
} else {
for (var current = appender.filter; current !== null; current = current.getNext) {
if (current.getNext == filter) {
current.setNext(filter.getNext)
return
}
}
}
}
private static class QueueAppender extends AppenderSkeleton {
@Accessors(PUBLIC_GETTER)
val Queue<LogEntry> events = new ConcurrentLinkedQueue
override boolean requiresLayout() {
false
}
override void close() {
}
override protected void append(LoggingEvent event) {
val entry = new LogEntry(event.renderedMessage, event.loggerName, event.getTimeStamp, event.getLevel)
events += entry
}
}
@Data
private static class SourceFilter extends Filter {
val Logger source
override decide(LoggingEvent event) {
if(event.loggerName == source.name) DENY else NEUTRAL
}
}
static val Comparator<LogEntry> TEMPORAL_ORDER = [Longs.compare($0.timeStamp, $1.timeStamp)]
}

View file

@ -1,400 +0,0 @@
/**
* Copyright (c) 2015, 2016 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.testing.logging;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.primitives.Longs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Category;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggingEvent;
import org.eclipse.xtend.lib.annotations.AccessorType;
import org.eclipse.xtend.lib.annotations.Accessors;
import org.eclipse.xtend.lib.annotations.Data;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
import org.junit.Assert;
@Beta
@SuppressWarnings("all")
public class LoggingTester {
@Data
public static class LogCapture {
private final List<LoggingTester.LogEntry> logEntries;
public void assertNoLogEntries() {
this.assertNumberOfLogEntries(0);
}
public void assertLogEntry(final String... messageParts) {
this.assertNumberOfLogEntries(1, messageParts);
}
public void assertLogEntry(final Level level, final String... messageParts) {
this.assertNumberOfLogEntries(1, level, messageParts);
}
public void assertNumberOfLogEntries(final int number) {
this.assertNumberOfLogEntries(number, new String[] {});
}
public void assertNumberOfLogEntries(final int number, final String... messageParts) {
this.assertNumberOfLogEntries(number, null, messageParts);
}
public void assertNumberOfLogEntries(final int number, final Level level, final String... messageParts) {
final Function1<LoggingTester.LogEntry, Boolean> _function = (LoggingTester.LogEntry log) -> {
return Boolean.valueOf((((level == null) || Objects.equal(log.level, level)) && IterableExtensions.<String>forall(((Iterable<String>)Conversions.doWrapArray(messageParts)), ((Function1<String, Boolean>) (String it) -> {
return Boolean.valueOf(log.message.contains(it));
}))));
};
final Iterable<LoggingTester.LogEntry> passed = IterableExtensions.<LoggingTester.LogEntry>filter(this.logEntries, _function);
int _size = IterableExtensions.size(passed);
boolean _notEquals = (_size != number);
if (_notEquals) {
StringConcatenation _builder = new StringConcatenation();
{
if ((number == 0)) {
_builder.append("Expected no log entries");
_builder.newLine();
} else {
if ((number == 1)) {
_builder.append("Expected a log entry");
_builder.newLine();
} else {
_builder.append("Expected ");
_builder.append(number);
_builder.append(" log entries");
_builder.newLineIfNotEmpty();
}
}
}
{
if ((level != null)) {
_builder.append("with ");
_builder.append(level);
_builder.append(" level");
_builder.newLineIfNotEmpty();
}
}
_builder.append("containing the phrases ");
final Function1<String, CharSequence> _function_1 = (String it) -> {
return (("\"" + it) + "\"");
};
String _join = IterableExtensions.<String>join(((Iterable<String>)Conversions.doWrapArray(messageParts)), ", ", _function_1);
_builder.append(_join);
_builder.newLineIfNotEmpty();
_builder.append("but got");
_builder.newLine();
_builder.append(this.logEntries);
_builder.newLineIfNotEmpty();
Assert.fail(_builder.toString());
}
}
public LogCapture(final List<LoggingTester.LogEntry> logEntries) {
super();
this.logEntries = logEntries;
}
@Override
@Pure
public int hashCode() {
return 31 * 1 + ((this.logEntries== null) ? 0 : this.logEntries.hashCode());
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LoggingTester.LogCapture other = (LoggingTester.LogCapture) obj;
if (this.logEntries == null) {
if (other.logEntries != null)
return false;
} else if (!this.logEntries.equals(other.logEntries))
return false;
return true;
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("logEntries", this.logEntries);
return b.toString();
}
@Pure
public List<LoggingTester.LogEntry> getLogEntries() {
return this.logEntries;
}
}
@Data
public static class LogEntry {
private final String message;
private final String source;
private final long timeStamp;
private final Level level;
public LogEntry(final String message, final String source, final long timeStamp, final Level level) {
super();
this.message = message;
this.source = source;
this.timeStamp = timeStamp;
this.level = level;
}
@Override
@Pure
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.message== null) ? 0 : this.message.hashCode());
result = prime * result + ((this.source== null) ? 0 : this.source.hashCode());
result = prime * result + (int) (this.timeStamp ^ (this.timeStamp >>> 32));
return prime * result + ((this.level== null) ? 0 : this.level.hashCode());
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LoggingTester.LogEntry other = (LoggingTester.LogEntry) obj;
if (this.message == null) {
if (other.message != null)
return false;
} else if (!this.message.equals(other.message))
return false;
if (this.source == null) {
if (other.source != null)
return false;
} else if (!this.source.equals(other.source))
return false;
if (other.timeStamp != this.timeStamp)
return false;
if (this.level == null) {
if (other.level != null)
return false;
} else if (!this.level.equals(other.level))
return false;
return true;
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("message", this.message);
b.add("source", this.source);
b.add("timeStamp", this.timeStamp);
b.add("level", this.level);
return b.toString();
}
@Pure
public String getMessage() {
return this.message;
}
@Pure
public String getSource() {
return this.source;
}
@Pure
public long getTimeStamp() {
return this.timeStamp;
}
@Pure
public Level getLevel() {
return this.level;
}
}
private static class QueueAppender extends AppenderSkeleton {
@Accessors(AccessorType.PUBLIC_GETTER)
private final Queue<LoggingTester.LogEntry> events = new ConcurrentLinkedQueue<LoggingTester.LogEntry>();
@Override
public boolean requiresLayout() {
return false;
}
@Override
public void close() {
}
@Override
protected void append(final LoggingEvent event) {
String _renderedMessage = event.getRenderedMessage();
String _loggerName = event.getLoggerName();
long _timeStamp = event.getTimeStamp();
Level _level = event.getLevel();
final LoggingTester.LogEntry entry = new LoggingTester.LogEntry(_renderedMessage, _loggerName, _timeStamp, _level);
this.events.add(entry);
}
@Pure
public Queue<LoggingTester.LogEntry> getEvents() {
return this.events;
}
}
@Data
private static class SourceFilter extends Filter {
private final Logger source;
@Override
public int decide(final LoggingEvent event) {
int _xifexpression = (int) 0;
String _loggerName = event.getLoggerName();
String _name = this.source.getName();
boolean _equals = Objects.equal(_loggerName, _name);
if (_equals) {
_xifexpression = Filter.DENY;
} else {
_xifexpression = Filter.NEUTRAL;
}
return _xifexpression;
}
public SourceFilter(final Logger source) {
super();
this.source = source;
}
@Override
@Pure
public int hashCode() {
return 31 * 1 + ((this.source== null) ? 0 : this.source.hashCode());
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LoggingTester.SourceFilter other = (LoggingTester.SourceFilter) obj;
if (this.source == null) {
if (other.source != null)
return false;
} else if (!this.source.equals(other.source))
return false;
return true;
}
@Override
@Pure
public String toString() {
return new ToStringBuilder(this)
.addAllFields()
.toString();
}
@Pure
public Logger getSource() {
return this.source;
}
}
public static LoggingTester.LogCapture captureLogging(final Level level, final Class<?> source, final Runnable action) {
final Logger logger = Logger.getLogger(source);
final LoggingTester.QueueAppender appender = new LoggingTester.QueueAppender();
final Level oldLevel = logger.getLevel();
final ArrayList<Appender> allAppenders = LoggingTester.appenderHierarchy(logger);
final LoggingTester.SourceFilter filter = new LoggingTester.SourceFilter(logger);
try {
final Consumer<Appender> _function = (Appender it) -> {
it.addFilter(filter);
};
allAppenders.forEach(_function);
logger.addAppender(appender);
logger.setLevel(level);
action.run();
final List<LoggingTester.LogEntry> events = IterableExtensions.<LoggingTester.LogEntry>sortWith(IterableExtensions.<LoggingTester.LogEntry>toList(appender.events), LoggingTester.TEMPORAL_ORDER);
return new LoggingTester.LogCapture(events);
} finally {
logger.removeAppender(appender);
final Consumer<Appender> _function_1 = (Appender it) -> {
LoggingTester.removeFilter(it, filter);
};
allAppenders.forEach(_function_1);
logger.setLevel(oldLevel);
}
}
private static ArrayList<Appender> appenderHierarchy(final Logger logger) {
ArrayList<Appender> _xblockexpression = null;
{
final ArrayList<Appender> appenders = CollectionLiterals.<Appender>newArrayList();
for (Category current = logger; (current != null); current = current.getParent()) {
appenders.addAll(Collections.<Appender>list(current.getAllAppenders()));
}
_xblockexpression = appenders;
}
return _xblockexpression;
}
private static void removeFilter(final Appender appender, final Filter filter) {
Filter _filter = appender.getFilter();
boolean _equals = Objects.equal(_filter, filter);
if (_equals) {
appender.clearFilters();
appender.addFilter(filter.getNext());
} else {
for (Filter current = appender.getFilter(); (current != null); current = current.getNext()) {
Filter _next = current.getNext();
boolean _equals_1 = Objects.equal(_next, filter);
if (_equals_1) {
current.setNext(filter.getNext());
return;
}
}
}
}
private static final Comparator<LoggingTester.LogEntry> TEMPORAL_ORDER = ((Comparator<LoggingTester.LogEntry>) (LoggingTester.LogEntry $0, LoggingTester.LogEntry $1) -> {
return Longs.compare($0.timeStamp, $1.timeStamp);
});
}

View file

@ -6,12 +6,6 @@
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry including="**/*.java|**/*.xtend" kind="src" output="bin/main" path="xtend-gen">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/main"/>

View file

@ -2,7 +2,6 @@ bin.includes = .,\
META-INF/,\
plugin.properties,\
about.html
source.. = src/,\
xtend-gen/
source.. = src/
output.. = bin/main/
src.includes = about.html

View file

@ -1,5 +1,5 @@
/**
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* Copyright (c) 2015, 2020 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
@ -9,12 +9,10 @@
package org.eclipse.xtext.util.internal;
import org.eclipse.xtend.lib.macro.Active;
import org.eclipse.xtext.util.internal.EmfAdaptableProcessor;
/**
* @author Sven Efftinge - Initial contribution and API
*/
@Active(EmfAdaptableProcessor.class)
@SuppressWarnings("all")
public @interface EmfAdaptable {
}

View file

@ -1,109 +0,0 @@
/*******************************************************************************
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.util.internal
import org.eclipse.emf.common.notify.Adapter
import org.eclipse.emf.common.notify.Notifier
import org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.RegisterGlobalsContext
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.emf.common.notify.impl.AdapterImpl
import java.util.List
/**
* @author Sven Efftinge - Initial contribution and API
*/
@Active(EmfAdaptableProcessor)
annotation EmfAdaptable {
}
class EmfAdaptableProcessor extends AbstractClassProcessor {
override doRegisterGlobals(ClassDeclaration annotatedClass, extension RegisterGlobalsContext context) {
context.registerClass(annotatedClass.adapterClassName)
}
override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {
val adapterClass = findClass(annotatedClass.adapterClassName)
annotatedClass.addMethod('findInEmfObject') [
addParameter('emfObject', Notifier.newTypeReference)
returnType = annotatedClass.newTypeReference
static = true
body = '''
for («Adapter» adapter : emfObject.eAdapters()) {
if (adapter instanceof «adapterClass») {
return ((«adapterClass») adapter).get();
}
}
return null;
'''
]
annotatedClass.addMethod('removeFromEmfObject') [
addParameter('emfObject', Notifier.newTypeReference)
returnType = annotatedClass.newTypeReference
static = true
body = '''
«List»<«Adapter»> adapters = emfObject.eAdapters();
for(int i = 0, max = adapters.size(); i < max; i++) {
«Adapter» adapter = adapters.get(i);
if (adapter instanceof «adapterClass») {
emfObject.eAdapters().remove(i);
return ((«adapterClass») adapter).get();
}
}
return null;
'''
]
annotatedClass.addMethod('attachToEmfObject') [
addParameter('emfObject', Notifier.newTypeReference)
returnType = primitiveVoid
body = '''
«annotatedClass.simpleName» result = findInEmfObject(emfObject);
if (result != null)
throw new IllegalStateException("The given EMF object already contains an adapter for «annotatedClass.simpleName»");
«adapterClass» adapter = new «adapterClass»(this);
emfObject.eAdapters().add(adapter);
'''
]
adapterClass.extendedClass = AdapterImpl.newTypeReference
adapterClass.addField('element') [
type = annotatedClass.newTypeReference
]
adapterClass.addConstructor[
addParameter('element', annotatedClass.newTypeReference)
body = '''
this.element = element;
'''
]
adapterClass.addMethod('get') [
returnType = annotatedClass.newTypeReference
body = '''
return this.element;
'''
]
adapterClass.addMethod('isAdapterForType') [
addAnnotation(newAnnotationReference(Override))
addParameter("object", Object.newTypeReference)
returnType = primitiveBoolean
body = '''
return object == «annotatedClass».class;
'''
]
}
def String getAdapterClassName(ClassDeclaration declaration) {
return declaration.qualifiedName+'.'+declaration.simpleName+"Adapter"
}
}

View file

@ -0,0 +1,173 @@
/**
* Copyright (c) 2015, 2020 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.util.internal;
import java.util.List;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.xtend.lib.macro.AbstractClassProcessor;
import org.eclipse.xtend.lib.macro.RegisterGlobalsContext;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.xbase.lib.Extension;
public class EmfAdaptableProcessor extends AbstractClassProcessor {
@Override
public void doRegisterGlobals(ClassDeclaration annotatedClass, @Extension RegisterGlobalsContext context) {
context.registerClass(getAdapterClassName(annotatedClass));
}
@Override
public void doTransform(MutableClassDeclaration annotatedClass, @Extension TransformationContext context) {
MutableClassDeclaration adapterClass = context.findClass(getAdapterClassName(annotatedClass));
annotatedClass.addMethod("findInEmfObject", it -> {
it.addParameter("emfObject", context.newTypeReference(Notifier.class));
it.setReturnType(context.newTypeReference(annotatedClass));
it.setStatic(true);
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append("for (");
builder.append(Adapter.class);
builder.append(" adapter : emfObject.eAdapters()) {");
builder.newLineIfNotEmpty();
builder.append("\t");
builder.append("if (adapter instanceof ");
builder.append(adapterClass, "\t");
builder.append(") {");
builder.newLineIfNotEmpty();
builder.append("\t\t");
builder.append("return ((");
builder.append(adapterClass, "\t\t");
builder.append(") adapter).get();");
builder.newLineIfNotEmpty();
builder.append("\t");
builder.append("}");
builder.newLine();
builder.append("}");
builder.newLine();
builder.append("return null;");
builder.newLine();
}
});
});
annotatedClass.addMethod("removeFromEmfObject", it -> {
it.addParameter("emfObject", context.newTypeReference(Notifier.class));
it.setReturnType(context.newTypeReference(annotatedClass));
it.setStatic(true);
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append(List.class);
builder.append("<");
builder.append(Adapter.class);
builder.append("> adapters = emfObject.eAdapters();");
builder.newLineIfNotEmpty();
builder.append("for(int i = 0, max = adapters.size(); i < max; i++) {");
builder.newLine();
builder.append("\t");
builder.append(Adapter.class, "\t");
builder.append(" adapter = adapters.get(i);");
builder.newLineIfNotEmpty();
builder.append("\t");
builder.append("if (adapter instanceof ");
builder.append(adapterClass, "\t");
builder.append(") {");
builder.newLineIfNotEmpty();
builder.append("\t\t");
builder.append("emfObject.eAdapters().remove(i);");
builder.newLine();
builder.append("\t\t");
builder.append("return ((");
builder.append(adapterClass, "\t\t");
builder.append(") adapter).get();");
builder.newLineIfNotEmpty();
builder.append("\t");
builder.append("}");
builder.newLine();
builder.append("}");
builder.newLine();
builder.append("return null;");
builder.newLine();
}
});
});
annotatedClass.addMethod("attachToEmfObject", it -> {
it.addParameter("emfObject", context.newTypeReference(Notifier.class));
it.setReturnType(context.getPrimitiveVoid());
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append(annotatedClass.getSimpleName());
builder.append(" result = findInEmfObject(emfObject);");
builder.newLineIfNotEmpty();
builder.append("if (result != null)");
builder.newLine();
builder.append("\t");
builder.append(
"throw new IllegalStateException(\"The given EMF object already contains an adapter for ");
builder.append(annotatedClass.getSimpleName(), "\t");
builder.append("\");");
builder.newLineIfNotEmpty();
builder.append(adapterClass);
builder.append(" adapter = new ");
builder.append(adapterClass);
builder.append("(this);");
builder.newLineIfNotEmpty();
builder.append("emfObject.eAdapters().add(adapter);");
builder.newLine();
}
});
});
adapterClass.setExtendedClass(context.newTypeReference(AdapterImpl.class));
adapterClass.addField("element", it -> it.setType(context.newTypeReference(annotatedClass)));
adapterClass.addConstructor(it -> {
it.addParameter("element", context.newTypeReference(annotatedClass));
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append("this.element = element;");
builder.newLine();
}
});
});
adapterClass.addMethod("get", it -> {
it.setReturnType(context.newTypeReference(annotatedClass));
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append("return this.element;");
builder.newLine();
}
});
});
adapterClass.addMethod("isAdapterForType", it -> {
it.addAnnotation(context.newAnnotationReference(Override.class));
it.addParameter("object", context.newTypeReference(Object.class));
it.setReturnType(context.getPrimitiveBoolean());
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append("return object == ");
builder.append(annotatedClass);
builder.append(".class;");
builder.newLineIfNotEmpty();
}
});
});
}
public String getAdapterClassName(ClassDeclaration declaration) {
return declaration.getQualifiedName() + "." + declaration.getSimpleName() + "Adapter";
}
}

View file

@ -1,197 +0,0 @@
/**
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.util.internal;
import java.util.List;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.xtend.lib.macro.AbstractClassProcessor;
import org.eclipse.xtend.lib.macro.RegisterGlobalsContext;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableConstructorDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
@SuppressWarnings("all")
public class EmfAdaptableProcessor extends AbstractClassProcessor {
@Override
public void doRegisterGlobals(final ClassDeclaration annotatedClass, @Extension final RegisterGlobalsContext context) {
context.registerClass(this.getAdapterClassName(annotatedClass));
}
@Override
public void doTransform(final MutableClassDeclaration annotatedClass, @Extension final TransformationContext context) {
final MutableClassDeclaration adapterClass = context.findClass(this.getAdapterClassName(annotatedClass));
final Procedure1<MutableMethodDeclaration> _function = (MutableMethodDeclaration it) -> {
it.addParameter("emfObject", context.newTypeReference(Notifier.class));
it.setReturnType(context.newTypeReference(annotatedClass));
it.setStatic(true);
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("for (");
_builder.append(Adapter.class);
_builder.append(" adapter : emfObject.eAdapters()) {");
_builder.newLineIfNotEmpty();
_builder.append("\t");
_builder.append("if (adapter instanceof ");
_builder.append(adapterClass, "\t");
_builder.append(") {");
_builder.newLineIfNotEmpty();
_builder.append("\t\t");
_builder.append("return ((");
_builder.append(adapterClass, "\t\t");
_builder.append(") adapter).get();");
_builder.newLineIfNotEmpty();
_builder.append("\t");
_builder.append("}");
_builder.newLine();
_builder.append("}");
_builder.newLine();
_builder.append("return null;");
_builder.newLine();
}
};
it.setBody(_client);
};
annotatedClass.addMethod("findInEmfObject", _function);
final Procedure1<MutableMethodDeclaration> _function_1 = (MutableMethodDeclaration it) -> {
it.addParameter("emfObject", context.newTypeReference(Notifier.class));
it.setReturnType(context.newTypeReference(annotatedClass));
it.setStatic(true);
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append(List.class);
_builder.append("<");
_builder.append(Adapter.class);
_builder.append("> adapters = emfObject.eAdapters();");
_builder.newLineIfNotEmpty();
_builder.append("for(int i = 0, max = adapters.size(); i < max; i++) {");
_builder.newLine();
_builder.append("\t");
_builder.append(Adapter.class, "\t");
_builder.append(" adapter = adapters.get(i);");
_builder.newLineIfNotEmpty();
_builder.append("\t");
_builder.append("if (adapter instanceof ");
_builder.append(adapterClass, "\t");
_builder.append(") {");
_builder.newLineIfNotEmpty();
_builder.append("\t\t");
_builder.append("emfObject.eAdapters().remove(i);");
_builder.newLine();
_builder.append("\t\t");
_builder.append("return ((");
_builder.append(adapterClass, "\t\t");
_builder.append(") adapter).get();");
_builder.newLineIfNotEmpty();
_builder.append("\t");
_builder.append("}");
_builder.newLine();
_builder.append("}");
_builder.newLine();
_builder.append("return null;");
_builder.newLine();
}
};
it.setBody(_client);
};
annotatedClass.addMethod("removeFromEmfObject", _function_1);
final Procedure1<MutableMethodDeclaration> _function_2 = (MutableMethodDeclaration it) -> {
it.addParameter("emfObject", context.newTypeReference(Notifier.class));
it.setReturnType(context.getPrimitiveVoid());
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
String _simpleName = annotatedClass.getSimpleName();
_builder.append(_simpleName);
_builder.append(" result = findInEmfObject(emfObject);");
_builder.newLineIfNotEmpty();
_builder.append("if (result != null)");
_builder.newLine();
_builder.append("\t");
_builder.append("throw new IllegalStateException(\"The given EMF object already contains an adapter for ");
String _simpleName_1 = annotatedClass.getSimpleName();
_builder.append(_simpleName_1, "\t");
_builder.append("\");");
_builder.newLineIfNotEmpty();
_builder.append(adapterClass);
_builder.append(" adapter = new ");
_builder.append(adapterClass);
_builder.append("(this);");
_builder.newLineIfNotEmpty();
_builder.append("emfObject.eAdapters().add(adapter);");
_builder.newLine();
}
};
it.setBody(_client);
};
annotatedClass.addMethod("attachToEmfObject", _function_2);
adapterClass.setExtendedClass(context.newTypeReference(AdapterImpl.class));
final Procedure1<MutableFieldDeclaration> _function_3 = (MutableFieldDeclaration it) -> {
it.setType(context.newTypeReference(annotatedClass));
};
adapterClass.addField("element", _function_3);
final Procedure1<MutableConstructorDeclaration> _function_4 = (MutableConstructorDeclaration it) -> {
it.addParameter("element", context.newTypeReference(annotatedClass));
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("this.element = element;");
_builder.newLine();
}
};
it.setBody(_client);
};
adapterClass.addConstructor(_function_4);
final Procedure1<MutableMethodDeclaration> _function_5 = (MutableMethodDeclaration it) -> {
it.setReturnType(context.newTypeReference(annotatedClass));
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("return this.element;");
_builder.newLine();
}
};
it.setBody(_client);
};
adapterClass.addMethod("get", _function_5);
final Procedure1<MutableMethodDeclaration> _function_6 = (MutableMethodDeclaration it) -> {
it.addAnnotation(context.newAnnotationReference(Override.class));
it.addParameter("object", context.newTypeReference(Object.class));
it.setReturnType(context.getPrimitiveBoolean());
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("return object == ");
_builder.append(annotatedClass);
_builder.append(".class;");
_builder.newLineIfNotEmpty();
}
};
it.setBody(_client);
};
adapterClass.addMethod("isAdapterForType", _function_6);
}
public String getAdapterClassName(final ClassDeclaration declaration) {
String _qualifiedName = declaration.getQualifiedName();
String _plus = (_qualifiedName + ".");
String _simpleName = declaration.getSimpleName();
String _plus_1 = (_plus + _simpleName);
return (_plus_1 + "Adapter");
}
}

View file

@ -6,24 +6,18 @@
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry including="**/*.java|**/*.xtend" kind="src" output="bin/main" path="xtend-gen">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/main" path="src-gen">
<attributes>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
<attribute name="ignore_optional_problems" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/main" path="emf-gen">
<attributes>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
<attribute name="ignore_optional_problems" value="true"/>
</attributes>
</classpathentry>
<classpathentry including="**/*.java|**/*.xtend" kind="src" output="bin/main" path="packrat">

View file

@ -12,7 +12,6 @@ bin.includes = .,\
source.. = src-gen/,\
emf-gen/,\
packrat/,\
src/,\
xtend-gen/
src/
output.. = bin/main/
src.includes = about.html

View file

@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others.
* Copyright (c) 2017, 2020 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
@ -9,14 +9,13 @@
package org.eclipse.xtext.generator.trace.node;
import org.eclipse.xtend.lib.macro.Active;
import org.eclipse.xtext.generator.trace.node.TracedProcessor;
/**
* @author Sven Efftinge - Initial contribution and API
*/
@Active(TracedProcessor.class)
@SuppressWarnings("all")
public @interface Traced {
public String tracingSugarFieldName() default "_traceExtensions";
public boolean useForDebugging() default false;
public String tracingSugarFieldName() default "_traceExtensions";
public boolean useForDebugging() default false;
}

View file

@ -1,67 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.generator.trace.node
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtend.lib.macro.AbstractMethodProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration
import org.eclipse.xtend2.lib.StringConcatenationClient
import org.eclipse.xtext.generator.trace.ILocationData
/**
* @author Sven Efftinge - Initial contribution and API
*/
@Active(TracedProcessor)
annotation Traced {
String tracingSugarFieldName = '_traceExtensions'
boolean useForDebugging = false
}
class TracedProcessor extends AbstractMethodProcessor {
override doTransform(MutableMethodDeclaration annotatedMethod, extension TransformationContext context) {
val useForDebugging = annotatedMethod.findAnnotation(Traced.findTypeGlobally).getBooleanValue("useForDebugging")
val traceSugar = TracingSugar.newTypeReference
val templateClient = StringConcatenationClient.newTypeReference
val nodeType = IGeneratorNode.newTypeReference
val eobjectType = EObject.newTypeReference
val clazz = annotatedMethod.declaringType as MutableClassDeclaration
val field = clazz.declaredFields.findFirst[
traceSugar.isAssignableFrom(type)
]
if (field === null) {
annotatedMethod.addError('''@«Traced.simpleName» methods can only declared in a class with a field of type «TracingSugar»''')
return;
}
val traceParam = annotatedMethod.parameters.findFirst[eobjectType.isAssignableFrom(type)]
if (traceParam === null) {
annotatedMethod.addError('''@«Traced.simpleName» methods need at least one parameter of type «EObject».''')
return;
}
clazz.addMethod('_'+annotatedMethod.simpleName) [
for (p : annotatedMethod.parameters) {
addParameter(p.simpleName, p.type)
}
returnType = templateClient
body = annotatedMethod.body
]
annotatedMethod.returnType = nodeType
annotatedMethod.body = '''
«ILocationData» _location = this.«field.simpleName».location(«traceParam.simpleName»);
«CompositeGeneratorNode» _traceNode = this.«field.simpleName».trace(_location, «useForDebugging»);
this.«field.simpleName».appendTemplate(_traceNode, _«annotatedMethod.simpleName»(«annotatedMethod.parameters.join(',')[simpleName]»));
return _traceNode;
'''
}
}

View file

@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others.
* Copyright (c) 2017, 2020 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
@ -10,13 +10,11 @@ package org.eclipse.xtext.generator.trace.node;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.xtend.lib.macro.Active;
import org.eclipse.xtext.generator.trace.node.TracedAccessorsProcessor;
/**
* @author Sven Efftinge - Initial contribution and API
*/
@Active(TracedAccessorsProcessor.class)
@SuppressWarnings("all")
public @interface TracedAccessors {
public Class<? extends EFactory>[] value();
public Class<? extends EFactory>[] value();
}

View file

@ -1,109 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.generator.trace.node
import java.util.function.Function
import org.eclipse.emf.ecore.EFactory
import org.eclipse.emf.ecore.EStructuralFeature
import org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.InterfaceDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.ResolvedMethod
import org.eclipse.xtext.generator.trace.ILocationData
/**
* @author Sven Efftinge - Initial contribution and API
*/
@Active(TracedAccessorsProcessor)
annotation TracedAccessors {
Class<? extends EFactory>[] value
}
class TracedAccessorsProcessor extends AbstractClassProcessor {
override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {
annotatedClass.extendedClass = TracingSugar.newTypeReference()
val iterableType = Iterable.newTypeReference(newWildcardTypeReference)
val annotationType = TracedAccessors.newTypeReference
val factories = annotatedClass.findAnnotation(annotationType.type)?.getClassArrayValue("value")
if (factories === null) {
return;
}
for (f : factories.map[type].filter(InterfaceDeclaration)) {
for (t: f.declaredMethods.filter[simpleName.startsWith('create') && parameters.empty].map[returnType]) {
for (getter : t.allResolvedMethods.filter[ isSupportedGetter].filter[!iterableType.isAssignableFrom(declaration.returnType)]) {
val rt = getter.resolvedReturnType
if (TYPES_WITH_GOOD_TO_STRING.contains(rt.type.simpleName.toLowerCase)) {
annotatedClass.addMethod(getter.tracerName) [
returnType = IGeneratorNode.newTypeReference()
addParameter('target', t)
body = '''
«EStructuralFeature» feature = target.eClass().getEStructuralFeature("«getter.featureName»");
«ILocationData» location = this.location(target, feature, -1);
«CompositeGeneratorNode» trace = this.trace(location);
this.append(trace, target.«getter.declaration.simpleName»());
return trace;
'''
]
annotatedClass.addMethod(getter.tracerName) [
returnType = IGeneratorNode.newTypeReference()
addParameter('target', t)
addParameter('useForDebugging', Boolean.TYPE.newTypeReference())
body = '''
«EStructuralFeature» feature = target.eClass().getEStructuralFeature("«getter.featureName»");
«ILocationData» location = this.location(target, feature, -1);
«CompositeGeneratorNode» trace = this.trace(location, useForDebugging);
this.append(trace, target.«getter.declaration.simpleName»());
return trace;
'''
]
}
annotatedClass.addMethod(getter.tracerName) [
returnType = IGeneratorNode.newTypeReference
addParameter('target', t)
val stringProvider = Function.newTypeReference(rt, string)
addParameter('stringProvider', stringProvider)
body = '''
«EStructuralFeature» feature = target.eClass().getEStructuralFeature("«getter.featureName»");
«ILocationData» location = this.location(target, feature, -1);
«CompositeGeneratorNode» trace = this.trace(location);
this.append(trace, stringProvider.apply(target.«getter.declaration.simpleName»()));
return trace;
'''
]
}
}
}
}
def String tracerName(ResolvedMethod m) {
'_'+m.featureName
}
def String featureName(ResolvedMethod m) {
val n = m.declaration.simpleName
val skip = if (n.startsWith('get')) 3 else 2
m.declaration.simpleName.substring(skip).toFirstLower
}
static val TYPES_WITH_GOOD_TO_STRING = #{'string','boolean','int','long','integer'}
def boolean isSupportedGetter(ResolvedMethod it) {
if (!declaration.parameters.empty)
return false
if (declaration.static)
return false
val n = declaration.simpleName
if (declaration.declaringType.qualifiedName == Object.name)
return false
return n.startsWith('get') || n.startsWith('is')
}
}

View file

@ -0,0 +1,169 @@
/**
* Copyright (c) 2017, 2020 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.generator.trace.node;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.function.Function;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtend.lib.macro.AbstractClassProcessor;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.AnnotationReference;
import org.eclipse.xtend.lib.macro.declaration.InterfaceDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.ResolvedMethod;
import org.eclipse.xtend.lib.macro.declaration.TypeReference;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.generator.trace.ILocationData;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.ListExtensions;
import org.eclipse.xtext.xbase.lib.StringExtensions;
import com.google.common.collect.Iterables;
public class TracedAccessorsProcessor extends AbstractClassProcessor {
private static final Set<String> TYPES_WITH_GOOD_TO_STRING = Collections
.unmodifiableSet(CollectionLiterals.newHashSet("string", "boolean", "int", "long", "integer"));
@Override
public void doTransform(MutableClassDeclaration annotatedClass, @Extension TransformationContext context) {
annotatedClass.setExtendedClass(context.newTypeReference(TracingSugar.class));
TypeReference iterableType = context.newTypeReference(Iterable.class, context.newWildcardTypeReference());
TypeReference annotationType = context.newTypeReference(TracedAccessors.class);
AnnotationReference annotation = annotatedClass.findAnnotation(annotationType.getType());
TypeReference[] factories = annotation == null ? null : annotation.getClassArrayValue("value");
if (factories == null)
return;
for (InterfaceDeclaration f : Iterables.filter(ListExtensions.map(Arrays.asList(factories), it -> it.getType()),
InterfaceDeclaration.class)) {
for (TypeReference t : IterableExtensions.map(
IterableExtensions
.filter(f.getDeclaredMethods(),
it -> it.getSimpleName().startsWith("create")
&& IterableExtensions.isEmpty(it.getParameters())),
it -> it.getReturnType())) {
for (ResolvedMethod getter : IterableExtensions.filter(
IterableExtensions.filter(t.getAllResolvedMethods(), it -> isSupportedGetter(it)),
it -> !iterableType.isAssignableFrom(it.getDeclaration().getReturnType()))) {
TypeReference rt = getter.getResolvedReturnType();
if (TracedAccessorsProcessor.TYPES_WITH_GOOD_TO_STRING
.contains(rt.getType().getSimpleName().toLowerCase())) {
annotatedClass.addMethod(tracerName(getter), it -> {
it.setReturnType(context.newTypeReference(IGeneratorNode.class));
it.addParameter("target", t);
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append(EStructuralFeature.class);
builder.append(" feature = target.eClass().getEStructuralFeature(\"");
builder.append(TracedAccessorsProcessor.this.featureName(getter));
builder.append("\");");
builder.newLineIfNotEmpty();
builder.append(ILocationData.class);
builder.append(" location = this.location(target, feature, -1);");
builder.newLineIfNotEmpty();
builder.append(CompositeGeneratorNode.class);
builder.append(" trace = this.trace(location);");
builder.newLineIfNotEmpty();
builder.append("this.append(trace, target.");
builder.append(getter.getDeclaration().getSimpleName());
builder.append("());");
builder.newLineIfNotEmpty();
builder.append("return trace;");
builder.newLine();
}
});
});
annotatedClass.addMethod(tracerName(getter), it -> {
it.setReturnType(context.newTypeReference(IGeneratorNode.class));
it.addParameter("target", t);
it.addParameter("useForDebugging", context.newTypeReference(Boolean.TYPE));
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append(EStructuralFeature.class);
builder.append(" feature = target.eClass().getEStructuralFeature(\"");
builder.append(TracedAccessorsProcessor.this.featureName(getter));
builder.append("\");");
builder.newLineIfNotEmpty();
builder.append(ILocationData.class);
builder.append(" location = this.location(target, feature, -1);");
builder.newLineIfNotEmpty();
builder.append(CompositeGeneratorNode.class);
builder.append(" trace = this.trace(location, useForDebugging);");
builder.newLineIfNotEmpty();
builder.append("this.append(trace, target.");
builder.append(getter.getDeclaration().getSimpleName());
builder.append("());");
builder.newLineIfNotEmpty();
builder.append("return trace;");
builder.newLine();
}
});
});
}
annotatedClass.addMethod(this.tracerName(getter), (MutableMethodDeclaration it) -> {
it.setReturnType(context.newTypeReference(IGeneratorNode.class));
it.addParameter("target", t);
it.addParameter("stringProvider",
context.newTypeReference(Function.class, rt, context.getString()));
it.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append(EStructuralFeature.class);
builder.append(" feature = target.eClass().getEStructuralFeature(\"");
builder.append(TracedAccessorsProcessor.this.featureName(getter));
builder.append("\");");
builder.newLineIfNotEmpty();
builder.append(ILocationData.class);
builder.append(" location = this.location(target, feature, -1);");
builder.newLineIfNotEmpty();
builder.append(CompositeGeneratorNode.class);
builder.append(" trace = this.trace(location);");
builder.newLineIfNotEmpty();
builder.append("this.append(trace, stringProvider.apply(target.");
builder.append(getter.getDeclaration().getSimpleName());
builder.append("()));");
builder.newLineIfNotEmpty();
builder.append("return trace;");
builder.newLine();
}
});
});
}
}
}
}
public String tracerName(ResolvedMethod m) {
return "_" + featureName(m);
}
public String featureName(ResolvedMethod m) {
int skip = m.getDeclaration().getSimpleName().startsWith("get") ? 3 : 2;
return StringExtensions.toFirstLower(m.getDeclaration().getSimpleName().substring(skip));
}
public boolean isSupportedGetter(ResolvedMethod it) {
if (!IterableExtensions.isEmpty(it.getDeclaration().getParameters()))
return false;
if (it.getDeclaration().isStatic())
return false;
String n = it.getDeclaration().getSimpleName();
if (Object.class.getName().equals(it.getDeclaration().getDeclaringType().getQualifiedName()))
return false;
return n.startsWith("get") || n.startsWith("is");
}
}

View file

@ -0,0 +1,85 @@
/**
* Copyright (c) 2017, 2020 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.generator.trace.node;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtend.lib.macro.AbstractMethodProcessor;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableParameterDeclaration;
import org.eclipse.xtend.lib.macro.declaration.TypeReference;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.generator.trace.ILocationData;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
public class TracedProcessor extends AbstractMethodProcessor {
@Override
public void doTransform(MutableMethodDeclaration annotatedMethod, @Extension TransformationContext context) {
boolean useForDebugging = annotatedMethod.findAnnotation(context.findTypeGlobally(Traced.class))
.getBooleanValue("useForDebugging");
TypeReference traceSugar = context.newTypeReference(TracingSugar.class);
TypeReference templateClient = context.newTypeReference(StringConcatenationClient.class);
TypeReference nodeType = context.newTypeReference(IGeneratorNode.class);
TypeReference eobjectType = context.newTypeReference(EObject.class);
MutableClassDeclaration clazz = (MutableClassDeclaration) annotatedMethod.getDeclaringType();
MutableFieldDeclaration field = IterableExtensions.findFirst(clazz.getDeclaredFields(),
it -> Boolean.valueOf(traceSugar.isAssignableFrom(it.getType())));
if (field == null) {
context.addError(annotatedMethod, "@" + Traced.class.getSimpleName()
+ " methods can only declared in a class with a field of type " + TracingSugar.class);
return;
}
MutableParameterDeclaration traceParam = IterableExtensions.findFirst(annotatedMethod.getParameters(),
it -> eobjectType.isAssignableFrom(it.getType()));
if (traceParam == null) {
context.addError(annotatedMethod, "@" + Traced.class.getSimpleName()
+ " methods need at least one parameter of type " + EObject.class + ".");
return;
}
clazz.addMethod("_" + annotatedMethod.getSimpleName(), it -> {
for (MutableParameterDeclaration p : annotatedMethod.getParameters())
it.addParameter(p.getSimpleName(), p.getType());
it.setReturnType(templateClient);
it.setBody(annotatedMethod.getBody());
});
annotatedMethod.setReturnType(nodeType);
annotatedMethod.setBody(new StringConcatenationClient() {
@Override
protected void appendTo(TargetStringConcatenation builder) {
builder.append(ILocationData.class);
builder.append(" _location = this.");
builder.append(field.getSimpleName());
builder.append(".location(");
builder.append(traceParam.getSimpleName());
builder.append(");");
builder.newLineIfNotEmpty();
builder.append(CompositeGeneratorNode.class);
builder.append(" _traceNode = this.");
builder.append(field.getSimpleName());
builder.append(".trace(_location, ");
builder.append(useForDebugging);
builder.append(");");
builder.newLineIfNotEmpty();
builder.append("this.");
builder.append(field.getSimpleName());
builder.append(".appendTemplate(_traceNode, _");
builder.append(annotatedMethod.getSimpleName());
builder.append("(");
builder.append(IterableExtensions.join(annotatedMethod.getParameters(), ",", it -> it.getSimpleName()));
builder.append("));");
builder.newLineIfNotEmpty();
builder.append("return _traceNode;");
builder.newLine();
}
});
}
}

View file

@ -1,228 +0,0 @@
/**
* Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.generator.trace.node;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtend.lib.macro.AbstractClassProcessor;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.AnnotationReference;
import org.eclipse.xtend.lib.macro.declaration.InterfaceDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.ResolvedMethod;
import org.eclipse.xtend.lib.macro.declaration.Type;
import org.eclipse.xtend.lib.macro.declaration.TypeReference;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.generator.trace.ILocationData;
import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode;
import org.eclipse.xtext.generator.trace.node.IGeneratorNode;
import org.eclipse.xtext.generator.trace.node.TracedAccessors;
import org.eclipse.xtext.generator.trace.node.TracingSugar;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.ListExtensions;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import org.eclipse.xtext.xbase.lib.StringExtensions;
@SuppressWarnings("all")
public class TracedAccessorsProcessor extends AbstractClassProcessor {
@Override
public void doTransform(final MutableClassDeclaration annotatedClass, @Extension final TransformationContext context) {
annotatedClass.setExtendedClass(context.newTypeReference(TracingSugar.class));
final TypeReference iterableType = context.newTypeReference(Iterable.class, context.newWildcardTypeReference());
final TypeReference annotationType = context.newTypeReference(TracedAccessors.class);
AnnotationReference _findAnnotation = annotatedClass.findAnnotation(annotationType.getType());
TypeReference[] _classArrayValue = null;
if (_findAnnotation!=null) {
_classArrayValue=_findAnnotation.getClassArrayValue("value");
}
final TypeReference[] factories = _classArrayValue;
if ((factories == null)) {
return;
}
final Function1<TypeReference, Type> _function = (TypeReference it) -> {
return it.getType();
};
Iterable<InterfaceDeclaration> _filter = Iterables.<InterfaceDeclaration>filter(ListExtensions.<TypeReference, Type>map(((List<TypeReference>)Conversions.doWrapArray(factories)), _function), InterfaceDeclaration.class);
for (final InterfaceDeclaration f : _filter) {
final Function1<MethodDeclaration, Boolean> _function_1 = (MethodDeclaration it) -> {
return Boolean.valueOf((it.getSimpleName().startsWith("create") && IterableExtensions.isEmpty(it.getParameters())));
};
final Function1<MethodDeclaration, TypeReference> _function_2 = (MethodDeclaration it) -> {
return it.getReturnType();
};
Iterable<TypeReference> _map = IterableExtensions.map(IterableExtensions.filter(f.getDeclaredMethods(), _function_1), _function_2);
for (final TypeReference t : _map) {
final Function1<ResolvedMethod, Boolean> _function_3 = (ResolvedMethod it) -> {
return Boolean.valueOf(this.isSupportedGetter(it));
};
final Function1<ResolvedMethod, Boolean> _function_4 = (ResolvedMethod it) -> {
boolean _isAssignableFrom = iterableType.isAssignableFrom(it.getDeclaration().getReturnType());
return Boolean.valueOf((!_isAssignableFrom));
};
Iterable<? extends ResolvedMethod> _filter_1 = IterableExtensions.filter(IterableExtensions.filter(t.getAllResolvedMethods(), _function_3), _function_4);
for (final ResolvedMethod getter : _filter_1) {
{
final TypeReference rt = getter.getResolvedReturnType();
boolean _contains = TracedAccessorsProcessor.TYPES_WITH_GOOD_TO_STRING.contains(rt.getType().getSimpleName().toLowerCase());
if (_contains) {
final Procedure1<MutableMethodDeclaration> _function_5 = (MutableMethodDeclaration it) -> {
it.setReturnType(context.newTypeReference(IGeneratorNode.class));
it.addParameter("target", t);
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append(EStructuralFeature.class);
_builder.append(" feature = target.eClass().getEStructuralFeature(\"");
String _featureName = TracedAccessorsProcessor.this.featureName(getter);
_builder.append(_featureName);
_builder.append("\");");
_builder.newLineIfNotEmpty();
_builder.append(ILocationData.class);
_builder.append(" location = this.location(target, feature, -1);");
_builder.newLineIfNotEmpty();
_builder.append(CompositeGeneratorNode.class);
_builder.append(" trace = this.trace(location);");
_builder.newLineIfNotEmpty();
_builder.append("this.append(trace, target.");
String _simpleName = getter.getDeclaration().getSimpleName();
_builder.append(_simpleName);
_builder.append("());");
_builder.newLineIfNotEmpty();
_builder.append("return trace;");
_builder.newLine();
}
};
it.setBody(_client);
};
annotatedClass.addMethod(this.tracerName(getter), _function_5);
final Procedure1<MutableMethodDeclaration> _function_6 = (MutableMethodDeclaration it) -> {
it.setReturnType(context.newTypeReference(IGeneratorNode.class));
it.addParameter("target", t);
it.addParameter("useForDebugging", context.newTypeReference(Boolean.TYPE));
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append(EStructuralFeature.class);
_builder.append(" feature = target.eClass().getEStructuralFeature(\"");
String _featureName = TracedAccessorsProcessor.this.featureName(getter);
_builder.append(_featureName);
_builder.append("\");");
_builder.newLineIfNotEmpty();
_builder.append(ILocationData.class);
_builder.append(" location = this.location(target, feature, -1);");
_builder.newLineIfNotEmpty();
_builder.append(CompositeGeneratorNode.class);
_builder.append(" trace = this.trace(location, useForDebugging);");
_builder.newLineIfNotEmpty();
_builder.append("this.append(trace, target.");
String _simpleName = getter.getDeclaration().getSimpleName();
_builder.append(_simpleName);
_builder.append("());");
_builder.newLineIfNotEmpty();
_builder.append("return trace;");
_builder.newLine();
}
};
it.setBody(_client);
};
annotatedClass.addMethod(this.tracerName(getter), _function_6);
}
final Procedure1<MutableMethodDeclaration> _function_7 = (MutableMethodDeclaration it) -> {
it.setReturnType(context.newTypeReference(IGeneratorNode.class));
it.addParameter("target", t);
final TypeReference stringProvider = context.newTypeReference(Function.class, rt, context.getString());
it.addParameter("stringProvider", stringProvider);
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append(EStructuralFeature.class);
_builder.append(" feature = target.eClass().getEStructuralFeature(\"");
String _featureName = TracedAccessorsProcessor.this.featureName(getter);
_builder.append(_featureName);
_builder.append("\");");
_builder.newLineIfNotEmpty();
_builder.append(ILocationData.class);
_builder.append(" location = this.location(target, feature, -1);");
_builder.newLineIfNotEmpty();
_builder.append(CompositeGeneratorNode.class);
_builder.append(" trace = this.trace(location);");
_builder.newLineIfNotEmpty();
_builder.append("this.append(trace, stringProvider.apply(target.");
String _simpleName = getter.getDeclaration().getSimpleName();
_builder.append(_simpleName);
_builder.append("()));");
_builder.newLineIfNotEmpty();
_builder.append("return trace;");
_builder.newLine();
}
};
it.setBody(_client);
};
annotatedClass.addMethod(this.tracerName(getter), _function_7);
}
}
}
}
}
public String tracerName(final ResolvedMethod m) {
String _featureName = this.featureName(m);
return ("_" + _featureName);
}
public String featureName(final ResolvedMethod m) {
String _xblockexpression = null;
{
final String n = m.getDeclaration().getSimpleName();
int _xifexpression = (int) 0;
boolean _startsWith = n.startsWith("get");
if (_startsWith) {
_xifexpression = 3;
} else {
_xifexpression = 2;
}
final int skip = _xifexpression;
_xblockexpression = StringExtensions.toFirstLower(m.getDeclaration().getSimpleName().substring(skip));
}
return _xblockexpression;
}
private static final Set<String> TYPES_WITH_GOOD_TO_STRING = Collections.<String>unmodifiableSet(CollectionLiterals.<String>newHashSet("string", "boolean", "int", "long", "integer"));
public boolean isSupportedGetter(final ResolvedMethod it) {
boolean _isEmpty = IterableExtensions.isEmpty(it.getDeclaration().getParameters());
boolean _not = (!_isEmpty);
if (_not) {
return false;
}
boolean _isStatic = it.getDeclaration().isStatic();
if (_isStatic) {
return false;
}
final String n = it.getDeclaration().getSimpleName();
String _qualifiedName = it.getDeclaration().getDeclaringType().getQualifiedName();
String _name = Object.class.getName();
boolean _equals = Objects.equal(_qualifiedName, _name);
if (_equals) {
return false;
}
return (n.startsWith("get") || n.startsWith("is"));
}
}

View file

@ -1,124 +0,0 @@
/**
* Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.xtext.generator.trace.node;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtend.lib.macro.AbstractMethodProcessor;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableParameterDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableTypeDeclaration;
import org.eclipse.xtend.lib.macro.declaration.TypeReference;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.generator.trace.ILocationData;
import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode;
import org.eclipse.xtext.generator.trace.node.IGeneratorNode;
import org.eclipse.xtext.generator.trace.node.Traced;
import org.eclipse.xtext.generator.trace.node.TracingSugar;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
@SuppressWarnings("all")
public class TracedProcessor extends AbstractMethodProcessor {
@Override
public void doTransform(final MutableMethodDeclaration annotatedMethod, @Extension final TransformationContext context) {
final boolean useForDebugging = annotatedMethod.findAnnotation(context.findTypeGlobally(Traced.class)).getBooleanValue("useForDebugging");
final TypeReference traceSugar = context.newTypeReference(TracingSugar.class);
final TypeReference templateClient = context.newTypeReference(StringConcatenationClient.class);
final TypeReference nodeType = context.newTypeReference(IGeneratorNode.class);
final TypeReference eobjectType = context.newTypeReference(EObject.class);
MutableTypeDeclaration _declaringType = annotatedMethod.getDeclaringType();
final MutableClassDeclaration clazz = ((MutableClassDeclaration) _declaringType);
final Function1<MutableFieldDeclaration, Boolean> _function = (MutableFieldDeclaration it) -> {
return Boolean.valueOf(traceSugar.isAssignableFrom(it.getType()));
};
final MutableFieldDeclaration field = IterableExtensions.findFirst(clazz.getDeclaredFields(), _function);
if ((field == null)) {
StringConcatenation _builder = new StringConcatenation();
_builder.append("@");
String _simpleName = Traced.class.getSimpleName();
_builder.append(_simpleName);
_builder.append(" methods can only declared in a class with a field of type ");
_builder.append(TracingSugar.class);
context.addError(annotatedMethod, _builder.toString());
return;
}
final Function1<MutableParameterDeclaration, Boolean> _function_1 = (MutableParameterDeclaration it) -> {
return Boolean.valueOf(eobjectType.isAssignableFrom(it.getType()));
};
final MutableParameterDeclaration traceParam = IterableExtensions.findFirst(annotatedMethod.getParameters(), _function_1);
if ((traceParam == null)) {
StringConcatenation _builder_1 = new StringConcatenation();
_builder_1.append("@");
String _simpleName_1 = Traced.class.getSimpleName();
_builder_1.append(_simpleName_1);
_builder_1.append(" methods need at least one parameter of type ");
_builder_1.append(EObject.class);
_builder_1.append(".");
context.addError(annotatedMethod, _builder_1.toString());
return;
}
String _simpleName_2 = annotatedMethod.getSimpleName();
String _plus = ("_" + _simpleName_2);
final Procedure1<MutableMethodDeclaration> _function_2 = (MutableMethodDeclaration it) -> {
Iterable<? extends MutableParameterDeclaration> _parameters = annotatedMethod.getParameters();
for (final MutableParameterDeclaration p : _parameters) {
it.addParameter(p.getSimpleName(), p.getType());
}
it.setReturnType(templateClient);
it.setBody(annotatedMethod.getBody());
};
clazz.addMethod(_plus, _function_2);
annotatedMethod.setReturnType(nodeType);
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append(ILocationData.class);
_builder.append(" _location = this.");
String _simpleName = field.getSimpleName();
_builder.append(_simpleName);
_builder.append(".location(");
String _simpleName_1 = traceParam.getSimpleName();
_builder.append(_simpleName_1);
_builder.append(");");
_builder.newLineIfNotEmpty();
_builder.append(CompositeGeneratorNode.class);
_builder.append(" _traceNode = this.");
String _simpleName_2 = field.getSimpleName();
_builder.append(_simpleName_2);
_builder.append(".trace(_location, ");
_builder.append(useForDebugging);
_builder.append(");");
_builder.newLineIfNotEmpty();
_builder.append("this.");
String _simpleName_3 = field.getSimpleName();
_builder.append(_simpleName_3);
_builder.append(".appendTemplate(_traceNode, _");
String _simpleName_4 = annotatedMethod.getSimpleName();
_builder.append(_simpleName_4);
_builder.append("(");
final Function1<MutableParameterDeclaration, CharSequence> _function = (MutableParameterDeclaration it) -> {
return it.getSimpleName();
};
String _join = IterableExtensions.join(annotatedMethod.getParameters(), ",", _function);
_builder.append(_join);
_builder.append("));");
_builder.newLineIfNotEmpty();
_builder.append("return _traceNode;");
_builder.newLine();
}
};
annotatedMethod.setBody(_client);
}
}