1. 程式人生 > >基礎知識漫談(4):講講元資料

基礎知識漫談(4):講講元資料

說幾個風馬牛不相及的詞兒,spring的依賴注入定義,hibernate的資料對映定義,XML的DTD,再就是我們常說的報文格式

如果對它們不甚瞭解,請參考章節一《想到哪兒寫到哪兒》。有了基本的瞭解之後,應當隱約之中有一種感覺,“它們很相似”。

本篇文章要說的就是這個相似性,我管它叫做資料格式\元資料,DataSchema\MetaData。當然,元資料的定義是要大於資料格式的,本文將它們當成同一個概念。

什麼是資料格式?看看這段XML(1):

1

2

3

4

5

6

7

8

9

10

11

<Bean type=”com.nlo.pojo.User”>

 

    <Fields>

 

        <Bean name=”name” type=”java.lang.String” default=”Abby”/>

 

        <Bean name=”id” type=”int” default=”1”/>

 

    

</Fields>

 

</Bean>

 

直覺上,我們可以把它可以這個類聯絡起來:

 

class User{

    String name=”Abby”;

    int id=1;

}

 

 

對於該User類的例項(User u1=new User()),該XML和該Java類定義,都是用於描述u1這個類例項的元資料。

再深化一點兒,看看如下這段XML(2):

 

<Element name=”Bean” min=”0” max=”*”>

    <Attribute name=”type” type=”String”/>

    <Element name=”fields” min=”1” max=”1” >

        <Attribute name=”type” type=”String”/>

        <Attribute name=”name” type=”String”/>

        <Attribute name=”default” type=”Object”/>

    </Element>

</Element>

 

能不能得出結論:XML(2)是XML(1)的元資料?

如果你還是學生,相信你一定想起了教科書上的一句話:元資料的元資料是元元資料。

 

總結一下剛才所說的:

1、元資料本身可以是任何描述性質的結構,比如XML、JAVA類定義、JSON,等等。

2、只要是按照特定規範組合起來的資料,一定具備特定的資料格式。

3、元資料是個相對的概念,提到它就一定要說明,該元資料是什麼玩意兒的元資料。就像上面的XML(1),它和User例項對比的時候,是元資料,和XML(2)對比的時候,又是被描述實體。

4、同一種資料實體,可以用不同的元資料結構(XML,JAVA)來描述。

 

現在再回頭看看章節頭的內容,是不是清晰很多了?

依賴注入和資料對映定義都是框架用於描述JavaBean的,DTD是用來描述XML的,報文格式是用來描述報文的。

它們在相對意義上,都是資料格式

利用對這個概念的理解,我們可以做到“自動解析指定格式的A例項為B例項”。

這裡的A、B可以用XML、Json、Java等等替代。這裡以XML轉化為Java例項作為示例講解。

步驟一

分析Java例項元元資料,得出Java實體類定義結構如下:

類名,包含多個欄位

類欄位,包含欄位名,欄位型別,預設值

欄位型別,有很多劃分方式,這裡劃分為基本資料型別(含String),集合型別(List、Map或者陣列),引用型別(另外一個自定義格式)。

 

如何用XML來描述它們?回頭看看XML(1)的結構你就懂。

除了XML描述檔案,我們還需要一個模型來歸納它們,見介面IDataSchema。

 

 

package galaxy.ide.configurable.editor.schema;

import galaxy.ide.configurable.editor.persistent.IPersistentHelper;

import java.util.Map.Entry;
import java.util.Set;

/**
 * 資料結構介面,使用 {@link DataSchemaAnalysts}可以根據資料結構來獲取具體資料模型中的具體值
 * 
 * @author caiyu
 * @date 2014-1-13
 */
@SuppressWarnings("rawtypes")
public interface IDataSchema<T> {
    /**
     * 獲取Class
     * 
     * @return
     */
    Class<T> getOwner();

    void setOwner(Class<T> type);

    /**
     * 資料結構關鍵字
     * 
     * @return
     */
    String getId();

    /**
     * 新增欄位
     * 
     * @param memberId
     * @param member
     */
    void addField(String memberId, IDataSchema<?> member);

    /**
     * 獲取欄位
     * 
     * @param memberId
     * @return
     */
    IDataSchema<?> getField(String memberId);

    /**
     * 獲取全部的欄位
     * 
     * @return
     */
    Set<Entry<String, IDataSchema<?>>> getFieldEntrySet();

    void setName(String name);

    /**
     * 獲取名稱
     * 
     * @return
     */
    String getName();

    /**
     * 複製目標結構
     * 
     * @param schema
     */
    void copy(IDataSchema<?> schema);

