All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.draagon.meta.loader.file.FileMetaDataParser Maven / Gradle / Ivy

package com.draagon.meta.loader.file;

import com.draagon.meta.MetaData;
import com.draagon.meta.MetaDataException;
import com.draagon.meta.MetaDataNotFoundException;
import com.draagon.meta.attr.MetaAttribute;
import com.draagon.meta.attr.StringAttribute;
import com.draagon.meta.loader.MetaDataLoader;
import com.draagon.meta.loader.types.ChildConfig;
import com.draagon.meta.loader.types.TypesConfig;
import com.draagon.meta.loader.types.TypeConfig;
import com.draagon.meta.util.MetaDataUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import static com.draagon.meta.util.MetaDataUtil.expandPackageForPath;

/**
 * Absract FileMetaDataParser for reading from source files
 */
public abstract class FileMetaDataParser {

    private static Log log = LogFactory.getLog(FileMetaDataParser.class);

    public final static String ATTR_METADATA        = "metadata";
    public final static String ATTR_TYPESCONFIG     = "typesConfig";
    public final static String ATTR_TYPES           = "types";
    public final static String ATTR_PACKAGE         = "package";
    public final static String ATTR_DEFPACKAGE      = "defaultPackage";
    public final static String ATTR_CHILDREN        = "children";
    public final static String ATTR_NAME            = "name";
    public final static String ATTR_DEFNAME         = "defaultName";
    public final static String ATTR_DEFNAMEPREFIX   = "defaultNamePrefix";
    public final static String ATTR_CLASS           = "class";
    public final static String ATTR_TYPE            = "type";
    public final static String ATTR_SUBTYPE         = "subType";
    public final static String ATTR_SUBTYPES        = "subTypes";
    public final static String ATTR_DEFSUBTYPE      = "defaultSubType";
    public final static String ATTR_SUPER           = "super";
    public final static String ATTR_VALUE           = "value";

    protected static List reservedAttributes = new ArrayList<>();
    static {
        reservedAttributes.add( ATTR_PACKAGE );
        reservedAttributes.add( ATTR_NAME );
        reservedAttributes.add( ATTR_CLASS );
        //reservedAttributes.add( ATTR_TYPES );
        reservedAttributes.add( ATTR_CHILDREN );
        reservedAttributes.add( ATTR_TYPE );
        reservedAttributes.add( ATTR_SUBTYPE );
        reservedAttributes.add( ATTR_SUBTYPES );
        reservedAttributes.add( ATTR_SUPER );
        reservedAttributes.add( ATTR_VALUE );
    }

    private FileMetaDataLoader loader;
    private String filename;
    private String defaultPackageName = "";

    protected class ParserInfoMsg {

        public final Map types = new TreeMap <>();
        public final Map data = new TreeMap <>();

        public ParserInfoMsg() {}
        public int incType( String n ) { return incMap( types, n ); }
        public int incData( String n ) { return incMap( data, n ); }


        public int incMap( Map map, String n ) {
            synchronized(map) {
                if ( map.get(n) == null ) {
                    map.put(n, 1);
                    return 1;
                } else {
                    Integer i = map.get(n);
                    map.put(n, ++i);
                    return i;
                }
            }
        }
    }

    protected ParserInfoMsg info = new ParserInfoMsg();

    /** Create the FileMetaDataParser */
    protected FileMetaDataParser(FileMetaDataLoader loader, String filename ) {
        this.loader = loader;
        this.filename = filename;
    }

    /** Return the FileMetaDataLoader */
    public FileMetaDataLoader getLoader() {
        return this.loader;
    }

    /** Return the filename being loaded */
    public String getFilename() {
        return filename;
    }

    /** Set default package name */
    protected void setDefaultPackageName(String defPkg ) {
        this.defaultPackageName = defPkg;
    }

    /** Set default package name */
    protected String getDefaultPackageName() {
        return defaultPackageName;
    }

    /** Load the metadata models from the inputstream */
    public abstract void loadFromStream( InputStream is );

    /** Get the MetaDataTypes from the loader's MetaDataConfig */
    public TypesConfig getTypesConfig() {
        return this.loader.getTypesConfig();
    }

    /**
     * Get or Create a Type Configuration
     * @param typeName Name of the metadata type
     * @param typeClass MetaData type class
     * @return The create TypeModel
     */
    protected TypeConfig getOrCreateTypeConfig(String typeName, String typeClass) {

        if ( typeName == null || typeName.isEmpty() ) {
            throw new MetaDataException( "MetaData Type was null or empty ["+typeName+"] in file [" +getFilename()+ "]");
        }

        // Get the TypeModel with the specified element name
        TypeConfig typeConfig = getTypesConfig().getTypeByName( typeName );

        // If it doesn't exist, then create it and check for the "class" attribute
        if ( typeConfig == null ) {

            if ( typeClass == null || typeClass.isEmpty() )
                throw new MetaDataException( "MetaData Type [" + typeName + "] has no 'class' attribute specified in file [" +getFilename()+ "]");

            // Add a new TypeModel and add to the mapping
            typeConfig = getTypesConfig().createAndAddType( typeName, typeClass );
        }
        return typeConfig;
    }

