org.photovault.replication.VersionedClassDesc.java Source code

Java tutorial

Introduction

Here is the source code for org.photovault.replication.VersionedClassDesc.java

Source

/*
  Copyright (c) 2008 Harri Kaimio
     
  This file is part of Photovault.
     
  Photovault is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
     
  Photovault is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even therrore 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 Photovault; if not, write to the Free Software Foundation,
  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

package org.photovault.replication;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 VersionedClassDesc is used to analyze and store description of methods that
 the replication framework uses to manipulate versioned classes. It parses the
 annotations in given class.
     
 @author Harri Kaimio
 @since 0.6.0
 */
public class VersionedClassDesc {

    Log log = LogFactory.getLog(VersionedClassDesc.class.getName());
    /**
     Class described by this instance
     */
    private Class clazz;

    /**
     Interface used for editing the class
     */
    private Class editorIntf;

    /**
     * Serialization method used for changes of this class
     */
    private ChangeSerializer changeSerializer;

    /**
     Method that returns history of the target obejct
     */
    private Method getHistoryMethod;

    /**
     Construct a new class description by analyzing given class
     @param clazz The class to analyze
     */
    public VersionedClassDesc(Class clazz) {
        this.clazz = clazz;
        analyzeClass(clazz);
    }

    /**
     Get the fields in the described class
     @return Read-unly set of field names
     */
    public Set<String> getFieldNames() {
        return Collections.unmodifiableSet(fields.keySet());
    }

    Collection<FieldDesc> getFields() {
        return Collections.unmodifiableCollection(fields.values());
    }

    public Class getDescribedClass() {
        return clazz;
    }

    public ChangeSerializer getChangeSerializer() {
        return changeSerializer;
    }

    /**
     Get the class that is used for converting given field between DTO format 
     and working copy representation.
     @param fieldName Name of the field
     @return DTO class for field named in in fieldName
     @throws IllegalArgumentException if the field name is not known.
     */
    public Class<? extends DTOResolver> getFieldResolverClass(String fieldName) {
        FieldDesc fd = fields.get(fieldName);
        if (fd == null) {
            throw new IllegalArgumentException("Field " + fieldName + " not found");
        }
        return fd.dtoResolverClass;
    }

    /**
     Analyze the annotations in described class and populate this object
     based on results
     @param cl the clas to analyze
     */
    private void analyzeClass(Class cl) {
        Class scl = cl.getSuperclass();
        if (scl != null) {
            analyzeClass(scl);
        }
        Versioned info = (Versioned) cl.getAnnotation(Versioned.class);
        if (info != null) {
            editorIntf = info.editor();
            Class csClass = info.changeSerializer();
            try {
                changeSerializer = (ChangeSerializer) csClass.newInstance();
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }
        }
        for (Method m : cl.getDeclaredMethods()) {
            if (m.isAnnotationPresent(ValueField.class)) {
                try {
                    ValueFieldDesc f = new ValueFieldDesc(this, m, editorIntf);
                    fields.put(f.name, f);
                } catch (NoSuchMethodException ex) {
                    log.error("Error analyzing method " + cl.getName() + "." + m.getName(), ex);
                    throw new IllegalArgumentException("Error analyzing method " + cl.getName() + "." + m.getName(),
                            ex);
                }
            } else if (m.isAnnotationPresent(SetField.class)) {
                try {
                    SetFieldDesc f = new SetFieldDesc(this, m, editorIntf);
                    fields.put(f.name, f);
                } catch (NoSuchMethodException ex) {
                    log.error("Error analyzing method " + cl.getName() + "." + m.getName(), ex);
                    throw new IllegalArgumentException("Error analyzing method " + cl.getName() + "." + m.getName(),
                            ex);
                }
            } else if (m.isAnnotationPresent(History.class)) {
                getHistoryMethod = m;
            }
        }
        if (cl.isAnnotationPresent(Versioned.class) && getHistoryMethod == null) {
            throw new IllegalStateException(
                    "Versioned class " + cl.getName() + " does not define method for accessing history");
        }
    }

    /**
     Set field value in an object of the class described by this instance
     @param target The target object
     @param fieldName Name of the field to set
     @param value New value for the field
         
     @throws IllegalArgumentException if the field name is not knowns.
     @throws IllegalStateException if the setter cannot be invoked using 
     reflection.
     */
    public void setFieldValue(Object target, String fieldName, Object value) {
        String subfield = null;
        int subFieldStart = fieldName.indexOf(".");
        if (subFieldStart > 0) {
            subfield = fieldName.substring(subFieldStart);
            fieldName = fieldName.substring(0, subFieldStart);
        }
        FieldDesc fd = fields.get(fieldName);
        if (fd == null) {
            throw new IllegalArgumentException("Field " + fieldName + " not found");
        }
        try {
            ((ValueFieldDesc) fd).setter.invoke(target, value);
        } catch (IllegalAccessException ex) {
            throw new IllegalStateException("Cannot access setter", ex);
        } catch (InvocationTargetException ex) {
            throw new IllegalStateException("InvocationTargetException while setting field", ex);
        }
    }

    /**
     Get field value in an object of the class descriobed by this instance.
     @param target Target object
     @param fieldName Name of the field
     @return Value of the field
        
     @throws IllegalArgumentException if the field name is not knowns.
     @throws IllegalStateException if the setter cannot be invoked using 
     reflection.
     */
    public Object getFieldValue(Object target, String fieldName) {
        FieldDesc fd = fields.get(fieldName);
        if (fd == null) {
            throw new IllegalArgumentException("Field " + fieldName + " not found");
        }
        try {
            return fd.getter.invoke(target);
        } catch (IllegalAccessException ex) {
            throw new IllegalStateException("Cannot access getter", ex);
        } catch (InvocationTargetException ex) {
            throw new IllegalStateException("InvocationTargetException while getting field", ex);
        }
    }

    ObjectHistory getObjectHistory(Object target) {
        try {
            return (ObjectHistory) getHistoryMethod.invoke(target);
        } catch (IllegalAccessException ex) {
            throw new IllegalStateException("Cannot access " + getHistoryMethod, ex);
        } catch (InvocationTargetException ex) {
            throw new IllegalStateException("InvocationTargetException while getting history", ex);
        }
    }

    /**
     Apply a field change to given oject
     @param target The object that is going to be change
     @param change The field change
     @param resolverFactory resolver factory used to resolve data transfer 
     objects to correct context
     */
    void applyChange(Object target, FieldChange change, DTOResolverFactory resolverFactory) {
        FieldDesc fd = fields.get(change.getName());
        if (fd != null) {
            fd.applyChange(target, change, resolverFactory);
        } else {
            log.error("Unknown field " + change.getName() + " in object " + target);
        }
    }

    /**
     Descriptors of all versioned fields of this class
     */
    private Map<String, FieldDesc> fields = new HashMap<String, FieldDesc>();

    /**
     Handlers for editor interface methods
     */
    private Map<Method, ProxyMethodHandler> editorMethodHandlers = new HashMap<Method, ProxyMethodHandler>();

    /**
     Register handler for a method in the editor interface
     @param m The method
     @param h Handler for m
     */
    void setEditorMethodHandler(Method m, ProxyMethodHandler h) {
        editorMethodHandlers.put(m, h);
    }

    /**
     Get the handler for certain method of editor interface
     @param m The editor method
     @return Handler for the method or <code>null</code> if none is defined
     */
    ProxyMethodHandler getEditorMethodHandler(Method m) {
        return editorMethodHandlers.get(m);
    }

    Class getEditorClass() {
        return editorIntf;
    }

}