/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

/*
 * PersistenceClassElement.java
 *
 * Created on February 28, 2000, 4:09 PM
 */

package com.sun.jdo.api.persistence.model.jdo;

import java.util.ArrayList;

import com.sun.jdo.api.persistence.model.Model;
import com.sun.jdo.api.persistence.model.ModelException;
import com.sun.jdo.spi.persistence.utility.I18NHelper;
import com.sun.jdo.spi.persistence.utility.StringHelper;

/* TODO:
	1. throw ModelException on add duplicate field or concurrency group (will 
	 need I18N for message)
	2. review javadoc, links for get/set methods
 */

/** 
 *
 * @author raccah
 * @version %I%
 */
public class PersistenceClassElement extends PersistenceElement 
	implements FieldElementHolder
{
	/** Constant representing jdo identity managed by the application. */
	public static final int APPLICATION_IDENTITY = 0;

	/** Constant representing jdo identity managed by the database. */
	public static final int DATABASE_IDENTITY = 1;

	/** Constant representing unmanaged jdo identity. */
	public static final int UNMANAGED_IDENTITY = 2;
    
	/** Create new PersistenceClassElement with no implementation. 
	 * This constructor should only be used for cloning and archiving.
	 */
	public PersistenceClassElement ()
	{
		this(null);
	}

	/** Create new PersistenceClassElement with the provided implementation. The
	 * implementation is responsible for storing all properties of the object.
	 * @param impl the implementation to use
	 */
	public PersistenceClassElement (PersistenceClassElement.Impl impl)
	{
		super(impl);
	}

	/** Returns the persistence class element associated with the class with  
	 * the given string name, using the given model object to look it up.
	 * @param name the fully qualified name of the desired class
	 * @param model the model object to be used to look it up
	 * @return persistence class element representing the desired class
	 */
	public static PersistenceClassElement forName (String name, Model model)
	{
		return model.getPersistenceClass(name);
	}

	/** @return implemetation factory for this class
	 */
	final Impl getClassImpl () { return (Impl)getImpl(); }

	/** Get the package name of this class element.
	 * @return the package
	 * @see PersistenceElement#getName
	 */
	public String getPackage ()
	{
		String className = getName();
		int index = className.lastIndexOf('.');

		return ((index != -1) ? className.substring(0, index) : "");	// NOI18N
	}

	/** Gets the modified flag for this persistence class.
	 * @return <code>true</code> if there have been (property) changes to this 
	 * class, <code>false</code> otherwise.
	 */
	public boolean isModified () { return getClassImpl().isModified(); }

	/** Set the modified flag for this persistence class to flag.  This is  
	 * usually set to <code>true</code> by property changes and 
	 * <code>false</code> after a save.
	 * @param flag if <code>true</code>, this class is marked as modified;
	 * if <code>false</code>, it is marked as unmodified.
	 */
	public void setModified (boolean flag) { getClassImpl().setModified(flag); }

	/** Get the object identity type of this class element.
	 * @return the object identity type, one of {@link #APPLICATION_IDENTITY}, 
	 * {@link #DATABASE_IDENTITY}, or {@link #UNMANAGED_IDENTITY}
	 */
	public int getObjectIdentityType ()
	{
		return getClassImpl().getObjectIdentityType();
	}

	/** Set the object identity type of this class element.
	 * @param type - an integer indicating the object identity type, one of:
	 * {@link #APPLICATION_IDENTITY}, {@link #DATABASE_IDENTITY}, or 
	 * {@link #UNMANAGED_IDENTITY}
	 * @exception ModelException if impossible
	 */
	public void setObjectIdentityType (int type) throws ModelException
	{
		getClassImpl().setObjectIdentityType(type);
	}

	/** Get the fully qualified name of the primary key class for this class 
	 * element.  This value is only used if <code>getObjectIdentityType</code> 
	 * returns <code>APPLICATION_IDENTITY</code>
	 * @return the fully qualified key class name, <code>null</code> if the 
	 * identity type is not managed by the application
	 * @see #setObjectIdentityType
	 * @see #APPLICATION_IDENTITY
	 * 
	 */
	public String getKeyClass () { return getClassImpl().getKeyClass(); }

	/** Set the primary key class for this class element.  For now the key 
	 * class is required to be in the same package with the same name as this 
	 * class and the suffix "Key" (any capitalization), or an inner class of 
	 * this class with the name "OID" (any capitalization).
	 * @param name - the fully qualified name which represents the primary key 
	 * class.  This value is only used if <code>getObjectIdentityType</code> 
	 * returns <code>APPLICATION_IDENTITY</code>
	 * @exception ModelException if impossible
	 * @see #setObjectIdentityType
	 * @see #APPLICATION_IDENTITY
	 */
	public void setKeyClass (String name) throws ModelException
	{
		boolean hasValue = (name != null);

		if (hasValue)
			name = name.trim();

		if (hasValue && (name.length() > 0))
		{
			String className = getName();
			boolean hasPrefix = name.startsWith(className);
			String nameSuffix = ((hasPrefix) ? 
				name.substring(className.length()) : name);

			if (!hasPrefix || (!nameSuffix.equalsIgnoreCase("Key") && 	// NOI18N
				!nameSuffix.equalsIgnoreCase(".OID")					// NOI18N
				&& !nameSuffix.equalsIgnoreCase("$OID")))				// NOI18N
			{
				throw new ModelException(I18NHelper.getMessage(getMessages(),
					"jdo.class.key_class_invalid", 						// NOI18N
					new Object[]{name, className}));
			}
		}

		getClassImpl().setKeyClass(name);
	}

	/** Set the name of this persistence element.  This method overrides 
	 * the one in PersistenceElement in order to keep the 
	 * {@link #getKeyClass key class} in sync if possible.
	 * @param name the name
	 * @exception ModelException if impossible
	 */
	public void setName (String name) throws ModelException
	{
		String oldName = getName();

		super.setName(name);

		if (!StringHelper.isEmpty(name))
		{
			String oldKeyClass = getKeyClass();

			// a rename -- set the key class too
			if ((oldKeyClass != null) && oldKeyClass.startsWith(oldName))
				setKeyClass(name + oldKeyClass.substring(oldName.length()));
		}
	}

	//================== Fields ===============================
	// PersistenceFieldElement handling, implementation of FieldElementHolder

	/** Add the supplied field to the collection of fields maintained by this
	 * holder.
	 * @param field the field to be added
	 * @exception ModelException if impossible
	 */
	public void addField (PersistenceFieldElement field) 
		throws ModelException 
	{
		addFields(new PersistenceFieldElement[]{field});
	}

	/** Add the supplied fields to the collection of fields maintained by this
	 * holder.
	 * @param fields the array of fields to be added
	 * @exception ModelException if impossible
	 */
	public void addFields(PersistenceFieldElement[] fields)
		throws ModelException 
	{
		getClassImpl().changeFields(fields, Impl.ADD);
	}

	/** Remove the supplied field from the collection of fields maintained by
	 * this holder.
	 * @param field the field to be removed
	 * @exception ModelException if impossible
	 */
	public void removeField (PersistenceFieldElement field)
		throws ModelException 
	{
		removeFields(new PersistenceFieldElement[]{field});
	}

	/** Removed the supplied fields from the collection of fields maintained
	 * by this holder.
	 * @param fields the array of fields to be removed
	 * @exception ModelException if impossible
	 */
	public void removeFields (PersistenceFieldElement[] fields)
		throws ModelException 
	{
		int i, count = ((fields != null) ? fields.length : 0);

		// first remove the fields from this class
		getClassImpl().changeFields(fields, Impl.REMOVE);

		// now remove the fields from any concurrency groups
		for (i = 0; i < count; i++)
		{
			PersistenceFieldElement field = fields[i];
			ConcurrencyGroupElement[] groups = field.getConcurrencyGroups();
			int j, groupCount = ((groups != null) ? groups.length : 0);

			for (j = 0; j < groupCount; j++)
				groups[j].removeField(field);
		}	
	}

	/** Returns the collection of fields maintained by this holder in the form
	 * of an array.
	 * @return the fields maintained by this holder
	 */
	public PersistenceFieldElement[] getFields ()
	{
		return getClassImpl().getFields();
	}

	/** Sets the collection of fields maintained by this holder to the contents
	 * of the supplied array.
	 * @param fields the fields maintained by this holder
	 * @exception ModelException if impossible
	 */
	public void setFields (PersistenceFieldElement[] fields)
		throws ModelException 
	{
		getClassImpl().changeFields(fields, Impl.SET);
	}

	/** Returns the field with the supplied name from the collection of fields
	 * maintained by this holder.
	 * @param name the name of the field to be found
	 * @return the field with the supplied name, <code>null</code> if none 
	 * exists
	 */
	public PersistenceFieldElement getField (String name)
	{
		return getClassImpl().getField(name);
	}

	/** Tests whether the supplied field is in the collection of fields 
	 * maintained by this holder.
	 * @param field the field to be tested
	 */
	public boolean containsField (PersistenceFieldElement field)
	{
		return (getClassImpl().getField(field.getName()) != null);
	}

	//================== Relationships ===============================
	// convenience methods to access RelationshipElements

	/** Returns the subset of the collection of fields which are relationahips.
	 * @return the relationship fields maintained by this holder
	 * @see PersistenceClassElement#getFields
	 */
	public RelationshipElement[] getRelationships ()
	{
		PersistenceFieldElement[] fields = getFields();
		int i, count = ((fields != null) ? fields.length : 0);
		ArrayList relationships = new ArrayList(count);

		for (i = 0; i < count; i++)
		{
			PersistenceFieldElement field = fields[i];

			if (field instanceof RelationshipElement)
				relationships.add(field);
		}
		
		count = relationships.size();

		return ((RelationshipElement[])relationships.toArray(
			new RelationshipElement[count]));
	}

	/** Returns the relationship with the supplied name from the collection of 
	 * relationships maintained by this holder.
	 * @param name the name of the relationship to be found
	 * @return the relationship with the supplied name, <code>null</code> if 
	 * none exists
	 * @see PersistenceClassElement#getRelationships
	 * @see PersistenceClassElement#getField
	*/
	public RelationshipElement getRelationship (String name)
	{
		RelationshipElement[] relationships = getRelationships();
		int i, count = ((relationships != null) ? relationships.length : 0);
		
		for (i = 0; i < count; i++)
		{
			RelationshipElement relationship = relationships[i];

			if (name.equals(relationship.getName()))
				return relationship;
		}

		return null;
	}

	//================== ConcurrencyGroups ===============================
	// ConcurrencyGroupElement handling

	/** Add the supplied group to the collection of concurrency groups for this
	 * class.
	 * @param group the concurrency group to be added
	 * @exception ModelException if impossible
	 */
	public void addConcurrencyGroup (ConcurrencyGroupElement group)
		throws ModelException 
	{
		addConcurrencyGroups(new ConcurrencyGroupElement[]{group});
	}

	/** Add the supplied groups to the collection of concurrency groups for 
	 * this class.
	 * @param groups the array of concurrency groups to be added
	 * @exception ModelException if impossible
	 */
	public void addConcurrencyGroups (ConcurrencyGroupElement[] groups)
		throws ModelException 
	{
		getClassImpl().changeConcurrencyGroups(groups, Impl.ADD);
	}

	/** Remove the supplied group from the collection of concurrency groups for 
	 * this class.
	 * @param group the concurrency group to be removed
	 * @exception ModelException if impossible
	 */
	public void removeConcurrencyGroup (ConcurrencyGroupElement group)
		throws ModelException 
	{
		removeConcurrencyGroups(new ConcurrencyGroupElement[]{group});
	}

	/** Removed the supplied groups from the collection of concurrency groups 
	 * for this class.
	 * @param groups the array of concurrency groups to be removed
	 * @exception ModelException if impossible
	 */
	public void removeConcurrencyGroups (ConcurrencyGroupElement[] groups)
		throws ModelException 
	{
		getClassImpl().changeConcurrencyGroups(groups, Impl.REMOVE);
	}

	/** Returns the collection of fields groups by this class in the form
	 * of an array.
	 * @return the concurrency groups maintained by this class
	 */
	public ConcurrencyGroupElement[] getConcurrencyGroups ()
	{
		return getClassImpl().getConcurrencyGroups();
	}

	/** Sets the collection of concurrency groups maintained by this class to 
	 * the contents of the supplied array.
	 * @param groups the concurrency groups maintained by this holder
	 * @exception ModelException if impossible
	 */
	public void setConcurrencyGroups (ConcurrencyGroupElement[] groups)
		throws ModelException 
	{
		getClassImpl().changeConcurrencyGroups(groups, Impl.SET);
	}

	/** Returns the concurrency group with the supplied name from the 
	 * collection of groups maintained by this class.
	 * @param name the name of the concurrency group to be found
	 * @return the concurrency group with the supplied name, <code>null</code>
	 * if none exists
	 */
	public ConcurrencyGroupElement getConcurrencyGroup (String name)
	{
		return getClassImpl().getConcurrencyGroup(name);
	}

	/** Tests whether the supplied group is in the collection of groups 
	 * maintained by this class.
	 * @param group the concurrency group to be tested
	 */
	public boolean containsConcurrencyGroup (ConcurrencyGroupElement group)
	{
		return (getClassImpl().getConcurrencyGroup(group.getName()) != null);
	}

	// for later
	/* get/setFieldInheritanceFlag - This would allow a user to choose whether
	 * db inheritance is used (field persistence and behavior inherited from
	 * persistent superclass or not) or the list of the superclass fields could
	 * be configured for persistence in the subclass. 
	 * - applies only to fields and relationships
	 */

	/** Pluggable implementation of the storage of class element properties.
	 * @see PersistenceClassElement#PersistenceClassElement
	 */
	public interface Impl extends PersistenceElement.Impl
	{
		/** Gets the modified flag for this persistence class.
		 * @return <code>true</code> if there have been (property) changes to  
		 * this class, <code>false</code> otherwise.
		 */
		public boolean isModified ();

		/** Set the modified flag for this persistence class to flag.  This is  
		 * usually set to <code>true</code> by property changes and 
		 * <code>false</code> after a save.
		 * @param flag if <code>true</code>, this class is marked as modified;
		 * if <code>false</code>, it is marked as unmodified.
		 */
		public void setModified (boolean flag);

		/** Get the object identity type of this class element.
		 * @return the object identity type, one of 
		 * {@link #APPLICATION_IDENTITY}, {@link #DATABASE_IDENTITY}, or 
		 * {@link #UNMANAGED_IDENTITY}
		 */
		public int getObjectIdentityType ();

		/** Set the object identity type of this class element.
		 * @param type - an integer indicating the object identity type, one of:
		 * {@link #APPLICATION_IDENTITY}, {@link #DATABASE_IDENTITY}, or 
		 * {@link #UNMANAGED_IDENTITY}
		 * @exception ModelException if impossible
		 */
		public void setObjectIdentityType (int type) throws ModelException;

		/** Get the fully qualified name of the primary key class for this class 
		 * element.  This value is only used if 
		 * <code>getObjectIdentityType</code> returns 
		 * <code>APPLICATION_IDENTITY</code>
		 * @return the fully qualified key class name, <code>null</code> if the 
		 * identity type is not managed by the application
		 * @see #setObjectIdentityType
		 * @see #APPLICATION_IDENTITY
		 * 
		 */
		public String getKeyClass ();

		/** Set the primary key class for this class element.
		 * @param name - the fully qualified name which represents the primary  
		 * key class.  This value is only used if 
		 * <code>getObjectIdentityType</code> returns 
		 * <code>APPLICATION_IDENTITY</code>
		 * @exception ModelException if impossible
		 * @see #setObjectIdentityType
		 * @see #APPLICATION_IDENTITY
		 */
		public void setKeyClass (String name) throws ModelException;

		/** Change the set of fields.
		 * @param fields the new fields
		 * @param action {@link #ADD}, {@link #REMOVE}, or {@link #SET}
		 * @exception ModelException if impossible
		 */
		public void changeFields (PersistenceFieldElement[] fields, int action)
			throws ModelException;

		/** Get all fields.
		 * @return the fields
		 */
		public PersistenceFieldElement[] getFields ();

		/** Find a field by name.
		 * @param name the name to match
		 * @return the field, or <code>null</code> if it does not exist
		 */
		public PersistenceFieldElement getField (String name);

		/** Change the set of concurrency groups.
		 * @param groups the new concurrency groups
		 * @param action {@link #ADD}, {@link #REMOVE}, or {@link #SET}
		 * @exception ModelException if impossible
		 */
		public void changeConcurrencyGroups (ConcurrencyGroupElement[] groups, 
			int action) throws ModelException;

		/** Get all concurrency groups.
		 * @return the concurrency groups
		 */
		public ConcurrencyGroupElement[] getConcurrencyGroups ();

		/** Find a concurrency group by name.
		 * @param name the name to match
		 * @return the concurrency group, or <code>null</code> if it does not
		 * exist 
		 */
		public ConcurrencyGroupElement getConcurrencyGroup (String name);
	}
}
