blob: 869e85b3ed314d0d8eb70b04ae46ccfef419ea2c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.pattern;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
* NameAbbreviator generates abbreviated logger and class names.
*/
@PerformanceSensitive("allocation")
public abstract class NameAbbreviator {
/**
* Default (no abbreviation) abbreviator.
*/
private static final NameAbbreviator DEFAULT = new NOPAbbreviator();
/**
* Gets an abbreviator.
* <p>
* For example, "%logger{2}" will output only 2 elements of the logger name, "%logger{1.}" will output only the
* first character of the non-final elements in the name, "%logger(1~.2~} will output the first character of the
* first element, two characters of the second and subsequent elements and will use a tilde to indicate abbreviated
* characters.
* </p>
*
* @param pattern
* abbreviation pattern.
* @return abbreviator, will not be null.
*/
public static NameAbbreviator getAbbreviator(final String pattern) {
if (pattern.length() > 0) {
// if pattern is just spaces and numbers then
// use MaxElementAbbreviator
final String trimmed = pattern.trim();
if (trimmed.isEmpty()) {
return DEFAULT;
}
final boolean isNegativeNumber;
final String number;
// check if number is a negative number
if (trimmed.length() > 1 && trimmed.charAt(0) == '-') {
isNegativeNumber = true;
number = trimmed.substring(1);
} else {
isNegativeNumber = false;
number = trimmed;
}
int i = 0;
while (i < number.length() && number.charAt(i) >= '0'
&& number.charAt(i) <= '9') {
i++;
}
//
// if all blanks and digits
//
if (i == number.length()) {
return new MaxElementAbbreviator(Integer.parseInt(number),
isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN);
}
final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5);
char ellipsis;
int charCount;
int pos = 0;
while (pos < trimmed.length() && pos >= 0) {
int ellipsisPos = pos;
if (trimmed.charAt(pos) == '*') {
charCount = Integer.MAX_VALUE;
ellipsisPos++;
} else {
if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
charCount = trimmed.charAt(pos) - '0';
ellipsisPos++;
} else {
charCount = 0;
}
}
ellipsis = '\0';
if (ellipsisPos < trimmed.length()) {
ellipsis = trimmed.charAt(ellipsisPos);
if (ellipsis == '.') {
ellipsis = '\0';
}
}
fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis));
pos = trimmed.indexOf('.', pos);
if (pos == -1) {
break;
}
pos++;
}
return new PatternAbbreviator(fragments);
}
//
// no matching abbreviation, return defaultAbbreviator
//
return DEFAULT;
}
/**
* Gets default abbreviator.
*
* @return default abbreviator.
*/
public static NameAbbreviator getDefaultAbbreviator() {
return DEFAULT;
}
/**
* Abbreviates a name in a String.
*
* @param original the text to abbreviate, may not be null.
* @param destination StringBuilder to write the result to
*/
public abstract void abbreviate(final String original, final StringBuilder destination);
/**
* Abbreviator that simply appends full name to buffer.
*/
private static class NOPAbbreviator extends NameAbbreviator {
/**
* Constructor.
*/
public NOPAbbreviator() {
}
/**
* {@inheritDoc}
*/
@Override
public void abbreviate(final String original, final StringBuilder destination) {
destination.append(original);
}
}
/**
* Abbreviator that drops starting path elements.
*/
private static class MaxElementAbbreviator extends NameAbbreviator {
/**
* <p>When the name is reduced in length by cutting parts, there can be two ways to do it.</p>
* 1. Remove a given number of parts starting from front - called DROP <br/>
* 2. Retain a given number of parts starting from the end - called RETAIN
*/
private enum Strategy {
DROP(0) {
@Override
void abbreviate(final int count, final String original, final StringBuilder destination) {
// If a path does not contain enough path elements to drop, none will be dropped.
int start = 0;
int nextStart;
for (int i = 0; i < count; i++) {
nextStart = original.indexOf('.', start);
if (nextStart == -1) {
destination.append(original);
return;
}
start = nextStart + 1;
}
destination.append(original, start, original.length());
}
},
RETAIN(1) {
@Override
void abbreviate(final int count, final String original, final StringBuilder destination) {
// We subtract 1 from 'len' when assigning to 'end' to avoid out of
// bounds exception in return r.substring(end+1, len). This can happen if
// precision is 1 and the category name ends with a dot.
int end = original.length() - 1;
for (int i = count; i > 0; i--) {
end = original.lastIndexOf('.', end - 1);
if (end == -1) {
destination.append(original);
return;
}
}
destination.append(original, end + 1, original.length());
}
};
final int minCount;
Strategy(final int minCount) {
this.minCount = minCount;
}
abstract void abbreviate(final int count, final String original, final StringBuilder destination);
}
/**
* Maximum number of path elements to output.
*/
private final int count;
/**
* Strategy used for cutting down the size of the name
*/
private final Strategy strategy;
/**
* Create new instance.
*
* @param count maximum number of path elements to drop or output.
* @param strategy drop or retain
*/
public MaxElementAbbreviator(final int count, final Strategy strategy) {
this.count = Math.max(count, strategy.minCount);
this.strategy = strategy;
}
/**
* Abbreviate name.
*
* @param original The String to abbreviate.
* @param destination the buffer to write the abbreviated name into
*/
@Override
public void abbreviate(final String original, final StringBuilder destination) {
strategy.abbreviate(count, original, destination);
}
}
/**
* Fragment of an pattern abbreviator.
*/
private static class PatternAbbreviatorFragment {
/**
* Count of initial characters of element to output.
*/
private final int charCount;
/**
* Character used to represent dropped characters.
* '\0' indicates no representation of dropped characters.
*/
private final char ellipsis;
/**
* Creates a PatternAbbreviatorFragment.
*
* @param charCount number of initial characters to preserve.
* @param ellipsis character to represent elimination of characters,
* '\0' if no ellipsis is desired.
*/
public PatternAbbreviatorFragment(
final int charCount, final char ellipsis) {
this.charCount = charCount;
this.ellipsis = ellipsis;
}
/**
* Abbreviate element of name.
*
* @param buf buffer to receive element.
* @param startPos starting index of name element.
* @return starting index of next element.
*/
public int abbreviate(final StringBuilder buf, final int startPos) {
final int start = (startPos < 0) ? 0 : startPos;
final int max = buf.length();
int nextDot = -1;
for (int i = start; i < max; i++) {
if (buf.charAt(i) == '.') {
nextDot = i;
break;
}
}
if (nextDot != -1) {
if (nextDot - startPos > charCount) {
buf.delete(startPos + charCount, nextDot);
nextDot = startPos + charCount;
if (ellipsis != '\0') {
buf.insert(nextDot, ellipsis);
nextDot++;
}
}
nextDot++;
}
return nextDot;
}
}
/**
* Pattern abbreviator.
*/
private static class PatternAbbreviator extends NameAbbreviator {
/**
* Element abbreviation patterns.
*/
private final PatternAbbreviatorFragment[] fragments;
/**
* Create PatternAbbreviator.
*
* @param fragments element abbreviation patterns.
*/
public PatternAbbreviator(final List<PatternAbbreviatorFragment> fragments) {
if (fragments.isEmpty()) {
throw new IllegalArgumentException(
"fragments must have at least one element");
}
this.fragments = new PatternAbbreviatorFragment[fragments.size()];
fragments.toArray(this.fragments);
}
/**
* Abbreviates name.
*
* @param original the original string to abbreviate
* @param destination buffer that abbreviated name is appended to
*/
@Override
public void abbreviate(final String original, final StringBuilder destination) {
//
// all non-terminal patterns are executed once
//
int pos = destination.length();
final int max = pos + original.length();
destination.append(original);
int fragmentIndex = 0;
while (pos < max && pos >= 0) {
pos = fragments[fragmentIndex].abbreviate(destination, pos);
// last pattern in executed repeatedly
if (fragmentIndex < fragments.length - 1) {
fragmentIndex++;
}
}
}
}
}