Revert optimization removal (#1352)
* Revert optimization removal javaee/jaxb-v2#1157
* Keep optimization for Java versions older then 9 with multi-release jar
* Add noOptimization flag for possible compatibility issues
Signed-off-by: Daniel Kec <daniel.kec@oracle.com>
diff --git a/jaxb-ri/pom.xml b/jaxb-ri/pom.xml
index 6ca9fdc..eee74e5 100644
--- a/jaxb-ri/pom.xml
+++ b/jaxb-ri/pom.xml
@@ -314,6 +314,11 @@
</dependency>
</dependencies>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>3.0.0-M3</version>
+ </plugin>
</plugins>
</pluginManagement>
diff --git a/jaxb-ri/runtime/impl/pom.xml b/jaxb-ri/runtime/impl/pom.xml
index 024cb95..3ec3bf7 100644
--- a/jaxb-ri/runtime/impl/pom.xml
+++ b/jaxb-ri/runtime/impl/pom.xml
@@ -80,6 +80,80 @@
<build>
<plugins>
<plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-mr-resource</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>add-resource</goal>
+ </goals>
+ <configuration>
+ <resources>
+ <resource>
+ <directory>${mrjar.sourceDirectory}</directory>
+ <targetPath>META-INF/versions</targetPath>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-compile-mr</id>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <configuration>
+ <compileSourceRoots>
+ <compileSourceRoot>${mrjar.sourceDirectory}/${upper.java.level}</compileSourceRoot>
+ </compileSourceRoots>
+ <outputDirectory>${project.build.outputDirectory}/META-INF/versions/${upper.java.level}</outputDirectory>
+ <source>${upper.java.level}</source>
+ <target>${upper.java.level}</target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Implementation-Vendor>${vendor.name}</Implementation-Vendor>
+ <Implementation-Vendor-Id>${project.groupId}</Implementation-Vendor-Id>
+ <Implementation-Build-Id>${project.version} - ${scmBranch}-${buildNumber}, ${timestamp}</Implementation-Build-Id>
+ <Multi-Release>true</Multi-Release>
+ <Import-Package>
+ javax.activation;version=!,
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
@@ -90,9 +164,43 @@
--add-opens org.glassfish.jaxb.runtime/com.sun.xml.bind.v2=java.xml.bind
--add-opens org.glassfish.jaxb.runtime/com.sun.xml.bind.v2.schemagen=java.xml.bind
--add-opens org.glassfish.jaxb.runtime/com.sun.xml.bind.v2.schemagen.xmlidref=java.xml.bind
+ --add-opens java.base/java.lang=org.glassfish.jaxb.runtime
+ --add-opens java.base/java.lang.reflect=org.glassfish.jaxb.runtime
+ --add-opens org.glassfish.jaxb.runtime/com.sun.xml.bind.v2.runtime.reflect.opt=java.xml.bind
</argLine>
</configuration>
</plugin>
+
+ <!-- Run tests on multi-release jar -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>java.util.logging.config.file</name>
+ <value>logging.properties</value>
+ </property>
+ </systemProperties>
+ <argLine>
+ --add-opens java.base/java.lang=com.sun.xml.bind
+ --add-opens org.glassfish.jaxb.runtime/com.sun.xml.bind.v2=java.xml.bind
+ --add-opens org.glassfish.jaxb.runtime/com.sun.xml.bind.v2.schemagen=java.xml.bind
+ --add-opens org.glassfish.jaxb.runtime/com.sun.xml.bind.v2.schemagen.xmlidref=java.xml.bind
+ </argLine>
+ <includes>
+ <include>**/*TestMultiRelease.java</include>
+ </includes>
+ </configuration>
+ </plugin>
</plugins>
</build>
</project>
diff --git a/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.java b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.java
new file mode 100644
index 0000000..7e15a18
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.sun.xml.bind.Utils;
+import com.sun.xml.bind.v2.bytecode.ClassTailor;
+
+/**
+ * Stub version of {@link AccessorInjector} for java versions >= 9
+ *
+ * @author Daniel Kec
+ */
+class AccessorInjector {
+
+ protected static final boolean noOptimize = true;
+
+ public static Class<?> prepare(
+ Class beanClass, String templateClassName, String newClassName, String... replacements) {
+ return null;
+ }
+
+}
diff --git a/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/Injector.java b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/Injector.java
new file mode 100644
index 0000000..9ac324e
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/Injector.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+/**
+ * Stub version of {@link Injector} for java versions >= 9
+ *
+ * @author Daniel Kec
+ */
+final class Injector {
+
+ /**
+ * Injects a new class into the given class loader.
+ *
+ * @return null
+ * if it fails to inject.
+ */
+ static Class inject(ClassLoader cl, String className, byte[] image) {
+ return null;
+ }
+
+ /**
+ * Returns the already injected class, or null.
+ */
+ static Class find(ClassLoader cl, String className) {
+ return null;
+ }
+}
diff --git a/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedAccessorFactory.java b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedAccessorFactory.java
new file mode 100644
index 0000000..a389e2f
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedAccessorFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import com.sun.xml.bind.v2.runtime.reflect.Accessor;
+
+import java.util.logging.Logger;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Field;
+
+import java.util.logging.Logger;
+
+/**
+ * Stub version of {@link OptimizedAccessorFactory} for java versions >= 9
+ *
+ * @author Daniel Kec
+ */
+public abstract class OptimizedAccessorFactory {
+ private OptimizedAccessorFactory() {} // no instantiation please
+
+ private static final Logger logger = Logger.getLogger(OptimizedAccessorFactory.class.getName());
+
+
+ public static <B, V> Accessor<B, V> get(Method param1, Method param2) {
+ return getStub();
+ }
+
+ public static <B, V> Accessor<B, V> get(Field param) {
+ return getStub();
+ }
+
+ private static <B, V> Accessor<B, V> getStub(){
+ logger.finer("Optimization is not available since java 9");
+ return null;
+ }
+
+}
diff --git a/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedTransducedAccessorFactory.java b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedTransducedAccessorFactory.java
new file mode 100644
index 0000000..88696b1
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/main/java-mr/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedTransducedAccessorFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import com.sun.xml.bind.v2.model.runtime.RuntimePropertyInfo;
+import com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor;
+
+import java.util.logging.Logger;
+
+/**
+ * Stub version of {@link OptimizedTransducedAccessorFactory} for java versions >= 9
+ *
+ * @author Daniel Kec
+ */
+public abstract class OptimizedTransducedAccessorFactory {
+ private OptimizedTransducedAccessorFactory() {} // no instantiation please
+
+ private static final Logger logger = Logger.getLogger(OptimizedTransducedAccessorFactory.class.getName());
+
+ public static final TransducedAccessor get(RuntimePropertyInfo prop) {
+ logger.finer("Optimization is not available since java 9");
+ return null;
+ }
+}
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeBuiltinLeafInfoImpl.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeBuiltinLeafInfoImpl.java
index 46177e5..fc412ee 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeBuiltinLeafInfoImpl.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeBuiltinLeafInfoImpl.java
@@ -113,6 +113,10 @@
return false;
}
+ public final boolean isDefault() {
+ return true;
+ }
+
public void declareNamespace(T o, XMLSerializer w) throws AccessorException {
}
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeClassInfoImpl.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeClassInfoImpl.java
index ceb3921..50f0085 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeClassInfoImpl.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeClassInfoImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -332,6 +332,10 @@
return xacc.useNamespace();
}
+ public boolean isDefault() {
+ return false;
+ }
+
public void declareNamespace(BeanT bean, XMLSerializer w) throws AccessorException {
try {
xacc.declareNamespace(bean,w);
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeEnumLeafInfoImpl.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeEnumLeafInfoImpl.java
index e1874d1..2bd2cb0 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeEnumLeafInfoImpl.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/model/impl/RuntimeEnumLeafInfoImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -95,6 +95,10 @@
return new QName[]{getTypeName()};
}
+ public boolean isDefault() {
+ return false;
+ }
+
@Override
public Class getClazz() {
return clazz;
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/FilterTransducer.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/FilterTransducer.java
index deb321f..e68cad7 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/FilterTransducer.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/FilterTransducer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -32,6 +32,12 @@
this.core = core;
}
+ public final boolean isDefault() {
+ // this must be used as a decorating transducer,
+ // so it may never be default.
+ return false;
+ }
+
public boolean useNamespace() {
return core.useNamespace();
}
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/Transducer.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/Transducer.java
index f088a3e..d95d3d2 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/Transducer.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/Transducer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -20,6 +20,7 @@
import com.sun.istack.NotNull;
import com.sun.xml.bind.api.AccessorException;
import com.sun.xml.bind.v2.model.runtime.RuntimePropertyInfo;
+import com.sun.xml.bind.v2.runtime.reflect.opt.OptimizedTransducedAccessorFactory;
import org.xml.sax.SAXException;
@@ -39,6 +40,14 @@
public interface Transducer<ValueT> {
/**
+ * If this {@link Transducer} is the default transducer for the <code>ValueT</code>,
+ * this method returns true.
+ *
+ * Used exclusively by {@link OptimizedTransducedAccessorFactory#get(RuntimePropertyInfo)}
+ */
+ boolean isDefault();
+
+ /**
* If true, this {@link Transducer} doesn't declare any namespace,
* and therefore {@link #declareNamespace(Object, XMLSerializer)} is no-op.
*
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/Accessor.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/Accessor.java
index 2649993..410ace8 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/Accessor.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/Accessor.java
@@ -31,6 +31,7 @@
import com.sun.xml.bind.v2.model.core.Adapter;
import com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder;
import com.sun.xml.bind.v2.runtime.JAXBContextImpl;
+import com.sun.xml.bind.v2.runtime.reflect.opt.OptimizedAccessorFactory;
import com.sun.xml.bind.v2.runtime.unmarshaller.Loader;
import com.sun.xml.bind.v2.runtime.unmarshaller.Receiver;
import com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext;
@@ -247,6 +248,14 @@
@Override
public Accessor<BeanT, ValueT> optimize(JAXBContextImpl context) {
+ if (context != null && context.fastBoot) {
+ // let's not waste time on doing this for the sake of faster boot.
+ return this;
+ }
+ Accessor<BeanT, ValueT> acc = OptimizedAccessorFactory.get(f);
+ if (acc != null) {
+ return acc;
+ }
return this;
}
}
@@ -353,6 +362,20 @@
@Override
public Accessor<BeanT, ValueT> optimize(JAXBContextImpl context) {
+ if (getter == null || setter == null) {
+ // if we aren't complete, OptimizedAccessor won't always work
+ return this;
+ }
+ if (context != null && context.fastBoot) {
+ // let's not waste time on doing this for the sake of faster boot.
+ return this;
+ }
+
+ Accessor<BeanT, ValueT> acc = OptimizedAccessorFactory.get(getter, setter);
+ if (acc != null) {
+ return acc;
+ }
+
return this;
}
}
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/TransducedAccessor.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/TransducedAccessor.java
index abc7557..29746be 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/TransducedAccessor.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/TransducedAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -30,6 +30,7 @@
import com.sun.xml.bind.v2.runtime.Transducer;
import com.sun.xml.bind.v2.runtime.XMLSerializer;
import com.sun.xml.bind.v2.runtime.JAXBContextImpl;
+import com.sun.xml.bind.v2.runtime.reflect.opt.OptimizedTransducedAccessorFactory;
import com.sun.xml.bind.v2.runtime.unmarshaller.Patcher;
import com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext;
import com.sun.xml.bind.v2.runtime.unmarshaller.LocatorEx;
@@ -133,6 +134,13 @@
if(prop.id()==ID.IDREF)
return new IDREFTransducedAccessorImpl(prop.getAccessor());
+ if (xducer.isDefault() && context != null && !context.fastBoot) {
+ TransducedAccessor xa = OptimizedTransducedAccessorFactory.get(prop);
+ if (xa != null) {
+ return xa;
+ }
+ }
+
if(xducer.useNamespace())
return new CompositeContextDependentTransducedAccessorImpl( context, xducer, prop.getAccessor() );
else
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.java
index d57489b..62b2c43 100644
--- a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.java
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.java
@@ -11,6 +11,7 @@
package com.sun.xml.bind.v2.runtime.reflect.opt;
import java.io.InputStream;
+import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.xml.bind.Utils;
@@ -32,6 +33,41 @@
}
/**
+ * Loads the optimized class and returns it.
+ *
+ * @return null
+ * if it fails for some reason.
+ */
+ public static Class<?> prepare(
+ Class beanClass, String templateClassName, String newClassName, String... replacements ) {
+
+ if(noOptimize)
+ return null;
+
+ try {
+ ClassLoader cl = SecureLoader.getClassClassLoader(beanClass);
+ if(cl==null) return null; // how do I inject classes to this "null" class loader? for now, back off.
+
+ Class c = Injector.find(cl,newClassName);
+ if (c==null) {
+ byte[] image = tailor(templateClassName,newClassName,replacements);
+ if (image==null) {
+ return null;
+ }
+ c = Injector.inject(cl,newClassName,image);
+ if (c == null) {
+ Injector.find(cl, newClassName);
+ }
+ }
+ return c;
+ } catch(SecurityException e) {
+ // we don't have enough permission to do this
+ logger.log(Level.INFO,"Unable to create an optimized TransducedAccessor ",e);
+ return null;
+ }
+ }
+
+ /**
* Customizes a class file by replacing constant pools.
*
* @param templateClassName
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/Injector.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/Injector.java
new file mode 100644
index 0000000..746fc99
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/Injector.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.ref.WeakReference;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.sun.xml.bind.v2.runtime.reflect.Accessor;
+import java.lang.reflect.Field;
+import java.security.CodeSource;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+
+/**
+ * A {@link ClassLoader} used to "inject" optimized accessor classes
+ * into the VM.
+ *
+ * <p>
+ * Its parent class loader needs to be set to the one that can see the user
+ * class.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+final class Injector {
+
+ /**
+ * {@link Injector}s keyed by their parent {@link ClassLoader}.
+ *
+ * We only need one injector per one user class loader.
+ */
+ private static final ReentrantReadWriteLock irwl = new ReentrantReadWriteLock();
+ private static final Lock ir = irwl.readLock();
+ private static final Lock iw = irwl.writeLock();
+ private static final Map<ClassLoader, WeakReference<Injector>> injectors =
+ new WeakHashMap<ClassLoader, WeakReference<Injector>>();
+ private static final Logger logger = Logger.getLogger(Injector.class.getName());
+ /**
+ * Injects a new class into the given class loader.
+ *
+ * @return null
+ * if it fails to inject.
+ */
+ static Class inject(ClassLoader cl, String className, byte[] image) {
+ Injector injector = get(cl);
+ if (injector != null) {
+ return injector.inject(className, image);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the already injected class, or null.
+ */
+ static Class find(ClassLoader cl, String className) {
+ Injector injector = get(cl);
+ if (injector != null) {
+ return injector.find(className);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets or creates an {@link Injector} for the given class loader.
+ *
+ * @return null
+ * if it fails.
+ */
+ private static Injector get(ClassLoader cl) {
+ Injector injector = null;
+ WeakReference<Injector> wr;
+ ir.lock();
+ try {
+ wr = injectors.get(cl);
+ } finally {
+ ir.unlock();
+ }
+ if (wr != null) {
+ injector = wr.get();
+ }
+ if (injector == null) {
+ try {
+ wr = new WeakReference<Injector>(injector = new Injector(cl));
+ iw.lock();
+ try {
+ if (!injectors.containsKey(cl)) {
+ injectors.put(cl, wr);
+ }
+ } finally {
+ iw.unlock();
+ }
+ } catch (SecurityException e) {
+ logger.log(Level.FINE, "Unable to set up a back-door for the injector", e);
+ return null;
+ }
+ }
+ return injector;
+ }
+ /**
+ * Injected classes keyed by their names.
+ */
+ private final Map<String, Class> classes = new HashMap<>();
+ private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
+ private final Lock r = rwl.readLock();
+ private final Lock w = rwl.writeLock();
+ private final ClassLoader parent;
+ /**
+ * True if this injector is capable of injecting accessors.
+ * False otherwise, which happens if this classloader can't see {@link Accessor}.
+ */
+ private final boolean loadable;
+ private static Method defineClass;
+ private static Method resolveClass;
+ private static Method findLoadedClass;
+ private static Object U;
+
+ static {
+ try {
+ Method[] m = AccessController.doPrivileged(
+ new PrivilegedAction<Method[]>() {
+ @Override
+ public Method[] run() {
+ return new Method[]{
+ getMethod(ClassLoader.class, "defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE),
+ getMethod(ClassLoader.class, "resolveClass", Class.class),
+ getMethod(ClassLoader.class, "findLoadedClass", String.class)
+ };
+ }
+ }
+ );
+ defineClass = m[0];
+ resolveClass = m[1];
+ findLoadedClass = m[2];
+ } catch (Throwable t) {
+ try {
+ U = AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ @Override
+ public Object run() throws Exception {
+ Class u = classForNames("sun.misc.Unsafe", "jdk.internal.misc.Unsafe");
+ Field theUnsafe = u.getDeclaredField("theUnsafe");
+ theUnsafe.setAccessible(true);
+ return theUnsafe.get(null);
+ }
+ });
+ defineClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+ @Override
+ public Method run() throws Exception {
+ try {
+ return U.getClass().getMethod("defineClass",
+ new Class[]{String.class,
+ byte[].class,
+ Integer.TYPE,
+ Integer.TYPE,
+ ClassLoader.class,
+ ProtectionDomain.class});
+ } catch (NoSuchMethodException | SecurityException ex) {
+ throw ex;
+ }
+ }
+ });
+ } catch (SecurityException | PrivilegedActionException ex) {
+ Logger.getLogger(Injector.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+
+ private static Class classForNames(String... names) throws ClassNotFoundException {
+ for (String name : names) {
+ try {
+ return Class.forName(name);
+ } catch (ClassNotFoundException e) {
+ continue;
+ }
+ }
+
+ throw new ClassNotFoundException(
+ String.format("No class found for supplied FQDNs %s",
+ Arrays.toString(names)));
+ }
+
+ private static Method getMethod(final Class<?> c, final String methodname, final Class<?>... params) {
+ try {
+ Method m = c.getDeclaredMethod(methodname, params);
+ m.setAccessible(true);
+ return m;
+ } catch (NoSuchMethodException e) {
+ throw new NoSuchMethodError(e.getMessage());
+ }
+ }
+
+ private Injector(ClassLoader parent) {
+ this.parent = parent;
+ assert parent != null;
+
+ boolean loadableCheck = false;
+
+ try {
+ loadableCheck = parent.loadClass(Accessor.class.getName()) == Accessor.class;
+ } catch (ClassNotFoundException e) {
+ // not loadable
+ }
+
+ this.loadable = loadableCheck;
+ }
+
+ @SuppressWarnings("LockAcquiredButNotSafelyReleased")
+ private Class inject(String className, byte[] image) {
+ if (!loadable) // this injector cannot inject anything
+ {
+ return null;
+ }
+
+ boolean wlocked = false;
+ boolean rlocked = false;
+ try {
+
+ r.lock();
+ rlocked = true;
+
+ Class c = classes.get(className);
+
+ // Unlock now during the findLoadedClass process to avoid
+ // deadlocks
+ r.unlock();
+ rlocked = false;
+
+ //find loaded class from classloader
+ if (c == null && findLoadedClass != null) {
+
+ try {
+ c = (Class) findLoadedClass.invoke(parent, className.replace('/', '.'));
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ logger.log(Level.FINE, "Unable to find " + className, e);
+ } catch (InvocationTargetException e) {
+ Throwable t = e.getTargetException();
+ logger.log(Level.FINE, "Unable to find " + className, t);
+ }
+
+ if (c != null) {
+
+ w.lock();
+ wlocked = true;
+
+ classes.put(className, c);
+
+ w.unlock();
+ wlocked = false;
+
+ return c;
+ }
+ }
+
+ if (c == null) {
+
+ r.lock();
+ rlocked = true;
+
+ c = classes.get(className);
+
+ // Unlock now during the define/resolve process to avoid
+ // deadlocks
+ r.unlock();
+ rlocked = false;
+
+ if (c == null) {
+
+ // we need to inject a class into the
+ try {
+ if (resolveClass != null) {
+ c = (Class) defineClass.invoke(parent, className.replace('/', '.'), image, 0, image.length);
+ resolveClass.invoke(parent, c);
+ } else {
+ c = (Class) defineClass.invoke(U, className.replace('/', '.'), image, 0, image.length, parent, Injector.class.getProtectionDomain());
+ }
+ } catch (IllegalAccessException e) {
+ logger.log(Level.FINE, "Unable to inject " + className, e);
+ return null;
+ } catch (InvocationTargetException e) {
+ Throwable t = e.getTargetException();
+ if (t instanceof LinkageError) {
+ logger.log(Level.FINE, "duplicate class definition bug occured? Please report this : " + className, t);
+ } else {
+ logger.log(Level.FINE, "Unable to inject " + className, t);
+ }
+ return null;
+ } catch (SecurityException e) {
+ logger.log(Level.FINE, "Unable to inject " + className, e);
+ return null;
+ } catch (LinkageError e) {
+ logger.log(Level.FINE, "Unable to inject " + className, e);
+ return null;
+ }
+
+ w.lock();
+ wlocked = true;
+
+ // During the time we were unlocked, we could have tried to
+ // load the class from more than one thread. Check now to see
+ // if someone else beat us to registering this class
+ if (!classes.containsKey(className)) {
+ classes.put(className, c);
+ }
+
+ w.unlock();
+ wlocked = false;
+ }
+ }
+ return c;
+ } finally {
+ if (rlocked) {
+ r.unlock();
+ }
+ if (wlocked) {
+ w.unlock();
+ }
+ }
+ }
+
+ private Class find(String className) {
+ r.lock();
+ try {
+ return classes.get(className);
+ } finally {
+ r.unlock();
+ }
+ }
+}
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedAccessorFactory.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedAccessorFactory.java
new file mode 100644
index 0000000..733bfca
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedAccessorFactory.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.sun.xml.bind.v2.runtime.reflect.Accessor;
+import com.sun.xml.bind.v2.runtime.RuntimeUtil;
+
+import static com.sun.xml.bind.v2.bytecode.ClassTailor.toVMClassName;
+import static com.sun.xml.bind.v2.bytecode.ClassTailor.toVMTypeName;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+public abstract class OptimizedAccessorFactory {
+ private OptimizedAccessorFactory() {} // no instanciation please
+
+ public static final boolean noOptimization = Boolean.getBoolean(OptimizedAccessorFactory.class.getName()+".noOptimization");
+
+ private static final Logger logger = Logger.getLogger(OptimizedAccessorFactory.class.getName());
+
+ private static final String fieldTemplateName;
+ private static final String methodTemplateName;
+
+ static {
+ String s = FieldAccessor_Byte.class.getName();
+ fieldTemplateName = s.substring(0,s.length()-"Byte".length()).replace('.','/');
+
+ s = MethodAccessor_Byte.class.getName();
+ methodTemplateName = s.substring(0,s.length()-"Byte".length()).replace('.','/');
+ }
+
+ /**
+ * Gets the optimized {@link Accessor} that accesses the given getter/setter.
+ *
+ * @return null
+ * if for some reason it fails to create an optimized version.
+ */
+ public static final <B,V> Accessor<B,V> get(Method getter, Method setter) {
+ if(noOptimization){
+ return null;
+ }
+
+ // make sure the method signatures are what we expect
+ if(getter.getParameterTypes().length!=0)
+ return null;
+ Class<?>[] sparams = setter.getParameterTypes();
+ if(sparams.length!=1)
+ return null;
+ if(sparams[0]!=getter.getReturnType())
+ return null;
+ if(setter.getReturnType()!=Void.TYPE)
+ return null;
+ if(getter.getDeclaringClass()!=setter.getDeclaringClass())
+ return null;
+ if(Modifier.isPrivate(getter.getModifiers()) || Modifier.isPrivate(setter.getModifiers()))
+ // we can't access private fields
+ return null;
+
+ Class t = sparams[0];
+ String typeName = t.getName().replace('.','_');
+ if (t.isArray()) {
+ typeName = "AOf_";
+ String compName = t.getComponentType().getName().replace('.','_');
+ while (compName.startsWith("[L")) {
+ compName = compName.substring(2);
+ typeName += "AOf_";
+ }
+ typeName = typeName + compName;
+ }
+
+ String newClassName = toVMClassName(getter.getDeclaringClass())+"$JaxbAccessorM_"+getter.getName()+'_'+setter.getName()+'_'+typeName;
+ Class opt;
+
+ if(t.isPrimitive())
+ opt = AccessorInjector.prepare( getter.getDeclaringClass(),
+ methodTemplateName+RuntimeUtil.primitiveToBox.get(t).getSimpleName(),
+ newClassName,
+ toVMClassName(Bean.class),
+ toVMClassName(getter.getDeclaringClass()),
+ "get_"+t.getName(),
+ getter.getName(),
+ "set_"+t.getName(),
+ setter.getName());
+ else
+ opt = AccessorInjector.prepare( getter.getDeclaringClass(),
+ methodTemplateName+"Ref",
+ newClassName,
+ toVMClassName(Bean.class),
+ toVMClassName(getter.getDeclaringClass()),
+ toVMClassName(Ref.class),
+ toVMClassName(t),
+ "()"+toVMTypeName(Ref.class),
+ "()"+toVMTypeName(t),
+ '('+toVMTypeName(Ref.class)+")V",
+ '('+toVMTypeName(t)+")V",
+ "get_ref",
+ getter.getName(),
+ "set_ref",
+ setter.getName());
+
+ if(opt==null)
+ return null;
+
+ Accessor<B,V> acc = instanciate(opt);
+ if (acc!=null) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "Using optimized Accessor for {0} and {1}", new Object[]{getter, setter});
+ }
+ }
+ return acc;
+ }
+
+
+ /**
+ * Gets the optimized {@link Accessor} that accesses the given field.
+ *
+ * @return null
+ * if for some reason it fails to create an optimized version.
+ */
+ public static final <B,V> Accessor<B,V> get(Field field) {
+ if(noOptimization){
+ return null;
+ }
+
+ int mods = field.getModifiers();
+ if(Modifier.isPrivate(mods) || Modifier.isFinal(mods))
+ // we can't access private fields
+ return null;
+
+ String newClassName = toVMClassName(field.getDeclaringClass())+"$JaxbAccessorF_"+field.getName();
+
+ Class opt;
+
+ if(field.getType().isPrimitive())
+ opt = AccessorInjector.prepare( field.getDeclaringClass(),
+ fieldTemplateName+RuntimeUtil.primitiveToBox.get(field.getType()).getSimpleName(),
+ newClassName,
+ toVMClassName(Bean.class),
+ toVMClassName(field.getDeclaringClass()),
+ "f_"+field.getType().getName(),
+ field.getName() );
+ else
+ opt = AccessorInjector.prepare( field.getDeclaringClass(),
+ fieldTemplateName+"Ref",
+ newClassName,
+ toVMClassName(Bean.class),
+ toVMClassName(field.getDeclaringClass()),
+ toVMClassName(Ref.class),
+ toVMClassName(field.getType()),
+ toVMTypeName(Ref.class),
+ toVMTypeName(field.getType()),
+ "f_ref",
+ field.getName() );
+
+ if(opt==null)
+ return null;
+
+ Accessor<B,V> acc = instanciate(opt);
+ if (acc!=null) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "Using optimized Accessor for {0}", field);
+ }
+ }
+ return acc;
+ }
+
+ private static <B,V> Accessor<B,V> instanciate(Class opt) {
+ try {
+ return (Accessor<B,V>)opt.newInstance();
+ } catch (InstantiationException e) {
+ logger.log(Level.INFO,"failed to load an optimized Accessor",e);
+ } catch (IllegalAccessException e) {
+ logger.log(Level.INFO,"failed to load an optimized Accessor",e);
+ } catch (SecurityException e) {
+ logger.log(Level.INFO,"failed to load an optimized Accessor",e);
+ }
+ return null;
+ }
+}
diff --git a/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedTransducedAccessorFactory.java b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedTransducedAccessorFactory.java
new file mode 100644
index 0000000..689012e
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/main/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedTransducedAccessorFactory.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.sun.xml.bind.v2.model.core.TypeInfo;
+import com.sun.xml.bind.v2.model.runtime.RuntimeClassInfo;
+import com.sun.xml.bind.v2.model.runtime.RuntimePropertyInfo;
+import com.sun.xml.bind.v2.runtime.reflect.Accessor;
+import com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor;
+
+import static com.sun.xml.bind.v2.bytecode.ClassTailor.toVMClassName;
+
+/**
+ * Prepares optimized {@link TransducedAccessor} from templates.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public abstract class OptimizedTransducedAccessorFactory {
+ private OptimizedTransducedAccessorFactory() {} // no instantiation please
+
+ // http://java.sun.com/docs/books/vmspec/2nd-edition/html/ConstantPool.doc.html#75929
+ // "same runtime package"
+
+ private static final Logger logger = Logger.getLogger(OptimizedTransducedAccessorFactory.class.getName());
+
+ private static final String fieldTemplateName;
+ private static final String methodTemplateName;
+
+ static {
+ String s = TransducedAccessor_field_Byte.class.getName();
+ fieldTemplateName = s.substring(0,s.length()-"Byte".length()).replace('.','/');
+
+ s = TransducedAccessor_method_Byte.class.getName();
+ methodTemplateName = s.substring(0,s.length()-"Byte".length()).replace('.','/');
+ }
+
+ /**
+ * Gets the optimized {@link TransducedAccessor} if possible.
+ *
+ * @return null
+ * if for some reason it fails to create an optimized version.
+ */
+ public static final TransducedAccessor get(RuntimePropertyInfo prop) {
+ if(OptimizedAccessorFactory.noOptimization){
+ return null;
+ }
+
+ Accessor acc = prop.getAccessor();
+
+ // consider using an optimized TransducedAccessor implementations.
+ Class opt=null;
+
+ TypeInfo<Type,Class> parent = prop.parent();
+ if(!(parent instanceof RuntimeClassInfo))
+ return null;
+
+ Class dc = ((RuntimeClassInfo)parent).getClazz();
+ String newClassName = toVMClassName(dc)+"_JaxbXducedAccessor_"+prop.getName();
+
+
+ if(acc instanceof Accessor.FieldReflection) {
+ // TODO: we also need to make sure that the default xducer is used.
+ Accessor.FieldReflection racc = (Accessor.FieldReflection) acc;
+ Field field = racc.f;
+
+ int mods = field.getModifiers();
+ if(Modifier.isPrivate(mods) || Modifier.isFinal(mods))
+ // we can't access private fields.
+ // TODO: think about how to improve this case
+ return null;
+
+ Class<?> t = field.getType();
+ if(t.isPrimitive())
+ opt = AccessorInjector.prepare( dc,
+ fieldTemplateName+suffixMap.get(t),
+ newClassName,
+ toVMClassName(Bean.class),
+ toVMClassName(dc),
+ "f_"+t.getName(),
+ field.getName() );
+ }
+
+ if(acc.getClass()==Accessor.GetterSetterReflection.class) {
+ Accessor.GetterSetterReflection gacc = (Accessor.GetterSetterReflection) acc;
+
+ if(gacc.getter==null || gacc.setter==null)
+ return null; // incomplete
+
+ Class<?> t = gacc.getter.getReturnType();
+
+ if(Modifier.isPrivate(gacc.getter.getModifiers())
+ || Modifier.isPrivate(gacc.setter.getModifiers()))
+ // we can't access private methods.
+ return null;
+
+
+ if(t.isPrimitive())
+ opt = AccessorInjector.prepare( dc,
+ methodTemplateName+suffixMap.get(t),
+ newClassName,
+ toVMClassName(Bean.class),
+ toVMClassName(dc),
+ "get_"+t.getName(),
+ gacc.getter.getName(),
+ "set_"+t.getName(),
+ gacc.setter.getName());
+ }
+
+ if(opt==null)
+ return null;
+
+ logger.log(Level.FINE,"Using optimized TransducedAccessor for "+prop.displayName());
+
+
+ try {
+ return (TransducedAccessor)opt.newInstance();
+ } catch (InstantiationException e) {
+ logger.log(Level.INFO,"failed to load an optimized TransducedAccessor",e);
+ } catch (IllegalAccessException e) {
+ logger.log(Level.INFO,"failed to load an optimized TransducedAccessor",e);
+ } catch (SecurityException e) {
+ logger.log(Level.INFO,"failed to load an optimized TransducedAccessor",e);
+ }
+ return null;
+ }
+
+ private static final Map<Class,String> suffixMap = new HashMap<Class, String>();
+
+ static {
+ suffixMap.put(Byte.TYPE,"Byte");
+ suffixMap.put(Short.TYPE,"Short");
+ suffixMap.put(Integer.TYPE,"Integer");
+ suffixMap.put(Long.TYPE,"Long");
+ suffixMap.put(Boolean.TYPE,"Boolean");
+ suffixMap.put(Float.TYPE,"Float");
+ suffixMap.put(Double.TYPE,"Double");
+ }
+
+}
diff --git a/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/test/AbstractTestMultiRelease.java b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/test/AbstractTestMultiRelease.java
new file mode 100644
index 0000000..d84c973
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/test/AbstractTestMultiRelease.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package com.sun.xml.bind.test;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * Tools for testing multi-release jar, makes possible to skip tests marked for java version above current one.
+ *
+ * @author Daniel Kec
+ * @see SinceJava9
+ */
+public abstract class AbstractTestMultiRelease {
+
+ @Rule
+ public TestWatcher javaVersionWatcher = new TestWatcher() {
+ @Override
+ protected void starting(Description description) {
+ if (description.getAnnotation(SinceJava9.class) != null) {
+ if (!isAboveOrEqualJava9()) {
+ Assume.assumeTrue("Skipping, test is applicable since java 9 and higher.", false);
+ }
+ }
+ super.starting(description);
+ }
+ };
+
+ protected boolean isAboveOrEqualJava9() {
+ try {
+ //Runtime#version is available since java 9
+ Runtime.class.getMethod("version");
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/test/SinceJava9.java b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/test/SinceJava9.java
new file mode 100644
index 0000000..039f337
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/test/SinceJava9.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package com.sun.xml.bind.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker for test methods which are assumed to be tested only on Java 9 and higher
+ *
+ * @see AbstractTestMultiRelease
+ * @author Daniel Kec
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface SinceJava9 {
+
+}
diff --git a/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizationTest.java b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizationTest.java
new file mode 100644
index 0000000..1546244
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizationTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * Test flag for disabling optimization
+ * com.sun.xml.bind.v2.runtime.reflect.opt.OptimizedAccessorFactory.noOptimization=true/false
+ *
+ * @author Daniel Kec
+ */
+public class OptimizationTest {
+
+ private static final String EXPECTED_ACCESSOR_CLASS_REF = Book.class.getName() + "$JaxbAccessorM_getAuthor_setAuthor_java_lang_String";
+ private static final String OPTIMIZATION_ON = "AUTHOR_EXPECTING_OPTIMIZATION_ON";
+ private static final String OPTIMIZATION_OFF = "AUTHOR_EXPECTING_OPTIMIZATION_OFF";
+
+ @Test
+ public void checkNoOptimizationFlagFalse() throws JAXBException {
+ Book book = unmarshal(OPTIMIZATION_ON);
+ Assert.assertTrue(book.errorMessage, book.errorMessage == null);
+ Assert.assertEquals(OPTIMIZATION_ON, book.getAuthor());
+ }
+
+ @Test
+ public void checkNoOptimizationFlagTrue() throws JAXBException, NoSuchFieldException, IllegalAccessException {
+ try {
+ setNoOptimizationFlag(true);
+ Book book = unmarshal(OPTIMIZATION_OFF);
+ Assert.assertTrue(book.errorMessage, book.errorMessage == null);
+ Assert.assertEquals(OPTIMIZATION_OFF, book.getAuthor());
+ } finally {
+ setNoOptimizationFlag(false);
+ }
+ }
+
+ private Book unmarshal(String autor) throws JAXBException {
+ JAXBContext context = JAXBContext.newInstance(Book.class);
+ Unmarshaller unmarshaller = context.createUnmarshaller();
+ return (Book) unmarshaller.unmarshal(new StringReader(
+ String.format("<book><author>%s</author></book>", autor)));
+ }
+
+ private void setNoOptimizationFlag(boolean flag) throws NoSuchFieldException, IllegalAccessException {
+ Field noOptimizationField = OptimizedAccessorFactory.class.getField("noOptimization");
+ Field modifiersField = Field.class.getDeclaredField("modifiers");
+ modifiersField.setAccessible(true);
+ noOptimizationField.setAccessible(true);
+ modifiersField.setInt(noOptimizationField, noOptimizationField.getModifiers() & ~Modifier.FINAL);
+ noOptimizationField.setBoolean(OptimizedAccessorFactory.class, true);
+ }
+
+ @XmlRootElement
+ static class Book {
+ private String author;
+
+ @XmlTransient
+ String errorMessage = null;
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ String artificialInnerClass = null;
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ for (StackTraceElement el : stackTrace) {
+ if (el.getClassName().equals(EXPECTED_ACCESSOR_CLASS_REF)) {
+ artificialInnerClass = el.getClassName();
+ break;
+ }
+ }
+
+ if (author.equals(OPTIMIZATION_OFF) && artificialInnerClass != null) {
+ this.errorMessage = String.format("Artificial accessor class detected %s", artificialInnerClass);
+ } else if (author.equals(OPTIMIZATION_ON) && artificialInnerClass == null) {
+ this.errorMessage = String.format("Expected artificial accessor class not found: %s", EXPECTED_ACCESSOR_CLASS_REF);
+ }
+ this.author = author;
+ }
+ }
+}
diff --git a/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizationTestMultiRelease.java b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizationTestMultiRelease.java
new file mode 100644
index 0000000..8ed8485
--- /dev/null
+++ b/jaxb-ri/runtime/impl/src/test/java/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizationTestMultiRelease.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package com.sun.xml.bind.v2.runtime.reflect.opt;
+
+import com.sun.xml.bind.test.AbstractTestMultiRelease;
+import com.sun.xml.bind.test.SinceJava9;
+import com.sun.xml.bind.v2.model.runtime.RuntimePropertyInfo;
+import com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test multi release for optimization feature,
+ * Feature needs to be disabled since java 9.
+ *
+ * @author Daniel Kec
+ */
+public class OptimizationTestMultiRelease extends AbstractTestMultiRelease {
+
+ @SuppressWarnings("WeakerAccess")
+ public static String TEST_FIELD = "TEST_FIELD";
+
+ @Test
+ @SinceJava9
+ public void testStubbedAccessorInjector() {
+ //Stubbed version doesn't mind null param
+ Assert.assertNull("Injector should always return null since Java 9",
+ AccessorInjector.prepare(null, null, null, null));
+ }
+
+ @Test
+ @SinceJava9
+ public void testStubbedInjector() {
+ //Stubbed version doesn't mind null param
+ Assert.assertNull("Injector should always return null since Java 9",
+ Injector.find(null, null));
+ }
+
+ @Test
+ @SinceJava9
+ public void testStubbedOptimizedAccessorFactory() throws NoSuchFieldException {
+ Field field = this.getClass().getDeclaredField(TEST_FIELD);
+ Assert.assertNull("OptimizedAccessorFactory should always return null since Java 9", OptimizedAccessorFactory.get(field));
+ }
+
+ @Test
+ @SinceJava9
+ @SuppressWarnings("ConstantConditions")
+ public void testStubbedOptimizedTransducedAccessorFactory() throws NoSuchFieldException {
+ final String errMessage = "OptimizedTransducedAccessorFactory should always return null since Java 9";
+ try {
+ //Stubbed version doesn't mind null param
+ TransducedAccessor accessor = OptimizedTransducedAccessorFactory.get((RuntimePropertyInfo) null);
+ Assert.assertNull(errMessage, accessor);
+ } catch (NullPointerException npe) {
+ Assert.fail(errMessage);
+ }
+ }
+
+}
diff --git a/jaxb-ri/tools/config/copyright-exclude b/jaxb-ri/tools/config/copyright-exclude
index eed5c68..79c07a7 100644
--- a/jaxb-ri/tools/config/copyright-exclude
+++ b/jaxb-ri/tools/config/copyright-exclude
@@ -1,6 +1,7 @@
/MANIFEST.MF
/META-INF/services/
/README
+.iml
.gif
.jpg
.png