001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.schematic.internal.document;
017
018import java.util.Collections;
019import java.util.Date;
020import java.util.Iterator;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Properties;
025import java.util.UUID;
026import java.util.regex.Pattern;
027import org.modeshape.schematic.document.Array;
028import org.modeshape.schematic.document.Binary;
029import org.modeshape.schematic.document.Bson;
030import org.modeshape.schematic.document.Code;
031import org.modeshape.schematic.document.CodeWithScope;
032import org.modeshape.schematic.document.Document;
033import org.modeshape.schematic.document.Json;
034import org.modeshape.schematic.document.MaxKey;
035import org.modeshape.schematic.document.MinKey;
036import org.modeshape.schematic.document.Null;
037import org.modeshape.schematic.document.ObjectId;
038import org.modeshape.schematic.document.Symbol;
039import org.modeshape.schematic.internal.schema.DocumentTransformer.PropertiesTransformer;
040import org.modeshape.schematic.internal.schema.DocumentTransformer.SystemPropertiesTransformer;
041
042public class BasicDocument extends LinkedHashMap<String, Object> implements MutableDocument {
043
044    private static final long serialVersionUID = 1L;
045
046    private transient Map<String, Object> unmodifiableView;
047
048    public BasicDocument() {
049        super();
050    }
051
052    public BasicDocument( Document document ) {
053        putAll(document);
054    }
055
056    public BasicDocument( int initialCapacity ) {
057        super(initialCapacity);
058    }
059
060    public BasicDocument( String name,
061                          Object value ) {
062        super();
063        if (name != null) put(name, value);
064    }
065
066    public BasicDocument( String name1,
067                          Object value1,
068                          String name2,
069                          Object value2 ) {
070        super();
071        if (name1 != null) put(name1, value1);
072        if (name2 != null) put(name2, value2);
073    }
074
075    public BasicDocument( String name1,
076                          Object value1,
077                          String name2,
078                          Object value2,
079                          String name3,
080                          Object value3 ) {
081        super();
082        if (name1 != null) put(name1, value1);
083        if (name2 != null) put(name2, value2);
084        if (name3 != null) put(name3, value3);
085    }
086
087    public BasicDocument( String name1,
088                          Object value1,
089                          String name2,
090                          Object value2,
091                          String name3,
092                          Object value3,
093                          String name4,
094                          Object value4 ) {
095        super();
096        if (name1 != null) put(name1, value1);
097        if (name2 != null) put(name2, value2);
098        if (name3 != null) put(name3, value3);
099        if (name4 != null) put(name4, value4);
100    }
101
102    @Override
103    public boolean containsField( String name ) {
104        return containsKey(name);
105    }
106
107    @Override
108    public boolean containsAll( Document document ) {
109        if (document == null) {
110            return true;
111        }
112        for (Field field : document.fields()) {
113            Object thisValue = this.get(field.getName());
114            Object thatValue = field.getValue();
115            if (!BsonUtils.valuesAreEqual(thisValue, thatValue)) {
116                return false;
117            }
118        }
119        return true;
120    }
121
122    @Override
123    public Object get( String name ) {
124        return super.get(name); // calls Map.get(Object)
125    }
126
127    @Override
128    public Map<String, ?> toMap() {
129        if (unmodifiableView == null) unmodifiableView = Collections.unmodifiableMap(this);
130        return unmodifiableView;
131    }
132
133    @Override
134    public Iterable<Field> fields() {
135        return () -> {
136            final Iterator<Map.Entry<String, Object>> iter = BasicDocument.this.entrySet().iterator();
137            return new Iterator<Field>() {
138                @Override
139                public boolean hasNext() {
140                    return iter.hasNext();
141                }
142
143                @Override
144                public Field next() {
145                    Map.Entry<String, Object> entry = iter.next();
146                    return new ImmutableField(entry.getKey(), entry.getValue());
147                }
148
149                @Override
150                public void remove() {
151                    throw new UnsupportedOperationException();
152                }
153            };
154        };
155    }
156
157    @Override
158    public Boolean getBoolean( String name ) {
159        Object value = get(name);
160        return (value instanceof Boolean) ? (Boolean)value : null;
161    }
162
163    @Override
164    public boolean getBoolean( String name,
165                               boolean defaultValue ) {
166        Object value = get(name);
167        return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : defaultValue;
168    }
169
170    @Override
171    public Integer getInteger( String name ) {
172        Object value = get(name);
173        return (value instanceof Integer) ? (Integer)value : null;
174    }
175
176    @Override
177    public int getInteger( String name,
178                           int defaultValue ) {
179        Object value = get(name);
180        return (value instanceof Integer) ? ((Integer)value).intValue() : defaultValue;
181    }
182
183    @Override
184    public Long getLong( String name ) {
185        Object value = get(name);
186        if (value instanceof Long) return (Long)value;
187        if (value instanceof Integer) return new Long(((Integer)value).longValue());
188        return null;
189    }
190
191    @Override
192    public long getLong( String name,
193                         long defaultValue ) {
194        Object value = get(name);
195        if (value instanceof Long) return ((Long)value).longValue();
196        if (value instanceof Integer) return ((Integer)value).longValue();
197        return defaultValue;
198    }
199
200    @Override
201    public Double getDouble( String name ) {
202        Object value = get(name);
203        return (value instanceof Double) ? (Double)value : null;
204    }
205
206    @Override
207    public double getDouble( String name,
208                             double defaultValue ) {
209        Object value = get(name);
210        return (value instanceof Double) ? ((Double)value).doubleValue() : defaultValue;
211    }
212
213    @Override
214    public Number getNumber( String name ) {
215        Object value = get(name);
216        return (value instanceof Number) ? (Number)value : null;
217    }
218
219    @Override
220    public Number getNumber( String name,
221                             Number defaultValue ) {
222        Object value = get(name);
223        return (value instanceof Number) ? (Number)value : defaultValue;
224    }
225    
226    @Override
227    public Date getDate(String name) {
228        Object value = get(name);
229        return (value instanceof Date) ? (Date)value : null;
230    }
231    
232    @Override
233    public String getString( String name ) {
234        return getString(name, null);
235    }
236
237    @Override
238    public String getString( String name,
239                             String defaultValue ) {
240        Object value = get(name);
241        if (value != null) {
242            if (value instanceof String) {
243                return (String)value;
244            }
245            if (value instanceof Symbol) {
246                return ((Symbol)value).getSymbol();
247            }
248        }
249        return defaultValue;
250    }
251
252    @Override
253    public List<?> getArray( String name ) {
254        Object value = get(name);
255        return (value instanceof List) ? (List<?>)value : null;
256    }
257
258    @Override
259    public Document getDocument( String name ) {
260        Object value = get(name);
261        return (value instanceof Document) ? (Document)value : null;
262    }
263
264    @Override
265    public boolean isNull( String name ) {
266        return get(name) instanceof Null;
267    }
268
269    @Override
270    public boolean isNullOrMissing( String name ) {
271        return Null.matches(get(name));
272    }
273
274    @Override
275    public MaxKey getMaxKey( String name ) {
276        Object value = get(name);
277        return (value instanceof MaxKey) ? (MaxKey)value : null;
278    }
279
280    @Override
281    public MinKey getMinKey( String name ) {
282        Object value = get(name);
283        return (value instanceof MinKey) ? (MinKey)value : null;
284    }
285
286    @Override
287    public Code getCode( String name ) {
288        Object value = get(name);
289        return (value instanceof Code) ? (Code)value : null;
290    }
291
292    @Override
293    public CodeWithScope getCodeWithScope( String name ) {
294        Object value = get(name);
295        return (value instanceof CodeWithScope) ? (CodeWithScope)value : null;
296    }
297
298    @Override
299    public ObjectId getObjectId( String name ) {
300        Object value = get(name);
301        return (value instanceof ObjectId) ? (ObjectId)value : null;
302    }
303
304    @Override
305    public Binary getBinary( String name ) {
306        Object value = get(name);
307        return (value instanceof Binary) ? (Binary)value : null;
308    }
309
310    @Override
311    public Symbol getSymbol( String name ) {
312        Object value = get(name);
313        if (value != null) {
314            if (value instanceof Symbol) {
315                return (Symbol)value;
316            }
317            if (value instanceof String) {
318                return new Symbol((String)value);
319            }
320        }
321        return null;
322    }
323
324    @Override
325    public Pattern getPattern( String name ) {
326        Object value = get(name);
327        return (value instanceof Pattern) ? (Pattern)value : null;
328    }
329
330    @Override
331    public UUID getUuid( String name ) {
332        return getUuid(name, null);
333    }
334
335    @Override
336    public UUID getUuid( String name,
337                         UUID defaultValue ) {
338        Object value = get(name);
339        if (value != null) {
340            if (value instanceof UUID) {
341                return (UUID)value;
342            }
343            if (value instanceof String) {
344                try {
345                    return UUID.fromString((String)value);
346                } catch (IllegalArgumentException e) {
347                    // do nothing ...
348                }
349            }
350        }
351        return defaultValue;
352    }
353
354    @Override
355    public int getType( String name ) {
356        return Bson.getTypeForValue(get(name));
357    }
358
359    @Override
360    public void putAll( Document object ) {
361        if (object != this) {
362            // Prevent going through BasicBsonObject.unmodifiableView if we can ...
363            Map<String, ?> original = object instanceof BasicDocument ? (BasicDocument)object : object.toMap();
364            for (Map.Entry<String, ?> entry : original.entrySet()) {
365                put(entry.getKey(), unwrap(entry.getValue()));
366            }
367        }
368    }
369
370    @Override
371    public Object remove( String name ) {
372        return super.remove(name); // calls Map.remove(Object)
373    }
374
375    @Override
376    public void removeAll() {
377        super.clear();
378    }
379
380    @Override
381    public int hashCode() {
382        return super.hashCode();
383    }
384
385    @Override
386    public boolean equals( Object obj ) {
387        if (this == obj) {
388            return true;
389        }
390        if (obj instanceof Iterable) {
391            // Probably an array
392            return false;
393        }
394        if (obj instanceof Document) {
395            Document that = (Document)obj;
396            if (this.size() != that.size()) {
397                return false;
398            }
399            for (Field thisField : fields()) {
400                Object thisValue = thisField.getValue();
401                Object thatValue = that.get(thisField.getName());
402                if (!BsonUtils.valuesAreEqual(thisValue, thatValue)) {
403                    return false;
404                }
405            }
406            return true;
407        }
408        if (obj instanceof Map) {
409            Map<?, ?> that = (Map<?, ?>)obj;
410            if (this.size() != that.size()) {
411                return false;
412            }
413            if (!this.keySet().equals(that.keySet())) {
414                return false;
415            }
416            for (Field thisField : fields()) {
417                Object thisValue = thisField.getValue();
418                Object thatValue = that.get(thisField.getName());
419                if (!BsonUtils.valuesAreEqual(thisValue, thatValue)) {
420                    return false;
421                }
422            }
423            return true;
424        }
425        return false;
426    }
427
428    @Override
429    public String toString() {
430        return Json.write(this);
431    }
432
433    @Override
434    public MutableDocument clone() {
435        BasicDocument clone = new BasicDocument();
436        for (Field field : this.fields()) {
437            Object value = unwrap(field.getValue());
438            if (value instanceof Array) {
439                value = ((Array)value).clone();
440            } else if (value instanceof Document) {
441                value = ((Document)value).clone();
442            }// every other kind of value is immutable
443            clone.put(field.getName(), value);
444        }
445        return clone;
446    }
447
448    @Override
449    public Document with( Map<String, Object> changedFields ) {
450        BasicDocument clone = new BasicDocument();
451        for (Field field : this.fields()) {
452            String name = field.getName();
453            Object newValue = unwrap(changedFields.get(name));
454            if (newValue != null) {
455                clone.put(name, newValue);
456            } else {
457                Object oldValue = field.getValue();
458                clone.put(name, oldValue);
459            }
460        }
461        return clone;
462    }
463
464    @Override
465    public Document with( String fieldName,
466                          Object newValue ) {
467        BasicDocument clone = new BasicDocument();
468        newValue = unwrap(newValue);
469        for (Field field : this.fields()) {
470            String name = field.getName();
471            if (name.equals(fieldName)) {
472                clone.put(name, newValue);
473            } else {
474                Object oldValue = field.getValue();
475                clone.put(name, oldValue);
476            }
477        }
478        return clone;
479    }
480
481    @Override
482    public Document with( ValueTransformer transformer ) {
483        boolean transformed = false;
484        BasicDocument clone = new BasicDocument();
485        for (Field field : this.fields()) {
486            String name = field.getName();
487            Object oldValue = field.getValue();
488            Object newValue = null;
489            if (oldValue instanceof Document) {
490                newValue = ((Document)oldValue).with(transformer);
491            } else {
492                newValue = transformer.transform(name, oldValue);
493            }
494            if (newValue != oldValue) transformed = true;
495            clone.put(name, unwrap(newValue));
496        }
497        return transformed ? clone : this;
498    }
499
500    @Override
501    public Document withVariablesReplaced( Properties properties ) {
502        return with(new PropertiesTransformer(properties));
503    }
504
505    @Override
506    public Document withVariablesReplacedWithSystemProperties() {
507        return with(new SystemPropertiesTransformer());
508    }
509
510    protected Object unwrap( Object value ) {
511        if (value instanceof DocumentEditor) {
512            return unwrap(((DocumentEditor)value).unwrap());
513        }
514        if (value instanceof ArrayEditor) {
515            return unwrap(((ArrayEditor)value).unwrap());
516        }
517        return value;
518    }
519
520}