1/*******************************************************************************
2 * Copyright (c) 2011 Google, Inc.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Google, Inc. - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.wb.internal.core.utils.reflect;
12
13import org.objectweb.asm.ClassWriter;
14import org.objectweb.asm.FieldVisitor;
15import org.objectweb.asm.MethodVisitor;
16import org.objectweb.asm.Opcodes;
17
18import java.lang.reflect.InvocationTargetException;
19import java.lang.reflect.Method;
20import java.util.Collections;
21import java.util.Map;
22import java.util.WeakHashMap;
23
24/**
25 * Helper for setting properties for {@link ClassLoader}.
26 * <p>
27 * http://java.dzone.com/articles/classloaderlocal-how-avoid
28 *
29 * @author Jevgeni Kabanov
30 * @author scheglov_ke
31 * @coverage core.util
32 */
33@SuppressWarnings("unchecked")
34public class ClassLoaderLocalMap implements Opcodes {
35  private static final String NAME = "GEN$$ClassLoaderProperties";
36  private static final Map<Object, Object> globalMap =
37      Collections.synchronizedMap(new WeakHashMap<Object, Object>());
38  private static Method defineMethod;
39  private static Method findLoadedClass;
40  static {
41    try {
42      defineMethod =
43          ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{
44              String.class,
45              byte[].class,
46              int.class,
47              int.class});
48      defineMethod.setAccessible(true);
49      findLoadedClass =
50          ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[]{String.class});
51      findLoadedClass.setAccessible(true);
52    } catch (NoSuchMethodException e) {
53      throw new RuntimeException(e);
54    }
55  }
56
57  ////////////////////////////////////////////////////////////////////////////
58  //
59  // Map
60  //
61  ////////////////////////////////////////////////////////////////////////////
62  public static boolean containsKey(ClassLoader cl, Object key) {
63    if (cl == null) {
64      return globalMap.containsKey(key);
65    }
66    // synchronizing over ClassLoader is usually safest
67    synchronized (cl) {
68      if (!hasHolder(cl)) {
69        return false;
70      }
71      return getLocalMap(cl).containsKey(key);
72    }
73  }
74
75  public static void put(ClassLoader cl, Object key, Object value) {
76    if (cl == null) {
77      globalMap.put(key, value);
78      return;
79    }
80    // synchronizing over ClassLoader is usually safest
81    synchronized (cl) {
82      getLocalMap(cl).put(key, value);
83    }
84  }
85
86  public static Object get(ClassLoader cl, Object key) {
87    if (cl == null) {
88      return globalMap.get(key);
89    }
90    // synchronizing over ClassLoader is usually safest
91    synchronized (cl) {
92      return getLocalMap(cl).get(key);
93    }
94  }
95
96  ////////////////////////////////////////////////////////////////////////////
97  //
98  // Implementation
99  //
100  ////////////////////////////////////////////////////////////////////////////
101  private static boolean hasHolder(ClassLoader cl) {
102    String propertiesClassName = NAME;
103    try {
104      Class<?> clazz = (Class<?>) findLoadedClass.invoke(cl, new Object[]{propertiesClassName});
105      if (clazz == null) {
106        return false;
107      }
108    } catch (IllegalArgumentException e) {
109      throw new RuntimeException(e);
110    } catch (IllegalAccessException e) {
111      throw new RuntimeException(e);
112    } catch (InvocationTargetException e) {
113      throw new RuntimeException(e.getTargetException());
114    }
115    return true;
116  }
117
118  private static Map<Object, Object> getLocalMap(ClassLoader cl) {
119    String holderClassName = NAME;
120    Class<?> holderClass;
121    try {
122      holderClass = (Class<?>) findLoadedClass.invoke(cl, new Object[]{holderClassName});
123    } catch (IllegalArgumentException e) {
124      throw new RuntimeException(e);
125    } catch (IllegalAccessException e) {
126      throw new RuntimeException(e);
127    } catch (InvocationTargetException e) {
128      throw new RuntimeException(e.getTargetException());
129    }
130    if (holderClass == null) {
131      byte[] classBytes = buildHolderByteCode(holderClassName);
132      try {
133        holderClass =
134            (Class<?>) defineMethod.invoke(
135                cl,
136                new Object[]{
137                    holderClassName,
138                    classBytes,
139                    Integer.valueOf(0),
140                    Integer.valueOf(classBytes.length)});
141      } catch (InvocationTargetException e1) {
142        throw new RuntimeException(e1.getTargetException());
143      } catch (Throwable e1) {
144        throw new RuntimeException(e1);
145      }
146    }
147    try {
148      return (Map<Object, Object>) holderClass.getDeclaredField("localMap").get(null);
149    } catch (Throwable e1) {
150      throw new RuntimeException(e1);
151    }
152  }
153
154  private static byte[] buildHolderByteCode(String holderClassName) {
155    ClassWriter cw = new ClassWriter(0);
156    FieldVisitor fv;
157    MethodVisitor mv;
158    cw.visit(V1_2, ACC_PUBLIC + ACC_SUPER, holderClassName, null, "java/lang/Object", null);
159    {
160      fv =
161          cw.visitField(
162              ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
163              "localMap",
164              "Ljava/util/Map;",
165              null,
166              null);
167      fv.visitEnd();
168    }
169    {
170      mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
171      mv.visitCode();
172      mv.visitTypeInsn(NEW, "java/util/WeakHashMap");
173      mv.visitInsn(DUP);
174      mv.visitMethodInsn(INVOKESPECIAL, "java/util/WeakHashMap", "<init>", "()V");
175      mv.visitFieldInsn(PUTSTATIC, holderClassName, "localMap", "Ljava/util/Map;");
176      mv.visitInsn(RETURN);
177      mv.visitMaxs(2, 0);
178      mv.visitEnd();
179    }
180    {
181      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
182      mv.visitCode();
183      mv.visitVarInsn(ALOAD, 0);
184      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
185      mv.visitInsn(RETURN);
186      mv.visitMaxs(1, 1);
187      mv.visitEnd();
188    }
189    cw.visitEnd();
190    return cw.toByteArray();
191  }
192}
193