package com.eactive.eai.transformer.layout;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;

import com.eactive.eai.transformer.message.InvalidAccessException;
import com.eactive.eai.transformer.message.Message;
import com.eactive.eai.transformer.message.MessageException;
import com.eactive.eai.transformer.util.ConstantUtil;
import com.eactive.eai.transformer.util.Constants;
import com.eactive.eai.transformer.util.Logger;


abstract public class Item implements Serializable, Cloneable {
	private static final long serialVersionUID = 4023501574176289617L;

	private static Logger logger = Logger.getLogger("transformer");

	public static final char FIELD_SEPARATOR = '.';
    // node type
    public static final int NODE_MESSAGE = 2201;
    public static final int NODE_GROUP   = 2202;
    public static final int NODE_FIELD   = 2203;
    public static final int NODE_ATTR    = 2204;
    
    // index type
    public static final char OCC_ONCE = '1';
    public static final char OCC_ONCE_OR_NONE = '?';
    public static final char OCC_MULTI_OR_NONE = '*';
    public static final char OCC_MULTI_OR_ONCE = '+';
    
    protected String name          	= "";
    protected String qualifiedName  = "";
    protected String description  	= "";
    protected String layoutName		= "";
    protected int depth;
    protected int itemId;
    protected int parentId;
    protected int nodeTypeId;
    protected int dataTypeId;
    protected String path        	= "";
    protected int length;
    protected String refInfo     	= "";
    protected int seqNo;
    protected String occRef      	= "";
    protected String occType     	= "";
    protected int occMax;
    protected int occMin;
    protected Object defaultValue;
    protected String mask        	= "";
    protected int maskLength        = 0; 
    protected int maskOffset        = 0; 

    protected String dataTypeRef 	= "";   // 레이아웃 항목 참조정보  - 예) PIC X(10), PIC 9(10)   
    protected int decimalPointLength = 0;   // 소수점 길이수    
    protected String pathName = "";         // xml path

    protected String multiLang = "";         // multi language 관련
    
    boolean mappingFlag = false;
    
    public boolean isMappingFlag() {
		return mappingFlag;
	}
    
    public void mapped() {
    	mappingFlag = true;
    }
    
    /**
     * reference item
     */
    protected Item referenceItem;
   
    /**
     * 상위 ITEM을 가리킴.
     * ROOT일 경우는 null이 됨.
     */
    protected Item parent;

    // 순회용

    /**
     * 같은 깊이에서 순번이 낮은 것을 가리킴.
     * 없을 경우는 null이 됨.
     */
    protected Item prev;

    /**
     * 같은 깊이에서 순번이 높은 것을 가리킴.
     * 없을 경우는 null이 됨.
     */
    protected Item next;

    // clone - deep copy
    protected int index = -1;
    protected Object value = null;
    
    protected boolean mappedValue = false;
    
    
    public boolean getMappedValue() {
		return mappedValue;
	}

	public void setMappedValue(boolean mappedValue) {
		this.mappedValue = mappedValue;
	}

	protected Item() {
        this("");
    }

    protected Item(String name) {
    }

    public int arraySize() {
        Item nextItem = getNext();
        if (nextItem == null) {
            return getIndex() + 1;
        } else if (!getName().equals(nextItem.getName())) {
            return getIndex() + 1;
        } else {
            return nextItem.arraySize();
        }
    }

