001    /**
002     * Copyright (C) 2009 Erik Putrycz <erik.putrycz@gmail.com>
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     *         http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package net.ep.db4o.javassist;
018    
019    import java.lang.reflect.Constructor;
020    import java.lang.reflect.Field;
021    import java.lang.reflect.Method;
022    import java.lang.reflect.Modifier;
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import javassist.CannotCompileException;
027    import javassist.ClassPool;
028    import javassist.CtClass;
029    import javassist.CtConstructor;
030    import javassist.CtField;
031    import javassist.CtMethod;
032    import javassist.CtNewMethod;
033    import net.ep.db4o.activator.TransparentActivation;
034    import net.ep.db4o.annotations.NoEnhancement;
035    
036    import com.db4o.activation.ActivationPurpose;
037    import com.db4o.activation.Activator;
038    import com.db4o.internal.Platform4;
039    import com.db4o.reflect.ReflectClass;
040    import com.db4o.reflect.ReflectField;
041    import com.db4o.reflect.ReflectMethod;
042    import com.db4o.reflect.Reflector;
043    import com.db4o.reflect.core.ConstructorSupport;
044    import com.db4o.reflect.core.ReflectConstructor;
045    import com.db4o.reflect.core.ReflectConstructorSpec;
046    import com.db4o.reflect.jdk.JavaReflectClass;
047    import com.db4o.reflect.jdk.JdkConstructor;
048    import com.db4o.reflect.jdk.JdkMethod;
049    import com.db4o.reflect.jdk.JdkReflector;
050    
051    @SuppressWarnings("unchecked")
052    public class JVSTClass implements JavaReflectClass {
053    
054            public static final String ACTIVATOR_FIELD = "_activator";
055    
056            private ReflectConstructorSpec _constructorSpec;
057    
058            // private Object[] _constructorParams;
059    
060            private final Class _enhancedClass;
061    
062            private final Class _realClazz;
063    
064            private final JVSTReflector _jVSTreflector;
065    
066            private final Reflector _reflector;
067    
068            private boolean _threadLocal;
069    
070            private PersistOnMethodPolicy _persistPolicy;
071    
072            private ActivateOnMethodPolicy _activatePolicy;
073    
074            private boolean _ignoreNullActivator;
075    
076            private static final boolean debug = false;
077    
078            public JVSTClass(Reflector reflector, JVSTReflector jreflector,
079                            ClassPool pool, Class clazz, boolean threadLocal, boolean ignoreNullActivator,
080                            PersistOnMethodPolicy persistPolicy, ActivateOnMethodPolicy activatePolicy) {
081                    if (reflector == null) {
082                            throw new NullPointerException();
083                    }
084                    if (jreflector == null) {
085                            throw new NullPointerException();
086                    }
087                    _jVSTreflector = jreflector;
088                    _reflector = reflector;
089                    _realClazz = clazz;
090                    _threadLocal = threadLocal;
091                    _ignoreNullActivator = ignoreNullActivator;
092                    _persistPolicy = persistPolicy;
093                    _activatePolicy = activatePolicy;
094                    _constructorSpec = ReflectConstructorSpec.UNSPECIFIED_CONSTRUCTOR;
095                    try {
096                            CtClass cClass = pool.get(_realClazz.getName());
097                            String ext = "TA$" + Long.toHexString(System.currentTimeMillis());
098                            if (_threadLocal)
099                                    ext = "L" + ext;
100                            if (_ignoreNullActivator)
101                                    ext = "I" + ext;
102                            CtClass _extClass = pool.makeClass(_realClazz.getName() + "_" + ext, cClass);
103                            // ACTIVATOR_FIELD
104                            CtField activatorField = null;
105                            if (_threadLocal) {
106                                    activatorField = new CtField(pool.get("java.lang.ThreadLocal"),
107                                                    ACTIVATOR_FIELD, _extClass);
108                                    _extClass.addField(activatorField);
109                                    // _extClass.addField(activatorField,"new ThreadLocal()");
110                            } else {
111                                    activatorField = new CtField(pool.get(Activator.class.getName()),
112                                                    ACTIVATOR_FIELD, _extClass);
113                                    _extClass.addField(activatorField);
114    
115                            }
116    
117                            _extClass.setSuperclass(cClass);
118                            _extClass.addInterface(pool.get(TransparentActivation.class.getName()));
119                            CtMethod bindMethod = null;
120                            if (_threadLocal) {
121                                    bindMethod = CtNewMethod.make(
122                                                    " public void bind(com.db4o.activation.Activator activator) { if ("
123                                                                    + ACTIVATOR_FIELD + " == null) " + ACTIVATOR_FIELD
124                                                                    + " = new ThreadLocal();" + ACTIVATOR_FIELD
125                                                                    + ".set(activator);}", _extClass);
126    
127                            } else {
128                                    bindMethod = CtNewMethod.make(
129                                                    " public void bind(com.db4o.activation.Activator activator) { "
130                                                                    + ACTIVATOR_FIELD + " = activator;}", _extClass);
131    
132                            }
133                            _extClass.addMethod(bindMethod);
134    
135                            List<String> extendedMethods = new ArrayList<String>();
136                            enhanceMethods(cClass, _extClass, extendedMethods);
137                            // enhance superclasses except java.lang.object
138                            while (!cClass.getSuperclass().getName().startsWith("java")) {
139                                    cClass = cClass.getSuperclass();
140                                    enhanceMethods(cClass, _extClass, extendedMethods);
141                            }
142                            boolean hasPublic = false;
143                            List<CtConstructor> consToDeclare = new ArrayList<CtConstructor>();
144                            CtConstructor defaultCons = null;
145                            for (CtConstructor cons : _extClass.getSuperclass()
146                                            .getDeclaredConstructors()) {
147                                    hasPublic = hasPublic
148                                                    || javassist.Modifier.isPublic(cons.getModifiers());
149                                    if (javassist.Modifier.isProtected(cons.getModifiers())
150                                                    || javassist.Modifier.isPublic(cons.getModifiers()))
151                                            consToDeclare.add(cons);
152                                    if (javassist.Modifier.isPublic(cons.getModifiers())
153                                                    && cons.getParameterTypes() == null)
154                                            defaultCons = cons;
155                            }
156                            for (CtConstructor cons : consToDeclare) {
157                                    CtConstructor newCons = new CtConstructor(cons.getParameterTypes(),
158                                                    _extClass);
159                                    newCons.setBody("super($$);");
160                                    newCons.setModifiers(javassist.Modifier.setPublic(cons.getModifiers()));
161                                    _extClass.addConstructor(newCons);
162                            }
163                            _extClass.makeClassInitializer();
164                            _enhancedClass = _extClass.toClass();
165                            // _enhancedClass = _realClazz;
166                            _extClass.freeze();
167                    } catch (Exception ex) {
168                            ex.printStackTrace();
169                            throw new RuntimeException("Error while enhancing "
170                                            + _realClazz.getName(), ex);
171                    }
172            }
173    
174            private void enhanceForActivation(CtClass cClass, CtClass extClass,
175                            List<String> extendedMethods) throws CannotCompileException {
176            }
177    
178            private void enhanceMethods(CtClass cClass, CtClass extClass,
179                            List<String> extendedMethods) throws CannotCompileException {
180                    enhanceForActivation(cClass, extClass, extendedMethods);
181                    for (CtMethod method : cClass.getDeclaredMethods()) {
182                            String methodID = method.getName() + "." + method.getSignature();
183                            // raise a diagnostic if one method is final
184                            // if (!extendedMethods.contains(methodID)
185                            // && Modifier.isFinal(method.getModifiers()) && _diagnostics != null)
186                            // _diagnostics.onDiagnostic(new ClassWithFinalMethod(cClass, method
187                            // .getName()));
188    
189                            if (doEnhanceMethod(method) && !extendedMethods.contains(methodID)) {
190                                    CtMethod extMethod = CtNewMethod.delegator(method, extClass);
191                                    String activationPurpose = null;
192                                    if (_activatePolicy != null && _activatePolicy.doActivate(method)) {
193                                            activationPurpose = "READ";
194                                    }
195                                    if (_persistPolicy != null && _persistPolicy.doPersist(method)) {
196                                            activationPurpose = "WRITE";
197                                    }
198                                    if (activationPurpose != null) {
199                                            String debugCode = "System.out.println(\"activating "
200                                                            + activationPurpose + " in " + method.getName()
201                                                            + " activator: \" + " + ACTIVATOR_FIELD + " + \" \");";
202                                            String activatorGetterCode = ACTIVATOR_FIELD;
203                                            if (_threadLocal) {
204                                                    activatorGetterCode = "((com.db4o.activation.Activator)"
205                                                                    + ACTIVATOR_FIELD + ".get())";
206                                            }
207                                            String testCode = "if (" + activatorGetterCode + " == null) throw new NullPointerException(\"Activator field not binded\");";
208                                            String activateCode = activatorGetterCode + ".activate("
209                                                            + ActivationPurpose.class.getName() + "." + activationPurpose
210                                                            + ");";
211                                            if (_ignoreNullActivator) {
212                                                    testCode = "";                                          
213                                                    activateCode = "if (" + activatorGetterCode + " != null) {" +  activateCode +" }" ;
214                                            }
215                                            if (debug)
216                                                    extMethod.insertBefore("{ " + debugCode + testCode + activateCode + "}");
217                                            else
218                                                    extMethod.insertBefore("{ " + testCode + activateCode + " }");
219    
220                                            extClass.addMethod(extMethod);
221                                            extendedMethods.add(methodID);
222                                    }
223                            }
224                    }
225            }
226    
227            public static boolean doEnhanceMethod(CtMethod method) {
228                    int mods = method.getModifiers();
229                    return !Modifier.isStatic(mods) && !Modifier.isNative(mods)
230                                    && !Modifier.isFinal(mods) && !Modifier.isAbstract(mods)
231                                    && !Tools.hasAnnotation(method, NoEnhancement.class);
232            }
233    
234            public ReflectClass getComponentType() {
235                    return _reflector.forClass(_realClazz.getComponentType());
236            }
237    
238            private ReflectConstructor[] getDeclaredConstructors() {
239                    try {
240                            Constructor[] constructors = _enhancedClass.getDeclaredConstructors();
241                            ReflectConstructor[] reflectors = new ReflectConstructor[constructors.length];
242                            for (int i = 0; i < constructors.length; i++) {
243                                    reflectors[i] = new JdkConstructor(_reflector, constructors[i]);
244                            }
245                            return reflectors;
246                    } catch (NoClassDefFoundError exc) {
247                            return new ReflectConstructor[0];
248                    }
249            }
250    
251            public ReflectField getDeclaredField(String name) {
252    
253                    try {
254                            return createField(_realClazz.getDeclaredField(name));
255                    } catch (Exception e) {
256                            return null;
257                    } catch (NoClassDefFoundError e) {
258                            return null;
259                    }
260            }
261    
262            private ReflectField createField(Field field) {
263                    return new JVSTField(_reflector, field);
264            }
265    
266            public ReflectField[] getDeclaredFields() {
267                    try {
268                            Field[] fields = _realClazz.getDeclaredFields();
269                            ReflectField[] reflectors = new ReflectField[fields.length];
270                            for (int i = 0; i < reflectors.length; i++) {
271                                    reflectors[i] = createField(fields[i]);
272                            }
273                            return reflectors;
274                    } catch (NoClassDefFoundError exc) {
275                            return new ReflectField[0];
276                    }
277    
278            }
279    
280            public ReflectClass getDelegate() {
281                    return this;
282            }
283    
284            public Class getJavaClass() {
285                    return _realClazz;
286            }
287    
288            public ReflectMethod getMethod(String methodName, ReflectClass[] paramClasses) {
289                    try {
290                            Method method = _realClazz.getMethod(methodName, JVSTReflector
291                                            .toNative(paramClasses));
292                            if (method == null) {
293                                    return null;
294                            }
295                            return new JdkMethod(method, reflector());
296                    } catch (Exception e) {
297                            return null;
298                    }
299            }
300    
301            public String getName() {
302                    return _realClazz.getName();
303            }
304    
305            public ReflectClass getSuperclass() {
306                    return _jVSTreflector.forClass(_realClazz.getSuperclass());
307            }
308    
309            public boolean isAbstract() {
310                    return Modifier.isAbstract(_realClazz.getModifiers());
311            }
312    
313            public boolean isArray() {
314                    return _realClazz.isArray();
315            }
316    
317            public boolean isAssignableFrom(ReflectClass type) {
318                    // if (!(type instanceof JVSTClass))
319                    // return false;
320                    return _realClazz.isAssignableFrom(JdkReflector.toNative(type));
321            }
322    
323            public boolean isCollection() {
324                    return _reflector.isCollection(this);
325            }
326    
327            public boolean isInstance(Object obj) {
328                    return _realClazz.isInstance(obj);
329            }
330    
331            public boolean isInterface() {
332                    return _realClazz.isInterface();
333            }
334    
335            public boolean isPrimitive() {
336                    return _realClazz.isPrimitive();
337            }
338    
339            public Object newInstance() {
340                    createConstructor();
341                    return _constructorSpec.newInstance();
342            }
343    
344            public Reflector reflector() {
345                    return _jVSTreflector;
346            }
347    
348            public Object[] toArray(Object obj) {
349                    return null;
350            }
351    
352            @Override
353            public String toString() {
354                    return "Proxy class for " + _realClazz + " (" + _enhancedClass.getName() + ")";
355            }
356    
357            public ReflectConstructor getSerializableConstructor() {
358                    Constructor serializableConstructor = Platform4.jdk()
359                                    .serializableConstructor(_enhancedClass);
360                    if (serializableConstructor == null) {
361                            return null;
362                    }
363                    return new JdkConstructor(_reflector, serializableConstructor);
364            }
365    
366            private void createConstructor() {
367                    if (!_constructorSpec.canBeInstantiated().isUnspecified()) {
368                            return;
369                    }
370                    _constructorSpec = ConstructorSupport.createConstructor(this,
371                                    _enhancedClass, _jVSTreflector.configuration(),
372                                    getDeclaredConstructors());
373            }
374    
375            public boolean ensureCanBeInstantiated() {
376                    createConstructor();
377                    return !_constructorSpec.canBeInstantiated().definiteNo();
378            }
379    
380            public Object nullValue() {
381                    return _jVSTreflector.nullValue(this);
382            }
383    
384            public Class getEnhancedClass() {
385                    return _enhancedClass;
386            }
387    
388    }