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