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