1    /*
2     * Copyright 2006 :torweg free software group
3     * 
4     * This program is free software: you can redistribute it and/or modify
5     * it under the terms of the GNU General Public License as published by
6     * the Free Software Foundation, either version 3 of the License, or
7     * (at your option) any later version.
8     * 
9     * This program is distributed in the hope that it will be useful,
10    * but WITHOUT ANY WARRANTY; without even the implied warranty of
11    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    * GNU General Public License for more details.
13    * 
14    * You should have received a copy of the GNU General Public License
15    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16    *
17    */
18   package org.torweg.pulse.util.entity;
19   
20   import java.io.Serializable;
21   import java.util.AbstractList;
22   import java.util.ArrayList;
23   import java.util.Comparator;
24   import java.util.Iterator;
25   import java.util.List;
26   import java.util.Set;
27   import java.util.TreeSet;
28   
29   import javax.persistence.Basic;
30   import javax.persistence.CascadeType;
31   import javax.persistence.Entity;
32   import javax.persistence.FetchType;
33   import javax.persistence.Inheritance;
34   import javax.persistence.InheritanceType;
35   import javax.persistence.ManyToOne;
36   import javax.persistence.OneToMany;
37   import javax.persistence.Transient;
38   import javax.xml.bind.annotation.XmlAccessOrder;
39   import javax.xml.bind.annotation.XmlAccessType;
40   import javax.xml.bind.annotation.XmlAccessorOrder;
41   import javax.xml.bind.annotation.XmlAccessorType;
42   import javax.xml.bind.annotation.XmlAttribute;
43   import javax.xml.bind.annotation.XmlElement;
44   import javax.xml.bind.annotation.XmlElementWrapper;
45   import javax.xml.bind.annotation.XmlRootElement;
46   import javax.xml.bind.annotation.XmlTransient;
47   
48   import net.sf.json.JSONArray;
49   import net.sf.json.JSONObject;
50   
51   import org.hibernate.LazyInitializationException;
52   import org.hibernate.annotations.BatchSize;
53   import org.hibernate.search.annotations.Indexed;
54   import org.slf4j.Logger;
55   import org.slf4j.LoggerFactory;
56   
57   /**
58    * is a base entity to build bi-directional tree entities with positionable
59    * children lists.
60    * <p>
61    * The EJB 3.0 persistence inheritance type of {@code Node} is "JOINED".
62    * </p>
63    * <p>
64    * <strong>Note:</strong> The {@code FetchType} of the children is
65    * {@code FetchType.LAZY}!
66    * </p>
67    * <p>
68    * <strong>if you want to delete a child, you have to update the parent in
69    * Hibernate<sup>TM</sup>!</strong>
70    * </p>
71    * 
72    * @author Thomas Weber, Daniel Dietz
73    * @version $Revision: 2003 $
74    */
75   @Entity
76   @Inheritance(strategy = InheritanceType.JOINED)
77   @Indexed
78   @XmlRootElement(name = "node")
79   @XmlAccessorOrder(XmlAccessOrder.UNDEFINED)
80   @XmlAccessorType(XmlAccessType.FIELD)
81   public class Node extends AbstractBasicEntity {
82   
83       /**
84        * serialVersionUID.
85        */
86       private static final long serialVersionUID = -712152371572827676L;
87   
88       /**
89        * logger.
90        */
91       private static final Logger LOGGER = LoggerFactory.getLogger(Node.class);
92   
93       /**
94        * the parent of the {@code Node}.
95        */
96       @ManyToOne(targetEntity = Node.class, cascade = CascadeType.ALL)
97       @XmlTransient
98       // getter JAXB-annotated {this.getParentIdJAXB()}
99       private Node parent;
100  
101      /**
102       * child nodes.
103       */
104      @OneToMany(targetEntity = Node.class, mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
105      @BatchSize(size = 100)
106      @XmlTransient
107      private final Set<Node> childrenSet;
108  
109      /**
110       * indicator, whether the {@code SitemapNode} has children.
111       */
112      @Basic
113      @XmlAttribute(name = "has-children")
114      private boolean hasChildren = false;
115  
116      /**
117       * the position in the list.
118       */
119      @Basic
120      @XmlAttribute(name = "position")
121      private int position;
122  
123      /**
124       * a {@code List} on top of the childrenSet {@code Set}.
125       */
126      @Transient
127      // FINDBUGS: ignore warning, children is stateless and the data is
128      // obtained from childrenSet
129      private final transient List<Node> children = new InnerList<Node>();
130  
131      /**
132       * builds a new {@code Node}.
133       */
134      public Node() {
135          super();
136          this.childrenSet = new TreeSet<Node>(new PositionComparator<Node>());
137          this.hasChildren = false;
138      }
139  
140      // /**
141      // * The fqn for evaluation in XSL.
142      // *
143      // * @return the fqn
144      // */
145      // @XmlAttribute(name = "class")
146      // @SuppressWarnings("unused")
147      // @Deprecated
148      // private String getFullQualifiedClassName() {
149      // return getClass().getCanonicalName();
150      // }
151  
152      /**
153       * sets the parent of the {@code Node}.
154       * 
155       * @param node
156       *            the parent of the {@code Node}
157       */
158      private void setParent(final Node node) {
159          this.parent = node;
160      }
161  
162      /**
163       * @return returns the parent {@code Node} of the {@code Node} or
164       *         {@code null}, if the {@code Node} has no parent.
165       */
166      public final Node getParent() {
167          return this.parent;
168      }
169  
170      /**
171       * For JAXB only.
172       * 
173       * @return this.getParent().getId()
174       */
175      @XmlElement(name = "parent-id")
176      @SuppressWarnings("unused")
177      @Deprecated
178      private Long getParentIdJAXB() {
179          Node par = getParent();
180          if (par == null) {
181              return null;
182          }
183          return par.getId();
184      }
185  
186      /**
187       * @return the child nodes of the {@code Node}.
188       */
189      public List<Node> getChildren() {
190          return this.children;
191      }
192  
193      /**
194       * For JAXB only.
195       * 
196       * @return this.getChildren()
197       */
198      @XmlElementWrapper(name = "children")
199      @XmlElement(name = "child")
200      @SuppressWarnings("unused")
201      @Deprecated
202      private ArrayList<Node> getChildrenJAXB() { // NOPMD
203          try {
204              return new ArrayList<Node>(getChildren());
205          } catch (LazyInitializationException e) {
206              LOGGER.debug("ignored: {}", e.getLocalizedMessage());
207              return null;
208          }
209      }
210  
211      /**
212       * gets the child {@code Node} which is at the given position in the child
213       * list.
214       * 
215       * @param pos
216       *            the position
217       * @return the child {@code Node} or null, if no such {@code Node} exists.
218       */
219      public Node getChild(final int pos) {
220          return this.children.get(pos);
221      }
222  
223      /**
224       * gets the child {@code Node} which is at the given position in the child
225       * list.
226       * 
227       * @param node
228       *            the {@code Node} to get the index for
229       * @return the {@code Node}'s index as {@code int} in it's parents list of
230       *         children or {@code -1}, if no such {@code Node} exists.
231       */
232      public int getChildIndex(final Node node) {
233          if (this.children.contains(node)) {
234              return this.children.indexOf(node);
235          } else {
236              return -1;
237          }
238      }
239  
240      /**
241       * adds a new {@code Node} to the childrenSet of this {@code Node} .
242       * 
243       * @param n
244       *            the {@code Node} to become a child of this {@code Node}.
245       */
246      public void addChild(final Node n) {
247          n.setParent(this);
248          this.children.add(n);
249          this.hasChildren = true;
250      }
251  
252      /**
253       * adds a new {@code Node} at a given position to the list childrenSet of
254       * this {@code Node}.
255       * 
256       * @param pos
257       *            the position in the list of childrenSet
258       * @param n
259       *            the {@code Node} to become a child of this {@code Node}.
260       */
261      public void addChild(final int pos, final Node n) {
262          n.setParent(this);
263          this.children.add(pos, n);
264          this.hasChildren = true;
265      }
266  
267      /**
268       * sets the childrenSet of the {@code Node}.
269       * 
270       * @param c
271       *            the list of child {@code &lt;E extends Node&gt;}s
272       * @param <E>
273       *            a {@code Node} or a subclass of {@code Node}
274       */
275      public <E extends Node> void setChildren(final List<E> c) {
276          for (E e : c) {
277              if (e == null) {
278                  throw new NullPointerException();
279              }
280          }
281          this.children.clear();
282          for (Node n : c) {
283              // n.setParent(this);
284              this.addChild(n);
285          }
286          this.hasChildren = this.children.isEmpty() ^ true;
287      }
288  
289      /**
290       * removes a {@code Node} from the list of child {@code Node}s.
291       * 
292       * @param c
293       *            the {@code Node} to be removed
294       * @return {@code true}, if the removal was successful. Otherwise
295       *         {@code false}.
296       */
297      public final boolean removeChild(final Node c) {
298          c.setParent(null);
299          boolean success = this.children.remove(c);
300          this.hasChildren = this.children.isEmpty() ^ true;
301          return success;
302      }
303  
304      /**
305       * Returns {@code true}, if the {@code Node} has children.&nbsp;Otherwise
306       * {@code false}.
307       * <p>
308       * Since the children are fetched lazily, this can be used to quickly check,
309       * if the {@code Node} contains children without actually loading them.
310       * </p>
311       * 
312       * @return {@code true}, if the {@code Node} has children.&nbsp;Otherwise
313       *         {@code false}.
314       */
315      public final boolean hasChildren() {
316          return this.hasChildren;
317      }
318  
319      /**
320       * @return the childrenSet
321       */
322      @XmlTransient
323      private Set<Node> getChildrenSet() {
324          return this.childrenSet;
325      }
326  
327      /**
328       * returns a {@code JSONObject} representation of the {@code Node} .
329       * 
330       * @return a {@code JSONObject} representation of the {@code Node}
331       */
332      public JSONObject toJSON() {
333          JSONObject nodeObject = new JSONObject();
334          nodeObject.put("id", getId());
335          // nodeObject.put("leaf", !hasChildren());
336          nodeObject.put("expandable", hasChildren());
337          nodeObject.put("has_Children", hasChildren());
338  
339          JSONArray expandIds = new JSONArray();
340          try {
341              Node n = this;
342              while (n != null) {
343                  expandIds.add(0, n.getId());
344                  n = n.getParent();
345              }
346          } catch (LazyInitializationException e) {
347              LOGGER.debug("{}.toJSON.error.ignored: \n{}", this.getClass()
348                      .getCanonicalName(), e.getLocalizedMessage());
349          }
350          nodeObject.put("expandIds", expandIds);
351          return nodeObject;
352      }
353  
354      /* ------------------------------- inner classes ------------------------- */
355  
356      /**
357       * Inner list operating on the stored set.
358       * 
359       * @author Thomas Weber, Daniel Dietz, Christian Schatt
360       * @version $Revision: 2003 $
361       * @param <E>
362       *            extends Node
363       */
364      private class InnerList<E extends Node> extends AbstractList<E> {
365  
366          /**
367           * used in exception reports.
368           */
369          private static final String GT = " >= ";
370  
371          /**
372           * add a {@code Node} to the end of the {@code List}.
373           * 
374           * @param n
375           *            the node
376           * @return {@code true}
377           */
378          @Override
379          public boolean add(final E n) {
380              n.position = getChildrenSet().size();
381              add(size(), n);
382              // modCount++;
383              hasChildren = children.isEmpty() ^ true;
384              return true;
385          }
386  
387          /**
388           * Returns the element at the specified position in this list.
389           * 
390           * @param index
391           *            index of element to return.
392           * 
393           * @return the element at the specified position in this list.
394           */
395          @Override
396          @SuppressWarnings("unchecked")
397          public E get(final int index) {
398              if (index < 0 || index >= getChildrenSet().size()) {
399                  throw new IndexOutOfBoundsException(index + GT
400                          + getChildrenSet().size());
401              }
402              Iterator<Node> childrenIterator = getChildrenSet().iterator();
403              while (childrenIterator.hasNext()) {
404                  Node n = childrenIterator.next();
405                  if (n.position == index) {
406                      // CHECKSTYLE:OFF
407                      return (E) n;
408                      // CHECKSTYLE:ON
409                  }
410              }
411              // fix positions
412              return fixPositionsAndGet(index);
413          }
414  
415          /**
416           * fixes the positions of the children set and returns the element at
417           * the given index.
418           * 
419           * @param index
420           *            the index
421           * @return the element at the given index
422           */
423          @SuppressWarnings("unchecked")
424          private E fixPositionsAndGet(final int index) {
425              Node element = null;
426              Iterator<Node> childrenIterator = getChildrenSet().iterator();
427              int pos = 0;
428              while (childrenIterator.hasNext()) {
429                  Node n = childrenIterator.next();
430                  if (pos == index) {
431                      element = n;
432                  }
433                  n.position = pos++;
434              }
435              return (E) element;
436          }
437  
438          /**
439           * Replaces the element at the specified position in this list with the
440           * specified element (optional operation).
441           * 
442           * @param index
443           *            index of element to replace.
444           * @param element
445           *            element to be stored at the specified position.
446           * @return the element previously at the specified position.
447           * 
448           */
449          @Override
450          public E set(final int index, final E element) {
451              if (index < 0 || index >= getChildrenSet().size()) {
452                  throw new IndexOutOfBoundsException(index + GT
453                          + getChildrenSet().size());
454              }
455              E n = remove(index);
456              element.position = index;
457              add(index, element);
458              return n;
459          }
460  
461          /**
462           * Inserts the specified element at the specified position in this list
463           * (optional operation). Shifts the element currently at that position
464           * (if any) and any subsequent elements to the right (adds one to their
465           * indices).
466           * 
467           * @param index
468           *            index at which the specified element is to be inserted.
469           * @param element
470           *            element to be inserted.
471           */
472          @Override
473          public void add(final int index, final E element) {
474              if (index < 0 || index > getChildrenSet().size()) {
475                  throw new IndexOutOfBoundsException(index + GT
476                          + getChildrenSet().size());
477              }
478  
479              LOGGER.trace("add(index,element): {} {}", index, element.getId());
480  
481              Iterator<Node> iterator = getChildrenSet().iterator();
482  
483              while (iterator.hasNext()) {
484                  Node n = iterator.next();
485                  if (n.position < index) {
486                      LOGGER.trace("  keeping:\t{} {}", n.position, n.getId());
487                      continue;
488                  }
489                  LOGGER.trace("  changing:\t{} {} to {}", new Object[] {
490                          n.position, n.getId(), (n.position + 1) });
491                  n.position = n.position + 1;
492  
493              }
494              element.position = index;
495              LOGGER.trace("  adding:\t{} {}", element.position, element.getId());
496              getChildrenSet().add(element);
497              hasChildren = children.isEmpty() ^ true;
498          }
499  
500          /**
501           * Removes the element at the specified position in this list (optional
502           * operation). Shifts any subsequent elements to the left (subtracts one
503           * from their indices). Returns the element that was removed from the
504           * list.
505           * <p>
506           * 
507           * @param index
508           *            the list index
509           * @return the removed element
510           */
511          @Override
512          @SuppressWarnings("unchecked")
513          public E remove(final int index) {
514              if (index < 0 || index >= getChildrenSet().size()) {
515                  throw new IndexOutOfBoundsException(index + GT
516                          + getChildrenSet().size());
517              }
518  
519              LOGGER.trace("remove(index): {}", index);
520              Iterator<Node> iterator = getChildrenSet().iterator();
521              Node removed = null;
522              while (iterator.hasNext()) {
523                  Node n = iterator.next();
524                  if (n.position < index) {
525                      LOGGER.trace("  keeping:\t{} {}", n.position, n.getId());
526                      continue;
527                  } else if (n.position > index) {
528                      LOGGER.trace("  changing:\t{} {} to {}", new Object[] {
529                              n.position, n.getId(), (n.position - 1) });
530                      n.position = n.position - 1;
531                      continue;
532                  } else {
533                      LOGGER.trace("  removing:\t{} {}", n.position, n.getId());
534                      removed = iterator.next();
535                      iterator.remove();
536                  }
537              }
538              // modCount++;
539              hasChildren = !children.isEmpty();
540              // CHECKSTYLE:OFF
541              return (E) removed;
542              // CHECKSTYLE:ON
543          }
544  
545          /**
546           * removes the given {@code Object} from the {@code List}.
547           * 
548           * @param o
549           *            the {@code Object} to be removed
550           * @return {@code true}, if the {@code Object} could be deleted
551           */
552          @Override
553          public boolean remove(final Object o) {
554              if (!(o instanceof Node)) {
555                  return false;
556              }
557              Node r = (Node) o;
558  
559              Iterator<Node> childIterator = getChildrenSet().iterator();
560              boolean deleted = false;
561              int index = -1;
562  
563              while (childIterator.hasNext()) {
564                  Node n = childIterator.next();
565                  if (n.getId().equals(r.getId())) {
566                      index = n.position;
567                      childIterator.remove();
568                      deleted = true;
569                  }
570              }
571              /* correct positions */
572              if (deleted) {
573                  LOGGER.trace("  node was deleted");
574                  childIterator = getChildrenSet().iterator();
575                  while (childIterator.hasNext()) {
576                      Node n = childIterator.next();
577                      if (n.position > index) {
578                          n.position = n.position - 1;
579                      }
580                  }
581              }
582              hasChildren = !children.isEmpty();
583              return deleted;
584          }
585  
586          /**
587           * @return the length of the list.
588           */
589          @Override
590          public int size() {
591              return getChildrenSet().size();
592          }
593      }
594  
595      /**
596       * compares two {@code Node}s by their position.
597       * 
598       * @author Thomas Weber, Daniel Dietz, Christian Schatt
599       * @version $Revision: 2003 $
600       * @param <E>
601       *            extends Node
602       */
603      private static class PositionComparator<E extends Node> implements
604              Comparator<E>, Serializable {
605  
606          /**
607           * serialVersionUID.
608           */
609          private static final long serialVersionUID = -111850720683236L;
610  
611          /**
612           * compares two {@code E}.
613           * 
614           * @param e1
615           *            object 1
616           * @param e2
617           *            object 2
618           * @return a negative integer, zero, or a positive integer as the first
619           *         argument is less than, equal to, or greater than the second.
620           */
621          public int compare(final E e1, final E e2) {
622              return e1.position - e2.position;
623          }
624  
625      }
626  
627  }
628