/*******************************************************************************
* Gisgraphy Project
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
*
* Copyright 2008 Gisgraphy project
* David Masclet <davidmasclet@gisgraphy.com>
*
*
*******************************************************************************/
package com.gisgraphy.domain.geoloc.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Index;
import com.gisgraphy.helper.FeatureClassCodeHelper;
import com.gisgraphy.helper.IntrospectionIgnoredField;
/**
* Represents a (sub) division of a {@link Country} (Region, Province, state,
* Department, and so on)<br>
* {@linkplain Adm} are in tree structure. An Adm can have some children and MUST
* have a parent if the Level is > 1 (an Adm with level 1 is to be a 'ROOT' Adm)
*
* @author <a href="mailto:david.masclet@gisgraphy.com">David Masclet</a>
*/
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Adm extends GisFeature {
/**
* Constructor that populate the Adm with the gisFeature fields and set the
* level<br>
* <u>note</u> The feature class will be set to 'A' and The feature code
* will be set according to the Level (ADM + level)
*
* @param gisFeature
* The gisFeature we want to populate the
* {@linkplain Adm}
* @param level
* The level of the Adm
*/
public Adm(GisFeature gisFeature, Integer level) {
super(gisFeature);
setFeatureClass("A");
setLevel(level);
setFeatureCode("ADM" + getLevel());
}
/**
* Constructor that create an Adm for the specified level<br>
* <u>note</u> the feature class will be set to 'A' and The feature code will
* be set according to the Level (ADM + level)
*
* @param level
* The level of the Adm
*/
public Adm(Integer level) {
setLevel(level);
setFeatureClass("A");
setFeatureCode("ADM" + getLevel());
}
/**
* Default constructor (Needed by CGLib)
*/
protected Adm() {
super();
}
/**
* Check that the country code is filled and the admXcode are correctly filled according
* to the level. this method will be soon deprecated since the admCode were a Geonames notion and we will
* soon migrate to OSM.
*/
@Transient
public boolean isConsistentForLevel() {
if (getCountryCode() == null) {
return false;
}
if (this.getLevel() == 1 && this.getAdm1Code() == null) {
return false;
} else if (this.getLevel() == 2
&& (this.getAdm1Code() == null || this.getAdm2Code() == null)) {
return false;
} else if (this.getLevel() == 3
&& (this.getAdm1Code() == null || this.getAdm2Code() == null || this
.getAdm3Code() == null)) {
return false;
} else if (this.getLevel() == 4
&& (this.getAdm1Code() == null || this.getAdm2Code() == null
|| this.getAdm3Code() == null || this.getAdm4Code() == null)) {
return false;
}
return true;
}
private Integer level;
@IntrospectionIgnoredField
private Adm parent;
private List<Adm> children;
/**
* @return The Level Of The Adm
*/
@Column(nullable = false)
@Index(name = "admLevel")
public Integer getLevel() {
return level;
}
/**
* Set the level and Check that 1<= level<= 4. If it is not the case, throw an
* {@link IllegalArgumentException}
*
* @param level
* The Level to set
* @throws IllegalArgumentException
* If level is not correct
*/
public void setLevel(Integer level) {
if (level < 1 || level > 5) {
throw new IllegalArgumentException(
"The level of an Adm can not be " + level
+ ". it must be beetween 1 and 5");
}
this.level = level;
}
/**
* Do a double set : Add (not replace ! ) The child to the current Adm and
* set the current Adm as the Parent of the specified Child.
*
* @param child
* The child to add
* @throws IllegalArgumentException if the level of the child
* is not equals to the level of this Adm +1
*/
public void addChild(Adm child) {
if (child == null) {
throw new IllegalArgumentException("Could not add a null child to "
+ this);
}
if (child.getLevel() != getLevel() + 1) {
throw new IllegalArgumentException("a child of level "
+ child.getLevel() + " (" + child
+ ") should not be added to an Adm with level " + getLevel()
+ " : " + child + " but will be added");
}
List<Adm> currentChilds = getChildren();
if (currentChilds == null) {
currentChilds = new ArrayList<Adm>();
}
currentChilds.add(child);
this.setChildren(currentChilds);
child.setParent(this);
}
/**
* Do a double set : Add (not replace !) the children to the current Adm and
* set the current Adm as the parent of the specified Children
*
* @see #addChild(Adm)
* @param children
* The children to add
*/
public void addChildren(List<Adm> children) {
if (children != null) {
for (Adm child : children) {
addChild(child);
}
}
;
}
/**
* Return the Adms of a directly higher Level in the adm the tree structure
* @return The Adms of a directly higher Level <br>
* <b>Example</b> Returns the Adm(s) with level 2 if the current
* Adm has a level equals to 1
*/
@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "parent")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Fetch(FetchMode.SELECT)
public List<Adm> getChildren() {
return children;
}
/**
* Set the Adms of a directly higher level
*
* @param children
* the children for the current Adm
*/
// @throws IllegalArgumentException If the children are are not equals to
// the level of this Adm+1
public void setChildren(List<Adm> children) {
// TODO v2 DSL
/*
* try { if (children != null ){ for (Adm child : children){ if
* (child.getLevel()!= getLevel()+1){ throw new
* IllegalArgumentException("Could not add a child of level
* "+child.getLevel()+" to an Adm of level "+getLevel()); } } }
*/
this.children = children;
}
/**
* Returns The parent Adm in the Adm tree structure
*
* @return The parent Adm (with lower Level)
*/
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(nullable = true, name = "parent")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Index(name = "admadmindex")
public Adm getParent() {
return parent;
}
/**
* Set the parent Adm in the tree structure
*
* @param parent
* the Parent Adm to set
*/
// @throws IllegalArgumentException if the parent is not equals to the level
// of this Adm-1
public void setParent(Adm parent) {
// TODO v2 dsl
/*
* if (parent != null && parent.getLevel()!=getLevel()-1 ){ throw new
* IllegalArgumentException("Could not add a null child"); }
*/
this.parent = parent;
}
private static boolean isAdmCodeEmpty(String admCode) {
if (admCode == null || admCode.trim().length() == 0) {
return true;
}
return false;
}
/**
* Determine what should be the level of
* an Adm which have the provided codes
*
* @param adm1Code
* The Adm1Code of the Adm to test
* @param adm2Code
* The Adm2Code of the Adm to test
* @param adm3Code
* The Adm3Code of the Adm to test
* @param adm4Code
* The Adm4Code of the Adm to test
* @return the processed Level or 0 if the level can not be determine or all
* the code are null
*/
public static int getProcessedLevelFromCodes(String adm1Code,
String adm2Code, String adm3Code, String adm4Code) {
if (!isAdmCodeEmpty(adm1Code)) {
if (!isAdmCodeEmpty(adm2Code)) {
if (!isAdmCodeEmpty(adm3Code)) {
if (!isAdmCodeEmpty(adm4Code)) {
// adm1,adm2,adm3,adm4
return 4;
} else {
// adm1,adm2,adm3,null
return 3;
}
} else {
// adm1,Adm2, null..
return 2;
}
} else {
// adm1,null...
return 1;
}
} else {
// if adm1 is empty can not retrieve any Adm
return 0;
}
}
/**
* Determine what should be the level of an adm which have a the
* specified featureClass and a featureCode.<br/> e.g :
* featureClass=A and featureCode=ADM3 will return 3 .<br/> featureClass=P
* and featureCode=ADM4 will return 0 because P_ADM4 is not of ADM type. This
* method is case sensitive.
*
* @param featureClass
* The featureClass of the Adm to test
* @param featureCode
* The featureCode of the Adm to Test
* @return the Level of the Adm or 0 if the level can not be determine
*/
public static int getProcessedLevelFromFeatureClassCode(
String featureClass, String featureCode) {
if (FeatureClassCodeHelper.is_Adm(featureClass, featureCode)) {
int level = 0;
try {
level = new Integer(featureCode.substring(3)).intValue();
} catch (NumberFormatException e) {
}
return level;
} else {
return 0;
}
}
/*
* (non-Javadoc)
*
* @see com.gisgraphy.domain.geoloc.entity.GisFeature#hashCode()
*/
@Override
public int hashCode() {
final int PRIME = 31;
int result = super.hashCode();
result = PRIME * result
+ ((getAdm1Code() == null) ? 0 : getAdm1Code().hashCode());
result = PRIME * result
+ ((getAdm2Code() == null) ? 0 : getAdm2Code().hashCode());
result = PRIME * result
+ ((getAdm3Code() == null) ? 0 : getAdm3Code().hashCode());
result = PRIME * result
+ ((getAdm4Code() == null) ? 0 : getAdm4Code().hashCode());
result = PRIME * result + ((level == null) ? 0 : level.hashCode());
return result;
}
/**
* @see com.gisgraphy.domain.geoloc.entity.GisFeature#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Adm other = (Adm) obj;
if (level == null) {
if (other.level != null) {
return false;
}
} else if (!level.equals(other.level)) {
return false;
}
if (getCountryCode() == null) {
if (other.getCountryCode() != null) {
return false;
}
} else if (!getCountryCode().equals(other.getCountryCode())) {
return false;
}
if (getAdm1Code() == null) {
if (other.getAdm1Code() != null) {
return false;
}
} else if (!getAdm1Code().equals(other.getAdm1Code())) {
return false;
}
if (getAdm2Code() == null) {
if (other.getAdm2Code() != null) {
return false;
}
} else if (!getAdm2Code().equals(other.getAdm2Code())) {
return false;
}
if (getAdm3Code() == null) {
if (other.getAdm3Code() != null) {
return false;
}
} else if (!getAdm3Code().equals(other.getAdm3Code())) {
return false;
}
if (getAdm4Code() == null) {
if (other.getAdm4Code() != null) {
return false;
}
} else if (!getAdm4Code().equals(other.getAdm4Code())) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see com.gisgraphy.domain.geoloc.entity.GisFeature#toString()
*/
@Override
public String toString() {
return "Adm[" + getCountryCode() + "." + getAdm1Code() + "."
+ getAdm2Code() + "." + getAdm3Code() + "." + getAdm4Code()
+ "][level=" + getLevel() + "][" + getFeatureId() + "]["
+ getName() + "]";
}
}