1 /*
2 * #%L
3 * JavaScriptWithScopeElement.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.element;
21
22 import static com.allanbank.mongodb.util.Assertions.assertNotNull;
23
24 import java.util.Iterator;
25
26 import com.allanbank.mongodb.bson.Document;
27 import com.allanbank.mongodb.bson.Element;
28 import com.allanbank.mongodb.bson.ElementType;
29 import com.allanbank.mongodb.bson.Visitor;
30 import com.allanbank.mongodb.bson.builder.BuilderFactory;
31 import com.allanbank.mongodb.bson.builder.DocumentBuilder;
32 import com.allanbank.mongodb.bson.io.StringEncoder;
33
34 /**
35 * A wrapper for a BSON JavaScript with Scope.
36 *
37 * @api.yes This class is part of the driver's API. Public and protected members
38 * will be deprecated for at least 1 non-bugfix release (version
39 * numbers are <major>.<minor>.<bugfix>) before being
40 * removed or modified.
41 * @copyright 2011-2013, Allanbank Consulting, Inc., All Rights Reserved
42 */
43 public class JavaScriptWithScopeElement extends JavaScriptElement {
44
45 /** The BSON type for a string. */
46 @SuppressWarnings("hiding")
47 public static final ElementType TYPE = ElementType.JAVA_SCRIPT_WITH_SCOPE;
48
49 /** Serialization version for the class. */
50 private static final long serialVersionUID = -5697976862389984453L;
51
52 /**
53 * Computes and returns the number of bytes that are used to encode the
54 * element.
55 *
56 * @param name
57 * The name for the element.
58 * @param javaScript
59 * The BSON JavaScript value.
60 * @param scope
61 * The scope for the JavaScript
62 * @return The size of the element when encoded in bytes.
63 */
64 private static long computeSize(final String name, final String javaScript,
65 final Document scope) {
66 long result = 15; // type (1) + name null byte (1) + length (4)
67 // javaScript length (4) and null byte (5)
68 result += StringEncoder.utf8Size(name);
69 result += StringEncoder.utf8Size(javaScript);
70 if (scope != null) {
71 result += scope.size();
72 }
73
74 return result;
75 }
76
77 /** The BSON scope value. */
78 private final Document myScope;
79
80 /**
81 * Constructs a new {@link JavaScriptWithScopeElement}.
82 *
83 * @param name
84 * The name for the BSON string.
85 * @param javaScript
86 * The BSON JavaScript value.
87 * @param scope
88 * The scope for the JavaScript
89 * @throws IllegalArgumentException
90 * If the {@code name}, {@code javaScript}, or {@code scope} is
91 * <code>null</code>.
92 */
93 public JavaScriptWithScopeElement(final String name,
94 final String javaScript, final Document scope) {
95 this(name, javaScript, scope, computeSize(name, javaScript, scope));
96
97 assertNotNull(scope, "JavaScript element's scope cannot be null.");
98 }
99
100 /**
101 * Constructs a new {@link JavaScriptWithScopeElement}.
102 *
103 * @param name
104 * The name for the BSON string.
105 * @param javaScript
106 * The BSON JavaScript value.
107 * @param scope
108 * The scope for the JavaScript
109 * @param size
110 * The size of the element when encoded in bytes. If not known
111 * then use the
112 * {@link JavaScriptWithScopeElement#JavaScriptWithScopeElement(String, String, Document)}
113 * constructor instead.
114 * @throws IllegalArgumentException
115 * If the {@code name}, {@code javaScript}, or {@code scope} is
116 * <code>null</code>.
117 */
118 public JavaScriptWithScopeElement(final String name,
119 final String javaScript, final Document scope, final long size) {
120 super(name, javaScript, size);
121
122 assertNotNull(scope, "JavaScript element's scope cannot be null.");
123
124 myScope = scope;
125 }
126
127 /**
128 * Accepts the visitor and calls the
129 * {@link Visitor#visitJavaScript(String,String,Document)} method.
130 *
131 * @see Element#accept(Visitor)
132 */
133 @Override
134 public void accept(final Visitor visitor) {
135 visitor.visitJavaScript(getName(), getJavaScript(), getScope());
136 }
137
138 /**
139 * {@inheritDoc}
140 * <p>
141 * Overridden to compare the Java Script (as text) if the base class
142 * comparison is equals.
143 * </p>
144 */
145 @Override
146 public int compareTo(final Element otherElement) {
147 int result = super.compareTo(otherElement);
148
149 if (result == 0) {
150 final JavaScriptWithScopeElement other = (JavaScriptWithScopeElement) otherElement;
151
152 final Iterator<Element> thisIter = myScope.iterator();
153 final Iterator<Element> otherIter = other.myScope.iterator();
154 while (thisIter.hasNext() && otherIter.hasNext()) {
155 result = thisIter.next().compareTo(otherIter.next());
156 if (result != 0) {
157 return result;
158 }
159 }
160
161 if (thisIter.hasNext()) {
162 return 1;
163 }
164 else if (otherIter.hasNext()) {
165 return -1;
166 }
167 else {
168 return 0;
169 }
170 }
171
172 return result;
173 }
174
175 /**
176 * Determines if the passed object is of this same type as this object and
177 * if so that its fields are equal.
178 *
179 * @param object
180 * The object to compare to.
181 *
182 * @see java.lang.Object#equals(java.lang.Object)
183 */
184 @Override
185 public boolean equals(final Object object) {
186 boolean result = false;
187 if (this == object) {
188 result = true;
189 }
190 else if ((object != null) && (getClass() == object.getClass())) {
191 final JavaScriptWithScopeElement other = (JavaScriptWithScopeElement) object;
192
193 result = super.equals(object)
194 && nullSafeEquals(myScope, other.myScope);
195 }
196 return result;
197 }
198
199 /**
200 * Returns the BSON JavaScript scope.
201 *
202 * @return The BSON JavaScript scope.
203 */
204 public Document getScope() {
205 return myScope;
206 }
207
208 /**
209 * {@inheritDoc}
210 */
211 @Override
212 public ElementType getType() {
213 return TYPE;
214 }
215
216 /**
217 * {@inheritDoc}
218 * <p>
219 * Returns a document representing the code and scope similar to the strict
220 * JSON encoding.
221 * </p>
222 * <p>
223 * <b>Note:</b> This value will not be recreated is a Object-->Element
224 * conversion. A more generic sub-document is created instead.
225 * </p>
226 */
227 @Override
228 public Document getValueAsObject() {
229 final DocumentBuilder b = BuilderFactory.start();
230 b.add("$code", getJavaScript());
231 b.add("$scope", myScope);
232
233 return b.build();
234 }
235
236 /**
237 * Computes a reasonable hash code.
238 *
239 * @return The hash code value.
240 */
241 @Override
242 public int hashCode() {
243 int result = 1;
244 result = (31 * result) + super.hashCode();
245 result = (31 * result) + ((myScope != null) ? myScope.hashCode() : 3);
246 return result;
247 }
248
249 /**
250 * {@inheritDoc}
251 * <p>
252 * Returns a new {@link JavaScriptElement}.
253 * </p>
254 */
255 @Override
256 public JavaScriptWithScopeElement withName(final String name) {
257 if (getName().equals(name)) {
258 return this;
259 }
260 return new JavaScriptWithScopeElement(name, getJavaScript(), myScope);
261 }
262 }