1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: AbstractKeyList.java 2110 2011-03-08 13:55:32Z dmsmith $
21   */
22  package org.crosswire.jsword.passage;
23  
24  import java.util.Iterator;
25  
26  /**
27   * An implementation of some of the easier methods from Key.
28   * 
29   * @see gnu.lgpl.License for license details.<br>
30   *      The copyright to this program is held by it's authors.
31   * @author Joe Walker [joe at eireneh dot com]
32   */
33  public abstract class AbstractKeyList implements Key {
34      /**
35       * Build an AbstractKeyList with the given name.
36       * 
37       * @param name
38       */
39      protected AbstractKeyList(String name) {
40          this.name = name;
41      }
42  
43      /* (non-Javadoc)
44       * @see org.crosswire.jsword.passage.Key#isEmpty()
45       */
46      public boolean isEmpty() {
47          return getCardinality() == 0;
48      }
49  
50      /* (non-Javadoc)
51       * @see org.crosswire.jsword.passage.Key#contains(org.crosswire.jsword.passage.Key)
52       */
53      public boolean contains(Key key) {
54          return indexOf(key) >= 0;
55      }
56  
57      /* (non-Javadoc)
58       * @see org.crosswire.jsword.passage.Key#retainAll(org.crosswire.jsword.passage.Key)
59       */
60      public void retainAll(Key key) {
61          Key shared = new DefaultKeyList();
62          shared.addAll(key);
63          retain(this, shared);
64      }
65  
66      /**
67       * Utility to remove all the keys from alter that are not in base
68       * 
69       * @param alter
70       *            The key to remove keys from
71       * @param base
72       *            The check key
73       */
74      protected static void retain(Key alter, Key base) {
75          Iterator<Key> it = alter.iterator();
76          while (it.hasNext()) {
77              Key sublist = it.next();
78              if (sublist.canHaveChildren()) {
79                  retain(sublist, base);
80                  if (sublist.isEmpty()) {
81                      it.remove();
82                  }
83              } else {
84                  if (!base.contains(sublist)) {
85                      it.remove();
86                  }
87              }
88          }
89      }
90  
91      @Override
92      public String toString() {
93          return getName();
94      }
95  
96      /**
97       * Override the default name with a custom name. If the name is null then a
98       * name will be generated by concatenating the names of all the elements of
99       * this node.
100      */
101     public void setName(String name) {
102         this.name = name;
103     }
104 
105     /* (non-Javadoc)
106      * @see org.crosswire.jsword.passage.Key#getName()
107      */
108     public String getName() {
109         if (name != null) {
110             return name;
111         }
112 
113         DefaultKeyVisitor visitor = new NameVisitor();
114         KeyUtil.visit(this, visitor);
115         return visitor.toString();
116     }
117 
118     /* (non-Javadoc)
119      * @see org.crosswire.jsword.passage.Key#getName(org.crosswire.jsword.passage.Key)
120      */
121     public String getName(Key base) {
122         return getName();
123     }
124 
125     /* (non-Javadoc)
126      * @see org.crosswire.jsword.passage.Key#getRootName()
127      */
128     public String getRootName() {
129         return getName();
130     }
131 
132     /* (non-Javadoc)
133      * @see org.crosswire.jsword.passage.Key#getOsisRef()
134      */
135     public String getOsisRef() {
136         DefaultKeyVisitor visitor = new OsisRefVisitor();
137         KeyUtil.visit(this, visitor);
138         return visitor.toString();
139     }
140 
141     /* (non-Javadoc)
142      * @see org.crosswire.jsword.passage.Key#getOsisID()
143      */
144     public String getOsisID() {
145         DefaultKeyVisitor visitor = new OsisIDVisitor();
146         KeyUtil.visit(this, visitor);
147         return visitor.toString();
148     }
149 
150     @Override
151     public boolean equals(Object obj) {
152         // Since this can not be null
153         if (obj == null) {
154             return false;
155         }
156 
157         // Check that that is the same as this
158         // Don't use instanceOf since that breaks inheritance
159         if (!obj.getClass().equals(this.getClass())) {
160             return false;
161         }
162 
163         return compareTo((Key) obj) == 0;
164     }
165 
166     @Override
167     public int hashCode() {
168         return getName().hashCode();
169     }
170 
171     /* (non-Javadoc)
172      * @see java.lang.Comparable#compareTo(java.lang.Object)
173      */
174     public int compareTo(Key that) {
175 
176         if (this == that) {
177             return 0;
178         }
179 
180         if (that == null) {
181             // he is empty, we are not so he is greater
182             return -1;
183         }
184 
185         int ret = this.getName().compareTo(that.getName());
186 
187         if (ret != 0) {
188             return ret;
189         }
190 
191         // Compare the contents.
192         Iterator<Key> thisIter = this.iterator();
193         Iterator<Key> thatIter = that.iterator();
194 
195         Key thisfirst = null;
196         Key thatfirst = null;
197 
198         if (thisIter.hasNext()) {
199             thisfirst = thisIter.next();
200         }
201 
202         if (thatIter.hasNext()) {
203             thatfirst = thatIter.next();
204         }
205 
206         if (thisfirst == null) {
207             if (thatfirst == null) {
208                 // we are both empty, and rank the same
209                 return 0;
210             }
211             // i am empty, he is not so we are greater
212             return 1;
213         }
214 
215         if (thatfirst == null) {
216             // he is empty, we are not so he is greater
217             return -1;
218         }
219 
220         return thisfirst.getName().compareTo(thatfirst.getName());
221     }
222 
223     @Override
224     public AbstractKeyList clone() {
225         AbstractKeyList clone = null;
226         try {
227             clone = (AbstractKeyList) super.clone();
228         } catch (CloneNotSupportedException e) {
229             assert false : e;
230         }
231         return clone;
232     }
233 
234     /**
235      * The <code>NameVisitor</code> constructs a readable representation of the
236      * Passage.
237      */
238     static class NameVisitor extends DefaultKeyVisitor {
239         /**
240          * Create a <code>NameVisitor</code>.
241          */
242         public NameVisitor() {
243             buffer = new StringBuilder();
244         }
245 
246         @Override
247         public void visitLeaf(Key key) {
248             buffer.append(key.getName());
249             buffer.append(AbstractPassage.REF_PREF_DELIM);
250         }
251 
252         @Override
253         public String toString() {
254             String reply = buffer.toString();
255             if (reply.length() > 0) {
256                 // strip off the final ", "
257                 reply = reply.substring(0, reply.length() - AbstractPassage.REF_PREF_DELIM.length());
258             }
259 
260             return reply;
261         }
262 
263         protected StringBuilder buffer;
264     }
265 
266     /**
267      * The <code>OsisRefVisitor</code> constructs a readable representation of
268      * the Passage, using OSIS names.
269      */
270     static class OsisRefVisitor extends NameVisitor {
271         @Override
272         public void visitLeaf(Key key) {
273             buffer.append(key.getOsisRef());
274             buffer.append(AbstractPassage.REF_PREF_DELIM);
275         }
276     }
277 
278     /**
279      * The <code>OsisRefVisitor</code> constructs a readable representation of
280      * the Passage, using OSIS names.
281      */
282     static class OsisIDVisitor extends NameVisitor {
283        @Override
284         public void visitLeaf(Key key) {
285             buffer.append(key.getOsisID());
286             buffer.append(AbstractPassage.REF_OSIS_DELIM);
287         }
288 
289         @Override
290         public String toString() {
291             String reply = super.toString();
292             if (reply.length() > 0) {
293                 // strip off the final " "
294                 reply = reply.substring(0, reply.length() - AbstractPassage.REF_OSIS_DELIM.length());
295             }
296 
297             return reply;
298         }
299     }
300 
301     /**
302      * The common user visible name for this work
303      */
304     private String name;
305 
306     /**
307      * Serialization ID
308      */
309     private static final long serialVersionUID = 3858640507828137034L;
310 }
311