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.text.ParseException;
019import java.util.Date;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023import java.util.Properties;
024import java.util.Set;
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.Code;
030import org.modeshape.schematic.document.CodeWithScope;
031import org.modeshape.schematic.document.Document;
032import org.modeshape.schematic.document.EditableArray;
033import org.modeshape.schematic.document.EditableDocument;
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;
039
040public class DocumentEditor implements EditableDocument {
041
042    /** The serialVersionUID */
043    private static final long serialVersionUID = 1L;
044
045    private final MutableDocument document;
046    protected final DocumentValueFactory factory;
047
048    /**
049     * Return the document that was edited.
050     * 
051     * @param document the document to be edited
052     */
053    public DocumentEditor( MutableDocument document ) {
054        assert document != null;
055        this.document = document;
056        this.factory = DefaultDocumentValueFactory.INSTANCE;
057    }
058
059    /**
060     * Return the document that was edited.
061     * 
062     * @param document the document to be edited
063     * @param factory the factory that should be used to create value objects
064     */
065    public DocumentEditor( MutableDocument document,
066                           DocumentValueFactory factory ) {
067        assert document != null;
068        this.document = document;
069        this.factory = factory != null ? factory : DefaultDocumentValueFactory.INSTANCE;
070    }
071
072    @Override
073    public DocumentEditor clone() {
074        return new DocumentEditor(this.document.clone(), factory);
075    }
076
077    @Override
078    public DocumentEditor with( Map<String, Object> changedFields ) {
079        return new DocumentEditor((MutableDocument)this.document.with(changedFields), factory);
080    }
081
082    @Override
083    public Document with( String fieldName,
084                          Object value ) {
085        return new DocumentEditor((MutableDocument)this.document.with(fieldName, value), factory);
086    }
087
088    @Override
089    public DocumentEditor with( ValueTransformer transformer ) {
090        return new DocumentEditor((MutableDocument)this.document.with(transformer), factory);
091    }
092
093    @Override
094    public Document withVariablesReplaced( Properties properties ) {
095        return new DocumentEditor((MutableDocument)this.document.withVariablesReplaced(properties), factory);
096    }
097
098    @Override
099    public Document withVariablesReplacedWithSystemProperties() {
100        return new DocumentEditor((MutableDocument)this.document.withVariablesReplacedWithSystemProperties(), factory);
101    }
102
103    @Override
104    public Document unwrap() {
105        return document;
106    }
107
108    @Override
109    public MutableDocument asMutableDocument() {
110        return document;
111    }
112
113    @Override
114    public Object get( String name ) {
115        return document.get(name);
116    }
117
118    @Override
119    public Boolean getBoolean( String name ) {
120        return document.getBoolean(name);
121    }
122
123    @Override
124    public boolean getBoolean( String name,
125                               boolean defaultValue ) {
126        return document.getBoolean(name, defaultValue);
127    }
128
129    public Object put( String name,
130                       Object value ) {
131        return doSetValue(name, value);
132    }
133
134    @Override
135    public void putAll( Document object ) {
136        doSetAllValues(object);
137    }
138
139    @Override
140    public void putAll( Map<? extends String, ?> map ) {
141        doSetAllValues(map);
142    }
143
144    @Override
145    public void merge( Document other ) {
146        if (other == this) return;
147        for (Field field : other.fields()) {
148            Document otherDoc = field.getValueAsDocument();
149            if (!Null.matches(otherDoc)) {
150                // Get the corresponding value in this document ...
151                EditableDocument thisField = getDocument(field.getName());
152                if (!Null.matches(thisField)) {
153                    // There are docs in both sides, so merge them ...
154                    thisField.merge(otherDoc);
155                } else {
156                    // There is not a document on this side (perhaps another value), so replace with that other doc ...
157                    doSetValue(field.getName(), otherDoc);
158                }
159            } else {
160                // The field is something other than a document, so just set it on this document ...
161                doSetValue(field.getName(), field.getValue());
162            }
163        }
164    }
165
166    @Override
167    public Object remove( String name ) {
168        return document.remove(name);
169    }
170
171    @Override
172    public Integer getInteger( String name ) {
173        return document.getInteger(name);
174    }
175
176    @Override
177    public int getInteger( String name,
178                           int defaultValue ) {
179        return document.getInteger(name, defaultValue);
180    }
181
182    @Override
183    public Long getLong( String name ) {
184        return document.getLong(name);
185    }
186
187    @Override
188    public long getLong( String name,
189                         long defaultValue ) {
190        return document.getLong(name, defaultValue);
191    }
192
193    @Override
194    public Double getDouble( String name ) {
195        return document.getDouble(name);
196    }
197
198    @Override
199    public double getDouble( String name,
200                             double defaultValue ) {
201        return document.getDouble(name, defaultValue);
202    }
203
204    @Override
205    public Number getNumber( String name ) {
206        return document.getNumber(name);
207    }
208
209    @Override
210    public Number getNumber( String name,
211                             Number defaultValue ) {
212        return document.getNumber(name, defaultValue);
213    }
214    
215    @Override
216    public Date getDate(String name) {
217        return document.getDate(name);
218    }
219    
220    @Override
221    public String getString( String name ) {
222        return document.getString(name);
223    }
224
225    @Override
226    public String getString( String name,
227                             String defaultValue ) {
228        return document.getString(name, defaultValue);
229    }
230
231    @Override
232    public EditableArray getArray( String name ) {
233        return editable(document.getArray(name), name);
234    }
235
236    @Override
237    public EditableArray getOrCreateArray( String name ) {
238        List<?> existing = document.getArray(name);
239        return existing != null ? editable(existing, name) : setArray(name);
240    }
241
242    @Override
243    public EditableDocument getDocument( String name ) {
244        return editable(document.getDocument(name), name);
245    }
246
247    @Override
248    public EditableDocument getOrCreateDocument( String name ) {
249        Document existing = document.getDocument(name);
250        return existing != null ? editable(existing, name) : setDocument(name);
251    }
252
253    @Override
254    public boolean isNull( String name ) {
255        return document.isNull(name);
256    }
257
258    @Override
259    public boolean isNullOrMissing( String name ) {
260        return document.isNullOrMissing(name);
261    }
262
263    @Override
264    public MaxKey getMaxKey( String name ) {
265        return document.getMaxKey(name);
266    }
267
268    @Override
269    public MinKey getMinKey( String name ) {
270        return document.getMinKey(name);
271    }
272
273    @Override
274    public Code getCode( String name ) {
275        return document.getCode(name);
276    }
277
278    @Override
279    public CodeWithScope getCodeWithScope( String name ) {
280        return document.getCodeWithScope(name);
281    }
282
283    @Override
284    public ObjectId getObjectId( String name ) {
285        return document.getObjectId(name);
286    }
287
288    @Override
289    public Binary getBinary( String name ) {
290        return document.getBinary(name);
291    }
292
293    @Override
294    public Symbol getSymbol( String name ) {
295        return document.getSymbol(name);
296    }
297
298    @Override
299    public Pattern getPattern( String name ) {
300        return document.getPattern(name);
301    }
302
303    @Override
304    public UUID getUuid( String name ) {
305        return document.getUuid(name);
306    }
307
308    @Override
309    public UUID getUuid( String name,
310                         UUID defaultValue ) {
311        return document.getUuid(name, defaultValue);
312    }
313
314    @Override
315    public int getType( String name ) {
316        return document.getType(name);
317    }
318
319    @Override
320    public Map<String, ?> toMap() {
321        return document.toMap();
322    }
323
324    @Override
325    public Iterable<Field> fields() {
326        return document.fields();
327    }
328
329    @Override
330    public boolean containsField( String name ) {
331        return document.containsField(name);
332    }
333
334    @Override
335    public boolean containsAll( Document document ) {
336        return this.document.containsAll(document);
337    }
338
339    @Override
340    public Set<String> keySet() {
341        return document.keySet();
342    }
343
344    @Override
345    public int size() {
346        return document.size();
347    }
348
349    @Override
350    public boolean isEmpty() {
351        return document.isEmpty();
352    }
353
354    @Override
355    public void removeAll() {
356        document.removeAll();
357    }
358
359    @Override
360    public EditableDocument set( String name,
361                                 Object value ) {
362        doSetValue(name, value);
363        return this;
364    }
365
366    @Override
367    public EditableDocument setBoolean( String name,
368                                        boolean value ) {
369        doSetValue(name, factory.createBoolean(value));
370        return this;
371    }
372
373    @Override
374    public EditableDocument setNumber( String name,
375                                       int value ) {
376        doSetValue(name, factory.createInt(value));
377        return this;
378    }
379
380    @Override
381    public EditableDocument setNumber( String name,
382                                       long value ) {
383        doSetValue(name, factory.createLong(value));
384        return this;
385    }
386
387    @Override
388    public EditableDocument setNumber( String name,
389                                       float value ) {
390        doSetValue(name, factory.createDouble(value));
391        return this;
392    }
393
394    @Override
395    public EditableDocument setNumber( String name,
396                                       double value ) {
397        doSetValue(name, factory.createDouble(value));
398        return this;
399    }
400
401    @Override
402    public EditableDocument setString( String name,
403                                       String value ) {
404        doSetValue(name, factory.createString(value));
405        return this;
406    }
407
408    @Override
409    public EditableDocument setSymbol( String name,
410                                       String value ) {
411        doSetValue(name, factory.createSymbol(value));
412        return this;
413    }
414
415    @Override
416    public EditableDocument setDocument( String name ) {
417        BasicDocument doc = new BasicDocument();
418        doSetValueIfAbsent(name, doc);
419        return editable(doc, name);
420    }
421
422    @Override
423    public EditableDocument setDocument( String name,
424                                         Document document ) {
425        if (document instanceof DocumentEditor) document = ((DocumentEditor)document).asMutableDocument();
426        doSetValue(name, document);
427        return editable(document, name);
428    }
429
430    @Override
431    public EditableArray setArray( String name ) {
432        List<?> array = new BasicArray();
433        doSetValueIfAbsent(name, array);
434        return editable(array, name);
435    }
436
437    @Override
438    public EditableArray setArray( String name,
439                                   Array array ) {
440        if (array instanceof ArrayEditor) array = ((ArrayEditor)array).unwrap();
441        doSetValue(name, array);
442        return editable((List<?>)array, name);
443    }
444
445    @Override
446    public EditableArray setArray( String name,
447                                   Object... values ) {
448        List<?> array = new BasicArray(values);
449        doSetValue(name, array);
450        return editable(array, name);
451    }
452
453    @Override
454    public EditableDocument setDate( String name,
455                                     Date value ) {
456        doSetValue(name, value);
457        return this;
458    }
459
460    @Override
461    public EditableDocument setDate( String name,
462                                     String isoDate ) throws ParseException {
463        doSetValue(name, factory.createDate(isoDate));
464        return this;
465    }
466
467    @Override
468    public EditableDocument setTimestamp( String name,
469                                          int timeInSeconds,
470                                          int increment ) {
471        doSetValue(name, factory.createTimestamp(timeInSeconds, increment));
472        return this;
473    }
474
475    @Override
476    public EditableDocument setObjectId( String name,
477                                         String hex ) {
478        doSetValue(name, factory.createObjectId(hex));
479        return this;
480    }
481
482    @Override
483    public EditableDocument setObjectId( String name,
484                                         byte[] bytes ) {
485        doSetValue(name, factory.createObjectId(bytes));
486        return this;
487    }
488
489    @Override
490    public EditableDocument setObjectId( String name,
491                                         int time,
492                                         int machine,
493                                         int process,
494                                         int inc ) {
495        doSetValue(name, factory.createObjectId(time, machine, process, inc));
496        return this;
497    }
498
499    @Override
500    public EditableDocument setRegularExpression( String name,
501                                                  String pattern ) {
502        doSetValue(name, factory.createRegex(pattern, null));
503        return this;
504    }
505
506    @Override
507    public EditableDocument setRegularExpression( String name,
508                                                  String pattern,
509                                                  int flags ) {
510        doSetValue(name, factory.createRegex(pattern, BsonUtils.regexFlagsFor(flags)));
511        return this;
512    }
513
514    @Override
515    public EditableDocument setNull( String name ) {
516        doSetValue(name, factory.createNull());
517        return this;
518    }
519
520    @Override
521    public EditableDocument setBinary( String name,
522                                       byte type,
523                                       byte[] data ) {
524        doSetValue(name, factory.createBinary(type, data));
525        return this;
526    }
527
528    @Override
529    public EditableDocument setUuid( String name,
530                                     UUID uuid ) {
531        doSetValue(name, uuid);
532        return this;
533    }
534
535    @Override
536    public EditableDocument setCode( String name,
537                                     String code,
538                                     boolean includeScope ) {
539        if (includeScope) {
540            BasicDocument scope = new BasicDocument();
541            doSetValue(name, factory.createCode(code, scope));
542            return editable(scope, name);
543        }
544        doSetValue(name, factory.createCode(code));
545        return this;
546    }
547
548    @Override
549    public EditableDocument setCode( String name,
550                                     String code,
551                                     Document scope ) {
552        if (scope != null) {
553            doSetValue(name, factory.createCode(code, scope));
554            return editable(scope, name);
555        }
556        doSetValue(name, factory.createCode(code));
557        return this;
558    }
559
560    /**
561     * The method that does the actual setting for all of the <code>set...</code> methods. This method may be overridden by
562     * subclasses when additional work needs to be performed during the set operations.
563     * 
564     * @param name the name of the field being set
565     * @param value the new value
566     * @return the old value, or null if there was no existing value
567     */
568    protected Object doSetValue( String name,
569                                 Object value ) {
570        if (value == null) {
571            value = Null.getInstance();
572        } else {
573            value = Utility.unwrap(value);
574        }
575        return document.put(name, value);
576    }
577
578    /**
579     * The method that does the actual setting for all of the <code>set...</code> methods. This method may be overridden by
580     * subclasses when additional work needs to be performed during the set operations.
581     * 
582     * @param name the name of the field being set
583     * @param value the new value
584     * @return the old value, or null if there was no existing value
585     */
586    protected Object doSetValueIfAbsent( String name,
587                                         Object value ) {
588        if (value == null) {
589            value = Null.getInstance();
590        } else {
591            value = Utility.unwrap(value);
592        }
593        return document.put(name, value);
594    }
595
596    /**
597     * The method that does the actual setting for all of the {@link #putAll(Document)} method. This method may be overridden by
598     * subclasses when additional work needs to be performed during this operation.
599     * 
600     * @param values the document containing the fields to be added
601     */
602    protected void doSetAllValues( Document values ) {
603        if (values != null) {
604            values = Utility.unwrap(values);
605            document.putAll(values);
606        }
607    }
608
609    /**
610     * The method that does the actual setting for all of the {@link #putAll(Map)} method. This method may be overridden by
611     * subclasses when additional work needs to be performed during this operation.
612     * 
613     * @param values the map containing the fields to be added
614     */
615    protected void doSetAllValues( Map<? extends String, ?> values ) {
616        if (values != null) {
617            document.putAll(Utility.unwrapValues(values));
618        }
619    }
620
621    protected EditableDocument editable( Document doc,
622                                         String fieldName ) {
623        if (doc == null) return null;
624        assert !(doc instanceof DocumentEditor) : "The document value should not be a DocumentEditor instance";
625        if (doc instanceof MutableArray) {
626            return createEditableArray((MutableArray)doc, fieldName, factory);
627        }
628        assert doc instanceof MutableDocument;
629        return createEditableDocument((MutableDocument)doc, fieldName, factory);
630    }
631
632    protected EditableArray editable( List<?> array,
633                                      String fieldName ) {
634        if (array == null) return null;
635        assert !(array instanceof ArrayEditor) : "The array value should not be an ArrayEditor instance";
636        return createEditableArray((BasicArray)array, fieldName, factory);
637    }
638
639    protected EditableDocument createEditableDocument( MutableDocument document,
640                                                       String fieldName,
641                                                       DocumentValueFactory factory ) {
642        return new DocumentEditor(document, factory);
643    }
644
645    protected EditableArray createEditableArray( MutableArray array,
646                                                 String fieldName,
647                                                 DocumentValueFactory factory ) {
648        return new ArrayEditor(array, factory);
649    }
650
651    @Override
652    public String toString() {
653        return document.toString();
654    }
655
656    @Override
657    public boolean equals(Object o) {
658        if (this == o) {
659            return true;
660        }
661        if (!(o instanceof MutableDocument) && ! (o instanceof DocumentEditor)) {
662            return false;
663        }
664        
665        if (o instanceof MutableDocument) {
666            return Objects.equals(document, o); 
667        } else {
668            DocumentEditor that = (DocumentEditor) o;
669            return Objects.equals(this.document, that.document);
670        }
671    }
672
673    @Override
674    public int hashCode() {
675        return Objects.hash(document);
676    }
677}