1/**
2 * Copyright (C) 2006 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.inject.internal.util;
18
19import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.CacheLoader;
21import com.google.common.cache.LoadingCache;
22import com.google.common.collect.MapMaker;
23
24import java.io.IOException;
25import java.lang.reflect.Constructor;
26import java.lang.reflect.Member;
27import java.util.Map;
28
29/**
30 * Creates stack trace elements for members.
31 *
32 * @author crazybob@google.com (Bob Lee)
33 */
34public class StackTraceElements {
35
36  private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
37  private static final InMemoryStackTraceElement[] EMPTY_INMEMORY_STACK_TRACE =
38      new InMemoryStackTraceElement[0];
39
40  /*if[AOP]*/
41  static final LoadingCache<Class<?>, LineNumbers> lineNumbersCache =
42      CacheBuilder.newBuilder().weakKeys().softValues().build(
43          new CacheLoader<Class<?>, LineNumbers>() {
44            public LineNumbers load(Class<?> key) {
45              try {
46                return new LineNumbers(key);
47              }
48              catch (IOException e) {
49                throw new RuntimeException(e);
50              }
51            }
52          });
53  /*end[AOP]*/
54
55  private static Map<Object, Object> cache = new MapMaker().makeMap();
56  private static final String UNKNOWN_SOURCE = "Unknown Source";
57
58  public static Object forMember(Member member) {
59    if (member == null) {
60      return SourceProvider.UNKNOWN_SOURCE;
61    }
62
63    Class declaringClass = member.getDeclaringClass();
64
65    /*if[AOP]*/
66    LineNumbers lineNumbers = lineNumbersCache.getUnchecked(declaringClass);
67    String fileName = lineNumbers.getSource();
68    Integer lineNumberOrNull = lineNumbers.getLineNumber(member);
69    int lineNumber = lineNumberOrNull == null ? lineNumbers.getFirstLine() : lineNumberOrNull;
70    /*end[AOP]*/
71    /*if[NO_AOP]
72    String fileName = null;
73    int lineNumber = -1;
74    end[NO_AOP]*/
75
76    Class<? extends Member> memberType = Classes.memberType(member);
77    String memberName = memberType == Constructor.class ? "<init>" : member.getName();
78    return new StackTraceElement(declaringClass.getName(), memberName, fileName, lineNumber);
79  }
80
81  public static Object forType(Class<?> implementation) {
82    /*if[AOP]*/
83    LineNumbers lineNumbers = lineNumbersCache.getUnchecked(implementation);
84    int lineNumber = lineNumbers.getFirstLine();
85    String fileName = lineNumbers.getSource();
86    /*end[AOP]*/
87    /*if[NO_AOP]
88    String fileName = null;
89    int lineNumber = -1;
90    end[NO_AOP]*/
91
92    return new StackTraceElement(implementation.getName(), "class", fileName, lineNumber);
93  }
94
95  /**
96   * Clears the internal cache for {@link StackTraceElement StackTraceElements}.
97   */
98  public static void clearCache() {
99    cache.clear();
100  }
101
102  /**
103   * Returns encoded in-memory version of {@link StackTraceElement StackTraceElements}.
104   */
105  public static InMemoryStackTraceElement[] convertToInMemoryStackTraceElement(
106      StackTraceElement[] stackTraceElements) {
107    if (stackTraceElements.length == 0) {
108      return EMPTY_INMEMORY_STACK_TRACE;
109    }
110    InMemoryStackTraceElement[] inMemoryStackTraceElements =
111        new InMemoryStackTraceElement[stackTraceElements.length];
112    for (int i = 0; i < stackTraceElements.length; i++) {
113      inMemoryStackTraceElements[i] =
114          weakIntern(new InMemoryStackTraceElement(stackTraceElements[i]));
115    }
116    return inMemoryStackTraceElements;
117  }
118
119  /**
120   * Decodes in-memory stack trace elements to regular {@link StackTraceElement StackTraceElements}.
121   */
122  public static StackTraceElement[] convertToStackTraceElement(
123      InMemoryStackTraceElement[] inMemoryStackTraceElements) {
124    if (inMemoryStackTraceElements.length == 0) {
125      return EMPTY_STACK_TRACE;
126    }
127    StackTraceElement[] stackTraceElements =
128        new StackTraceElement[inMemoryStackTraceElements.length];
129    for (int i = 0; i < inMemoryStackTraceElements.length; i++) {
130      String declaringClass = inMemoryStackTraceElements[i].getClassName();
131      String methodName = inMemoryStackTraceElements[i].getMethodName();
132      int lineNumber = inMemoryStackTraceElements[i].getLineNumber();
133      stackTraceElements[i] =
134          new StackTraceElement(declaringClass, methodName, UNKNOWN_SOURCE, lineNumber);
135    }
136    return stackTraceElements;
137  }
138
139  private static InMemoryStackTraceElement weakIntern(
140      InMemoryStackTraceElement inMemoryStackTraceElement) {
141    InMemoryStackTraceElement cached =
142        (InMemoryStackTraceElement) cache.get(inMemoryStackTraceElement);
143    if (cached != null) {
144      return cached;
145    }
146    inMemoryStackTraceElement = new InMemoryStackTraceElement(
147        weakIntern(inMemoryStackTraceElement.getClassName()),
148        weakIntern(inMemoryStackTraceElement.getMethodName()),
149        inMemoryStackTraceElement.getLineNumber());
150    cache.put(inMemoryStackTraceElement, inMemoryStackTraceElement);
151    return inMemoryStackTraceElement;
152  }
153
154  private static String weakIntern(String s) {
155    String cached = (String) cache.get(s);
156    if (cached != null) {
157      return cached;
158    }
159    cache.put(s, s);
160    return s;
161  }
162
163  /**
164   * In-Memory version of {@link StackTraceElement} that does not store the file name.
165   */
166  public static class InMemoryStackTraceElement {
167    private String declaringClass;
168    private String methodName;
169    private int lineNumber;
170
171    InMemoryStackTraceElement(StackTraceElement ste) {
172      this(ste.getClassName(), ste.getMethodName(), ste.getLineNumber());
173    }
174
175    InMemoryStackTraceElement(String declaringClass, String methodName, int lineNumber) {
176      this.declaringClass = declaringClass;
177      this.methodName = methodName;
178      this.lineNumber = lineNumber;
179    }
180
181    String getClassName() {
182      return declaringClass;
183    }
184
185    String getMethodName() {
186      return methodName;
187    }
188
189    int getLineNumber() {
190      return lineNumber;
191    }
192
193    @Override
194    public boolean equals(Object obj) {
195      if (obj == this) {
196        return true;
197      }
198      if (!(obj instanceof InMemoryStackTraceElement)) {
199        return false;
200      }
201      InMemoryStackTraceElement e = (InMemoryStackTraceElement) obj;
202      return e.declaringClass.equals(declaringClass) && e.lineNumber == lineNumber &&
203          methodName.equals(e.methodName);
204    }
205
206    @Override
207    public int hashCode() {
208      int result = 31 * declaringClass.hashCode() + methodName.hashCode();
209      result = 31 * result + lineNumber;
210      return result;
211    }
212
213    @Override
214    public String toString() {
215      return declaringClass + "." + methodName + "(" + lineNumber + ")";
216    }
217  }
218}
219