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.graph.request;
025
026 import org.jboss.dna.common.util.CheckArg;
027 import org.jboss.dna.common.util.HashCode;
028 import org.jboss.dna.graph.GraphI18n;
029 import org.jboss.dna.graph.Location;
030 import org.jboss.dna.graph.NodeConflictBehavior;
031 import org.jboss.dna.graph.property.Name;
032 import org.jboss.dna.graph.property.Path;
033
034 /**
035 * Instruction that a branch be moved from one location into another.
036 *
037 * @author Randall Hauch
038 */
039 public class MoveBranchRequest extends ChangeRequest {
040
041 private static final long serialVersionUID = 1L;
042
043 public static final NodeConflictBehavior DEFAULT_CONFLICT_BEHAVIOR = NodeConflictBehavior.APPEND;
044
045 private final Location from;
046 private final Location into;
047 private final Location before;
048 private final String workspaceName;
049 private final Name desiredNameForNode;
050 private final NodeConflictBehavior conflictBehavior;
051 private Location actualOldLocation;
052 private Location actualNewLocation;
053
054 /**
055 * Create a request to move a branch from one location into another.
056 *
057 * @param from the location of the top node in the existing branch that is to be moved
058 * @param into the location of the existing node into which the branch should be moved
059 * @param workspaceName the name of the workspace
060 * @throws IllegalArgumentException if any of the parameters are null
061 */
062 public MoveBranchRequest( Location from,
063 Location into,
064 String workspaceName ) {
065 this(from, into, null, workspaceName, null, DEFAULT_CONFLICT_BEHAVIOR);
066 }
067
068 /**
069 * Create a request to move a branch from one location into another.
070 *
071 * @param from the location of the top node in the existing branch that is to be moved
072 * @param into the location of the existing node into which the branch should be moved
073 * @param workspaceName the name of the workspace
074 * @param newNameForMovedNode the new name for the node being moved, or null if the name of the original should be used
075 * @throws IllegalArgumentException if any of the parameters are null
076 */
077 public MoveBranchRequest( Location from,
078 Location into,
079 String workspaceName,
080 Name newNameForMovedNode ) {
081 this(from, into, null, workspaceName, newNameForMovedNode, DEFAULT_CONFLICT_BEHAVIOR);
082 }
083
084 /**
085 * Create a request to move a branch from one location into another.
086 *
087 * @param from the location of the top node in the existing branch that is to be moved
088 * @param into the location of the existing node into which the branch should be moved
089 * @param workspaceName the name of the workspace
090 * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
091 * location
092 * @throws IllegalArgumentException if any of the parameters are null
093 */
094 public MoveBranchRequest( Location from,
095 Location into,
096 String workspaceName,
097 NodeConflictBehavior conflictBehavior ) {
098 this(from, into, null, workspaceName, null, conflictBehavior);
099 }
100
101 /**
102 * Create a request to move a branch from one location into another.
103 *
104 * @param from the location of the top node in the existing branch that is to be moved
105 * @param into the location of the existing node into which the branch should be moved
106 * @param before the location of the child of the {@code into} node that the branch should be placed before; null indicates
107 * that the branch should be the last child of its new parent
108 * @param workspaceName the name of the workspace
109 * @param newNameForMovedNode the new name for the node being moved, or null if the name of the original should be used
110 * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
111 * location
112 * @throws IllegalArgumentException if any of the parameters are null
113 */
114 public MoveBranchRequest( Location from,
115 Location into,
116 Location before,
117 String workspaceName,
118 Name newNameForMovedNode,
119 NodeConflictBehavior conflictBehavior ) {
120 CheckArg.isNotNull(from, "from");
121 // CheckArg.isNotNull(into, "into");
122 CheckArg.isNotNull(workspaceName, "workspaceName");
123 CheckArg.isNotNull(conflictBehavior, "conflictBehavior");
124 this.from = from;
125 this.into = into;
126 this.before = before;
127 this.workspaceName = workspaceName;
128 this.desiredNameForNode = newNameForMovedNode;
129 this.conflictBehavior = conflictBehavior;
130 }
131
132 /**
133 * Get the location defining the top of the branch to be moved
134 *
135 * @return the from location; never null
136 */
137 public Location from() {
138 return from;
139 }
140
141 /**
142 * Get the location defining the parent where the branch is to be placed
143 *
144 * @return the to location; never null
145 */
146 public Location into() {
147 return into;
148 }
149
150 /**
151 * Get the location defining the node before which the branch is to be placed
152 *
153 * @return the to location; null indicates that the branch should be the last child node of its new parent
154 */
155 public Location before() {
156 return before;
157 }
158
159 /**
160 * Get the name of the workspace in which the branch exists.
161 *
162 * @return the name of the workspace containing the branch; never null
163 */
164 public String inWorkspace() {
165 return workspaceName;
166 }
167
168 /**
169 * Get the name of the copy if it is to be different than that of the original.
170 *
171 * @return the desired name of the copy, or null if the name of the original is to be used
172 */
173 public Name desiredName() {
174 return desiredNameForNode;
175 }
176
177 /**
178 * Get the expected behavior when copying the branch and the {@link #into() destination} already has a node with the same
179 * name.
180 *
181 * @return the behavior specification
182 */
183 public NodeConflictBehavior conflictBehavior() {
184 return conflictBehavior;
185 }
186
187 /**
188 * {@inheritDoc}
189 *
190 * @see org.jboss.dna.graph.request.Request#isReadOnly()
191 */
192 @Override
193 public boolean isReadOnly() {
194 return false;
195 }
196
197 /**
198 * Determine whether this move request can be determined to have no effect.
199 * <p>
200 * A move is known to have no effect when all of the following conditions are true:
201 * <ul>
202 * <li>the {@link #into() into} location has a {@link Location#hasPath() path} but no {@link Location#hasIdProperties()
203 * identification properties};</li>
204 * <li>the {@link #from() from} location has a {@link Location#getPath() path}; and</li>
205 * <li>the {@link #from() from} location's {@link Path#getParent() parent} is the same as the {@link #into() into} location's
206 * path.</li>
207 * </ul>
208 * If all of these conditions are not true, this method returns false.
209 * </p>
210 *
211 * @return true if this move request really doesn't change the parent of the node, or false if it cannot be determined
212 */
213 public boolean hasNoEffect() {
214 if (into != null && into.hasPath() && into.hasIdProperties() == false && from.hasPath()) {
215 if (!from.getPath().getParent().equals(into.getPath())) return false;
216 if (desiredName() != null && !desiredName().equals(from.getPath().getLastSegment().getName())) return false;
217 if (before != null) return false;
218 return true;
219 }
220 // Can't be determined for certain
221 return false;
222 }
223
224 /**
225 * Sets the actual and complete location of the node being renamed and its new location. This method must be called when
226 * processing the request, and the actual location must have a {@link Location#getPath() path}.
227 *
228 * @param oldLocation the actual location of the node before being moved
229 * @param newLocation the actual new location of the node
230 * @throws IllegalArgumentException if the either location is null, if the old location does not represent the
231 * {@link Location#isSame(Location) same location} as the {@link #from() from location}, if the new location does not
232 * represent the {@link Location#isSame(Location) same location} as the {@link #into() into location}, or if the
233 * either location does not have a path
234 * @throws IllegalStateException if the request is frozen
235 */
236 public void setActualLocations( Location oldLocation,
237 Location newLocation ) {
238 checkNotFrozen();
239 CheckArg.isNotNull(oldLocation, "oldLocation");
240 CheckArg.isNotNull(newLocation, "newLocation");
241 if (!from.isSame(oldLocation)) { // not same if actual is null
242 throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(oldLocation, from));
243 }
244 if (!oldLocation.hasPath()) {
245 throw new IllegalArgumentException(GraphI18n.actualOldLocationMustHavePath.text(oldLocation));
246 }
247 if (!newLocation.hasPath()) {
248 throw new IllegalArgumentException(GraphI18n.actualNewLocationMustHavePath.text(newLocation));
249 }
250 if (into() != null && into().hasPath() && !newLocation.getPath().getParent().isSameAs(into.getPath())) {
251 throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(newLocation, into));
252 }
253 Name actualNewName = newLocation.getPath().getLastSegment().getName();
254 Name expectedNewName = desiredName() != null ? desiredName() : oldLocation.getPath().getLastSegment().getName();
255 if (!actualNewName.equals(expectedNewName)) {
256 throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(newLocation, into));
257 }
258 this.actualOldLocation = oldLocation;
259 this.actualNewLocation = newLocation;
260 }
261
262 /**
263 * Get the actual location of the node before being moved.
264 *
265 * @return the actual location of the node before being moved, or null if the actual location was not set
266 */
267 public Location getActualLocationBefore() {
268 return actualOldLocation;
269 }
270
271 /**
272 * Get the actual location of the node after being moved.
273 *
274 * @return the actual location of the node after being moved, or null if the actual location was not set
275 */
276 public Location getActualLocationAfter() {
277 return actualNewLocation;
278 }
279
280 /**
281 * {@inheritDoc}
282 *
283 * @see org.jboss.dna.graph.request.ChangeRequest#changes(java.lang.String, org.jboss.dna.graph.property.Path)
284 */
285 @Override
286 public boolean changes( String workspace,
287 Path path ) {
288 if (this.into() != null) {
289 return this.workspaceName.equals(workspace)
290 && (into.hasPath() && into.getPath().isAtOrBelow(path) || from.hasPath() && from.getPath().isAtOrBelow(path));
291 }
292 // into or before must be non-null
293 assert before() != null;
294 return this.workspaceName.equals(workspace)
295 && (before.hasPath() && before.getPath().getParent().isAtOrBelow(path) || from.hasPath()
296 && from.getPath().isAtOrBelow(path));
297
298 }
299
300 /**
301 * {@inheritDoc}
302 *
303 * @see org.jboss.dna.graph.request.ChangeRequest#changedLocation()
304 */
305 @Override
306 public Location changedLocation() {
307 return into != null ? into : before;
308 }
309
310 /**
311 * {@inheritDoc}
312 *
313 * @see org.jboss.dna.graph.request.ChangeRequest#changedWorkspace()
314 */
315 @Override
316 public String changedWorkspace() {
317 return workspaceName;
318 }
319
320 /**
321 * {@inheritDoc}
322 *
323 * @see org.jboss.dna.graph.request.Request#cancel()
324 */
325 @Override
326 public void cancel() {
327 super.cancel();
328 this.actualOldLocation = null;
329 this.actualNewLocation = null;
330 }
331
332 /**
333 * {@inheritDoc}
334 *
335 * @see java.lang.Object#hashCode()
336 */
337 @Override
338 public int hashCode() {
339 return HashCode.compute(from, workspaceName, into);
340 }
341
342 /**
343 * {@inheritDoc}
344 *
345 * @see java.lang.Object#equals(java.lang.Object)
346 */
347 @Override
348 public boolean equals( Object obj ) {
349 if (obj == this) return true;
350 if (this.getClass().isInstance(obj)) {
351 MoveBranchRequest that = (MoveBranchRequest)obj;
352 if (!this.from().equals(that.from())) return false;
353 if (!this.into().equals(that.into())) return false;
354 if (!this.conflictBehavior().equals(that.conflictBehavior())) return false;
355 if (!this.workspaceName.equals(that.workspaceName)) return false;
356 return true;
357 }
358 return false;
359 }
360
361 /**
362 * {@inheritDoc}
363 *
364 * @see java.lang.Object#toString()
365 */
366 @Override
367 public String toString() {
368 if (desiredName() != null) {
369 return "move branch " + from() + " in the \"" + inWorkspace() + "\" workspace "
370 + (into() == null ? "before " + before() : "into " + into()) + " with name " + desiredName();
371 }
372 return "move branch " + from() + " in the \"" + inWorkspace() + "\" workspace into "
373 + (into() == null ? "before " + before() : "into " + into());
374 }
375 }