1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file or at 6 // https://developers.google.com/open-source/licenses/bsd 7 8 package com.google.protobuf; 9 10 import com.google.protobuf.Descriptors.FieldDescriptor; 11 import java.util.ArrayList; 12 import java.util.Collections; 13 import java.util.HashMap; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.Map.Entry; 17 18 /** 19 * Data structure which is populated with the locations of each field value parsed from the text. 20 * 21 * <p>The locations of primary fields values are retrieved by {@code getLocation} or {@code 22 * getLocations}. The locations of sub message values are within nested {@code 23 * TextFormatParseInfoTree}s and are retrieve by {@code getNestedTree} or {@code getNestedTrees}. 24 * 25 * <p>The {@code TextFormatParseInfoTree} is created by a Builder. 26 */ 27 public class TextFormatParseInfoTree { 28 29 // Defines a mapping between each field's descriptor to the list of locations where 30 // its value(s) were was encountered. 31 private Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField; 32 33 // Defines a mapping between a field's descriptor to a list of TextFormatParseInfoTrees for 34 // sub message location information. 35 Map<FieldDescriptor, List<TextFormatParseInfoTree>> subtreesFromField; 36 37 /** 38 * Construct a {@code TextFormatParseInfoTree}. 39 * 40 * @param locationsFromField a map of fields to location in the source code 41 * @param subtreeBuildersFromField a map of fields to parse tree location information builders 42 */ TextFormatParseInfoTree( Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField, Map<FieldDescriptor, List<TextFormatParseInfoTree.Builder>> subtreeBuildersFromField)43 private TextFormatParseInfoTree( 44 Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField, 45 Map<FieldDescriptor, List<TextFormatParseInfoTree.Builder>> subtreeBuildersFromField) { 46 47 // The maps are unmodifiable. The values in the maps are unmodifiable. 48 Map<FieldDescriptor, List<TextFormatParseLocation>> locs = 49 new HashMap<FieldDescriptor, List<TextFormatParseLocation>>(); 50 for (Entry<FieldDescriptor, List<TextFormatParseLocation>> kv : locationsFromField.entrySet()) { 51 locs.put(kv.getKey(), Collections.unmodifiableList(kv.getValue())); 52 } 53 this.locationsFromField = Collections.unmodifiableMap(locs); 54 55 Map<FieldDescriptor, List<TextFormatParseInfoTree>> subs = 56 new HashMap<FieldDescriptor, List<TextFormatParseInfoTree>>(); 57 for (Entry<FieldDescriptor, List<Builder>> kv : subtreeBuildersFromField.entrySet()) { 58 List<TextFormatParseInfoTree> submessagesOfField = new ArrayList<TextFormatParseInfoTree>(); 59 for (Builder subBuilder : kv.getValue()) { 60 submessagesOfField.add(subBuilder.build()); 61 } 62 subs.put(kv.getKey(), Collections.unmodifiableList(submessagesOfField)); 63 } 64 this.subtreesFromField = Collections.unmodifiableMap(subs); 65 } 66 67 /** 68 * Retrieve all the locations of a field. 69 * 70 * @param fieldDescriptor the {@link FieldDescriptor} of the desired field 71 * @return a list of the locations of values of the field. If there are not values or the field 72 * doesn't exist, an empty list is returned. 73 */ getLocations(final FieldDescriptor fieldDescriptor)74 public List<TextFormatParseLocation> getLocations(final FieldDescriptor fieldDescriptor) { 75 List<TextFormatParseLocation> result = locationsFromField.get(fieldDescriptor); 76 return (result == null) ? Collections.<TextFormatParseLocation>emptyList() : result; 77 } 78 79 /** 80 * Get the location in the source of a field's value. 81 * 82 * <p>Returns the {@link TextFormatParseLocation} for index-th value of the field in the parsed 83 * text. 84 * 85 * @param fieldDescriptor the {@link FieldDescriptor} of the desired field 86 * @param index the index of the value. 87 * @return the {@link TextFormatParseLocation} of the value 88 * @throws IllegalArgumentException index is out of range 89 */ getLocation(final FieldDescriptor fieldDescriptor, int index)90 public TextFormatParseLocation getLocation(final FieldDescriptor fieldDescriptor, int index) { 91 return getFromList(getLocations(fieldDescriptor), index, fieldDescriptor); 92 } 93 94 /** 95 * Retrieve a list of all the location information trees for a sub message field. 96 * 97 * @param fieldDescriptor the {@link FieldDescriptor} of the desired field 98 * @return A list of {@link TextFormatParseInfoTree} 99 */ getNestedTrees(final FieldDescriptor fieldDescriptor)100 public List<TextFormatParseInfoTree> getNestedTrees(final FieldDescriptor fieldDescriptor) { 101 List<TextFormatParseInfoTree> result = subtreesFromField.get(fieldDescriptor); 102 return result == null ? Collections.<TextFormatParseInfoTree>emptyList() : result; 103 } 104 105 /** 106 * Returns the parse info tree for the given field, which must be a message type. 107 * 108 * @param fieldDescriptor the {@link FieldDescriptor} of the desired sub message 109 * @param index the index of message value. 110 * @return the {@code ParseInfoTree} of the message value. {@code null} is returned if the field 111 * doesn't exist or the index is out of range. 112 * @throws IllegalArgumentException if index is out of range 113 */ getNestedTree(final FieldDescriptor fieldDescriptor, int index)114 public TextFormatParseInfoTree getNestedTree(final FieldDescriptor fieldDescriptor, int index) { 115 return getFromList(getNestedTrees(fieldDescriptor), index, fieldDescriptor); 116 } 117 118 /** 119 * Create a builder for a {@code ParseInfoTree}. 120 * 121 * @return the builder 122 */ builder()123 public static Builder builder() { 124 return new Builder(); 125 } 126 getFromList(List<T> list, int index, FieldDescriptor fieldDescriptor)127 private static <T> T getFromList(List<T> list, int index, FieldDescriptor fieldDescriptor) { 128 if (index >= list.size() || index < 0) { 129 throw new IllegalArgumentException( 130 String.format( 131 "Illegal index field: %s, index %d", 132 fieldDescriptor == null ? "<null>" : fieldDescriptor.getName(), index)); 133 } 134 return list.get(index); 135 } 136 137 /** Builder for a {@link TextFormatParseInfoTree}. */ 138 public static class Builder { 139 140 private Map<FieldDescriptor, List<TextFormatParseLocation>> locationsFromField; 141 142 // Defines a mapping between a field's descriptor to a list of ParseInfoTrees builders for 143 // sub message location information. 144 private Map<FieldDescriptor, List<Builder>> subtreeBuildersFromField; 145 146 /** Create a root level {@ParseInfoTree} builder. */ Builder()147 private Builder() { 148 locationsFromField = new HashMap<FieldDescriptor, List<TextFormatParseLocation>>(); 149 subtreeBuildersFromField = new HashMap<FieldDescriptor, List<Builder>>(); 150 } 151 152 /** 153 * Record the starting location of a single value for a field. 154 * 155 * @param fieldDescriptor the field 156 * @param location source code location information 157 */ setLocation( final FieldDescriptor fieldDescriptor, TextFormatParseLocation location)158 public Builder setLocation( 159 final FieldDescriptor fieldDescriptor, TextFormatParseLocation location) { 160 List<TextFormatParseLocation> fieldLocations = locationsFromField.get(fieldDescriptor); 161 if (fieldLocations == null) { 162 fieldLocations = new ArrayList<TextFormatParseLocation>(); 163 locationsFromField.put(fieldDescriptor, fieldLocations); 164 } 165 fieldLocations.add(location); 166 return this; 167 } 168 169 /** 170 * Set for a sub message. 171 * 172 * <p>A new builder is created for a sub message. The builder that is returned is a new builder. 173 * The return is <em>not</em> the invoked {@code builder.getBuilderForSubMessageField}. 174 * 175 * @param fieldDescriptor the field whose value is the submessage 176 * @return a new Builder for the sub message 177 */ getBuilderForSubMessageField(final FieldDescriptor fieldDescriptor)178 public Builder getBuilderForSubMessageField(final FieldDescriptor fieldDescriptor) { 179 List<Builder> submessageBuilders = subtreeBuildersFromField.get(fieldDescriptor); 180 if (submessageBuilders == null) { 181 submessageBuilders = new ArrayList<Builder>(); 182 subtreeBuildersFromField.put(fieldDescriptor, submessageBuilders); 183 } 184 Builder subtreeBuilder = new Builder(); 185 submessageBuilders.add(subtreeBuilder); 186 return subtreeBuilder; 187 } 188 189 /** 190 * Build the {@code TextFormatParseInfoTree}. 191 * 192 * @return the {@code TextFormatParseInfoTree} 193 */ build()194 public TextFormatParseInfoTree build() { 195 return new TextFormatParseInfoTree(locationsFromField, subtreeBuildersFromField); 196 } 197 } 198 } 199