1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.api.generator.gapic.protoparser; 16 17 import com.google.api.generator.gapic.model.SourceCodeInfoLocation; 18 import com.google.common.base.Function; 19 import com.google.common.base.Joiner; 20 import com.google.common.collect.ImmutableList; 21 import com.google.common.collect.ImmutableListMultimap; 22 import com.google.common.collect.Maps; 23 import com.google.common.collect.Multimaps; 24 import com.google.protobuf.DescriptorProtos.DescriptorProto; 25 import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; 26 import com.google.protobuf.DescriptorProtos.FileDescriptorProto; 27 import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto; 28 import com.google.protobuf.DescriptorProtos.SourceCodeInfo.Location; 29 import com.google.protobuf.Descriptors.Descriptor; 30 import com.google.protobuf.Descriptors.EnumDescriptor; 31 import com.google.protobuf.Descriptors.EnumValueDescriptor; 32 import com.google.protobuf.Descriptors.FieldDescriptor; 33 import com.google.protobuf.Descriptors.FileDescriptor; 34 import com.google.protobuf.Descriptors.MethodDescriptor; 35 import com.google.protobuf.Descriptors.OneofDescriptor; 36 import com.google.protobuf.Descriptors.ServiceDescriptor; 37 import java.util.Map; 38 import javax.annotation.Nullable; 39 40 /** 41 * A helper class which provides protocol buffer source info for descriptors. 42 * 43 * <p>In order to make this work, the descriptors need to be produced using the flag {@code 44 * --include_source_info}. Note that descriptors taken from the generated java code have source info 45 * stripped, and won't work with this class. 46 * 47 * <p>This class uses internal caches to speed up access to the source info. It is not thread safe. 48 * If you think you need this functionality in a thread-safe context, feel free to suggest a 49 * refactor. 50 */ 51 public class SourceCodeInfoParser { 52 /** 53 * A map from file descriptors to the analyzed source info, stored as a multimap from a path of 54 * the form {@code n.m.l} to the location info. 55 */ 56 private final Map<FileDescriptor, ImmutableListMultimap<String, Location>> fileToPathToLocation = 57 Maps.newHashMap(); 58 59 /** A map from descriptor objects to the path to those objects in their proto file. */ 60 private final Map<Object, String> descriptorToPath = Maps.newHashMap(); 61 62 /** Gets the location of a message, if available. */ 63 @Nullable getLocation(Descriptor message)64 public SourceCodeInfoLocation getLocation(Descriptor message) { 65 FileDescriptor file = message.getFile(); 66 if (!file.toProto().hasSourceCodeInfo()) { 67 return null; 68 } 69 return SourceCodeInfoLocation.create(getLocation(file, buildPath(message))); 70 } 71 72 /** Gets the location of a field, if available. */ 73 @Nullable getLocation(FieldDescriptor field)74 public SourceCodeInfoLocation getLocation(FieldDescriptor field) { 75 FileDescriptor file = field.getFile(); 76 if (!file.toProto().hasSourceCodeInfo()) { 77 return null; 78 } 79 Location fieldLocation = getLocation(file, buildPath(field)); 80 return SourceCodeInfoLocation.create(fieldLocation); 81 } 82 83 /** Gets the location of a service, if available. */ 84 @Nullable getLocation(ServiceDescriptor service)85 public SourceCodeInfoLocation getLocation(ServiceDescriptor service) { 86 FileDescriptor file = service.getFile(); 87 if (!file.toProto().hasSourceCodeInfo()) { 88 return null; 89 } 90 return SourceCodeInfoLocation.create(getLocation(file, buildPath(service))); 91 } 92 93 /** Gets the location of a method, if available. */ 94 @Nullable getLocation(MethodDescriptor method)95 public SourceCodeInfoLocation getLocation(MethodDescriptor method) { 96 FileDescriptor file = method.getFile(); 97 if (!file.toProto().hasSourceCodeInfo()) { 98 return null; 99 } 100 return SourceCodeInfoLocation.create(getLocation(file, buildPath(method))); 101 } 102 103 /** Gets the location of an enum type, if available. */ 104 @Nullable getLocation(EnumDescriptor enumType)105 public SourceCodeInfoLocation getLocation(EnumDescriptor enumType) { 106 FileDescriptor file = enumType.getFile(); 107 if (!file.toProto().hasSourceCodeInfo()) { 108 return null; 109 } 110 return SourceCodeInfoLocation.create(getLocation(file, buildPath(enumType))); 111 } 112 113 /** Gets the location of an enum value, if available. */ 114 @Nullable getLocation(EnumValueDescriptor enumValue)115 public SourceCodeInfoLocation getLocation(EnumValueDescriptor enumValue) { 116 FileDescriptor file = enumValue.getFile(); 117 if (!file.toProto().hasSourceCodeInfo()) { 118 return null; 119 } 120 return SourceCodeInfoLocation.create(getLocation(file, buildPath(enumValue))); 121 } 122 123 /** Gets the location of a oneof, if available. */ 124 @Nullable getLocation(OneofDescriptor oneof)125 public SourceCodeInfoLocation getLocation(OneofDescriptor oneof) { 126 FileDescriptor file = oneof.getFile(); 127 if (!file.toProto().hasSourceCodeInfo()) { 128 return null; 129 } 130 return SourceCodeInfoLocation.create(getLocation(file, buildPath(oneof))); 131 } 132 133 // ----------------------------------------------------------------------------- 134 // Helpers 135 136 /** 137 * A helper to compute the location based on a file descriptor and a path into that descriptor. 138 */ getLocation(FileDescriptor file, String path)139 private Location getLocation(FileDescriptor file, String path) { 140 ImmutableList<Location> cands = getCandidateLocations(file, path); 141 if (cands != null && cands.isEmpty()) { 142 return null; 143 } else { 144 return cands.get(0); // We choose the first one. 145 } 146 } 147 getCandidateLocations(FileDescriptor file, String path)148 private ImmutableList<Location> getCandidateLocations(FileDescriptor file, String path) { 149 ImmutableListMultimap<String, Location> locationMap = fileToPathToLocation.get(file); 150 if (locationMap == null) { 151 locationMap = 152 Multimaps.index( 153 file.toProto().getSourceCodeInfo().getLocationList(), 154 new Function<Location, String>() { 155 @Override 156 public String apply(Location location) { 157 return Joiner.on('.').join(location.getPathList()); 158 } 159 }); 160 fileToPathToLocation.put(file, locationMap); 161 } 162 return locationMap.get(path); 163 } 164 buildPath(Descriptor message)165 private String buildPath(Descriptor message) { 166 String path = descriptorToPath.get(message); 167 if (path != null) { 168 return path; 169 } 170 if (message.getContainingType() != null) { 171 path = 172 String.format( 173 "%s.%d.%d", 174 buildPath(message.getContainingType()), 175 DescriptorProto.NESTED_TYPE_FIELD_NUMBER, 176 message.getContainingType().getNestedTypes().indexOf(message)); 177 } else { 178 path = 179 String.format( 180 "%d.%d", 181 FileDescriptorProto.MESSAGE_TYPE_FIELD_NUMBER, 182 message.getFile().getMessageTypes().indexOf(message)); 183 } 184 descriptorToPath.put(message, path); 185 return path; 186 } 187 buildPath(FieldDescriptor field)188 private String buildPath(FieldDescriptor field) { 189 String path = descriptorToPath.get(field); 190 if (path != null) { 191 return path; 192 } 193 if (field.isExtension()) { 194 if (field.getExtensionScope() == null) { 195 path = 196 String.format( 197 "%d.%d", 198 FileDescriptorProto.EXTENSION_FIELD_NUMBER, 199 field.getFile().getExtensions().indexOf(field)); 200 } else { 201 path = 202 String.format( 203 "%s.%d.%d", 204 buildPath(field.getExtensionScope()), 205 DescriptorProto.EXTENSION_FIELD_NUMBER, 206 field.getExtensionScope().getExtensions().indexOf(field)); 207 } 208 } else { 209 path = 210 String.format( 211 "%s.%d.%d", 212 buildPath(field.getContainingType()), 213 DescriptorProto.FIELD_FIELD_NUMBER, 214 field.getContainingType().getFields().indexOf(field)); 215 } 216 descriptorToPath.put(field, path); 217 return path; 218 } 219 buildPath(ServiceDescriptor service)220 private String buildPath(ServiceDescriptor service) { 221 String path = descriptorToPath.get(service); 222 if (path != null) { 223 return path; 224 } 225 path = 226 String.format( 227 "%d.%d", 228 FileDescriptorProto.SERVICE_FIELD_NUMBER, 229 service.getFile().getServices().indexOf(service)); 230 descriptorToPath.put(service, path); 231 return path; 232 } 233 buildPath(MethodDescriptor method)234 private String buildPath(MethodDescriptor method) { 235 String path = descriptorToPath.get(method); 236 if (path != null) { 237 return path; 238 } 239 path = 240 String.format( 241 "%s.%d.%d", 242 buildPath(method.getService()), 243 ServiceDescriptorProto.METHOD_FIELD_NUMBER, 244 method.getService().getMethods().indexOf(method)); 245 descriptorToPath.put(method, path); 246 return path; 247 } 248 buildPath(EnumDescriptor enumType)249 private String buildPath(EnumDescriptor enumType) { 250 String path = descriptorToPath.get(enumType); 251 if (path != null) { 252 return path; 253 } 254 if (enumType.getContainingType() != null) { 255 path = 256 String.format( 257 "%s.%d.%d", 258 buildPath(enumType.getContainingType()), 259 DescriptorProto.ENUM_TYPE_FIELD_NUMBER, 260 enumType.getContainingType().getEnumTypes().indexOf(enumType)); 261 } else { 262 path = 263 String.format( 264 "%d.%d", 265 FileDescriptorProto.ENUM_TYPE_FIELD_NUMBER, 266 enumType.getFile().getEnumTypes().indexOf(enumType)); 267 } 268 descriptorToPath.put(enumType, path); 269 return path; 270 } 271 buildPath(EnumValueDescriptor enumValue)272 private String buildPath(EnumValueDescriptor enumValue) { 273 String path = descriptorToPath.get(enumValue); 274 if (path != null) { 275 return path; 276 } 277 path = 278 String.format( 279 "%s.%d.%d", 280 buildPath(enumValue.getType()), 281 EnumDescriptorProto.VALUE_FIELD_NUMBER, 282 enumValue.getType().getValues().indexOf(enumValue)); 283 descriptorToPath.put(enumValue, path); 284 return path; 285 } 286 buildPath(OneofDescriptor oneof)287 private String buildPath(OneofDescriptor oneof) { 288 String path = descriptorToPath.get(oneof); 289 if (path != null) { 290 return path; 291 } 292 path = 293 String.format( 294 "%s.%d.%d", 295 buildPath(oneof.getContainingType()), 296 DescriptorProto.ONEOF_DECL_FIELD_NUMBER, 297 oneof.getContainingType().getOneofs().indexOf(oneof)); 298 299 descriptorToPath.put(oneof, path); 300 return path; 301 } 302 } 303