    /** Get the default package from the element */
    protected String parsePackageValue( String value ) {

        String defaultPackageName = value;
        if (defaultPackageName == null || defaultPackageName.trim().length() == 0) {
            defaultPackageName = "";
        }
        return defaultPackageName;
    }

    protected String getNextNamePrefix( MetaData parent, String typeName, String prefix ) {
        int i = 1;
        for( MetaData md : (List) parent.getChildrenOfType( typeName, true )) {
            if ( md.getName().startsWith( prefix )) {
                try {
                    int n = Integer.parseInt(md.getName().substring(prefix.length()));
                    if ( n >= i ) i = n+1;
                }
                catch( NumberFormatException ignore ) {}
            }
        }
        return prefix + i;
    }

    /** Create or Overlay the MetaData */
    protected MetaData createOrOverlayMetaData( boolean isRoot, MetaData parent, String typeName, String subTypeName, String name, String packageName, String superName) {

        if ( subTypeName != null && subTypeName.equals("*")) subTypeName = null;

        // Get the TypeModel map for this element
        TypeConfig types = getTypesConfig().getTypeByName( typeName );
        if ( types == null ) {
            // TODO:  What is the best behavior here?
            throw new MetaDataException( "Unknown type [" +typeName+ "] found on parent [" +parent+ "] in file [" +getFilename()+ "]" );
        }

        if (name == null || name.equals("")) {
            name = types.getDefaultName();
            if ( name == null ) {
                String prefix = types.getDefaultNamePrefix();
                if ( prefix != null ) {
                    name = getNextNamePrefix(parent, typeName, prefix);
                }
                if ( name == null ) throw new MetaDataException("MetaData [" +typeName+ "] found on parent [" +parent
                        + "] had no name specfied and no defaultName existed in file ["+getFilename()+"]");
            }
        }

        // Load or get the MetaData
        MetaData md = null;

        if (packageName == null || packageName.trim().isEmpty()) {
            // If not found, then use the default
            packageName = getDefaultPackageName();
        } else {
            // Convert any relative paths to the full package path
            packageName = expandPackageForPath( getDefaultPackageName(), packageName );
        }

        // Check if the metadata described already existed, and if so create and overloaded version
        try {
            if ( isRoot && packageName.length() > 0 ) {
                md = parent.getChild( packageName + MetaDataLoader.PKG_SEPARATOR + name, types.getMetaDataClass() );
            } else {
                md = parent.getChild( name, types.getMetaDataClass() );

                // If it's not a child from the same parent, we need to wrap it
                if ( md.getParent() != parent ) {
                    md = md.overload();
                    parent.addChild(md);
                }
            }
        }
        catch (MetaDataNotFoundException e) {
            // Handle cases where it's bad that it wasn't found
        }

        // If this MetaData doesn't exist yet, then we need to create it
        if (md == null) {

            // Get the super metadata if it exists
            MetaData superData = getSuperMetaData(parent, typeName, name, packageName, superName, types);

            // Create the new MetaData
            md = createNewMetaData(isRoot, parent, typeName, subTypeName, name, packageName, types, superData);

            // Add to the parent metadata
            parent.addChild(md);

            // Set the super data class if one exists
            if (superData != null) {
                md.setSuperData(superData);
            }
        }

        return md;
    }

