Skip to content
Snippets Groups Projects
ModelUtil.java 8.63 KiB
/*
 * Copyright (c) 2021 NIBIO <http://www.nibio.no/>. 
 * 
 * This file is part of VIPSCommon.
 * VIPSCommon is free software: you can redistribute it and/or modify
 * it under the terms of the NIBIO Open Source License as published by 
 * NIBIO, either version 1 of the License, or (at your option) any
 * later version.
 * 
 * VIPSCommon is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * NIBIO Open Source License for more details.
 * 
 * You should have received a copy of the NIBIO Open Source License
 * along with VIPSCommon.  If not, see <http://www.nibio.no/licenses/>.
 * 
 */

package no.nibio.vips.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import no.nibio.vips.entity.WeatherObservation;
import no.nibio.vips.model.ConfigValidationException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

/**
 * @copyright 2021 <a href="http://www.nibio.no/">NIBIO</a>
 * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
 */
public class ModelUtil {


    /**
     * Util method to read the contents of a file in your model jar into a string
     * @param clazz A class that is part of the jar file (the model package)
     * @param fileName Path to the file. Start with "/"
     * @return The file's contents
     */
    public String getTextFromFileInJar(Class clazz, String fileName)
    {
        BufferedInputStream inputStream = new BufferedInputStream(clazz.getResourceAsStream(fileName));
        try(Scanner scanner = new Scanner(inputStream,StandardCharsets.UTF_8))
        {
            return scanner.useDelimiter("\\A").next();
        }
    }
    
    /**
     * Parsing text with reference to images. Image template tag format is:
     * {{filename="/path/to/filename.jpg" description="Sample description" float="[CSSFloat property]"}}
     * {{filename="/1348485230424.jpg" description="Chart for apple scab model" float="none"}}
     * 
     * These tags are parsed and transformed to html &lt;img/&gt; tags, and the image files 
     * are base64 encoded and embedded. The only mandatory parameter is filename. It refers to the
     * file relative to the Model jar in which it is placed.
     * @param text
     * @param resourceClass the class whose classloader you want to use
     * for getting the images. Use class in the package that the images are placed.
     * @return The same text with embedded image(s)
     * 
     */
    public String getTextWithBase64EncodedImages(String text, Class resourceClass) throws IOException
    {
        if(resourceClass == null)
        {
            resourceClass = this.getClass();
        }
        // Finding all {{}} tags
        String tagPattern = "\\{\\{(.*?)\\}\\}";
        Pattern tr = Pattern.compile(tagPattern, Pattern.DOTALL);
        Matcher tm = tr.matcher(text);
        
        // Preparing to find attrName="attrVal" inside the tags
        String attrPattern = "([^=\\s]+)=\"([^\"]+)\"";
        Pattern ar = Pattern.compile(attrPattern);
        
        StringBuffer transformedText = new StringBuffer();
        while(tm.find())
        {
            String tagText ="<img";
            // Here we get the filename="/path/to/filename.jpg" description="Sample description" float="[CSSFloat property]" from the matcher
            String tagContents = tm.group(1);
            Matcher am = ar.matcher(tagContents);
            Boolean filenameAttributeFound = false;
            while(am.find())
            {
                String attrName = am.group(1);
                String attrVal = am.group(2);
                // Analyzing each tag
                if(attrName.equals("filename"))
                {
                    filenameAttributeFound = true;
                    BufferedInputStream inputStream = new BufferedInputStream(resourceClass.getResourceAsStream(attrVal));
                    // Attempt to use 
                    String mimeType = URLConnection.guessContentTypeFromStream(inputStream);
                    tagText +=" src=\"data:" + mimeType +";base64," + this.getBase64EncodedImage(inputStream) + "\"";
                    
                }
                if(attrName.equals("description"))
                {
                    tagText += " alt=\"" + attrVal + "\""
                            + " title=\"" + attrVal + "\"";
                }
                if(attrName.equals("float"))
                {
                    tagText += " style=\"float:" + attrVal + "\"";
                }
            }
            tagText += " class=\"img-responsive\"/>";
            if(filenameAttributeFound)
            {
                tm.appendReplacement(transformedText,tagText);
            }    
        }
        tm.appendTail(transformedText);
        return transformedText.toString();
    }
    
