/*
 NeoDatis ODB : Native Object Database (odb.info@neodatis.org)
 Copyright (C) 2007 NeoDatis Inc. http://www.neodatis.org

 "This file is part of the NeoDatis ODB open source object database".

 NeoDatis ODB 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.

 NeoDatis ODB 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.neodatis.odb.core.layers.layer2.meta;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.neodatis.odb.ODBRuntimeException;
import org.neodatis.odb.OID;
import org.neodatis.odb.core.NeoDatisError;
import org.neodatis.tool.wrappers.OdbTime;
import org.neodatis.tool.wrappers.list.IOdbList;
import org.neodatis.tool.wrappers.list.OdbArrayList;
import org.neodatis.tool.wrappers.map.OdbHashMap;

/**
 * A meta representation of a class
 * 
 * @author osmadja
 * 
 */
public class ClassInfo implements Serializable {

	/** Constant used for the classCategory variable to indicate a system class */
	public static final byte CATEGORY_SYSTEM_CLASS = 1;

	/** Constant used for the classCategory variable to indicate a user class */
	public static final byte CATEGORY_USER_CLASS = 2;

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/** To specify the type of the class : system class or user class */
	private byte classCategory;

	/** The full class name with package */
	private String fullClassName;
	/** Extra info of the class - no used in java version */
	private String extraInfo;

	private IOdbList attributes;

	/**
	 * This map is redundant with the field 'attributes', but it is to enable
	 * fast access to attributes by name TODO use only the map and remove list
	 * key=attribute name, key =ClassInfoattribute
	 */
	private Map attributesByName;
	/**
	 * This map is redundant with the field 'attributes', but it is to enable
	 * fast access to attributes by id key=attribute Id(Integer), key
	 * =ClassAttributeInfo
	 */
	private Map attributesById;

	/**
	 * To keep session original numbers, original number of committed
	 * objects,first and last object position
	 */
	private CommittedCIZoneInfo original;

	/**
	 * To keep session numbers, number of committed objects,first and last
	 * object position
	 */
	private CommittedCIZoneInfo committed;

	/**
	 * To keep session uncommitted numbers, number of uncommitted objects,first
	 * and last object position
	 */
	private CIZoneInfo uncommitted;

	/** Physical location of this class in the file (in byte) */
	private long position;

	private OID id;

	/** Where is the previous class. -1, if it does not exist */
	private OID previousClassOID;

	/** Where is the next class, -1, if it does not exist */
	private OID nextClassOID;

	/** Where starts the block of attributes definition of this class ? */
	private long attributesDefinitionPosition;

	/** The size (in bytes) of the class block */
	private int blockSize;

	/** Infos about the last object of this class */
	private ObjectInfoHeader lastObjectInfoHeader;

	/**
	 * The max id is used to give a unique id for each attribute and allow
	 * refactoring like new field and/or removal
	 */
	private int maxAttributeId;

	private IOdbList indexes;

	private transient IOdbList history;

	public ClassInfo() {
		this.original = new CommittedCIZoneInfo(this, null, null, 0);
		this.committed = new CommittedCIZoneInfo(this, null, null, 0);
		this.uncommitted = new CIZoneInfo(this, null, null, 0);
		this.previousClassOID = null;
		this.nextClassOID = null;
		this.blockSize = -1;
		this.position = -1;
		this.maxAttributeId = -1;
		this.classCategory = CATEGORY_USER_CLASS;
		this.history = new OdbArrayList();
	}

	public ClassInfo(String className) {
		this(className, "", null);
	}

	public ClassInfo(String className, String extraInfo) {
		this(className, extraInfo, null);
	}

	protected ClassInfo(String fullClassName, String extraInfo, IOdbList attributes) {
		this();
		this.fullClassName = fullClassName;
		this.extraInfo = extraInfo;
		this.attributes = attributes;
		this.attributesByName = new OdbHashMap();
		this.attributesById = new OdbHashMap();
		if (attributes != null) {
			fillAttributesMap();
		}
		this.maxAttributeId = (attributes == null ? 1 : attributes.size() + 1);
	}

	private void fillAttributesMap() {
		ClassAttributeInfo cai = null;
		if (attributesByName == null) {
			attributesByName = new OdbHashMap();
			attributesById = new OdbHashMap();
		} else {
			// attributesMap.clear();
		}
		for (int i = 0; i < attributes.size(); i++) {
			cai = attributes.get(i);
			attributesByName.put(cai.getName(), cai);
			attributesById.put(new Integer(cai.getId()), cai);
		}
	}

	public boolean equals(Object obj) {
		if (obj == null || obj.getClass() != ClassInfo.class) {
			return false;
		}
		ClassInfo classInfo = (ClassInfo) obj;
		return classInfo.fullClassName.equals(fullClassName);
	}

	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(" [ ").append(fullClassName).append(" - id=").append(id);
		buffer.append(" - previousClass=").append(previousClassOID).append(" - nextClass=").append(nextClassOID).append(" - attributes=(");
		// buffer.append(" | position=").append(position);
		// buffer.append(" | class=").append(className).append(" | attributes=[");