    /** Get the Super MetaData if it exists */
    protected MetaData getSuperMetaData(MetaData parent, String typeName, String name, String packageName, String superName, TypeConfig types ) {

        MetaData superData = null;

        // If a super class was specified
        if (superName != null && !superName.isEmpty()) {

            // Try to find it with the name prepended if not fully qualified
            try {
                if (superName.indexOf(MetaDataLoader.PKG_SEPARATOR) < 0 && packageName.length() > 0) {

                    superData = getLoader().getChild(packageName + MetaDataLoader.PKG_SEPARATOR + superName, types.getMetaDataClass() );
                }
            } catch (MetaDataNotFoundException e) {
                // TODO:  Should this throw a real exception
                log.debug("Could not find MetaData [" + packageName + MetaDataLoader.PKG_SEPARATOR + superName + "], assuming fully qualified");
            }

            // Try to find it by the provided name in the 'super' attribute
            if (superData == null) {
                String fullyQualifiedSuperName = getFullyQualifiedSuperMetaDataName(parent, packageName, superName);
                try {
                    superData = getLoader().getChild(fullyQualifiedSuperName, types.getMetaDataClass());
                }
                catch (MetaDataNotFoundException e) {
                    //log.info( "packageName="+packageName+", parentPkg="+(parent==null?null:parent.getPackage())
                    //        +", pkg="+pkg+", superName="+superName+", sn="+sn);
                    //log.error("Invalid MetaData [" +typeName+ "][" +name+ "] on parent ["+parent+"], the SuperClass [" + superName + "] does not exist in file ["+getFilename()+"]");
                    throw new MetaDataException("Invalid MetaData [" +typeName+ "][" +name+ "] on parent ["+parent
                            +"], the SuperClass [" + superName + "] does not exist in file ["+getFilename()+"]");
                }
            }
        }
        // Check to make sure people arent' defining attributes when it shouldn't
        else {
            if (superName != null && !superName.isEmpty()) {
                logErrorOnce( parent, "getSuperMetaData("+typeName+","+name+")",
                        "Attribute 'super' defined on MetaData [" +typeName+ "][" +name+ "] under parent [" +parent
                                + "], but should not be as metadata with that name already existed: file ["+getFilename()+"]");
            }
        }

        return superData;
    }

    /** Get the fully qualified metadata name for the Super MetaData */
    protected String getFullyQualifiedSuperMetaDataName(MetaData parent, String packageName, String superName) {

        if (shouldUseParentPackage(parent, packageName)) {
            packageName = parent.getPackage();
        }
        return MetaDataUtil.expandPackageForMetaDataRef(packageName, superName);
    }

    /** Determine if the packageName should change based on the parent metadata */
    protected boolean shouldUseParentPackage( MetaData parent, String packageName ) {
        // TODO:  This may need to be refactored
        return parent != null
                && !(parent instanceof MetaDataLoader)
                && !parent.getPackage().isEmpty()
                && !parent.getPackage().equals( packageName );
    }

    /** Create new MetaData */
    protected MetaData createNewMetaData(boolean isRoot, MetaData parent, String typeName, String subTypeName, String name, String packageName, TypeConfig typeConfig, MetaData superData) {

        if (subTypeName != null && subTypeName.isEmpty()) subTypeName = null;

        Class c = null;

        // Attempt to load the referenced class
        if (subTypeName == null) {

            // Use the Super class type if no type is defined and a super class exists
            if (superData != null) {
                c = superData.getClass();
                subTypeName = superData.getSubTypeName();
            }
            else {
                if ( isRoot ) {
                    subTypeName = getSubTypeFromChildConfigs(ATTR_METADATA, null, typeConfig, name);
                } else {
                    subTypeName = getSubTypeFromChildConfigs(parent.getTypeName(), parent.getSubTypeName(), typeConfig, name);
                }

                if ( subTypeName == null ) {
                    subTypeName = typeConfig.getDefaultSubType();
                    c = typeConfig.getDefaultTypeClass();
                }
                else {
                    c = (Class) typeConfig.getSubTypeClass(subTypeName);
                }
                if (c == null) {
                    throw new MetaDataException("MetaData [type=" + typeName + "][name=" + name
                            + "] has no subtype defined and type [" + typeName + "] had no default specified in file ["
                            + getFilename() + "]");
                }
            }
        } else {
            c = (Class) typeConfig.getSubTypeClass(subTypeName);
        }

        if (c == null) {
            throw new MetaDataException("MetaData [" + typeName + "] had type [" + subTypeName
                    + "], but it was not recognized in file ["+getFilename()+"]");
        }

        // Figure out the full name for the element, needs package prefix if root
        // TODO: Clean this up and go to caller where it exists as well
        String fullname = isRoot ? packageName + MetaDataLoader.PKG_SEPARATOR + name : name;

        // Use the parent type child records to verify this metadata child is acceptable
        if ( isRoot ) {
            verifyAcceptableChild( ATTR_METADATA, null, typeName, subTypeName, name);
        } else {
            verifyAcceptableChild(parent.getTypeName(), parent.getSubTypeName(), typeName, subTypeName, name);
        }

        // Create the object
        MetaData md= getLoader().newInstanceFromClass(c, typeName, subTypeName, fullname);

        return md;
    }


    protected String getSubTypeFromChildConfigs( String parentType, String parentSubType, TypeConfig typeConfig, String name ) {

        TypeConfig tc = getTypesConfig ().getTypeByName( parentType );

        ChildConfig cc = findBestChildConfigMatch( tc, parentType, parentSubType,
                typeConfig.getName(), null, name );

        if ( cc == null ) return null;

        return ( cc.getSubType().equals("*")) ? null : cc.getSubType();
    }


