# FileConfigurationService.java

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */
    package org.qi4j.library.fileconfig;

    import java.io.File;
    import java.util.HashMap;
    import java.util.Locale;
    import java.util.Map;
    import java.util.ResourceBundle;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import org.qi4j.api.activation.ActivatorAdapter;
    import org.qi4j.api.activation.Activators;
    import org.qi4j.api.injection.scope.Structure;
    import org.qi4j.api.injection.scope.This;
    import org.qi4j.api.injection.scope.Uses;
    import org.qi4j.api.mixin.Mixins;
    import org.qi4j.api.property.Property;
    import org.qi4j.api.service.ServiceComposite;
    import org.qi4j.api.service.ServiceDescriptor;
    import org.qi4j.api.service.ServiceReference;
    import org.qi4j.api.structure.Application;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    @Mixins( FileConfigurationService.Mixin.class )
    @Activators( FileConfigurationService.Activator.class )
    public interface FileConfigurationService
            extends FileConfiguration, ServiceComposite
    {

        void resolveFileConfiguration();

        public static class Activator
                extends ActivatorAdapter<ServiceReference<FileConfigurationService>>
        {

            @Override
            public void afterActivation( ServiceReference<FileConfigurationService> activated )
                    throws Exception
            {
                activated.get().resolveFileConfiguration();
            }

        }

        interface Data
        {

            Property<OS> os();

            Property<String> application();

            Property<File> user();

            Property<File> configuration();

            Property<File> data();

            Property<File> temporary();

            Property<File> cache();

            Property<File> log();

        }

        abstract class Mixin
                implements FileConfigurationService
        {

            final Logger logger = LoggerFactory.getLogger( getClass().getName() );

            @This
            Data data;

            @Uses
            ServiceDescriptor descriptor;

            @Structure
            Application app;

            @Override
            public void resolveFileConfiguration()
            {
                OS os = detectOS();

                data.os().set( os );
                logger.info( "Operating system : " + os.name() );

                // Get bundle with application name and configured directories
                ResourceBundle bundle = ResourceBundle.getBundle( FileConfiguration.class.getName(), new Locale( os.name() ) );

                Map arguments = getArguments( os );

                data.configuration().set( new File( format( bundle.getString( "configuration" ), arguments ) ) );
                data.data().set( new File( format( bundle.getString( "data" ), arguments ) ) );
                data.temporary().set( new File( format( bundle.getString( "temporary" ), arguments ) ) );
                data.cache().set( new File( format( bundle.getString( "cache" ), arguments ) ) );
                data.log().set( new File( format( bundle.getString( "log" ), arguments ) ) );

                applyOverride();
                autoCreateDirectories();
            }

            private Map getArguments( OS os )
            {
                // Get user directory
                String user = System.getenv( "USERPROFILE" ); // On Windows we use this instead of user.home
                if ( user == null ) {
                    user = System.getProperty( "user.home" );
                }

                data.user().set( new File( user ) );

                // Set application name. This is taken from the Zest application but can be overriden by a system property
                String application = System.getProperty( "application", app.name() );

                if ( !app.mode().equals( Application.Mode.production ) ) {
                    application += "-" + app.mode().name();
                }

                data.application().set( application );

                // Temp dir
                String temp = System.getProperty( "java.io.tmpdir" );
                if ( temp.endsWith( "/" ) ) {
                    temp = temp.substring( 0, temp.length() - 1 );
                }

                // Arguments available to use in directory specifications
                Map<String, String> arguments = new HashMap<String, String>();
                arguments.put( "application", application );
                arguments.put( "user", user );
                arguments.put( "os", os.name() );
                arguments.put( "temp", temp );

                // Add environment variables
                for ( Map.Entry<String, String> envVariable : System.getenv().entrySet() ) {
                    arguments.put( "environment." + envVariable.getKey(), envVariable.getValue() );
                }
                // Add system properties
                for ( Map.Entry<Object, Object> envVariable : System.getProperties().entrySet() ) {
                    arguments.put( "system." + envVariable.getKey(), envVariable.getValue().toString() );
                }

                return arguments;
            }

            private void applyOverride()
            {
                FileConfigurationOverride override = descriptor.metaInfo( FileConfigurationOverride.class );
                if ( override != null ) {
                    if ( override.configuration() != null ) {
                        data.configuration().set( override.configuration() );
                    }
                    if ( override.data() != null ) {
                        data.data().set( override.data() );
                    }
                    if ( override.temporary() != null ) {
                        data.temporary().set( override.temporary() );
                    }
                    if ( override.cache() != null ) {
                        data.cache().set( override.cache() );
                    }
                    if ( override.log() != null ) {
                        data.log().set( override.log() );
                    }
                }
            }

            @Override
            public OS os()
            {
                return data.os().get();
            }

            @Override
            public File user()
            {
                return data.user().get();
            }

            @Override
            public File configurationDirectory()
            {
                return data.configuration().get();
            }

            @Override
            public File dataDirectory()
            {
                return data.data().get();
            }

            @Override
            public File temporaryDirectory()
            {
                return data.temporary().get();
            }

            @Override
            public File cacheDirectory()
            {
                return data.cache().get();
            }

            @Override
            public File logDirectory()
            {
                return data.log().get();
            }

            private OS detectOS()
            {
                String osName = System.getProperty( "os.name" ).toLowerCase();
                OS os;
                if ( osName.indexOf( "win" ) != -1 ) {
                    os = OS.windows;
                } else if ( osName.indexOf( "mac" ) != -1 ) {
                    os = OS.mac;
                } else {
                    os = OS.unix;
                }
                return os;
            }

            private void autoCreateDirectories()
            {
                // Create directories
                if ( !configurationDirectory().exists() && !configurationDirectory().mkdirs() ) {
                    throw new IllegalStateException( "Could not create configuration directory(" + configurationDirectory() + ")" );
                }
                if ( !dataDirectory().exists() && !dataDirectory().mkdirs() ) {
                    throw new IllegalStateException( "Could not create data directory(" + dataDirectory() + ")" );
                }
                if ( !temporaryDirectory().exists() && !temporaryDirectory().mkdirs() ) {
                    throw new IllegalStateException( "Could not create temporary directory(" + temporaryDirectory() + ")" );
                }
                if ( !cacheDirectory().exists() && !cacheDirectory().mkdirs() ) {
                    throw new IllegalStateException( "Could not create cache directory(" + cacheDirectory() + ")" );
                }
                if ( !logDirectory().exists() && !logDirectory().mkdirs() ) {
                    throw new IllegalStateException( "Could not create log directory(" + logDirectory() + ")" );
                }
            }

            private String format( String configuration, Map arguments )
            {
                Pattern paramPattern = Pattern.compile( "\\{(.*?)\\}" );

                Matcher matcher = paramPattern.matcher( configuration );

                StringBuilder buffer = new StringBuilder();
                int lastEnd = 0;
                while ( matcher.find() ) {
                    buffer.append( configuration.substring( lastEnd, matcher.start() ) );
                    lastEnd = matcher.end();

                    String var = matcher.group( 1 );
                    Object value = arguments.get( var );

                    if ( value != null ) {
                        buffer.append( format( value.toString(), arguments ) );
                    } else {
                        throw new IllegalArgumentException( "Illegal file configuration parameter:" + var );
                    }
                }
                buffer.append( configuration.substring( lastEnd ) );

                return buffer.toString();
            }

        }

    }