    /**
     * 根據過濾器複製
     * 
     * @param schema
     * @param filters
     */
    void copy(IDataSchema<?> schema, String... filters);

    /**
     * 
     * @return
     */
    String getBundleId();

    /**
     * 設定bundleId
     * 
     * @param bundleId
     * @return
     */
    void setBundleId(String bundleId);

    /**
     * 獲取Class分類
     * 
     * @return
     */
    ClassType getType();

    /**
     * 獲取當前結構預設的持久化助手
     * 
     * @return
     */
    IPersistentHelper getDefaultPersistentHelper();

    void setDefualtPersistentHelper(IPersistentHelper persistHelper);

    IDataSchema<?> getFirstField();

    boolean containsField(String key);

    void addProperty(String key, Object property);

    /**
     * 獲取額外屬性
     * 
     * @param key
     * @return
     */
    Object getProperty(String key);

    String[] getPropertyKeys();
}

 

 

步驟二:

有了結構說明,還需要一個Xml解析工具,一個結構分析工具。

Xml解析可以使用dom4j,結構分析工具要自己寫,見介面IPersistentHelper。

 

 

package galaxy.ide.configurable.editor.persistent;

import galaxy.ide.configurable.editor.schema.IDataSchema;

/**
 * 持久化助手介面
 * <P>
 * C->Content object class</br> P->Persistent object class
 * 
 * 
 * @author caiyu
 * @date 2013-12-19
 */
public interface IPersistentHelper<C, P> {

    /**
     * 根據資料結構,從持久化物件中載入內容
     * 
     * @return
     */
    C load(P persistentTarget, IDataSchema<C> schema);

    /**
     * 根據資料結構,將內容儲存為持久化物件
     * 
     * @param content
     */
    P save(C content, IDataSchema<C> schema);

}

 

 

其中有三個物件,C和P是可以互相轉化的物件,這裡分別指Java和Xml,schema是dom4j讀取後物件化的結構描述。

參考實現如XmlPersistentHelper(部分原始碼)所示。

 

 

/**
 * 留待日後重寫,使用DataSchemaAnalysts分析
 * 
 * @author caiyu
 * @date 2014-1-7
 */
