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 static final String JCR_NAME_PROPERTY_NAME = "jcr:name";
054    
055        private final Map<Path, List<PropertyValue>> data;
056        private transient boolean valuesSorted = true;
057        private final ValueFactories factories;
058        private final Name jcrName;
059    
060        public SequencerOutputMap( ValueFactories factories ) {
061            CheckArg.isNotNull(factories, "factories");
062            this.data = new HashMap<Path, List<PropertyValue>>();
063            this.factories = factories;
064            this.jcrName = this.factories.getNameFactory().create(JCR_NAME_PROPERTY_NAME);
065        }
066    
067        ValueFactories getFactories() {
068            return this.factories;
069        }
070    
071        /**
072         * {@inheritDoc}
073         */
074        public void setProperty( Path nodePath,
075                                 Name propertyName,
076                                 Object... values ) {
077            CheckArg.isNotNull(nodePath, "nodePath");
078            CheckArg.isNotNull(propertyName, "property");
079            // Ignore the "jcr:name" property, as that's handled by the path ...
080            if (this.jcrName.equals(propertyName)) return;
081    
082            // Find or create the entry for this node ...
083            List<PropertyValue> properties = this.data.get(nodePath);
084            if (properties == null) {
085                if (values == null || values.length == 0) return; // do nothing
086                properties = new ArrayList<PropertyValue>();
087                this.data.put(nodePath, properties);
088            }
089            if (values == null || values.length == 0) {
090                properties.remove(new PropertyValue(propertyName, null));
091            } else {
092                Object propValue = values.length == 1 ? values[0] : values;
093                PropertyValue value = new PropertyValue(propertyName, propValue);
094                properties.add(value);
095                valuesSorted = false;
096            }
097        }
098    
099        /**
100         * {@inheritDoc}
101         */
102        public void setProperty( String nodePath,
103                                 String property,
104                                 Object... values ) {
105            CheckArg.isNotEmpty(nodePath, "nodePath");
106            CheckArg.isNotEmpty(property, "property");
107            Path path = this.factories.getPathFactory().create(nodePath);
108            Name propertyName = this.factories.getNameFactory().create(property);
109            setProperty(path, propertyName, values);
110        }
111    
112        /**
113         * {@inheritDoc}
114         */
115        public void setReference( String nodePath,
116                                  String propertyName,
117                                  String... paths ) {
118            PathFactory pathFactory = this.factories.getPathFactory();
119            Path path = pathFactory.create(nodePath);
120            Name name = this.factories.getNameFactory().create(propertyName);
121            Object[] values = null;
122            if (paths != null && paths.length != 0) {
123                values = new Path[paths.length];
124                for (int i = 0, len = paths.length; i != len; ++i) {
125                    String pathValue = paths[i];
126                    values[i] = pathFactory.create(pathValue);
127                }
128            }
129            setProperty(path, name, values);
130        }
131    
132        /**
133         * Return the number of node entries in this map.
134         * 
135         * @return the number of entries
136         */
137        public int size() {
138            return this.data.size();
139        }
140    
141        /**
142         * Return whether there are no entries
143         * 
144         * @return true if this container is empty, or false otherwise
145         */
146        public boolean isEmpty() {
147            return this.data.isEmpty();
148        }
149    
150        protected List<PropertyValue> removeProperties( Path nodePath ) {
151            return this.data.remove(nodePath);
152        }
153    
154        /**
155         * Get the properties for the node given by the supplied path.
156         * 
157         * @param nodePath the path to the node
158         * @return the property values, or null if there are none
159         */
160        protected List<PropertyValue> getProperties( Path nodePath ) {
161            return data.get(nodePath);
162        }
163    
164        /**
165         * Return the entries in this output in an order with shorter paths first.
166         * <p>
167         * {@inheritDoc}
168         */
169        public Iterator<Entry> iterator() {
170            LinkedList<Path> paths = new LinkedList<Path>(data.keySet());
171            Collections.sort(paths);
172            sortValues();
173            return new EntryIterator(paths.iterator());
174        }
175    
176        protected void sortValues() {
177            if (!valuesSorted) {
178                for (List<PropertyValue> values : this.data.values()) {
179                    if (values.size() > 1) Collections.sort(values);
180                }
181                valuesSorted = true;
182            }
183        }
184    
185        /**
186         * {@inheritDoc}
187         */
188        @Override
189        public String toString() {
190            return this.data.toString();
191        }
192    
193        /**
194         * A property name and value pair. PropertyValue instances have a natural order where the <code>jcr:primaryType</code> is
195         * first, followed by all other properties in ascending lexicographical order according to the {@link #getName() name}.
196         * 
197         * @author Randall Hauch
198         */
199        @Immutable
200        public class PropertyValue implements Comparable<PropertyValue> {
201    
202            private final Name name;
203            private final Object value;
204    
205            protected PropertyValue( Name propertyName,
206                                     Object value ) {
207                this.name = propertyName;
208                this.value = value;
209            }
210    
211            /**
212             * Get the property name.
213             * 
214             * @return the property name; never null
215             */
216            public Name getName() {
217                return this.name;
218            }
219    
220            /**
221             * Get the property value, which is either a single value or an array of values.
222             * 
223             * @return the property value
224             */
225            public Object getValue() {
226                return this.value;
227            }
228    
229            /**
230             * {@inheritDoc}
231             */
232            public int compareTo( PropertyValue that ) {
233                if (this == that) return 0;
234                if (this.name.equals(JcrLexicon.PRIMARY_TYPE)) return -1;
235                if (that.name.equals(JcrLexicon.PRIMARY_TYPE)) return 1;
236                return this.name.compareTo(that.name);
237            }
238    
239            /**
240             * {@inheritDoc}
241             */
242            @Override
243            public int hashCode() {
244                return this.name.hashCode();
245            }
246    
247            /**
248             * {@inheritDoc}
249             */
250            @Override
251            public boolean equals( Object obj ) {
252                if (obj == this) return true;
253                if (obj instanceof PropertyValue) {
254                    PropertyValue that = (PropertyValue)obj;
255                    if (!this.getName().equals(that.getName())) return false;
256                    return true;
257                }
258                return false;
259            }
260    
261            /**
262             * {@inheritDoc}
263             */
264            @Override
265            public String toString() {
266                return "[" + this.name + "=" + value + "]";
267            }
268        }
269    
270        /**
271         * An entry in a SequencerOutputMap, which contains the path of the node and the {@link #getPropertyValues() property values}
272         * on the node.
273         * 
274         * @author Randall Hauch
275         */
276        @Immutable
277        public class Entry {
278    
279            private final Path path;
280            private final Name primaryType;
281            private final List<PropertyValue> properties;
282    
283            protected Entry( Path path,
284                             List<PropertyValue> properties ) {
285                assert path != null;
286                assert properties != null;
287                this.path = path;
288                this.properties = properties;
289                if (this.properties.size() > 0 && this.properties.get(0).getName().equals("jcr:primaryType")) {
290                    PropertyValue primaryTypeProperty = this.properties.remove(0);
291                    this.primaryType = getFactories().getNameFactory().create(primaryTypeProperty.getValue());
292                } else {
293                    this.primaryType = null;
294                }
295            }
296    
297            /**
298             * @return path
299             */
300            public Path getPath() {
301                return this.path;
302            }
303    
304            /**
305             * Get the primary type specified for this node, or null if the type was not specified
306             * 
307             * @return the primary type, or null
308             */
309            public Name getPrimaryTypeValue() {
310                return this.primaryType;
311            }
312    
313            /**
314             * Get the property values, which may be empty
315             * 
316             * @return value
317             */
318            public List<PropertyValue> getPropertyValues() {
319                return getProperties(path);
320            }
321        }
322    
323        protected class EntryIterator implements Iterator<Entry> {
324    
325            private Path last;
326            private final Iterator<Path> iter;
327    
328            protected EntryIterator( Iterator<Path> iter ) {
329                this.iter = iter;
330            }
331    
332            /**
333             * {@inheritDoc}
334             */
335            public boolean hasNext() {
336                return iter.hasNext();
337            }
338    
339            /**
340             * {@inheritDoc}
341             */
342            public Entry next() {
343                this.last = iter.next();
344                return new Entry(last, getProperties(last));
345            }
346    
347            /**
348             * {@inheritDoc}
349             */
350            public void remove() {
351                if (last == null) throw new IllegalStateException();
352                try {
353                    removeProperties(last);
354                } finally {
355                    last = null;
356                }
357            }
358        }
359    
360    }