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