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 trueif 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