@@ -44,15 +44,15 @@ import org.springsource.loaded.ri.JavaMethodCache;
|
||||
|
||||
/**
|
||||
* Represents a type that has been processed such that it can be reloaded at runtime.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ReloadableType {
|
||||
|
||||
// TODO when a field is shadowed or renamed and the old one never accessed again, it may be holding onto something and prevent it from GC.
|
||||
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can
|
||||
// check whether a 'repair' is needed on a field accessor (because the type has been reloaded and
|
||||
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can
|
||||
// check whether a 'repair' is needed on a field accessor (because the type has been reloaded and
|
||||
// the map in the accessor hasnt been repaired yet)
|
||||
private static Logger log = Logger.getLogger(ReloadableType.class.getName());
|
||||
|
||||
@@ -140,13 +140,14 @@ public class ReloadableType {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dottedtypename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new ReloadableType with the specified name and the specified initial bytecode.
|
||||
*
|
||||
*
|
||||
* @param dottedtypename the dotted name
|
||||
* @param initialBytes the bytecode for the initial version
|
||||
* @param id for this reloadable type, allocated by the registry
|
||||
@@ -190,7 +191,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Gets the 'orignal' method corresponding to given name and method descriptor. This only considers methods that
|
||||
* exist in the first (non-reloaded) version of the type.
|
||||
*
|
||||
*
|
||||
* @param name method name
|
||||
* @param descriptor method descriptor (e.g (Ljava/lang/String;)I)
|
||||
* @return the MethodMember or an exception if not found
|
||||
@@ -221,7 +222,7 @@ public class ReloadableType {
|
||||
* This method will attempt to apply any pre-existing transforms to the provided bytecode, if it is thought to be
|
||||
* necessary. Currently 'necessary' is determined by finding ourselves running under tcServer and Spring Insight
|
||||
* being turned on.
|
||||
*
|
||||
*
|
||||
* @param bytes the new bytes to be possibly transformed.
|
||||
* @return either the original bytes or a transformed set of bytes
|
||||
*/
|
||||
@@ -300,7 +301,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Load a new version of this type, using the specified suffix to tag the newly generated artifact class names.
|
||||
*
|
||||
*
|
||||
* @param versionsuffix the String suffix to append to classnames being created for the reloaded class
|
||||
* @param newbytedata the class bytes for the new version of this class
|
||||
* @return true if the reload succeeded
|
||||
@@ -328,16 +329,17 @@ public class ReloadableType {
|
||||
// Not allowed to change the type
|
||||
reload = false;
|
||||
|
||||
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(this.dottedtypename).append(
|
||||
"\n");
|
||||
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(
|
||||
this.dottedtypename).append(
|
||||
"\n");
|
||||
if (td.hasTypeAccessChanged()) {
|
||||
s.append(" Reason: Type modifiers changed\n");
|
||||
s.append(" Reason: Type modifiers changed from=0x" + Integer.toHexString(td.oAccess) + " to=0x"
|
||||
+ Integer.toHexString(td.nAccess) + "\n");
|
||||
cantReload = true;
|
||||
}
|
||||
if (td.hasTypeSupertypeChanged()) {
|
||||
s.append(" Reason: Supertype changed from ").append(td.oSuperName).append(" to ").append(
|
||||
td.nSuperName)
|
||||
.append("\n");
|
||||
td.nSuperName).append("\n");
|
||||
cantReload = true;
|
||||
}
|
||||
if (td.hasTypeInterfacesChanged()) {
|
||||
@@ -370,8 +372,8 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
if (!justGroovyObjectMoved) {
|
||||
s.append(" Reason: Interfaces changed from ").append(td.oInterfaces).append(" to ")
|
||||
.append(td.nInterfaces).append("\n");
|
||||
s.append(" Reason: Interfaces changed from ").append(td.oInterfaces).append(" to ").append(
|
||||
td.nInterfaces).append("\n");
|
||||
cantReload = true;
|
||||
}
|
||||
}
|
||||
@@ -535,17 +537,19 @@ public class ReloadableType {
|
||||
/**
|
||||
* Go through proxies we know about in this registry and see if any of them are for the type we have just reloaded.
|
||||
* If they are, regenerate them and reload them.
|
||||
*
|
||||
*
|
||||
* @param versionsuffix the suffix to use when reloading the proxies (it matches what is being used to reload the
|
||||
* type)
|
||||
*/
|
||||
private void reloadProxiesIfNecessary(String versionsuffix) {
|
||||
ReloadableType proxy = typeRegistry.cglibProxies.get(this.slashedtypename);
|
||||
if (proxy != null) {
|
||||
Object[] strategyAndGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndClassGeneratorMap.get(getClazz());
|
||||
Object[] strategyAndGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndClassGeneratorMap.get(
|
||||
getClazz());
|
||||
if (strategyAndGeneratorPair == null) {
|
||||
if (log.isLoggable(Level.SEVERE)) {
|
||||
log.severe("Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type");
|
||||
log.severe(
|
||||
"Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -572,9 +576,10 @@ public class ReloadableType {
|
||||
|
||||
proxy = typeRegistry.cglibProxiesFastClass.get(this.slashedtypename);
|
||||
if (proxy != null) {
|
||||
Object[] strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap
|
||||
.get(getClazz());
|
||||
strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(getClazz());
|
||||
Object[] strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(
|
||||
getClazz());
|
||||
strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(
|
||||
getClazz());
|
||||
// System.out.println("need to reload fastclass " + proxy + " os=" + os);
|
||||
if (strategyAndFCGeneratorPair != null) {
|
||||
Object a = strategyAndFCGeneratorPair[0];
|
||||
@@ -617,8 +622,8 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename, t)
|
||||
.printStackTrace();
|
||||
new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename,
|
||||
t).printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,7 +749,7 @@ public class ReloadableType {
|
||||
Class<?> class_ClassInfo = typeRegistry.getClass_ClassInfo();
|
||||
Field field_globalClassSet = class_ClassInfo.getDeclaredField("globalClassSet");
|
||||
field_globalClassSet.setAccessible(true);
|
||||
Object/*ClassInfoSet*/instance_classInfoSet = field_globalClassSet.get(null);
|
||||
Object/*ClassInfoSet*/ instance_classInfoSet = field_globalClassSet.get(null);
|
||||
Method method_ClassInfoSetRemove = instance_classInfoSet.getClass().getMethod("remove", Object.class);
|
||||
Object retval = method_ClassInfoSetRemove.invoke(instance_classInfoSet, this.clazz);
|
||||
|
||||
@@ -853,7 +858,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Gets the method corresponding to given name and descriptor, taking into consideration changes that have happened
|
||||
* by reloading.
|
||||
*
|
||||
*
|
||||
* @param name the member name
|
||||
* @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I)
|
||||
* @return the MethodMember for that name and descriptor. Null if not found on a live version, or an exception if
|
||||
@@ -870,7 +875,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Gets the method corresponding to given name and descriptor, from the original type descriptor.
|
||||
*
|
||||
*
|
||||
* @param nameAndDescriptor the method name and descriptor (e.g. foo(Ljava/lang/String;)I)
|
||||
* @return the MethodMember for the name and descriptor if it exists, otherwise null
|
||||
*/
|
||||
@@ -973,7 +978,7 @@ public class ReloadableType {
|
||||
RewriteClassAdaptor rca = (RewriteClassAdaptor) cv;
|
||||
if (rca.isEnum && rca.fieldcount > GlobalConfiguration.enumLimit) {
|
||||
// that is too many fields, marking this as not reloadable
|
||||
// TODO ...
|
||||
// TODO ...
|
||||
}
|
||||
TypeRewriter.RewriteClassAdaptor a = (TypeRewriter.RewriteClassAdaptor) rca.getClassVisitor();
|
||||
return ((ClassWriter) a.getClassVisitor()).toByteArray();
|
||||
@@ -1028,7 +1033,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Check if the specified method is different to the original form from the type as loaded.
|
||||
*
|
||||
*
|
||||
* @param methodId the ID of the method currently executing
|
||||
* @return 0 if the method cannot have changed. 1 if the method has changed. 2 if the method has been deleted in a
|
||||
* new version.
|
||||
@@ -1116,7 +1121,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Intended to handle dynamic dispatch. This will determine the right type to handle the specified method and return
|
||||
* a dispatcher that can handle it.
|
||||
*
|
||||
*
|
||||
* @param instance the target instance for the invocation
|
||||
* @param nameAndDescriptor an encoded method name and descriptor, e.g. foo(Ljava/langString;)V
|
||||
* @return a dispatcher that can handle the method indicated
|
||||
@@ -1255,7 +1260,7 @@ public class ReloadableType {
|
||||
* the non-reloadable types. This method also avoids interfaces because it is looking for instance fields. This is
|
||||
* slightly naughty but if we assume the code we are reloading is valid code, it should never be referring to
|
||||
* interface fields.
|
||||
*
|
||||
*
|
||||
* @param name the name of the field to locate
|
||||
* @return the FieldMember or null if the field is not found
|
||||
*/
|
||||
@@ -1283,7 +1288,7 @@ public class ReloadableType {
|
||||
* Search for a static field from this type upwards, as far as the topmost reloadable types. This is searching for a
|
||||
* field, it is not checking the result. It is up to the caller to check they have not ended up with an instance
|
||||
* field and throw the appropriate exception.
|
||||
*
|
||||
*
|
||||
* @param name the name of the field to look for
|
||||
* @return a FieldMember for the named field or null if not found
|
||||
*/
|
||||
@@ -1320,7 +1325,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Attempt to set the value of a field on an instance to the specified value.
|
||||
*
|
||||
*
|
||||
* @param instance the object upon which to set the field (maybe null for static fields)
|
||||
* @param fieldname the name of the field
|
||||
* @param isStatic whether the field is static
|
||||
@@ -1349,7 +1354,8 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
|
||||
private Set<WeakReference<Object>> liveInstances = Collections.synchronizedSet(new HashSet<WeakReference<Object>>());
|
||||
private Set<WeakReference<Object>> liveInstances = Collections.synchronizedSet(
|
||||
new HashSet<WeakReference<Object>>());
|
||||
|
||||
private ReferenceQueue<Object> liveInstancesRQ = new ReferenceQueue<Object>();
|
||||
|
||||
@@ -1361,7 +1367,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Attempt to set the value of a field on an instance to the specified value. Simply locate the field, which returns
|
||||
* an object capable of reading/writing it, then use that to retrieve the value.
|
||||
*
|
||||
*
|
||||
* @param instance the object upon which to set the field (maybe null for static fields)
|
||||
* @param fieldname the name of the field
|
||||
* @param isStatic whether the field is static or not
|
||||
@@ -1607,7 +1613,7 @@ public class ReloadableType {
|
||||
* Return the ReloadableType representing the superclass of this type. If the supertype is not reloadable, this
|
||||
* method will return null. The ReloadableType that is returned may not be within the same type registry, if the
|
||||
* supertype was loaded by a different classloader.
|
||||
*
|
||||
*
|
||||
* @return the ReloadableType for the supertype or null if it is not reloadable
|
||||
*/
|
||||
public ReloadableType getSuperRtype() {
|
||||
|
||||
@@ -26,11 +26,14 @@ import java.lang.reflect.Method;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.CurrentLiveVersion;
|
||||
import org.springsource.loaded.MethodMember;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
/**
|
||||
* This class encapsulates dependencies on Java 8 APIs (e.g. LambdaMetafactory).
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 1.2
|
||||
*/
|
||||
@@ -38,30 +41,30 @@ public class Java8 {
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
*
|
||||
*
|
||||
* Useful to have an example of how this code behaves. Here is a bit of code:
|
||||
*
|
||||
*
|
||||
* class basic.LambdaA { interface Foo { int m(); } static int run() { Foo f = null; f = () -> 77; return f.m(); } }
|
||||
*
|
||||
*
|
||||
* Here is a bootstrap method entry in the constant pool:
|
||||
*
|
||||
*
|
||||
* 0: #31 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:
|
||||
* (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/
|
||||
* lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;
|
||||
* Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #32
|
||||
* ()I #33 invokestatic basic/LambdaA.lambda$run$0:()I #32 ()I
|
||||
*
|
||||
*
|
||||
* At the invokedynamic site: bsmId = 0 nameAndDescriptor = m()Lbasic/LambdaA$Foo;
|
||||
*
|
||||
*
|
||||
* When invoking the metafactory bootstrap method the first two parameters are stacked by the VM automatically,
|
||||
* namely the MethodHandles$Lookup instance (caller) and the first String (invokedName). What the VM actually sees
|
||||
* is this:
|
||||
*
|
||||
*
|
||||
* metaFactory parameters: 0:MethodHandles$Lookup caller = basic.LambdaA 1:String invokedName = "m" 2:MethodType
|
||||
* invokedType = "()Foo" 3:MethodType samMethodType = "()int" 4:MethodHandle implMethod = (actually a
|
||||
* DirectMethodHandle where memberName is "basic.LambdaA.lambda$run$0()int/invokeStatic") 5:MethodType
|
||||
* instantiatedMethodType = "()int"
|
||||
*
|
||||
*
|
||||
* With all that information then the calls in this case are relatively straightforward: CallSite callsite =
|
||||
* LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
|
||||
* instantiatedMethodType); callsite.dynamicInvoker().invokeWithArguments((Object[])null);
|
||||
@@ -70,7 +73,7 @@ public class Java8 {
|
||||
/**
|
||||
* Programmatic emulation of INVOKEDYNAMIC so initialize the callsite via use of the bootstrap method then invoke
|
||||
* the result.
|
||||
*
|
||||
*
|
||||
* @param executorClass the executor that will contain the lambda function, null if not yet reloaded
|
||||
* @param handle bootstrap method handle
|
||||
* @param bsmArgs bootstrap method arguments
|
||||
@@ -104,9 +107,9 @@ public class Java8 {
|
||||
MethodType invokedType = MethodType.fromMethodDescriptorString(
|
||||
indyNameAndDescriptor.substring(descriptorStart), callerLoader);
|
||||
|
||||
// Use bsmArgs to build the parameters
|
||||
// Use bsmArgs to build the parameters
|
||||
MethodType samMethodType = MethodType.fromMethodDescriptorString(
|
||||
(String) (((Type) bsmArgs[0]).getDescriptor()), callerLoader);
|
||||
(((Type) bsmArgs[0]).getDescriptor()), callerLoader);
|
||||
|
||||
Handle bsmArgsHandle = (Handle) bsmArgs[1];
|
||||
String owner = bsmArgsHandle.getOwner();
|
||||
@@ -121,7 +124,7 @@ public class Java8 {
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, implMethodType);
|
||||
break;
|
||||
case Opcodes.H_INVOKESPECIAL:
|
||||
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
|
||||
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
|
||||
// will be static with a new leading parameter.
|
||||
if (executorClass == null) {
|
||||
// TODO is final parameter here correct?
|
||||
@@ -133,15 +136,56 @@ public class Java8 {
|
||||
}
|
||||
break;
|
||||
case Opcodes.H_INVOKEVIRTUAL:
|
||||
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
|
||||
// will be static with a new leading parameter.
|
||||
if (executorClass == null) {
|
||||
// TODO when can this scenario occur? Aren't we only here if reloading has happened?
|
||||
implMethod = caller.findVirtual(caller.lookupClass(), name, implMethodType);
|
||||
// There is a possibility to 'shortcut' here. Basically we are trying to resolve a callsite reference
|
||||
// to the method that satisfies it. The easiest option is to just find the method on the originally
|
||||
// loaded version of the target class and return that. A more optimal shortcut could return the
|
||||
// method on the executor class if the target has been reloaded (effectively bypassing the method
|
||||
// on the originally loaded version since we know that it will be acting as a pass through). But this
|
||||
// opens up a can of worms related to visibility. The executor is loaded into the child classloader,
|
||||
// and if the caller has not been reloaded it will not be able to 'see' the executor (since it is in
|
||||
// a child classloader). So, basically keep this dumb (but reliable) for now.
|
||||
|
||||
TypeRegistry typeRegistry = rtype.getTypeRegistry();
|
||||
ReloadableType ownerRType = typeRegistry.getReloadableType(owner);
|
||||
if (null == ownerRType || !ownerRType.hasBeenReloaded()) {
|
||||
// target containing the reference/lambdaMethod has not been reloaded, no need to get over
|
||||
// complicated.
|
||||
Class<?> ownerClazz = ownerRType.getClazz();
|
||||
implMethod = caller.findVirtual(ownerClazz, name, implMethodType);
|
||||
}
|
||||
else {
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString(
|
||||
"(L" + owner + ";" + descriptor.substring(1), callerLoader));
|
||||
MethodMember targetReferenceMethodMember = ownerRType.getCurrentMethod(name, descriptor);
|
||||
String targetReferenceDescriptor = targetReferenceMethodMember.getDescriptor();
|
||||
MethodType targetReferenceMethodType = MethodType.fromMethodDescriptorString(
|
||||
targetReferenceDescriptor, callerLoader);
|
||||
Class<?> targetReferenceClass = ownerRType.getClazz();
|
||||
MethodMember currentMethod = ownerRType.getCurrentMethod(name, descriptor);
|
||||
|
||||
if (currentMethod.original == null) {
|
||||
// null means this method did not exist on the original version of the target.
|
||||
// Assert that the caller must have been reloaded, otherwise how would it
|
||||
// have a reference to something that did not exist on the first version of the type. In that
|
||||
// case we know we can return the method on the executor class because both the reloaded
|
||||
// caller and reloaded target are in the same child classloader (no visibility problem).
|
||||
if (!rtype.hasBeenReloaded()) {
|
||||
throw new IllegalStateException(
|
||||
"Assertion violated: When a method added on reload is being referenced"
|
||||
+ "in target type '" + ownerRType.getName()
|
||||
+ "', expected the caller to also have been reloaded: '"
|
||||
+ rtype.getName() + "'");
|
||||
}
|
||||
CurrentLiveVersion ownerLiveVersion = ownerRType.getLiveVersion();
|
||||
Class<?> ownerExecutorClass = ownerLiveVersion.getExecutorClass();
|
||||
Method executorMethod = ownerLiveVersion.getExecutorMethod(currentMethod);
|
||||
String methodDescriptor = Type.getType(executorMethod).getDescriptor();
|
||||
MethodType type = MethodType.fromMethodDescriptorString(methodDescriptor, callerLoader);
|
||||
implMethod = caller.findStatic(ownerExecutorClass, name, type);
|
||||
}
|
||||
else {
|
||||
// This finds the reference method on the originally loaded class. It will pass through
|
||||
// to the actual code on the reloaded version.
|
||||
implMethod = caller.findVirtual(targetReferenceClass, name, targetReferenceMethodType);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Opcodes.H_INVOKEINTERFACE:
|
||||
@@ -150,7 +194,7 @@ public class Java8 {
|
||||
// TODO Should there not be a more direct way to this than classloading?
|
||||
// TODO What about when this is a method added to the interface on a reload? It won't really exist, should we point
|
||||
// to the executor? or something else? (maybe just directly the real method that will satisfy the interface - if it can be worked out)
|
||||
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo
|
||||
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo
|
||||
implMethod = caller.findVirtual(interfaceClass, name, implMethodType);
|
||||
break;
|
||||
default:
|
||||
@@ -158,16 +202,17 @@ public class Java8 {
|
||||
}
|
||||
|
||||
MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString(
|
||||
(String) (((Type) bsmArgs[2]).getDescriptor()), callerLoader);
|
||||
(((Type) bsmArgs[2]).getDescriptor()), callerLoader);
|
||||
|
||||
return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
|
||||
instantiatedMethodType);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The metafactory we are enhancing is responsible for generating the anonymous classes that will call the lambda
|
||||
* methods in our type
|
||||
*
|
||||
*
|
||||
* @param bytes the class bytes for the InnerClassLambdaMetaFactory that is going to be modified
|
||||
* @return the class bytes for the modified InnerClassLambdaMetaFactory
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springsource.loaded.test.infra.Result;
|
||||
|
||||
/**
|
||||
* Test reloading of Java 8.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 1.2
|
||||
*/
|
||||
@@ -294,6 +294,116 @@ public class Java8Tests extends SpringLoadedTests {
|
||||
assertEquals("fooab", r.returnValue);
|
||||
}
|
||||
|
||||
// https://github.com/spring-projects/spring-loaded/issues/87
|
||||
@Test
|
||||
public void lambdaMethodReference() throws Exception {
|
||||
String t = "basic.LambdaM";
|
||||
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
|
||||
|
||||
byte[] sc = loadBytesForClass(t);
|
||||
ReloadableType rtype = typeRegistry.addType(t, sc);
|
||||
|
||||
Class<?> simpleClass = rtype.getClazz();
|
||||
Result r = runUnguarded(simpleClass, "run");
|
||||
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{5=test3}", r.returnValue);
|
||||
|
||||
rtype.loadNewVersion("2", retrieveRename(t, t + "2"));//, t + "2$Foo:" + t + "$Foo"));
|
||||
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{10=test3}", r.returnValue);
|
||||
|
||||
}
|
||||
|
||||
// https://github.com/spring-projects/spring-loaded/issues/87
|
||||
// This variant reloads both pieces
|
||||
@Test
|
||||
public void lambdaMethodReferenceInAnotherClass() throws Exception {
|
||||
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
|
||||
|
||||
byte[] sc = loadBytesForClass("basic.LambdaN");
|
||||
ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc);
|
||||
byte[] helperBytes = loadBytesForClass("basic.HelperN");
|
||||
ReloadableType htype = typeRegistry.addType("basic.HelperN", helperBytes);
|
||||
|
||||
Class<?> simpleClass = rtype.getClazz();
|
||||
Result r = runUnguarded(simpleClass, "run");
|
||||
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{15=test3}", r.returnValue);
|
||||
|
||||
rtype.loadNewVersion(sc);
|
||||
htype.loadNewVersion(helperBytes);
|
||||
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{15=test3}", r.returnValue);
|
||||
}
|
||||
|
||||
// This variant reloads only the caller
|
||||
@Test
|
||||
public void lambdaMethodReferenceInAnotherClass2() throws Exception {
|
||||
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
|
||||
|
||||
byte[] sc = loadBytesForClass("basic.LambdaN");
|
||||
ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc);
|
||||
byte[] helperBytes = loadBytesForClass("basic.HelperN");
|
||||
typeRegistry.addType("basic.HelperN", helperBytes);
|
||||
|
||||
Class<?> simpleClass = rtype.getClazz();
|
||||
Result r = runUnguarded(simpleClass, "run");
|
||||
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{15=test3}", r.returnValue);
|
||||
|
||||
rtype.loadNewVersion(sc);
|
||||
// htype.loadNewVersion(helperBytes); // don't reload the helper
|
||||
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{15=test3}", r.returnValue);
|
||||
}
|
||||
|
||||
// This variant reloads only the helper (target)
|
||||
@Test
|
||||
public void lambdaMethodReferenceInAnotherClass3() throws Exception {
|
||||
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
|
||||
|
||||
byte[] sc = loadBytesForClass("basic.LambdaN");
|
||||
ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc);
|
||||
byte[] helperBytes = loadBytesForClass("basic.HelperN");
|
||||
ReloadableType htype = typeRegistry.addType("basic.HelperN", helperBytes);
|
||||
|
||||
Class<?> simpleClass = rtype.getClazz();
|
||||
Result r = runUnguarded(simpleClass, "run");
|
||||
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{15=test3}", r.returnValue);
|
||||
|
||||
// rtype.loadNewVersion(sc);
|
||||
htype.loadNewVersion(helperBytes);
|
||||
|
||||
// try {
|
||||
r = runUnguarded(simpleClass, "run");
|
||||
assertEquals("{15=test3}", r.returnValue);
|
||||
// fail("did not expect that to work");
|
||||
// }
|
||||
// catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// // Caused by: java.lang.NoClassDefFoundError: basic/HelperN$$E2
|
||||
// // at basic.LambdaN$$Lambda$8/2085857771.apply(Unknown Source)
|
||||
// // at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
|
||||
// // at java.util.stream.Collectors$$Lambda$5/1521118594.accept(Unknown Source)
|
||||
// // That happens because LambdaN, which has not been reloaded, is loaded by classloader X, the computed
|
||||
// // method to satisfy the lambda is in the executor for the helper, which is in a child classloader - that
|
||||
// // is not visible from the one that loaded LambdaN.
|
||||
// // However, part of the resolution process in the Java8 handling forces LambdaN to reload, so next
|
||||
// // time we go in, the class can be seen because LambdaN$$E2 is in the same classloader. That is
|
||||
// // why when we repeat what we just did, it'll work
|
||||
// }
|
||||
// r = runUnguarded(simpleClass, "run");
|
||||
// assertEquals("{15=test3}", r.returnValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void streamWithLambda() throws Exception {
|
||||
String t = "basic.StreamA";
|
||||
|
||||
@@ -113,7 +113,8 @@ public class ReloadingJVM {
|
||||
"/bin/java -noverify -javaagent:" + agentJarLocation + " -cp " + javaclasspath + " "
|
||||
+ AGENT_OPTION_STRING +
|
||||
" " + OPTS + " "
|
||||
+ ReloadingJVMCommandProcess.class.getName(), new String[] { OPTS });
|
||||
+ ReloadingJVMCommandProcess.class.getName(),
|
||||
new String[] { OPTS });
|
||||
writer = new DataOutputStream(process.getOutputStream());
|
||||
reader = new DataInputStream(process.getInputStream());
|
||||
readerErrors = new DataInputStream(process.getErrorStream());
|
||||
@@ -172,9 +173,9 @@ public class ReloadingJVM {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder("==STDOUT==\n").append(stdout).append("\n").append("==STDERR==\n").append(
|
||||
stderr)
|
||||
.append("\n==========\n");
|
||||
StringBuilder s = new StringBuilder("==STDOUT==\n").append(stdout).append("\n").append(
|
||||
"==STDERR==\n").append(
|
||||
stderr).append("\n==========\n");
|
||||
return s.toString();
|
||||
}
|
||||
}
|
||||
@@ -261,22 +262,27 @@ public class ReloadingJVM {
|
||||
return sendAndReceive("run " + classname);
|
||||
}
|
||||
|
||||
public void copyToTestdataDirectory(String classname) {
|
||||
if (DEBUG_CLIENT_SIDE) {
|
||||
System.out.println("(client) copying class to test data directory: " + classname);
|
||||
public void copyToTestdataDirectory(String... classnames) {
|
||||
for (String classname : classnames) {
|
||||
if (DEBUG_CLIENT_SIDE) {
|
||||
System.out.println("(client) copying class to test data directory: " + classname);
|
||||
}
|
||||
String classfile = classname.replaceAll("\\.", File.separator) + ".class";
|
||||
File f = new File("../testdata/bin", classfile);
|
||||
if (!f.exists()) {
|
||||
f = new File("../testdata-groovy/bin", classfile);
|
||||
}
|
||||
if (!f.exists()) {
|
||||
f = new File("../testdata-java8/bin", classfile);
|
||||
}
|
||||
byte[] data = Utils.load(f);
|
||||
// Ensure directories exist
|
||||
int dotPos = classname.lastIndexOf(".");
|
||||
if (dotPos != -1) {
|
||||
new File(testdataDirectory, classname.substring(0, dotPos).replaceAll("\\.", File.separator)).mkdirs();
|
||||
}
|
||||
Utils.write(new File(testdataDirectory, classfile), data);
|
||||
}
|
||||
String classfile = classname.replaceAll("\\.", File.separator) + ".class";
|
||||
File f = new File("../testdata/bin", classfile);
|
||||
if (!f.exists()) {
|
||||
f = new File("../testdata-groovy/bin", classfile);
|
||||
}
|
||||
byte[] data = Utils.load(f);
|
||||
// Ensure directories exist
|
||||
int dotPos = classname.lastIndexOf(".");
|
||||
if (dotPos != -1) {
|
||||
new File(testdataDirectory, classname.substring(0, dotPos).replaceAll("\\.", File.separator)).mkdirs();
|
||||
}
|
||||
Utils.write(new File(testdataDirectory, classfile), data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,7 +76,7 @@ import org.springsource.loaded.test.infra.TestClassLoader;
|
||||
|
||||
/**
|
||||
* Abstract root test class containing helper functions.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -100,6 +100,9 @@ public abstract class SpringLoadedTests implements Constants {
|
||||
// TODO [java8] replace this with project dependency when Java8 is out
|
||||
protected String Java8CodeJar = findJar("../testdata-java8/build/libs", "testdata-java8");
|
||||
|
||||
protected String TestDataJava8Path = TestUtils.getPathToClasses("../testdata-java8/");
|
||||
|
||||
|
||||
protected String GroovyrtJar = "../testdata-groovy/groovy-all-1.8.6.jar";
|
||||
|
||||
protected Result result;
|
||||
@@ -111,7 +114,8 @@ public abstract class SpringLoadedTests implements Constants {
|
||||
public void setup() throws Exception {
|
||||
SpringLoadedPreProcessor.disabled = true;
|
||||
NameRegistry.reset();
|
||||
binLoader = new TestClassLoader(toURLs(TestDataPath, TestDataAspectJPath, AspectjrtJar, CodeJar, Java8CodeJar),
|
||||
binLoader = new TestClassLoader(
|
||||
toURLs(TestDataPath, TestDataAspectJPath, AspectjrtJar, CodeJar, Java8CodeJar, TestDataJava8Path),
|
||||
this.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
@@ -127,7 +131,7 @@ public abstract class SpringLoadedTests implements Constants {
|
||||
|
||||
/**
|
||||
* Convert an array of string paths to an array of URLs
|
||||
*
|
||||
*
|
||||
* @param paths the string paths
|
||||
* @return the converted URLs
|
||||
*/
|
||||
@@ -472,7 +476,7 @@ public abstract class SpringLoadedTests implements Constants {
|
||||
|
||||
/**
|
||||
* retargets are "from.this.thing:to.this.thing"
|
||||
*
|
||||
*
|
||||
* @param newName
|
||||
* @param name
|
||||
* @param retargets of the form "this.from:this.to"
|
||||
@@ -672,8 +676,8 @@ public abstract class SpringLoadedTests implements Constants {
|
||||
// if (attrs = !null) {
|
||||
// sb.append("attrs(").append(toStringAttributes(attrs)).append(") ");
|
||||
// }
|
||||
sb.append("0x").append(Integer.toHexString(fieldNode.access)).append("(")
|
||||
.append(ClassPrinter.toAccessForMember(fieldNode.access)).append(") ");
|
||||
sb.append("0x").append(Integer.toHexString(fieldNode.access)).append("(").append(
|
||||
ClassPrinter.toAccessForMember(fieldNode.access)).append(") ");
|
||||
sb.append(fieldNode.name).append(" ");
|
||||
sb.append(fieldNode.desc).append(" ");
|
||||
if (fieldNode.signature != null) {
|
||||
@@ -710,7 +714,7 @@ public abstract class SpringLoadedTests implements Constants {
|
||||
|
||||
/**
|
||||
* From asm:
|
||||
*
|
||||
*
|
||||
* The name value pairs of this annotation. Each name value pair is stored as two consecutive elements in the list.
|
||||
* The name is a {@link String}, and the value may be a {@link Byte}, {@link Boolean}, {@link Character},
|
||||
* {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double}, {@link String} or
|
||||
@@ -999,7 +1003,7 @@ public abstract class SpringLoadedTests implements Constants {
|
||||
|
||||
/**
|
||||
* Create a type registry, configure it with the specified reloadable type/packages and return it.
|
||||
*
|
||||
*
|
||||
* @return new TypeRegistry
|
||||
*/
|
||||
protected TypeRegistry getTypeRegistry(String includePatterns) {
|
||||
|
||||
@@ -76,6 +76,110 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests {
|
||||
assertStdout("Hello World!\n", output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReferenceInstanceMethodOfObject() throws Exception {
|
||||
JVMOutput jo = null;
|
||||
|
||||
jvm.copyToTestdataDirectory("basic.LambdaL");
|
||||
jvm.copyToTestdataDirectory("basic.LambdaL$Foo");
|
||||
|
||||
// jvm.newInstance("l", "basic.LambdaL", true);
|
||||
|
||||
jo = jvm.run("basic.LambdaL");
|
||||
|
||||
// Total output:
|
||||
// original static initializer
|
||||
// original instance
|
||||
// in first foo
|
||||
// fooa
|
||||
assertStdoutContains("in first foo", jo);
|
||||
assertStdoutContains("fooa", jo);
|
||||
|
||||
jvm.updateClass("basic.LambdaL", loadBytesForClass("basic.LambdaL"));
|
||||
pause(2);
|
||||
|
||||
// Run the same thing as before:
|
||||
jo = jvm.run("basic.LambdaL");
|
||||
assertStdoutContains("in first foo", jo);
|
||||
assertStdoutContains("fooa", jo);
|
||||
|
||||
// New version: Foo interface has one method argument
|
||||
jvm.updateClass("basic.LambdaL$Foo",
|
||||
retrieveRename("basic.LambdaL$Foo", "basic.LambdaL2$Foo"));
|
||||
waitForReloadToOccur();
|
||||
|
||||
jvm.updateClass("basic.LambdaL",
|
||||
retrieveRename("basic.LambdaL", "basic.LambdaL2", "basic.LambdaL2$Foo:basic.LambdaL$Foo"));
|
||||
waitForReloadToOccur();
|
||||
|
||||
// Run the new version
|
||||
jo = jvm.run("basic.LambdaL");
|
||||
assertStdoutContains("in second foo", jo);
|
||||
assertStdoutContains("fooab", jo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaticMethodReference() throws Exception {
|
||||
JVMOutput jo = null;
|
||||
|
||||
jvm.copyToTestdataDirectory("basic.StaticMethodReference", "basic.StaticMethodReference$Foo",
|
||||
"basic.StaticMethodReference$Bar");
|
||||
jo = jvm.run("basic.StaticMethodReference");
|
||||
assertStdoutContains("in 1st static Method", jo);
|
||||
assertStdoutContains("staticsa", jo);
|
||||
|
||||
// Reload itself
|
||||
jvm.updateClass("basic.StaticMethodReference", loadBytesForClass("basic.StaticMethodReference"));
|
||||
waitForReloadToOccur();
|
||||
|
||||
jo = jvm.run("basic.StaticMethodReference");
|
||||
assertStdoutContains("in 1st static Method", jo);
|
||||
assertStdoutContains("staticsa", jo);
|
||||
|
||||
jvm.updateClass("basic.StaticMethodReference$Foo",
|
||||
retrieveRename("basic.StaticMethodReference$Foo", "basic.StaticMethodReference2$Foo"));
|
||||
jvm.updateClass("basic.StaticMethodReference$Bar",
|
||||
retrieveRename("basic.StaticMethodReference$Bar", "basic.StaticMethodReference2$Bar"));
|
||||
jvm.updateClass("basic.StaticMethodReference",
|
||||
retrieveRename("basic.StaticMethodReference", "basic.StaticMethodReference2",
|
||||
"basic.StaticMethodReference2$Foo:basic.StaticMethodReference$Foo",
|
||||
"basic.StaticMethodReference2$Bar:basic.StaticMethodReference$Bar"));
|
||||
waitForReloadToOccur();
|
||||
|
||||
jo = jvm.run("basic.StaticMethodReference");
|
||||
assertStdoutContains("in 2nd static Method", jo);
|
||||
assertStdoutContains("staticsasb", jo);
|
||||
|
||||
// // New version: Foo interface has one method argument
|
||||
// jvm.updateClass("basic.LambdaL$Foo",
|
||||
// retrieveRename("basic.LambdaL$Foo", "basic.LambdaL2$Foo"));
|
||||
// waitForReloadToOccur();
|
||||
//
|
||||
// jvm.updateClass("basic.LambdaL",
|
||||
// retrieveRename("basic.LambdaL", "basic.LambdaL2", "basic.LambdaL2$Foo:basic.LambdaL$Foo"));
|
||||
// waitForReloadToOccur();
|
||||
//
|
||||
// // Run the new version
|
||||
// jo = jvm.run("basic.LambdaL");
|
||||
// assertStdoutContains("in second foo", jo);
|
||||
// assertStdoutContains("fooab", jo);
|
||||
// compile("/original/", "/original/basic/StaticMethodReference.java.file");
|
||||
//+ JVMOutput output = jvm.sendAndReceive("run basic.StaticMethodReference");
|
||||
//+
|
||||
//+ assertStdoutContains("in 1st static Method", output);
|
||||
//+ assertStdoutContains("staticsa", output);
|
||||
//+
|
||||
//+ compile("/modified/", "/modified/basic/StaticMethodReference.java.file");
|
||||
//+ jvm.reload("basic.StaticMethodReference");
|
||||
//+ waitForReloadToOccur();
|
||||
//+
|
||||
//+ output = jvm.sendAndReceive("run basic.StaticMethodReference");
|
||||
//+ assertStdoutContains("in 2nd static Method", output);
|
||||
//+ assertStdoutContains("staticsasb", output);
|
||||
//+ }
|
||||
//
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serialization() throws Exception {
|
||||
jvm.copyToTestdataDirectory("remote.Serialize");
|
||||
@@ -146,7 +250,8 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests {
|
||||
|
||||
@Test
|
||||
public void testCreatingAndInvokingMethodsOnInstance() throws Exception {
|
||||
assertStderrContains("creating new instance 'a' of type 'jvmtwo.Runner'", jvm.newInstance("a", "jvmtwo.Runner"));
|
||||
assertStderrContains("creating new instance 'a' of type 'jvmtwo.Runner'",
|
||||
jvm.newInstance("a", "jvmtwo.Runner"));
|
||||
assertStdout("jvmtwo.Runner.run1() running", jvm.call("a", "run1"));
|
||||
}
|
||||
|
||||
|
||||
34
testdata-java8/src/main/java/basic/LambdaL.java
Normal file
34
testdata-java8/src/main/java/basic/LambdaL.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package basic;
|
||||
|
||||
public class LambdaL {
|
||||
static {
|
||||
System.out.println("original static initializer");
|
||||
}
|
||||
|
||||
public LambdaL() {
|
||||
System.out.println("original instance");
|
||||
}
|
||||
|
||||
public interface Foo { String m(String s); }
|
||||
|
||||
public String getFoo(String s) {
|
||||
System.out.println("in first foo");
|
||||
return "foo"+s;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
String res= new LambdaL().run2();
|
||||
System.out.println(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
public String run2() {
|
||||
Foo f = this::getFoo;
|
||||
return f.m("a");
|
||||
}
|
||||
|
||||
}
|
||||
27
testdata-java8/src/main/java/basic/LambdaL2.java
Normal file
27
testdata-java8/src/main/java/basic/LambdaL2.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package basic;
|
||||
|
||||
public class LambdaL2 {
|
||||
|
||||
public interface Foo { String m(String s, String t); }
|
||||
|
||||
public String getFoo(String s, String t) {
|
||||
System.out.println("in second foo");
|
||||
return "foo"+s+t;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
String res= new LambdaL2().run2();
|
||||
System.out.println(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
public String run2() {
|
||||
Foo f = this::getFoo;
|
||||
return f.m("a","b");
|
||||
}
|
||||
|
||||
}
|
||||
32
testdata-java8/src/main/java/basic/LambdaM.java
Normal file
32
testdata-java8/src/main/java/basic/LambdaM.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package basic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LambdaM {
|
||||
|
||||
// public interface Foo { String m(String s); }
|
||||
|
||||
// public String getFoo(String s) {
|
||||
// return "foo"+s;
|
||||
// }
|
||||
|
||||
public static void main(String[] args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
return new LambdaM().run2();
|
||||
}
|
||||
|
||||
public String run2() {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("test3");
|
||||
Map m = list.stream().collect(Collectors.toMap(String::length, Function.identity()));
|
||||
return m.toString();
|
||||
}
|
||||
|
||||
}
|
||||
30
testdata-java8/src/main/java/basic/LambdaM2.java
Normal file
30
testdata-java8/src/main/java/basic/LambdaM2.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package basic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LambdaM2 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
return new LambdaM2().run2();
|
||||
}
|
||||
|
||||
public String run2() {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("test3");
|
||||
Map m = list.stream().collect(Collectors.toMap(this::foo, Function.identity()));
|
||||
return m.toString();
|
||||
}
|
||||
|
||||
public int foo(String input) {
|
||||
return input.length()*2;
|
||||
}
|
||||
|
||||
}
|
||||
35
testdata-java8/src/main/java/basic/LambdaN.java
Normal file
35
testdata-java8/src/main/java/basic/LambdaN.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package basic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LambdaN {
|
||||
|
||||
public static void main(String[] args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
return new LambdaN().run2();
|
||||
}
|
||||
|
||||
public String run2() {
|
||||
List<String> list = new ArrayList<>();
|
||||
HelperN h = new HelperN();
|
||||
list.add("test3");
|
||||
Map m = list.stream().collect(Collectors.toMap(h::foo, Function.identity()));
|
||||
return m.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class HelperN {
|
||||
public int foo(String input) {
|
||||
return input.length()*3;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package basic;
|
||||
|
||||
public class StaticMethodReference {
|
||||
|
||||
public static String staticMethod(String s1) {
|
||||
System.out.println("in 1st static Method");
|
||||
return "static" + s1;
|
||||
}
|
||||
|
||||
public interface Bar { String sm(String s);}
|
||||
|
||||
public interface Foo { String m(String s); }
|
||||
|
||||
public String getFoo(String s) {
|
||||
return "foo"+s;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
StaticMethodReference l = new StaticMethodReference();
|
||||
String r = l.run3();
|
||||
System.out.println(r);
|
||||
return l.run2();
|
||||
}
|
||||
|
||||
public String run2() {
|
||||
Foo f = this::getFoo;
|
||||
return f.m("a");
|
||||
}
|
||||
|
||||
public String run3() {
|
||||
Bar b = StaticMethodReference::staticMethod;
|
||||
return b.sm("sa");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package basic;
|
||||
|
||||
public class StaticMethodReference2 {
|
||||
|
||||
public static String staticMethod(String s1, String s2) {
|
||||
System.out.println("in 2nd static Method");
|
||||
return "static" + s1 + s2;
|
||||
}
|
||||
|
||||
public interface Bar { String sm(String s, String s1);}
|
||||
|
||||
public interface Foo { String m(String s); }
|
||||
|
||||
public String getFoo(String s) {
|
||||
return "foo"+s;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
run();
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
StaticMethodReference2 l = new StaticMethodReference2();
|
||||
String r = l.run3();
|
||||
System.out.println(r);
|
||||
return l.run2();
|
||||
}
|
||||
|
||||
public String run2() {
|
||||
Foo f = this::getFoo;
|
||||
return f.m("a");
|
||||
}
|
||||
|
||||
public String run3() {
|
||||
Bar b = StaticMethodReference2::staticMethod;
|
||||
return b.sm("sa", "sb");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user