Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / search / Document.java
blob34def04910ab7be64432b36f0a20d3f6e7067ccd
1 // Copyright 2010 Google Inc. All Rights Reserved.
3 package com.google.appengine.api.search;
5 import com.google.appengine.api.search.checkers.DocumentChecker;
6 import com.google.appengine.api.search.checkers.FieldChecker;
7 import com.google.appengine.api.search.checkers.Preconditions;
8 import com.google.apphosting.api.search.DocumentPb;
10 import java.io.Serializable;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Locale;
16 import java.util.Map;
17 import java.util.Set;
19 /**
20 * Represents a user generated document. The following example shows how to
21 * create a document consisting of a set of fields, some with plain text and
22 * some in HTML.
23 * <pre>
24 * Document document = Document.newBuilder().setId("document id")
25 * .setLocale(Locale.UK)
26 * .addField(Field.newBuilder()
27 * .setName("subject")
28 * .setText("going for dinner"))
29 * .addField(Field.newBuilder()
30 * .setName("body")
31 * .setHTML("&lt;html&gt;I found a restaurant.&lt;/html&gt;"))
32 * .addField(Field.newBuilder()
33 * .setName("signature")
34 * .setText("ten post jest przeznaczony dla odbiorcy")
35 * .setLocale(new Locale("pl")))
36 * .addField(Field.newBuilder()
37 * .setName("tag")
38 * .setText("food"))
39 * .addField(Field.newBuilder()
40 * .setName("tag")
41 * .setText("friend"))
42 * .build();
43 * </pre>
45 * The following example shows how to access the fields within a document:
46 * <pre>
47 * Document document = ...
49 * for (Field field : document.getFields()) {
50 * switch (field.getType()) {
51 * case TEXT: use(field.getText()); break;
52 * case HTML: use(field.getHtml()); break;
53 * case ATOM: use(field.getAtom()); break;
54 * case DATE: use(field.getDate()); break;
55 * }
56 * }
57 * </pre>
60 public class Document implements Serializable {
62 static final int MAX_FIELDS_TO_STRING = 10;
64 /**
65 * A builder of documents. This is not thread-safe.
67 public static class Builder {
68 private final Map<String, List<Field>> fieldMap = new HashMap<String, List<Field>>();
69 private final List<Field> fields = new ArrayList<Field>();
70 private String documentId; private Locale locale; private Integer rank;
72 /**
73 * Constructs a builder for a document.
75 protected Builder() {
78 /**
79 * Set the document id to a unique valid value. A valid document id must
80 * be a printable ASCII string of between 1 and
81 * {@literal DocumentChecker#MAXIMUM_DOCUMENT_ID_LENGTH} characters, and
82 * also not start with '!' which is reserved. If no document id is
83 * provided, then the search service will provide one when the document
84 * is indexed.
86 * @param documentId the unique id for the document to be built
87 * @return this builder
88 * @throws IllegalArgumentException if documentId is not valid
90 public Builder setId(String documentId) {
91 if (documentId != null) {
92 this.documentId = DocumentChecker.checkDocumentId(documentId);
94 return this;
97 /**
98 * Adds the field builder to the document builder. Allows multiple
99 * fields with the same name.
101 * @param builder the builder of the field to add
102 * @return this document builder
104 public Builder addField(Field.Builder builder) {
105 Preconditions.checkNotNull(builder, "field builder cannot be null");
106 return addField(builder.build());
110 * Adds the field to the builder. Allows multiple
111 * fields with the same name.
113 * @param field the field to add
114 * @return this builder
115 * @throws IllegalArgumentException if the field is invalid
117 public Builder addField(Field field) {
118 Preconditions.checkNotNull(field, "field cannot be null");
119 fields.add(field);
120 List<Field> fieldsForName = fieldMap.get(field.getName());
121 if (fieldsForName == null) {
122 fieldsForName = new ArrayList<Field>();
123 fieldMap.put(field.getName(), fieldsForName);
125 fieldsForName.add(field);
126 return this;
130 * Sets the {@link Locale} the document is written in.
132 * @param locale the {@link Locale} the document is written in
133 * @return this document builder
135 public Builder setLocale(Locale locale) {
136 this.locale = locale;
137 return this;
141 * @deprecated Please use {@link #setRank(int)} instead
142 * @see #setRank(int)
144 @Deprecated
145 public Builder setOrderId(int orderId) {
146 this.rank = orderId;
147 return this;
151 * Sets the rank of this document, which determines the order of documents
152 * returned by search, if no sorting or scoring is given. If it is not
153 * specified, then the number of seconds since 2011/1/1 will be used.
155 * @param rank the rank of this document
156 * @return this builder
158 public Builder setRank(int rank) {
159 this.rank = rank;
160 return this;
164 * Builds a valid document. The builder must have set a valid document
165 * id, and have a non-empty set of valid fields.
167 * @return the document built by this builder
168 * @throws IllegalArgumentException if the document built is not valid
170 public Document build() {
171 return new Document(this);
175 private static final long serialVersionUID = 309382038422977263L;
177 private final String documentId;
178 private final Map<String, List<Field>> fieldMap;
179 private final List<Field> fields;
181 private final int rank;
183 private final Locale locale;
186 * Constructs a document with the given builder.
188 * @param builder the builder capable of building a document
190 protected Document(Builder builder) {
191 documentId = builder.documentId;
192 fieldMap = new HashMap<String, List<Field>>(builder.fieldMap);
193 fields = Collections.unmodifiableList(builder.fields);
194 locale = builder.locale;
195 rank = Util.defaultIfNull(builder.rank, DocumentChecker.getNumberOfSecondsSince());
196 checkValid();
200 * @return an iterable of {@link Field} in the document
202 public Iterable<Field> getFields() {
203 return fields;
207 * @return an unmodifiable {@link Set} of the field names in the document
209 public Set<String> getFieldNames() {
210 return Collections.unmodifiableSet(fieldMap.keySet());
214 * @param name the name of the field name whose values are to be returned
215 * @return an unmodifiable {@link Iterable} of {@link Field} with the given name
216 * or {@code null}
218 public Iterable<Field> getField(String name) {
219 List<Field> fieldsForName = fieldMap.get(name);
220 if (fieldsForName == null) {
221 return null;
223 return Collections.unmodifiableList(fieldsForName);
227 * Returns the single field with the given name.
229 * @param name the name of the field to return
230 * @return the single field with name
231 * @throws IllegalArgumentException if the document does not have exactly
232 * one field with the name
234 public Field getOnlyField(String name) {
235 List<Field> fieldsForName = fieldMap.get(name);
236 Preconditions.checkArgument(
237 fieldsForName != null && fieldsForName.size() == 1,
238 "Field %s is present %d times; expected 1",
239 name, (fieldsForName == null? 0 : fieldsForName.size()));
240 return fieldsForName.get(0);
244 * Returns the number of times a field with the given name is present
245 * in this document.
247 * @param name the name of the field to be counted
248 * @return the number of times a field with the given name is present
250 public int getFieldCount(String name) {
251 List<Field> fieldsForName = fieldMap.get(name);
252 return fieldsForName == null ? 0 : fieldsForName.size();
256 * @return the id of the document
258 public String getId() {
259 return documentId;
263 * @return the {@link Locale} the document is written in. Can be null
265 public Locale getLocale() {
266 return locale;
270 * @deprecated please use {@link #getRank()}
271 * @see #getRank()
273 @Deprecated
274 public int getOrderId() {
275 return rank;
279 * Returns the rank of this document. A document's rank is used to
280 * determine the default order in which documents are returned by
281 * search, if no sorting or scoring is specified.
283 * @return the rank of this document
285 public int getRank() {
286 return rank;
289 @Override
290 public int hashCode() {
291 return documentId.hashCode();
294 @Override
295 public boolean equals(Object object) {
296 if (this == object) {
297 return true;
299 if (!(object instanceof Document)) {
300 return false;
302 Document doc = (Document) object;
303 return documentId.equals(doc.getId());
307 * Checks whether the document is valid. A document is valid if
308 * it has a valid document id, a locale, a non-empty collection of
309 * valid fields.
311 * @return this document
312 * @throws IllegalArgumentException if the document has an invalid
313 * document id, has no fields, or some field is invalid
315 private Document checkValid() {
316 if (documentId != null) {
317 DocumentChecker.checkDocumentId(documentId);
319 Preconditions.checkArgument(fieldMap != null,
320 "Null map of fields in document for indexing");
321 Preconditions.checkArgument(fields != null,
322 "Null list of fields in document for indexing");
323 return this;
327 * Creates a new document builder. You must use this method to obtain a new
328 * builder. The returned builder must be used to specify all properties of
329 * the document. To obtain the document call the {@link Builder#build()}
330 * method on the returned builder.
332 * @return a builder which constructs a document object
334 public static Builder newBuilder() {
335 return new Builder();
339 * Creates a new document builder from the given document. All document
340 * properties are copied to the returned builder.
342 * @param document the document protocol buffer to build a document object
343 * from
344 * @return the document builder initialized from a document protocol buffer
345 * @throws SearchException if a field value is invalid
347 static Builder newBuilder(DocumentPb.Document document) {
348 Document.Builder docBuilder = Document.newBuilder().setId(document.getId());
349 if (document.hasLanguage()) {
350 docBuilder.setLocale(FieldChecker.parseLocale(document.getLanguage()));
352 for (DocumentPb.Field field : document.getFieldList()) {
353 docBuilder.addField(Field.newBuilder(field));
355 if (document.hasOrderId()) {
356 docBuilder.setRank(document.getOrderId());
358 return docBuilder;
362 * Copies a {@link Document} object into a {@link DocumentPb.Document}
363 * protocol buffer.
365 * @return the document protocol buffer copy of the document object
366 * @throws IllegalArgumentException if any parts of the document are invalid
367 * or the document protocol buffer is too large
369 DocumentPb.Document copyToProtocolBuffer() {
370 DocumentPb.Document.Builder docBuilder = DocumentPb.Document.newBuilder();
371 if (documentId != null) {
372 docBuilder.setId(documentId);
374 if (locale != null) {
375 docBuilder.setLanguage(locale.toString());
377 for (Field field : fields) {
378 docBuilder.addField(field.copyToProtocolBuffer());
380 docBuilder.setOrderId(rank);
381 return DocumentChecker.checkValid(docBuilder.build());
384 @Override
385 public String toString() {
386 return String.format("Document(documentId=%s, fields=%s%s, orderId=%d)",
387 documentId,
388 Util.iterableToString(fields, MAX_FIELDS_TO_STRING),
389 Util.fieldToString("locale", locale), rank);