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,
105                tylerImplicitData.getParentObjectClass());
106        aggregationAssessor.setContext(context);
107
108        AggregationType aggregationType = aggregationAssessor.computeAggregationType(nestedElementTagName);
109
110        switch (aggregationType) {
111        case NOT_FOUND:
112            addWarn(IGNORING_UNKNOWN_PROP + " [" + nestedElementTagName + "] in [" + o.getClass().getName() + "]");
113            inError = true;
114            // no point in processing submodels
115            implicitModel.markAsSkipped();
116            return;
117        case AS_BASIC_PROPERTY:
118        case AS_BASIC_PROPERTY_COLLECTION:
119            doBasicProperty(mic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType);
120            return;
121        // we only push action data if NestComponentIA is applicable
122        case AS_COMPLEX_PROPERTY_COLLECTION:
123        case AS_COMPLEX_PROPERTY:
124            doComplex(tmic, implicitModel, aggregationAssessor, tylerImplicitData, aggregationType);
125            return;
126        default:
127            addError("PropertySetter.computeAggregationType returned " + aggregationType);
128            inError = true;
129            return;
130        }
131
132    }
133
134    private void doComplex(TylerModelInterpretationContext tmic, ImplicitModel implicitModel,
135            AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple,
136            AggregationType aggregationType) {
137
138        String className = implicitModel.getClassName();
139        String fqcn = tmic.getImport(className);
140        String nestedElementTagName = implicitModel.getTag();
141
142        Class<?> componentClass = null;
143        try {
144
145            if (!OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) {
146                componentClass = Loader.loadClass(fqcn, context);
147            } else {
148                // guess class name via implicit rules
149                componentClass = aggregationAssessor.getClassNameViaImplicitRules(nestedElementTagName, aggregationType,
150                        tmic.getDefaultNestedComponentRegistry());
151            }
152
153            if (componentClass == null) {
154                inError = true;
155                String errMsg = "Could not find an appropriate class for property [" + nestedElementTagName + "]";
156                addError(errMsg);
157                return;
158            }
159            if (OptionHelper.isNullOrEmptyOrAllSpaces(fqcn)) {
160                addInfo("Assuming default type [" + componentClass.getName() + "] for [" + nestedElementTagName
161                        + "] property");
162            }
163
164            this.implicitModelHandlerData = addJavaStatementForComplexProperty(tmic, implicitModel,
165                    classAndMethodSpecTuple, componentClass);
166            tmic.pushObject(implicitModelHandlerData);
167
168        } catch (Exception oops) {
169            inError = true;
170            String msg = "Could not create component [" + implicitModel.getTag() + "] of type [" + fqcn + "]";
171            addError(msg, oops);
172        }
173
174    }
175
176    private ImplicitModelHandlerData addJavaStatementForComplexProperty(TylerModelInterpretationContext tmic,
177            ImplicitModel implicitModel, ImplicitModelHandlerData implicitModelHandlerData, Class<?> componentClass) {
178
179        MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder;
180        String parentVariableName = implicitModelHandlerData.getVariableName();
181        String variableName = StringUtil.lowercaseFirstLetter(componentClass.getSimpleName());
182        ClassName componentCN = ClassName.get(componentClass.getPackageName(), componentClass.getSimpleName());
183
184        methodSpecBuilder.addCode("\n");
185        methodSpecBuilder.addComment("Configure component of type $T", componentCN);
186        methodSpecBuilder.addStatement("$1T $2N = new $1T()", componentCN, variableName);
187        methodSpecBuilder.beginControlFlow("if ($N instanceof $T)", variableName, ContextAware.class);
188        methodSpecBuilder.addStatement("$N.setContext($N)", variableName, tmic.getContextFieldSpec());
189        methodSpecBuilder.endControlFlow();
190
191        ImplicitModelHandlerData cvnmsbt = new ImplicitModelHandlerData(parentVariableName, componentClass,
192                variableName, methodSpecBuilder);
193        return cvnmsbt;
194    }
195
196    private void doBasicProperty(ModelInterpretationContext mic, ImplicitModel implicitModel,
197            AggregationAssessor aggregationAssessor, ImplicitModelHandlerData classAndMethodSpecTuple,
198            AggregationType aggregationType) {
199        String finalBody = mic.subst(implicitModel.getBodyText());
200        String nestedElementTagName = implicitModel.getTag();
201
202        switch (aggregationType) {
203        case AS_BASIC_PROPERTY:
204            Method setterMethod = aggregationAssessor.findSetterMethod(nestedElementTagName);
205            Class<?>[] paramTypes = setterMethod.getParameterTypes();
206            setPropertyJavaStatement(classAndMethodSpecTuple, setterMethod, finalBody, paramTypes[0]);
207            break;
208        case AS_BASIC_PROPERTY_COLLECTION:
209            //actionData.parentBean.addBasicProperty(actionData.propertyName, finalBody);
210            break;
211        default:
212            addError("Unexpected aggregationType " + aggregationType);
213        }
214    }
215
216    private void setPropertyJavaStatement(ImplicitModelHandlerData classAndMethodSpecTuple, Method setterMethod,
217            String value, Class<?> type) {
218
219        MethodSpec.Builder methodSpecBuilder = classAndMethodSpecTuple.methodSpecBuilder;
220        String variableName = classAndMethodSpecTuple.getVariableName();
221        //String setterSuffix = StringUtil.capitalizeFirstLetter(nestedElementTagName);
222        String valuePart = StringToVariableStament.convertArg(type);
223        methodSpecBuilder.addStatement("$N.$N(" + valuePart + ")", variableName, setterMethod.getName(),
224                value.toLowerCase());
225    }
226
227    @Override
228    public void postHandle(ModelInterpretationContext mic, Model model) {
229        if (inError) {
230            return;
231        }
232        if (implicitModelHandlerData != null) {
233            postHandleComplex(mic, model);
234        }
235
236    }
237
238    private void postHandleComplex(ModelInterpretationContext mic, Model model) {
239
240        Object o = mic.peekObject();
241        if (o != implicitModelHandlerData) {
242            addError("The object on the top the of the stack is not the " + ImplicitModelHandlerData.class
243                    + " instance pushed earlier.");
244        } else {
245            mic.popObject();
246            ImplicitModel implicitModel = (ImplicitModel) model;
247            MethodSpec.Builder methodSpecBuilder = implicitModelHandlerData.methodSpecBuilder;
248            String parentVariableName = implicitModelHandlerData.getParentVariableName();
249            String variableName = implicitModelHandlerData.getVariableName();
250            Class objClass = implicitModelHandlerData.getParentObjectClass();
251            Method setterMethod = aggregationAssessor.findSetterMethod(implicitModel.getTag());
252
253            AggregationAssessor nestedAggregationAssessor = new AggregationAssessor(beanDescriptionCache,
254                    setterMethod.getParameterTypes()[0]);
255            nestedAggregationAssessor.setContext(context);
256
257            Method parentSetterMethod = nestedAggregationAssessor.findSetterMethod(ModelConstants.PARENT_PROPPERTY_KEY);
258
259            if (parentSetterMethod != null) {
260                methodSpecBuilder.addStatement("$N.$N($N)", variableName, parentSetterMethod.getName(),
261                        parentVariableName);
262            } else {
263                methodSpecBuilder.addComment("===========no parent setter");
264            }
265
266            methodSpecBuilder.addComment("start the complex property if it implements LifeCycle and is not");
267            methodSpecBuilder.addComment("marked with a @NoAutoStart annotation");
268            methodSpecBuilder.beginControlFlow("if($T.shouldBeStarted($N))", NoAutoStartUtil.class, variableName);
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}