001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.model.processor;
015
016import java.util.ArrayList;
017import java.util.Collections;
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.Properties;
022import java.util.Stack;
023
024import ch.qos.logback.core.Appender;
025import ch.qos.logback.core.Context;
026import ch.qos.logback.core.joran.JoranConstants;
027import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
028import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
029import ch.qos.logback.core.model.Model;
030import ch.qos.logback.core.spi.AppenderAttachable;
031import ch.qos.logback.core.spi.ContextAwareBase;
032import ch.qos.logback.core.spi.PropertyContainer;
033import ch.qos.logback.core.spi.ScanException;
034import ch.qos.logback.core.util.OptionHelper;
035
036public class ModelInterpretationContext extends ContextAwareBase implements PropertyContainer {
037
038    Stack<Object> objectStack;
039    Stack<Model> modelStack;
040
041    Map<String, Object> objectMap;
042    protected Map<String, String> propertiesMap;
043    protected Map<String, String> importMap;
044
045    final private BeanDescriptionCache beanDescriptionCache;
046    final DefaultNestedComponentRegistry defaultNestedComponentRegistry = new DefaultNestedComponentRegistry();
047    List<DependencyDefinition> dependencyDefinitionList = new ArrayList<>();
048    final List<String> startedDependees = new ArrayList<>();
049
050    public ModelInterpretationContext(Context context) {
051        this.context = context;
052        this.objectStack = new Stack<>();
053        this.modelStack = new Stack<>();
054        this.beanDescriptionCache = new BeanDescriptionCache(context);
055        objectMap = new HashMap<>(5);
056        propertiesMap = new HashMap<>(5);
057        importMap = new HashMap<>(5);
058    }
059
060    public ModelInterpretationContext(ModelInterpretationContext otherMic) {
061        this(otherMic.context);
062        importMap = new HashMap<>(otherMic.importMap);
063        propertiesMap = new HashMap<>(otherMic.propertiesMap);
064        defaultNestedComponentRegistry.duplicate(otherMic.getDefaultNestedComponentRegistry());
065        createAppenderBags();
066    } 
067        
068    public Map<String, Object> getObjectMap() {
069        return objectMap;
070    }
071
072    public void createAppenderBags() {
073        objectMap.put(JoranConstants.APPENDER_BAG, new HashMap<String, Appender<?>>());
074        objectMap.put(JoranConstants.APPENDER_REF_BAG, new HashMap<String, AppenderAttachable<?>>());
075    }
076    
077    // modelStack =================================
078
079    public void pushModel(Model m) {
080        modelStack.push(m);
081    }
082
083    public Model peekModel() {
084        return modelStack.peek();
085    }
086
087    public boolean isModelStackEmpty() {
088        return modelStack.isEmpty();
089    }
090
091    public Model popModel() {
092        return modelStack.pop();
093    }
094
095    // =================== object stack
096
097    public Stack<Object> getObjectStack() {
098        return objectStack;
099    }
100
101    public boolean isObjectStackEmpty() {
102        return objectStack.isEmpty();
103    }
104
105    public Object peekObject() {
106        return objectStack.peek();
107    }
108
109    public void pushObject(Object o) {
110        objectStack.push(o);
111    }
112
113    public Object popObject() {
114        return objectStack.pop();
115    }
116
117    public Object getObject(int i) {
118        return objectStack.get(i);
119    }
120
121    // ===================== END object stack
122
123    public BeanDescriptionCache getBeanDescriptionCache() {
124        return beanDescriptionCache;
125    }
126
127    public String subst(String ref) {
128        if (ref == null) {
129            return null;
130        }
131
132        try {
133            return OptionHelper.substVars(ref, this, context);
134        } catch (ScanException | IllegalArgumentException e) {
135            addError("Problem while parsing [" + ref + "]", e);
136            return ref;
137        }
138
139    }
140
141    /**
142     * Add a property to the properties of this execution context. If the property
143     * exists already, it is overwritten.
144     */
145    public void addSubstitutionProperty(String key, String value) {
146        if (key == null || value == null) {
147            return;
148        }
149        // values with leading or trailing spaces are bad. We remove them now.
150        value = value.trim();
151        propertiesMap.put(key, value);
152    }
153
154    public void addSubstitutionProperties(Properties props) {
155        if (props == null) {
156            return;
157        }
158        for (Object keyObject : props.keySet()) {
159            String key = (String) keyObject;
160            String val = props.getProperty(key);
161            addSubstitutionProperty(key, val);
162        }
163    }
164
165    public DefaultNestedComponentRegistry getDefaultNestedComponentRegistry() {
166        return defaultNestedComponentRegistry;
167    }
168
169    // ================================== dependencies
170
171    public void addDependencyDefinition(DependencyDefinition dd) {
172        dependencyDefinitionList.add(dd);
173    }
174
175    public List<DependencyDefinition> getDependencyDefinitions() {
176        return Collections.unmodifiableList(dependencyDefinitionList);
177    }
178
179    public List<String> getDependeeNamesForModel(Model model) {
180        List<String> dependencyList = new ArrayList<>();
181        for (DependencyDefinition dd : dependencyDefinitionList) {
182            if (dd.getDepender() == model) {
183               dependencyList.add(dd.getDependee());
184            }
185        }
186        return dependencyList;
187    }
188
189    public boolean hasDependers(String dependeeName) {
190
191        if (dependeeName == null || dependeeName.trim().length() == 0) {
192            new IllegalArgumentException("Empty dependeeName name not allowed here");
193        }
194
195        for (DependencyDefinition dd : dependencyDefinitionList) {
196            if (dd.dependee.equals(dependeeName))
197                return true;
198        }
199
200        return false;
201    }
202
203
204    public void markStartOfNamedDependee(String name) {
205        startedDependees.add(name);
206    }
207
208    public boolean isNamedDependeeStarted(String name) {
209        return startedDependees.contains(name);
210    }
211
212    // ========================================== object map
213
214    /**
215     * If a key is found in propertiesMap then return it. Otherwise, delegate to the
216     * context.
217     */
218    public String getProperty(String key) {
219        String v = propertiesMap.get(key);
220        if (v != null) {
221            return v;
222        } else {
223            return context.getProperty(key);
224        }
225    }
226
227    @Override
228    public Map<String, String> getCopyOfPropertyMap() {
229        return new HashMap<String, String>(propertiesMap);
230    }
231
232    // imports
233
234    /**
235     * Add an import to the importMao
236     * 
237     * @param stem the class to import
238     * @param fqcn the fully qualified name of the class
239     * 
240     * @since 1.3
241     */
242    public void addImport(String stem, String fqcn) {
243        importMap.put(stem, fqcn);
244    }
245
246    public Map<String, String> getImportMapCopy() {
247        return new HashMap<>(importMap);
248    }
249
250    
251    /**
252     * Given a stem, get the fully qualified name of the class corresponding to the
253     * stem. For unknown stems, returns the stem as is. If stem is null, null is
254     * returned.
255     * 
256     * @param stem may be null
257     * @return fully qualified name of the class corresponding to the stem. For
258     *         unknown stems, returns the stem as is. If stem is null, null is
259     *         returned.
260     * @since 1.3
261     */
262    public String getImport(String stem) {
263        if (stem == null)
264            return null;
265
266        String result = importMap.get(stem);
267        if (result == null)
268            return stem;
269        else
270            return result;
271    }
272
273}