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 
17 package com.google.inject.internal.util;
18 
19 import com.google.common.cache.CacheBuilder;
20 import com.google.common.cache.CacheLoader;
21 import com.google.common.cache.LoadingCache;
22 import java.io.IOException;
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.Member;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.ConcurrentMap;
27 
28 /**
29  * Creates stack trace elements for members.
30  *
31  * @author crazybob@google.com (Bob Lee)
32  */
33 public class StackTraceElements {
34 
35   private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
36   private static final InMemoryStackTraceElement[] EMPTY_INMEMORY_STACK_TRACE =
37       new InMemoryStackTraceElement[0];
38 
39   /*if[AOP]*/
40   static final LoadingCache<Class<?>, LineNumbers> lineNumbersCache =
41       CacheBuilder.newBuilder()
42           .weakKeys()
43           .softValues()
44           .build(
45               new CacheLoader<Class<?>, LineNumbers>() {
46                 @Override
47                 public LineNumbers load(Class<?> key) {
48                   try {
49                     return new LineNumbers(key);
50                   } catch (IOException e) {
51                     throw new RuntimeException(e);
52                   }
53                 }
54               });
55   /*end[AOP]*/
56 
57   private static final ConcurrentMap<InMemoryStackTraceElement, InMemoryStackTraceElement>
58       elementCache = new ConcurrentHashMap<>();
59   private static final ConcurrentMap<String, String> stringCache = new ConcurrentHashMap<>();
60 
61   private static final String UNKNOWN_SOURCE = "Unknown Source";
62 
forMember(Member member)63   public static Object forMember(Member member) {
64     if (member == null) {
65       return SourceProvider.UNKNOWN_SOURCE;
66     }
67 
68     Class declaringClass = member.getDeclaringClass();
69 
70     /*if[AOP]*/
71     LineNumbers lineNumbers = lineNumbersCache.getUnchecked(declaringClass);
72     String fileName = lineNumbers.getSource();
73     Integer lineNumberOrNull = lineNumbers.getLineNumber(member);
74     int lineNumber = lineNumberOrNull == null ? lineNumbers.getFirstLine() : lineNumberOrNull;
75     /*end[AOP]*/
76     /*if[NO_AOP]
77     String fileName = null;
78     int lineNumber = -1;
79     end[NO_AOP]*/
80 
81     Class<? extends Member> memberType = Classes.memberType(member);
82     String memberName = memberType == Constructor.class ? "<init>" : member.getName();
83     return new StackTraceElement(declaringClass.getName(), memberName, fileName, lineNumber);
84   }
85 
forType(Class<?> implementation)86   public static Object forType(Class<?> implementation) {
87     /*if[AOP]*/
88     LineNumbers lineNumbers = lineNumbersCache.getUnchecked(implementation);
89     int lineNumber = lineNumbers.getFirstLine();
90     String fileName = lineNumbers.getSource();
91     /*end[AOP]*/
92     /*if[NO_AOP]
93     String fileName = null;
94     int lineNumber = -1;
95     end[NO_AOP]*/
96 
97     return new StackTraceElement(implementation.getName(), "class", fileName, lineNumber);
98   }
99 
100   /** Clears the internal cache for {@link StackTraceElement StackTraceElements}. */
clearCache()101   public static void clearCache() {
102     elementCache.clear();
103     stringCache.clear();
104   }
105 
106   /** Returns encoded in-memory version of {@link StackTraceElement StackTraceElements}. */
convertToInMemoryStackTraceElement( StackTraceElement[] stackTraceElements)107   public static InMemoryStackTraceElement[] convertToInMemoryStackTraceElement(
108       StackTraceElement[] stackTraceElements) {
109     if (stackTraceElements.length == 0) {
110       return EMPTY_INMEMORY_STACK_TRACE;
111     }
112     InMemoryStackTraceElement[] inMemoryStackTraceElements =
113         new InMemoryStackTraceElement[stackTraceElements.length];
114     for (int i = 0; i < stackTraceElements.length; i++) {
115       inMemoryStackTraceElements[i] =
116           weakIntern(new InMemoryStackTraceElement(stackTraceElements[i]));
117     }
118     return inMemoryStackTraceElements;
119   }
120 
121   /**
122    * Decodes in-memory stack trace elements to regular {@link StackTraceElement StackTraceElements}.
123    */
convertToStackTraceElement( InMemoryStackTraceElement[] inMemoryStackTraceElements)124   public static StackTraceElement[] convertToStackTraceElement(
125       InMemoryStackTraceElement[] inMemoryStackTraceElements) {
126     if (inMemoryStackTraceElements.length == 0) {
127       return EMPTY_STACK_TRACE;
128     }
129     StackTraceElement[] stackTraceElements =
130         new StackTraceElement[inMemoryStackTraceElements.length];
131     for (int i = 0; i < inMemoryStackTraceElements.length; i++) {
132       String declaringClass = inMemoryStackTraceElements[i].getClassName();
133       String methodName = inMemoryStackTraceElements[i].getMethodName();
134       int lineNumber = inMemoryStackTraceElements[i].getLineNumber();
135       stackTraceElements[i] =
136           new StackTraceElement(declaringClass, methodName, UNKNOWN_SOURCE, lineNumber);
137     }
138     return stackTraceElements;
139   }
140 
weakIntern( InMemoryStackTraceElement inMemoryStackTraceElement)141   private static InMemoryStackTraceElement weakIntern(
142       InMemoryStackTraceElement inMemoryStackTraceElement) {
143     InMemoryStackTraceElement cached = elementCache.get(inMemoryStackTraceElement);
144     if (cached != null) {
145       return cached;
146     }
147     inMemoryStackTraceElement =
148         new InMemoryStackTraceElement(
149             weakIntern(inMemoryStackTraceElement.getClassName()),
150             weakIntern(inMemoryStackTraceElement.getMethodName()),
151             inMemoryStackTraceElement.getLineNumber());
152     elementCache.put(inMemoryStackTraceElement, inMemoryStackTraceElement);
153     return inMemoryStackTraceElement;
154   }
155 
weakIntern(String s)156   private static String weakIntern(String s) {
157     String cached = stringCache.get(s);
158     if (cached != null) {
159       return cached;
160     }
161     stringCache.put(s, s);
162     return s;
163   }
164 
165   /** In-Memory version of {@link StackTraceElement} that does not store the file name. */
166   public static class InMemoryStackTraceElement {
167     private String declaringClass;
168     private String methodName;
169     private int lineNumber;
170 
InMemoryStackTraceElement(StackTraceElement ste)171     InMemoryStackTraceElement(StackTraceElement ste) {
172       this(ste.getClassName(), ste.getMethodName(), ste.getLineNumber());
173     }
174 
InMemoryStackTraceElement(String declaringClass, String methodName, int lineNumber)175     InMemoryStackTraceElement(String declaringClass, String methodName, int lineNumber) {
176       this.declaringClass = declaringClass;
177       this.methodName = methodName;
178       this.lineNumber = lineNumber;
179     }
180 
getClassName()181     String getClassName() {
182       return declaringClass;
183     }
184 
getMethodName()185     String getMethodName() {
186       return methodName;
187     }
188 
getLineNumber()189     int getLineNumber() {
190       return lineNumber;
191     }
192 
193     @Override
equals(Object obj)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)
203           && e.lineNumber == lineNumber
204           && methodName.equals(e.methodName);
205     }
206 
207     @Override
hashCode()208     public int hashCode() {
209       int result = 31 * declaringClass.hashCode() + methodName.hashCode();
210       result = 31 * result + lineNumber;
211       return result;
212     }
213 
214     @Override
toString()215     public String toString() {
216       return declaringClass + "." + methodName + "(" + lineNumber + ")";
217     }
218   }
219 }
220