blob: 8aad4c7d30f52dfe05d0a866941c064d95206a78 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.query.runtime.impl.namespace;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.acceleo.query.runtime.IReadOnlyQueryEnvironment;
import org.eclipse.acceleo.query.runtime.IService;
import org.eclipse.acceleo.query.runtime.lookup.basic.CacheLookupEngine;
import org.eclipse.acceleo.query.runtime.namespace.IQualifiedNameLookupEngine;
import org.eclipse.acceleo.query.runtime.namespace.IQualifiedNameResolver;
import org.eclipse.acceleo.query.validation.type.IType;
/**
* Lookup engine for qualified name.
*
* @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
*/
public class QualifiedNameLookupEngine extends CacheLookupEngine implements IQualifiedNameLookupEngine {
/**
* Mapping from qualifiedName to its {@link CacheLookupEngine}.
*/
private final Map<String, CacheLookupEngine> qualifiedNameServices = new HashMap<String, CacheLookupEngine>();
/**
* The {@link IQualifiedNameResolver}.
*/
private final IQualifiedNameResolver resolver;
/**
* The qualified names context stack.
*/
private final Deque<CallStack> callStacks = new ArrayDeque<CallStack>();
/**
* Constructor.
*
* @param queryEnvironment
* the {@link IReadOnlyQueryEnvironment}
* @param resolver
* the {@link IQualifiedNameResolver}
*/
public QualifiedNameLookupEngine(IReadOnlyQueryEnvironment queryEnvironment,
IQualifiedNameResolver resolver) {
super(queryEnvironment);
this.resolver = resolver;
}
@Override
public IService<?> lookup(String name, IType[] argumentTypes) {
final CallStack currentStack = getCurrentContext();
/* PRIVATE query or template in the same module as our current (last of the stack) */
String last = currentStack.peek();
final IService<?> lastService = getLookupEngine(last).lookup(name, argumentTypes);
IService<?> result = null;
if (lastService != null && lastService.getVisibility() == IService.Visibility.PRIVATE) {
result = lastService;
}
/*
* PUBLIC or PROTECTED template or query in the extends hierarchy of our "lowest" module in that
* hierarchy (first of the stack)
*/
if (result == null) {
String start = currentStack.getStartingQualifiedName();
result = lookupExtendedService(start, name, argumentTypes, IService.Visibility.PROTECTED,
IService.Visibility.PUBLIC);
}
/*
* We couldn't find a template or query matching that in our current extends hierarchy, try the
* imports of our current (last of the stack) module for a PUBLIC matching module element.
*/
if (result == null) {
result = lookupImportedService(last, name, argumentTypes);
}
/* There is no module element matching our target, fall back to regular services. */
if (result == null) {
result = super.lookup(name, argumentTypes);
}
return result;
}
/**
* Looks up in the hierarchy of the given {@code start}ing module qualified name for a query or template
* matching the given name and arguments.
*
* @param startQualifiedName
* The module qualified name we're considering as the "root" of our extends hierarchy.
* @param name
* The name of the service we're looking for.
* @param argumentTypes
* Type of the arguments accepted by the service we're looking for.
* @param candidateVisibilities
* The IService.Visibility to consider for our services.
* @return The service matching the criteria if any, <code>null</code> if none.
*/
private IService<?> lookupExtendedService(String startQualifiedName, String name, IType[] argumentTypes,
IService.Visibility... candidateVisibilities) {
final IService<?> service = getLookupEngine(startQualifiedName).lookup(name, argumentTypes);
IService<?> result = null;
if (service != null && isVisible(service, candidateVisibilities)) {
result = service;
}
if (result == null) {
final String extendedModuleQualifiedName = resolver.getExtend(startQualifiedName);
if (extendedModuleQualifiedName != null) {
result = lookupExtendedService(extendedModuleQualifiedName, name, argumentTypes,
candidateVisibilities);
}
}
return result;
}
/**
* Looks up in the imports of the given {@code start}ing module qualified name for a query or template
* matching the given name and arguments. This will consider public templates and queries from the imports
* and their extends hierarchy.
*
* @param start
* The module qualified name we're considering as the "root" of our imports lookup.
* @param name
* The name of the service we're looking for.
* @param argumentTypes
* Type of the arguments accepted by the service we're looking for.
* @return The service matching the criteria if any, <code>null</code> if none.
*/
private IService<?> lookupImportedService(String start, String name, IType[] argumentTypes) {
IService<?> result = null;
for (String imported : resolver.getImports(start)) {
final IService<?> service = getLookupEngine(imported).lookup(name, argumentTypes);
if (service != null && service.getVisibility() == IService.Visibility.PUBLIC) {
result = service;
break;
}
}
return result;
}
@Override
public Set<IService<?>> getServices(Set<IType> receiverTypes) {
final Set<IService<?>> result = new LinkedHashSet<IService<?>>();
final Set<IService<?>> storedServices = getRegisteredServices();
for (IType type : receiverTypes) {
if (type != null) {
for (IService<?> service : storedServices) {
if (service.getParameterTypes(queryEnvironment).get(0).isAssignableFrom(type)) {
result.add(service);
}
}
}
}
return result;
}
@Override
public Set<IService<?>> getRegisteredServices() {
final Set<IService<?>> result = new LinkedHashSet<IService<?>>();
final CallStack currentStack = getCurrentContext();
/* Query or Template in the same module as our current (last of the stack) */
final String last = currentStack.peek();
Set<IService<?>> lastServices = getLookupEngine(last).getRegisteredServices();
result.addAll(filterByVisibility(lastServices, IService.Visibility.PRIVATE));
/*
* PUBLIC or PROTECTED template or query in the extends hierarchy of our "lowest" module in that
* hierarchy (first of the stack)
*/
String start = currentStack.getStartingQualifiedName();
result.addAll(getExtendedService(start, IService.Visibility.PROTECTED, IService.Visibility.PUBLIC));
/*
* Imports of our current (last of the stack) module for a PUBLIC matching module element.
*/
result.addAll(getImportedService(last));
result.addAll(super.getRegisteredServices());
return result;
}
/**
* Gets the {@link Set} of imported {@link IService} for the given qualified name.
*
* @param start
* the qualified name
* @return the {@link Set} of imported {@link IService} for the given qualified name and that match the
* given receiver {@link IType}
*/
private Set<IService<?>> getImportedService(String start) {
Set<IService<?>> result = new LinkedHashSet<IService<?>>();
for (String imported : resolver.getImports(start)) {
result.addAll(getExtendedService(imported, IService.Visibility.PUBLIC));
}
return result;
}
/**
* Gets the {@link Set} of {@link IService} form the given qualified name that match the given
* {@link IService.Visibility}.
*
* @param startQualifiedName
* the qualified name
* @param candidateVisibilities
* @return the {@link Set} of {@link IService} form the given qualified name that match the given
* {@link IService.Visibility}
*/
private Set<IService<?>> getExtendedService(String startQualifiedName,
IService.Visibility... candidateVisibilities) {
Set<IService<?>> services = getLookupEngine(startQualifiedName).getRegisteredServices();
Set<IService<?>> result = filterByVisibility(services, candidateVisibilities);
final String extendedModuleQualifiedName = resolver.getExtend(startQualifiedName);
if (extendedModuleQualifiedName != null) {
result.addAll(getExtendedService(extendedModuleQualifiedName, candidateVisibilities));
}
return result;
}
@Override
public boolean isRegisteredService(IService<?> service) {
return getRegisteredServices().contains(service);
}
@Override
public void pushContext(String qualifiedName) {
final CallStack currentStack = callStacks.peekLast();
currentStack.push(qualifiedName);
}
@Override
public void pushImportsContext(String importQualifiedName, String serviceContextQualifiedName) {
final CallStack currentStack = new CallStack(importQualifiedName);
callStacks.addLast(currentStack);
currentStack.push(serviceContextQualifiedName);
}
@Override
public void popContext(String qualifiedName) {
final CallStack currentStack = callStacks.peekLast();
if (currentStack == null || (!currentStack.pop().equals(qualifiedName) && currentStack.isEmpty())) {
throw new IllegalStateException("call stack is out of synchronization");
}
if (currentStack.isEmpty()) {
callStacks.pollLast();
}
}
@Override
public void clearContext(String qualifiedName) {
qualifiedNameServices.remove(qualifiedName);
}
@Override
public CallStack getCurrentContext() {
final CallStack res;
if (!callStacks.isEmpty()) {
res = callStacks.peekLast();
} else {
res = null;
}
return res;
}
/**
* Gets the {@link Set} of filtered {@link IService} with the given {@link IService.Visibility} form the
* given {@link Set} of {@link IService}.
*
* @param services
* the {@link Set} of {@link IService}
* @param candidateVisibilities
* the {@link IService.Visibility}
* @return the {@link Set} of filtered {@link IService} with the given {@link IService.Visibility} form
* the given {@link Set} of {@link IService}
*/
private Set<IService<?>> filterByVisibility(Set<IService<?>> services,
IService.Visibility... candidateVisibilities) {
return services.stream().filter(s -> isVisible(s, candidateVisibilities)).collect(Collectors
.toCollection(LinkedHashSet::new));
}
/**
* Tells if the given {@link IService} is visible according to given {@link IService.Visibility}.
*
* @param service
* the {@link IService}
* @param candidateVisibilities
* The visibilities we're expecting this service to have.
* @return <code>true</code> if the given {@link IService} is visible according to given
* {@link IService.Visibility}, <code>false</code> otherwise
*/
private boolean isVisible(IService<?> service, IService.Visibility... candidateVisibilities) {
final boolean res;
final List<IService.Visibility> visibilityList = Arrays.asList(candidateVisibilities);
res = visibilityList.contains(service.getVisibility());
return res;
}
private CacheLookupEngine getLookupEngine(String qualifiedName) {
if (!qualifiedNameServices.containsKey(qualifiedName)) {
final Object object = resolver.resolve(qualifiedName);
final CacheLookupEngine engine = new CacheLookupEngine(queryEnvironment);
for (IService<?> service : resolver.getServices(this, object, qualifiedName)) {
engine.registerService(service);
}
qualifiedNameServices.put(qualifiedName, engine);
}
return qualifiedNameServices.get(qualifiedName);
}
@Override
public IReadOnlyQueryEnvironment getQueryEnvironment() {
return queryEnvironment;
}
@Override
public String getExtend(String qualifiedName) {
return resolver.getExtend(qualifiedName);
}
@Override
public List<String> getImports(String qualifiedName) {
return resolver.getImports(qualifiedName);
}
@Override
public IQualifiedNameResolver getResolver() {
return resolver;
}
}