/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, 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/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * MappingTableElementImpl.java
 *
 * Created on March 3, 2000, 1:11 PM
 */

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

import java.util.*;
import java.beans.PropertyVetoException;

import org.netbeans.modules.dbschema.TableElement;
import org.netbeans.modules.dbschema.ColumnElement;
import org.netbeans.modules.dbschema.util.NameUtil;

import com.sun.jdo.api.persistence.model.ModelException;
import com.sun.jdo.api.persistence.model.ModelVetoException;
import com.sun.jdo.api.persistence.model.mapping.*;
import com.sun.jdo.spi.persistence.utility.I18NHelper;

/** 
 *
 * @author Mark Munro
 * @author Rochelle Raccah
 * @version %I%
 */
public class MappingTableElementImpl extends MappingMemberElementImpl
	implements MappingTableElement
{
	private ArrayList _key;	// array of column names
	//@olsen: made transient to prevent from serializing into mapping files
	private transient ArrayList _keyObjects;	// array of ColumnElement (for runtime)
	private ArrayList _referencingKeys;	// array of MappingReferenceKeyElement
	private String _table;
	//@olsen: made transient to prevent from serializing into mapping files
	private transient TableElement _tableObject;	// for runtime

	/** Create new MappingTableElementImpl with no corresponding name or 
	 * declaring class.  This constructor should only be used for cloning and 
	 * archiving.
	 */
	public MappingTableElementImpl () 
	{
		this((String)null, null);
	}

	/** Create new MappingTableElementImpl with the corresponding name and 
	 * declaring class.
	 * @param name the name of the element
	 * @param declaringClass the class to attach to
	 */
	public MappingTableElementImpl (String name,
		MappingClassElement declaringClass) 
	{
		super(name, declaringClass);
	}

	/** Creates new MappingTableElementImpl with a corresponding 
	 * table and declaring class. 
	 * @param table table element to be used by the mapping table.
	 * @param declaringClass the class to attach to
	 */
	public MappingTableElementImpl (TableElement table, 
		MappingClassElement declaringClass) throws ModelException
	{
		this(table.toString(), declaringClass);

		// don't use setTable so as not to fire property change events
		_table = getName();
	}

	//======================= table handling ===========================

	/** Returns the name of the table element used by this mapping table.
	 * @return the table name for this mapping table
	 */
	public String getTable () { return _table; }

	/** Set the table element for this mapping table to the supplied table.
	 * @param table table element to be used by the mapping table.
	 * @exception ModelException if impossible
	 */
	public void setTable (TableElement table) throws ModelException
	{
		String old = getTable();
		String newName = table.toString();

		try
		{
			fireVetoableChange(PROP_TABLE, old, newName);
			_table = newName;
			firePropertyChange(PROP_TABLE, old, newName);
			setName(_table);

			// sync up runtime's object too: force next
			// access to getTableObject to recompute it
			_tableObject = null;
		}
		catch (PropertyVetoException e)
		{
			throw new ModelVetoException(e);
		}
	}

	/** Override method in MappingElementImpl to set the _table variable 
	 * if necessary (used for unarchiving).
	 * @param name the name
	 * @exception ModelException if impossible
	 */
	public void setName (String name) throws ModelException
	{
		super.setName(name);

		if (getTable() == null)
			_table = name;
	}

	/** Returns true if the table element used by this mapping table is equal
	 * to the supplied table.
	 * @return <code>true</code> if table elements are equal,
	 * <code>false</code> otherwise.
	 */
	public boolean isEqual (TableElement table)
	{
		return ((table != null) ? getTable().equals(table.toString()) : false);
	}

	//===================== primary key handling ===========================

	/** Returns the list of column names in the primary key for this 
	 * mapping table.
	 * @return the names of the columns in the primary key for this mapping 
	 * table
	 */
	public ArrayList getKey ()
	{
		if (_key == null)
			_key = new ArrayList();

		return _key;
	}

	/** Adds a column to the primary key of columns in this mapping table.
	 * This method should only be used to manipulate the key columns of the 
	 * primary table.  The secondary table key columns should be manipulated 
	 * using MappingReferenceKeyElement methods for pairs.
	 * @param column column element to be added
	 * @exception ModelException if impossible
	 */
	public void addKeyColumn (ColumnElement column) throws ModelException
	{
		if (column != null)
		{
			String columnName = NameUtil.getRelativeMemberName(
				column.getName().getFullName());

			if (!getKey().contains(columnName))
				addKeyColumnInternal(column);
			else
			{
				// this part was blank -- do we want an error or skip here?
			}
		}
		else
		{
			throw new ModelException(I18NHelper.getMessage(getMessages(), 
				"mapping.element.null_argument"));				// NOI18N
		}
	}

	/** Adds a column to the primary key of columns in this mapping table.
	 * This method is used internally to manipulate primary key columns 
	 * that have passed the null and duplicate tests in addKeyColumn and 
	 * secondary table key columns when pairs are being set up and ignoring 
	 * duplicates is done at the pair level.
	 * @param column column element to be added
	 * @exception ModelException if impossible
	 */
	protected void addKeyColumnInternal (ColumnElement column) throws ModelException
	{
		ArrayList key = getKey();
		String columnName = NameUtil.getRelativeMemberName(
			column.getName().getFullName());

		try
		{
			fireVetoableChange(PROP_KEY_COLUMNS, null, null);
			key.add(columnName);
			firePropertyChange(PROP_KEY_COLUMNS, null, null);

			// sync up runtime's object list too
			//@olsen: rather clear objects instead of maintaining them
			//getKeyObjects().add(column);
			_keyObjects = null;
		}
		catch (PropertyVetoException e)
		{
			throw new ModelVetoException(e);
		}
	}

	/** Removes a column from the primary key of columns in this mapping table.
	 * This method should only be used to manipulate the key columns of the 
	 * primary table.  The secondary table key columns should be manipulated 
	 * using MappingReferenceKeyElement methods for pairs.
	 * @param columnName the relative name of the column to be removed
	 * @exception ModelException if impossible
	 */
	public void removeKeyColumn (String columnName) throws ModelException
	{
		if (columnName != null)
		{
			try
			{
				fireVetoableChange(PROP_KEY_COLUMNS, null, null);

				if (!getKey().remove(columnName))
				{
					throw new ModelException(
						I18NHelper.getMessage(getMessages(), 
						"mapping.element.element_not_removed",		// NOI18N
						columnName));
				}

				firePropertyChange(PROP_KEY_COLUMNS, null, null);

				// sync up runtime's object list too
				//@olsen: rather clear objects instead of maintaining them
				//getKeyObjects().remove(column);
				_keyObjects = null;
			}
			catch (PropertyVetoException e)
			{
				throw new ModelVetoException(e);
			}
		}
	}

	//===================== reference key handling ===========================

	/** Returns the list of keys (MappingReferenceKeyElements) for this
	 * mapping table. There will be keys for foreign keys and "fake" foreign
	 * keys.
	 * @return the reference key elements for this mapping table
	 */
	public ArrayList getReferencingKeys ()
	{
		if (_referencingKeys == null)
			_referencingKeys = new ArrayList();

		return _referencingKeys;
	}

	/** Adds a referencing key to the list of keys in this mapping table.
	 * @param referencingKey referencing key element to be added
	 * @exception ModelException if impossible
	 */
	public void addReferencingKey (MappingReferenceKeyElement referencingKey)
		throws ModelException
	{
		try
		{
			fireVetoableChange(PROP_REFERENCING_KEYS, null, null);
			getReferencingKeys().add(referencingKey);
			firePropertyChange(PROP_REFERENCING_KEYS, null, null);
		}
		catch (PropertyVetoException e)
		{
			throw new ModelVetoException(e);
		}
	}

	/** Removes the referencing key for the supplied table element from list 
	 * of keys in this mapping table.
	 * @param table mapping table element for which to remove referencing keys
	 * @exception ModelException if impossible
	 */
	public void removeReference (MappingTableElement table)
		throws ModelException
	{
		if (table != null)
		{
			Iterator keyIterator = getReferencingKeys().iterator();

			while (keyIterator.hasNext())
			{
				MappingReferenceKeyElement nextKey = 
					(MappingReferenceKeyElement)keyIterator.next();

				if (nextKey.getTable().equals(table))
				{
					try
					{
						fireVetoableChange(PROP_REFERENCING_KEYS, null, null);
						keyIterator.remove();
						firePropertyChange(PROP_REFERENCING_KEYS, null, null);
					}
					catch (PropertyVetoException e)
					{
						throw new ModelVetoException(e);
					}
				}
			}
		}
		else
		{
			throw new ModelException(I18NHelper.getMessage(getMessages(), 
				"mapping.element.null_argument"));					// NOI18N
		}
	}

	//============= extra object support for runtime ========================

	/** Returns the table element (TableElement) used by this mapping
	 * table.  This method should only be used by the runtime.
	 * @return the table element for this mapping table
	 */
	public TableElement getTableObject ()
	{
		if (_tableObject == null)
		{
			String absoluteTableName = NameUtil.getAbsoluteTableName(
				getDeclaringClass().getDatabaseRoot(), _table);

			_tableObject = TableElement.forName(absoluteTableName);
		}

		return _tableObject;
	}

	/** Returns the list of columns (ColumnElements) in the primary key for
	 * this mapping table.  This method should only be used by the runtime.
	 * @return the column elements in the primary key for this mapping table
	 */
	public ArrayList getKeyObjects ()
	{
		if (_keyObjects == null)
		{
			//@olsen: calculate the key objects based on
			//        the key names as stored in _key
			//_keyObjects = new ArrayList();
			_keyObjects = MappingClassElementImpl.toColumnObjects(
				getDeclaringClass().getDatabaseRoot(), getKey());
		}

		return _keyObjects;
	}

	//=============== extra set methods needed for xml archiver ==============

	/** Set the name of the table element used by this mapping table.  This 
	 * method should only be used internally and for cloning and archiving.
	 * @param table the table name for this mapping table
	 */
	public void setTable (String table)
	{
		_table = table;
	}

	/** Set the list of column names in the primary key for this mapping 
	 * table.  This method should only be used internally and for cloning 
	 * and archiving.
	 * @param key the list of names of the columns in the primary key for 
	 * this mapping table
	 */
	public void setKey (ArrayList key) { _key = key; }

	/** Set the list of keys (MappingReferenceKeyElements) for this mapping 
	 * table.  This method should only be used internally and for cloning 
	 * and archiving.
	 * @param referencingKeys the list of reference key elements for this 
	 * mapping table
	 */
	public void setReferencingKeys (ArrayList referencingKeys)
	{
		_referencingKeys = referencingKeys;
	}

	//=============== extra method for Boston -> Pilsen conversion ============

	/** Boston to Pilsen conversion.  This method converts the absolute db 
	 * element names to relative names.  This affects the name of the 
	 * MappingTableElement itself and the column names stored in _keys.
	 * The method is recursively called for all MappingReferenceKeyElements 
	 * attached to this MappingTableElement.
	 */
	protected void stripSchemaName ()
	{
		// handle _name
		_name = NameUtil.getRelativeTableName(_name);

		// handle _table
		_table = NameUtil.getRelativeTableName(_table);

		// handle _referencingKeys
		// call method stripSchemaName on the MappingReferenceKeyElementImpl 
		// objects
		if (_referencingKeys != null)
		{
			Iterator i = _referencingKeys.iterator(); 

			while (i.hasNext())
			{
				MappingReferenceKeyElementImpl refKey = 
					(MappingReferenceKeyElementImpl)i.next();

				refKey.stripSchemaName();
			}
		}
		
		// handle _key
		if (_key != null)
		{
			// Use ListIterator here, because I want to replace the value 
			// stored in the ArrayList.  The ListIterator returned by 
			// ArrayList.listIterator() supports the set method.
			ListIterator i = _key.listIterator(); 

			while (i.hasNext())
				i.set(NameUtil.getRelativeMemberName((String)i.next()));
		}
	}
}
