com.regnosys.rosetta.ide.util.AbstractLanguageServerService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.regnosys.rosetta.ide Show documentation
Show all versions of com.regnosys.rosetta.ide Show documentation
Responsibilities: adding support for developing Rosetta in an IDE, including
- language server features (semantic highlighting, inlay hints, etc)
- syntax highlighting
The newest version!
/*
* Copyright 2024 REGnosys
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.regnosys.rosetta.ide.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.lsp4j.Range;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.SimpleCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TODO: contribute to Xtext.
*
*/
public class AbstractLanguageServerService {
private static final Logger log = LoggerFactory.getLogger(AbstractLanguageServerService.class);
private final Class resultType;
private final Class extends Annotation> methodAnnotation;
private final ThreadLocal state;
private volatile Set> providerMethods = null;
private final SimpleCache, List>> methodsForType = new SimpleCache<>(
param -> {
List> result = new ArrayList<>();
for (MethodWrapper mw : providerMethods) {
if (mw.isMatching(param))
result.add(mw);
}
return result;
});
@Inject
private Injector injector;
@Inject
protected RangeUtils rangeUtils;
protected AbstractLanguageServerService(Class resultType, Class extends Annotation> methodAnnotation) {
this.resultType = resultType;
this.methodAnnotation = methodAnnotation;
this.state = new ThreadLocal<>();
}
protected List computeResult(Document document, XtextResource resource, CancelIndicator cancelIndicator) {
TreeIterator contents = resource.getAllContents();
return computeResult(document, resource, contents, cancelIndicator);
}
protected List computeResult(Document document, XtextResource resource, Range range, CancelIndicator cancelIndicator) {
TreeIterator contents = rangeUtils.iterateOverlapping(resource, range);
return computeResult(document, resource, contents, cancelIndicator);
}
@SuppressWarnings("unchecked")
protected List computeResult(Document document, XtextResource resource, Iterator objects, CancelIndicator cancelIndicator) {
if (providerMethods == null) {
synchronized (this) {
if (providerMethods == null) {
Set> providerMethods = Sets.newLinkedHashSet();
providerMethods.addAll(collectMethods((Class extends AbstractLanguageServerService>)getClass()));
this.providerMethods = providerMethods;
}
}
}
List result = Lists.newArrayList();
while (objects.hasNext()) {
EObject obj = objects.next();
if (cancelIndicator.isCanceled()) return result;
for (MethodWrapper m: methodsForType.get(obj.getClass())) {
if (cancelIndicator.isCanceled()) return result;
result.addAll(
m.invoke(new State(obj, m.getMethod(), document, resource, cancelIndicator)));
}
}
return result;
}
private List> collectMethods(Class extends AbstractLanguageServerService> clazz) {
List> providerMethods = new ArrayList<>();
Set> visitedClasses = new HashSet<>(4);
collectMethods(this, clazz, visitedClasses, providerMethods);
return providerMethods;
}
private void collectMethods(AbstractLanguageServerService instance,
Class extends AbstractLanguageServerService> clazz,
Collection> visitedClasses,
Collection> result) {
if (visitedClasses.contains(clazz))
return;
collectMethodsImpl(instance, clazz, visitedClasses, result);
}
private void collectMethodsImpl(AbstractLanguageServerService instance,
Class extends AbstractLanguageServerService> clazz, Collection> visitedClasses,
Collection> result) {
if (!visitedClasses.add(clazz))
return;
AbstractLanguageServerService instanceToUse;
instanceToUse = instance;
if (instanceToUse == null) {
instanceToUse = newInstance(clazz);
}
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.getAnnotation(methodAnnotation) != null && method.getParameterTypes().length == 1) {
result.add(createMethodWrapper(instanceToUse, method));
}
}
Class extends AbstractLanguageServerService> superClass = getSuperClass(clazz);
if (superClass != null)
collectMethodsImpl(instanceToUse, superClass, visitedClasses, result);
}
protected MethodWrapper createMethodWrapper(AbstractLanguageServerService instanceToUse, Method method) {
return new MethodWrapper(instanceToUse, method);
}
protected AbstractLanguageServerService newInstance(Class extends AbstractLanguageServerService> clazz) {
AbstractLanguageServerService instanceToUse;
if (injector == null)
throw new IllegalStateException("The class is not configured with an injector.");
instanceToUse = injector.getInstance(clazz);
return instanceToUse;
}
@SuppressWarnings("unchecked")
private Class extends AbstractLanguageServerService> getSuperClass(
Class extends AbstractLanguageServerService> clazz) {
try {
Class> superClass = clazz.getSuperclass().asSubclass(
AbstractLanguageServerService.class);
if (AbstractLanguageServerService.class.equals(superClass))
return null;
return (Class extends AbstractLanguageServerService>) superClass;
} catch (ClassCastException e) {
return null;
}
}
private static class MethodWrapper {
private final AbstractLanguageServerService instance;
private final Method method;
private final String s;
protected MethodWrapper(AbstractLanguageServerService instance, Method m) {
this.instance = instance;
this.method = m;
this.s = m.getName() + ":" + m.getParameterTypes()[0].getName();
}
public boolean isMatching(Class> param) {
return method.getParameterTypes()[0].isAssignableFrom(param);
}
public List invoke(State state) {
State instanceState = getInstance().state.get();
if (instanceState != null && instanceState != state)
throw new IllegalStateException("State is already assigned.");
boolean wasNull = instanceState == null;
if (wasNull)
getInstance().state.set(state);
try {
try {
method.setAccessible(true);
if (state.cancelIndicator.isCanceled()) {
return List.of();
}
Object res = method.invoke(getInstance(), state.currentObject);
if (res instanceof Optional) {
res = ((Optional>)res).orElse(null);
}
if (res == null) {
return List.of();
}
if (res instanceof List>) {
return ((List>) res).stream()
.filter(r -> {
if (getInstance().resultType.isInstance(r)) {
return true;
} else {
log.error("Incorrect return type for method " + method);
return false;
}
})
.map(getInstance().resultType::cast)
.collect(Collectors.toList());
} else if (getInstance().resultType.isInstance(res)) {
return List.of(getInstance().resultType.cast(res));
} else {
log.error("Incorrect return type for method " + method);
}
} catch (IllegalArgumentException | IllegalAccessException e) {
log.error(e.getMessage(), e);
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
log.error(e.getMessage(), targetException);
}
} finally {
if (wasNull)
getInstance().state.remove();
}
return List.of();
}
@Override
public int hashCode() {
return s.hashCode() ^ getInstance().hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AbstractLanguageServerService.MethodWrapper))
return false;
MethodWrapper> mw = (MethodWrapper>)obj;
return s.equals(mw.s) && getInstance() == mw.getInstance();
}
public Method getMethod() {
return method;
}
public AbstractLanguageServerService getInstance() {
return this.instance;
}
}
private static class State {
private final EObject currentObject;
private final Method currentMethod;
private final Document currentDocument;
private final XtextResource currentResource;
private final CancelIndicator cancelIndicator;
private State(EObject currentObject, Method currentMethod, Document currentDocument,
XtextResource currentResource, CancelIndicator cancelIndicator) {
super();
this.currentObject = currentObject;
this.currentMethod = currentMethod;
this.currentDocument = currentDocument;
this.currentResource = currentResource;
this.cancelIndicator = cancelIndicator;
}
@Override
public int hashCode() {
return Objects.hash(cancelIndicator, currentDocument, currentMethod, currentObject,
currentResource);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
State other = (State) obj;
return Objects.equals(cancelIndicator, other.cancelIndicator)
&& Objects.equals(currentDocument, other.currentDocument)
&& Objects.equals(currentMethod, other.currentMethod)
&& Objects.equals(currentObject, other.currentObject)
&& Objects.equals(currentResource, other.currentResource);
}
}
}