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.Iterator;
020import java.util.List;
021import java.util.stream.Collectors;
022import org.modeshape.schematic.document.Array;
023import org.modeshape.schematic.document.Changes;
024import org.modeshape.schematic.document.Editor;
025import org.modeshape.schematic.internal.delta.AddValueIfAbsentOperation;
026import org.modeshape.schematic.internal.delta.AddValueOperation;
027import org.modeshape.schematic.internal.delta.ClearOperation;
028import org.modeshape.schematic.internal.delta.Operation;
029import org.modeshape.schematic.internal.delta.PutIfAbsentOperation;
030import org.modeshape.schematic.internal.delta.PutOperation;
031import org.modeshape.schematic.internal.delta.RemoveAllValuesOperation;
032import org.modeshape.schematic.internal.delta.RemoveAtIndexOperation;
033import org.modeshape.schematic.internal.delta.RemoveOperation;
034import org.modeshape.schematic.internal.delta.RemoveValueOperation;
035import org.modeshape.schematic.internal.delta.RetainAllValuesOperation;
036import org.modeshape.schematic.internal.delta.SetValueOperation;
037
038/**
039 * Document editor implementation which allows the editing of a document via a list of incremental changes.
040 * 
041 * @author Horia Chiorean (hchiorea@redhat.com)
042 * @since 5.0
043 */
044public class IncrementalDocumentEditor extends ObservableDocumentEditor implements Editor {
045    
046    private static final long serialVersionUID = 1L;
047    
048    private transient final List<Operation> operations;
049
050    public IncrementalDocumentEditor(MutableDocument document,
051                                     List<Operation> operations) {
052        super(document, Paths.rootPath(), operations::add, null);
053        this.operations = operations;
054    }
055
056    @Override
057    public Changes getChanges() {
058        return new DocumentChanges(operations != null ? operations : Collections.emptyList());
059    }
060
061    @Override
062    public void apply(Changes changes) {
063        apply(changes.clone(), null);
064    }
065
066    private static Array.Entry newEntry(int index,
067                                        Object value) {
068        return new BasicArray.BasicEntry(index, value);
069    }
070
071    @Override
072    public void apply(Changes changes,
073                      Observer observer) {
074        if (changes.isEmpty()) {
075            return;
076        }
077        MutableDocument mutable = asMutableDocument();
078        for (Operation operation : (DocumentChanges) changes) {
079            operation.replay(mutable);
080            if (observer != null) {
081                if (operation instanceof SetValueOperation) {
082                    SetValueOperation op = (SetValueOperation) operation;
083                    observer.setArrayValue(op.getParentPath(), newEntry(op.getIndex(), op.getValue()));
084                } else if (operation instanceof AddValueOperation) {
085                    AddValueOperation op = (AddValueOperation) operation;
086                    if (op.getActualIndex() != -1) {
087                        observer.addArrayValue(op.getParentPath(), newEntry(op.getActualIndex(), op.getValue()));
088                    }
089                } else if (operation instanceof AddValueIfAbsentOperation) {
090                    AddValueIfAbsentOperation op = (AddValueIfAbsentOperation) operation;
091                    if (op.isAdded()) {
092                        observer.addArrayValue(op.getParentPath(), newEntry(op.getIndex(), op.getValue()));
093                    }
094                } else if (operation instanceof RemoveValueOperation) {
095                    RemoveValueOperation op = (RemoveValueOperation) operation;
096                    if (op.getActualIndex() != -1) {
097                        observer.removeArrayValue(op.getParentPath(), newEntry(op.getActualIndex(), op.getRemovedValue()));
098                    }
099                } else if (operation instanceof RemoveAtIndexOperation) {
100                    RemoveAtIndexOperation op = (RemoveAtIndexOperation) operation;
101                    observer.removeArrayValue(op.getParentPath(), newEntry(op.getIndex(), op.getRemovedValue()));
102                } else if (operation instanceof RetainAllValuesOperation) {
103                    RetainAllValuesOperation op = (RetainAllValuesOperation) operation;
104                    for (Array.Entry entry : op.getRemovedEntries()) {
105                        observer.removeArrayValue(op.getParentPath(), entry);
106                    }
107                } else if (operation instanceof RemoveAllValuesOperation) {
108                    RemoveAllValuesOperation op = (RemoveAllValuesOperation) operation;
109                    for (Array.Entry entry : op.getRemovedEntries()) {
110                        observer.removeArrayValue(op.getParentPath(), entry);
111                    }
112                } else if (operation instanceof ClearOperation) {
113                    ClearOperation op = (ClearOperation) operation;
114                    observer.clear(op.getParentPath());
115                } else if (operation instanceof PutOperation) {
116                    PutOperation op = (PutOperation) operation;
117                    observer.put(op.getParentPath(), op.getFieldName(), op.getNewValue());
118                } else if (operation instanceof PutIfAbsentOperation) {
119                    PutIfAbsentOperation op = (PutIfAbsentOperation) operation;
120                    if (op.isApplied()) {
121                        observer.put(op.getParentPath(), op.getFieldName(), op.getNewValue());
122                    }
123                } else if (operation instanceof RemoveOperation) {
124                    RemoveOperation op = (RemoveOperation) operation;
125                    if (op.isRemoved()) {
126                        observer.remove(op.getParentPath(), op.getFieldName());
127                    }
128                }
129            }
130        }
131    }
132
133    protected static class DocumentChanges implements Changes, Iterable<Operation> {
134
135        private final List<Operation> operations;
136
137        protected DocumentChanges( List<Operation> operations ) {
138            this.operations = operations;
139        }
140
141        @Override
142        public DocumentChanges clone() {
143            List<Operation> newOps = operations.stream().map(Operation::clone).collect(Collectors.toList());
144            return new DocumentChanges(newOps);
145        }
146
147        @Override
148        public Iterator<Operation> iterator() {
149            return operations.iterator();
150        }
151
152        @Override
153        public boolean isEmpty() {
154            return operations.isEmpty();
155        }
156
157        @Override
158        public String toString() {
159            StringBuilder sb = new StringBuilder();
160            Iterator<Operation> iter = operations.iterator();
161            if (iter.hasNext()) {
162                sb.append(iter.next());
163                while (iter.hasNext()) {
164                    sb.append("\n").append(iter.next());
165                }
166            }
167            return sb.toString();
168        }
169    }
170}