<commit_before>package org.intermine.web.logic.config;

/*
 * Copyright (C) 2002-2011 FlyMine
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  See the LICENSE file for more
 * information or http://www.gnu.org/copyleft/lesser.html.
 *
 */

import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Properties;

import javax.servlet.ServletContext;

import org.apache.commons.digester.Digester;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.intermine.metadata.ClassDescriptor;
import org.intermine.metadata.FieldDescriptor;
import org.intermine.metadata.Model;
import org.intermine.pathquery.Path;
import org.intermine.pathquery.PathException;
import org.intermine.web.logic.widget.config.EnrichmentWidgetConfig;
import org.intermine.web.logic.widget.config.GraphWidgetConfig;
import org.intermine.web.logic.widget.config.HTMLWidgetConfig;
import org.intermine.web.logic.widget.config.TableWidgetConfig;
import org.intermine.web.logic.widget.config.WidgetConfig;
import org.intermine.web.logic.session.SessionMethods;
import org.xml.sax.SAXException;

/**
 * Configuration object for web site
 *
 * @author <NAME>
 */
public class WebConfig
{
    private static final Logger LOG = Logger.getLogger(WebConfig.class);
    private Map<String, Type> types = new TreeMap<String, Type>();
    private Map<String, TableExportConfig> tableExportConfigs =
        new HashMap<String, TableExportConfig>();
    private Map<String, WidgetConfig> widgets = new HashMap<String, WidgetConfig>();
    private List<ReportDisplayerConfig> reportDisplayerConfigs =
        new ArrayList<ReportDisplayerConfig>();

