| /* |
| * Copyright (c) 2011 Google Inc. |
| * |
| * 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 |
| */ |
| package com.google.eclipse.protobuf.scoping; |
| |
| import static com.google.eclipse.protobuf.scoping.QualifiedNames.addLeadingDot; |
| import static java.util.Collections.emptyList; |
| import static org.eclipse.emf.common.util.URI.createURI; |
| import static org.eclipse.emf.ecore.util.EcoreUtil.getAllContents; |
| import static org.eclipse.xtext.EcoreUtil2.getAllContentsOfType; |
| import static org.eclipse.xtext.resource.EObjectDescription.create; |
| |
| import java.util.*; |
| |
| import org.eclipse.emf.common.util.TreeIterator; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.xtext.naming.IQualifiedNameProvider; |
| import org.eclipse.xtext.naming.QualifiedName; |
| import org.eclipse.xtext.resource.IEObjectDescription; |
| import org.eclipse.xtext.resource.XtextResource; |
| import org.eclipse.xtext.scoping.IScope; |
| import org.eclipse.xtext.scoping.impl.*; |
| |
| import com.google.eclipse.protobuf.protobuf.*; |
| import com.google.eclipse.protobuf.protobuf.Enum; |
| import com.google.eclipse.protobuf.protobuf.Package; |
| import com.google.eclipse.protobuf.util.FieldOptions; |
| import com.google.eclipse.protobuf.util.ProtobufElementFinder; |
| import com.google.inject.Inject; |
| |
| /** |
| * Custom scoping description. |
| * |
| * @author alruiz@google.com (Alex Ruiz) |
| * |
| * @see <a href="http://www.eclipse.org/Xtext/documentation/latest/xtext.html#scoping">Xtext Scoping</a> |
| */ |
| public class ProtobufScopeProvider extends AbstractDeclarativeScopeProvider { |
| |
| private static final boolean DO_NOT_IGNORE_CASE = false; |
| |
| @Inject private FieldOptions fieldOptions; |
| @Inject private ProtobufElementFinder finder; |
| @Inject private ProtoDescriptorProvider descriptorProvider; |
| @Inject private IQualifiedNameProvider nameProvider; |
| @Inject private ImportUriResolver uriResolver; |
| @Inject private LocalNamesProvider localNamesProvider; |
| @Inject private ImportedNamesProvider importedNamesProvider; |
| @Inject private PackageResolver packageResolver; |
| |
| @SuppressWarnings("unused") |
| IScope scope_TypeRef_type(TypeRef typeRef, EReference reference) { |
| Protobuf root = finder.rootOf(typeRef); |
| Set<IEObjectDescription> descriptions = new HashSet<IEObjectDescription>(); |
| EObject current = typeRef.eContainer().eContainer(); // get message of the property containing the TypeReference |
| while (current != null) { |
| descriptions.addAll(typesIn(current)); |
| current = current.eContainer(); |
| } |
| descriptions.addAll(importedTypes(root, Type.class)); |
| return createScope(descriptions); |
| } |
| |
| private Collection<IEObjectDescription> typesIn(EObject root) { |
| return children(root, Type.class); |
| } |
| |
| @SuppressWarnings("unused") |
| IScope scope_MessageRef_type(MessageRef msgRef, EReference reference) { |
| Protobuf root = finder.rootOf(msgRef); |
| Set<IEObjectDescription> descriptions = new HashSet<IEObjectDescription>(); |
| descriptions.addAll(messagesIn(root)); |
| descriptions.addAll(importedTypes(root, Message.class)); |
| return createScope(descriptions); |
| } |
| |
| private Collection<IEObjectDescription> messagesIn(Protobuf root) { |
| return children(root, Message.class); |
| } |
| |
| private <T extends Type> Collection<IEObjectDescription> children(EObject root, Class<T> targetType) { |
| return children(root, targetType, 0); |
| } |
| |
| private <T extends Type> Collection<IEObjectDescription> children(EObject root, Class<T> targetType, int level) { |
| List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>(); |
| for (EObject element : root.eContents()) { |
| if (!targetType.isInstance(element)) continue; |
| List<QualifiedName> names = localNamesProvider.namesOf(element); |
| int nameCount = names.size(); |
| for (int i = level; i < nameCount; i++) { |
| descriptions.add(create(names.get(i), element)); |
| } |
| descriptions.addAll(fullyQualifiedNamesOf(element)); |
| // TODO investigate if groups can have messages, and if so, add those messages to the scope. |
| if (element instanceof Message) { |
| descriptions.addAll(children(element, targetType, level + 1)); |
| } |
| } |
| return descriptions; |
| } |
| |
| private <T extends Type> Collection<IEObjectDescription> importedTypes(Protobuf root, Class<T> targetType) { |
| List<Import> allImports = finder.importsIn(root); |
| if (allImports.isEmpty()) return emptyList(); |
| return importedTypes(allImports, finder.packageOf(root), targetType); |
| } |
| |
| private <T extends Type> Collection<IEObjectDescription> importedTypes(List<Import> allImports, Package aPackage, |
| Class<T> targetType) { |
| List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>(); |
| for (Import anImport : allImports) { |
| if (isImportingDescriptor(anImport)) { |
| descriptions.addAll(allBuiltInTypes(targetType)); |
| continue; |
| } |
| Resource importedResource = importedResourceFrom(anImport); |
| Protobuf importedRoot = rootElementOf(importedResource); |
| if (importedRoot != null) { |
| descriptions.addAll(publicImportedTypes(importedRoot, targetType)); |
| if (arePackagesRelated(aPackage, importedRoot)) { |
| descriptions.addAll(typesIn(importedRoot)); |
| continue; |
| } |
| } |
| descriptions.addAll(children(importedResource, targetType)); |
| } |
| return descriptions; |
| } |
| |
| private boolean isImportingDescriptor(Import anImport) { |
| String descriptorLocation = descriptorProvider.descriptorLocation().toString(); |
| return descriptorLocation.equals(anImport.getImportURI()); |
| } |
| |
| private <T extends Type> Collection<IEObjectDescription> allBuiltInTypes(Class<T> targetType) { |
| List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>(); |
| ProtoDescriptor descriptor = descriptorProvider.get(); |
| for (Type t : descriptor.allTypes()) { |
| if (!targetType.isInstance(t)) continue; |
| T type = targetType.cast(t); |
| descriptions.addAll(fullyQualifiedNamesOf(type)); |
| } |
| return descriptions; |
| } |
| |
| private <T extends Type> Collection<IEObjectDescription> publicImportedTypes(Protobuf root, Class<T> targetType) { |
| List<Import> allImports = finder.publicImportsIn(root); |
| if (allImports.isEmpty()) return emptyList(); |
| return importedTypes(allImports, finder.packageOf(root), targetType); |
| } |
| |
| private Resource importedResourceFrom(Import anImport) { |
| ResourceSet resourceSet = finder.rootOf(anImport).eResource().getResourceSet(); |
| URI importUri = createURI(uriResolver.apply(anImport)); |
| try { |
| return resourceSet.getResource(importUri, true); |
| } catch (Throwable t) { |
| return null; |
| } |
| } |
| |
| private Protobuf rootElementOf(Resource resource) { |
| if (resource instanceof XtextResource) { |
| EObject root = ((XtextResource) resource).getParseResult().getRootASTElement(); |
| return (Protobuf) root; |
| } |
| TreeIterator<Object> contents = getAllContents(resource, true); |
| if (contents.hasNext()) { |
| Object next = contents.next(); |
| if (next instanceof Protobuf) return (Protobuf) next; |
| } |
| return null; |
| } |
| |
| private boolean arePackagesRelated(Package aPackage, EObject root) { |
| Package p = finder.packageOf(root); |
| return packageResolver.areRelated(aPackage, p); |
| } |
| |
| private <T extends Type> Collection<IEObjectDescription> children(Resource resource, Class<T> targetType) { |
| List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>(); |
| TreeIterator<Object> contents = getAllContents(resource, true); |
| while (contents.hasNext()) { |
| Object next = contents.next(); |
| if (!targetType.isInstance(next)) continue; |
| T type = targetType.cast(next); |
| descriptions.addAll(fullyQualifiedNamesOf(type)); |
| for (QualifiedName name : importedNamesProvider.namesOf(type)) { |
| descriptions.add(create(name, type)); |
| } |
| } |
| return descriptions; |
| } |
| |
| private Collection<IEObjectDescription> fullyQualifiedNamesOf(EObject obj) { |
| List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>(); |
| QualifiedName fqn = nameProvider.getFullyQualifiedName(obj); |
| descriptions.add(create(fqn, obj)); |
| descriptions.add(create(addLeadingDot(fqn), obj)); |
| return descriptions; |
| } |
| |
| @SuppressWarnings("unused") |
| IScope scope_LiteralRef_literal(LiteralRef literalRef, EReference reference) { |
| EObject container = literalRef.eContainer(); |
| if (container instanceof Property) { |
| Enum anEnum = finder.enumTypeOf((Property) container); |
| if (anEnum != null) return scopeForLiterals(anEnum); |
| } |
| Enum anEnum = enumTypeOfOption(container); |
| if (anEnum != null) return scopeForLiterals(anEnum); |
| return null; |
| } |
| |
| private Enum enumTypeOfOption(EObject mayBeOption) { |
| ProtoDescriptor descriptor = descriptorProvider.get(); |
| if (mayBeOption instanceof BuiltInOption) { |
| return descriptor.enumTypeOf((BuiltInOption) mayBeOption); |
| } |
| if (mayBeOption instanceof BuiltInFieldOption) { |
| BuiltInFieldOption option = (BuiltInFieldOption) mayBeOption; |
| if (fieldOptions.isDefaultValueOption(option)) { |
| Property property = (Property) option.eContainer(); |
| return finder.enumTypeOf(property); |
| } |
| return descriptor.enumTypeOf(option); |
| } |
| return null; |
| } |
| |
| private static IScope scopeForLiterals(Enum anEnum) { |
| Collection<IEObjectDescription> descriptions = describeLiterals(anEnum); |
| return createScope(descriptions); |
| } |
| |
| private static Collection<IEObjectDescription> describeLiterals(Enum anEnum) { |
| List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>(); |
| for (Literal literal : getAllContentsOfType(anEnum, Literal.class)) |
| descriptions.add(create(literal.getName(), literal)); |
| return descriptions; |
| } |
| |
| private static IScope createScope(Iterable<IEObjectDescription> descriptions) { |
| return new SimpleScope(descriptions, DO_NOT_IGNORE_CASE); |
| } |
| } |