    /**
     * index
     */
    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
        this.qualifiedName = name + "[" + index + "]";
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) throws InvalidAccessException {
    	setValue(value,false);
    }  
    public void setValue(Object value, boolean isConvertPass) throws InvalidAccessException {
    	try {
    		if (isConvertPass){
    			this.value = value;
    		}else{
    			this.value = Converter.convert(value, this);
    		}
    	} catch(Exception e) {
    		throw new InvalidAccessException("setValue error", getItemPath(), null, e);
    	}
    }    
       
    public Message getRoot() {
        if (isMessage())
            return (Message) this;
        if (parent == null)
        	throw new MessageException("item " + name + "'s parent is null");
        return (Message) parent.getRoot();
    }
    
    public Message getTop() {
    	if (parent == null)
    		return (Message) this;
    	return parent.getTop();
    }
    
    public int getDepth() {
    	return depth;
    }
    
    public void setDepth(int depth) {
    	this.depth = depth;
    }

    public int getItemId() {
        return itemId;
    }

    public void setItemId(int itemId) {
        this.itemId = itemId;
    }

    public int getParentId() {
        return parentId;
    }

    public void setParentId(int parentId) {
        this.parentId = parentId;
    }

    public String getName() {
        return name;
    }

    public String getItemPath() {
        if (parent == null) {
            return name + "[" + index + "]";
        } else {
            return parent.getItemPath() + Types.FIELD_SEPARATOR + name + "[" + index + "]";
        }
    }

    public void setName(String name) {
        this.name = name;
        this.qualifiedName = name + "[" + index + "]";
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * layout name
     */
    public void setLayoutName(String layoutName) {
        this.layoutName = layoutName;
    }

    public String getLayoutName() {
        return this.layoutName;
    }

    /**
     * node type
     */
    public int getNodeTypeId() {
        return nodeTypeId;
    }
    
    public String getNodeType() {
    	return ConstantUtil.getNodeType(nodeTypeId);
    }

    public void setNodeTypeId(int nodeTypeId) {
        this.nodeTypeId = nodeTypeId;
    }
    
    public boolean isTop() {
    	return (getParent() == null);
    }
    
    public boolean isRoot() {
    	return isMessage();
    }

    public boolean isMessage() {
        return (nodeTypeId == NODE_MESSAGE);
    }

    public boolean isGroup() {
        return (nodeTypeId == NODE_GROUP);
    }
    
    public boolean isField() {
        return (nodeTypeId == NODE_FIELD);
    }
	
    public boolean isAttr() {
        return (nodeTypeId == NODE_ATTR);
    }
    
    public int getDataTypeId() {
        return dataTypeId;
    }

    public void setDataTypeId(int dataTypeId) {
        this.dataTypeId = dataTypeId;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public String getRefInfo() {
        return refInfo;
    }

    public void setRefInfo(String refInfo) {
        this.refInfo = refInfo;
    }

    public String getOccRef() {
        return occRef;
    }

    public void setOccRef(String occRef) {
    	if (occRef != null) {
    		this.occRef = occRef.trim();
    	}
    }

    public int getSeqNo() {
        return seqNo;
    }

    public void setSeqNo(int seqNo) {
        this.seqNo = seqNo;
    }

    public String getOccType() {
        return occType;
    }

    public void setOccType(String occType) {
        this.occType = occType;
    }

    public int getOccMax() {
        return occMax;
    }

    public void setOccMax(int occMax) {
        this.occMax = occMax;
    }

    public int getOccMin() {
        return occMin;
    }

    public void setOccMin(int occMin) {
        this.occMin = occMin;
    }
    
    public String getMask() {
        return this.mask;
    }
    
    public void setMask(String mask) {
        this.mask = mask;
    }

    public int getMaskLength() {
        return maskLength;
    }

    public void setMaskLength(int maskLength) {
        this.maskLength = maskLength;
    }
    
    public int getMaskOffset() {
    	return maskOffset;
    }
    
    public void setMaskOffset(int maskOffset) {
    	this.maskOffset = maskOffset;
    }
    
    public Object getDefaultValue() {
    	if (defaultValue == null){
        	/**
        	 * epb에서 추가됨 JSON 타입의 null 처리 때문 (item.java, Transformer.java 도 같이 변경)
        	 * JSON 관련해서 null 처리로  default가 null 일경우 item이 생성 안되도록 변경했으나 문제생김
        	 * 1. flat 데이터에서 값이 안생기는 문제 발생
        	 *    ==> BYTEARRAY 타입을 예외처리(FLAT은 byte array이기 때문) 20210101 jun's
        	 */
    		if (getDataTypeId() == Types.TYPE_BYTEARRAY){
        		return new byte[0];
        	}
    		else {
    			return null;
    		}
    	}
    	else {
            return defaultValue;
    	}
    }

    public abstract void setDefaultValue(String defaultValue);
    
    public int getDecimalPointLength() {
        return decimalPointLength;
    }

    public void setDecimalPointLength(int decimalPointLength) {
        this.decimalPointLength = decimalPointLength;
    }

    public String getPathName() {
        return pathName;
    }

    public void setPathName(String pathName) {
        this.pathName = pathName;
    }
    
    public String getDataTypeRef() {
        return dataTypeRef;
    }

    public void setDataTypeRef(String dataTypeRef) {
        this.dataTypeRef = dataTypeRef;        
    }
   
   /**
	 * @return the multiLang
	 */
	public String getMultiLang() {
		return multiLang;
	}

	/**
	 * @param multiLang the multiLang to set
	 */
	public void setMultiLang(String multiLang) {
		this.multiLang = multiLang;
	}

	public Item getParent() {
        return parent;
    }

    public void setParent(Item parent) {
        this.parent = parent;
    }

    public Item getPrev() {
        return prev;
    }

    public void setPrev(Item item) {
        this.prev = item;
    }

    public Item getNext() {
        return next;
    }

    public void setNext(Item item) {
        this.next = item;
    }

    public List<Item> getChildList() {
    	throw new UnsupportedOperationException("unsupported operation getChildList() - " + 
    			getNodeType() + "|" + getName());
    }

    public void addChild(Item item) {
    	throw new UnsupportedOperationException("unsupported operation addChild() - " + 
    			getNodeType() + "|" + getName() + ", child=" + item.getName());
    }

    public void addChild(Item sibling, Item item) {
    	throw new UnsupportedOperationException("unsupported operation addChild() - " + 
    			getNodeType() + "|" + getName() + 
    			", sibling=" + sibling.getName() +
    			", item=" + item.getName());
    }

    public void removeChild(Item item) {
    	throw new UnsupportedOperationException("unsupported operation removeChild() - " + 
    			getNodeType() + "|" + getName() + ", child=" + item.getName());
    }
   
    public void replaceChild(Item oldItem, Item newItem) {
    	throw new UnsupportedOperationException("unsupported operation replaceChild() - " + 
    			getNodeType() + "|" + getName() + 
    			", old=" + oldItem.getName() +
    			", new=" + newItem.getName());
    }
   
    public Item getChild(int index) {
    	throw new UnsupportedOperationException();
    }

    public int getChildCount() {
    	throw new UnsupportedOperationException();
    }

    public Item getChildByName(String name) {
    	throw new UnsupportedOperationException();
    }

    /**
     * reference item
     */
    public Item getReferenceItem() {
        return referenceItem;
    }

    public void setReferenceItem(Item referenceItem) {
        this.referenceItem = referenceItem;
    }

    public abstract void clearValue();

    public abstract Item findItemByPath(String path);

    public abstract void initDefaultValue();

    public abstract void initDepth(int depth);
    
    public abstract void initInclude();
    
    /**
     * OccMin 설정
     * - min 갯수 만큼 sibling을 만든다.
     * - group 또는 message일 경우 child 에도 recursive 하게 작업해 준다.
     */
    public abstract void initOccMin();   

    /**
     * itemId, parentId
     */
    public abstract int initItemParentId(int seq, int parentId);
    
    public abstract boolean existMask();
    
    public String normalizeFullName(String itemPathValue) {
        // if root return
        if (parent == null) {
	        int sepIndex = itemPathValue.indexOf(FIELD_SEPARATOR);
	        if (sepIndex == -1) {
	            return itemPathValue;
	        }
	        else {
	        	return nomalizeFullNameParts(itemPathValue);
	        }
        }
        else {
            return itemPathValue;
        }
    }
    
    private String nomalizeFullNameParts(String source) {
    	StringBuilder sb = new StringBuilder(1024);
    	CharSequence chars = source;
    	int start = 0;
    	int end = 0;
    	int partCount = 0;
    	int i=0;
    	for(; i<chars.length(); i++) {
    		if(chars.charAt(i) == FIELD_SEPARATOR) {
    			end = i;
    			sb.append(chars.subSequence(start, end).toString());
    			if(chars.charAt(i-1) != ']') {
    				if(partCount == 0) sb.append("[-1]");
    				else sb.append("[0]");
    			}
    			sb.append(FIELD_SEPARATOR);
    			partCount++;
    			start = end+1;
    			i = i+1;
    		}
    	}
    	
    	if(end == 0) {
    		sb.append(chars.toString());    		
    	}
    	else {
    		sb.append(chars.subSequence(start, chars.length()).toString());
    	}
    	
    	if(chars.charAt(i-1) != ']') {
			sb.append("[0]");
		}
    	
    	return sb.toString();
    }

    protected Item getSibling(String itemPath) {
        if (itemPath.indexOf(Types.FIELD_SEPARATOR) != -1) {
            throw new InvalidNodeException(
                "getSibing() does not support multi level itemPath" +
                itemPath, getRoot(), this, null);
        }

        int sIndex = itemPath.lastIndexOf('[');
        if (!name.equals(itemPath.substring(0, sIndex))) {
            return null;
        }

        itemPath = itemPath.substring(0, sIndex) + "[-1]";

        Item sibling = getItem(itemPath);

        if (sibling == null) {
            throw new InvalidNodeException("sibling을 찾을 수 없읍니다 - " + itemPath, getRoot(), this, null);
        }

        while (true) {
            if ((sibling.getNext() != null) &&
                    name.equals(sibling.getNext().getName())) {
                sibling = sibling.getNext();
            } else {
                break;
            }
        }

        return sibling;
    }

    public Item getItem(String itemPath, boolean bNormalize) {
        if (bNormalize)
            itemPath = normalizeFullName(itemPath);
        return getItem(itemPath);
    }
    
    protected abstract Item getItem(String itemPath);
    
    public Item getOrCreateItem(String itemPath, boolean bNormalize) {
        if (bNormalize) {
            itemPath = normalizeFullName(itemPath);
        }
        return getOrCreateItem(itemPath);
    }

    protected abstract Item getOrCreateItem(String itemPath);
    
    public Item getLastSibling(Item item) {
        Item nextItm = item.getNext();

        if (nextItm == null) {
            return item;
        } else if (!item.getName().equals(nextItm.getName())) {
            return item;
        } else {
            return getLastSibling(nextItm);
        }
    }

    public Item createItem(Item sibling, String itemPath, boolean bSetOcc) {
    	return createItem( sibling, itemPath,  bSetOcc,  null );
    }
    public Item createItem(Item sibling, String itemPath, boolean bSetOcc, List<Item> itemList) {
        sibling = getLastSibling(sibling);

        Item parent = sibling.getParent();
        int idx = 0;
        try{
        	idx = Integer.parseInt(itemPath.substring(itemPath.lastIndexOf('[') +
                    1, itemPath.lastIndexOf(']')));
        }catch(NumberFormatException e){
            throw new InvalidNodeException("GRID 항목매핑오류 GRID간 매핑인지 확인, itemPath="+itemPath+ ", " + sibling.getName(), getRoot(), this, null);
        }

        if ((sibling.getOccMax() > 0) && (idx >= sibling.getOccMax())) {
            throw new InvalidNodeException("cannot set value over maxOcc , idx=" + idx + ", " + sibling.getName(), 
            		getRoot(), this, null);
        }

        Item clone = null;
        Item occItem = null;
        
        for (int i = sibling.getIndex() + 1; i <= idx; i++) {
            clone = (Item) sibling.clone();
            clone.setIndex(i);

            /**
             * root item 이 아닐 경우
             */
            if (parent != null) {
                try {
                    parent.addChild(sibling, clone);
                    clone.setParent(parent);
                } catch (InvalidNodeException e) {
                    logger.error(e.getMessage(), e);
                    clone = null;
                }
            }

            clone.initDefaultValue();

            try {
                clone.initOccMin();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }

            // 하위 그룹에 대한 참조필드 초기화 (2021.10.14)
            if(bSetOcc && clone.getIndex() >=0 && clone.getNodeTypeId() == NODE_GROUP
            		&& clone.getRoot().getLayout().getLayoutType().getName().equals(LayoutTypeKey.BYTES)) {
            	for (int id = 0; id < clone.getChildCount(); id++) {
	    			Item child = clone.getChild(id);
    				if(child.getNodeTypeId() == NODE_GROUP && child.getIndex() == 0) {
    	    			child.setOccRefCount(child);
    	    		}
	            }
            }

            /**
             * reference item 에 occurrence setting
             */
            if (bSetOcc && ("*".equals(clone.getOccType()) || "+".equals(clone.getOccType()) || clone.getOccMax()>clone.getOccMin())) {
                if (clone.getOccRef() == null && clone.getRoot().getLayout().getLayoutType().getName().equals(LayoutTypeKey.BYTES)) {
                    throw new InvalidNodeException(
                    		getRoot().getName() + " | OccType 이 '*', '+' 또는 occType > occMin' 인 경우는 RefOcc 정보를 반드시 설정하여야 합니다. " +
                             clone.toString(), getRoot(), this, null);
                } else if (clone.getOccRef() != null  && !"".equals(clone.getOccRef().trim())) {
                    /**
                     * occRef item 정보 update
                     */
                	// 2009.10.12 - 반복참조회수 설정 제거
                	// 매핑한 값을 변경하지 않도록 수정
                	
                	occItem = getRoot().getItem(clone.getOccRefItem().getItemPath());
                	boolean isMappedOcc = occItem.getMappedValue();
            		
                	if(clone.getIndex() == 0) {
	                	try {
	                		if(!isMappedOcc) {
	                			if(occItem.getValue() != null &&
	                					getRoot().getInt(clone.getOccRefItem().getItemPath()) > 0) {
	                				isMappedOcc = true;	                				
	                			}
	                			else {
	                				// 설정값이 없거나 숫자가 아닌 경우
	                				isMappedOcc  = false;
	                			}
	                		}
	                	}
	                	catch(Exception ex) {
	                		// 숫자가 아닌 경우(공백문자)
	                		isMappedOcc = false;
	                	}
	                	occItem.setMappedValue(isMappedOcc);
                	}
                
                	String type =clone.getRoot().getLayout().getLayoutType().getName();
                	//변경 20210105 jun's 원칙으로 변경함
                	if(!(LayoutTypeKey.VFIELD.equals(type) && clone.isField() && clone.getLength()==0) ) {                		
                		getRoot().setInt(clone.getOccRefItem().getItemPath(), clone.getIndex() + 1);
                	}
                }
            }
            if(itemList != null) itemList.add(clone);
            sibling = clone;
        }

        return clone;
    }
    
 // clone 으로 생성되는 경우에 설정되지 않는 문제가 있음.
    public void setOccRefCount(Item clone) {        
        Item occItem = null;
        /**
         * reference item 에 occurrence setting
         */
        if (("*".equals(clone.getOccType()) || "+".equals(clone.getOccType()) || clone.getOccMax()>clone.getOccMin())) {
            if (clone.getOccRef() == null && clone.getRoot().getLayout().getLayoutType().getName().equals(LayoutTypeKey.BYTES)) {
                throw new InvalidNodeException(
                		getRoot().getName() + " | OccType 이 '*', '+' 또는 occType > occMin' 인 경우는 RefOcc 정보를 반드시 설정하여야 합니다. " +
                         clone.toString(), getRoot(), this, null);
            } else if (clone.getOccRef() != null  && !"".equals(clone.getOccRef().trim())) {
                /**
                 * occRef item 정보 update
                 */
            	// 매핑한 값을 변경하지 않도록 수정
            	occItem = getRoot().getItem(clone.getOccRefItem().getItemPath());
            	boolean isMappedOcc = occItem.getMappedValue();
            	if(clone.getIndex() == 0) {
                	try {
                		if(!isMappedOcc) {
                			if(occItem.getValue() != null &&
                					getRoot().getInt(clone.getOccRefItem().getItemPath()) > 0) {
                				isMappedOcc  = true;	  
                			}
                			else {
                				isMappedOcc  = false;
                			}
                		}
                	}
                	catch(Exception ex) {
                		// 숫자가 아닌 경우(공백문자)
                		isMappedOcc = false;	                		
                	}
                	// commented : IMPORANT createitem과 중복처리될 수 있으므로 설정하지 않고, clone만 처리될 경우 초기값을 설정
                	// occItem.setMappedValue(isMappedOcc);
            	}
            
            	String type =clone.getRoot().getLayout().getLayoutType().getName();
            	if (LayoutTypeKey.JSON.equals(type)){
            		//원칙적인 로직은 아래와 같음
        			getRoot().setInt(clone.getOccRefItem().getItemPath(), clone.getIndex() + 1);
            	}else{
            		//예외처리하는 로직 
            		//case 1 : 원래등록해야되는 레이아웃 : 고정그리드 에 유효한 건수를 가지고 있는 값
            		//       : 업무팀에서 가변그리드로 등록후 위에 등록한거와 같이 되게 요청해서 넣은 로직으로 예상됨
            		if( !(LayoutTypeKey.VFIELD.equals(type) && clone.isField() && clone.getLength()==0)             			
                		&& !isMappedOcc) {
                		getRoot().setInt(clone.getOccRefItem().getItemPath(), clone.getIndex() + 1);
            		}
            	}
            }
        }
    }
    
    protected Item getOccRefItem() {
    	/**
    	 * 1. [0] 을 붙여서 occRef 존재 유무 체크
    	 */
		String itemPath = getParent().getItemPath() + "." + getOccRef() + "[0]";
		Item occRefItem = getTop().getItem(itemPath);
		if (occRefItem != null)
			return occRefItem;

    	/**
    	 * 2. [-1] 을 붙여서 occRef 존재 유무 체크
    	 */
		itemPath = getParent().getItemPath() + "." + getOccRef() + "[-1]";
		occRefItem = getTop().getItem(itemPath);
		if (occRefItem != null)
			return occRefItem;
    	
        /**
         * 3. layout2 에서 - occRef 에 한글필드명 (description) 을 쓸 경우 처리를 위해
         */
		for (Iterator<Item> iter = getParent().fullIterator(); iter.hasNext(); ) {
			Item x = iter.next();						
			if (getOccRef().trim().equals(x.getDescription())) {
				setOccRef(x.getName());
	    		itemPath = getParent().getItemPath() + "." + getOccRef() + "[-1]";
				occRefItem = getTop().getItem(itemPath);
				if (occRefItem != null) {						
					return occRefItem;
				}
			}
		}
		
		throw new MessageException(getRoot().getName() + " - 항목명(" + getName() + ")의 반복횟수참조정보가 없습니다." + 
				getOccRef() + " (" + itemPath + ")");
    }
    
    
    public Object clone() {
    	try {
    		return super.clone();
    	} catch(CloneNotSupportedException e) {
    		throw new RuntimeException("error when clone object", e);
    	}
    }   

	protected String maskString(int size) {
        StringBuilder sb = new StringBuilder(size);
        
        for (int i = 0; i < size; i++) {
            sb.append("*");
        }

        return sb.toString();
    }

    public String maskString(Object val) {
        if (val==null) {
            return null;
        }

        String str = null;
        if (val instanceof byte[])
            str = new String((byte[]) val);
        else
            str = val.toString();
            
        StringBuilder sb = new StringBuilder(str.length());
        
        for (int i = 0; i < str.length(); i++) {
            sb.append("*");
        }

        return sb.toString();
    }


    /**
     * mask 처리 
     * MaskYN : Y/N , MaskLength : 0 , 1~9
     * MaskLength 가 0 이면 전체 마스킹 1이면 1바이트 보여주고 마스킹 필드값이 MaskLength 보다 짧으면 전체 unmaking
     * @param val
     * @param maskLength
     * @return
     */
    public String maskString2(Object val, int maskLength) {
        if (val==null) {
            return null;
        }

        String str = null;
        if (val instanceof byte[])
            str = new String((byte[]) val);
        else
            str = val.toString();
            
        int len = str.length();
        if( len < maskLength ) return str;
        
        StringBuilder sb = new StringBuilder(len);
        if( maskLength > 0 ) sb.append(str.substring(0, maskLength));
        
        for (int i = maskLength; i < str.length(); i++) {
            sb.append("*");
        }

        return sb.toString();
    }
    
    /**
     * mask 처리 
     * MaskYN : Y/N , MaskLength : 0 , 1~9
     * MaskLength 가 0 이면 전체 마스킹 1이면 1바이트 보여주고 마스킹 필드값이 MaskLength 보다 짧으면 전체 unmaking
     * @param val
     * @param maskLength
     * @return
     */
    public String maskString2(Object val, int maskLength, int maskOffset) {
    	if (val==null) {
    		return null;
    	}
    	
    	String str = null;
    	if (val instanceof byte[])
    		str = new String((byte[]) val);
    	else
    		str = val.toString();
    	
    	int len = str.length();
    	if( len <= maskOffset ) return str; 
    	
    	char[] sourceChars = str.toCharArray();
    	char[] targetChars = new char[len];
    	
    	if(maskOffset > 0)
    		System.arraycopy(sourceChars, 0, targetChars, 0, maskOffset);
    	for(int i = maskOffset; i < maskOffset+maskLength; i++) {
    		if(i >= len)
    			break;
    		targetChars[i] = '*'; 
    	}
    	int maskEnd = maskOffset+maskLength;
    	if(len > maskEnd) 
    		System.arraycopy(sourceChars, maskEnd, targetChars, maskEnd, len - maskEnd);
    	
    	return new String(targetChars);
    }
    /**
     * iterator 
     */
    public Iterator<Item> iterator() {
    	throw new UnsupportedOperationException();
    }
    
    public Iterator<Item> fullIterator() {
    	throw new UnsupportedOperationException();
    }


    public String toString() {
        return toString(Constants.TAB, false);
    }   
    public String toXMLString() {
        return toXMLString(Constants.TAB, false);
    }   

    public abstract String toString(String tab, boolean bShowAllFlag);	
    
    public abstract String toXMLString(String tab, boolean bShowAllFlag);	
}