View Javadoc
1   /*******************************************************************************
2    *   Gisgraphy Project 
3    * 
4    *   This library is free software; you can redistribute it and/or
5    *   modify it under the terms of the GNU Lesser General Public
6    *   License as published by the Free Software Foundation; either
7    *   version 2.1 of the License, or (at your option) any later version.
8    * 
9    *   This library 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 GNU
12   *   Lesser General Public License for more details.
13   * 
14   *   You should have received a copy of the GNU Lesser General Public
15   *   License along with this library; if not, write to the Free Software
16   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
17   * 
18   *  Copyright 2008  Gisgraphy project 
19   *  David Masclet <davidmasclet@gisgraphy.com>
20   *  
21   *  
22   *******************************************************************************/
23  package com.gisgraphy.domain.geoloc.entity;
24  
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import javax.persistence.CascadeType;
29  import javax.persistence.Column;
30  import javax.persistence.Entity;
31  import javax.persistence.FetchType;
32  import javax.persistence.JoinColumn;
33  import javax.persistence.ManyToOne;
34  import javax.persistence.OneToMany;
35  import javax.persistence.Transient;
36  
37  import org.hibernate.annotations.Cache;
38  import org.hibernate.annotations.CacheConcurrencyStrategy;
39  import org.hibernate.annotations.Fetch;
40  import org.hibernate.annotations.FetchMode;
41  import org.hibernate.annotations.Index;
42  
43  import com.gisgraphy.helper.FeatureClassCodeHelper;
44  import com.gisgraphy.helper.IntrospectionIgnoredField;
45  
46  /**
47   * Represents a (sub) division of a {@link Country} (Region, Province, state,
48   * Department, and so on)<br>
49   * {@linkplain Adm} are in tree structure. An Adm can have some children and MUST
50   * have a parent if the Level is > 1 (an Adm with level 1 is to be a 'ROOT' Adm)
51   * 
52   * @author <a href="mailto:david.masclet@gisgraphy.com">David Masclet</a>
53   */
54  @Entity
55  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
56  public class Adm extends GisFeature {
57  
58      /**
59       * Constructor that populate the Adm with the gisFeature fields and set the
60       * level<br>
61       * <u>note</u> The feature class will be set to 'A' and The feature code
62       * will be set according to the Level (ADM + level)
63       * 
64       * @param gisFeature
65       *                The gisFeature we want to populate the
66       *                {@linkplain Adm}
67       * @param level
68       *                The level of the Adm
69       */
70      public Adm(GisFeature gisFeature, Integer level) {
71  	super(gisFeature);
72  	setFeatureClass("A");
73  	setLevel(level);
74  	setFeatureCode("ADM" + getLevel());
75      }
76  
77      /**
78       * Constructor that create an Adm for the specified level<br>
79       * <u>note</u> the feature class will be set to 'A' and The feature code will
80       * be set according to the Level (ADM + level)
81       * 
82       * @param level
83       *                The level of the Adm
84       */
85      public Adm(Integer level) {
86  	setLevel(level);
87  	setFeatureClass("A");
88  	setFeatureCode("ADM" + getLevel());
89      }
90  
91      /**
92       * Default constructor (Needed by CGLib)
93       */
94      protected Adm() {
95  	super();
96      }
97  
98      /**
99       * Check that the country code is filled and the admXcode are correctly filled according
100      * to the level
101      */
102     @Transient
103     public boolean isConsistentForLevel() {
104 	if (getCountryCode() == null) {
105 	    return false;
106 	}
107 	if (this.getLevel() == 1 && this.getAdm1Code() == null) {
108 	    return false;
109 	} else if (this.getLevel() == 2
110 		&& (this.getAdm1Code() == null || this.getAdm2Code() == null)) {
111 	    return false;
112 	} else if (this.getLevel() == 3
113 		&& (this.getAdm1Code() == null || this.getAdm2Code() == null || this
114 			.getAdm3Code() == null)) {
115 	    return false;
116 	} else if (this.getLevel() == 4
117 		&& (this.getAdm1Code() == null || this.getAdm2Code() == null
118 			|| this.getAdm3Code() == null || this.getAdm4Code() == null)) {
119 	    return false;
120 	}
121 	return true;
122     }
123 
124     private Integer level;
125 
126     @IntrospectionIgnoredField
127     private Adm parent;
128 
129     private List<Adm> children;
130 
131     /**
132      * @return The Level Of The Adm
133      */
134     @Column(nullable = false)
135     @Index(name = "admLevel")
136     public Integer getLevel() {
137 	return level;
138     }
139 
140     /**
141      * Set the level and Check that 1<= level<= 4. If it is not the case, throw an
142      * {@link IllegalArgumentException}
143      * 
144      * @param level
145      *                The Level to set
146      * @throws IllegalArgumentException
147      *                 If level is not correct
148      */
149     public void setLevel(Integer level) {
150 	if (level < 1 || level > 4) {
151 	    throw new IllegalArgumentException(
152 		    "The level of an Adm can not be " + level
153 			    + ". it must be beetween 1 and 4");
154 	}
155 	this.level = level;
156     }
157 
158     /**
159      * Do a double set : Add (not replace ! ) The child to the current Adm and
160      * set the current Adm as the Parent of the specified Child.
161      * 
162      * @param child
163      *                The child to add 
164      * @throws IllegalArgumentException if the level of the child 
165      * is not equals to the level of this Adm +1
166      */
167     public void addChild(Adm child) {
168 	if (child == null) {
169 	    throw new IllegalArgumentException("Could not add a null child to "
170 		    + this);
171 	}
172 	if (child.getLevel() != getLevel() + 1) {
173 	    throw new IllegalArgumentException("a child of level "
174 		    + child.getLevel() + " (" + child
175 		    + ") should not be added to an Adm with level " + getLevel()
176 		    + " : " + child + " but will be added");
177 	}
178 	List<Adm> currentChilds = getChildren();
179 	if (currentChilds == null) {
180 	    currentChilds = new ArrayList<Adm>();
181 	}
182 	currentChilds.add(child);
183 	this.setChildren(currentChilds);
184 	child.setParent(this);
185 
186     }
187 
188     /**
189      * Do a double set : Add (not replace !) the children to the current Adm and
190      * set the current Adm as the parent of the specified Children
191      * 
192      * @see #addChild(Adm)
193      * @param children
194      *                The children to add
195      */
196     public void addChildren(List<Adm> children) {
197 	if (children != null) {
198 	    for (Adm child : children) {
199 		addChild(child);
200 	    }
201 	}
202 	;
203     }
204 
205     /**
206      * Return the Adms of a directly higher Level in the adm the tree structure
207      * @return The Adms of a directly higher Level <br>
208      * <b>Example</b> Returns the Adm(s) with level 2 if the current
209      *         Adm has a level equals to 1
210      */
211     @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "parent")
212     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
213     @Fetch(FetchMode.SELECT)
214     public List<Adm> getChildren() {
215 	return children;
216     }
217 
218     /**
219      * Set the Adms of a directly higher level
220      * 
221      * @param children
222      *                the children for the current Adm
223      */
224     // @throws IllegalArgumentException If the children are are not equals to
225     // the level of this Adm+1
226     public void setChildren(List<Adm> children) {
227 	// TODO v2 DSL
228 	/*
229 	 * try { if (children != null ){ for (Adm child : children){ if
230 	 * (child.getLevel()!= getLevel()+1){ throw new
231 	 * IllegalArgumentException("Could not add a child of level
232 	 * "+child.getLevel()+" to an Adm of level "+getLevel()); } } }
233 	 */
234 	this.children = children;
235 
236     }
237 
238     /**
239      * Returns The parent Adm in the Adm tree structure
240      * 
241      * @return The parent Adm (with lower Level)
242      */
243     @ManyToOne(fetch = FetchType.EAGER)
244     @JoinColumn(nullable = true, name = "parent")
245     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
246     @Index(name = "admadmindex")
247     public Adm getParent() {
248 	return parent;
249     }
250 
251     /**
252      * Set the parent Adm in the tree structure
253      * 
254      * @param parent
255      *                the Parent Adm to set
256      */
257     // @throws IllegalArgumentException if the parent is not equals to the level
258     // of this Adm-1
259     public void setParent(Adm parent) {
260 	// TODO v2 dsl
261 	/*
262 	 * if (parent != null && parent.getLevel()!=getLevel()-1 ){ throw new
263 	 * IllegalArgumentException("Could not add a null child"); }
264 	 */
265 	this.parent = parent;
266     }
267 
268     private static boolean isAdmCodeEmpty(String admCode) {
269 	if (admCode == null || admCode.trim().length() == 0) {
270 	    return true;
271 	}
272 	return false;
273     }
274 
275     /**
276      * Determine what should be the level of 
277      * an Adm which have the provided codes
278      * 
279      * @param adm1Code
280      *                The Adm1Code of the Adm to test
281      * @param adm2Code
282      *                The Adm2Code of the Adm to test
283      * @param adm3Code
284      *                The Adm3Code of the Adm to test
285      * @param adm4Code
286      *                The Adm4Code of the Adm to test
287      * @return the processed Level or 0 if the level can not be determine or all
288      *         the code are null
289      */
290     public static int getProcessedLevelFromCodes(String adm1Code,
291 	    String adm2Code, String adm3Code, String adm4Code) {
292 	if (!isAdmCodeEmpty(adm1Code)) {
293 	    if (!isAdmCodeEmpty(adm2Code)) {
294 		if (!isAdmCodeEmpty(adm3Code)) {
295 		    if (!isAdmCodeEmpty(adm4Code)) {
296 			// adm1,adm2,adm3,adm4
297 			return 4;
298 		    } else {
299 			// adm1,adm2,adm3,null
300 			return 3;
301 		    }
302 		} else {
303 		    // adm1,Adm2, null..
304 		    return 2;
305 		}
306 	    } else {
307 		// adm1,null...
308 		return 1;
309 	    }
310 	} else {
311 	    // if adm1 is empty can not retrieve any Adm
312 	    return 0;
313 	}
314 
315     }
316 
317     /**
318      * Determine what should be the level of an adm which have a the 
319      * specified featureClass and a featureCode.<br/> e.g :
320      * featureClass=A and featureCode=ADM3 will return 3 .<br/> featureClass=P
321      * and featureCode=ADM4 will return 0 because P_ADM4 is not of ADM type. This
322      * method is case sensitive.
323      * 
324      * @param featureClass
325      *                The featureClass of the Adm to test
326      * @param featureCode
327      *                The featureCode of the Adm to Test
328      * @return the Level of the Adm or 0 if the level can not be determine
329      */
330     public static int getProcessedLevelFromFeatureClassCode(
331 	    String featureClass, String featureCode) {
332 	if (FeatureClassCodeHelper.is_Adm(featureClass, featureCode)) {
333 	    int level = 0;
334 	    try {
335 		level = new Integer(featureCode.substring(3)).intValue();
336 	    } catch (NumberFormatException e) {
337 	    }
338 	    return level;
339 
340 	} else {
341 	    return 0;
342 	}
343 
344     }
345 
346     /*
347      * (non-Javadoc)
348      * 
349      * @see com.gisgraphy.domain.geoloc.entity.GisFeature#hashCode()
350      */
351     @Override
352     public int hashCode() {
353 	final int PRIME = 31;
354 	int result = super.hashCode();
355 	result = PRIME * result
356 		+ ((getAdm1Code() == null) ? 0 : getAdm1Code().hashCode());
357 	result = PRIME * result
358 		+ ((getAdm2Code() == null) ? 0 : getAdm2Code().hashCode());
359 	result = PRIME * result
360 		+ ((getAdm3Code() == null) ? 0 : getAdm3Code().hashCode());
361 	result = PRIME * result
362 		+ ((getAdm4Code() == null) ? 0 : getAdm4Code().hashCode());
363 	result = PRIME * result + ((level == null) ? 0 : level.hashCode());
364 	return result;
365     }
366 
367     /**
368      * @see com.gisgraphy.domain.geoloc.entity.GisFeature#equals(java.lang.Object)
369      */
370     @Override
371     public boolean equals(Object obj) {
372 	if (this == obj) {
373 	    return true;
374 	}
375 	if (!super.equals(obj)) {
376 	    return false;
377 	}
378 	if (getClass() != obj.getClass()) {
379 	    return false;
380 	}
381 	final Adm other = (Adm) obj;
382 	if (level == null) {
383 	    if (other.level != null) {
384 		return false;
385 	    }
386 	} else if (!level.equals(other.level)) {
387 	    return false;
388 	}
389 	if (getCountryCode() == null) {
390 	    if (other.getCountryCode() != null) {
391 		return false;
392 	    }
393 	} else if (!getCountryCode().equals(other.getCountryCode())) {
394 	    return false;
395 	}
396 	if (getAdm1Code() == null) {
397 	    if (other.getAdm1Code() != null) {
398 		return false;
399 	    }
400 	} else if (!getAdm1Code().equals(other.getAdm1Code())) {
401 	    return false;
402 	}
403 	if (getAdm2Code() == null) {
404 	    if (other.getAdm2Code() != null) {
405 		return false;
406 	    }
407 	} else if (!getAdm2Code().equals(other.getAdm2Code())) {
408 	    return false;
409 	}
410 	if (getAdm3Code() == null) {
411 	    if (other.getAdm3Code() != null) {
412 		return false;
413 	    }
414 	} else if (!getAdm3Code().equals(other.getAdm3Code())) {
415 	    return false;
416 	}
417 	if (getAdm4Code() == null) {
418 	    if (other.getAdm4Code() != null) {
419 		return false;
420 	    }
421 	} else if (!getAdm4Code().equals(other.getAdm4Code())) {
422 	    return false;
423 	}
424 	return true;
425     }
426 
427     /*
428      * (non-Javadoc)
429      * 
430      * @see com.gisgraphy.domain.geoloc.entity.GisFeature#toString()
431      */
432     @Override
433     public String toString() {
434 	return "Adm[" + getCountryCode() + "." + getAdm1Code() + "."
435 		+ getAdm2Code() + "." + getAdm3Code() + "." + getAdm4Code()
436 		+ "][level=" + getLevel() + "][" + getFeatureId() + "]["
437 		+ getName() + "]";
438     }
439 
440 }