| /* |
| * 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.common.base.Strings.isNullOrEmpty; |
| import static com.google.common.collect.Lists.newArrayList; |
| import static com.google.common.collect.Maps.newHashMap; |
| import static com.google.common.io.Closeables.closeQuietly; |
| import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.MESSAGE_FIELD__TYPE; |
| import static com.google.eclipse.protobuf.scoping.OptionType.findOptionTypeForLevelOf; |
| import static com.google.eclipse.protobuf.util.Encodings.UTF_8; |
| import static java.util.Collections.*; |
| import static org.eclipse.xtext.EcoreUtil2.*; |
| import static org.eclipse.xtext.util.CancelIndicator.NullImpl; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.eclipse.protobuf.model.util.INodes; |
| import com.google.eclipse.protobuf.protobuf.*; |
| import com.google.eclipse.protobuf.protobuf.Enum; |
| |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.xtext.nodemodel.INode; |
| import org.eclipse.xtext.parser.*; |
| import org.eclipse.xtext.resource.XtextResource; |
| |
| import java.io.*; |
| import java.net.URL; |
| import java.util.*; |
| |
| /** |
| * Contains the elements from descriptor.proto (provided with protobuf's library.) |
| * |
| * @author alruiz@google.com (Alex Ruiz) |
| */ |
| public class ProtoDescriptor { |
| private static final Map<String, OptionType> OPTION_DEFINITION_BY_NAME = newHashMap(); |
| |
| static { |
| populateMap(); |
| } |
| |
| private static void populateMap() { |
| for (OptionType type : OptionType.values()) { |
| OPTION_DEFINITION_BY_NAME.put(type.messageName(), type); |
| } |
| } |
| |
| private final List<ComplexType> allTypes = newArrayList(); |
| private final Map<OptionType, Map<String, MessageField>> optionsByType = newHashMap(); |
| private final Map<String, Enum> enumsByName = newHashMap(); |
| |
| private Protobuf root; |
| |
| private final String importUri; |
| private final INodes nodes; |
| private final XtextResource resource; |
| |
| ProtoDescriptor(String importUri, URI location, IParser parser, INodes nodes) { |
| this.importUri = importUri; |
| this.nodes = nodes; |
| addOptionTypes(); |
| InputStreamReader reader = null; |
| try { |
| resource = new XtextResource(location); |
| reader = new InputStreamReader(contents(location), UTF_8); |
| IParseResult result = parser.parse(reader); |
| root = (Protobuf) result.getRootASTElement(); |
| resource.getContents().add(root); |
| resolveLazyCrossReferences(resource, NullImpl); |
| initContents(); |
| } catch (Throwable t) { |
| throw new IllegalStateException("Unable to parse descriptor.proto", t); |
| } finally { |
| closeQuietly(reader); |
| } |
| } |
| |
| /** |
| * Returns the contents of the descriptor file at the given location. |
| * @param descriptorLocation the location of the descriptor file. |
| * @return the contents of the descriptor file. |
| * @throws IOException if something goes wrong. |
| */ |
| protected InputStream contents(URI descriptorLocation) throws IOException { |
| URL url = new URL(descriptorLocation.toString()); |
| return url.openConnection().getInputStream(); |
| } |
| |
| private void addOptionTypes() { |
| for (OptionType type : OptionType.values()) { |
| optionsByType.put(type, new LinkedHashMap<String, MessageField>()); |
| } |
| } |
| |
| private void initContents() { |
| allTypes.addAll(getAllContentsOfType(root, ComplexType.class)); |
| for (ComplexType t : allTypes) { |
| if (!(t instanceof Message)) { |
| continue; |
| } |
| Message m = (Message) t; |
| OptionType type = OPTION_DEFINITION_BY_NAME.get(m.getName()); |
| if (type == null) { |
| continue; |
| } |
| initOptions(m, type); |
| } |
| } |
| |
| private void initOptions(Message optionGroup, OptionType type) { |
| for (MessageElement e : optionGroup.getElements()) { |
| if (e instanceof MessageField) { |
| addOption((MessageField) e, type); |
| continue; |
| } |
| if (e instanceof Enum) { |
| Enum anEnum = (Enum) e; |
| String name = anEnum.getName(); |
| enumsByName.put(name, anEnum); |
| } |
| } |
| } |
| |
| private void addOption(MessageField optionSource, OptionType type) { |
| if (shouldIgnore(optionSource)) { |
| return; |
| } |
| String name = optionSource.getName(); |
| optionsByType.get(type).put(name, optionSource); |
| } |
| |
| private boolean shouldIgnore(MessageField field) { |
| return "uninterpreted_option".equals(field.getName()); |
| } |
| |
| /** |
| * Returns the options available for the given option or option container. The returned options are defined in |
| * {@code google/protobuf/descriptor.proto} (more details can be found <a |
| * href=http://code.google.com/apis/protocolbuffers/docs/proto.html#options" target="_blank">here</a>.) |
| * @param o the given option or option container. |
| * @return the options available for the given option or option container, or an empty collection if the are not any |
| * options available. |
| */ |
| public Collection<MessageField> availableOptionsFor(EObject o) { |
| EObject target = o; |
| if (target instanceof NativeOption) { |
| target = target.eContainer(); |
| } |
| OptionType type = findOptionTypeForLevelOf(target); |
| if (type == null) { |
| return emptyList(); |
| } |
| return optionsOfType(type); |
| } |
| |
| @VisibleForTesting Collection<MessageField> optionsOfType(OptionType type) { |
| return unmodifiableCollection(optionsByType.get(type).values()); |
| } |
| |
| /** |
| * Returns the enum type of the given field, only if the given field is defined in |
| * {@code google/protobuf/descriptor.proto} and its type is enum (more details can be found <a |
| * href=http://code.google.com/apis/protocolbuffers/docs/proto.html#options" target="_blank">here</a>.) |
| * @param field the given field. |
| * @return the enum type of the given field or {@code null} if the type of the given field is not enum. |
| */ |
| public Enum enumTypeOf(MessageField field) { |
| if (field == null) { |
| return null; |
| } |
| INode node = nodes.firstNodeForFeature(field, MESSAGE_FIELD__TYPE); |
| if (node == null) { |
| return null; |
| } |
| String typeName = node.getText(); |
| return (isNullOrEmpty(typeName)) ? null : enumByName(typeName.trim()); |
| } |
| |
| @VisibleForTesting Enum enumByName(String qualifiedName) { |
| String[] segments = qualifiedName.split("\\."); |
| if (segments == null || segments.length == 0) { |
| return null; |
| } |
| return enumsByName.get(segments[segments.length - 1]); |
| } |
| |
| /** |
| * Returns all types in descriptor.proto. |
| * @return all types in descriptor.proto. |
| */ |
| public List<ComplexType> allTypes() { |
| return unmodifiableList(allTypes); |
| } |
| |
| public XtextResource resource() { |
| return resource; |
| } |
| |
| /** |
| * Returns the URI to use when importing descriptor.proto. |
| * @return the URI to use when importing descriptor.proto. |
| */ |
| public String importUri() { |
| return importUri; |
| } |
| |
| @VisibleForTesting MessageField option(String name, OptionType type) { |
| Map<String, MessageField> optionByName = optionsByType.get(type); |
| return (optionByName != null) ? optionByName.get(name) : null; |
| } |
| } |