001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.util;
018
019import org.apache.logging.log4j.Logger;
020import org.apache.logging.log4j.status.StatusLogger;
021
022/**
023 * Utility for preventing primitive parameter values from being auto-boxed. Auto-boxing creates temporary objects
024 * which contribute to pressure on the garbage collector. With this utility users can convert primitive values directly
025 * into text without allocating temporary objects.
026 * <p>
027 * Example usage:
028 * </p><pre>
029 * import static org.apache.logging.log4j.util.Unbox.box;
030 * ...
031 * long longValue = 123456L;
032 * double doubleValue = 3.14;
033 * // prevent primitive values from being auto-boxed
034 * logger.debug("Long value={}, double value={}", box(longValue), box(doubleValue));
035 * </pre>
036 * <p>
037 * This class manages a small thread-local ring buffer of StringBuilders.
038 * Each time one of the {@code box()} methods is called, the next slot in the ring buffer is used, until the ring
039 * buffer is full and the first slot is reused. By default the Unbox ring buffer has 32 slots, so user code can
040 * have up to 32 boxed primitives in a single logger call.
041 * </p>
042 * <p>
043 * If more slots are required, set system property {@code log4j.unbox.ringbuffer.size} to the desired ring buffer size.
044 * Note that the specified number will be rounded up to the nearest power of 2.
045 * </p>
046 * @since 2.6
047 */
048@PerformanceSensitive("allocation")
049public class Unbox {
050    private static final Logger LOGGER = StatusLogger.getLogger();
051    private static final int BITS_PER_INT = 32;
052    private static final int RINGBUFFER_MIN_SIZE = 32;
053    private static final int RINGBUFFER_SIZE = calculateRingBufferSize("log4j.unbox.ringbuffer.size");
054    private static final int MASK = RINGBUFFER_SIZE - 1;
055
056    /**
057     * State implementation that only puts JDK classes in ThreadLocals, so this is safe to be used from
058     * web applications. Web application containers have thread pools that may hold on to ThreadLocal objects
059     * after the application was stopped. This may prevent the classes of the application from being unloaded,
060     * causing memory leaks.
061     * <p>
062     * Such memory leaks will not occur if only JDK classes are stored in ThreadLocals.
063     * </p>
064     */
065    private static class WebSafeState {
066        private final ThreadLocal<StringBuilder[]> ringBuffer = new ThreadLocal<>();
067        private final ThreadLocal<int[]> current = new ThreadLocal<>();
068
069        public StringBuilder getStringBuilder() {
070            StringBuilder[] array = ringBuffer.get();
071            if (array == null) {
072                array = new StringBuilder[RINGBUFFER_SIZE];
073                for (int i = 0; i < array.length; i++) {
074                    array[i] = new StringBuilder(21);
075                }
076                ringBuffer.set(array);
077                current.set(new int[1]);
078            }
079            final int[] index = current.get();
080            final StringBuilder result = array[MASK & index[0]++];
081            result.setLength(0);
082            return result;
083        }
084
085        public boolean isBoxedPrimitive(final StringBuilder text) {
086            final StringBuilder[] array = ringBuffer.get();
087            if (array == null) {
088                return false;
089            }
090            for (int i = 0; i < array.length; i++) {
091                if (text == array[i]) {
092                    return true;
093                }
094            }
095            return false;
096        }
097    }
098
099    private static class State {
100        private final StringBuilder[] ringBuffer = new StringBuilder[RINGBUFFER_SIZE];
101        private int current;
102        State() {
103            for (int i = 0; i < ringBuffer.length; i++) {
104                ringBuffer[i] = new StringBuilder(21);
105            }
106        }
107
108        public StringBuilder getStringBuilder() {
109            final StringBuilder result = ringBuffer[MASK & current++];
110            result.setLength(0);
111            return result;
112        }
113
114        public boolean isBoxedPrimitive(final StringBuilder text) {
115            for (int i = 0; i < ringBuffer.length; i++) {
116                if (text == ringBuffer[i]) {
117                    return true;
118                }
119            }
120            return false;
121        }
122    }
123    private static ThreadLocal<State> threadLocalState = new ThreadLocal<>();
124    private static WebSafeState webSafeState = new WebSafeState();
125
126    private Unbox() {
127        // this is a utility
128    }
129
130    private static int calculateRingBufferSize(final String propertyName) {
131        final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName,
132                String.valueOf(RINGBUFFER_MIN_SIZE));
133        try {
134            int size = Integer.parseInt(userPreferredRBSize);
135            if (size < RINGBUFFER_MIN_SIZE) {
136                size = RINGBUFFER_MIN_SIZE;
137                LOGGER.warn("Invalid {} {}, using minimum size {}.", propertyName, userPreferredRBSize,
138                        RINGBUFFER_MIN_SIZE);
139            }
140            return ceilingNextPowerOfTwo(size);
141        } catch (final Exception ex) {
142            LOGGER.warn("Invalid {} {}, using default size {}.", propertyName, userPreferredRBSize,
143                    RINGBUFFER_MIN_SIZE);
144            return RINGBUFFER_MIN_SIZE;
145        }
146    }
147
148    /**
149     * Calculate the next power of 2, greater than or equal to x.
150     * <p>
151     * From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
152     *
153     * @param x Value to round up
154     * @return The next power of 2 from x inclusive
155     */
156    private static int ceilingNextPowerOfTwo(final int x) {
157        return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1));
158    }
159
160    /**
161     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
162     * This method will not allocate temporary objects.
163     *
164     * @param value the value whose text representation to return
165     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
166     */
167    @PerformanceSensitive("allocation")
168    public static StringBuilder box(final float value) {
169        return getSB().append(value);
170    }
171
172    /**
173     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
174     * This method will not allocate temporary objects.
175     *
176     * @param value the value whose text representation to return
177     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
178     */
179    @PerformanceSensitive("allocation")
180    public static StringBuilder box(final double value) {
181        return getSB().append(value);
182    }
183
184    /**
185     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
186     * This method will not allocate temporary objects.
187     *
188     * @param value the value whose text representation to return
189     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
190     */
191    @PerformanceSensitive("allocation")
192    public static StringBuilder box(final short value) {
193        return getSB().append(value);
194    }
195
196    /**
197     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
198     * This method will not allocate temporary objects.
199     *
200     * @param value the value whose text representation to return
201     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
202     */
203    @PerformanceSensitive("allocation")
204    public static StringBuilder box(final int value) {
205        return getSB().append(value);
206    }
207
208    /**
209     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
210     * This method will not allocate temporary objects.
211     *
212     * @param value the value whose text representation to return
213     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
214     */
215    @PerformanceSensitive("allocation")
216    public static StringBuilder box(final char value) {
217        return getSB().append(value);
218    }
219
220    /**
221     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
222     * This method will not allocate temporary objects.
223     *
224     * @param value the value whose text representation to return
225     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
226     */
227    @PerformanceSensitive("allocation")
228    public static StringBuilder box(final long value) {
229        return getSB().append(value);
230    }
231
232    /**
233     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
234     * This method will not allocate temporary objects.
235     *
236     * @param value the value whose text representation to return
237     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
238     */
239    @PerformanceSensitive("allocation")
240    public static StringBuilder box(final byte value) {
241        return getSB().append(value);
242    }
243
244    /**
245     * Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
246     * This method will not allocate temporary objects.
247     *
248     * @param value the value whose text representation to return
249     * @return a {@code StringBuilder} containing the text representation of the specified primitive value
250     */
251    @PerformanceSensitive("allocation")
252    public static StringBuilder box(final boolean value) {
253        return getSB().append(value);
254    }
255
256    private static State getState() {
257        State state = threadLocalState.get();
258        if (state == null) {
259            state = new State();
260            threadLocalState.set(state);
261        }
262        return state;
263    }
264
265    private static StringBuilder getSB() {
266        return Constants.ENABLE_THREADLOCALS ? getState().getStringBuilder() : webSafeState.getStringBuilder();
267    }
268
269    /** For testing. */
270    static int getRingbufferSize() {
271        return RINGBUFFER_SIZE;
272    }
273}