1 /*
2 * Copyright 2014 - 2020 Rafael Winterhalter
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 package net.bytebuddy.implementation;
17
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import net.bytebuddy.build.HashCodeAndEqualsPlugin;
20 import net.bytebuddy.description.type.TypeDescription;
21 import net.bytebuddy.utility.JavaModule;
22 import net.bytebuddy.utility.privilege.SetAccessibleAction;
23
24 import java.io.Serializable;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.Modifier;
27 import java.security.AccessController;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31
32 /**
33 * Implementations of this interface explicitly initialize a loaded type. Usually, such implementations inject runtime
34 * context into an instrumented type which cannot be defined by the means of the Java class file format.
35 */
36 public interface LoadedTypeInitializer {
37
38 /**
39 * Callback that is invoked on the creation of an instrumented type. If the loaded type initializer is alive, this
40 * method should be implemented empty instead of throwing an exception.
41 *
42 * @param type The manifestation of the instrumented type.
43 */
44 void onLoad(Class<?> type);
45
46 /**
47 * Indicates if this initializer is alive and needs to be invoked. This is only meant as a mark. A loaded type
48 * initializer that is not alive might still be called and must therefore not throw an exception but rather
49 * provide an empty implementation.
50 *
51 * @return {@code true} if this initializer is alive.
52 */
53 boolean isAlive();
54
55 /**
56 * A loaded type initializer that does not do anything.
57 */
58 enum NoOp implements LoadedTypeInitializer {
59
60 /**
61 * The singleton instance.
62 */
63 INSTANCE;
64
65 /**
66 * {@inheritDoc}
67 */
68 public void onLoad(Class<?> type) {
69 /* do nothing */
70 }
71
72 /**
73 * {@inheritDoc}
74 */
75 public boolean isAlive() {
76 return false;
77 }
78 }
79
80 /**
81 * A type initializer for setting a value for a static field.
82 */
83 @HashCodeAndEqualsPlugin.Enhance
84 class ForStaticField implements LoadedTypeInitializer, Serializable {
85
86 /**
87 * This class's serial version UID.
88 */
89 private static final long serialVersionUID = 1L;
90
91 /**
92 * A value for accessing a static field.
93 */
94 private static final Object STATIC_FIELD = null;
95
96 /**
97 * The name of the field.
98 */
99 private final String fieldName;
100
101 /**
102 * The value of the field.
103 */
104 private final Object value;
105
106 /**
107 * Creates a new {@link LoadedTypeInitializer} for setting a static field.
108 *
109 * @param fieldName the name of the field.
110 * @param value The value to be set.
111 */
112 public ForStaticField(String fieldName, Object value) {
113 this.fieldName = fieldName;
114 this.value = value;
115 }
116
117 /**
118 * {@inheritDoc}
119 */
120 public void onLoad(Class<?> type) {
121 try {
122 Field field = type.getDeclaredField(fieldName);
123 if (!Modifier.isPublic(field.getModifiers())
124 || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
125 || JavaModule.isSupported()
126 && !JavaModule.ofType(type).isExported(new TypeDescription.ForLoadedType(type).getPackage(), JavaModule.ofType(ForStaticField.class))) {
127 AccessController.doPrivileged(new SetAccessibleAction<Field>(field));
128 }
129 field.set(STATIC_FIELD, value);
130 } catch (IllegalAccessException exception) {
131 throw new IllegalArgumentException("Cannot access " + fieldName + " from " + type, exception);
132 } catch (NoSuchFieldException exception) {
133 throw new IllegalStateException("There is no field " + fieldName + " defined on " + type, exception);
134 }
135 }
136
137 /**
138 * {@inheritDoc}
139 */
140 public boolean isAlive() {
141 return true;
142 }
143 }
144
145 /**
146 * A compound loaded type initializer that combines several type initializers.
147 */
148 @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "Serialization is considered opt-in for a rare use case")
149 @HashCodeAndEqualsPlugin.Enhance
150 class Compound implements LoadedTypeInitializer, Serializable {
151
152 /**
153 * This class's serial version UID.
154 */
155 private static final long serialVersionUID = 1L;
156
157 /**
158 * The loaded type initializers that are represented by this compound type initializer.
159 */
160 private final List<LoadedTypeInitializer> loadedTypeInitializers;
161
162 /**
163 * Creates a new compound loaded type initializer.
164 *
165 * @param loadedTypeInitializer A number of loaded type initializers in their invocation order.
166 */
167 public Compound(LoadedTypeInitializer... loadedTypeInitializer) {
168 this(Arrays.asList(loadedTypeInitializer));
169 }
170
171 /**
172 * Creates a new compound loaded type initializer.
173 *
174 * @param loadedTypeInitializers A number of loaded type initializers in their invocation order.
175 */
176 public Compound(List<? extends LoadedTypeInitializer> loadedTypeInitializers) {
177 this.loadedTypeInitializers = new ArrayList<LoadedTypeInitializer>();
178 for (LoadedTypeInitializer loadedTypeInitializer : loadedTypeInitializers) {
179 if (loadedTypeInitializer instanceof Compound) {
180 this.loadedTypeInitializers.addAll(((Compound) loadedTypeInitializer).loadedTypeInitializers);
181 } else if (!(loadedTypeInitializer instanceof NoOp)) {
182 this.loadedTypeInitializers.add(loadedTypeInitializer);
183 }
184 }
185 }
186
187 /**
188 * {@inheritDoc}
189 */
190 public void onLoad(Class<?> type) {
191 for (LoadedTypeInitializer loadedTypeInitializer : loadedTypeInitializers) {
192 loadedTypeInitializer.onLoad(type);
193 }
194 }
195
196 /**
197 * {@inheritDoc}
198 */
199 public boolean isAlive() {
200 for (LoadedTypeInitializer loadedTypeInitializer : loadedTypeInitializers) {
201 if (loadedTypeInitializer.isAlive()) {
202 return true;
203 }
204 }
205 return false;
206 }
207 }
208 }
209