/*
 * Copyright (C) 2004, 2005, 2006 Joe Walnes.
 * Copyright (C) 2006, 2007, 2008 XStream Committers.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 * 
 * Created on 09. May 2004 by Joe Walnes
 */
package com.thoughtworks.xstream.core;

import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.security.AccessControlException;
import java.text.AttributedString;
import java.util.HashMap;
import java.util.Map;

public class JVM {

    private ReflectionProvider reflectionProvider;
    private transient Map loaderCache = new HashMap();
    
    private final boolean supportsAWT = loadClass("java.awt.Color") != null;
    private final boolean supportsSwing = loadClass("javax.swing.LookAndFeel") != null;
    private final boolean supportsSQL = loadClass("java.sql.Date") != null; 

    private static final String vendor = System.getProperty("java.vm.vendor");
    private static final float majorJavaVersion = getMajorJavaVersion();
    private static final boolean reverseFieldOrder = isHarmony() || (isIBM() && !is15());

    static final float DEFAULT_JAVA_VERSION = 1.3f;

    /**
     * Parses the java version system property to determine the major java version,
     * i.e. 1.x
     *
     * @return A float of the form 1.x
     */
    private static final float getMajorJavaVersion() {
        try {
            return Float.parseFloat(System.getProperty("java.specification.version"));
        } catch ( NumberFormatException e ){
            // Some JVMs may not conform to the x.y.z java.version format
            return DEFAULT_JAVA_VERSION;
        }
    }

    public static boolean is14() {
        return majorJavaVersion >= 1.4f;
    }

    public static boolean is15() {
        return majorJavaVersion >= 1.5f;
    }

    public static boolean is16() {
        return majorJavaVersion >= 1.6f;
    }

    private static boolean isSun() {
        return vendor.indexOf("Sun") != -1;
    }

    private static boolean isApple() {
        return vendor.indexOf("Apple") != -1;
    }

    private static boolean isHPUX() {
        return vendor.indexOf("Hewlett-Packard Company") != -1;
    }

    private static boolean isIBM() {
    	return vendor.indexOf("IBM") != -1;
    }

    private static boolean isBlackdown() {
        return vendor.indexOf("Blackdown") != -1;
    }

    private static boolean isDiablo() {
        return vendor.indexOf("FreeBSD Foundation") != -1;
    }

    private static boolean isHarmony() {
        return vendor.indexOf("Apache Software Foundation") != -1;
    }

    /*
     * Support for sun.misc.Unsafe and sun.reflect.ReflectionFactory is present
     * in JRockit versions R25.1.0 and later, both 1.4.2 and 5.0 (and in future
     * 6.0 builds).
     */
    private static boolean isBEAWithUnsafeSupport() {
        // This property should be "BEA Systems, Inc."
        if (vendor.indexOf("BEA") != -1) {

            /*
             * Recent 1.4.2 and 5.0 versions of JRockit have a java.vm.version
             * string starting with the "R" JVM version number, i.e.
             * "R26.2.0-38-57237-1.5.0_06-20060209..."
             */
            String vmVersion = System.getProperty("java.vm.version");
            if (vmVersion.startsWith("R")) {
                /*
                 * We *could* also check that it's R26 or later, but that is
                 * implicitly true
                 */
                return true;
            }

            /*
             * For older JRockit versions we can check java.vm.info. JRockit
             * 1.4.2 R24 -> "Native Threads, GC strategy: parallel" and JRockit
             * 5.0 R25 -> "R25.2.0-28".
             */
            String vmInfo = System.getProperty("java.vm.info");
            if (vmInfo != null) {
                // R25.1 or R25.2 supports Unsafe, other versions do not
                return (vmInfo.startsWith("R25.1") || vmInfo
                        .startsWith("R25.2"));
            }
        }
        // If non-BEA, or possibly some very old JRockit version
        return false;
    }
    
    private static boolean isHitachi() {
        return vendor.indexOf("Hitachi") != -1;
    }
    
    private static boolean isSAP() {
        return vendor.indexOf("SAP AG") != -1;
    }

    public Class loadClass(String name) {
        try {
            WeakReference reference = (WeakReference) loaderCache.get(name);
            if (reference != null) {
                Class cached = (Class) reference.get();
                if (cached != null) {
                    return cached;
                }
            }
            
            Class clazz = Class.forName(name, false, getClass().getClassLoader());
            loaderCache.put(name, new WeakReference(clazz));
            return clazz;
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    public synchronized ReflectionProvider bestReflectionProvider() {
        if (reflectionProvider == null) {
            try {
                if ( canUseSun14ReflectionProvider() ) {
                    String cls = "com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider";
                    reflectionProvider = (ReflectionProvider) loadClass(cls).newInstance();
                } else if (canUseHarmonyReflectionProvider()) {
                    String cls = "com.thoughtworks.xstream.converters.reflection.HarmonyReflectionProvider";
                    reflectionProvider = (ReflectionProvider) loadClass(cls).newInstance();
                } 
                if (reflectionProvider == null) {
                    reflectionProvider = new PureJavaReflectionProvider();
                }
            } catch (InstantiationException e) {
                reflectionProvider = new PureJavaReflectionProvider();
            } catch (IllegalAccessException e) {
                reflectionProvider = new PureJavaReflectionProvider();
            } catch (AccessControlException e) {
                // thrown when trying to access sun.misc package in Applet context.
                reflectionProvider = new PureJavaReflectionProvider();
            }
        }
        return reflectionProvider;
    }

    private boolean canUseSun14ReflectionProvider() {
        return (isSun()
            || isApple()
            || isHPUX()
            || isIBM()
            || isBlackdown()
            || isBEAWithUnsafeSupport()
            || isHitachi()
            || isSAP() 
            || isDiablo())
            && is14()
            && loadClass("sun.misc.Unsafe") != null;
    }

    private boolean canUseHarmonyReflectionProvider() {
        return isHarmony();
    }

    public static boolean reverseFieldDefinition() {
        return reverseFieldOrder;
    }

    /**
     * Checks if the jvm supports awt.
     */
    public boolean supportsAWT() {
        return this.supportsAWT;
    }

    /**
     * Checks if the jvm supports swing.
     */
    public boolean supportsSwing() {
        return this.supportsSwing;
    }

    /**
     * Checks if the jvm supports sql.
     */
    public boolean supportsSQL() {
        return this.supportsSQL;
    }

    private Object readResolve() {
        loaderCache = new HashMap();
        return this;
    }
    
    public static void main(String[] args) {
        boolean reverse = false;
        Field[] fields = AttributedString.class.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getName().equals("text")) {
                reverse = i > 3;
                break;
            }
        }
        if (reverse) {
            fields = JVM.class.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                if (fields[i].getName().equals("reflectionProvider")) {
                    reverse = i > 2;
                    break;
                }
            }
        }

        JVM jvm = new JVM();
        System.out.println("XStream JVM diagnostics");
        System.out.println("java.specification.version: " + System.getProperty("java.specification.version"));
        System.out.println("java.vm.vendor: " + vendor);
        System.out.println("Version: " + majorJavaVersion);
        System.out.println("XStream support for enhanced Mode: " + (jvm.canUseSun14ReflectionProvider() || jvm.canUseHarmonyReflectionProvider()));
        System.out.println("Supports AWT: " + jvm.supportsAWT());
        System.out.println("Supports Swing: " + jvm.supportsSwing());
        System.out.println("Supports SQL: " + jvm.supportsSQL());
        System.out.println("Reverse field order detected (may have failed): " + reverse);
    }
}