    /**
     * Parse a WebConfig XML file
     *
     * @param is the InputStream to parse
     * @param model the Model to use when reading - used for checking class names and for finding
     * sub and super classes
     * @return a WebConfig object
     * @throws SAXException if there is an error in the XML file
     * @throws IOException if there is an error reading the XML file
     * @throws ClassNotFoundException if a class is mentioned in the XML that isn't in the model
     */
    public static WebConfig parse(ServletContext context, Model model)
        throws IOException, SAXException, ClassNotFoundException, FileNotFoundException {

    	BasicConfigurator.configure();

        InputStream webconfXML = context.getResourceAsStream("/WEB-INF/webconfig-model.xml");
        if (webconfXML == null)
            throw new FileNotFoundException("Could not find webconfig-model.xml");

        Digester digester = new Digester();
        digester.setValidating(false);

        digester.addObjectCreate("webconfig", WebConfig.class);

        digester.addObjectCreate("webconfig/class", Type.class);
        digester.addSetProperties("webconfig/class", "className", "className");
        digester.addSetProperties("webconfig/class", "fieldName", "fieldName");

        /* configure how the "title" of an object is displayed on Type */
        digester.addObjectCreate("webconfig/class/headerconfig/titles", HeaderConfigTitle.class);
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "mainTitles", "mainTitles");
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "subTitles", "subTitles");
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "numberOfMainTitlesToShow", "numberOfMainTitlesToShow");
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "appendConfig", "appendConfig");
        digester.addSetNext("webconfig/class/headerconfig/titles", "addHeaderConfigTitle");

        digester.addObjectCreate("webconfig/class/headerconfig/customlinks",
                HeaderConfigLink.class);
        digester.addSetProperties("webconfig/class/headerconfig/customlinks/customlink",
                "url", "url");
        digester.addSetProperties("webconfig/class/headerconfig/customlinks/customlink",
                "text", "text");
        digester.addSetProperties("webconfig/class/headerconfig/customlinks/customlink",
                "image", "image");
        digester.addSetNext("webconfig/class/headerconfig/customlinks", "addHeaderConfigLink");

        digester.addObjectCreate("webconfig/class/tabledisplayer", Displayer.class);
        digester.addSetProperties("webconfig/class/tabledisplayer", "src", "src");
        digester.addSetNext("webconfig/class/tabledisplayer", "setTableDisplayer");

        digester.addCallMethod("webconfig/class/tabledisplayer/param", "addParam", 2);
        digester.addCallParam("webconfig/class/tabledisplayer/param", 0, "name");
        digester.addCallParam("webconfig/class/tabledisplayer/param", 1, "value");

        digester.addObjectCreate("webconfig/class/fields/fieldconfig", FieldConfig.class);
        digester.addSetProperties("webconfig/class/fields/fieldconfig", "fieldExpr", "fieldExpr");
        digester.addSetProperties("webconfig/class/fields/fieldconfig", "name", "name");
        digester.addSetProperties("webconfig/class/fields/fieldconfig", "displayer", "displayer");
        digester.addSetNext("webconfig/class/fields/fieldconfig", "addFieldConfig");

        digester.addObjectCreate("webconfig/class/longdisplayers/displayer", Displayer.class);
        digester.addSetProperties("webconfig/class/longdisplayers/displayer");
        digester.addSetNext("webconfig/class/longdisplayers/displayer", "addLongDisplayer");

        digester.addCallMethod("webconfig/class/longdisplayers/displayer/param", "addParam", 2);
        digester.addCallParam("webconfig/class/longdisplayers/displayer/param", 0, "name");
        digester.addCallParam("webconfig/class/longdisplayers/displayer/param", 1, "value");

        /* display inline tables as inline lists instead */
        digester.addObjectCreate("webconfig/class/inlinelist/table", InlineList.class);
        digester.addSetProperties("webconfig/class/inlinelist/table");
        digester.addSetNext("webconfig/class/inlinelist/table", "addInlineList");
        digester.addSetProperties("webconfig/class/inlinelist/table", "path", "path");
        digester.addSetProperties("webconfig/class/inlinelist/table",
                "showLinksToObjects", "showLinksToObjects");
        digester.addSetProperties("webconfig/class/inlinelist/table",
                "showInHeader", "showInHeader");
        digester.addSetProperties("webconfig/class/inlinelist/table", "lineLength", "lineLength");


        digester.addObjectCreate("webconfig/class/bagdisplayers/displayer", Displayer.class);
        digester.addSetProperties("webconfig/class/bagdisplayers/displayer");
        digester.addSetNext("webconfig/class/bagdisplayers/displayer", "addBagDisplayer");

        digester.addCallMethod("webconfig/class/bagdisplayers/displayer/param", "addParam", 2);
        digester.addCallParam("webconfig/class/bagdisplayers/displayer/param", 0, "name");
        digester.addCallParam("webconfig/class/bagdisplayers/displayer/param", 1, "value");

        digester.addObjectCreate("webconfig/widgets/graphdisplayer", GraphWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/graphdisplayer");
        digester.addSetNext("webconfig/widgets/graphdisplayer", "addWidget");

        digester.addObjectCreate("webconfig/widgets/enrichmentwidgetdisplayer",
                EnrichmentWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/enrichmentwidgetdisplayer");
        digester.addSetNext("webconfig/widgets/enrichmentwidgetdisplayer", "addWidget");

        digester.addObjectCreate("webconfig/widgets/bagtabledisplayer", TableWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/bagtabledisplayer");
        digester.addSetNext("webconfig/widgets/bagtabledisplayer", "addWidget");

        digester.addObjectCreate("webconfig/widgets/htmldisplayer", HTMLWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/htmldisplayer");
        digester.addSetNext("webconfig/widgets/htmldisplayer", "addWidget");

        digester.addSetNext("webconfig/class", "addType");

        digester.addObjectCreate("webconfig/tableExportConfig", TableExportConfig.class);
        digester.addSetProperties("webconfig/tableExportConfig", "id", "id");
        digester.addSetProperties("webconfig/tableExportConfig", "className", "className");

        digester.addSetNext("webconfig/tableExportConfig", "addTableExportConfig");

        digester.addObjectCreate("webconfig/reportdisplayers/reportdisplayer",
                ReportDisplayerConfig.class);
        digester.addSetProperties("webconfig/reportdisplayers/reportdisplayer");
        digester.addSetNext("webconfig/reportdisplayers/reportdisplayer", "addReportDisplayer");

        WebConfig webConfig = (WebConfig) digester.parse(webconfXML);

        webConfig.validate(model);

        webConfig.setSubClassConfig(model);

        webConfig.loadLabelsFromMappingsFile(context, model);

        return webConfig;
    }

    /**
     * Get all the file names of properties files that configure class name mappings.
     * @param props the main configuration to look in.
     */
    private static List<String> getClassMappingFileNames(Properties props) {
        return getMappingFileNames(props, "web.config.classname.mappings");
    }

    /**
     * Get all the file names of properties files that configure field name mappings.
     * @param props the main configuration to look in.
     */
    private static List<String> getFieldMappingFileNames(Properties props) {
        return getMappingFileNames(props, "web.config.fieldname.mappings");
    }

    /**
     * Get all the files configured in a properties file with a certain prefix.
     * @param prefix The prefix to use to get the list of values.
     */
    private static List<String> getMappingFileNames(Properties props, String prefix) {
        List<String> returnVal = new ArrayList<String>();
        for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
            String key = (String) e.nextElement();
            if (key.startsWith(prefix)) {
                returnVal.add(props.getProperty(key));
            }
        }
        return returnVal;
    }

    /**
     * Load a set of files into a single merged properties file. These files should all be
     * located in the WEB-INF directory of the webapp war.
     *
     * @param fileNames The file names to load.
     * @throws FileNotFoundException If a file is listed but does not exist in WEB-INF.
     * @throws IllegalStateException If two files configure the same key.
     * @throws IOException if the properties cannot be loaded.
     */
    private static Properties loadMergedProperties(List<String> fileNames,
        final ServletContext context)
        throws IllegalStateException, FileNotFoundException, IOException {
        Properties props = new Properties();
        for ( String fileName : fileNames ) {
            LOG.info("Loading properties from " + fileName);
            Properties theseProps = new Properties();
            InputStream is = context.getResourceAsStream("/WEB-INF/" + fileName);
            if (is == null)
                throw new FileNotFoundException("Could not find mappings file: " + fileName);
            try {
	            theseProps.load(is);
            } catch (IOException e) {
            	throw new Error("Problem reading from " + fileName, e);
            }
            if (!props.isEmpty()) {
                for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
                    String key = (String) e.nextElement();
                    if (theseProps.containsKey(key)) {
                        throw new IllegalStateException(
                                "Duplicate label found for " + key + " in " + fileName);
                    }
                }
            }
            if (theseProps.isEmpty()) {
            	LOG.info("No properties loaded from " + fileName);
            } else {
            	LOG.info("Merging in " + theseProps.size() + " mappings from " + fileName);
	            props.putAll(theseProps);
            }
        }
        return props;
    }


    /**
     * Load labels specified in any configured mapping files, and apply them to the
     * configuration for the appropriate classes and fields.
     * @param context The servlet context to use to find configuration with.
     * @param model The data model which lists our classes and fields.
     */
    private void loadLabelsFromMappingsFile(
        final ServletContext context,
        final Model model)
        throws FileNotFoundException, IOException, IllegalStateException {

        Properties webProperties = SessionMethods.getWebProperties(context);

        List<String> classFileNames = getClassMappingFileNames(webProperties);
        List<String> fieldFileNames = getFieldMappingFileNames(webProperties);

        Properties fieldNameProperties = loadMergedProperties(fieldFileNames, context);
        Properties classNameProperties = loadMergedProperties(classFileNames, context);

        for (ClassDescriptor cd : model.getClassDescriptors()) {
            labelClass(cd, classNameProperties, fieldNameProperties);
        }
    }

    /**
     * Apply any labels configured in the property files to the class. This means
     * a label for the class itself, and labels for any of its fields.
     * @param cd a class descriptor specifying the class.
     * @param classNameProperties The mapping from our class names to a readable version
     * @param fieldNameProperties The mapping from our field names to a readable version
     */
    private void labelClass(
    		final ClassDescriptor cd,
    		final Properties classNameProperties,
            final Properties fieldNameProperties) {
        String originalName = cd.getUnqualifiedName();
        if ("InterMineObject".equals(originalName)) {
        	return;
        }
        Type classConfig = getTypes().get(cd.getName());
        if (classConfig == null) {
            classConfig = new Type();
            classConfig.setClassName(cd.getName());
            addType(classConfig);
        }

        if (classNameProperties.containsKey(originalName)) {
            String classNameLabel = classNameProperties.getProperty(originalName);<commit_msg>checkstyle<commit_after>package org.intermine.web.logic.config;

/*
 * Copyright (C) 2002-2011 FlyMine
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  See the LICENSE file for more
 * information or http://www.gnu.org/copyleft/lesser.html.
 *
 */

import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Properties;

import javax.servlet.ServletContext;

import org.apache.commons.digester.Digester;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.intermine.metadata.ClassDescriptor;
import org.intermine.metadata.FieldDescriptor;
import org.intermine.metadata.Model;
import org.intermine.pathquery.Path;
import org.intermine.pathquery.PathException;
import org.intermine.web.logic.widget.config.EnrichmentWidgetConfig;
import org.intermine.web.logic.widget.config.GraphWidgetConfig;
import org.intermine.web.logic.widget.config.HTMLWidgetConfig;
import org.intermine.web.logic.widget.config.TableWidgetConfig;
import org.intermine.web.logic.widget.config.WidgetConfig;
import org.intermine.web.logic.session.SessionMethods;
import org.xml.sax.SAXException;

/**
 * Configuration object for web site
 *
 * @author <NAME>
 */
public class WebConfig
{
    private static final Logger LOG = Logger.getLogger(WebConfig.class);
    private Map<String, Type> types = new TreeMap<String, Type>();
    private Map<String, TableExportConfig> tableExportConfigs =
        new HashMap<String, TableExportConfig>();
    private Map<String, WidgetConfig> widgets = new HashMap<String, WidgetConfig>();
    private List<ReportDisplayerConfig> reportDisplayerConfigs =
        new ArrayList<ReportDisplayerConfig>();

    /**
     * Parse a WebConfig XML file
     *
     * @param is the InputStream to parse
     * @param model the Model to use when reading - used for checking class names and for finding
     * sub and super classes
     * @return a WebConfig object
     * @throws SAXException if there is an error in the XML file
     * @throws IOException if there is an error reading the XML file
     * @throws ClassNotFoundException if a class is mentioned in the XML that isn't in the model
     */
    public static WebConfig parse(ServletContext context, Model model)
        throws IOException, SAXException, ClassNotFoundException, FileNotFoundException {

        BasicConfigurator.configure();

        InputStream webconfXML = context.getResourceAsStream("/WEB-INF/webconfig-model.xml");
        if (webconfXML == null)
            throw new FileNotFoundException("Could not find webconfig-model.xml");

        Digester digester = new Digester();
        digester.setValidating(false);

        digester.addObjectCreate("webconfig", WebConfig.class);

        digester.addObjectCreate("webconfig/class", Type.class);
        digester.addSetProperties("webconfig/class", "className", "className");
        digester.addSetProperties("webconfig/class", "fieldName", "fieldName");

        /* configure how the "title" of an object is displayed on Type */
        digester.addObjectCreate("webconfig/class/headerconfig/titles", HeaderConfigTitle.class);
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "mainTitles", "mainTitles");
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "subTitles", "subTitles");
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "numberOfMainTitlesToShow", "numberOfMainTitlesToShow");
        digester.addSetProperties("webconfig/class/headerconfig/titles/title",
                "appendConfig", "appendConfig");
        digester.addSetNext("webconfig/class/headerconfig/titles", "addHeaderConfigTitle");

        digester.addObjectCreate("webconfig/class/headerconfig/customlinks",
                HeaderConfigLink.class);
        digester.addSetProperties("webconfig/class/headerconfig/customlinks/customlink",
                "url", "url");
        digester.addSetProperties("webconfig/class/headerconfig/customlinks/customlink",
                "text", "text");
        digester.addSetProperties("webconfig/class/headerconfig/customlinks/customlink",
                "image", "image");
        digester.addSetNext("webconfig/class/headerconfig/customlinks", "addHeaderConfigLink");

        digester.addObjectCreate("webconfig/class/tabledisplayer", Displayer.class);
        digester.addSetProperties("webconfig/class/tabledisplayer", "src", "src");
        digester.addSetNext("webconfig/class/tabledisplayer", "setTableDisplayer");

        digester.addCallMethod("webconfig/class/tabledisplayer/param", "addParam", 2);
        digester.addCallParam("webconfig/class/tabledisplayer/param", 0, "name");
        digester.addCallParam("webconfig/class/tabledisplayer/param", 1, "value");

        digester.addObjectCreate("webconfig/class/fields/fieldconfig", FieldConfig.class);
        digester.addSetProperties("webconfig/class/fields/fieldconfig", "fieldExpr", "fieldExpr");
        digester.addSetProperties("webconfig/class/fields/fieldconfig", "name", "name");
        digester.addSetProperties("webconfig/class/fields/fieldconfig", "displayer", "displayer");
        digester.addSetNext("webconfig/class/fields/fieldconfig", "addFieldConfig");

        digester.addObjectCreate("webconfig/class/longdisplayers/displayer", Displayer.class);
        digester.addSetProperties("webconfig/class/longdisplayers/displayer");
        digester.addSetNext("webconfig/class/longdisplayers/displayer", "addLongDisplayer");

        digester.addCallMethod("webconfig/class/longdisplayers/displayer/param", "addParam", 2);
        digester.addCallParam("webconfig/class/longdisplayers/displayer/param", 0, "name");
        digester.addCallParam("webconfig/class/longdisplayers/displayer/param", 1, "value");

        /* display inline tables as inline lists instead */
        digester.addObjectCreate("webconfig/class/inlinelist/table", InlineList.class);
        digester.addSetProperties("webconfig/class/inlinelist/table");
        digester.addSetNext("webconfig/class/inlinelist/table", "addInlineList");
        digester.addSetProperties("webconfig/class/inlinelist/table", "path", "path");
        digester.addSetProperties("webconfig/class/inlinelist/table",
                "showLinksToObjects", "showLinksToObjects");
        digester.addSetProperties("webconfig/class/inlinelist/table",
                "showInHeader", "showInHeader");
        digester.addSetProperties("webconfig/class/inlinelist/table", "lineLength", "lineLength");


        digester.addObjectCreate("webconfig/class/bagdisplayers/displayer", Displayer.class);
        digester.addSetProperties("webconfig/class/bagdisplayers/displayer");
        digester.addSetNext("webconfig/class/bagdisplayers/displayer", "addBagDisplayer");

        digester.addCallMethod("webconfig/class/bagdisplayers/displayer/param", "addParam", 2);
        digester.addCallParam("webconfig/class/bagdisplayers/displayer/param", 0, "name");
        digester.addCallParam("webconfig/class/bagdisplayers/displayer/param", 1, "value");

        digester.addObjectCreate("webconfig/widgets/graphdisplayer", GraphWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/graphdisplayer");
        digester.addSetNext("webconfig/widgets/graphdisplayer", "addWidget");

        digester.addObjectCreate("webconfig/widgets/enrichmentwidgetdisplayer",
                EnrichmentWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/enrichmentwidgetdisplayer");
        digester.addSetNext("webconfig/widgets/enrichmentwidgetdisplayer", "addWidget");

        digester.addObjectCreate("webconfig/widgets/bagtabledisplayer", TableWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/bagtabledisplayer");
        digester.addSetNext("webconfig/widgets/bagtabledisplayer", "addWidget");

        digester.addObjectCreate("webconfig/widgets/htmldisplayer", HTMLWidgetConfig.class);
        digester.addSetProperties("webconfig/widgets/htmldisplayer");
        digester.addSetNext("webconfig/widgets/htmldisplayer", "addWidget");

        digester.addSetNext("webconfig/class", "addType");

        digester.addObjectCreate("webconfig/tableExportConfig", TableExportConfig.class);
        digester.addSetProperties("webconfig/tableExportConfig", "id", "id");
        digester.addSetProperties("webconfig/tableExportConfig", "className", "className");

        digester.addSetNext("webconfig/tableExportConfig", "addTableExportConfig");

        digester.addObjectCreate("webconfig/reportdisplayers/reportdisplayer",
                ReportDisplayerConfig.class);
        digester.addSetProperties("webconfig/reportdisplayers/reportdisplayer");
        digester.addSetNext("webconfig/reportdisplayers/reportdisplayer", "addReportDisplayer");

        WebConfig webConfig = (WebConfig) digester.parse(webconfXML);

        webConfig.validate(model);

        webConfig.setSubClassConfig(model);

        webConfig.loadLabelsFromMappingsFile(context, model);

        return webConfig;
    }

    /**
     * Get all the file names of properties files that configure class name mappings.
     * @param props the main configuration to look in.
     */
    private static List<String> getClassMappingFileNames(Properties props) {
        return getMappingFileNames(props, "web.config.classname.mappings");
    }

    /**
     * Get all the file names of properties files that configure field name mappings.
     * @param props the main configuration to look in.
     */
    private static List<String> getFieldMappingFileNames(Properties props) {
        return getMappingFileNames(props, "web.config.fieldname.mappings");
    }

    /**
     * Get all the files configured in a properties file with a certain prefix.
     * @param prefix The prefix to use to get the list of values.
     */
    private static List<String> getMappingFileNames(Properties props, String prefix) {
        List<String> returnVal = new ArrayList<String>();
        for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
            String key = (String) e.nextElement();
            if (key.startsWith(prefix)) {
                returnVal.add(props.getProperty(key));
            }
        }
        return returnVal;
    }

    /**
     * Load a set of files into a single merged properties file. These files should all be
     * located in the WEB-INF directory of the webapp war.
     *
     * @param fileNames The file names to load.
     * @throws FileNotFoundException If a file is listed but does not exist in WEB-INF.
     * @throws IllegalStateException If two files configure the same key.
     * @throws IOException if the properties cannot be loaded.
     */
    private static Properties loadMergedProperties(List<String> fileNames,
        final ServletContext context)
        throws IllegalStateException, FileNotFoundException, IOException {
        Properties props = new Properties();
        for ( String fileName : fileNames ) {
            LOG.info("Loading properties from " + fileName);
            Properties theseProps = new Properties();
            InputStream is = context.getResourceAsStream("/WEB-INF/" + fileName);
            if (is == null)
                throw new FileNotFoundException("Could not find mappings file: " + fileName);
            try {
                theseProps.load(is);
            } catch (IOException e) {
                throw new Error("Problem reading from " + fileName, e);
            }
            if (!props.isEmpty()) {
                for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
                    String key = (String) e.nextElement();
                    if (theseProps.containsKey(key)) {
                        throw new IllegalStateException(
                                "Duplicate label found for " + key + " in " + fileName);
                    }
                }
            }
            if (theseProps.isEmpty()) {
                LOG.info("No properties loaded from " + fileName);
            } else {
                LOG.info("Merging in " + theseProps.size() + " mappings from " + fileName);
                props.putAll(theseProps);
            }
        }
        return props;
    }


    /**
     * Load labels specified in any configured mapping files, and apply them to the
     * configuration for the appropriate classes and fields.
     * @param context The servlet context to use to find configuration with.
     * @param model The data model which lists our classes and fields.
     */
    private void loadLabelsFromMappingsFile(
        final ServletContext context,
        final Model model)
        throws FileNotFoundException, IOException, IllegalStateException {

        Properties webProperties = SessionMethods.getWebProperties(context);

        List<String> classFileNames = getClassMappingFileNames(webProperties);
        List<String> fieldFileNames = getFieldMappingFileNames(webProperties);

        Properties fieldNameProperties = loadMergedProperties(fieldFileNames, context);
        Properties classNameProperties = loadMergedProperties(classFileNames, context);

        for (ClassDescriptor cd : model.getClassDescriptors()) {
            labelClass(cd, classNameProperties, fieldNameProperties);
        }
    }

    /**
     * Apply any labels configured in the property files to the class. This means
     * a label for the class itself, and labels for any of its fields.
     * @param cd a class descriptor specifying the class.
     * @param classNameProperties The mapping from our class names to a readable version
     * @param fieldNameProperties The mapping from our field names to a readable version
     */
    private void labelClass(
            final ClassDescriptor cd,
            final Properties classNameProperties,
            final Properties fieldNameProperties) {
        String originalName = cd.getUnqualifiedName();
        if ("InterMineObject".equals(originalName)) {
            return;
        }
        Type classConfig = getTypes().get(cd.getName());
        if (classConfig == null) {
            classConfig = new Type();
            classConfig.setClassName(cd.getName());
            addType(classConfig);
        }

        if (classNameProperties.containsKey(originalName)) {
            String classNameLabel = classNameProperties.getProperty(originalName);
{'fraction_non_alphanumeric': 0.10193392425463337, 'fraction_numerical': 0.001275852806876175, 'mean_word_length': 3.334254947613504, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5012844', 'n_tokens_mistral': 7932, 'n_tokens_neox': 7712, 'n_words': 1721}