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