		if (attributes != null) {
			for (int i = 0; i < attributes.size(); i++) {
				ClassAttributeInfo cai = (ClassAttributeInfo) attributes.get(i);
				buffer.append(cai.getName()).append(",");
			}
		} else {
			buffer.append("not yet defined");
		}
		buffer.append(") ]");

		/*
		 * buffer.append("- nbobjects=(");
		 * buffer.append(committed.getNbObjects()
		 * ).append(",").append(uncommitted
		 * .getNbObjects()).append(") | first object oid.=(").append(
		 * committed.first
		 * ).append(",").append(uncommitted.first).append(") | last object oid.=("
		 * ).append(committed.last).append(",")
		 * .append(uncommitted.last).append(")");
		 * buffer.append(" | definition=")
		 * .append(attributesDefinitionPosition);
		 * buffer.append(" | blockSize=").append(blockSize);
		 */
		return buffer.toString();
	}

	public IOdbList getAttributes() {
		return attributes;
	}

	public void setAttributes(IOdbList attributes) {
		this.attributes = attributes;
		// We must always keep the original maxAttribute ID. this is to enable refactoring removing fields
		this.maxAttributeId = Math.max(this.maxAttributeId,attributes.size());
		fillAttributesMap();
	}

	public CommittedCIZoneInfo getCommitedZoneInfo() {
		return committed;
	}

	public long getAttributesDefinitionPosition() {
		return attributesDefinitionPosition;
	}

	public void setAttributesDefinitionPosition(long definitionPosition) {
		this.attributesDefinitionPosition = definitionPosition;
	}

	public OID getNextClassOID() {
		return nextClassOID;
	}

	public void setNextClassOID(OID nextClassOID) {
		this.nextClassOID = nextClassOID;
	}

	public OID getPreviousClassOID() {
		return previousClassOID;
	}

	public void setPreviousClassOID(OID previousClassOID) {
		this.previousClassOID = previousClassOID;
	}

	public long getPosition() {
		return position;
	}

	public void setPosition(long position) {
		this.position = position;
	}

	public int getBlockSize() {
		return blockSize;
	}

	public void setBlockSize(int blockSize) {
		this.blockSize = blockSize;
	}

	/**
	 * @return the fullClassName
	 */
	public String getFullClassName() {
		return fullClassName;
	}

	/**
	 * This method could be optimized, but it is only on Class creation, one
	 * time in the database life time... This is used to get all (non native)
	 * attributes a class info have to store them in the meta model before
	 * storing the class itself
	 * 
	 * @return
	 */
	public IOdbList getAllNonNativeAttributes() {
		IOdbList result = new OdbArrayList(attributes.size());
		ClassAttributeInfo cai = null;
		for (int i = 0; i < attributes.size(); i++) {
			cai = attributes.get(i);
			if (!cai.isNative() || cai.getAttributeType().isEnum()) {
				result.add(cai);
			} else if (cai.getAttributeType().isArray() && !cai.getAttributeType().getSubType().isNative()) {
				result.add(new ClassAttributeInfo(-1, "subtype", cai.getAttributeType().getSubType().getName(), null));
			}
		}
		return result;
	}

	public OID getId() {
		return id;
	}

	public void setId(OID id) {
		this.id = id;
	}

	public ClassAttributeInfo getAttributeInfoFromId(int id) {
		return attributesById.get(new Integer(id));
	}

	public int getAttributeId(String name) {
		ClassAttributeInfo cai = attributesByName.get(name);
		if (cai == null) {
			return -1;
		}
		return cai.getId();
	}

	public ClassAttributeInfo getAttributeInfoFromName(String name) {
		return attributesByName.get(name);
	}

	public ClassAttributeInfo getAttributeInfo(int index) {
		return attributes.get(index);
	}

	public int getMaxAttributeId() {
		return maxAttributeId;
	}

	public void setMaxAttributeId(int maxAttributeId) {
		this.maxAttributeId = maxAttributeId;
	}

	public ClassInfoCompareResult extractDifferences(ClassInfo newCI, boolean update) {
		String attributeName = null;
		ClassAttributeInfo cai1 = null;
		ClassAttributeInfo cai2 = null;
		ClassInfoCompareResult result = new ClassInfoCompareResult(getFullClassName());
		boolean isCompatible = true;
		IOdbList attributesToRemove = new OdbArrayList(10);
		IOdbList attributesToAdd = new OdbArrayList(10);
		int nbAttributes = attributes.size();
		for (int id = 0; id < nbAttributes; id++) {
			// !!!WARNING : ID start with 1 and not 0
			cai1 = attributes.get(id);
			if (cai1 == null) {
				continue;
			}
			attributeName = cai1.getName();
			cai2 = newCI.getAttributeInfoFromId(cai1.getId());
			if (cai2 == null) {
				result.addCompatibleChange("Field '" + attributeName + "' (type="+cai1.getFullClassname() +") has been removed");
				if (update) {
					// Simply remove the attribute from meta-model
					attributesToRemove.add(cai1);
				}
			} else {
				if (!ODBType.typesAreCompatible(cai1.getAttributeType(), cai2.getAttributeType())) {
					result.addIncompatibleChange("Type of Field '" + attributeName + "' has changed : old='" + cai1.getFullClassname()
							+ "' - new='" + cai2.getFullClassname() + "'");
					isCompatible = false;
				}
			}
		}
		int nbNewAttributes = newCI.attributes.size();
		for (int id = 0; id < nbNewAttributes; id++) {
			// !!!WARNING : ID start with 1 and not 0
			cai2 = newCI.attributes.get(id);
			if (cai2 == null) {
				continue;
			}
			attributeName = cai2.getName();
			cai1 = getAttributeInfoFromId(cai2.getId());
			if (cai1 == null) {
				result.addCompatibleChange("Field '" + attributeName + "' (type="+ cai2.getFullClassname()+") has been added");
				if (update) {
					// Sets the right id of attribute
					cai2.setId(maxAttributeId + 1);
					maxAttributeId++;
					// Then adds the new attribute to the meta-model
					attributesToAdd.add(cai2);
				}

			}
		}
		attributes.removeAll(attributesToRemove);
		attributes.addAll(attributesToAdd);
		fillAttributesMap();
		return result;
	}

	public int getNumberOfAttributes() {
		return attributes.size();
	}

	public ClassInfoIndex addIndexOn(String name, String[] indexFields, boolean acceptMultipleValuesForSameKey) {
		if (indexes == null) {
			indexes = new OdbArrayList();
		}
		ClassInfoIndex cii = new ClassInfoIndex();
		cii.setClassInfoId(id);
		cii.setCreationDate(OdbTime.getCurrentTimeInMs());
		cii.setLastRebuild(cii.getCreationDate());
		cii.setName(name);
		cii.setStatus(ClassInfoIndex.ENABLED);
		cii.setUnique(!acceptMultipleValuesForSameKey);
		int[] attributeIds = new int[indexFields.length];
		for (int i = 0; i < indexFields.length; i++) {
			attributeIds[i] = getAttributeId(indexFields[i]);
			if(attributeIds[i]==-1){
				throw new ODBRuntimeException(NeoDatisError.CLASS_INFO_DO_NOT_HAVE_THE_ATTRIBUTE.addParameter(getFullClassName()).addParameter(indexFields[i]));
			}
		}
		cii.setAttributeIds(attributeIds);
		indexes.add(cii);
		return cii;
	}

	/**
	 * Removes an index
	 * 
	 * @param cii
	 */
	public void removeIndex(ClassInfoIndex cii) {
		indexes.remove(cii);
	}

	public int getNumberOfIndexes() {
		if (indexes == null) {
			return 0;
		}
		return indexes.size();

	}

	public ClassInfoIndex getIndex(int index) {
		if (indexes == null || index >= indexes.size()) {
			throw new ODBRuntimeException(NeoDatisError.INDEX_NOT_FOUND.addParameter(getFullClassName()).addParameter(index));
		}
		return indexes.get(index);
	}

	public void setIndexes(IOdbList indexes2) {
		this.indexes = indexes2;
	}

	/**
	 * To detect if a class has cyclic reference
	 * 
	 * @return true if this class info has cyclic references
	 */
	public boolean hasCyclicReference() {
		return hasCyclicReference(new OdbHashMap());
	}

	/**
	 * To detect if a class has cyclic reference
	 * 
	 * @param alreadyVisitedClasses
	 *            A hashmap containg all the already visited classes
	 * @return true if this class info has cyclic references
	 */
	private boolean hasCyclicReference(Map alreadyVisitedClasses) {
		ClassAttributeInfo cai = null;
		boolean hasCyclicRef = false;
		if (alreadyVisitedClasses.get(fullClassName) != null) {
			return true;
		}
		Map localMap = new OdbHashMap();
		alreadyVisitedClasses.put(fullClassName, this);
		for (int i = 0; i < attributes.size(); i++) {
			cai = getAttributeInfo(i);
			if (!cai.isNative()) {
				localMap = new OdbHashMap(alreadyVisitedClasses);
				hasCyclicRef = cai.getClassInfo().hasCyclicReference(localMap);
				if (hasCyclicRef) {
					return true;
				}
			}
		}
		return false;
	}

	public byte getClassCategory() {
		return classCategory;
	}

	public void setClassCategory(byte classInfoType) {
		this.classCategory = classInfoType;
	}

	public ObjectInfoHeader getLastObjectInfoHeader() {
		return lastObjectInfoHeader;
	}

	public void setLastObjectInfoHeader(ObjectInfoHeader lastObjectInfoHeader) {
		this.lastObjectInfoHeader = lastObjectInfoHeader;
	}

	public CIZoneInfo getUncommittedZoneInfo() {
		return uncommitted;
	}

	/**
	 * Get number of objects: committed and uncommitted
	 * 
	 * @return The number of committed and uncommitted objects
	 */
	public long getNumberOfObjects() {
		return committed.getNbObjects() + uncommitted.getNbObjects();
	}

	public CommittedCIZoneInfo getOriginalZoneInfo() {
		return original;
	}

	public boolean isSystemClass() {
		return classCategory == CATEGORY_SYSTEM_CLASS;
	}

	public ClassInfoIndex getIndexWithName(String name) {
		ClassInfoIndex cii = null;
		if (indexes == null) {
			return null;
		}
		for (int i = 0; i < indexes.size(); i++) {
			cii = indexes.get(i);
			if (cii.getName().equals(name)) {
				return cii;
			}
		}
		return null;
	}

	public ClassInfoIndex getIndexForAttributeId(int attributeId) {
		ClassInfoIndex cii = null;
		if (indexes == null) {
			return null;
		}
		for (int i = 0; i < indexes.size(); i++) {
			cii = indexes.get(i);
			if (cii.getAttributeIds().length == 1 && cii.getAttributeId(0) == attributeId) {
				return cii;
			}
		}
		return null;
	}

	public ClassInfoIndex getIndexForAttributeIds(int[] attributeIds) {
		ClassInfoIndex cii = null;
		if (indexes == null) {
			return null;
		}
		for (int i = 0; i < indexes.size(); i++) {
			cii = indexes.get(i);
			if (cii.matchAttributeIds(attributeIds)) {
				return cii;
			}
		}
		return null;
	}

	public String[] getAttributeNames(int[] attributeIds) {

		int nbIds = attributeIds.length;
		String[] names = new String[nbIds];

		for (int i = 0; i < nbIds; i++) {
			names[i] = getAttributeInfoFromId(attributeIds[i]).getName();
		}
		return names;
	}

	public List getAttributeNamesAsList(int[] attributeIds) {

		int nbIds = attributeIds.length;
		List names = new ArrayList(attributeIds.length);

		for (int i = 0; i < nbIds; i++) {
			names.add(getAttributeInfoFromId(attributeIds[i]).getName());
		}
		return names;
	}

	public IOdbList getIndexes() {
		if (indexes == null) {
			return new OdbArrayList();
		}
		return indexes;
	}

	public void removeAttribute(ClassAttributeInfo cai) {
		attributes.remove(cai);
		attributesByName.remove(cai.getName());
	}

	public void addAttribute(ClassAttributeInfo cai) {
		cai.setId(maxAttributeId++);
		attributes.add(cai);
		attributesByName.put(cai.getName(), cai);
	}

	public void setFullClassName(String fullClassName) {
		this.fullClassName = fullClassName;
	}

	public void addHistory(Object o) {
		if (history == null) {
			history = new OdbArrayList(1);
		}
		history.add(o);
	}

	public IOdbList getHistory() {
		return history;
	}

	public boolean hasIndex(String indexName) {
		ClassInfoIndex cii = null;
		if (indexes == null) {
			return false;
		}
		for (int i = 0; i < indexes.size(); i++) {
			cii = indexes.get(i);
			if (indexName.equals(cii.getName())) {
				return true;
			}
		}
		return false;
	}

	public boolean hasIndex() {
		return indexes != null && !indexes.isEmpty();
	}

	public void setExtraInfo(String extraInfo) {
		this.extraInfo = extraInfo;
	}

	public String getExtraInfo() {
		return extraInfo;
	}

	public ClassInfo duplicate(boolean onlyData) {
		ClassInfo ci = new ClassInfo(fullClassName);
		ci.extraInfo = extraInfo;

		ci.setAttributes(attributes);
		ci.setClassCategory(classCategory);
		ci.setMaxAttributeId(maxAttributeId);

		if (onlyData) {
			return ci;
		}

		ci.setAttributesDefinitionPosition(attributesDefinitionPosition);
		ci.setBlockSize(blockSize);
		ci.setExtraInfo(extraInfo);
		ci.setId(id);
		ci.setPreviousClassOID(previousClassOID);
		ci.setNextClassOID(nextClassOID);
		ci.setLastObjectInfoHeader(lastObjectInfoHeader);
		ci.setPosition(position);
		ci.setIndexes(indexes);

		return ci;
	}

}

 
 

Ads help maintain this website.