/*
 * Copyright 2005 Werner Guttmann
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id: CollectionProxy.java 5980 2006-06-17 09:55:54Z rjoachim $
 */
package org.castor.persist.proxy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.jdo.util.ClassLoadingUtils;
import org.exolab.castor.persist.FieldMolder;
import org.exolab.castor.persist.spi.Identity;

/**
 * This class is a proxy for different types of Collection and Maps.
 */
public abstract class CollectionProxy {

    private static final Log LOG = LogFactory.getLog(CollectionProxy.class);
    
    public abstract Object getCollection();

    public abstract void add(Identity key, Object value);

    public abstract void close();

    public static CollectionProxy create(final FieldMolder fieldMolder,
            final Object object, final ClassLoader classLoader) {
        Class cls = fieldMolder.getCollectionType();
        if (cls == Vector.class) {
            return new ColProxy(fieldMolder, object, classLoader, new Vector());
        } else if (cls == ArrayList.class) {
            return new ColProxy(fieldMolder, object, classLoader, new ArrayList());
        } else if (cls == Collection.class) {
            return new ColProxy(fieldMolder, object, classLoader, new ArrayList());
        } else if (cls == Set.class) {
            return new ColProxy(fieldMolder, object, classLoader, new HashSet());
        } else if (cls == HashSet.class) {
            return new ColProxy(fieldMolder, object, classLoader, new HashSet());
        } else if (cls == Hashtable.class) {
            return new MapProxy(fieldMolder, object, classLoader, new Hashtable());
        } else if (cls == HashMap.class) {
            return new MapProxy(fieldMolder, object, classLoader, new HashMap());
        } else if (cls == Iterator.class) {
            return new IteratorProxy(fieldMolder, object, classLoader, new ArrayList());
        } else if (cls == Enumeration.class) {
            return new EnumerationProxy(fieldMolder, object, classLoader,
                    new ArrayList());
        } else if (cls == Map.class) {
            return new MapProxy(fieldMolder, object, classLoader, new HashMap());
        } else if (cls == SortedSet.class) {
            String comparatorClassName = fieldMolder.getComparator();
            if (comparatorClassName != null) {
                Comparator comparator;
                try {
                    comparator = (Comparator) ClassLoadingUtils.loadClass(classLoader, comparatorClassName).newInstance();
                } catch (InstantiationException e) {
                    LOG.error ("Problem instantiating instance of " + comparatorClassName);
                    throw new IllegalArgumentException("Problem instantiating instance of " + comparatorClassName);
                } catch (IllegalAccessException e) {
                    LOG.error ("Problem accessing constructor of " + comparatorClassName);
                    throw new IllegalArgumentException("Problem accessing constructor of " + comparatorClassName);
                } catch (ClassNotFoundException e) {
                    LOG.error ("Problem loading class for " + comparatorClassName);
                    throw new IllegalArgumentException("Problem instantiating instance of " + comparatorClassName);
                }
                return new ColProxy(fieldMolder, object, classLoader, new TreeSet(comparator));
            }
            return new ColProxy(fieldMolder, object, classLoader, new TreeSet());
        } else {
            throw new IllegalArgumentException(
                    "Collection Proxy doesn't exist for this type : " + cls);
        }
    }

    private static final class ColProxy extends CollectionProxy {
        
        private Collection _col;

        private FieldMolder _fm;

        private Object _object;

        private ClassLoader _cl;

        private ColProxy(final FieldMolder fm, final Object object,
                final ClassLoader cl, final Collection col) {
            _cl = cl;
            _fm = fm;
            _col = col;
            _object = object;
        }

        public Object getCollection() {
            return _col;
        }

        public void add(final Identity key, final Object value) {
            if (!_fm.isAddable()) {
                // [TODO] Find a better way to express this scenario where no
                // setter is specified either.
                _col.add(value);
            } else {
                _fm.addValue(_object, value, _cl);
            }
        }

        public void close() {
            if (!_fm.isAddable()) {
                _fm.setValue(_object, _col, _cl);
            }
        }
    }

    private static final class EnumerationProxy extends CollectionProxy {
        
        private Collection _collection;

        private FieldMolder _fm;

        private Object _object;

        private ClassLoader _cl;

        private EnumerationProxy(final FieldMolder fm, final Object object,
                final ClassLoader cl, final Collection collection) {
            _cl = cl;
            _fm = fm;
            _collection = collection;
            _object = object;
        }

        public Object getCollection() {
            return Collections.enumeration(_collection);
        }

        public void add(final Identity key, final Object value) {
            if (!_fm.isAddable()) {
                // [TODO] Find a better way to express this scenario where no
                // setter is specified either.
                _collection.add(value);
            } else {
                _fm.addValue(_object, value, _cl);
            }
        }

        public void close() {
            if (!_fm.isAddable()) {
                _fm.setValue(_object, Collections.enumeration(_collection), _cl);
            }
        }
    }

    private static final class IteratorProxy extends CollectionProxy {
        
        private Collection _collection;

        private FieldMolder _fieldMolder;

        private Object _object;

        private ClassLoader _cl;

        private IteratorProxy(final FieldMolder fm, final Object object,
                final ClassLoader cl, final Collection col) {
            _cl = cl;
            _fieldMolder = fm;
            _collection = col;
            _object = object;
        }

        public Object getCollection() {
            return _collection.iterator();
        }

        public void add(final Identity key, final Object value) {
            if (!_fieldMolder.isAddable()) {
                // [TODO] Find a better way to express this scenario where no
                // setter is specified either.
                _collection.add(value);
            } else {
                _fieldMolder.addValue(_object, value, _cl);
            }
        }

        public void close() {
            if (!_fieldMolder.isAddable()) {
                _fieldMolder.setValue(_object, _collection.iterator(), _cl);
            }
        }
    }

    private static final class MapProxy extends CollectionProxy {
        private Map _map;

        private FieldMolder _fm;

        private Object _object;

        private ClassLoader _cl;

        private MapProxy(final FieldMolder fm, final Object object,
                final ClassLoader cl, final Map map) {
            _cl = cl;
            _map = map;
            _fm = fm;
            _object = object;
        }

        public Object getCollection() {
            return _map;
        }

        public void add(final Identity key, final Object value) {
            if (key.size() == 1) {
                _map.put(key.get(0), value);
            } else {
                _map.put(key, value);
            }
        }

        public void close() {
            if (!_fm.isAddable()) {
                _fm.setValue(_object, _map, _cl);
            }
        }

    }
}
