/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ package com.db4o.qlin; import com.db4o.foundation.*; import com.db4o.internal.*; import com.db4o.reflect.*; import com.db4o.reflect.core.*; import com.db4o.reflect.generic.*; /** * creates prototype objects for classes. Each field on prototype objects is set * to a newly created object or primitive that can be identified either by it's * identity or by an int ID that is generated by the system. Creation of fields * is recursed to the depth specified in the constructor.<br> * <br> * Allows analyzing expressions called on prototype objects to find the * underlying field that delivers the return value of the expression. Passed * expressions should not have side effects on objects, otherwise the * "prototype world" will no longer work.<br> * <br> * We plan to supply an ImmutableFieldClassLoader to instrument the code to * throw on every modification. This ClassLoader could also supply information * about all the method calls involved.<br> * <br> * For now our approach only works if expressions are directly backed by a * single field.<br> * <br> * We were inspired for this approach when we saw that Thomas Mueller managed to * map expressions to fields for his JaQu query interface, Kudos! * http://www.h2database.com/html/jaqu.html<br> * <br> * * We took the idea a bit further and made it work for all primitives except for * boolean and we plan to also get deeper expressions, collections and * interfaces working nicely. */ public class Prototypes { private final Reflector _reflector; private final Hashtable4 _prototypes = new Hashtable4(); private final boolean _ignoreTransient; private final int _recursionDepth; public Prototypes(Reflector reflector, int recursionDepth, boolean ignoreTransient){ _reflector = reflector; _recursionDepth = recursionDepth; _ignoreTransient = ignoreTransient; } public Prototypes() { this(defaultReflector(), 5, false); } /** * returns a prototype object for a specific class. */ public <T> T prototypeForClass(Class<T> clazz){ if(clazz == null){ throw new PrototypesException("Class can not be null"); } ReflectClass claxx = _reflector.forClass(clazz); if(claxx == null){ throw new PrototypesException("Not found in the reflector: " + clazz); } final String className = claxx.getName(); Prototype<T> prototype = (Prototype) _prototypes.get(className); if(prototype != null){ return prototype.object(); } prototype = new Prototype(claxx); _prototypes.put(className, prototype); return prototype.object(); } /** * analyzes the passed expression and tries to find the path to the * backing field that is accessed. */ public <T> Iterator4<String> backingFieldPath(Class<T> clazz, Object expression){ return backingFieldPath(_reflector.forClass(clazz), expression); } /** * analyzes the passed expression and tries to find the path to the * backing field that is accessed. */ public <T> Iterator4<String> backingFieldPath(ReflectClass claxx, Object expression){ return backingFieldPath(claxx.getName(), expression); } /** * analyzes the passed expression and tries to find the path to the * backing field that is accessed. */ public <T> Iterator4<String> backingFieldPath(String className, Object expression){ Prototype prototype = (Prototype) _prototypes.get(className); if(prototype == null){ return null; } return prototype.backingFieldPath(_reflector, expression); } private class Prototype <T> { private final IdentityHashtable4 _fieldsByIdentity = new IdentityHashtable4(); private final Hashtable4 _fieldsByIntId = new Hashtable4(); private final T _object; private int intIdGenerator; public Prototype(final ReflectClass claxx){ _object = (T) claxx.newInstance(); if(_object == null){ throw new PrototypesException("Prototype could not be created for class " + claxx.getName()); } analyze(_object, claxx, _recursionDepth, null); } private void analyze(final Object object, final ReflectClass claxx, final int depth, final List4 parentPath) { if(depth < 0){ return; } ReflectorUtils.forEachField(claxx, new Procedure4<ReflectField>() { public void apply(ReflectField field) { if(field.isStatic()){ return; } if(_ignoreTransient && field.isTransient()){ return; } ReflectClass fieldType = field.getFieldType(); List4 path = new List4(parentPath, field); IntegerConverter converter = integerConverterforClassName(claxx.reflector(), fieldType.getName()); if(converter != null){ int id = ++intIdGenerator; Object integerRepresentation = converter.fromInteger(id); if (! trySetField(field, object, integerRepresentation)){ return; } _fieldsByIntId.put(id, new Pair(integerRepresentation, path)); return; } if(! fieldType.isPrimitive()){ Object identityInstance = fieldType.newInstance(); if(identityInstance == null){ return; } if (! trySetField(field, object, identityInstance)){ return; } _fieldsByIdentity.put(identityInstance, path); analyze(identityInstance, claxx, depth - 1, path); } } }); } public T object(){ return _object; } public Iterator4 <String> backingFieldPath(Reflector reflector, Object expression) { if(expression == null){ return null; } ReflectClass claxx = reflector.forObject(expression); if(claxx == null){ return null; } IntegerConverter converter = integerConverterforClassName(reflector, claxx.getName()); if(converter != null){ Pair entry = (Pair)_fieldsByIntId.get(converter.toInteger(expression)); if(entry == null){ return null; } if(entry.first.equals(expression)){ return asIterator((List4) entry.second); } return null; } if(claxx.isPrimitive()){ return null; } return asIterator((List4)_fieldsByIdentity.get(expression)); } private Iterator4 <String> asIterator(List4 lastElement){ return Iterators.revert( Iterators.map(Iterators.iterate(lastElement), new Function4<ReflectField, String>() { public String apply(ReflectField field) { return field.getName(); } })); } } private static IntegerConverter integerConverterforClassName(Reflector reflector, String className){ if(_integerConverters == null){ _integerConverters = new Hashtable4(); IntegerConverter[] converters = new IntegerConverter[]{ new IntegerConverter(){ public String primitiveName() {return int.class.getName();} public Object fromInteger(int i) {return new Integer(i);} }, new IntegerConverter(){ public String primitiveName() {return long.class.getName();} public Object fromInteger(int i) {return new Long(i);} }, new IntegerConverter(){ public String primitiveName() {return double.class.getName();} public Object fromInteger(int i) {return new Double(i);} }, new IntegerConverter(){ public String primitiveName() {return float.class.getName();} public Object fromInteger(int i) {return new Float(i);} }, new IntegerConverter(){ public String primitiveName() {return byte.class.getName();} public Object fromInteger(int i) {return new Byte((byte)i);} }, new IntegerConverter(){ public String primitiveName() {return char.class.getName();} public Object fromInteger(int i) {return new Character((char)i);} }, new IntegerConverter(){ public String primitiveName() {return short.class.getName();} public Object fromInteger(int i) {return new Short((short)i);} }, new IntegerConverter(){ public String primitiveName() {return String.class.getName();} public Object fromInteger(int i) {return STRING_IDENTIFIER + i;} @Override public int toInteger(Object obj) { if(! (obj instanceof String)){ return -1; } String str = (String)obj; if(str.length() < STRING_IDENTIFIER.length()){ return -1; } if(str.indexOf(STRING_IDENTIFIER) != 0){ return -1; } return Integer.parseInt(str.substring(STRING_IDENTIFIER.length())); } }, }; for (IntegerConverter converter : converters) { _integerConverters.put(converter.primitiveName(), converter); if(! converter.primitiveName().equals(converter.wrapperName(reflector))){ _integerConverters.put(converter.wrapperName(reflector), converter); } } } return (IntegerConverter) _integerConverters.get(className); } private static Hashtable4 _integerConverters; private static abstract class IntegerConverter <T> { public String wrapperName(Reflector reflector){ return reflector.forObject(fromInteger(1)).getName(); } public abstract String primitiveName(); public abstract T fromInteger(int i); public int toInteger(T obj){ return Integer.parseInt(obj.toString()); } } // Strings get prepended the following, so we can also use strings // without restrictions in queries. private static final String STRING_IDENTIFIER = "QLinIdentity"; public Reflector reflector(){ return _reflector; } // We could always use this, but we want to make users of this class // aware that they have control over the reflector and that it is // important. public static Reflector defaultReflector(){ return new GenericReflector(Platform4.reflectorForType(Prototypes.class)); } private static boolean trySetField(ReflectField field, Object onObject, Object value) { try{ field.set(onObject, value); } catch (Throwable t){ return false; } return true; } }