001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.spi;
015
016import java.net.URL;
017import java.security.CodeSource;
018import java.util.HashMap;
019
020//import sun.reflect.Reflection;
021
022// import java.security.AccessControlException; import java.security.AccessController;import java.security.PrivilegedAction;
023/**
024 * Given a classname locate associated PackageInfo (jar name, version name).
025 *
026 * @author James Strachan
027 * @Ceki Gülcü
028 */
029public class PackagingDataCalculator {
030
031    final static StackTraceElementProxy[] STEP_ARRAY_TEMPLATE = new StackTraceElementProxy[0];
032
033    HashMap<String, ClassPackagingData> cache = new HashMap<String, ClassPackagingData>();
034
035    private static boolean GET_CALLER_CLASS_METHOD_AVAILABLE = false; // private static boolean
036                                                                      // HAS_GET_CLASS_LOADER_PERMISSION = false;
037
038    static {
039        // if either the Reflection class or the getCallerClass method
040        // are unavailable, then we won't invoke Reflection.getCallerClass()
041        // This approach ensures that this class will *run* on JDK's lacking
042        // sun.reflect.Reflection class. However, this class will *not compile*
043        // on JDKs lacking sun.reflect.Reflection.
044        try {
045            // Reflection.getCallerClass(2);
046            // GET_CALLER_CLASS_METHOD_AVAILABLE = true;
047        } catch (NoClassDefFoundError e) {
048        } catch (NoSuchMethodError e) {
049        } catch (UnsupportedOperationException e) {
050        } catch (Throwable e) {
051            System.err.println("Unexpected exception");
052            e.printStackTrace();
053        }
054    }
055
056    public void calculate(IThrowableProxy tp) {
057        while (tp != null) {
058            populateFrames(tp.getStackTraceElementProxyArray());
059            IThrowableProxy[] suppressed = tp.getSuppressed();
060            if (suppressed != null) {
061                for (IThrowableProxy current : suppressed) {
062                    populateFrames(current.getStackTraceElementProxyArray());
063                }
064            }
065            tp = tp.getCause();
066        }
067    }
068
069    @SuppressWarnings("unused")
070    void populateFrames(StackTraceElementProxy[] stepArray) {
071        // in the initial part of this method we populate package information for
072        // common stack frames
073        final Throwable t = new Throwable("local stack reference");
074        final StackTraceElement[] localSTEArray = t.getStackTrace();
075        final int commonFrames = STEUtil.findNumberOfCommonFrames(localSTEArray, stepArray);
076        final int localFirstCommon = localSTEArray.length - commonFrames;
077        final int stepFirstCommon = stepArray.length - commonFrames;
078
079        ClassLoader lastExactClassLoader = null;
080        ClassLoader firsExactClassLoader = null;
081
082        int missfireCount = 0;
083        for (int i = 0; i < commonFrames; i++) {
084            Class<?> callerClass = null;
085            if (GET_CALLER_CLASS_METHOD_AVAILABLE) {
086                // callerClass = Reflection.getCallerClass(localFirstCommon + i - missfireCount
087                // + 1);
088            }
089            StackTraceElementProxy step = stepArray[stepFirstCommon + i];
090            String stepClassname = step.ste.getClassName();
091
092            if (callerClass != null && stepClassname.equals(callerClass.getName())) {
093                // see also LBCLASSIC-263
094                lastExactClassLoader = callerClass.getClassLoader();
095                if (firsExactClassLoader == null) {
096                    firsExactClassLoader = lastExactClassLoader;
097                }
098                ClassPackagingData pi = calculateByExactType(callerClass);
099                step.setClassPackagingData(pi);
100            } else {
101                missfireCount++;
102                ClassPackagingData pi = computeBySTEP(step, lastExactClassLoader);
103                step.setClassPackagingData(pi);
104            }
105        }
106        populateUncommonFrames(commonFrames, stepArray, firsExactClassLoader);
107    }
108
109    void populateUncommonFrames(int commonFrames, StackTraceElementProxy[] stepArray,
110            ClassLoader firstExactClassLoader) {
111        int uncommonFrames = stepArray.length - commonFrames;
112        for (int i = 0; i < uncommonFrames; i++) {
113            StackTraceElementProxy step = stepArray[i];
114            ClassPackagingData pi = computeBySTEP(step, firstExactClassLoader);
115            step.setClassPackagingData(pi);
116        }
117    }
118
119    private ClassPackagingData calculateByExactType(Class<?> type) {
120        String className = type.getName();
121        ClassPackagingData cpd = cache.get(className);
122        if (cpd != null) {
123            return cpd;
124        }
125        String version = getImplementationVersion(type);
126        String codeLocation = getCodeLocation(type);
127        cpd = new ClassPackagingData(codeLocation, version);
128        cache.put(className, cpd);
129        return cpd;
130    }
131
132    private ClassPackagingData computeBySTEP(StackTraceElementProxy step, ClassLoader lastExactClassLoader) {
133        String className = step.ste.getClassName();
134        ClassPackagingData cpd = cache.get(className);
135        if (cpd != null) {
136            return cpd;
137        }
138        Class<?> type = bestEffortLoadClass(lastExactClassLoader, className);
139        String version = getImplementationVersion(type);
140        String codeLocation = getCodeLocation(type);
141        cpd = new ClassPackagingData(codeLocation, version, false);
142        cache.put(className, cpd);
143        return cpd;
144    }
145
146    String getImplementationVersion(Class<?> type) {
147        if (type == null) {
148            return "na";
149        }
150        Package aPackage = type.getPackage();
151        if (aPackage != null) {
152            String v = aPackage.getImplementationVersion();
153            if (v == null) {
154                return "na";
155            } else {
156                return v;
157            }
158        }
159        return "na";
160
161    }
162
163    String getCodeLocation(Class<?> type) {
164        try {
165            if (type != null) {
166                // file:/C:/java/maven-2.0.8/repo/com/icegreen/greenmail/1.3/greenmail-1.3.jar
167                CodeSource codeSource = type.getProtectionDomain().getCodeSource();
168                if (codeSource != null) {
169                    URL resource = codeSource.getLocation();
170                    if (resource != null) {
171                        String locationStr = resource.toString();
172                        // now lets remove all but the file name
173                        String result = getCodeLocation(locationStr, '/');
174                        if (result != null) {
175                            return result;
176                        }
177                        return getCodeLocation(locationStr, '\\');
178                    }
179                }
180            }
181        } catch (Exception e) {
182            // ignore
183        }
184        return "na";
185    }
186
187    private String getCodeLocation(String locationStr, char separator) {
188        int idx = locationStr.lastIndexOf(separator);
189        if (isFolder(idx, locationStr)) {
190            idx = locationStr.lastIndexOf(separator, idx - 1);
191            return locationStr.substring(idx + 1);
192        } else if (idx > 0) {
193            return locationStr.substring(idx + 1);
194        }
195        return null;
196    }
197
198    private boolean isFolder(int idx, String text) {
199        return (idx != -1 && idx + 1 == text.length());
200    }
201
202    private Class<?> loadClass(ClassLoader cl, String className) {
203        if (cl == null) {
204            return null;
205        }
206        try {
207            return cl.loadClass(className);
208        } catch (ClassNotFoundException e1) {
209            return null;
210        } catch (NoClassDefFoundError e1) {
211            return null;
212        } catch (Exception e) {
213            e.printStackTrace(); // this is unexpected
214            return null;
215        }
216
217    }
218
219    /**
220     * @param lastGuaranteedClassLoader may be null
221     * @param className
222     * @return
223     */
224    private Class<?> bestEffortLoadClass(ClassLoader lastGuaranteedClassLoader, String className) {
225        Class<?> result = loadClass(lastGuaranteedClassLoader, className);
226        if (result != null) {
227            return result;
228        }
229        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
230        if (tccl != lastGuaranteedClassLoader) {
231            result = loadClass(tccl, className);
232        }
233        if (result != null) {
234            return result;
235        }
236
237        try {
238            return Class.forName(className);
239        } catch (ClassNotFoundException e1) {
240            return null;
241        } catch (NoClassDefFoundError e1) {
242            return null;
243        } catch (Exception e) {
244            e.printStackTrace(); // this is unexpected
245            return null;
246        }
247    }
248
249}