    protected void verifyAcceptableChild( String parentType, String parentSubType, String type, String subType, String name ) {

        TypeConfig tc = getTypesConfig().getTypeByName( parentType );

        ChildConfig cc = findBestChildConfigMatch( tc, parentType, parentSubType, type, subType, name );

        if (cc == null) {
            if ( getLoader().getLoaderOptions().isStrict() ) {
                throw new MetaDataException( "Child record ["+type+":"+subType+"] with name ["+name+"] is not allowed"
                        +" on parent records ["+parentType+":"+parentSubType+"] in file ["+getFilename()+"]" );
            }
            else if ( type.equals( MetaAttribute.TYPE_ATTR ) && getLoader().getLoaderOptions().allowsAutoAttrs() ) {
                // Auto add the new child configuration
                cc = tc.createChildConfig( type, subType, name );
                cc.setAutoCreatedFromFile( getFilename() );
                tc.addTypeChildConfig( cc );
            }
            else {
                //log.info( "LOADER: " + getLoader() );
                //log.info( "LOADER CONFIG: " + getLoader().getLoaderConfig() );
                String errMsg = "Child record ["+type+":"+subType+"] with name ["+name+"] was not configured"
                        +" for parent records ["+parentType+":"+parentSubType+"]: file ["+getFilename()+"]";
                logWarnOnce( getLoader(), "verifyAcceptableChild("+parentType+","+parentSubType+","+
                        type+","+subType+","+name+")", errMsg );
            }
        }
    }

    protected void logErrorOnce( MetaData parent, String KEY, String errMsg ) {

        if ( log.isErrorEnabled() ) {
            Object v = parent.getCacheValue( KEY );
            if ( v == null ) {
                log.error( errMsg );
                parent.setCacheValue( KEY, Boolean.TRUE );
            }
        }
    }


    protected void logWarnOnce( MetaData parent, String KEY, String errMsg ) {

        if ( log.isWarnEnabled() ) {
            Object v = parent.getCacheValue( KEY );
            if ( v == null ) {
                log.warn( errMsg );
                parent.setCacheValue( KEY, Boolean.TRUE );
            }
        }
    }

    protected ChildConfig findBestChildConfigMatch(TypeConfig parentTypeConfig, String parentType, String parentSubType, String type, String subType, String name ) {

        ChildConfig cc = null;
        TypeConfig tc = parentTypeConfig;

        List ccList;// Check for best match on subtype
        if ( parentSubType != null ) {
            ccList = tc.getSubTypeChildConfigs(parentSubType);
            if ( ccList != null ) {
                cc = tc.getBestMatchChildConfig(ccList, type, subType, name);
            }
        }

        if ( cc == null ) {

            // Check for best match type level
            ccList = tc.getTypeChildConfigs();
            cc = tc.getBestMatchChildConfig(ccList, type, subType, name);
        }

        return cc;
    }

    protected void createAttributeOnParent(MetaData parentMetaData, String attrName, String value) {

        String parentType = parentMetaData.getTypeName();
        String parentSubType = parentMetaData.getSubTypeName();

        TypeConfig parentTypeConfig = getTypesConfig().getTypeByName( parentMetaData.getTypeName() );
        ChildConfig cc = findBestChildConfigMatch( parentTypeConfig, parentType, parentSubType,
                MetaAttribute.TYPE_ATTR, null, attrName );

        MetaAttribute attr = null;

        if ( cc == null ) {
            String errMsg = "MetaAttribute with name ["+attrName+"] is not allowed on parent record ["
                    +parentType+":"+parentSubType+":"+parentMetaData.getName()+"] in file ["+getFilename()+"]";

            if ( getLoader().getLoaderOptions().allowsAutoAttrs() ) {
                cc = parentTypeConfig.createChildConfig( StringAttribute.TYPE_ATTR, StringAttribute.SUBTYPE_STRING, attrName );
                cc.setAutoCreatedFromFile( getFilename() );
            }
            else if ( getLoader().getLoaderOptions().isStrict() ) {
                throw new MetaDataException( errMsg );
            }
            else {
                logWarnOnce( parentMetaData, "createAttributeOnParent("+attrName+")", errMsg );

                attr = new StringAttribute( attrName );
                parentMetaData.addChild( attr );
            }
        }

        if ( attr == null && cc != null ) {
            attr = (MetaAttribute) createOrOverlayMetaData(parentType.equals(MetaDataLoader.TYPE_LOADER), parentMetaData,
                    cc.getType(), cc.getSubType(), attrName /*cc.getName()*/, null, null);
        }

        if ( attr != null ) {
            attr.setValueAsString(value);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy