View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.graph.request;
25  
26  import org.modeshape.common.util.CheckArg;
27  import org.modeshape.common.util.HashCode;
28  import org.modeshape.graph.GraphI18n;
29  import org.modeshape.graph.Location;
30  import org.modeshape.graph.NodeConflictBehavior;
31  import org.modeshape.graph.property.Name;
32  import org.modeshape.graph.property.Path;
33  
34  /**
35   * Instruction that a branch be moved from one location into another.
36   */
37  public class MoveBranchRequest extends ChangeRequest {
38  
39      private static final long serialVersionUID = 1L;
40  
41      public static final NodeConflictBehavior DEFAULT_CONFLICT_BEHAVIOR = NodeConflictBehavior.APPEND;
42  
43      private final Location from;
44      private final Location into;
45      private final Location before;
46      private final String workspaceName;
47      private final Name desiredNameForNode;
48      private final NodeConflictBehavior conflictBehavior;
49      private Location actualOldLocation;
50      private Location actualNewLocation;
51  
52      /**
53       * Create a request to move a branch from one location into another.
54       * 
55       * @param from the location of the top node in the existing branch that is to be moved
56       * @param into the location of the existing node into which the branch should be moved
57       * @param workspaceName the name of the workspace
58       * @throws IllegalArgumentException if any of the parameters are null
59       */
60      public MoveBranchRequest( Location from,
61                                Location into,
62                                String workspaceName ) {
63          this(from, into, null, workspaceName, null, DEFAULT_CONFLICT_BEHAVIOR);
64      }
65  
66      /**
67       * Create a request to move a branch from one location into another.
68       * 
69       * @param from the location of the top node in the existing branch that is to be moved
70       * @param into the location of the existing node into which the branch should be moved
71       * @param workspaceName the name of the workspace
72       * @param newNameForMovedNode the new name for the node being moved, or null if the name of the original should be used
73       * @throws IllegalArgumentException if any of the parameters are null
74       */
75      public MoveBranchRequest( Location from,
76                                Location into,
77                                String workspaceName,
78                                Name newNameForMovedNode ) {
79          this(from, into, null, workspaceName, newNameForMovedNode, DEFAULT_CONFLICT_BEHAVIOR);
80      }
81  
82      /**
83       * Create a request to move a branch from one location into another.
84       * 
85       * @param from the location of the top node in the existing branch that is to be moved
86       * @param into the location of the existing node into which the branch should be moved
87       * @param workspaceName the name of the workspace
88       * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
89       *        location
90       * @throws IllegalArgumentException if any of the parameters are null
91       */
92      public MoveBranchRequest( Location from,
93                                Location into,
94                                String workspaceName,
95                                NodeConflictBehavior conflictBehavior ) {
96          this(from, into, null, workspaceName, null, conflictBehavior);
97      }
98  
99      /**
100      * Create a request to move a branch from one location into another.
101      * 
102      * @param from the location of the top node in the existing branch that is to be moved
103      * @param into the location of the existing node into which the branch should be moved
104      * @param before the location of the child of the {@code into} node that the branch should be placed before; null indicates
105      *        that the branch should be the last child of its new parent
106      * @param workspaceName the name of the workspace
107      * @param newNameForMovedNode the new name for the node being moved, or null if the name of the original should be used
108      * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
109      *        location
110      * @throws IllegalArgumentException if any of the parameters are null
111      */
112     public MoveBranchRequest( Location from,
113                               Location into,
114                               Location before,
115                               String workspaceName,
116                               Name newNameForMovedNode,
117                               NodeConflictBehavior conflictBehavior ) {
118         CheckArg.isNotNull(from, "from");
119         // CheckArg.isNotNull(into, "into");
120         CheckArg.isNotNull(workspaceName, "workspaceName");
121         CheckArg.isNotNull(conflictBehavior, "conflictBehavior");
122         this.from = from;
123         this.into = into;
124         this.before = before;
125         this.workspaceName = workspaceName;
126         this.desiredNameForNode = newNameForMovedNode;
127         this.conflictBehavior = conflictBehavior;
128     }
129 
130     /**
131      * Get the location defining the top of the branch to be moved
132      * 
133      * @return the from location; never null
134      */
135     public Location from() {
136         return from;
137     }
138 
139     /**
140      * Get the location defining the parent where the branch is to be placed
141      * 
142      * @return the to location; or null if the node is being reordered within the same parent
143      */
144     public Location into() {
145         return into;
146     }
147 
148     /**
149      * Get the location defining the node before which the branch is to be placed
150      * 
151      * @return the to location; null indicates that the branch should be the last child node of its new parent
152      */
153     public Location before() {
154         return before;
155     }
156 
157     /**
158      * Get the name of the workspace in which the branch exists.
159      * 
160      * @return the name of the workspace containing the branch; never null
161      */
162     public String inWorkspace() {
163         return workspaceName;
164     }
165 
166     /**
167      * Get the name of the copy if it is to be different than that of the original.
168      * 
169      * @return the desired name of the copy, or null if the name of the original is to be used
170      */
171     public Name desiredName() {
172         return desiredNameForNode;
173     }
174 
175     /**
176      * Get the expected behavior when copying the branch and the {@link #into() destination} already has a node with the same
177      * name.
178      * 
179      * @return the behavior specification
180      */
181     public NodeConflictBehavior conflictBehavior() {
182         return conflictBehavior;
183     }
184 
185     /**
186      * {@inheritDoc}
187      * 
188      * @see org.modeshape.graph.request.Request#isReadOnly()
189      */
190     @Override
191     public boolean isReadOnly() {
192         return false;
193     }
194 
195     /**
196      * Determine whether this move request can be determined to have no effect.
197      * <p>
198      * A move is known to have no effect when all of the following conditions are true:
199      * <ul>
200      * <li>the {@link #into() into} location has a {@link Location#hasPath() path} but no {@link Location#hasIdProperties()
201      * identification properties};</li>
202      * <li>the {@link #from() from} location has a {@link Location#getPath() path}; and</li>
203      * <li>the {@link #from() from} location's {@link Path#getParent() parent} is the same as the {@link #into() into} location's
204      * path.</li>
205      * </ul>
206      * If all of these conditions are not true, this method returns false.
207      * </p>
208      * 
209      * @return true if this move request really doesn't change the parent of the node, or false if it cannot be determined
210      */
211     public boolean hasNoEffect() {
212         if (into != null && into.hasPath() && into.hasIdProperties() == false && from.hasPath()) {
213             if (!from.getPath().getParent().equals(into.getPath())) return false;
214             if (desiredName() != null && !desiredName().equals(from.getPath().getLastSegment().getName())) return false;
215             if (before != null) return false;
216             return true;
217         }
218         // Can't be determined for certain
219         return false;
220     }
221 
222     /**
223      * Sets the actual and complete location of the node being renamed and its new location. This method must be called when
224      * processing the request, and the actual location must have a {@link Location#getPath() path}.
225      * 
226      * @param oldLocation the actual location of the node before being moved
227      * @param newLocation the actual new location of the node
228      * @throws IllegalArgumentException if the either location is null, or if the either location does not have a path
229      * @throws IllegalStateException if the request is frozen
230      */
231     public void setActualLocations( Location oldLocation,
232                                     Location newLocation ) {
233         checkNotFrozen();
234         CheckArg.isNotNull(oldLocation, "oldLocation");
235         CheckArg.isNotNull(newLocation, "newLocation");
236         if (!oldLocation.hasPath()) {
237             throw new IllegalArgumentException(GraphI18n.actualOldLocationMustHavePath.text(oldLocation));
238         }
239         if (!newLocation.hasPath()) {
240             throw new IllegalArgumentException(GraphI18n.actualNewLocationMustHavePath.text(newLocation));
241         }
242         Name actualNewName = newLocation.getPath().getLastSegment().getName();
243         Name expectedNewName = desiredName() != null ? desiredName() : oldLocation.getPath().getLastSegment().getName();
244         if (!actualNewName.equals(expectedNewName)) {
245             throw new IllegalArgumentException(GraphI18n.actualLocationNotEqualToInputLocation.text(newLocation, into));
246         }
247         this.actualOldLocation = oldLocation;
248         this.actualNewLocation = newLocation;
249     }
250 
251     /**
252      * Get the actual location of the node before being moved.
253      * 
254      * @return the actual location of the node before being moved, or null if the actual location was not set
255      */
256     public Location getActualLocationBefore() {
257         return actualOldLocation;
258     }
259 
260     /**
261      * Get the actual location of the node after being moved.
262      * 
263      * @return the actual location of the node after being moved, or null if the actual location was not set
264      */
265     public Location getActualLocationAfter() {
266         return actualNewLocation;
267     }
268 
269     /**
270      * {@inheritDoc}
271      * 
272      * @see org.modeshape.graph.request.ChangeRequest#changes(java.lang.String, org.modeshape.graph.property.Path)
273      */
274     @Override
275     public boolean changes( String workspace,
276                             Path path ) {
277         if (this.into() != null) {
278             return this.workspaceName.equals(workspace)
279                    && (into.hasPath() && into.getPath().isAtOrBelow(path) || from.hasPath() && from.getPath().isAtOrBelow(path));
280         }
281         // into or before must be non-null
282         assert before() != null;
283         return this.workspaceName.equals(workspace)
284                && (before.hasPath() && before.getPath().getParent().isAtOrBelow(path) || from.hasPath()
285                                                                                          && from.getPath().isAtOrBelow(path));
286 
287     }
288 
289     /**
290      * {@inheritDoc}
291      * <p>
292      * This method returns the {@link #getActualLocationAfter()} location, or if null the {@link #into()} location.
293      * </p>
294      * 
295      * @see org.modeshape.graph.request.ChangeRequest#changedLocation()
296      */
297     @Override
298     public Location changedLocation() {
299         return actualNewLocation != null ? actualNewLocation : into != null ? into : before;
300     }
301 
302     /**
303      * {@inheritDoc}
304      * 
305      * @see org.modeshape.graph.request.ChangeRequest#changedWorkspace()
306      */
307     @Override
308     public String changedWorkspace() {
309         return workspaceName;
310     }
311 
312     /**
313      * {@inheritDoc}
314      * 
315      * @see org.modeshape.graph.request.Request#cancel()
316      */
317     @Override
318     public void cancel() {
319         super.cancel();
320         this.actualOldLocation = null;
321         this.actualNewLocation = null;
322     }
323 
324     /**
325      * {@inheritDoc}
326      * 
327      * @see java.lang.Object#hashCode()
328      */
329     @Override
330     public int hashCode() {
331         return HashCode.compute(from, workspaceName, into);
332     }
333 
334     /**
335      * {@inheritDoc}
336      * 
337      * @see java.lang.Object#equals(java.lang.Object)
338      */
339     @Override
340     public boolean equals( Object obj ) {
341         if (obj == this) return true;
342         if (this.getClass().isInstance(obj)) {
343             MoveBranchRequest that = (MoveBranchRequest)obj;
344             if (!this.from().isSame(that.from())) return false;
345             if (!this.into().isSame(that.into())) return false;
346             if (!this.conflictBehavior().equals(that.conflictBehavior())) return false;
347             if (!this.workspaceName.equals(that.workspaceName)) return false;
348             return true;
349         }
350         return false;
351     }
352 
353     /**
354      * {@inheritDoc}
355      * 
356      * @see java.lang.Object#toString()
357      */
358     @Override
359     public String toString() {
360         if (desiredName() != null) {
361             return "move branch " + from() + " in the \"" + inWorkspace() + "\" workspace "
362                    + (into() == null ? "before " + before() : "into " + into()) + " with name " + desiredName();
363         }
364         return "move branch " + from() + " in the \"" + inWorkspace() + "\" workspace into "
365                + (into() == null ? "before " + before() : "into " + into());
366     }
367 
368     /**
369      * {@inheritDoc}
370      * <p>
371      * This method does not clone the results.
372      * </p>
373      * 
374      * @see org.modeshape.graph.request.ChangeRequest#clone()
375      */
376     @Override
377     public MoveBranchRequest clone() {
378         MoveBranchRequest request = new MoveBranchRequest(actualOldLocation != null ? actualOldLocation : from, into, before,
379                                                           workspaceName, desiredNameForNode, conflictBehavior);
380         request.setActualLocations(actualOldLocation, actualNewLocation);
381         return request;
382     }
383 
384     @Override
385     public RequestType getType() {
386         return RequestType.MOVE_BRANCH;
387     }
388 }