/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.persistence;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import com.servoy.base.persistence.constants.IContentSpecConstantsBase;
/**
* The <code>ContentSpec</code> class is a inmem representation of the servoy_content_spec table which can be accessed in core.
*/
public class ContentSpec
{
public static final Integer MINUS_ONE = IContentSpecConstantsBase.MINUS_ONE;
public static final Integer ZERO = IContentSpecConstantsBase.ZERO;
public static final Object JAVA_DEFAULT_VALUE = IContentSpecConstantsBase.JAVA_DEFAULT_VALUE;
public ContentSpec()
{
contentSpecElements = new TreeMap<Integer, Element>(new Comparator<Integer>()
{
// sort by content id descending
public int compare(Integer o1, Integer o2)
{
return o2.compareTo(o1);
}
});
}
/**
* The <code>Element</code> class represents one row in the servoy_content_spec table, which represents a property of an object.
*/
public class Element
{
/**
* The content id, the primary key into the content spec table.
*/
private final int contentID;
/**
* The object type id, which indicates what object type this element is a property of.
*/
private final int objectTypeID;
/**
* The name of this property.
*/
private final String name;
/**
* The type id of this property, which indicates what type the value of this property has.
*/
private final int typeID;
/**
* Indicates whether or not this property is meta data. Meta data properties are stored in revision 0 of an object.
*/
private boolean isMetaData;
/**
* Indicates whether or not this property is required.
*/
private boolean isRequired;
/**
* Indicate if content item is deprecated; 0 - not deprecated; -1 deprecated with no replacement specified; >0 content id replacement
*/
private int deprecatedMoveContentID = 0;
/**
* The default_textual_classvalue of this property.
*/
private Object defaultclassvalue;
protected Element(int contentID, int objectTypeID, String name, int typeID)
{
this(contentID, objectTypeID, name, typeID, ContentSpec.JAVA_DEFAULT_VALUE);
}
/**
* Constructs a new element with the specified values.
*
* @param contentID the content id
* @param objectTypeID the object type id
* @param name the property name
* @param typeID the type id
* @param defaultValue the value to fill by default
*/
public Element(int contentID, int objectTypeID, String name, int typeID, Object defaultValue)
{
contentSpecElements.put(new Integer(contentID), this);
this.contentID = contentID;
this.objectTypeID = objectTypeID;
this.name = name;
this.typeID = typeID;
if (defaultValue == JAVA_DEFAULT_VALUE)
{
this.defaultclassvalue = getJavaClassMemberDefaultValue(typeID);
}
else if (defaultValue != null)
{
this.defaultclassvalue = defaultValue;
}
}
public int getContentID()
{
return contentID;
}
public int getObjectTypeID()
{
return objectTypeID;
}
public String getName()
{
return name;
}
public int getTypeID()
{
return typeID;
}
public boolean isMetaData()
{
return isMetaData;
}
public void flagAsMetaData()
{
isMetaData = true;
}
public boolean isRequired()
{
return isRequired;
}
public void flagAsRequired()
{
isRequired = true;
}
public String getDefaultTextualClassValue()
{
return defaultclassvalue == null ? null : defaultclassvalue.toString();
}
public Object getDefaultClassValue()
{
return defaultclassvalue;
}
public boolean isDeprecated()
{
return deprecatedMoveContentID != 0;
}
public void flagAsDeprecated()
{
flagAsDeprecated(-1);
}
public void flagAsDeprecated(int moveContentID)
{
this.deprecatedMoveContentID = moveContentID;
}
public int getDeprecatedMoveContentID()
{
return deprecatedMoveContentID;
}
}
public static Object getJavaClassMemberDefaultValue(int type_id)
{
Object retval = null;
switch (type_id)
{
case IRepository.DIMENSION :
retval = null;
break;
case IRepository.MEDIA :
case IRepository.ELEMENTS :
case IRepository.INTEGER :
case IRepository.BLOBS :
retval = ZERO;
break;
case IRepository.BORDER :
retval = null;
break;
case IRepository.COLOR :
retval = null;
break;
case IRepository.POINT :
retval = null;
break;
case IRepository.INSETS :
retval = null;
break;
case IRepository.STRING :
case IRepository.STYLES :
case IRepository.TEMPLATES :
case IRepository.SERVERS :
case IRepository.TABLES :
case IRepository.DATASOURCES :
retval = null;
break;
case IRepository.FONT :
retval = null;
break;
case IRepository.BOOLEAN :
retval = Boolean.FALSE;
break;
default :
throw new IllegalArgumentException("Type with id: " + type_id + " does not exist"); //$NON-NLS-2$
}
return retval;
}
/**
* The content spec elements mapped by their content id.
*/
private TreeMap<Integer, Element> contentSpecElements = null;
/**
* A map containing all object types by object type id to maps of mapping the property name of each of their properties to their respective content spec
* element.
*/
private Map<Integer, LinkedHashMap<String, Element>> objectTypes = null;
/**
* Get a content spec element by content_id.
*
* @param id the content id
*
* @return the content spec element with the specified content id
*
* @throws RepositoryException if the content spec is not loaded or no element with the specified id exists
*/
public Element getElementByContentID(int content_id) throws RepositoryException
{
if (contentSpecElements == null)
{
throw new RepositoryException("Content spec uninitialized");
}
Element element = contentSpecElements.get(new Integer(content_id));
if (element == null)
{
throw new RepositoryException("Content spec element with id " + content_id + " does not exist"); //$NON-NLS-2$
}
return element;
}
/**
* Gets a property for the specified object type by name and returns the meta data as a content spec element.
*
* @param objectTypeID the object type id
* @param name the name of the property
*
* @return the element of the specified object type with the specified name or <code>null</code> if no such property of the object type exists.
*
*/
public Element getPropertyForObjectTypeByName(int objectTypeID, String name)
{
if (objectTypes == null)
{
fillObjectTypesMap();
}
Map<String, Element> elements = objectTypes.get(new Integer(objectTypeID));
return elements != null ? elements.get(name) : null;
}
/**
* Gets all properties for the specified object type.
*
* @param objectTypeID the object type id
*
* @return an iterator which iterates over all elements belonging to the specified object type, or an iterator over an empty list if no such object type
* exists.
*
*/
public Iterator<Element> getPropertiesForObjectType(int objectTypeID)
{
if (objectTypes == null)
{
fillObjectTypesMap();
}
Map<String, Element> elements = objectTypes.get(new Integer(objectTypeID));
return elements != null ? elements.values().iterator() : Collections.<Element> emptyList().iterator();
}
/**
* Gets all the properties of all object types. sorted by content id (descending) to make sure deprecated properties are return after their replacement
*/
public Iterator<Element> getElements()
{
return contentSpecElements.values().iterator();
}
/**
* @param typeID
* @param content_id
* @param property_value
*/
public boolean mustUpdateValue(int content_id, String property_value) throws RepositoryException
{
Element element = getElementByContentID(content_id);
String default_value = element.getDefaultTextualClassValue();
if (default_value == property_value) return false;
if (default_value != null && (property_value == null || property_value.trim().length() == 0))
{
// FIXME: The default value for this property is not null, but
// FIXME: the property value is. Since we cannot save null values
// FIXME: in the database, nor values consisting of only white space,
// FIXME: don't save the value. This will cause a subsequent load
// FIXME: to restore the value to the default value.
// FIXME: Move this purely to save & load methods of properties in
// FIXME: the database, to avoid it on the Java side.
return false;
}
if (default_value == null) return true;
return (!default_value.equals(property_value));
}
private void fillObjectTypesMap()
{
objectTypes = new HashMap<Integer, LinkedHashMap<String, Element>>();
// Create the object types hash map, which for each object type
// which has properties maps contains a map of property names to
// content spec elements.
Iterator<Element> iterator = getElements(); // sorted by content id desc
while (iterator.hasNext())
{
Element element = iterator.next();
Integer objectTypeID = new Integer(element.getObjectTypeID());
LinkedHashMap<String, Element> elements = objectTypes.get(objectTypeID);
if (elements == null)
{
// use LinkedHashMap to preserve sorting
elements = new LinkedHashMap<String, Element>();
objectTypes.put(objectTypeID, elements);
}
elements.put(element.getName(), element);
}
}
}