View Javadoc
1   /*
2    * #%L
3    * AbstractBuilder.java - mongodb-async-driver - Allanbank Consulting, Inc.
4    * %%
5    * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.allanbank.mongodb.bson.builder.impl;
21  
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.util.ArrayList;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  
29  import com.allanbank.mongodb.bson.Element;
30  import com.allanbank.mongodb.bson.ElementType;
31  import com.allanbank.mongodb.bson.Visitor;
32  import com.allanbank.mongodb.bson.builder.ArrayBuilder;
33  import com.allanbank.mongodb.bson.builder.Builder;
34  import com.allanbank.mongodb.bson.builder.DocumentBuilder;
35  import com.allanbank.mongodb.bson.element.AbstractElement;
36  
37  /**
38   * Base class with common functionality for the all builders. A builder is
39   * responsible for constructing a single level of the BSON document.
40   * 
41   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
42   *         mutated in incompatible ways between any two releases of the driver.
43   * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
44   */
45  public abstract class AbstractBuilder implements Builder {
46  
47      /** If true then assertions have been enabled for the class. */
48      protected static final boolean ASSERTIONS_ENABLED;
49  
50      /** The class used for intermediate sub-builders in the elements list. */
51      protected static final Class<BuilderElement> BUILDER_ELEMENT_CLASS;
52  
53      static {
54          BUILDER_ELEMENT_CLASS = BuilderElement.class;
55          ASSERTIONS_ENABLED = AbstractBuilder.class.desiredAssertionStatus();
56      }
57  
58      /** The list of elements in the builder. */
59      protected final List<Element> myElements;
60  
61      /** The size of the document added. */
62      protected long mySize;
63  
64      /** The outer scope to this builder. */
65      private final AbstractBuilder myOuterBuilder;
66  
67      /**
68       * Creates a new builder.
69       * 
70       * @param outerBuilder
71       *            The outer scoped builder.
72       */
73      public AbstractBuilder(final AbstractBuilder outerBuilder) {
74          super();
75          myOuterBuilder = outerBuilder;
76          myElements = new ArrayList<Element>(32);
77          mySize = 0;
78      }
79  
80      /**
81       * {@inheritDoc}
82       */
83      @Override
84      public Builder pop() {
85          return myOuterBuilder;
86      }
87  
88      /**
89       * {@inheritDoc}
90       */
91      @Override
92      public Builder reset() {
93          myElements.clear();
94          return this;
95      }
96  
97      /**
98       * Constructs the final form of the element being constructed.
99       * 
100      * @param name
101      *            The name of the element.
102      * @return The Element constructed by the builder.
103      */
104     protected abstract Element build(String name);
105 
106     /**
107      * Pushes a context for constructing a sub-document.
108      * 
109      * @param name
110      *            The name of the sub-document.
111      * @return A {@link DocumentBuilder} for constructing the sub-document.
112      */
113 
114     protected DocumentBuilder doPush(final String name) {
115         final DocumentBuilderImpl pushed = new DocumentBuilderImpl(this);
116         myElements.add(new BuilderElement(name, pushed));
117         return pushed;
118     }
119 
120     /**
121      * Pushes a context for constructing a sub-array.
122      * 
123      * @param name
124      *            The name of the sub-array.
125      * @return A {@link ArrayBuilder} for constructing the sub-array.
126      */
127     protected ArrayBuilder doPushArray(final String name) {
128         final ArrayBuilderImpl pushed = new ArrayBuilderImpl(this);
129         myElements.add(new BuilderElement(name, pushed));
130         return pushed;
131     }
132 
133     /**
134      * Renders the final form of the sub elements in the builder replacing all
135      * {@link BuilderElement}s with the final element form.
136      * 
137      * @return The final sub element list.
138      */
139     protected List<Element> subElements() {
140         final List<Element> elements = new ArrayList<Element>(myElements.size());
141 
142         Set<String> names = null;
143         for (Element element : myElements) {
144             if (element.getClass() == BUILDER_ELEMENT_CLASS) {
145                 element = ((BuilderElement) element).build();
146             }
147 
148             if (ASSERTIONS_ENABLED) {
149                 if (names == null) {
150                     names = new HashSet<String>(myElements.size() << 1);
151                 }
152                 final String name = element.getName();
153                 if (!names.add(name)) {
154                     assert false : name + " is not unique in  " + myElements;
155                 }
156             }
157 
158             elements.add(element);
159         }
160 
161         return elements;
162     }
163 
164     /**
165      * A temporary Element to stand in for a element being constructed with a
166      * builder.
167      * <p>
168      * <b>Note:</b> This class if final to allow the class comparison in
169      * {@link AbstractBuilder}.subElements() method.
170      * </p>
171      */
172     public static final class BuilderElement extends AbstractElement {
173 
174         /** Serialization version for the class. */
175         private static final long serialVersionUID = 4421203621373216989L;
176 
177         /** The encapsulated builder. */
178         private transient AbstractBuilder myBuilder;
179 
180         /**
181          * Creates a new {@link BuilderElement}.
182          * 
183          * @param name
184          *            The name for the element to build.
185          * @param builder
186          *            The Builder doing the building.
187          */
188         public BuilderElement(final String name, final AbstractBuilder builder) {
189             super(name, 0);
190             myBuilder = builder;
191         }
192 
193         /**
194          * {@inheritDoc}
195          */
196         @Override
197         public void accept(final Visitor visitor) {
198             build().accept(visitor);
199         }
200 
201         /**
202          * Constructs the final form of the element being constructed by the
203          * encapsulated builder.
204          * 
205          * @return The Element constructed by the encapsulated builder.
206          */
207         public Element build() {
208             return myBuilder.build(getName());
209         }
210 
211         /**
212          * {@inheritDoc}
213          */
214         @Override
215         public ElementType getType() {
216             return null;
217         }
218 
219         /**
220          * {@inheritDoc}
221          * <p>
222          * Overridden to return null as this class should not be seen outside of
223          * the builders.
224          * </p>
225          */
226         @Override
227         public Object getValueAsObject() {
228             return null;
229         }
230 
231         /**
232          * {@inheritDoc}
233          * <p>
234          * Returns a new {@link BuilderElement}.
235          * </p>
236          */
237         @Override
238         public BuilderElement withName(final String name) {
239             return new BuilderElement(name, myBuilder);
240         }
241 
242         /**
243          * Sets the transient state of this non-Element.
244          * 
245          * @param in
246          *            The input stream.
247          * @throws ClassNotFoundException
248          *             On a failure loading a class in this classed reachable
249          *             tree.
250          * @throws IOException
251          *             On a failure reading from the stream.
252          */
253         private void readObject(final ObjectInputStream in)
254                 throws ClassNotFoundException, IOException {
255             in.defaultReadObject();
256             myBuilder = null;
257         }
258     }
259 }