/*
 * Copyright 2008 Sebastian Gabmeyer
 *
 * 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.
 */
package org.castor.cpa.util.classresolution.command;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.constants.cpa.JDOConstants;
import org.castor.cpa.util.classresolution.nature.ClassLoaderNature;
import org.castor.cpa.util.classresolution.nature.PackageBasedCDRResolutionNature;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.xml.util.resolvers.ResolveHelpers;

/**
 * This class tries to resolve a {@link ClassDescriptor} from a package that
 * contains a {@link JDOConstants#PKG_CDR_LIST_FILE} file. This file defines the
 * mapping between a domain class and its corresponding
 * <code>ClassDescriptor</code>.
 * 
 * @author Sebastian Gabmeyer
 * @version 1.2.1
 */
public class ClassResolutionByCDR extends BaseResolutionCommand {

    /** The Logger instance to use. */
    private static final Log LOG = LogFactory
            .getLog(ClassResolutionByCDR.class);

    /**
     * Constructor.
     */
    public ClassResolutionByCDR() {
        addNature(ClassLoaderNature.class.getName());
        addNature(PackageBasedCDRResolutionNature.class.getName());
    }

    /**
     * Resolves a <code>type</code> to a {@link ClassDescriptor} by looking for
     * a {@link JDOConstants#PKG_CDR_LIST_FILE} file in the root directory.
     * 
     * @param type
     *            the <code>class</code> to obtain the
     *            <code>ClassDescriptor</code> for.
     * @return the {@link ClassDescriptor} if found, <code>null</code>
     *         otherwise.
     */
    public ClassDescriptor resolve(final Class type) {
        List<String> packages = new PackageBasedCDRResolutionNature(this)
                .getPackageNames();

        if (packages == null) {
            if (LOG.isDebugEnabled()) {
                LOG
                        .debug("No package names to search descriptors in were passed."
                                + " No descriptors will be loaded!");
            }
            return null;
        }

        String typeName = type.getName();

        for (Iterator<String> it = packages.iterator(); it.hasNext(); ) {
            Map<String, ClassDescriptor> descriptors = getDescriptors(it.next());
            if (descriptors.containsKey(typeName)) {
                return descriptors.get(typeName);
            }
            // TODO remove this following code if above implementation is
            // sufficient in regard to performance.
            // String packageName = (String) it.next();
            // URL cdrUrl = loader.getResource(
            // ResolveHelpers
            // .getQualifiedFileName(JDOConstants.PKG_CDR_LIST_FILE,
            // packageName));
            // try {
            // Properties cdrList = getProperties(cdrUrl);
            // Enumeration classes = cdrList.keys();
            // for ( ; classes.hasMoreElements(); ) {
            // String className = (String) classes.nextElement();
            // if (className.equals(typeName)) {
            // String classDescriptorName = cdrList.getProperty(className);
            // Class classDescriptor =
            // ResolveHelpers.loadClass(loader, classDescriptorName);
            // if (classDescriptor != null) {
            // return (ClassDescriptor) classDescriptor.newInstance();
            // }
            // }
            // }
            // } catch (Exception e) {
            // String message = "Failed to load package: " + packageName +
            // "with exception: " + e;
            // LOG.warn(message);
            // ResolverException resolverException =
            // (ResolverException) new ResolverException(message).initCause(e);
            // throw resolverException;
            // }
        }
        return null;
    }

    /**
     * Loads {@link Properties} from the specified <code>URL</code>.
     * 
     * @param url
     *            the location where the properties are located.
     * @return the loaded properties.
     * @throws IOException
     *             if an IOException occurs.
     */
    private Properties getProperties(final URL url) throws IOException {
        Properties properties = new Properties();

        java.io.InputStream stream = url.openStream();
        properties.load(stream);
        stream.close();

        return properties;
    }

    /**
     * Get all descriptors from the package defined by the
     * <code>packageName</code> that contains the
     * {@link JDOConstants#PKG_CDR_LIST_FILE} file.
     * 
     * @param packageName
     *            the package to search descriptors for.
     * @return a {@link java.util.List} of descriptors contained in the package.
     */
    public Map<String, ClassDescriptor> getDescriptors(final String packageName) {
        Map<String, ClassDescriptor> descriptors = new HashMap<String, ClassDescriptor>();

        ClassLoader loader = new ClassLoaderNature(this).getClassLoader();

        URL cdrUrl = loader.getResource(ResolveHelpers.getQualifiedFileName(
                JDOConstants.PKG_CDR_LIST_FILE, packageName));

        Properties cdrList = new Properties();

        try {
            cdrList = getProperties(cdrUrl);
            Enumeration classes = cdrList.keys();
            for (; classes.hasMoreElements(); ) {
                String className = (String) classes.nextElement();
                String classDescriptorName = cdrList.getProperty(className);
                Class classDescriptor = ResolveHelpers.loadClass(loader,
                        classDescriptorName);
                if (classDescriptor != null) {
                    ClassDescriptor descriptorInstance = (ClassDescriptor) classDescriptor
                            .newInstance();
                    descriptors.put(className, descriptorInstance);
                }
            }
        } catch (Exception e) {
            String message = "Failure occured while loading classes from packacge \""
                    + packageName + "\" with exception: " + e;
            LOG.warn(message);
            throw new RuntimeException(e.getMessage());
        }
        return descriptors;
    }
}
