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  */
32 package com.jme3.export;
33 
34 import com.jme3.animation.Animation;
35 import com.jme3.effect.shapes.*;
36 import com.jme3.material.MatParamTexture;
37 import java.io.IOException;
38 import java.lang.reflect.Field;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.logging.Level;
43 import 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  */
55 public class SavableClassUtil {
56 
57     private final static HashMap<String, String> classRemappings = new HashMap<String, String>();
58 
addRemapping(String oldClass, Class<? extends Savable> newClass)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 
remapClass(String className)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 
isImplementingSavable(Class clazz)84     public static boolean isImplementingSavable(Class clazz){
85         boolean result = Savable.class.isAssignableFrom(clazz);
86         return result;
87     }
88 
getSavableVersions(Class<? extends Savable> clazz)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 
getSavableVersion(Class<? extends Savable> clazz)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 
getSavedSavableVersion(Object savable, Class<? extends Savable> desiredClass, int[] versions, int formatVersion)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      */
fromName(String className)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 
fromName(String className, List<ClassLoader> loaders)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