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;
17
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import net.bytebuddy.build.CachedReturnPlugin;
20 import net.bytebuddy.build.HashCodeAndEqualsPlugin;
21 import net.bytebuddy.description.type.TypeDescription;
22 import net.bytebuddy.dynamic.ClassFileLocator;
23 import net.bytebuddy.utility.OpenedClassReader;
24 import net.bytebuddy.jar.asm.Opcodes;
25
26 import java.io.IOException;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.security.AccessController;
30 import java.security.PrivilegedAction;
31
32 /**
33 * A wrapper object for representing a validated class file version in the format that is specified by the
34 * <a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html">JVMS</a>.
35 */
36 @HashCodeAndEqualsPlugin.Enhance
37 public class ClassFileVersion implements Comparable<ClassFileVersion> {
38
39 /**
40 * Returns the minimal version number that is legal.
41 */
42 protected static final int BASE_VERSION = 44;
43
44 /**
45 * The class file version of Java 1.
46 */
47 public static final ClassFileVersion JAVA_V1 = new ClassFileVersion(Opcodes.V1_1);
48
49 /**
50 * The class file version of Java 2.
51 */
52 public static final ClassFileVersion JAVA_V2 = new ClassFileVersion(Opcodes.V1_2);
53
54 /**
55 * The class file version of Java 3.
56 */
57 public static final ClassFileVersion JAVA_V3 = new ClassFileVersion(Opcodes.V1_3);
58
59 /**
60 * The class file version of Java 4.
61 */
62 public static final ClassFileVersion JAVA_V4 = new ClassFileVersion(Opcodes.V1_4);
63
64 /**
65 * The class file version of Java 5.
66 */
67 public static final ClassFileVersion JAVA_V5 = new ClassFileVersion(Opcodes.V1_5);
68
69 /**
70 * The class file version of Java 6.
71 */
72 public static final ClassFileVersion JAVA_V6 = new ClassFileVersion(Opcodes.V1_6);
73
74 /**
75 * The class file version of Java 7.
76 */
77 public static final ClassFileVersion JAVA_V7 = new ClassFileVersion(Opcodes.V1_7);
78
79 /**
80 * The class file version of Java 8.
81 */
82 public static final ClassFileVersion JAVA_V8 = new ClassFileVersion(Opcodes.V1_8);
83
84 /**
85 * The class file version of Java 9.
86 */
87 public static final ClassFileVersion JAVA_V9 = new ClassFileVersion(Opcodes.V9);
88
89 /**
90 * The class file version of Java 10.
91 */
92 public static final ClassFileVersion JAVA_V10 = new ClassFileVersion(Opcodes.V10);
93
94 /**
95 * The class file version of Java 11.
96 */
97 public static final ClassFileVersion JAVA_V11 = new ClassFileVersion(Opcodes.V11);
98
99 /**
100 * The class file version of Java 12.
101 */
102 public static final ClassFileVersion JAVA_V12 = new ClassFileVersion(Opcodes.V12);
103
104 /**
105 * The class file version of Java 13.
106 */
107 public static final ClassFileVersion JAVA_V13 = new ClassFileVersion(Opcodes.V13);
108
109 /**
110 * The class file version of Java 14.
111 */
112 public static final ClassFileVersion JAVA_V14 = new ClassFileVersion(Opcodes.V14);
113
114 /**
115 * The class file version of Java 15.
116 */
117 public static final ClassFileVersion JAVA_V15 = new ClassFileVersion(Opcodes.V15);
118
119 /**
120 * The class file version of Java 16.
121 */
122 public static final ClassFileVersion JAVA_V16 = new ClassFileVersion(Opcodes.V16);
123
124 /**
125 * A version locator for the executing JVM.
126 */
127 private static final VersionLocator VERSION_LOCATOR = AccessController.doPrivileged(VersionLocator.CreationAction.INSTANCE);
128
129 /**
130 * The version number that is represented by this class file version instance.
131 */
132 private final int versionNumber;
133
134 /**
135 * Creates a wrapper for a given minor-major release of the Java class file file.
136 *
137 * @param versionNumber The minor-major release number.
138 */
139 protected ClassFileVersion(int versionNumber) {
140 this.versionNumber = versionNumber;
141 }
142
143 /**
144 * Creates a wrapper for a given minor-major release of the Java class file file.
145 *
146 * @param versionNumber The minor-major release number.
147 * @return A representation of the version number.
148 */
149 public static ClassFileVersion ofMinorMajor(int versionNumber) {
150 ClassFileVersion classFileVersion = new ClassFileVersion(versionNumber);
151 if (classFileVersion.getMajorVersion() <= BASE_VERSION) {
152 throw new IllegalArgumentException("Class version " + versionNumber + " is not valid");
153 }
154 return classFileVersion;
155 }
156
157 /**
158 * Returns the Java class file by its representation by a version string in accordance to the formats known to <i>javac</i>.
159 *
160 * @param javaVersionString The Java version string.
161 * @return The appropriate class file version.
162 */
163 public static ClassFileVersion ofJavaVersionString(String javaVersionString) {
164 if (javaVersionString.equals("1.1")) {
165 return JAVA_V1;
166 } else if (javaVersionString.equals("1.2")) {
167 return JAVA_V2;
168 } else if (javaVersionString.equals("1.3")) {
169 return JAVA_V3;
170 } else if (javaVersionString.equals("1.4")) {
171 return JAVA_V4;
172 } else if (javaVersionString.equals("1.5") || javaVersionString.equals("5")) {
173 return JAVA_V5;
174 } else if (javaVersionString.equals("1.6") || javaVersionString.equals("6")) {
175 return JAVA_V6;
176 } else if (javaVersionString.equals("1.7") || javaVersionString.equals("7")) {
177 return JAVA_V7;
178 } else if (javaVersionString.equals("1.8") || javaVersionString.equals("8")) {
179 return JAVA_V8;
180 } else if (javaVersionString.equals("1.9") || javaVersionString.equals("9")) {
181 return JAVA_V9;
182 } else if (javaVersionString.equals("1.10") || javaVersionString.equals("10")) {
183 return JAVA_V10;
184 } else if (javaVersionString.equals("1.11") || javaVersionString.equals("11")) {
185 return JAVA_V11;
186 } else if (javaVersionString.equals("1.12") || javaVersionString.equals("12")) {
187 return JAVA_V12;
188 } else if (javaVersionString.equals("1.13") || javaVersionString.equals("13")) {
189 return JAVA_V13;
190 } else if (javaVersionString.equals("1.14") || javaVersionString.equals("14")) {
191 return JAVA_V14;
192 } else if (javaVersionString.equals("1.15") || javaVersionString.equals("15")) {
193 return JAVA_V15;
194 } else if (javaVersionString.equals("1.16") || javaVersionString.equals("16")) {
195 return JAVA_V16;
196 } else {
197 if (OpenedClassReader.EXPERIMENTAL) {
198 try {
199 int version = Integer.parseInt(javaVersionString.startsWith("1.")
200 ? javaVersionString.substring(2)
201 : javaVersionString);
202 if (version > 0) {
203 return new ClassFileVersion(BASE_VERSION + version);
204 }
205 } catch (NumberFormatException ignored) {
206 }
207 }
208 throw new IllegalArgumentException("Unknown Java version string: " + javaVersionString);
209 }
210 }
211
212 /**
213 * Creates a class file version for a given major release of Java. Currently, all versions reaching from
214 * Java 1 to Java 9 are supported.
215 *
216 * @param javaVersion The Java version.
217 * @return A wrapper for the given Java class file version.
218 */
219 public static ClassFileVersion ofJavaVersion(int javaVersion) {
220 switch (javaVersion) {
221 case 1:
222 return JAVA_V1;
223 case 2:
224 return JAVA_V2;
225 case 3:
226 return JAVA_V3;
227 case 4:
228 return JAVA_V4;
229 case 5:
230 return JAVA_V5;
231 case 6:
232 return JAVA_V6;
233 case 7:
234 return JAVA_V7;
235 case 8:
236 return JAVA_V8;
237 case 9:
238 return JAVA_V9;
239 case 10:
240 return JAVA_V10;
241 case 11:
242 return JAVA_V11;
243 case 12:
244 return JAVA_V12;
245 case 13:
246 return JAVA_V13;
247 case 14:
248 return JAVA_V14;
249 case 15:
250 return JAVA_V15;
251 case 16:
252 return JAVA_V16;
253 default:
254 if (OpenedClassReader.EXPERIMENTAL && javaVersion > 0) {
255 return new ClassFileVersion(BASE_VERSION + javaVersion);
256 } else {
257 throw new IllegalArgumentException("Unknown Java version: " + javaVersion);
258 }
259 }
260 }
261
262 /**
263 * Finds the highest class file version that is compatible to the current JVM version. Prior to Java 9, this is achieved
264 * by parsing the {@code java.version} property which is provided by {@link java.lang.System#getProperty(String)}. If the system
265 * property is not available, an {@link IllegalStateException} is thrown.
266 *
267 * @return The currently running Java process's class file version.
268 */
269 @CachedReturnPlugin.Enhance
270 public static ClassFileVersion ofThisVm() {
271 return VERSION_LOCATOR.locate();
272 }
273
274 /**
275 * Finds the highest class file version that is compatible to the current JVM version. Prior to Java 9, this is achieved
276 * by parsing the {@code java.version} property which is provided by {@link java.lang.System#getProperty(String)}. If the system
277 * property is not available, the {@code fallback} version is returned.
278 *
279 * @param fallback The version to fallback to if locating a class file version is not possible.
280 * @return The currently running Java process's class file version or the fallback if locating this version is impossible.
281 */
282 @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
283 public static ClassFileVersion ofThisVm(ClassFileVersion fallback) {
284 try {
285 return ofThisVm();
286 } catch (Exception ignored) {
287 return fallback;
288 }
289 }
290
291 /**
292 * Extracts a class' class version. The class' byte code is located by querying the {@link ClassLoader} of the class.
293 *
294 * @param type The type for which to locate a class file version.
295 * @return The type's class file version.
296 * @throws IOException If an error occurs while reading the class file.
297 */
298 public static ClassFileVersion of(Class<?> type) throws IOException {
299 return of(type, ClassFileLocator.ForClassLoader.of(type.getClassLoader()));
300 }
301
302 /**
303 * Extracts a class' class version.
304 *
305 * @param type The type for which to locate a class file version.
306 * @param classFileLocator The class file locator to query for a class file.
307 * @return The type's class file version.
308 * @throws IOException If an error occurs while reading the class file.
309 */
310 public static ClassFileVersion of(Class<?> type, ClassFileLocator classFileLocator) throws IOException {
311 return of(TypeDescription.ForLoadedType.of(type), classFileLocator);
312 }
313
314 /**
315 * Extracts a class' class version.
316 *
317 * @param typeDescription The type for which to locate a class file version.
318 * @param classFileLocator The class file locator to query for a class file.
319 * @return The type's class file version.
320 * @throws IOException If an error occurs while reading the class file.
321 */
322 public static ClassFileVersion of(TypeDescription typeDescription, ClassFileLocator classFileLocator) throws IOException {
323 return ofClassFile(classFileLocator.locate(typeDescription.getName()).resolve());
324 }
325
326 /**
327 * Extracts a class' class version from a class file.
328 *
329 * @param binaryRepresentation The class file's binary representation.
330 * @return The supplied class file's class file version.
331 */
332 public static ClassFileVersion ofClassFile(byte[] binaryRepresentation) {
333 if (binaryRepresentation.length < 7) {
334 throw new IllegalArgumentException("Supplied byte array is too short to be a class file with " + binaryRepresentation.length + " byte");
335 }
336 return ofMinorMajor(binaryRepresentation[6] << 8 | binaryRepresentation[7] & 0xFF);
337 }
338
339 /**
340 * Returns the minor-major release number of this class file version.
341 *
342 * @return The minor-major release number of this class file version.
343 */
344 public int getMinorMajorVersion() {
345 return versionNumber;
346 }
347
348 /**
349 * Returns the major version this instance represents.
350 *
351 * @return The major version this instance represents.
352 */
353 public short getMajorVersion() {
354 return (short) (versionNumber & 0xFF);
355 }
356
357 /**
358 * Returns the minor version this instance represents.
359 *
360 * @return The minor version this instance represents.
361 */
362 public short getMinorVersion() {
363 return (short) (versionNumber >> 16);
364 }
365
366 /**
367 * Returns the Java runtime version number of this class file version.
368 *
369 * @return The Java runtime version.
370 */
371 public int getJavaVersion() {
372 return getMajorVersion() - BASE_VERSION;
373 }
374
375 /**
376 * Checks if this class file version is at least as new as the provided version.
377 *
378 * @param classFileVersion The version to check against.
379 * @return {@code true} if this version is at least of the given version.
380 */
381 public boolean isAtLeast(ClassFileVersion classFileVersion) {
382 return compareTo(classFileVersion) > -1;
383 }
384
385 /**
386 * Checks if this class file version is newer than the provided version.
387 *
388 * @param classFileVersion The version to check against.
389 * @return {@code true} if this version is newer than the provided version.
390 */
391 public boolean isGreaterThan(ClassFileVersion classFileVersion) {
392 return compareTo(classFileVersion) > 0;
393 }
394
395 /**
396 * Checks if this class file version is at most as new as the provided version.
397 *
398 * @param classFileVersion The version to check against.
399 * @return {@code true} if this version is as most as new as the provided version.
400 */
401 public boolean isAtMost(ClassFileVersion classFileVersion) {
402 return compareTo(classFileVersion) < 1;
403 }
404
405 /**
406 * Checks if this class file version is older than the provided version.
407 *
408 * @param classFileVersion The version to check against.
409 * @return {@code true} if this version is older than the provided version.
410 */
411 public boolean isLessThan(ClassFileVersion classFileVersion) {
412 return compareTo(classFileVersion) < 0;
413 }
414
415 /**
416 * Returns this class file version indicating a class using preview features.
417 *
418 * @return This class file version but indicating the use of preview features.
419 */
420 public ClassFileVersion asPreviewVersion() {
421 return new ClassFileVersion(versionNumber | Opcodes.V_PREVIEW);
422 }
423
424 /**
425 * Returns {@code true} if this class file version indicates the use of preview features.
426 *
427 * @return {@code true} if this class file version indicates the use of preview features.
428 */
429 public boolean isPreviewVersion() {
430 return (versionNumber & Opcodes.V_PREVIEW) == Opcodes.V_PREVIEW;
431 }
432
433 /**
434 * {@inheritDoc}
435 */
436 public int compareTo(ClassFileVersion other) {
437 return Integer.signum(getMajorVersion() == other.getMajorVersion()
438 ? getMinorVersion() - other.getMinorVersion()
439 : getMajorVersion() - other.getMajorVersion());
440 }
441
442 @Override
443 public String toString() {
444 return "Java " + getJavaVersion() + " (" + getMinorMajorVersion() + ")";
445 }
446
447 /**
448 * A locator for the executing VM's Java version.
449 */
450 protected interface VersionLocator {
451
452 /**
453 * Locates the current VM's major version number.
454 *
455 * @return The current VM's major version number.
456 */
457 ClassFileVersion locate();
458
459 /**
460 * A creation action for a version locator.
461 */
462 enum CreationAction implements PrivilegedAction<VersionLocator> {
463
464 /**
465 * The singleton instance.
466 */
467 INSTANCE;
468
469 /**
470 * {@inheritDoc}
471 */
472 @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
473 public VersionLocator run() {
474 try {
475 return new VersionLocator.ForJava9CapableVm(Runtime.class.getMethod("version"),
476 Class.forName("java.lang.Runtime$Version").getMethod("major"));
477 } catch (Exception ignored) {
478 return VersionLocator.ForLegacyVm.INSTANCE;
479 }
480 }
481 }
482
483 /**
484 * A version locator for a JVM of at least version 9.
485 */
486 @HashCodeAndEqualsPlugin.Enhance
487 class ForJava9CapableVm implements VersionLocator {
488
489 /**
490 * Indicates that a reflective method call invokes a static method.
491 */
492 private static final Object STATIC_METHOD = null;
493
494 /**
495 * The {@code java.lang.Runtime#version()} method.
496 */
497 private final Method current;
498
499 /**
500 * The {@code java.lang.Runtime.Version#major()} method.
501 */
502 private final Method major;
503
504 /**
505 * Creates a new version locator for a Java 9 capable VM.
506 *
507 * @param current The {@code java.lang.Runtime#version()} method.
508 * @param major The {@code java.lang.Runtime.Version#major()} method.
509 */
510 protected ForJava9CapableVm(Method current, Method major) {
511 this.current = current;
512 this.major = major;
513 }
514
515 /**
516 * {@inheritDoc}
517 */
518 public ClassFileVersion locate() {
519 try {
520 return ClassFileVersion.ofJavaVersion((Integer) major.invoke(current.invoke(STATIC_METHOD)));
521 } catch (IllegalAccessException exception) {
522 throw new IllegalStateException("Could not access VM version lookup", exception);
523 } catch (InvocationTargetException exception) {
524 throw new IllegalStateException("Could not look up VM version", exception.getCause());
525 }
526 }
527 }
528
529 /**
530 * A version locator for a JVM that does not provide the {@code java.lang.Runtime.Version} class.
531 */
532 enum ForLegacyVm implements VersionLocator, PrivilegedAction<String> {
533
534 /**
535 * The singleton instance.
536 */
537 INSTANCE;
538
539 /**
540 * The system property for this JVM's Java version.
541 */
542 private static final String JAVA_VERSION_PROPERTY = "java.version";
543
544 /**
545 * {@inheritDoc}
546 */
547 public ClassFileVersion locate() {
548 String versionString = AccessController.doPrivileged(this);
549 int[] versionIndex = {-1, 0, 0};
550 for (int i = 1; i < 3; i++) {
551 versionIndex[i] = versionString.indexOf('.', versionIndex[i - 1] + 1);
552 if (versionIndex[i] == -1) {
553 throw new IllegalStateException("This JVM's version string does not seem to be valid: " + versionString);
554 }
555 }
556 return ClassFileVersion.ofJavaVersion(Integer.parseInt(versionString.substring(versionIndex[1] + 1, versionIndex[2])));
557 }
558
559 /**
560 * {@inheritDoc}
561 */
562 public String run() {
563 return System.getProperty(JAVA_VERSION_PROPERTY);
564 }
565 }
566 }
567 }
568