    public String getBase64EncodedImage(InputStream image) throws IOException
    {
        return this.getBase64EncodedImage(IOUtils.toByteArray(image));
    }
    
    public String getBase64EncodedImage(byte[] bytes)
    {
        return Base64.encodeBase64String(bytes);
    }

    /**
     * This solves the problem of the model not knowing if the ModelConfiguration
     * contains a halfway serialized List of WeatherObservation (typically LinkedHashMap) 
     * OR a List of actual WeatherObservations.
     * @param unknownClassList
     * @return 
     */
    public List<WeatherObservation> extractWeatherObservationList(Object unknownClassList)
    {
        try
        {
            // These are real WeatherObservation classes
            List<WeatherObservation> obsList = (List<WeatherObservation>) unknownClassList;
            if(obsList != null && obsList.size() > 0)
            {
                WeatherObservation o = obsList.get(0);
            }
            return obsList;
        }
        catch(ClassCastException ex)
        {
            // These are semi serialized, tell ObjectMapper/Jackson how to do it
            ObjectMapper mapper = new ObjectMapper();
            return mapper.convertValue(unknownClassList, new TypeReference<List<WeatherObservation>>(){});
        }
    }
    
    public Double getDouble(Object possibleNumber) throws ConfigValidationException
    {
    	if(possibleNumber == null)
    	{
    		return null;
    	}
    	
    	if(possibleNumber instanceof String)
    	{
    		try
    		{
    			return Double.valueOf((String) possibleNumber);
    		}
    		catch(NumberFormatException ex)
    		{
    			throw new ConfigValidationException(ex.getMessage());
    		}
    	}
    	if(possibleNumber instanceof Integer)
    	{
    		return ((Integer) possibleNumber).doubleValue();
    	}
    	if(possibleNumber instanceof Float)
    	{
    		return ((Float) possibleNumber).doubleValue();
    	}
    	if(possibleNumber instanceof Double)
    	{
    		return (Double) possibleNumber;
    	}
    	// Clutching at straws
    	if(possibleNumber instanceof BigInteger)
    	{
    		return ((BigInteger) possibleNumber).doubleValue();
    	}
    	if(possibleNumber instanceof BigDecimal)
    	{
    		return ((BigDecimal) possibleNumber).doubleValue();
    	}
    	// Out of options. Throw Exception
    	try
    	{
    		return (Double) possibleNumber;
    	}
    	catch(ClassCastException ex)
    	{
    		throw new ConfigValidationException(ex.getMessage());
    	}
    }
    
    public Double[] getDoubleArray(Object possibleArrayOfPossibleNumbers) throws ConfigValidationException
    {
        // Is it not an array?
        if(!possibleArrayOfPossibleNumbers.getClass().isArray() && ! (possibleArrayOfPossibleNumbers instanceof List))
        {
            Double oneDouble = this.getDouble(possibleArrayOfPossibleNumbers);
            Double[] retVal = {oneDouble};
            return retVal;
        }
        
        // Is it an array? Convert to list
        List<Object> listOfPossibleNumbers = possibleArrayOfPossibleNumbers.getClass().isArray() ?
                Arrays.asList((Object[])possibleArrayOfPossibleNumbers)
                : (List) possibleArrayOfPossibleNumbers;
        
        Double[] retVal = new Double[listOfPossibleNumbers.size()];
        int counter = 0;
        for(Object possibleNumber: listOfPossibleNumbers)
        {
            retVal[counter++] = this.getDouble(possibleNumber);
        }
        return retVal;
    }

}