1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32package com.jme3.export;
33
34import com.jme3.animation.Animation;
35import com.jme3.effect.shapes.*;
36import com.jme3.material.MatParamTexture;
37import java.io.IOException;
38import java.lang.reflect.Field;
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.List;
42import java.util.logging.Level;
43import java.util.logging.Logger;
44
45/**
46 * <code>SavableClassUtil</code> contains various utilities to handle
47 * Savable classes. The methods are general enough to not be specific to any
48 * particular implementation.
49 * Currently it will remap any classes from old paths to new paths
50 * so that old J3O models can still be loaded.
51 *
52 * @author mpowell
53 * @author Kirill Vainer
54 */
55public class SavableClassUtil {
56
57    private final static HashMap<String, String> classRemappings = new HashMap<String, String>();
58
59    private static void addRemapping(String oldClass, Class<? extends Savable> newClass){
60        classRemappings.put(oldClass, newClass.getName());
61    }
62
63    static {
64        addRemapping("com.jme3.effect.EmitterSphereShape", EmitterSphereShape.class);
65        addRemapping("com.jme3.effect.EmitterBoxShape", EmitterBoxShape.class);
66        addRemapping("com.jme3.effect.EmitterMeshConvexHullShape", EmitterMeshConvexHullShape.class);
67        addRemapping("com.jme3.effect.EmitterMeshFaceShape", EmitterMeshFaceShape.class);
68        addRemapping("com.jme3.effect.EmitterMeshVertexShape", EmitterMeshVertexShape.class);
69        addRemapping("com.jme3.effect.EmitterPointShape", EmitterPointShape.class);
70        addRemapping("com.jme3.material.Material$MatParamTexture", MatParamTexture.class);
71        addRemapping("com.jme3.animation.BoneAnimation", Animation.class);
72        addRemapping("com.jme3.animation.SpatialAnimation", Animation.class);
73    }
74
75    private static String remapClass(String className) throws ClassNotFoundException {
76        String result = classRemappings.get(className);
77        if (result == null) {
78            return className;
79        } else {
80            return result;
81        }
82    }
83
84    public static boolean isImplementingSavable(Class clazz){
85        boolean result = Savable.class.isAssignableFrom(clazz);
86        return result;
87    }
88
89    public static int[] getSavableVersions(Class<? extends Savable> clazz) throws IOException{
90        ArrayList<Integer> versionList = new ArrayList<Integer>();
91        Class superclass = clazz;
92        do {
93            versionList.add(getSavableVersion(superclass));
94            superclass = superclass.getSuperclass();
95        } while (superclass != null && SavableClassUtil.isImplementingSavable(superclass));
96
97        int[] versions = new int[versionList.size()];
98        for (int i = 0; i < versionList.size(); i++){
99            versions[i] = versionList.get(i);
100        }
101        return versions;
102    }
103
104    public static int getSavableVersion(Class<? extends Savable> clazz) throws IOException{
105        try {
106            Field field = clazz.getField("SAVABLE_VERSION");
107            Class<? extends Savable> declaringClass = (Class<? extends Savable>) field.getDeclaringClass();
108            if (declaringClass == clazz){
109                return field.getInt(null);
110            }else{
111                return 0; // This class doesn't declare this field, e.g. version == 0
112            }
113        } catch (IllegalAccessException ex) {
114            IOException ioEx = new IOException();
115            ioEx.initCause(ex);
116            throw ioEx;
117        } catch (IllegalArgumentException ex) {
118            throw ex; // can happen if SAVABLE_VERSION is not static
119        } catch (NoSuchFieldException ex) {
120            return 0; // not using versions
121        }
122    }
123
124    public static int getSavedSavableVersion(Object savable, Class<? extends Savable> desiredClass, int[] versions, int formatVersion){
125        Class thisClass = savable.getClass();
126        int count = 0;
127
128        while (thisClass != desiredClass) {
129            thisClass = thisClass.getSuperclass();
130            if (thisClass != null && SavableClassUtil.isImplementingSavable(thisClass)){
131                count ++;
132            }else{
133                break;
134            }
135        }
136
137        if (thisClass == null){
138            throw new IllegalArgumentException(savable.getClass().getName() +
139                                               " does not extend " +
140                                               desiredClass.getName() + "!");
141        }else if (count >= versions.length){
142            if (formatVersion <= 1){
143                return 0; // for buggy versions of j3o
144            }else{
145                throw new IllegalArgumentException(savable.getClass().getName() +
146                                                   " cannot access version of " +
147                                                   desiredClass.getName() +
148                                                   " because it doesn't implement Savable");
149            }
150        }
151        return versions[count];
152    }
153
154    /**
155     * fromName creates a new Savable from the provided class name. First registered modules
156     * are checked to handle special cases, if the modules do not handle the class name, the
157     * class is instantiated directly.
158     * @param className the class name to create.
159     * @param inputCapsule the InputCapsule that will be used for loading the Savable (to look up ctor parameters)
160     * @return the Savable instance of the class.
161     * @throws InstantiationException thrown if the class does not have an empty constructor.
162     * @throws IllegalAccessException thrown if the class is not accessable.
163     * @throws ClassNotFoundException thrown if the class name is not in the classpath.
164     * @throws IOException when loading ctor parameters fails
165     */
166    public static Savable fromName(String className) throws InstantiationException,
167            IllegalAccessException, ClassNotFoundException, IOException {
168
169        className = remapClass(className);
170        try {
171            return (Savable) Class.forName(className).newInstance();
172        } catch (InstantiationException e) {
173            Logger.getLogger(SavableClassUtil.class.getName()).log(
174                    Level.SEVERE, "Could not access constructor of class ''{0}" + "''! \n"
175                    + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", className);
176            throw e;
177        } catch (IllegalAccessException e) {
178            Logger.getLogger(SavableClassUtil.class.getName()).log(
179                    Level.SEVERE, "{0} \n"
180                    + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", e.getMessage());
181            throw e;
182        }
183    }
184
185    public static Savable fromName(String className, List<ClassLoader> loaders) throws InstantiationException,
186            IllegalAccessException, ClassNotFoundException, IOException {
187        if (loaders == null) {
188            return fromName(className);
189        }
190
191        String newClassName = remapClass(className);
192        synchronized(loaders) {
193            for (ClassLoader classLoader : loaders){
194                try {
195                    return (Savable) classLoader.loadClass(newClassName).newInstance();
196                } catch (InstantiationException e) {
197                } catch (IllegalAccessException e) {
198                }
199
200            }
201        }
202
203        return fromName(className);
204    }
205}
206