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

com.mchange.v2.cfg.ConfigUtils Maven / Gradle / Ivy

There is a newer version: 0.3.1
Show newest version
/*
 * Distributed as part of mchange-commons-java 0.2.11
 *
 * Copyright (C) 2015 Machinery For Change, Inc.
 *
 * Author: Steve Waldman 
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of EITHER:
 *
 *     1) The GNU Lesser General Public License (LGPL), version 2.1, as 
 *        published by the Free Software Foundation
 *
 * OR
 *
 *     2) The Eclipse Public License (EPL), version 1.0
 *
 * You may choose which license to accept if you wish to redistribute
 * or modify this work. You may offer derivatives of this work
 * under the license you have chosen, or you may provide the same
 * choice of license which you have been offered here.
 *
 * This software 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.
 *
 * You should have received copies of both LGPL v2.1 and EPL v1.0
 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
 * If not, the text of these licenses are currently available at
 *
 * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php 
 * 
 */

package com.mchange.v2.cfg;

import java.util.*;
import java.io.*;

import static com.mchange.v2.cfg.DelayedLogItem.*;

// external clients should go through the MConfig facade.
final class ConfigUtils
{
    private final static String[] DFLT_VM_RSRC_PATHFILES    = new String[] {"/com/mchange/v2/cfg/vmConfigResourcePaths.txt", "/mchange-config-resource-paths.txt"};
    private final static String[] HARDCODED_DFLT_RSRC_PATHS = new String[] 
	{
	    "/mchange-commons.properties", 
	    "hocon:/reference,/application,/", 
	    "/"
	};

    final static String[] NO_PATHS                  = new String[0];

    //MT: protected by class' lock
    static MultiPropertiesConfig vmConfig = null;

    //public static MultiPropertiesConfig read(String[] resourcePath, MLogger logger)
    //{ return new BasicMultiPropertiesConfig( resourcePath, logger ); }

    static MultiPropertiesConfig read(String[] resourcePath, List delayedLogItems)
    { return new BasicMultiPropertiesConfig( resourcePath, delayedLogItems ); }

    public static MultiPropertiesConfig read(String[] resourcePath)
    { return new BasicMultiPropertiesConfig( resourcePath ); }

    /**
     *  Later entries in the configs array override earlier entries.
     */
    public static MultiPropertiesConfig combine( MultiPropertiesConfig[] configs )
    { return new CombinedMultiPropertiesConfig( configs ).toBasic(); }

    public static MultiPropertiesConfig readVmConfig(String[] defaultResources, String[] preemptingResources )
    { return readVmConfig( defaultResources, preemptingResources, (List) null ); }

    /*
    public static MultiPropertiesConfig readVmConfig(String[] defaultResources, String[] preemptingResources, MLogger logger)
    {
	List items = new ArrayList();
	MultiPropertiesConfig out = readVmConfig( defaultResources, preemptingResources, items );
	items.addAll( out.getDelayedLogItems() );
	for (Iterator ii = items.iterator(); ii.hasNext(); )
	{
	    DelayedLogItem item = (DelayedLogItem) ii.next();
	    logger.log( item.getLevel(), item.getText(), item.getException() );
	}
	return out;
    }
    */ 

    static List vmCondensedPaths(String[] defaultResources, String[] preemptingResources, List delayedLogItemsOut)
    {
	List raw = condensePaths( new String[][]{ defaultResources, vmResourcePaths( delayedLogItemsOut ), preemptingResources } );
	return ensureHoconInterresolvability( raw );
    }

    static String stringFromPathsList( List pathsList )
    {
	StringBuffer sb = new StringBuffer(2048);
	for ( int i = 0, len = pathsList.size(); i < len; ++i)
	    {
		if ( i != 0 ) sb.append(", ");
		sb.append( pathsList.get(i) );
	    }
	return sb.toString();
    }

    public static MultiPropertiesConfig readVmConfig(String[] defaultResources, String[] preemptingResources, List delayedLogItemsOut)
    {
	defaultResources = ( defaultResources == null ? NO_PATHS : defaultResources );
	preemptingResources = ( preemptingResources == null ? NO_PATHS : preemptingResources );
	List pathsList = vmCondensedPaths( defaultResources, preemptingResources, delayedLogItemsOut );
	
	if ( delayedLogItemsOut != null )
	    delayedLogItemsOut.add( new DelayedLogItem(Level.FINER, "Reading VM config for path list " + stringFromPathsList( pathsList ) ) );

	return read( (String[]) pathsList.toArray(new String[pathsList.size()]), delayedLogItemsOut );
    }

