001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006    * See the AUTHORS.txt file in the distribution for a full listing of 
007    * individual contributors.
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.repository.sequencer;
025    
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.Iterator;
030    import java.util.LinkedList;
031    import java.util.List;
032    import java.util.Map;
033    import net.jcip.annotations.Immutable;
034    import net.jcip.annotations.NotThreadSafe;
035    import org.jboss.dna.common.util.CheckArg;
036    import org.jboss.dna.graph.JcrLexicon;
037    import org.jboss.dna.graph.property.Name;
038    import org.jboss.dna.graph.property.Path;
039    import org.jboss.dna.graph.property.PathFactory;
040    import org.jboss.dna.graph.property.ValueFactories;
041    import org.jboss.dna.graph.sequencer.SequencerOutput;
042    
043    /**
044     * A basic {@link SequencerOutput} that records all information in-memory and which organizes the properties by {@link Path node
045     * paths} and provides access to the nodes in a natural path-order.
046     * 
047     * @author Randall Hauch
048     * @author John Verhaeg
049     */
050    @NotThreadSafe
051    public class SequencerOutputMap implements SequencerOutput, Iterable<SequencerOutputMap.Entry> {
052    
053        private final Map<Path, List<PropertyValue>> data;
054        private transient boolean valuesSorted = true;
055        private final ValueFactories factories;
056    
057        public SequencerOutputMap( ValueFactories factories ) {
058            CheckArg.isNotNull(factories, "factories");
059            this.data = new HashMap<Path, List<PropertyValue>>();
060            this.factories = factories;
061        }
062    
063        ValueFactories getFactories() {
064            return this.factories;
065        }
066    
067        /**
068         * {@inheritDoc}
069         */
070        public void setProperty( Path nodePath,
071                                 Name propertyName,
072                                 Object... values ) {
073            CheckArg.isNotNull(nodePath, "nodePath");
074            CheckArg.isNotNull(propertyName, "property");
075    
076            // Find or create the entry for this node ...
077            List<PropertyValue> properties = this.data.get(nodePath);
078            if (properties == null) {
079                if (values == null || values.length == 0) return; // do nothing
080                properties = new ArrayList<PropertyValue>();
081                this.data.put(nodePath, properties);
082            }
083            if (values == null || values.length == 0) {
084                properties.remove(new PropertyValue(propertyName, null));
085            } else {
086                Object propValue = values.length == 1 ? values[0] : values;
087                PropertyValue value = new PropertyValue(propertyName, propValue);
088                properties.add(value);
089                valuesSorted = false;
090            }
091        }
092    
093        /**
094         * {@inheritDoc}
095         */
096        public void setProperty( String nodePath,
097                                 String property,
098                                 Object... values ) {
099            CheckArg.isNotEmpty(nodePath, "nodePath");
100            CheckArg.isNotEmpty(property, "property");
101            Path path = this.factories.getPathFactory().create(nodePath);
102            Name propertyName = this.factories.getNameFactory().create(property);
103            setProperty(path, propertyName, values);
104        }
105    
106        /**
107         * {@inheritDoc}
108         */
109        public void setReference( String nodePath,
110                                  String propertyName,
111                                  String... paths ) {
112            PathFactory pathFactory = this.factories.getPathFactory();
113            Path path = pathFactory.create(nodePath);
114            Name name = this.factories.getNameFactory().create(propertyName);
115            Object[] values = null;
116            if (paths != null && paths.length != 0) {
117                values = new Path[paths.length];
118                for (int i = 0, len = paths.length; i != len; ++i) {
119                    String pathValue = paths[i];
120                    values[i] = pathFactory.create(pathValue);
121                }
122            }
123            setProperty(path, name, values);
124        }
125    
126        /**
127         * Return the number of node entries in this map.
128         * 
129         * @return the number of entries
130         */
131        public int size() {
132            return this.data.size();
133        }
134    
135        /**
136         * Return whether there are no entries
137         * 
138         * @return true if this container is empty, or false otherwise
139         */
140        public boolean isEmpty() {
141            return this.data.isEmpty();
142        }
143    
144        protected List<PropertyValue> removeProperties( Path nodePath ) {
145            return this.data.remove(nodePath);
146        }
147    
148        /**
149         * Get the properties for the node given by the supplied path.
150         * 
151         * @param nodePath the path to the node
152         * @return the property values, or null if there are none
153         */
154        protected List<PropertyValue> getProperties( Path nodePath ) {
155            return data.get(nodePath);
156        }
157    
158        /**
159         * Return the entries in this output in an order with shorter paths first.
160         * <p>
161         * {@inheritDoc}
162         */
163        public Iterator<Entry> iterator() {
164            LinkedList<Path> paths = new LinkedList<Path>(data.keySet());
165            Collections.sort(paths);
166            sortValues();
167            return new EntryIterator(paths.iterator());
168        }
169    
170        protected void sortValues() {
171            if (!valuesSorted) {
172                for (List<PropertyValue> values : this.data.values()) {
173                    if (values.size() > 1) Collections.sort(values);
174                }
175                valuesSorted = true;
176            }
177        }
178    
179        /**
180         * {@inheritDoc}
181         */
182        @Override
183        public String toString() {
184            return this.data.toString();
185        }
186    
187        /**
188         * A property name and value pair. PropertyValue instances have a natural order where the <code>jcr:primaryType</code> is
189         * first, followed by all other properties in ascending lexicographical order according to the {@link #getName() name}.
190         * 
191         * @author Randall Hauch
192         */
193        @Immutable
194        public class PropertyValue implements Comparable<PropertyValue> {
195    
196            private final Name name;
197            private final Object value;
198    
199            protected PropertyValue( Name propertyName,
200                                     Object value ) {
201                this.name = propertyName;
202                this.value = value;
203            }
204    
205            /**
206             * Get the property name.
207             * 
208             * @return the property name; never null
209             */
210            public Name getName() {
211                return this.name;
212            }
213    
214            /**
215             * Get the property value, which is either a single value or an array of values.
216             * 
217             * @return the property value
218             */
219            public Object getValue() {
220                return this.value;
221            }
222    
223            /**
224             * {@inheritDoc}
225             */
226            public int compareTo( PropertyValue that ) {
227                if (this == that) return 0;
228                if (this.name.equals(JcrLexicon.PRIMARY_TYPE)) return -1;
229                if (that.name.equals(JcrLexicon.PRIMARY_TYPE)) return 1;
230                return this.name.compareTo(that.name);
231            }
232    
233            /**
234             * {@inheritDoc}
235             */
236            @Override
237            public int hashCode() {
238                return this.name.hashCode();
239            }
240    
241            /**
242             * {@inheritDoc}
243             */
244            @Override
245            public boolean equals( Object obj ) {
246                if (obj == this) return true;
247                if (obj instanceof PropertyValue) {
248                    PropertyValue that = (PropertyValue)obj;
249                    if (!this.getName().equals(that.getName())) return false;
250                    return true;
251                }
252                return false;
253            }
254    
255            /**
256             * {@inheritDoc}
257             */
258            @Override
259            public String toString() {
260                return "[" + this.name + "=" + value + "]";
261            }
262        }
263    
264        /**
265         * An entry in a SequencerOutputMap, which contains the path of the node and the {@link #getPropertyValues() property values}
266         * on the node.
267         * 
268         * @author Randall Hauch
269         */
270        @Immutable
271        public class Entry {
272    
273            private final Path path;
274            private final Name primaryType;
275            private final List<PropertyValue> properties;
276    
277            protected Entry( Path path,
278                             List<PropertyValue> properties ) {
279                assert path != null;
280                assert properties != null;
281                this.path = path;
282                this.properties = properties;
283                if (this.properties.size() > 0 && this.properties.get(0).getName().equals("jcr:primaryType")) {
284                    PropertyValue primaryTypeProperty = this.properties.remove(0);
285                    this.primaryType = getFactories().getNameFactory().create(primaryTypeProperty.getValue());
286                } else {
287                    this.primaryType = null;
288                }
289            }
290    
291            /**
292             * @return path
293             */
294            public Path getPath() {
295                return this.path;
296            }
297    
298            /**
299             * Get the primary type specified for this node, or null if the type was not specified
300             * 
301             * @return the primary type, or null
302             */
303            public Name getPrimaryTypeValue() {
304                return this.primaryType;
305            }
306    
307            /**
308             * Get the property values, which may be empty
309             * 
310             * @return value
311             */
312            public List<PropertyValue> getPropertyValues() {
313                return getProperties(path);
314            }
315        }
316    
317        protected class EntryIterator implements Iterator<Entry> {
318    
319            private Path last;
320            private final Iterator<Path> iter;
321    
322            protected EntryIterator( Iterator<Path> iter ) {
323                this.iter = iter;
324            }
325    
326            /**
327             * {@inheritDoc}
328             */
329            public boolean hasNext() {
330                return iter.hasNext();
331            }
332    
333            /**
334             * {@inheritDoc}
335             */
336            public Entry next() {
337                this.last = iter.next();
338                return new Entry(last, getProperties(last));
339            }
340    
341            /**
342             * {@inheritDoc}
343             */
344            public void remove() {
345                if (last == null) throw new IllegalStateException();
346                try {
347                    removeProperties(last);
348                } finally {
349                    last = null;
350                }
351            }
352        }
353    
354    }