• 基础知识漫谈(4):讲讲元数据


     

    说几个风马牛不相及的词儿,spring的依赖注入定义,hibernate的数据映射定义,XML的DTD,再就是我们常说的报文格式

    如果对它们不甚了解,请参考章节一《想到哪儿写到哪儿》。有了基本的了解之后,应当隐约之中有一种感觉,“它们很相似”。

    本篇文章要说的就是这个相似性,我管它叫做数据格式元数据,DataSchemaMetaData。当然,元数据的定义是要大于数据格式的,本文将它们当成同一个概念。

    什么是数据格式?看看这段XML(1):

    <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();
    }
    View Code

    步骤二:

    有了结构说明,还需要一个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;
        }
        
    }
    View Code

    步骤三:

    要实现为一个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;
            }
        }
    }
    View Code

    扩展思考:完全把Java映射成XML是可能的,因为复杂是由简单构成,虽然Java里有大量的类型,但一直跟进到代码深处你会发现,所有的复杂的类结构,最终都是基础数据类型。可是,完全把Java映射成XML是不可行的,因为这棵定义树不知道到底有多深,有可能不是人工能完成的工作量,那么,想想,如何做到呢?

     

    最近的三篇都应用到了面向对象相关基础来分析复杂的需求,下一篇文章将专门讲解什么叫万物皆对象,并应用来解析SQL语言

  • 相关阅读:
    CSS Sprite
    使用float和display:block将内联元素转换成块元素的不同点
    [POJ 1185] 炮兵阵地
    [POJ 1947] Rebuilding Roads
    [HDU 1561] The more, The Better
    [HDU 1011] Starship Troopers
    [POJ 1155] TELE
    [HDU 2196] Computer
    [HDU 1520] Anniversary party
    [HDU 5029] Relief grain
  • 原文地址:https://www.cnblogs.com/anrainie/p/5622533.html
Copyright © 2020-2023  润新知