    private static List condensePaths(String[][] pathLists)
    {
	// we do this in reverse, so that the "first" time
	// we encounter a path becomes the last in the resultant
	// list. that is, we want redundantly specified paths 
	// to have their maximum specified preference

	Set pathSet = new HashSet();
	List reverseMe = new ArrayList();
	for ( int i = pathLists.length; --i >= 0; )
	    for( int j = pathLists[i].length; --j >= 0; )
	    {
		String path = pathLists[i][j];
		if (! pathSet.contains( path ) )
		{
		    pathSet.add( path );
		    reverseMe.add( path );
		}
	    }
	 Collections.reverse( reverseMe );
	 return reverseMe;
    }

    private static List readResourcePathsFromResourcePathsTextFile( String resourcePathsTextFileResourcePath,  List delayedLogItemsOut )
    {
	List rps = new ArrayList();

	BufferedReader br = null;
	try
	    {
		InputStream is = MultiPropertiesConfig.class.getResourceAsStream( resourcePathsTextFileResourcePath );
		if ( is != null )
		    {
			br = new BufferedReader( new InputStreamReader( is, "8859_1" ) );
			String rp;
			while ((rp = br.readLine()) != null)
			    {
				rp = rp.trim();
				if ("".equals( rp ) || rp.startsWith("#"))
				    continue;
				
				rps.add( rp );
			    }

			if ( delayedLogItemsOut != null )
			    delayedLogItemsOut.add( new DelayedLogItem( Level.FINEST, String.format( "Added paths from resource path text file at '%s'", resourcePathsTextFileResourcePath ) ) );
		    }
		else if ( delayedLogItemsOut != null )
		    delayedLogItemsOut.add( new DelayedLogItem( Level.FINEST, String.format( "Could not find resource path text file for path '%s'. Skipping.", resourcePathsTextFileResourcePath ) ) );

	    }
	catch (IOException e)
	    { e.printStackTrace(); }
	finally
	    {
		try { if ( br != null ) br.close(); }
		catch (IOException e) { e.printStackTrace(); }
	    }

	return rps;
    }

    private static List readResourcePathsFromResourcePathsTextFiles( String[] resourcePathsTextFileResourcePaths, List delayedLogItemsOut )
    {
	List out = new ArrayList();
	for ( int i = 0, len = resourcePathsTextFileResourcePaths.length; i < len; ++i )
	    out.addAll( readResourcePathsFromResourcePathsTextFile(  resourcePathsTextFileResourcePaths[i], delayedLogItemsOut ) );
	return out;
    }

    private static String[] vmResourcePaths( List delayedLogItemsOut ) 
    {
	List paths = vmResourcePathList(  delayedLogItemsOut );
	return (String[]) paths.toArray( new String[ paths.size() ] );
    }

    private static List vmResourcePathList( List delayedLogItemsOut )
    {
	List pathsFromFiles = readResourcePathsFromResourcePathsTextFiles( DFLT_VM_RSRC_PATHFILES, delayedLogItemsOut );
	List rps;
	if ( pathsFromFiles.size() > 0 )
	    rps = pathsFromFiles;
	else
	    rps = Arrays.asList( HARDCODED_DFLT_RSRC_PATHS );
	return rps;
    }
    
    public synchronized static MultiPropertiesConfig readVmConfig()
    { return readVmConfig( (List) null ); }

    /*
    public synchronized static MultiPropertiesConfig readVmConfig( MLogger logger )
    {
	List items = new ArrayList();
	MultiPropertiesConfig out = readVmConfig( items );
	items.addAll( out.getDelayedLogItems() );
	for (Iterator ii = items.iterator(); ii.hasNext(); )
	{
	    DelayedLogItem item = (DelayedLogItem) ii.next();
	    logger.log( item.getLevel(), item.getText(), item.getException() );
	}
	return out;
    }
    */

