/*
 * Copyright 2011, 2012 Odysseus Software GmbH
 *
 * 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.apache.synapse.commons.staxon.core.json.stream;

import org.apache.synapse.commons.staxon.core.json.stream.impl.Constants;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Properties;

import javax.xml.stream.FactoryConfigurationError;

/**
 * Abstract JSON stream ({@link JsonStreamSource} and {@link JsonStreamTarget}) factory.
 * <p/>
 * <p>This class provides the static {@link #newFactory()} method to lookup and instantiate a default
 * implementation using the Services API (as detailed in the JAR specification).</p>
 */
public abstract class JsonStreamFactory {
    private static String getMetaInfServicesClassName(Class<?> serviceInterface, ClassLoader classLoader) {
        String serviceId = "META-INF/services/" + serviceInterface.getName();
        InputStream input = classLoader.getResourceAsStream(serviceId);
        if (input != null) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
                try {
                    return reader.readLine();
                } catch (IOException e) {
                    // do nothing
                } finally {
                    try {
                        reader.close();
                    } catch (Exception io) {
                        // do nothing
                    }
                }
            } catch (IOException e) {
                try {
                    input.close();
                } catch (Exception io) {
                    // do nothing
                }
            }
        }
        return null;
    }

    private static String getJavaHomeLibClassName(Class<?> serviceInterface, String bundleName) {
        String home = System.getProperty("java.home");
        if (home != null) {
            InputStream input = null;
            String path = home + File.separator + "lib" + File.separator + bundleName + ".properties";
            File file = new File(path);
            try {
                if (file.exists()) {
                    input = new FileInputStream(file);
                    Properties props = new Properties();
                    props.load(input);
                    return props.getProperty(serviceInterface.getName());
                }
            } catch (IOException e) {
                // do nothing
            } catch (SecurityException e) {
                // do nothing
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException io) {
                        // do nothing
                    }
                }
            }
        }
        return null;
    }

    /**
     * <p>Create a new instance of a JsonStreamFactory.</p>
     * <p>Determines the class to instantiate as follows:
     * <ol>
     * <li>Use the Services API (as detailed in the JAR specification). If a resource with the name
     * of META-INF/services/de.odysseus.staxon.json.stream.JsonStreamFactory exists, then its first line,
     * if present, is used as the UTF-8 encoded name of the implementation class.</li>
     * <li>Use the properties file "lib/staxon.properties" in the JRE directory. If this file exists
     * and  is readable by the java.util.Properties.load(InputStream) method, and it contains an entry
     * whose key is "de.odysseus.staxon.json.stream.JsonStreamFactory", then the value of that entry is
     * used as the name of the implementation class.</li>
     * <li>Use the de.odysseus.staxon.json.stream.JsonStreamFactory system property. If a system property
     * with this name is defined, then its value is used as the name of the implementation class.</li>
     * <li>Use platform default: "de.odysseus.staxon.json.stream.impl.JsonStreamFactoryImpl".</li>
     * </ol>
     * </p>
     *
     * @return An instance of JsonStreamFactory.
     * @throws FactoryConfigurationError if a factory class cannot be found or instantiation fails.
     */
    public static JsonStreamFactory newFactory() throws FactoryConfigurationError {
        ClassLoader classLoader;
        try {
            classLoader = Thread.currentThread().getContextClassLoader();
        } catch (SecurityException e) {
            classLoader = JsonStreamFactory.class.getClassLoader();
        }

        String className = getMetaInfServicesClassName(JsonStreamFactory.class, classLoader);

        if (className == null || className.trim().length() == 0) {
            className = getJavaHomeLibClassName(JsonStreamFactory.class, "staxon");
        }

        if (className == null || className.trim().length() == 0) {
            try {
                className = System.getProperty(JsonStreamFactory.class.getName());
            } catch (Exception se) {
                // do nothing
            }
        }

        if (className == null || className.trim().length() == 0) {
            className = "org.apache.synapse.commons.staxon.core.json.stream.impl.JsonStreamFactoryImpl";
        }

        try {
            return (JsonStreamFactory) classLoader.loadClass(className).newInstance();
        } catch (Throwable e) {
            throw new FactoryConfigurationError("Error creating stream factory: " + e);
        }
    }

    /**
     * Create stream source.
     *
     * @param input
     * @return stream source
     * @throws IOException
     */
    public abstract JsonStreamSource createJsonStreamSource(InputStream input) throws IOException;

    /**
     * Create stream source.
     *
     * @param input
     * @param scanner JSON Scanner to use
     * @return stream source
     * @throws IOException
     */
    public abstract JsonStreamSource createJsonStreamSource(InputStream input, Constants.SCANNER scanner) throws IOException;

    /**
     * Create stream source.
     *
     * @param reader
     * @return stream source
     * @throws IOException
     */
    public abstract JsonStreamSource createJsonStreamSource(Reader reader) throws IOException;

    /**
     * Create stream source.
     *
     * @param reader
     * @param scanner JSON Scanner to use
     * @return stream source
     * @throws IOException
     */
    public abstract JsonStreamSource createJsonStreamSource(Reader reader, Constants.SCANNER scanner) throws IOException;

    /**
     * Create stream target.
     *
     * @param output
     * @param pretty
     * @return stream target
     * @throws IOException
     */
    public abstract JsonStreamTarget createJsonStreamTarget(OutputStream output, boolean pretty) throws IOException;

    /**
     * Create stream target.
     *
     * @param writer
     * @param pretty
     * @return stream target
     * @throws IOException
     */
    public abstract JsonStreamTarget createJsonStreamTarget(Writer writer, boolean pretty) throws IOException;
}