public final class XmlPersistentHelper extends
        AbstractPersistentHelper<Object, Element> {
    @SuppressWarnings("rawtypes")
    @Override
    public Object load(Element persistentTarget, IDataSchema schema) {
        // TODO load
        Object instance = deserialCustomType(persistentTarget, schema);
        if (instance != null)
            return instance;

        if (instance == null)
            instance = deserialBasicType(persistentTarget, schema);
        if (instance == null)
            instance = deserialKeyValueType(persistentTarget, schema);
        if (instance == null)
            instance = deserialBeanType(persistentTarget, schema);
        return instance;
    }

    
    @SuppressWarnings("rawtypes")
    @Override
    public Element save(Object content, IDataSchema schema) {
        Assert.isNotNull(schema);
        // TODO save
        Element root = serialCustomType(content, schema);
        if (content != null) {
            if (root == null)
                root = serialBasicType(content, schema);
            if (root == null)
                root = serialKeyValueType(content, schema);
            if (root == null)
                root = serialBeanType(content, schema);
        }
        return root;
    }

    

    public Element serialBeanType(Object content, IDataSchema<?> schema) {
        Assert.isNotNull(schema);
        Element root = DocumentFactory.getInstance().createElement(
                schema.getName());
        try {
            Element child = null;
            for (Entry<String, IDataSchema<?>> field : schema
                    .getFieldEntrySet()) {
                Field f = DataSchemaAnalysts.getFieldByName(schema.getOwner(),
                        field.getKey());
                // schema.getOwner().getDeclaredField(field.getKey());
                f.setAccessible(true);
                IDataSchema<?> subSchema = field.getValue();
                Object o = f.get(content);
                child = save(o, subSchema);
                if (child != null)
                    root.add(child);
                f.setAccessible(false);
            }
            return root;
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            throw new InvalidBeanSchemaException("can not handle schema "
                    + e.getMessage());
        }
        return null;
    }

    @SuppressWarnings("rawtypes")
    private Element serialBasicType(Object content, IDataSchema<?> schema) {
        Element root = DocumentFactory.getInstance().createElement(
                schema.getName());
        Class<?> owner = schema.getOwner();
        boolean flag = false;

        if (owner == String.class || owner == Double.class
                || owner == Long.class || owner == Float.class
                || owner == Integer.class || owner == Character.class
                || owner == Boolean.class) {
            flag = true;
            root.setText(content.toString());
        } else {
            if (List.class.isAssignableFrom(owner)) {
                Assert.isTrue(content instanceof List,
                        "unaccept content owner: " + content.getClass());
                List list = (List) content;
                seriaList(list, schema, root);
                flag = true;
            } else if (Map.class.isAssignableFrom(owner)) {
                Assert.isTrue(content instanceof Map,
                        "unaccept content owner: " + content.getClass());
                Map map = (Map) content;
                serialMap(map, schema, root);
                flag = true;
            }
        }
        if (flag)
            return root;

        return null;
    }

    @SuppressWarnings("rawtypes")
    private void serialMap(Map map, IDataSchema<?> schema, Element root) {
        String primaryKey = (String) schema
                .getProperty(ExtensionConstants.PRIMARY_KEY);
        if (primaryKey == null || primaryKey.trim().length() == 0) {
            Element child = null;
            Set<Entry<String, IDataSchema<?>>> set = schema.getFieldEntrySet();
            for (Entry<String, IDataSchema<?>> entry : set) {
                Object o = map.get(entry.getValue().getName());
                child = save(o, entry.getValue());
                if (child != null)
                    root.add(child);
            }
        } else {
            IDataSchema<?> valueSchema = schema.getFieldEntrySet().iterator()
                    .next().getValue();
            Element child = null;
            for (Object value : map.values()) {
                child = save(value, valueSchema);
                if (child != null)
                    root.add(child);
            }
        }
    }

    
    /**
     * 反序列化基本資料型別,比如String,Integer,Double等
     * 
     * @param schema
     * @param element
     * @return
     */
    private Object deserialBasicType(Element persistentTarget,
            IDataSchema<?> schema) {
        Class<?> owner = schema.getOwner();
        String value = persistentTarget == null ? null : persistentTarget
                .getText();
        ClassType type = schema.getType();
        switch (type) {
        case Map:
        case List:
            if (value == null)
                break;
            try {
                // 處理集合型資料
                if (List.class.isAssignableFrom(owner)) {
                    Object o = schema.getOwner().newInstance();
                    Assert.isTrue(o instanceof List, "unaccept content owner: "
                            + o.getClass());
                    return deserialList(persistentTarget, schema, o);
                } else if (Map.class.isAssignableFrom(owner)) {
                    Object o = schema.getOwner().newInstance();
                    Assert.isTrue(o instanceof Map, "unaccept content owner: "
                            + o.getClass());
                    return deserialMap(persistentTarget, schema, o);
                }
            } catch (InstantiationException e1) {
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            }
            break;
        default:
            return type.handle(value);
        }
        return null;
    }
    
}

 

 

步驟三:

要實現為一個JavaBean存取值,還需要提供一個數據分析工具,利用反射,從JavaBean裡抽取指定值,或者設定值。參見DataSchemaAnalysts。

 

 

/**
 * 
 * 資料結構解析器
 * 
 * @author caiyu
 * @date 2014-4-15
 */
public class DataSchemaAnalysts {
    public static Field getFieldByName(Object obj, String fieldName)
            throws NoSuchFieldException {
        Assert.isNotNull(obj, fieldName + " should not be null");
        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            try {
                return superClass.getDeclaredField(fieldName);
            } catch (Exception e) {
                // System.out.println(e.getLocalizedMessage());
            }
        }
        throw new NoSuchFieldException("can not find field: [" + fieldName
                + "] in " + obj);
    }

    public static Field getFieldByName(Class<?> clazz, String fieldName)
            throws NoSuchFieldException {
        for (Class<?> superClass = clazz; superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            try {
                return superClass.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                // System.out.println(e.getLocalizedMessage());
            }
        }
        throw new NoSuchFieldException(clazz + " " + fieldName);
    }

    public static Field[] getAllFields(Class<?> clazz) {
        List<Field> field = new ArrayList<Field>(20);
        for (Class<?> superClass = clazz; superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            field.addAll(Arrays.asList(superClass.getDeclaredFields()));
        }
        return field.toArray(new Field[0]);
    }

    /**
     * 根據父物件(parent)的資料結構(parentSchema),分析抽取關鍵字(key)對應的子物件
     * 
     * @param parentSchema
     * @param parent
     * @param key
     * @return
     * @throws NoSuchFieldException
     * @throws SecurityException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    @SuppressWarnings("rawtypes")
    public static Object analyse(IDataSchema<?> parentSchema, Object parent,
            String key) throws NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException,
            InvocationTargetException {
        ClassType type = parentSchema.getType();
        if (type == null)
            throw new AnalyseDataSchemaException("type of "
                    + parentSchema.getName() + " [" + parent
                    + "] can not be null");
        switch (type) {
        case Bean:
            Field f = getFieldByName(parent, key);
            f.setAccessible(true);
            Object o = f.get(parent);
            f.setAccessible(false);
            return o;
        case Map:
            return ((Map) parent).get(key);
        case List:
            int i = Integer.parseInt(key);
            return ((List) parent).get(i);
        case Key_Value:
            DataType dataType = parentSchema.getOwner().getAnnotation(
                    DataType.class);
            if (dataType != null && dataType.value() == DataTypeValue.MAP) {
                Method getMethod = extraMethodByAnnotation(
                        parentSchema.getOwner(), get.class);
                return getMethod.invoke(parent, key);
            }
        }
        return null;
    }

    /**
     * 抽取含有指定註解的方法
     * 
     * @param owner
     * @param annotationClass
     * @return
     */
    public static Method extraMethodByAnnotation(Class<?> owner,
            Class<? extends Annotation> annotationClass) {
        for (Method method : owner.getDeclaredMethods()) {
            Annotation t = method.getAnnotation(annotationClass);
            if (t != null) {
                return method;
            }

        }
        throw new InvalidAnnotationConfigException(owner + " has no annoation "
                + annotationClass);
    }

    public static ClassType analyseClassType(final Class<?> owner) {
        if (owner == String.class)
            return ClassType.String;
        else if (owner == Integer.class)
            return ClassType.Integer;
        else if (owner == Double.class)
            return ClassType.Double;
        else if (owner == Float.class)
            return ClassType.Float;
        else if (owner == Long.class)
            return ClassType.Long;
        else if (owner == Character.class)
            return ClassType.Character;
        else if (owner == Boolean.class)
            return ClassType.Boolean;
        else if (List.class.isAssignableFrom(owner))
            return ClassType.List;
        else if (Map.class.isAssignableFrom(owner))
            return ClassType.Map;
        else if (owner.getAnnotation(DataType.class) != null) {
            if (owner.getAnnotation(DataType.class).value() == DataTypeValue.MAP) {
                return ClassType.Key_Value;
            }
        }
        return ClassType.Bean;
    }

    /**
     * 將value應用到指定的target上
     * 
     * @param targetSchema
     * @param target
     * @param valueSchema
     * @param value
     * @throws SecurityException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void perform(IDataSchema<?> targetSchema, Object target,
            String key, Object value) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException,
            IllegalAccessException, InvocationTargetException {
        // TODO Auto-generated method stub

        ClassType type = targetSchema.getType();
        if (type == null)
            throw new AnalyseDataSchemaException("type of "
                    + targetSchema.getName() + " [" + target
                    + "] can not be null");
        switch (type) {
        case Bean:
            Field f = getFieldByName(target, key);
            f.setAccessible(true);
            if (f.getType().isPrimitive() && value == null)
                value = DataSchemaUtil.getPrimitiveDefaultValue(f.getType());
            f.set(target, value);
            f.setAccessible(false);
            break;
        case Map:
            String primaryKey = (String) targetSchema
                    .getProperty(ExtensionConstants.PRIMARY_KEY);
            if (primaryKey != null && primaryKey.length() != 0) {
                IDataSchema<?> childSchema = targetSchema.getFieldEntrySet()
                        .iterator().next().getValue();
                Object k = analyse(childSchema, value, (String) primaryKey);
                ((Map) target).put(k, value);
            } else
                ((Map) target).put(key, value);
            break;
        case List:
            if (target != null)
                ((List) target).add(value);
            else
                throw new IllegalArgumentException(
                        "XML format error, missing element "
                                + targetSchema.getName());
            break;
        case Key_Value:
            DataType dataType = targetSchema.getOwner().getAnnotation(
                    DataType.class);
            if (dataType != null && dataType.value() == DataTypeValue.MAP) {
                Method putMethod = extraMethodByAnnotation(
                        targetSchema.getOwner(), put.class);
                putMethod.invoke(target, key, value);
            }
            break;
        }
    }
}

 

擴充套件思考:完全把Java對映成XML是可能的,因為複雜是由簡單構成,雖然Java裡有大量的型別,但一直跟進到程式碼深處你會發現,所有的複雜的類結構,最終都是基礎資料型別。可是,完全把Java對映成XML是不可行的,因為這棵定義樹不知道到底有多深,有可能不是人工能完成的工作量,那麼,想想,如何做到呢?

最近的三篇都應用到了面向物件相關基礎來分析複雜的需求,下一篇文章將專門講解什麼叫萬物皆物件,並應用來解析SQL語言

特此宣告:因此文章個人認為總結的很到位,很有價值,特此轉載!轉載地址:http://www.cnblogs.com/anrainie/p/5622533.html