    public synchronized static MultiPropertiesConfig readVmConfig( List delayedLogItemsOut )
    {
	if ( vmConfig == null )
	    {
		List rps = vmResourcePathList( delayedLogItemsOut );
		vmConfig = new BasicMultiPropertiesConfig( (String[]) rps.toArray( new String[ rps.size() ] ) ); 
	    }
	return vmConfig;
    }

    public static synchronized boolean foundVmConfig()
    { return vmConfig != null; }

    public static void dumpByPrefix( MultiPropertiesConfig mpc, String pfx )
    {
	Properties props = mpc.getPropertiesByPrefix(pfx);
	Map m = new TreeMap();
	m.putAll( props );
	for ( Iterator ii = m.entrySet().iterator(); ii.hasNext(); )
	{
	    Map.Entry entry = (Map.Entry) ii.next();
	    System.err.println( entry.getKey() + " --> " + entry.getValue() );
	}
    }

    private static void putToSet(Map> map, String key, String value ) {
	Set set = map.get( key );
	if ( set == null ) {
	    set = new HashSet();
	    map.put( key, set );
	}
	set.add( value );
    }

    private static String makeHoconPathFromElements( List newElementsList ) {
	StringBuilder sb = new StringBuilder();
	sb.append("hocon:");
	boolean first = true;
	for( String element : newElementsList ) {
	    if ( first ) first = false;
	    else sb.append(",");
	    sb.append( element );
	}
	return sb.toString();
    }

    private static String normalizeHoconPathElement( String element ) {
	return ( element.indexOf(":") < 0 && element.charAt(0) != '/' ) ? ('/' + element) : element;
    }


    /*
     * Well, this is a pain.
     *
     * The issue is that multiple applications can set up mutiple HOCON overlapping paths,
     * and users, expecting the resolution associated with one application's ful HOCON path,
     * can define substitutions that can't be fulfilled in a different application's config path.
     *
     * Our solution is to detect HOCON paths that overlap, and let overlapping paths fall back
     * to one another in an ordering preserving way, so that most-recent HOCON paths always win,
     * and within individual HOCON paths, the explicit specification overrides, but other overlapping
     * specifications are available behind the explicit specification, in the same ordering as their
     * paths are specified for the VM
     */
    private static List ensureHoconInterresolvability( List paths ) {
	Map> hoconPathToElementsList = new HashMap>();
	Map>  elementToHoconPaths     = new HashMap>();

	List out = new ArrayList();

	// pass 1
	for ( String path : paths ) {
	    if (path.toLowerCase().startsWith("hocon:")) {
		String[] elements = path.substring("hocon:".length()).split("\\s*,\\s*");
		for ( int i = 0, len = elements.length; i < len; ++i )
		    elements[i] = normalizeHoconPathElement( elements[i] );
		hoconPathToElementsList.put( path, Arrays.asList( elements ) );
		for (String element : elements ) {
		    putToSet(elementToHoconPaths, element, path );
		    if ( element.indexOf('.') < 0 && !"/".equals( element ) ) {
			putToSet( elementToHoconPaths, element + ".conf", path );
			putToSet( elementToHoconPaths, element + ".properties", path );
			putToSet( elementToHoconPaths, element + ".json", path );
		    }
		}
	    }
	}

	// pass 2
	for ( String path : paths ) {
	    if (path.toLowerCase().startsWith("hocon:")) {
		List elements = hoconPathToElementsList.get( path );
		Set pathSet = new HashSet();
		for( String element : elements ) {
		    // don't let mutual use of system properties constitute overlap,
		    // since system properties can't contain resolvable substitutions
		    if ( !"/".equals( element ) ) pathSet.addAll( elementToHoconPaths.get( element ) );
		}
		List newElementsList = new ArrayList();
		for( String orderSettingPath : paths ) {
		    if (path.toLowerCase().startsWith("hocon:")) {
			if ( orderSettingPath != path ) { // we add the current path's elements last, since last override
			    if ( pathSet.contains( orderSettingPath ) ) {
				newElementsList.addAll( hoconPathToElementsList.get( orderSettingPath ) );
			    }
			}
		    }
		}
		newElementsList.addAll( hoconPathToElementsList.get( path ) );
		out.add( makeHoconPathFromElements( newElementsList ) );
	    } else {
		out.add( path );
	    }
	}
	return out;
    }

    private ConfigUtils()
    {}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy