/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.
 ****************************************************************/

package org.apache.cayenne.dba;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.util.Util;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/** 
 * TypesHandler provides JDBC-RDBMS types mapping. Loads types info from 
 * an XML file.
 * 
 * @author Andrus Adamchik
 */
public class TypesHandler {

    private static Map<URL, TypesHandler> handlerMap = new HashMap<URL, TypesHandler>();

    protected Map<Integer, String[]> typesMap;

    /**
     * @since 1.1
     */
    public static TypesHandler getHandler(URL typesConfig) {
        synchronized (handlerMap) {
            TypesHandler handler = handlerMap.get(typesConfig);

            if (handler == null) {
                handler = new TypesHandler(typesConfig);
                handlerMap.put(typesConfig, handler);
            }

            return handler;
        }
    }

    /**
     * Creates new TypesHandler loading configuration info from the XML
     * file specified as <code>typesConfigPath</code> parameter.
     * 
     * @since 1.1
     */
    public TypesHandler(URL typesConfig) {
        try {
            InputStream in = typesConfig.openStream();

            try {
                XMLReader parser = Util.createXmlReader();
                TypesParseHandler ph = new TypesParseHandler();
                parser.setContentHandler(ph);
                parser.setErrorHandler(ph);
                parser.parse(new InputSource(in));

                typesMap = ph.getTypes();
            }
            catch (Exception ex) {
                throw new CayenneRuntimeException(
                    "Error creating TypesHandler '" + typesConfig + "'.",
                    ex);
            }
            finally {
                try {
                    in.close();
                }
                catch (IOException ioex) {
                }
            }
        }
        catch (IOException ioex) {
            throw new CayenneRuntimeException(
                "Error opening config file '" + typesConfig + "'.",
                ioex);
        }
    }

    public String[] externalTypesForJdbcType(int type) {
        return typesMap.get(Integer.valueOf(type));
    }

    /** 
     * Helper class to load types data from XML.
     */
    final class TypesParseHandler extends DefaultHandler {
        private static final String JDBC_TYPE_TAG = "jdbc-type";
        private static final String DB_TYPE_TAG = "db-type";
        private static final String NAME_ATTR = "name";

        private Map<Integer, String[]> types = new HashMap<Integer, String[]>();
        private List<String> currentTypes = new ArrayList<String>();
        private int currentType = TypesMapping.NOT_DEFINED;

        public Map<Integer, String[]> getTypes() {
            return types;
        }

        @Override
        public void startElement(
            String namespaceURI,
            String localName,
            String qName,
            Attributes atts)
            throws SAXException {
            if (JDBC_TYPE_TAG.equals(localName)) {
                currentTypes.clear();
                String strType = atts.getValue("", NAME_ATTR);

                // convert to Types int value
                try {
                    currentType = Types.class.getDeclaredField(strType).getInt(null);
                }
                catch (Exception ex) {
                    currentType = TypesMapping.NOT_DEFINED;
                }
            }
            else if (DB_TYPE_TAG.equals(localName)) {
                currentTypes.add(atts.getValue("", NAME_ATTR));
            }
        }

        @Override
        public void endElement(String namespaceURI, String localName, String qName)
            throws SAXException {
            if (JDBC_TYPE_TAG.equals(localName)
                && currentType != TypesMapping.NOT_DEFINED) {
                String[] typesAsArray = new String[currentTypes.size()];
                types.put(Integer.valueOf(currentType), currentTypes.toArray(typesAsArray));
            }
        }
    }
}
