1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.model.processor;
15  
16  import java.net.URL;
17  import java.util.ArrayList;
18  import java.util.Collections;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Stack;
23  import java.util.function.Supplier;
24  
25  import ch.qos.logback.core.Appender;
26  import ch.qos.logback.core.Context;
27  import ch.qos.logback.core.joran.GenericXMLConfigurator;
28  import ch.qos.logback.core.joran.JoranConstants;
29  import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
30  import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
31  import ch.qos.logback.core.model.Model;
32  import ch.qos.logback.core.model.util.VariableSubstitutionsHelper;
33  import ch.qos.logback.core.spi.AppenderAttachable;
34  import ch.qos.logback.core.spi.ContextAwareBase;
35  import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
36  
37  public class ModelInterpretationContext extends ContextAwareBase implements ContextAwarePropertyContainer {
38  
39      Stack<Object> objectStack;
40      Stack<Model> modelStack;
41  
42  
43      URL topURL;
44      Boolean topScanBoolean =  null;
45  
46      /**
47       * A supplier of JoranConfigurator instances.
48       *
49       * May be null.
50       *
51       * @since 1.5.5
52       */
53      Supplier<? extends GenericXMLConfigurator> configuratorSupplier;
54  
55  
56      Map<String, Object> objectMap;
57      protected VariableSubstitutionsHelper variableSubstitutionsHelper;
58      protected Map<String, String> importMap;
59  
60      final private BeanDescriptionCache beanDescriptionCache;
61      final DefaultNestedComponentRegistry defaultNestedComponentRegistry = new DefaultNestedComponentRegistry();
62      List<DependencyDefinition> dependencyDefinitionList = new ArrayList<>();
63      final List<String> startedDependees = new ArrayList<>();
64  
65      Object configuratorHint;
66  
67      Model topModel;
68  
69      public ModelInterpretationContext(Context context) {
70          this(context, null);
71      }
72  
73      public ModelInterpretationContext(Context context, Object configuratorHint) {
74          this.context = context;
75          this.configuratorHint = configuratorHint;
76          this.objectStack = new Stack<>();
77          this.modelStack = new Stack<>();
78          this.beanDescriptionCache = new BeanDescriptionCache(context);
79          objectMap = new HashMap<>(5);
80          variableSubstitutionsHelper = new VariableSubstitutionsHelper(context);
81          importMap = new HashMap<>(5);
82      }
83  
84      public ModelInterpretationContext(ModelInterpretationContext otherMic) {
85          this(otherMic.context, otherMic.configuratorHint);
86          importMap = new HashMap<>(otherMic.importMap);
87          variableSubstitutionsHelper =  new VariableSubstitutionsHelper(context, otherMic.getCopyOfPropertyMap());
88          defaultNestedComponentRegistry.duplicate(otherMic.getDefaultNestedComponentRegistry());
89          createAppenderBags();
90      } 
91          
92      public Map<String, Object> getObjectMap() {
93          return objectMap;
94      }
95  
96      public void createAppenderBags() {
97          objectMap.put(JoranConstants.APPENDER_BAG, new HashMap<String, Appender<?>>());
98          objectMap.put(JoranConstants.APPENDER_REF_BAG, new HashMap<String, AppenderAttachable<?>>());
99      }
100 
101     public Model getTopModel() {
102         return topModel;
103     }
104 
105     public void setTopModel(Model topModel) {
106         this.topModel = topModel;
107     }
108 
109     // modelStack =================================
110 
111     public void pushModel(Model m) {
112         modelStack.push(m);
113     }
114 
115     public Model peekModel() {
116         return modelStack.peek();
117     }
118 
119     public boolean isModelStackEmpty() {
120         return modelStack.isEmpty();
121     }
122 
123     public Model popModel() {
124         return modelStack.pop();
125     }
126 
127     // =================== object stack
128 
129     public Stack<Object> getObjectStack() {
130         return objectStack;
131     }
132 
133     public boolean isObjectStackEmpty() {
134         return objectStack.isEmpty();
135     }
136 
137     public Object peekObject() {
138         return objectStack.peek();
139     }
140 
141     public void pushObject(Object o) {
142         objectStack.push(o);
143     }
144 
145     public Object popObject() {
146         return objectStack.pop();
147     }
148 
149     public Object getObject(int i) {
150         return objectStack.get(i);
151     }
152 
153     // ===================== END object stack
154 
155     public Object getConfiguratorHint() {
156         return configuratorHint;
157     }
158 
159     public void setConfiguratorHint(Object configuratorHint) {
160         this.configuratorHint = configuratorHint;
161     }
162 
163     public BeanDescriptionCache getBeanDescriptionCache() {
164         return beanDescriptionCache;
165     }
166 
167     /**
168      * Performs variable substitution on the provided {@code ref} string.
169      *
170      * <p>Value substitution will follow the order</p>
171      * <ol>
172      * <li>properties defined in this {@link ModelInterpretationContext}</li>
173      * <li>properties defined in the {@link Context context} of this {@link ModelInterpretationContext}</li>
174      * <li>System properties</li>
175      * <li>Environment variables</li>
176      * </ol>
177      *
178      * <p>If value substitution occurs it will be output as a status message, unless marked confidential, that is,
179      * if {@code ref} contains the case-insensitive strings PASSWORD, SECRET or CONFIDENTIAL.</p>
180      *
181      * @param ref the string that may contain variables to be substituted; can be {@code null}
182      * @return the string with substitutions applied if applicable; may return {@code null} if {@code ref} is {@code null}
183      */
184     public String subst(String ref)  {
185 
186         String substituted = variableSubstitutionsHelper.subst(ref);
187         if(ref != null && !ref.equals(substituted) ) {
188             String sanitized = variableSubstitutionsHelper.sanitizeIfConfidential(ref, substituted);
189             addInfo("value \""+sanitized+"\" substituted for \""+ref+"\"");
190         }
191         return substituted;
192     }
193 
194     public DefaultNestedComponentRegistry getDefaultNestedComponentRegistry() {
195         return defaultNestedComponentRegistry;
196     }
197 
198     // ================================== dependencies
199 
200     public void addDependencyDefinition(DependencyDefinition dd) {
201         dependencyDefinitionList.add(dd);
202     }
203 
204     public List<DependencyDefinition> getDependencyDefinitions() {
205         return Collections.unmodifiableList(dependencyDefinitionList);
206     }
207 
208     public List<String> getDependencyNamesForModel(Model model) {
209         List<String> dependencyList = new ArrayList<>();
210         for (DependencyDefinition dd : dependencyDefinitionList) {
211             if (dd.getDepender() == model) {
212                dependencyList.add(dd.getDependency());
213             }
214         }
215         return dependencyList;
216     }
217 
218     public boolean hasDependers(String dependencyName) {
219 
220         if (dependencyName == null || dependencyName.trim().length() == 0) {
221             new IllegalArgumentException("Empty dependeeName name not allowed here");
222         }
223 
224         for (DependencyDefinition dd : dependencyDefinitionList) {
225             if (dd.dependency.equals(dependencyName))
226                 return true;
227         }
228 
229         return false;
230     }
231 
232 
233     public void markStartOfNamedDependee(String name) {
234         startedDependees.add(name);
235     }
236 
237     public boolean isNamedDependemcyStarted(String name) {
238         return startedDependees.contains(name);
239     }
240 
241     // ========================================== object map
242 
243     /**
244      * Add a property to the properties of this execution context. If the property
245      * exists already, it is overwritten.
246      */
247     @Override
248     public void addSubstitutionProperty(String key, String value) {
249         variableSubstitutionsHelper.addSubstitutionProperty(key, value);
250     }
251 
252     /**
253      * If a key is found in propertiesMap then return it. Otherwise, delegate to the
254      * context.
255      */
256     public String getProperty(String key) {
257       return  variableSubstitutionsHelper.getProperty(key);
258     }
259 
260     @Override
261     public Map<String, String> getCopyOfPropertyMap() {
262         return variableSubstitutionsHelper.getCopyOfPropertyMap();
263     }
264 
265     // imports ===================================================================
266 
267     /**
268      * Add an import to the importMao
269      * 
270      * @param stem the class to import
271      * @param fqcn the fully qualified name of the class
272      * 
273      * @since 1.3
274      */
275     public void addImport(String stem, String fqcn) {
276         importMap.put(stem, fqcn);
277     }
278 
279     public Map<String, String> getImportMapCopy() {
280         return new HashMap<>(importMap);
281     }
282 
283     
284     /**
285      * Given a stem, get the fully qualified name of the class corresponding to the
286      * stem. For unknown stems, returns the stem as is. If stem is null, null is
287      * returned.
288      * 
289      * @param stem may be null
290      * @return fully qualified name of the class corresponding to the stem. For
291      *         unknown stems, returns the stem as is. If stem is null, null is
292      *         returned.
293      * @since 1.3
294      */
295     public String getImport(String stem) {
296         if (stem == null)
297             return null;
298 
299         String result = importMap.get(stem);
300         if (result == null)
301             return stem;
302         else
303             return result;
304     }
305 
306     /**
307      * Returns a supplier of {@link GenericXMLConfigurator} instance. The returned value may be null.
308      *
309      * @return a supplier of {@link GenericXMLConfigurator} instance, may be null
310      */
311     @Override
312     public Supplier<? extends GenericXMLConfigurator> getConfiguratorSupplier() {
313         return this.configuratorSupplier;
314     }
315 
316     /**
317      *
318      * @param configuratorSupplier
319      */
320     public void setConfiguratorSupplier(Supplier<? extends GenericXMLConfigurator> configuratorSupplier) {
321         this.configuratorSupplier = configuratorSupplier;
322     }
323 
324 
325     public URL getTopURL() {
326         return topURL;
327     }
328 
329     public void setTopURL(URL topURL) {
330         this.topURL = topURL;
331     }
332 
333     public Boolean getTopScanBoolean() {
334         return topScanBoolean;
335     }
336     public void setTopScanBoolean(Boolean topScanBoolean) {
337         this.topScanBoolean = topScanBoolean;
338     }
339 }