001    /*
002     * Copyright (c) 2024 QOS.ch Sarl (Switzerland)
003     * All rights reserved.
004     *
005     * Permission is hereby granted, free  of charge, to any person obtaining
006     * a  copy  of this  software  and  associated  documentation files  (the
007     * "Software"), to  deal in  the Software without  restriction, including
008     * without limitation  the rights to  use, copy, modify,  merge, publish,
009     * distribute,  sublicense, and/or sell  copies of  the Software,  and to
010     * permit persons to whom the Software  is furnished to do so, subject to
011     * the following conditions:
012     *
013     * The  above  copyright  notice  and  this permission  notice  shall  be
014     * included in all copies or substantial portions of the Software.
015     *
016     * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
017     * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
018     * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
019     * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
020     * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
021     * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
022     * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023     *
024     *
025     *
026     */
027
028    package ch.qos.logback.tyler.base;
029
030    import ch.qos.logback.classic.Level;
031    import ch.qos.logback.classic.LoggerContext;
032    import ch.qos.logback.classic.spi.Configurator;
033    import ch.qos.logback.classic.tyler.TylerConfiguratorBase;
034    import ch.qos.logback.core.Context;
035    import ch.qos.logback.core.model.processor.ModelInterpretationContext;
036    import ch.qos.logback.tyler.base.spi.StaticImportData;
037    import ch.qos.logback.tyler.base.util.ClassUtil;
038    import ch.qos.logback.tyler.base.util.VariableNameUtil;
039    import com.squareup.javapoet.*;
040
041    import javax.lang.model.element.Modifier;
042
043    import java.util.*;
044
045    import static ch.qos.logback.tyler.base.TylerConstants.*;
046
047    public class TylerModelInterpretationContext extends ModelInterpretationContext {
048
049        final public TypeSpec.Builder tylerConfiguratorTSB;
050        final public MethodSpec.Builder configureMethodSpecBuilder;
051        final public Map<String, MethodSpec.Builder> mapOfMethodSpecBuilders = new LinkedHashMap<>();
052
053        final FieldSpec contextFieldSpec = FieldSpec.builder(LoggerContext.class, CONTEXT_FIELD_NAME, Modifier.PRIVATE).build();
054        final ParameterSpec contextParameterSpec = ParameterSpec.builder(LoggerContext.class, LOGGER_CONTEXT_PARAMETER_NAME).build();
055        final ParameterSpec levelParameterSpec = ParameterSpec.builder(Level.class, LEVEL_FIELD_NAME).build();
056
057        final List<FieldSpec> fieldSpecs = new ArrayList<>();
058
059        final List<StaticImportData> staticImportsList = new ArrayList<>();
060
061
062        public TylerModelInterpretationContext(Context context) {
063            super(context);
064            this.configureMethodSpecBuilder = initializeConfigureMethodSpecBuilder();
065            this.tylerConfiguratorTSB = initializeTylerConfiguratorTSB();
066        }
067
068        public void addStaticImport(StaticImportData sid) {
069            if (!staticImportsList.contains(sid))
070                staticImportsList.add(sid);
071        }
072
073        TypeSpec.Builder initializeTylerConfiguratorTSB() {
074            //MethodSpec setupLoggerMS = makeSetupLoggerMethodSpec();
075
076            TypeSpec.Builder tsb = TypeSpec.classBuilder(TylerConstants.TYLER_CONFIGURATOR).addJavadoc("""
077                                            
078                                            <p>This file was generated by logback-tyler version %s</p>
079                                            
080                                            <p>Eventual errors and warnings are appended at the end.</p>                    
081                                            
082                                            <p>You may experiment with logback.xml to Java translation, i.e. 
083                                            TylerConfigurator generation, at the following URL:</p>
084                                            
085                                            <p>     https://logback.qos.ch/translator/services/xml2Java.html </p>
086                                            
087                                            <p>This generated TylerConfigurator class is intended to be copied and integrated 
088                                            into the user's project as a custom configurator. It will configure logback 
089                                            without XML. You are free to rename TylerConfigurator as you wish.</p>
090                                            
091                                            <p>It requires logback-classic version %s or later at runtime.</p>
092                                            
093                                            <p>Custom configurators are looked up via Java's service-provide facility. If a 
094                                            custom provider is found, it takes precedence over logback's own configurators, 
095                                            e.g. DefaultJoranConfigurator.</p>
096                                            
097                                            <p>To install your custom configurator to your project, add a 
098                                            provider-configuration file to the following path:</p> 
099                                            
100                                            <pre>  META-INF/services/ch.qos.logback.classic.spi.Configurator</pre>
101                                            
102                                            <p>This provider-configuration file should contain a line with the fully 
103                                            qualified class name of your tyler configurator.</p>
104                                            
105                                            <p>See also item 1 of 'Configuration at initialization' section at </p>
106                                            
107                                            <p>  https://logback.qos.ch/manual/configuration.html#auto_configuration</p> 
108                                            
109                                            <p>With recent versions of logback and logback-tyler you can still
110                                            configure logger levels dynamically using properties files. Note that
111                                            configuration files in properties format can be watched for
112                                            changes. See the documentation on PropertiesConfigurator for more details.</p>
113                                            
114                                            <p>https://logback.qos.ch/manual/configuration.html#propertiesConfigurator</p>
115                                            
116                                            <p>Keep in mind that by integrating a .properties configuration file info 
117                                            your tyler configurator, you can still change logger levels dynamically, without 
118                                            redeploying your application.</p>
119                                            
120                                            """.formatted(TYLER_VERSION, REQUIRED_LOGBACK_VERSION))
121                            .addSuperinterface(Configurator.class)
122                            .superclass(TylerConfiguratorBase.class)
123                            .addModifiers(Modifier.PUBLIC);
124            return tsb;
125        }
126
127        private MethodSpec.Builder initializeConfigureMethodSpecBuilder() {
128            MethodSpec.Builder msb = MethodSpec.methodBuilder(CONFIGURE_METHOD_NAME).addJavadoc("""
129                                            <p>This method performs configuration per {@link $T} interface.</p>
130                                            
131                                            <p>If <code>TylerConfigurator</code> is installed as a configurator service, this 
132                                            method will be called by logback-classic during initialization.</p>
133                                            """, Configurator.class).addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).addParameter(contextParameterSpec)
134                            .returns(Configurator.ExecutionStatus.class)
135                            .addStatement("$N($N)", TylerConfiguratorBase.SET_CONTEXT_METHOD_NAME, contextParameterSpec);
136
137            return msb;
138        }
139
140        public FieldSpec getContextFieldSpec() {
141            return contextFieldSpec;
142        }
143
144//        Shows how to build fields with generic types
145//
146//        public FieldSpec createAppenderBagSpec() {
147//            ParameterizedTypeName mapTypeName = ParameterizedTypeName.get(Map.class, String.class, Appender.class);
148//            FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(mapTypeName, TYLER_APPENDER_BAG_FIELD_NAME).addModifiers(Modifier.PROTECTED, Modifier.FINAL);
149//            fieldSpecBuilder.initializer("new $T<>()", HashMap.class);
150//            fieldSpecBuilder.addJavadoc("A map used to reference appenders during configuration.");
151//
152//            return fieldSpecBuilder.build();
153//        }
154
155        public FieldSpec createAppenderFieldSpec(ClassName desiredAppenderCN, String appenderName) {
156            String appenderVariableName = VariableNameUtil.appenderNameToVariableName(appenderName);
157            FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(desiredAppenderCN, appenderVariableName).addModifiers(Modifier.PROTECTED);
158            fieldSpecBuilder.addJavadoc("Appender variable referencing the appender named $S.", appenderName);
159            return fieldSpecBuilder.build();
160        }
161
162        public FieldSpec createPropertyConditionFieldSpec(ClassName className, String variableName) {
163            FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(className, variableName).addModifiers(Modifier.PROTECTED);
164            fieldSpecBuilder.addJavadoc("Variable used in conditional processing");
165            return fieldSpecBuilder.build();
166        }
167
168
169        public Collection<FieldSpec> getFieldSpecs() {
170            return fieldSpecs;
171        }
172
173        public ClassName makeClassName(String fqcn) {
174            String packageName = ClassUtil.extractPackageName(fqcn);
175            String simpleName = ClassUtil.extractSimpleClassName(fqcn);
176            return ClassName.get(packageName, simpleName);
177        }
178    }