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
028package ch.qos.logback.tyler.base.handler;
029
030import ch.qos.logback.core.Context;
031import ch.qos.logback.core.joran.spi.NoAutoStartUtil;
032import ch.qos.logback.core.joran.util.AggregationAssessor;
033import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
034import ch.qos.logback.core.model.ImplicitModel;
035import ch.qos.logback.core.model.Model;
036import ch.qos.logback.core.model.ModelConstants;
037import ch.qos.logback.core.model.processor.ModelHandlerBase;
038import ch.qos.logback.core.model.processor.ModelHandlerException;
039import ch.qos.logback.core.model.processor.ModelInterpretationContext;
040import ch.qos.logback.core.spi.ContextAware;
041import ch.qos.logback.core.spi.LifeCycle;
042import ch.qos.logback.core.util.AggregationType;
043import ch.qos.logback.core.util.Loader;
044import ch.qos.logback.core.util.OptionHelper;
045import ch.qos.logback.core.util.StringUtil;
046import ch.qos.logback.tyler.base.TylerModelInterpretationContext;
047import ch.qos.logback.tyler.base.util.StringToVariableStament;
048import com.squareup.javapoet.ClassName;
049import com.squareup.javapoet.MethodSpec;
050
051import java.lang.reflect.Method;
052
053public class ImplicitModelHandler extends ModelHandlerBase {
054
055    boolean inError = false;
056    private final BeanDescriptionCache beanDescriptionCache;
057    ImplicitModelHandlerData implicitModelHandlerData;
058    AggregationAssessor aggregationAssessor;
059
060    static public final String IGNORING_UNKNOWN_PROP = "Ignoring unknown property";
061
062    public ImplicitModelHandler(Context context, BeanDescriptionCache beanDescriptionCache) {
063        super(context);
064        this.beanDescriptionCache = beanDescriptionCache;
065    }
066
067    static public ImplicitModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
068        BeanDescriptionCache beanDescriptionCache = mic.getBeanDescriptionCache();
069        return new ImplicitModelHandler(context, beanDescriptionCache);
070    }
071
072    protected Class<? extends ImplicitModel> getSupportedModelClass() {
073        return ImplicitModel.class;
074    }
075
076    @Override
077    public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
078        ImplicitModel implicitModel = (ImplicitModel) model;
079
080        TylerModelInterpretationContext tmic = (TylerModelInterpretationContext) mic;
081
082        // calling intercon.peekObject with an empty stack will throw an exception
083        if (mic.isObjectStackEmpty()) {
084            inError = true;
085            return;
086        }
087
088        String nestedElementTagName = implicitModel.getTag();
089        Object o = mic.peekObject();
090
091        if (o == null) {
092            addError("Null object at the top of the stack");
093            inError = true;
094            return;
095        }
096
097        if (!(o instanceof ImplicitModelHandlerData)) {
098            addError("Was expecting class of type " + ImplicitModelHandlerData.class.getName() + " but found"
099                    + o.getClass().getName());
100            inError = true;
101            return;
102        }
103        ImplicitModelHandlerData tylerImplicitData = (ImplicitModelHandlerData) o;
104        this.aggregationAssessor = new AggregationAssessor(beanDescriptionCache, tylerImplicitData.getParentObjectClass());
105        aggregationAssessor.setContext(context);
106
107        AggregationType aggregationType = aggregationAssessor.computeAggregationType(nestedElementTagName);
108
109        switch (aggregationType) {
110        case NOT_FOUND:
111            addWarn(IGNORING_UNKNOWN_PROP + " [" + nestedElementTagName + "] in [" + o.getClass().getName() + "]");
112            inError = true;
113            // no point in processing submodels
114            implicitModel.markAsSkipped();
115            return;
116        case AS_BASIC_PROPERTY:
117        case AS_BASIC_PROPERTY_COLLECTION:
118            doBasicProperty(mic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType);
119            return;
120        // we only push action data if NestComponentIA is applicable
121        case AS_COMPLEX_PROPERTY_COLLECTION:
122        case AS_COMPLEX_PROPERTY:
123            doComplex(tmic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType);
124            return;
125        default:
126            addError("PropertySetter.computeAggregationType returned " + aggregationType);
127            inError = true;
128            return;
129        }
130
131    }
132
133    private void doComplex(TylerModelInterpretationContext tmic, ImplicitModel implicitModel,
134            AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple,
135            AggregationType aggregationType) {
136
137        String className = implicitModel.getClassName();
138        String fqcn = tmic.getImport(className);
139        String nestedElementTagName = implicitModel.getTag();
140
141        Class<?> componentClass = null;
142        try {
143
144            if (!OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) {
145                componentClass = Loader.loadClass(fqcn, context);
146            } else {
147                // guess class name via implicit rules
148                componentClass = aggregationAssessor.getClassNameViaImplicitRules(nestedElementTagName, aggregationType,
149                        tmic.getDefaultNestedComponentRegistry());
150            }
151
152            if (componentClass == null) {
153                inError = true;
154                String errMsg = "Could not find an appropriate class for property [" + nestedElementTagName + "]";
155                addError(errMsg);
156                return;
157            }
158            if (OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) {
159                addInfo("Assuming default type [" + componentClass.getName() + "] for [" + nestedElementTagName
160                        + "] property");
161            }
162
163            this.implicitModelHandlerData = addJavaStatementForComplexProperty(tmic, implicitModel,
164                    classAndMethodSpecTuple, componentClass);
165            tmic.pushObject(implicitModelHandlerData);
166
167        } catch (Exception oops) {
168            inError = true;
169            String msg = "Could not create component [" + implicitModel.getTag() + "] of type [" + fqcn + "]";
170            addError(msg, oops);
171        }
172
173    }
174
175    private ImplicitModelHandlerData addJavaStatementForComplexProperty(TylerModelInterpretationContext tmic,
176            ImplicitModel implicitModel, ImplicitModelHandlerData implicitModelHandlerData, Class<?> componentClass) {
177
178        MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder;
179        String parentVariableName = implicitModelHandlerData.getVariableName();
180        String variableName = StringUtil.lowercaseFirstLetter(componentClass.getSimpleName());
181        ClassName componentCN = ClassName.get(componentClass.getPackageName(), componentClass.getSimpleName());
182
183        methodSpecBuilder.addCode("\n");
184        methodSpecBuilder.addComment("Configure component of type $T", componentCN);
185        methodSpecBuilder.addStatement("$1T $2N = new $1T()", componentCN, variableName);
186        methodSpecBuilder.beginControlFlow("if ($N instanceof $T)", variableName, ContextAware.class);
187        methodSpecBuilder.addStatement("$N.setContext($N)", variableName, tmic.getContextFieldSpec());
188        methodSpecBuilder.endControlFlow();
189
190        ImplicitModelHandlerData cvnmsbt = new ImplicitModelHandlerData(parentVariableName, componentClass,
191                variableName, methodSpecBuilder);
192        return cvnmsbt;
193    }
194
195    private void doBasicProperty(ModelInterpretationContext mic, ImplicitModel implicitModel,
196            AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple,
197            AggregationType aggregationType) {
198        String finalBody = mic.subst(implicitModel.getBodyText());
199        String nestedElementTagName = implicitModel.getTag();
200
201        switch (aggregationType) {
202        case AS_BASIC_PROPERTY:
203            Method setterMethod = aggregationAssessor.findSetterMethod(nestedElementTagName);
204            Class<?>[] paramTypes = setterMethod.getParameterTypes();
205            setPropertyJavaStatement(classAndMethodSpecTuple, setterMethod, finalBody, paramTypes[0]);
206            break;
207        case AS_BASIC_PROPERTY_COLLECTION:
208            //actionData.parentBean.addBasicProperty(actionData.propertyName, finalBody);
209            break;
210        default:
211            addError("Unexpected aggregationType " + aggregationType);
212        }
213    }
214
215    private void setPropertyJavaStatement(ImplicitModelHandlerData classAndMethodSpecTuple, Method setterMethod,
216            String value, Class<?> type) {
217
218        MethodSpec.Builder methodSpecBuilder = classAndMethodSpecTuple.methodSpecBuilder;
219        String variableName = classAndMethodSpecTuple.getVariableName();
220        //String setterSuffix = StringUtil.capitalizeFirstLetter(nestedElementTagName);
221        String valuePart = StringToVariableStament.convertArg(type);
222        methodSpecBuilder.addStatement("$N.$N(" + valuePart + ")", variableName, setterMethod.getName(),
223                value.toLowerCase());
224    }
225
226    @Override
227    public void postHandle(ModelInterpretationContext mic, Model model) {
228        if (inError) {
229            return;
230        }
231        if (implicitModelHandlerData != null) {
232            postHandleComplex(mic, model);
233        }
234
235    }
236
237    private void postHandleComplex(ModelInterpretationContext mic, Model model) {
238
239        Object o = mic.peekObject();
240        if (o != implicitModelHandlerData) {
241            addError("The object on the top the of the stack is not the " + ImplicitModelHandlerData.class
242                    + " instance pushed earlier.");
243        } else {
244            mic.popObject();
245            ImplicitModel implicitModel = (ImplicitModel) model;
246            MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder;
247            String parentVariableName = implicitModelHandlerData.getParentVariableName();
248            String variableName = implicitModelHandlerData.getVariableName();
249            Class objClass = implicitModelHandlerData.getParentObjectClass();
250            Method setterMethod = aggregationAssessor.findSetterMethod(implicitModel.getTag());
251
252            AggregationAssessor nestedAggregationAssessor = new AggregationAssessor(beanDescriptionCache, setterMethod.getParameterTypes()[0]);
253            nestedAggregationAssessor.setContext(context);
254
255            Method parentSetterMethod = nestedAggregationAssessor.findSetterMethod(ModelConstants.PARENT_PROPPERTY_KEY);
256
257            if(parentSetterMethod != null) {
258                methodSpecBuilder.addStatement("$N.$N($N)", variableName, parentSetterMethod.getName(),parentVariableName);
259            } else {
260                methodSpecBuilder.addComment("===========no parent setter");
261            }
262
263
264
265            methodSpecBuilder.addComment("start the complex property if it implements LifeCycle and is not");
266            methodSpecBuilder.addComment("marked with a @NoAutoStart annotation");
267            methodSpecBuilder.beginControlFlow("if(($1N instanceof $2T) && $3T.notMarkedWithNoAutoStart($1N))", variableName, LifeCycle.class,
268                    NoAutoStartUtil.class);
269            methodSpecBuilder.addStatement("(($T) $N).start()", LifeCycle.class, variableName);
270            methodSpecBuilder.endControlFlow();
271
272            methodSpecBuilder.addComment("Inject component of type $T into parent", objClass);
273            methodSpecBuilder.addStatement("$N.$N($N)", parentVariableName, setterMethod.getName(), variableName);
274        }
275    }
276}