package com.idega.block.entity.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import com.idega.data.EntityAttribute;
import com.idega.data.EntityRepresentation;
import com.idega.data.GenericEntity;
import com.idega.data.IDOEntity;
import com.idega.idegaweb.IWResourceBundle;
import com.idega.repository.data.RefactorClassRegistry;
/**
*@author <a href="mailto:thomas@idega.is">Thomas Hilbig</a>
*@version 1.0
*/
public class EntityPath {
public final static String SERIALIZATION_DELIMITER = "@";
public final static String SERIALIZATION_NEXT_ENTITY_PATH_DELIMITER = "#";
public final static String SHORT_KEY_NEXT_ENTITY_PATH_DELIMITER = ":";
public final static String SHORT_KEY_ENTITY_NAME_COLUMN_NAME_DELIMITER = ".";
public final static String SHORT_KEY_COLUMN_NAME_DELIMITER = "|";
public final static String DESCRIPTION_NEXT_ENTITY_PATH_DELIMITER = "+";
public final static String DESCRIPTION_COLUMN_DELIMITER = ".";
private static int DEFAULT_SEARCH_DEPTH = 2;
private Class sourceEntity = null;
private Class targetEntity = null;
private Class classOfValue = null;
private ArrayList pathToEntity = new ArrayList();
private EntityPath nextEntityPath = null;
public EntityPath(Class sourceEntity) {
this.sourceEntity = sourceEntity;
}
public static SortedMap getInstances(Class sourceEntity) {
return getInstances(DEFAULT_SEARCH_DEPTH, sourceEntity);
}
public static SortedMap getInstances(int maximumSearchDepth, Class sourceEntity) {
return getAllEntityPathes( new EntityPath(sourceEntity) , sourceEntity, 0 , maximumSearchDepth);
}
public static EntityPath getInstance(String serialization) throws ClassNotFoundException {
EntityPath lastEntityPath = null;
EntityPath firstEntityPath = null;
StringTokenizer entityPathTokenizer = new StringTokenizer(serialization, SERIALIZATION_NEXT_ENTITY_PATH_DELIMITER);
while (entityPathTokenizer.hasMoreElements()) {
// build single entity path
String singleEntityPathSerialization = entityPathTokenizer.nextToken();
StringTokenizer tokenizer = new StringTokenizer(singleEntityPathSerialization, SERIALIZATION_DELIMITER);
List columnNames = new ArrayList();
while (tokenizer.hasMoreElements()) {
String columnName = tokenizer.nextToken();
columnNames.add(columnName);
}
int size;
if ((size = columnNames.size()) < 2) {
throw new ClassNotFoundException("EntityPath could not be created from string: " + serialization);
}
String sourceName = (String) columnNames.get(0);
Class sourceEntity = RefactorClassRegistry.forName(sourceName);
// remove target name
columnNames.remove(size-1);
EntityPath createdEntityPath;
if (size == 2) {
createdEntityPath = new EntityPath(sourceEntity);
}
else {
createdEntityPath = getEntityPath(new EntityPath(sourceEntity) , sourceEntity, 0 , columnNames);
}
if (lastEntityPath == null) {
// remember the first entity path
firstEntityPath = createdEntityPath;
}
else {
// add the child
lastEntityPath.add(createdEntityPath);
}
lastEntityPath = createdEntityPath;
}
return firstEntityPath;
}
public Class getSourceEntityClass() {
return this.sourceEntity;
}
public void add(String aColumnName) {
this.pathToEntity.add(aColumnName);
}
public void add(EntityPath aPath) {
if (this.nextEntityPath == null) {
this.nextEntityPath = aPath;
}
else {
this.nextEntityPath.add(aPath);
}
}
/** @return my next entityPath or null */
public EntityPath getNextEntityPath() {
return this.nextEntityPath;
}
private void setTargetEntity(Class targetEntityClass) {
this.targetEntity = targetEntityClass;
}
private void setClassOfValue(Class classOfValue) {
this.classOfValue = classOfValue;
}
public String getSerialization() {
StringBuffer serialization = new StringBuffer();
getSerialization(serialization);
return serialization.toString();
}
private void getSerialization(StringBuffer stringBuffer) {
StringBuffer serialization = new StringBuffer();
serialization
.append(this.sourceEntity.getName())
.append(SERIALIZATION_DELIMITER);
Iterator iterator = this.pathToEntity.iterator();
while (iterator.hasNext()) {
serialization
.append((String) iterator.next())
.append(SERIALIZATION_DELIMITER);
}
serialization.append(this.targetEntity.getName());
stringBuffer.append(serialization);
if (this.nextEntityPath == null) {
return;
}
// put a delimiter between the two objects
stringBuffer.append(SERIALIZATION_NEXT_ENTITY_PATH_DELIMITER);
this.nextEntityPath.getSerialization(stringBuffer);
}
/** Gets a shortkey that is used as key in the hash map
* that is returned by the method getInstances
* @return shortKey e.g. "com.idega.user.data.User.MIDDLE_NAME"
* or e.g. "com.idega.user.data.User.FIRST_NAME:com.idega.user.data.User.Last_NAME"
* (if this path consists of two pathes)
*/
public String getShortKey() {
StringBuffer buffer = new StringBuffer();
getShortKey(buffer, true);
return buffer.toString();
}
/** Gets only the section of the short key of this instance */
public String getShortKeySection() {
StringBuffer buffer = new StringBuffer();
getShortKey(buffer, false);
return buffer.toString();
}
private void getShortKey(StringBuffer buffer, boolean getCompletePath) {
int lastIndex = this.pathToEntity.size() - 1;
if (lastIndex < 0) {
return;
}
// add name of the target entity
buffer
.append(this.targetEntity.getName())
.append(SHORT_KEY_ENTITY_NAME_COLUMN_NAME_DELIMITER);
// add all column names of this entity path
Iterator iterator = this.pathToEntity.iterator();
boolean notTheFirstTime = false;
while (iterator.hasNext()) {
if (notTheFirstTime) {
// append this delimiter not the very first time
buffer.append(SHORT_KEY_COLUMN_NAME_DELIMITER);
}
else {
notTheFirstTime = true;
}
String columnName = (String) iterator.next();
buffer.append(columnName);
}
// go to the next entity path
if (this.nextEntityPath != null && getCompletePath) {
buffer.append(SHORT_KEY_NEXT_ENTITY_PATH_DELIMITER);
this.nextEntityPath.getShortKey(buffer, true);
}
}
/** Gets the original description, do get a localized description use
* getLocalizedDescription.
* @return a description of the value that is representated by this instance
*/
public String getDescription() {
StringBuffer buffer = new StringBuffer();
getDescription(buffer);
return buffer.toString();
}
private void getDescription(StringBuffer buffer) {
if (this.targetEntity == null) {
return;
}
// add target name (without package path)
String targetClassName = this.targetEntity.getName();
String classNameWithoutPath = targetClassName.substring(targetClassName.lastIndexOf(".") + 1);
buffer.append(classNameWithoutPath);
// buffer.append('.').append(pathToEntity.get(pathToEntity.size()-1));
// add all column names of this entity path
Iterator iterator = this.pathToEntity.iterator();
while (iterator.hasNext()) {
buffer.append(DESCRIPTION_COLUMN_DELIMITER);
String columnName = (String) iterator.next();
buffer.append(columnName);
}
// add describtion of the next entity path
if (this.nextEntityPath != null) {
buffer.append(DESCRIPTION_NEXT_ENTITY_PATH_DELIMITER);
this.nextEntityPath.getDescription(buffer);
}
}
/** Gets the localized description.
* @param resourceBundle the resourceBundle that serves as a source for the localization
* @return String a localized description of the value that is representated by this
* instance
*/
public String getLocalizedDescription(IWResourceBundle resourceBundle) {
String description = getDescription();
return resourceBundle.getLocalizedString( description, description);
}
/** Returns an object or null.
* This is the value of this instance without considering the next entity pathes.
* Do not use this method in convertes, use rather getValues(EntityRepresentation).
* @return the value of this entity path without the next entity path
*/
public Object getValue(EntityRepresentation entity) {
Iterator iterator = this.pathToEntity.iterator();
// start
Object value = entity;
// sometimes you can not go down the complete path because some columns are null
while (iterator.hasNext() && value != null ) {
String currentColumnName = (String) iterator.next();
// that is a little bit tricky: the column can be
// a reference to another entity or a real value like a String, Integer, Date and so on.
// If the path is well-defined, there will be no CastException, because the very last value
// is not casted to EntityRepresentation.
value = ((EntityRepresentation) value).getColumnValue(currentColumnName);
}
return value;
}
/** @return values of this entity path plus all the values of the next
* entity pathes
*/
public List getValues(EntityRepresentation entity) {
List list = new ArrayList();
getValues(list, entity);
return list;
}
/** Returns the class of the value or if not detectable null (e.g. if this is an artificial path).
* This is the class of the value of this instance without considering the next entity pathes.
* Do not use this method in convertes, use rather getClassesOfValues(EntityRepresentation).
* @return the class of the value or null if the class is not detectable
*/
public Class getClassOfValue() {
return this.classOfValue;
}
public List getClassesOfValues() {
List list = new ArrayList();
getClassesOfValues(list);
return list;
}
public Object clone() {
EntityPath entityPath = new EntityPath(this.sourceEntity);
entityPath.setTargetEntity(this.targetEntity);
entityPath.setClassOfValue(this.classOfValue);
Iterator iterator = this.pathToEntity.iterator();
while (iterator.hasNext()) {
entityPath.add((String) iterator.next());
}
if (this.nextEntityPath != null) {
EntityPath nextClone = (EntityPath) this.nextEntityPath.clone();
entityPath.add(nextClone);
}
return entityPath;
}
private void getClassesOfValues(List list) {
list.add(getClassOfValue());
if (this.nextEntityPath == null) {
return;
}
this.nextEntityPath.getClassesOfValues(list);
}
private void getValues(List list, EntityRepresentation entity) {
list.add(getValue(entity));
if (this.nextEntityPath == null) {
return;
}
this.nextEntityPath.getValues(list, entity);
}
private static SortedMap getAllEntityPathes
( EntityPath motherEntityPath,
Class currentEntityClass,
int currentLayer,
int maximumSearchDepth) {
// entering a new layer....
currentLayer++;
//TODO thomas: change this by using IDOEntity and EntityDefinition classes
// entering a new layer....
GenericEntity entity = (GenericEntity) getEntity(currentEntityClass);
if (entity == null) {
return new TreeMap();
}
Collection coll = entity.getAttributes();
Iterator iterator = coll.iterator();
TreeMap pathes = new TreeMap();
while (iterator.hasNext()) {
EntityAttribute attribute = (EntityAttribute) iterator.next();
Class anEntityClass = attribute.getRelationShipClass();
EntityPath currentPath = (EntityPath) motherEntityPath.clone();
currentPath.add(attribute.getColumnName());
if (anEntityClass == null) {
// finished!
currentPath.setClassOfValue(attribute.getStorageClass());
currentPath.setTargetEntity(currentEntityClass);
// use shortKey as key for the returned HashMap
pathes.put(currentPath.getShortKey(), currentPath);
}
else if (currentLayer < maximumSearchDepth) {
// go further!
SortedMap PathesOfChild = EntityPath.getAllEntityPathes(currentPath, anEntityClass, currentLayer, maximumSearchDepth);
pathes.putAll(PathesOfChild);
}
}
return pathes;
}
private static EntityPath getEntityPath
( EntityPath motherEntityPath,
Class currentEntityClass,
int currentLayer,
List columnNames) {
//TODO thomas: change this by using IDOEntity and EntityDefinition classes
// entering a new layer....
currentLayer++;
GenericEntity entity = (GenericEntity) getEntity(currentEntityClass);
if (entity == null) {
return null;
}
EntityAttribute attribute = entity.getAttribute((String) columnNames.get(currentLayer));
Class anEntityClass = attribute.getRelationShipClass();
motherEntityPath.add(attribute.getColumnName());
if (anEntityClass == null) {
// finished!
motherEntityPath.setClassOfValue(attribute.getStorageClass());
motherEntityPath.setTargetEntity(currentEntityClass);
}
else if (currentLayer < columnNames.size()) {
// go further!
motherEntityPath = EntityPath.getEntityPath(motherEntityPath, anEntityClass, currentLayer, columnNames);
}
return motherEntityPath;
}
private static IDOEntity getEntity(Class currentEntityClass) {
return GenericEntity.getStaticInstanceIDO(currentEntityClass);
}
}