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 */
017
018package org.apache.log4j.pattern;
019
020import java.util.ArrayList;
021import java.util.List;
022
023
024/**
025 * NameAbbreviator generates abbreviated logger and class names.
026 *
027 */
028public abstract class NameAbbreviator {
029  /**
030   * Default (no abbreviation) abbreviator.
031   */
032  private static final NameAbbreviator DEFAULT = new NOPAbbreviator();
033
034  /**
035   * Gets an abbreviator.
036   *
037   * For example, "%logger{2}" will output only 2 elements of the logger name,
038   * %logger{-2} will drop 2 elements from the logger name,
039   * "%logger{1.}" will output only the first character of the non-final elements in the name,
040   * "%logger{1~.2~} will output the first character of the first element, two characters of
041   * the second and subsequent elements and will use a tilde to indicate abbreviated characters.
042   *
043   * @param pattern abbreviation pattern.
044   * @return abbreviator, will not be null.
045   */
046  public static NameAbbreviator getAbbreviator(final String pattern) {
047    if (pattern.length() > 0) {
048      //  if pattern is just spaces and numbers then
049      //     use MaxElementAbbreviator
050      String trimmed = pattern.trim();
051
052      if (trimmed.length() == 0) {
053        return DEFAULT;
054      }
055
056      int i = 0;
057      if (trimmed.length() > 0) {
058          if (trimmed.charAt(0) == '-') {
059              i++;
060          }
061          for (;
062                (i < trimmed.length()) &&
063                  (trimmed.charAt(i) >= '0') &&
064                  (trimmed.charAt(i) <= '9');
065               i++) {
066          }
067      }
068
069
070      //
071      //  if all blanks and digits
072      //
073      if (i == trimmed.length()) {
074        int elements = Integer.parseInt(trimmed);
075        if (elements >= 0) {
076            return new MaxElementAbbreviator(elements);
077        } else {
078            return new DropElementAbbreviator(-elements);
079        }
080      }
081
082      ArrayList fragments = new ArrayList(5);
083      char ellipsis;
084      int charCount;
085      int pos = 0;
086
087      while ((pos < trimmed.length()) && (pos >= 0)) {
088        int ellipsisPos = pos;
089
090        if (trimmed.charAt(pos) == '*') {
091          charCount = Integer.MAX_VALUE;
092          ellipsisPos++;
093        } else {
094          if ((trimmed.charAt(pos) >= '0') && (trimmed.charAt(pos) <= '9')) {
095            charCount = trimmed.charAt(pos) - '0';
096            ellipsisPos++;
097          } else {
098            charCount = 0;
099          }
100        }
101
102        ellipsis = '\0';
103
104        if (ellipsisPos < trimmed.length()) {
105          ellipsis = trimmed.charAt(ellipsisPos);
106
107          if (ellipsis == '.') {
108            ellipsis = '\0';
109          }
110        }
111
112        fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis));
113        pos = trimmed.indexOf(".", pos);
114
115        if (pos == -1) {
116          break;
117        }
118
119        pos++;
120      }
121
122      return new PatternAbbreviator(fragments);
123    }
124
125    //
126    //  no matching abbreviation, return defaultAbbreviator
127    //
128    return DEFAULT;
129  }
130
131  /**
132   * Gets default abbreviator.
133   *
134   * @return default abbreviator.
135   */
136  public static NameAbbreviator getDefaultAbbreviator() {
137    return DEFAULT;
138  }
139
140  /**
141   * Abbreviates a name in a StringBuffer.
142   *
143   * @param nameStart starting position of name in buf.
144   * @param buf buffer, may not be null.
145   */
146  public abstract void abbreviate(final int nameStart, final StringBuffer buf);
147
148  /**
149   * Abbreviator that simply appends full name to buffer.
150   */
151  private static class NOPAbbreviator extends NameAbbreviator {
152    /**
153     * Constructor.
154     */
155    public NOPAbbreviator() {
156    }
157
158    /**
159     * {@inheritDoc}
160     */
161    public void abbreviate(final int nameStart, final StringBuffer buf) {
162    }
163  }
164
165  /**
166   * Abbreviator that drops starting path elements.
167   */
168  private static class MaxElementAbbreviator extends NameAbbreviator {
169    /**
170     * Maximum number of path elements to output.
171     */
172    private final int count;
173
174    /**
175     * Create new instance.
176     * @param count maximum number of path elements to output.
177     */
178    public MaxElementAbbreviator(final int count) {
179      this.count = count;
180    }
181
182    /**
183     * Abbreviate name.
184     * @param buf buffer to append abbreviation.
185     * @param nameStart start of name to abbreviate.
186     */
187    public void abbreviate(final int nameStart, final StringBuffer buf) {
188      // We substract 1 from 'len' when assigning to 'end' to avoid out of
189      // bounds exception in return r.substring(end+1, len). This can happen if
190      // precision is 1 and the category name ends with a dot.
191      int end = buf.length() - 1;
192
193      String bufString = buf.toString();
194      for (int i = count; i > 0; i--) {
195        end = bufString.lastIndexOf(".", end - 1);
196
197        if ((end == -1) || (end < nameStart)) {
198          return;
199        }
200      }
201
202      buf.delete(nameStart, end + 1);
203    }
204  }
205
206  /**
207   * Abbreviator that drops starting path elements.
208   */
209  private static class DropElementAbbreviator extends NameAbbreviator {
210    /**
211     * Maximum number of path elements to output.
212     */
213    private final int count;
214
215    /**
216     * Create new instance.
217     * @param count maximum number of path elements to output.
218     */
219    public DropElementAbbreviator(final int count) {
220      this.count = count;
221    }
222
223    /**
224     * Abbreviate name.
225     * @param buf buffer to append abbreviation.
226     * @param nameStart start of name to abbreviate.
227     */
228    public void abbreviate(final int nameStart, final StringBuffer buf) {
229      int i = count;
230      for(int pos = buf.indexOf(".", nameStart);
231        pos != -1;
232        pos = buf.indexOf(".", pos + 1)) {
233          if(--i == 0) {
234              buf.delete(nameStart, pos + 1);
235              break;
236          }
237      }
238    }
239  }
240
241
242  /**
243   * Fragment of an pattern abbreviator.
244   *
245   */
246  private static class PatternAbbreviatorFragment {
247    /**
248     * Count of initial characters of element to output.
249     */
250    private final int charCount;
251
252    /**
253     *  Character used to represent dropped characters.
254     * '\0' indicates no representation of dropped characters.
255     */
256    private final char ellipsis;
257
258    /**
259     * Creates a PatternAbbreviatorFragment.
260     * @param charCount number of initial characters to preserve.
261     * @param ellipsis character to represent elimination of characters,
262     *    '\0' if no ellipsis is desired.
263     */
264    public PatternAbbreviatorFragment(
265      final int charCount, final char ellipsis) {
266      this.charCount = charCount;
267      this.ellipsis = ellipsis;
268    }
269
270    /**
271     * Abbreviate element of name.
272     * @param buf buffer to receive element.
273     * @param startPos starting index of name element.
274     * @return starting index of next element.
275     */
276    public int abbreviate(final StringBuffer buf, final int startPos) {
277      int nextDot = buf.toString().indexOf(".", startPos);
278
279      if (nextDot != -1) {
280        if ((nextDot - startPos) > charCount) {
281          buf.delete(startPos + charCount, nextDot);
282          nextDot = startPos + charCount;
283
284          if (ellipsis != '\0') {
285            buf.insert(nextDot, ellipsis);
286            nextDot++;
287          }
288        }
289
290        nextDot++;
291      }
292
293      return nextDot;
294    }
295  }
296
297  /**
298   * Pattern abbreviator.
299   *
300   *
301   */
302  private static class PatternAbbreviator extends NameAbbreviator {
303    /**
304     * Element abbreviation patterns.
305     */
306    private final PatternAbbreviatorFragment[] fragments;
307
308    /**
309     * Create PatternAbbreviator.
310     *
311     * @param fragments element abbreviation patterns.
312     */
313    public PatternAbbreviator(List fragments) {
314      if (fragments.size() == 0) {
315        throw new IllegalArgumentException(
316          "fragments must have at least one element");
317      }
318
319      this.fragments = new PatternAbbreviatorFragment[fragments.size()];
320      fragments.toArray(this.fragments);
321    }
322
323    /**
324     * Abbreviate name.
325     * @param buf buffer that abbreviated name is appended.
326     * @param nameStart start of name.
327     */
328    public void abbreviate(final int nameStart, final StringBuffer buf) {
329      //
330      //  all non-terminal patterns are executed once
331      //
332      int pos = nameStart;
333
334      for (int i = 0; (i < (fragments.length - 1)) && (pos < buf.length());
335          i++) {
336        pos = fragments[i].abbreviate(buf, pos);
337      }
338
339      //
340      //   last pattern in executed repeatedly
341      //
342      PatternAbbreviatorFragment terminalFragment =
343        fragments[fragments.length - 1];
344
345      while ((pos < buf.length()) && (pos >= 0)) {
346        pos = terminalFragment.abbreviate(buf, pos);
347      }
348    }
349  }
350}