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        caterForLegacy_DefaultConverterMap(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);
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 caterForLegacy_DefaultConverterMap(Map<String, Supplier<DynamicConverter>> effectiveMap) {
108        // this transformation is for backward compatibility of existing code
109        Map<String, String> defaultConverterMap = getDefaultConverterMap();
110        if(defaultConverterMap != null) {
111            for(Map.Entry<String, String> entry: defaultConverterMap.entrySet()) {
112                String key = entry.getKey().toString();
113                String converterClassName = entry.getValue();
114                ConverterSupplierByClassName converterSupplierByClassName = new ConverterSupplierByClassName(key, converterClassName);
115                converterSupplierByClassName.setContext(getContext());
116                effectiveMap.put(key, converterSupplierByClassName);
117            }
118        }
119    }
120
121    public void start() {
122        if (pattern == null || pattern.length() == 0) {
123            addError("Empty or null pattern.");
124            return;
125        }
126        try {
127            Parser<E> p = new Parser<E>(pattern);
128            if (getContext() != null) {
129                p.setContext(getContext());
130            }
131            Node t = p.parse();
132            this.head = p.compile(t, getEffectiveConverterMap());
133            if (postCompileProcessor != null) {
134                postCompileProcessor.process(context, head);
135            }
136            ConverterUtil.setContextForConverters(getContext(), head);
137            ConverterUtil.startConverters(this.head);
138            super.start();
139        } catch (ScanException sce) {
140            StatusManager sm = getContext().getStatusManager();
141            sm.add(new ErrorStatus("Failed to parse pattern \"" + getPattern() + "\".", this, sce));
142        }
143    }
144
145    public void setPostCompileProcessor(PostCompileProcessor<E> postCompileProcessor) {
146        this.postCompileProcessor = postCompileProcessor;
147    }
148
149    /**
150     *
151     * @param head
152     * @deprecated Use {@link ConverterUtil#setContextForConverters} instead. This
153     *             method will be removed in future releases.
154     */
155    protected void setContextForConverters(Converter<E> head) {
156        ConverterUtil.setContextForConverters(getContext(), head);
157    }
158
159    protected String writeLoopOnConverters(E event) {
160        StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE);
161        Converter<E> c = head;
162        while (c != null) {
163            c.write(strBuilder, event);
164            c = c.getNext();
165        }
166        return strBuilder.toString();
167    }
168
169    public String getPattern() {
170        return pattern;
171    }
172
173    public void setPattern(String pattern) {
174        this.pattern = pattern;
175    }
176
177    public String toString() {
178        return this.getClass().getName() + "(\"" + getPattern() + "\")";
179    }
180
181    public Map<String, Supplier<DynamicConverter>> getInstanceConverterMap() {
182        return instanceConverterMap;
183    }
184
185    protected String getPresentationHeaderPrefix() {
186        return CoreConstants.EMPTY_STRING;
187    }
188
189    public boolean isOutputPatternAsHeader() {
190        return outputPatternAsHeader;
191    }
192
193    public void setOutputPatternAsHeader(boolean outputPatternAsHeader) {
194        this.outputPatternAsHeader = outputPatternAsHeader;
195    }
196
197    @Override
198    public String getPresentationHeader() {
199        if (outputPatternAsHeader)
200            return getPresentationHeaderPrefix() + pattern;
201        else
202            return super.getPresentationHeader();
203    }
204}