mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
Merge pull request #1532 from eclipse/ade-refactor-xtend-to-java
[eclipse/xtext#1777] Translate more code to java.
This commit is contained in:
commit
097ad57191
24 changed files with 1052 additions and 2059 deletions
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)]
|
||||
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -12,7 +12,6 @@ bin.includes = .,\
|
|||
source.. = src-gen/,\
|
||||
emf-gen/,\
|
||||
packrat/,\
|
||||
src/,\
|
||||
xtend-gen/
|
||||
src/
|
||||
output.. = bin/main/
|
||||
src.includes = about.html
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
'''
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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')
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue