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 }