/* 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;
}
}