001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, 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.pattern;
015
016import java.util.HashMap;
017import java.util.Map;
018import java.util.function.Supplier;
019
020import ch.qos.logback.core.Context;
021import ch.qos.logback.core.CoreConstants;
022import ch.qos.logback.core.LayoutBase;
023import ch.qos.logback.core.pattern.color.ConverterSupplierByClassName;
024import ch.qos.logback.core.pattern.parser.Node;
025import ch.qos.logback.core.pattern.parser.Parser;
026import ch.qos.logback.core.spi.ScanException;
027import ch.qos.logback.core.status.ErrorStatus;
028import ch.qos.logback.core.status.StatusManager;
029
030abstract public class PatternLayoutBase<E> extends LayoutBase<E> {
031
032    static final int INTIAL_STRING_BUILDER_SIZE = 256;
033    Converter<E> head;
034    String pattern;
035    protected PostCompileProcessor<E> postCompileProcessor;
036
037    /**
038     * <p>It should be noted that the default converter map is a static variable. Thus, changes made
039     * through {@link #getDefaultConverterSupplierMap()} apply to all instances of this class.
040     * </p>
041     *
042     * <p>The {@link #getInstanceConverterMap} variable allows for very specific extensions
043     * without impacting other instances</p>
044     */
045    Map<String, Supplier<DynamicConverter>> instanceConverterMap = new HashMap<>();
046    protected boolean outputPatternAsHeader = false;
047
048    /**
049     * Concrete implementations of this class are responsible for elaborating the
050     * mapping between pattern words and supplying converter instances.
051     * 
052     * @return A map associating pattern words to the names of converter suppliers
053     * @since 1.5.13
054     */
055     protected abstract Map<String, Supplier<DynamicConverter>> getDefaultConverterSupplierMap();
056
057    /**
058     * <p>BEWARE: The map of type String,String for mapping conversion words is deprecated.
059     * Use {@link #getDefaultConverterSupplierMap()} instead.</p>
060     *
061     * <p>Existing code such as getDefaultMap().put("k", X.class.getName()) should be replaced by
062     * getDefaultConverterSupplierMap().put("k", X::new) </p>
063     *
064     * <p>Note that values in the map will still be taken into account and processed correctly.</p>
065     *
066     * @return a map of keys and class names
067     */
068    @Deprecated
069    abstract public Map<String, String> getDefaultConverterMap();
070
071    /**
072     * Returns a map where the default converter map is merged with the map
073     * contained in the context.
074     */
075    public Map<String, Supplier<DynamicConverter>> getEffectiveConverterMap() {
076        Map<String, Supplier<DynamicConverter>> effectiveMap = new HashMap<>();
077
078        // add the least specific map fist
079        Map<String, Supplier<DynamicConverter>> defaultConverterSupplierMap = getDefaultConverterSupplierMap();
080        if (defaultConverterSupplierMap != null) {
081            effectiveMap.putAll(defaultConverterSupplierMap);
082        }
083
084        caterForLegacyConverterMaps(effectiveMap);
085
086        // contextMap is more specific than the default map
087        Context context = getContext();
088        if (context != null) {
089            @SuppressWarnings("unchecked")
090            Map<String, Supplier<DynamicConverter>> contextMap = (Map<String, Supplier<DynamicConverter>>) context
091                    .getObject(CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS);
092            if (contextMap != null) {
093                effectiveMap.putAll(contextMap);
094            }
095        }
096        // set the most specific map last
097        effectiveMap.putAll(instanceConverterMap);
098        return effectiveMap;
099    }
100
101    /**
102     * Add class name values into the effective map to support external extensions
103     * and subclasses.
104     *
105     * @param effectiveMap
106     */
107    private void caterForLegacyConverterMaps(Map<String, Supplier<DynamicConverter>> effectiveMap) {
108        Map<String, String> mapFromContext = (Map<String, String>) this.context
109                        .getObject(CoreConstants.PATTERN_RULE_REGISTRY);
110
111        migrateFromStringMapToSupplierMap(mapFromContext, effectiveMap);
112
113        Map<String, String> defaultConverterMap = getDefaultConverterMap();
114        migrateFromStringMapToSupplierMap(defaultConverterMap, effectiveMap);
115    }
116
117    private void migrateFromStringMapToSupplierMap(Map<String, String> legacyMap, Map<String, Supplier<DynamicConverter>> targetSupplierMap) {
118        if(legacyMap == null)
119            return;
120
121        // this transformation is for backward compatibility of existing code
122        for(Map.Entry<String, String> entry: legacyMap.entrySet()) {
123            String key = entry.getKey();
124            String converterClassName = entry.getValue();
125            ConverterSupplierByClassName converterSupplierByClassName = new ConverterSupplierByClassName(key, converterClassName);
126            converterSupplierByClassName.setContext(getContext());
127            targetSupplierMap.put(key, converterSupplierByClassName);
128        }
129
130    }
131
132    public void start() {
133        if (pattern == null || pattern.length() == 0) {
134            addError("Empty or null pattern.");
135            return;
136        }
137        try {
138            Parser<E> p = new Parser<E>(pattern);
139            if (getContext() != null) {
140                p.setContext(getContext());
141            }
142            Node t = p.parse();
143            this.head = p.compile(t, getEffectiveConverterMap());
144            if (postCompileProcessor != null) {
145                postCompileProcessor.process(context, head);
146            }
147            ConverterUtil.setContextForConverters(getContext(), head);
148            ConverterUtil.startConverters(this.head);
149            super.start();
150        } catch (ScanException sce) {
151            StatusManager sm = getContext().getStatusManager();
152            sm.add(new ErrorStatus("Failed to parse pattern \"" + getPattern() + "\".", this, sce));
153        }
154    }
155
156    public void setPostCompileProcessor(PostCompileProcessor<E> postCompileProcessor) {
157        this.postCompileProcessor = postCompileProcessor;
158    }
159
160    /**
161     *
162     * @param head
163     * @deprecated Use {@link ConverterUtil#setContextForConverters} instead. This
164     *             method will be removed in future releases.
165     */
166    protected void setContextForConverters(Converter<E> head) {
167        ConverterUtil.setContextForConverters(getContext(), head);
168    }
169
170    protected String writeLoopOnConverters(E event) {
171        StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE);
172        Converter<E> c = head;
173        while (c != null) {
174            c.write(strBuilder, event);
175            c = c.getNext();
176        }
177        return strBuilder.toString();
178    }
179
180    public String getPattern() {
181        return pattern;
182    }
183
184    public void setPattern(String pattern) {
185        this.pattern = pattern;
186    }
187
188    public String toString() {
189        return this.getClass().getName() + "(\"" + getPattern() + "\")";
190    }
191
192    public Map<String, Supplier<DynamicConverter>> getInstanceConverterMap() {
193        return instanceConverterMap;
194    }
195
196    protected String getPresentationHeaderPrefix() {
197        return CoreConstants.EMPTY_STRING;
198    }
199
200    public boolean isOutputPatternAsHeader() {
201        return outputPatternAsHeader;
202    }
203
204    public void setOutputPatternAsHeader(boolean outputPatternAsHeader) {
205        this.outputPatternAsHeader = outputPatternAsHeader;
206    }
207
208    @Override
209    public String getPresentationHeader() {
210        if (outputPatternAsHeader)
211            return getPresentationHeaderPrefix() + pattern;
212        else
213            return super.getPresentationHeader();
214    }
215}