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

org.jbundle.base.model.Utility Maven / Gradle / Ivy

There is a newer version: 1.0.9
Show newest version
/*
 * Copyright © 2012 jbundle.org. All rights reserved.
 */
package org.jbundle.base.model;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;

import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.jbundle.model.PropertyOwner;
import org.jbundle.model.RecordOwnerParent;
import org.jbundle.model.Task;
import org.jbundle.model.db.Field;
import org.jbundle.thin.base.db.Constants;
import org.jbundle.thin.base.db.Converter;
import org.jbundle.thin.base.db.Params;
import org.jbundle.thin.base.message.MessageConstants;
import org.jbundle.thin.base.util.ThinUtil;
import org.w3c.dom.Node;


/**
 * Static utility methods.
 */
public class Utility extends ThinUtil
{
    /**
     * SaveProductParam Method.
     */
    public static Map addFieldParam(Map map, Field field)
    {
        if (!field.isNull())
            map.put(field.getFieldName(), field.toString());
        return map;
    }
    /**
     * SaveProductParam Method.
     */
    public static String addFieldParam(String strCommand, Field field)
    {
        if (!field.isNull())
            strCommand = Utility.addURLParam(strCommand, field.getFieldName(), field.toString());
        return strCommand;
    }
    /**
     * RestoreProductParam Method.
     */
    public static void restoreFieldParam(PropertyOwner propertyOwner, Field field)
    {
        String strFieldName = field.getFieldName();
        if (propertyOwner.getProperty(strFieldName) != null)
            field.setString((String)propertyOwner.getProperty(strFieldName));
    }
    /**
     * Add to this URL using all the properties in this property list.
     * @param strURL The original URL string.
     * @param properties The properties to add (key/value pairs).
     * @return The new URL string.
     */
    public static String propertiesToURL(String strURL, Map properties)
    {
        if (properties != null)
        {
            for (String strKey : properties.keySet())
            {
                Object strValue = properties.get(strKey);
                strURL = Utility.addURLParam(strURL, strKey.toString(), strValue.toString());                
            }
        }
        return strURL;
    }
    /**
     * Create an argument list using all the properties in this property list.
     * @param properties The properties to convert.
     * @return A string array with entries of key=value.
     */
    public static String[] propertiesToArgs(Map properties)
    {
        if (properties == null)
            return null;
        String[] rgArgs = new String[properties.size()];
        int i = 0;
        for (String strKey : properties.keySet())
        {
            Object strValue = properties.get(strKey);
            rgArgs[i++] = strKey.toString() + '=' + strValue.toString();
        }
        return rgArgs;
    }
    /**
     * Replace every occurrence of this string with the new data.
     * @param string The original string.
     * @param strOldParam The string to find.
     * @param strNewData The data to replace the string with.
     * @return The new string.
     */
    public static String replace(String string, String strOldParam, String strNewData)
    {
        if (string == null)
            return null;
        StringBuilder sb = new StringBuilder(string);
        return Utility.replace(sb, strOldParam, strNewData).toString();
    }
    /**
     * Replace every occurrence of this string with the new data.
     * @param sb The original string.
     * @param strOldParam The string to find.
     * @param strNewData The data to replace the string with.
     * @return The new string.
     */
    public static StringBuilder replace(StringBuilder sb, String strOldParam, String strNewData)
    {
        int iIndex = 0;
        if (strNewData == null)
            strNewData = Constants.BLANK;
        while ((iIndex = sb.indexOf(strOldParam, iIndex)) != -1)
        {
            sb.replace(iIndex, iIndex + strOldParam.length(), strNewData);
            iIndex = iIndex + strNewData.length();
        }
        return sb;
    }
    /**
     * Replace every occurrence of the strings in the hashtable with the new data.
     * This method assumes that the hashtable contains the keys which must be replaced with the associated values.
     * NOTE: This method might be re-worked to search the string for the delimiter character rather than
     * calling replace for each param.
     * @param string The original string.
     * @param ht The table of old/new values.
     * @return The new string.
     */
    public static StringBuilder replace(StringBuilder sb, String[][] mxStrings)
    {
        for (int x = 0; x < mxStrings.length; x++)
        {
            if (mxStrings[x].length != 2)
                return sb;
            sb = Utility.replace(sb, mxStrings[x][0], mxStrings[x][1]);
        }
        return sb;
    }
    /**
     * Replace every occurrence of the strings in the map with the new data.
     * This method assumes that the map contains the keys which must be replaced with the associated values.
     * NOTE: This method might be re-worked to search the string for the delimiter character rather than
     * calling replace for each param.
     * @param string The original string.
     * @param ht The table of old/new values.
     * @return The new string.
     */
    public static String replace(String string, String[][] mxStrings)
    {
        return Utility.replace(new StringBuilder(string), mxStrings).toString();
    }
    /**
     * Replace every occurrence of the strings in the map with the new data.
     */
    public static StringBuilder replace(StringBuilder sb, Map ht)
    {
        for (Object strKey : ht.keySet())
        {
            sb = Utility.replace(sb, (String)strKey, (String)ht.get(strKey));            
        }
        return sb;
    }
    /**
     * Replace every occurrence of the strings in the map with the new data.
     */
    public static String replace(String string, Map ht)
    {
        return Utility.replace(new StringBuilder(string), ht).toString();
    }
    /**
     * Replace the {} resources in this string.
     * Typically either reg or map are non-null (the null one is ignored)
     * @param reg A resource bundle
     * @param map A map of key/values
     * @param strResource
     * @return
     */
    public static StringBuilder replaceResources(StringBuilder sb, ResourceBundle reg, Map map, PropertyOwner propertyOwner)
    {
        return Utility.replaceResources(sb, reg, map, propertyOwner, false);
    }
    /**
     * Replace the {} resources in this string.
     * Typically either reg or map are non-null (the null one is ignored)
     * @param reg A resource bundle
     * @param map A map of key/values
     * @param strResource
     * @return
     */
    public static StringBuilder replaceResources(StringBuilder sb, ResourceBundle reg, Map map, PropertyOwner propertyOwner, boolean systemProperties)
    {
    	boolean bDoubleBraces = false;
        int index = 0;
        while (index < sb.length())
        {
            int iStartBrace = sb.indexOf("{", index);
            if (iStartBrace == -1)
                break;
            if (iStartBrace + 1 < sb.length())
            	if (sb.charAt(iStartBrace + 1) == '{')
        	{
            	bDoubleBraces = true;
            	index = iStartBrace + 2;
            	continue;
        	}
            int iEndBrace = sb.indexOf("}", iStartBrace);
            if (iEndBrace == -1)
                break;
            String strKey = sb.substring(iStartBrace + 1, iEndBrace);
            if (iStartBrace > 0)
            	if (sb.charAt(iStartBrace - 1) == '$')
            		iStartBrace--;
            String string = null;
            if (map != null)
            	if (map.get(strKey) != null)
            		string = map.get(strKey).toString();
            if (reg != null)
                if (string == null)
                    string = reg.getString(strKey);
            if (propertyOwner != null)
                if (string == null)
                    string = propertyOwner.getProperty(strKey);
            if (string == null)
                if (systemProperties)
                    try {
                        string = System.getProperty(strKey);
                    } catch (Exception e) { // Ignore (security) errors
                    }
            if (string == null)
                string = strKey;    // Never
            sb.replace(iStartBrace, iEndBrace + 1, string);
            index = index + string.length();
        }
        if (bDoubleBraces)
        {
            index = 0;
            while (index < sb.length() - 1)
            {
            	index = sb.indexOf("{{", index);
            	if (index == -1)
            		break;
    			sb.replace(index, index + 2, "{");
            	index = sb.indexOf("}}", index);
            	if (index == -1)
            		break;
    			sb.replace(index, index + 2, "}");
            }
        }
        return sb;
    }
    /**
     * Replace the {} resources in this string.
     * @param reg
     * @param map A map of key/values
     * @param strResource
     * @return
     */
    public static String replaceResources(String string, ResourceBundle reg, Map map, PropertyOwner propertyOwner)
    {
        return Utility.replaceResources(string, reg, map, propertyOwner, false);
    }
    /**
     * Replace the {} resources in this string.
     * @param reg
     * @param map A map of key/values
     * @param strResource
     * @return
     */
    public static String replaceResources(String string, ResourceBundle reg, Map map, PropertyOwner propertyOwner, boolean systemProperties)
    {
        if (string != null)
            if (string.indexOf('{') == -1)
                return string;
        return Utility.replaceResources(new StringBuilder(string), reg, map, propertyOwner, systemProperties).toString();
    }
    /**
     * A utility method to get an UTF-8 Input stream from a string.
     * @param string The string to be converted to an InputStream.
     * @return The input stream.
     */
    public static InputStream getStringInputStream(String string)
    {
        InputStream is = null;
        try   {
            ByteArrayOutputStream ba = new ByteArrayOutputStream();
            DataOutputStream os = new DataOutputStream(ba);
            os.writeUTF(string);
            os.flush();
            is = new ByteArrayInputStream(ba.toByteArray());
        } catch (IOException ex)    {
            ex.printStackTrace();
        }
        return is;
    }
    /**
     * Transfer the data stream from this URL to a string or file.
     * @param strURL The URL to read.
     * @param strFilename If non-null, create this file and send the URL data here.
     * @param strFilename If null, return the stream as a string.
     * @return The stream as a string if filename is null.
     */
    public static String transferURLStream(String strURL, String strFilename)
    {
        return Utility.transferURLStream(strURL, strFilename, null);
    }
    /**
     * Transfer the data stream from this URL to a string or file.
     * @param strURL The URL to read.
     * @param strFilename If non-null, create this file and send the URL data here.
     * @param strFilename If null, return the stream as a string.
     * @param in If this is non-null, read from this input source.
     * @return The stream as a string if filename is null.
     */
    public static String transferURLStream(String strURL, String strFilename, Reader in)
    {
        return Utility.transferURLStream(strURL, strFilename, in, null);
    }
    /**
     * Transfer the data stream from this URL to a string or file.
     * @param strURL The URL to read.
     * @param strFilename If non-null, create this file and send the URL data here.
     * @param strFilename If null, return the stream as a string.
     * @param in If this is non-null, read from this input source.
     * @return The stream as a string if filename is null.
     */
    public static String transferURLStream(String strURL, String strFilename, Reader in, Writer out)
    {
        String strMessage = null;
        URL url = null;
        try {
            InputStream inStream = null;
            if (in == null)
            {
                url = new URL(strURL);
                inStream = url.openStream();
                in = new BufferedReader(new InputStreamReader(inStream));
            }
            Writer outTemp = out;
            if (outTemp == null)
            {
                if ((strFilename == null) || (strFilename.length() == 0))
                    outTemp = new StringWriter();
                else
                {
                	File file = new File(strFilename);
                	file = file.getParentFile();
                	if (!file.exists())
                		file.mkdirs();
                    outTemp = new FileWriter(strFilename);
                }
            }
            Utility.transferStream(in, outTemp);
            if (out == null)
            {
                outTemp.flush();
                outTemp.close();
                if ((strFilename == null) || (strFilename.length() == 0))
                    strMessage = ((StringWriter)outTemp).getBuffer().toString();
            }
            in.close();
            if (inStream != null)
                inStream.close();
        } catch (MalformedURLException ex)  {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return strMessage;
    }
    /**
     * Change this string to a XML Text string.
     * @param string The tag to enclose as an xml tag.
     * @return The XML tag.
     */
    public static String startTag(String string)
    {
        return "<" + string + '>';
    }
    /**
     * Change this string to a XML end Text string.
     * @param string The tag to enclose as an xml tag.
     * @return The ending XML tag.
     */
    public static String endTag(String string)
    {
        if (string.indexOf(' ') != -1)
            string = string.substring(0, string.indexOf(' '));
        return "';
    }
    /**
     * Change this string to a XML Text string.
     * This just replaces < with < and > with > and & with &.
     * @param string The string to fix so it is valid XML.
     * @return The new valid XML string.
     */
    public static String encodeXML(String string)
    {
        return Utility.replace(string, mxXML);
    }
    public static String[][] mxXML =
    {
        {"<", "<"},
        {">", ">"},
        {"&", "&"}//,
//        {"\n", "
"} }; /** * Get this property from the map and convert it to the target class. * @param properties The map object to get the property from. * @param strKey The key of the property. * @param classData The target class to convert the property to. * @return The data in the correct class. */ public static Object getAs(Map properties, String strKey, Class classData) { return Utility.getAs(properties, strKey, classData, null); } /** * Get this property from the map and convert it to the target class. * @param properties The map object to get the property from. * @param strKey The key of the property. * @param classData The target class to convert the property to. * @param objDefault The default value. * @return The data in the correct class. */ public static Object getAs(Map properties, String strKey, Class classData, Object objDefault) { if (properties == null) return objDefault; Object objData = properties.get(strKey); try { return Converter.convertObjectToDatatype(objData, classData, objDefault); } catch (Exception ex) { return null; } } /** * Get this item from the map and convert it to the target class. * Convert this object to an formatted string. * @param map The map to pull the param from. * @param strKey The property key. * @param classData The java class to convert the data to. * @param objDefault The default value. * @return The propety value in the correct class. */ public static String getAsFormattedString(Map map, String strKey, Class classData, Object objDefault) { Object objData = map.get(strKey); try { return Converter.formatObjectToString(objData, classData, objDefault); } catch (Exception ex) { return null; } } /** * Convert this object to an formatted string. * @param obj In * @return String out. */ public static String formatObjectToString(Object objData) { if (objData == null) return null; try { return Converter.formatObjectToString(objData, objData.getClass(), null); } catch (Exception ex) { return null; } } /** * Do a smart conversion of this object to an unfomatted string (ie., toString). * @param obj In * @return String out. */ public static String convertObjectToString(Object objData) { if (objData == null) return null; try { return Utility.convertObjectToString(objData, objData.getClass(), null); } catch (Exception ex) { return null; } } /** * Convert this object to an unfomatted string (ie., toString). * @param properties The map object to get the property from. * @param strKey The key of the property. * @param classData The target class to convert the property to. * @param objDefault The default value. * @return The data in the correct class. */ public static String convertObjectToString(Object objData, Class classData, Object objDefault) { try { objData = Converter.convertObjectToDatatype(objData, classData, objDefault); } catch (Exception ex) { objData = null; } if (objData == null) return null; return objData.toString(); } /** * Convert this properties object to a map. * @param properties * @return */ static public Map propertiesToMap(Properties properties) { Map map = null; if (properties != null) { map = new Hashtable(); for (Object key : properties.keySet()) { map.put((String)key, properties.get(key)); } } return map; } /** * Convert this properties object to a map. * @param properties * @return */ static public Map propertiesToMap(Dictionary properties) { Map map = null; if (properties != null) { map = new Hashtable(); Enumeration keys = properties.keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); map.put((String)key, properties.get(key)); } } return map; } /** * Convert map to properties. * @param map * @return */ public static Properties mapToProperties(Map map) { Properties properties = new Properties(); Iterator> i = map.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = i.next(); properties.setProperty((String)e.getKey(), (e.getValue() != null) ? e.getValue().toString() : DBConstants.BLANK); } return properties; } public static Map arrayToMap(String[][] data) { Map map = new Hashtable(); if (data != null) { for (String[] row : data) { map.put(row[0], row[1]); } } return map; } /** * Same as putall, except if the property already exists in the dest map, don't move it. * TODO Convert this to generics. * @param dest * @param source * @return */ public static Map putAllIfNew(Map dest, Map source) { if (dest == null) return source; if (source == null) return dest; for (String key : source.keySet()) { if (dest.get(key) == null) dest.put(key, source.get(key)); } return dest; } /** * Copy the application properties to this map. * @param properties * @param appProperties * @return */ public static Map copyAppProperties(Map properties, Map appProperties) { if (appProperties != null) { appProperties = Utility.putAllIfNew(new HashMap(), appProperties); if (appProperties.get(Params.APP_NAME) != null) appProperties.remove(Params.APP_NAME); //if (appProperties.get(Params.MESSAGE_SERVER) != null) // appProperties.remove(Params.MESSAGE_SERVER); if (appProperties.get(DBParams.FREEIFDONE) != null) appProperties.remove(DBParams.FREEIFDONE); if (appProperties.get(MessageConstants.MESSAGE_FILTER) != null) appProperties.remove(MessageConstants.MESSAGE_FILTER); } return Utility.putAllIfNew(properties, appProperties); } /** * Get the domain name from this URL. * @param strDomain * @return */ public static String getDomainFromURL(String strURL, String strContextPathAtEnd) { return Utility.getDomainFromURL(strURL, strContextPathAtEnd, false); } /** * Get the domain name from this URL. * @param strDomain * @return */ public static String getDomainFromURL(String strURL, String strContextPathAtEnd, boolean includePortNumber) { String strDomain = strURL; if (strDomain.indexOf(':') < 8) strDomain = strDomain.substring(strDomain.indexOf(':') + 1); // Get rid of protocol if (strDomain.indexOf("//") == 0) strDomain = strDomain.substring(2); // Get rid of '//' if (strDomain.indexOf('?') != -1) strDomain = strDomain.substring(0, strDomain.indexOf('?')); // Get rid of params int iEndDomain = strDomain.indexOf('/'); if (iEndDomain == -1) iEndDomain = strDomain.length(); if (strDomain.lastIndexOf(Constants.DEFAULT_SERVLET) >= iEndDomain) strDomain = strDomain.substring(0, strDomain.lastIndexOf(Constants.DEFAULT_SERVLET) - 1); // Strip servlet name if ((strDomain.indexOf(':') != -1) && (includePortNumber == false)) strDomain = strDomain.substring(0, strDomain.indexOf(':')); // Get rid of port number else strDomain = strDomain.substring(0, iEndDomain); if (strContextPathAtEnd != null) { int iStartIndex = strURL.indexOf(strDomain); int iContextIndex = strURL.indexOf(strContextPathAtEnd, iStartIndex + strDomain.length()); if (iContextIndex != -1) { // Always iContextIndex = iContextIndex + strContextPathAtEnd.length(); strDomain = strURL.substring(iStartIndex, iContextIndex); if (!strDomain.endsWith(System.getProperty("file.separator"))) strDomain = strDomain + System.getProperty("file.separator"); } } return strDomain; } /** * Get a recordowner from this recordOwnerParent. * This method does a deep search using the listeners and the database connections to find a recordowner. * @param record * @return */ public static RecordOwner getRecordOwner(RecordOwnerParent recordOwnerParent) { if (recordOwnerParent instanceof RecordOwner) return (RecordOwner)recordOwnerParent; // Duh RecordOwner recordOwner = null; if (recordOwnerParent != null) if (recordOwnerParent.getTask() != null) if (recordOwnerParent.getTask().getApplication() != null) recordOwner = (RecordOwner)recordOwnerParent.getTask().getApplication().getSystemRecordOwner(); return recordOwner; } /** * Use XSLT to convert this source tree into a new tree. * @param result If this is specified, transform the message to this result (and return null). * @param source The source to convert. * @param streamTransformer The (optional) input stream that contains the XSLT document. * If you don't supply a streamTransformer, you should override getTransforerStream() method. * @return The new tree. */ public static void transformMessage(String strXMLIn, String strXMLOut, String strTransformer) { try { Reader reader = new FileReader(strXMLIn); Writer stringWriter = new FileWriter(strXMLOut); Reader readerxsl = new FileReader(strTransformer); Utility.transformMessage(reader, stringWriter, readerxsl); } catch (IOException ex) { ex.printStackTrace(); } } /** * Use XSLT to convert this source tree into a new tree. * @param result If this is specified, transform the message to this result (and return null). * @param source The source to convert. * @param streamTransformer The (optional) input stream that contains the XSLT document. * If you don't supply a streamTransformer, you should override getTransforerStream() method. * @return The new tree. */ public static void transformMessage(Reader reader, Writer stringWriter, Reader readerxsl) { try { StreamSource source = new StreamSource(reader); Result result = new StreamResult(stringWriter); TransformerFactory tFact = TransformerFactory.newInstance(); StreamSource streamTransformer = new StreamSource(readerxsl); Transformer transformer = tFact.newTransformer(streamTransformer); transformer.transform(source, result); } catch (TransformerConfigurationException ex) { ex.printStackTrace(); } catch (TransformerException ex) { ex.printStackTrace(); } } public static void main(String args[]) { Utility.transformMessage(args[0], args[1], args[2]); } /** * Copy DOM tree to a SOAP tree. * @param tree * @param node * @return The parent of the new child node. */ public static Node copyTreeToNode(Node tree, Node node) { DOMResult result = new DOMResult(node); if (Utility.copyTreeToResult(tree, result)) return node.getLastChild(); else return null; // Failure } /** * Copy DOM tree to a SOAP tree. * @param tree * @param node * @return The parent of the new child node. */ public static boolean copyTreeToResult(Node tree, Result result) { try { // Use a Transformer for output TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(); DOMSource source = new DOMSource(tree); transformer.transform(source, result); return true; // Success } catch (TransformerConfigurationException tce) { // Error generated by the parser tce.printStackTrace(); } catch (TransformerException te) { // Error generated by the parser te.printStackTrace(); } return false; // Failure } /** * Grow this array to the size of count (plus a buffer). * @param rgobjEnable * @param iNewSize * @param iBufferSize Extra size to add to the array if it has to grow. * @return */ public static Object[] growArray(Object[] rgobjEnable, int iNewSize, int iBufferSize) { if (iNewSize > rgobjEnable.length) { Object[] rgobjNew = new Object[iNewSize + iBufferSize]; for (int i = 0; i < rgobjEnable.length; i++) rgobjNew[i] = rgobjEnable[i]; for (int i = rgobjEnable.length; i < rgobjNew.length ;i++) rgobjNew[i] = null; rgobjEnable = rgobjNew; } return rgobjEnable; } /** * Add the base path to get an http path (**Move this to Util**) * @param basePath * @param path * @return */ public static String addURLPath(String basePath, String path) { if (basePath == null) basePath = ""; if ((!basePath.endsWith("/")) && (!path.startsWith("/"))) path = "/" + path; if (basePath.length() > 0) path = basePath + path; if (path.length() == 0) path = "/"; else if ((path.length() > 1) && (path.endsWith("/"))) path = path.substring(0, path.length() -1); return path; } /** * Get the path to the target servlet. * @param strServletParam The servlet type (html or xml) * @return the servlet path. */ public static String getServletPath(Task task, String strServletParam) { String strServletName = null; if (strServletParam == null) strServletParam = Params.SERVLET; if (task != null) strServletName = task.getProperty(strServletParam); if ((strServletName == null) || (strServletName.length() == 0)) { strServletName = Constants.DEFAULT_SERVLET; //? if (this.getTask() instanceof RemoteRecordOwner) //? strServletName = strServletName + "xsl"; // Special case - if task is a session, servlet should be appxsl if (Params.XHTMLSERVLET.equalsIgnoreCase(strServletParam)) strServletName = Constants.DEFAULT_XHTML_SERVLET; } return strServletName; } public static final String DEFAULT_SYSTEM_SUFFIX = "tourgeek"; /** * Get the system suffix, fix it and return it. * @return */ public static String getSystemSuffix(String suffix, String defaultSuffix) { if (defaultSuffix == null) defaultSuffix = DEFAULT_SYSTEM_SUFFIX; if (suffix == null) suffix = defaultSuffix; for (int i = suffix.length() - 2; i > 0; i--) { // Only use last word if (!Character.isLetterOrDigit(suffix.charAt(i))) { suffix = suffix.substring(i + 1); // Typical to pass groupId break; } } return suffix; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy