Browse Source
We now provide MethodsMetadataReader to read method metadata from a class file. MethodMetadata is read for all user-declared methods except for constructors (which are technically methods, too).
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory();
MethodsMetadataReader metadataReader = factory.getMetadataReader("com.acme.Foo");
MethodsMetadata metadata = metadataReader.getMethodsMetadata();
This new API is now used by DefaultProjectionInformation to make sure the order of input properties is based on the declaration order in the projection interfaces. Previously that order could not be guaranteed to be stable.
Original pull request: #263.
pull/269/head
11 changed files with 688 additions and 6 deletions
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.type; |
||||
|
||||
import java.util.Set; |
||||
|
||||
import org.springframework.core.type.ClassMetadata; |
||||
import org.springframework.core.type.MethodMetadata; |
||||
|
||||
/** |
||||
* Interface that defines abstract metadata of a specific class, in a form that does not require that class to be loaded |
||||
* yet. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 2.1 |
||||
* @see MethodMetadata |
||||
* @see ClassMetadata |
||||
* @see MethodsMetadataReader#getMethodsMetadata() |
||||
*/ |
||||
public interface MethodsMetadata extends ClassMetadata { |
||||
|
||||
/** |
||||
* Return all methods. |
||||
* |
||||
* @return the methods declared in the class ordered as found in the class file. Order does not necessarily reflect |
||||
* the declaration order in the source file. |
||||
*/ |
||||
Set<MethodMetadata> getMethods(); |
||||
|
||||
/** |
||||
* Return all methods matching method {@code name}. |
||||
* |
||||
* @param name name of the method, must not be {@literal null} or empty. |
||||
* @return the methods matching method {@code name } declared in the class ordered as found in the class file. Order |
||||
* does not necessarily reflect the declaration order in the source file. |
||||
*/ |
||||
Set<MethodMetadata> getMethods(String name); |
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.type; |
||||
|
||||
import org.springframework.core.type.classreading.MetadataReader; |
||||
|
||||
/** |
||||
* Extension to {@link MetadataReader} for accessing class metadata and method metadata as read by an ASM |
||||
* {@link org.springframework.asm.ClassReader}. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 2.1 |
||||
*/ |
||||
public interface MethodsMetadataReader extends MetadataReader { |
||||
|
||||
/** |
||||
* @return the metadata for methods in the class file. |
||||
*/ |
||||
MethodsMetadata getMethodsMetadata(); |
||||
} |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.type.classreading; |
||||
|
||||
import java.io.BufferedInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
import org.springframework.asm.ClassReader; |
||||
import org.springframework.core.NestedIOException; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.core.type.AnnotationMetadata; |
||||
import org.springframework.core.type.ClassMetadata; |
||||
import org.springframework.data.type.MethodsMetadata; |
||||
import org.springframework.data.type.MethodsMetadataReader; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* {@link MethodsMetadataReader} implementation based on an ASM {@link org.springframework.asm.ClassReader}. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 2.1 |
||||
*/ |
||||
class DefaultMethodsMetadataReader implements MethodsMetadataReader { |
||||
|
||||
private final Resource resource; |
||||
private final ClassMetadata classMetadata; |
||||
private final AnnotationMetadata annotationMetadata; |
||||
private final MethodsMetadata methodsMetadata; |
||||
|
||||
DefaultMethodsMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { |
||||
|
||||
this.resource = resource; |
||||
|
||||
ClassReader classReader; |
||||
|
||||
try (InputStream is = new BufferedInputStream(getResource().getInputStream())) { |
||||
classReader = new ClassReader(is); |
||||
} catch (IllegalArgumentException ex) { |
||||
throw new NestedIOException("ASM ClassReader failed to parse class file - " |
||||
+ "probably due to a new Java class file version that isn't supported yet: " + getResource(), ex); |
||||
} |
||||
|
||||
MethodsMetadataReadingVisitor visitor = new MethodsMetadataReadingVisitor(classLoader); |
||||
classReader.accept(visitor, ClassReader.SKIP_DEBUG); |
||||
|
||||
classMetadata = visitor; |
||||
annotationMetadata = visitor; |
||||
methodsMetadata = visitor; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.core.type.classreading.MetadataReader#getResource() |
||||
*/ |
||||
@Override |
||||
public Resource getResource() { |
||||
return resource; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.core.type.classreading.MetadataReader#getClassMetadata() |
||||
*/ |
||||
@Override |
||||
public ClassMetadata getClassMetadata() { |
||||
return classMetadata; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata() |
||||
*/ |
||||
@Override |
||||
public AnnotationMetadata getAnnotationMetadata() { |
||||
return annotationMetadata; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.util.ClassMetadataReader#getMethodsMetadata() |
||||
*/ |
||||
@Override |
||||
public MethodsMetadata getMethodsMetadata() { |
||||
return methodsMetadata; |
||||
} |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.type.classreading; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.core.io.ResourceLoader; |
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; |
||||
import org.springframework.data.type.MethodsMetadata; |
||||
import org.springframework.data.type.MethodsMetadataReader; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Extension of {@link SimpleMetadataReaderFactory} that reads {@link MethodsMetadata}, creating a new ASM |
||||
* {@link MethodsMetadataReader} for every request. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 2.1 |
||||
*/ |
||||
public class MethodsMetadataReaderFactory extends SimpleMetadataReaderFactory { |
||||
|
||||
/** |
||||
* Create a new {@link MethodsMetadataReaderFactory} for the default class loader. |
||||
*/ |
||||
public MethodsMetadataReaderFactory() {} |
||||
|
||||
/** |
||||
* Create a new {@link MethodsMetadataReaderFactory} for the given {@link ResourceLoader}. |
||||
* |
||||
* @param resourceLoader the Spring {@link ResourceLoader} to use (also determines the {@link ClassLoader} to use). |
||||
*/ |
||||
public MethodsMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { |
||||
super(resourceLoader); |
||||
} |
||||
|
||||
/** |
||||
* Create a new {@link MethodsMetadataReaderFactory} for the given {@link ClassLoader}. |
||||
* |
||||
* @param classLoader the class loader to use. |
||||
*/ |
||||
public MethodsMetadataReaderFactory(@Nullable ClassLoader classLoader) { |
||||
super(classLoader); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.core.type.classreading.SimpleMetadataReaderFactory#getMetadataReader(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public MethodsMetadataReader getMetadataReader(String className) throws IOException { |
||||
return (MethodsMetadataReader) super.getMetadataReader(className); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.core.type.classreading.SimpleMetadataReaderFactory#getMetadataReader(org.springframework.core.io.Resource) |
||||
*/ |
||||
@Override |
||||
public MethodsMetadataReader getMetadataReader(Resource resource) throws IOException { |
||||
return new DefaultMethodsMetadataReader(resource, getResourceLoader().getClassLoader()); |
||||
} |
||||
} |
||||
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.type.classreading; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.asm.MethodVisitor; |
||||
import org.springframework.asm.Opcodes; |
||||
import org.springframework.asm.Type; |
||||
import org.springframework.core.type.ClassMetadata; |
||||
import org.springframework.core.type.MethodMetadata; |
||||
import org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor; |
||||
import org.springframework.core.type.classreading.MethodMetadataReadingVisitor; |
||||
import org.springframework.data.type.MethodsMetadata; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* ASM class visitor which looks for the class name and implemented types as well as for the methods defined in the |
||||
* class, exposing them through the {@link MethodsMetadata} interface. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 2.1 |
||||
* @see ClassMetadata |
||||
* @see MethodMetadata |
||||
* @see MethodMetadataReadingVisitor |
||||
*/ |
||||
class MethodsMetadataReadingVisitor extends AnnotationMetadataReadingVisitor implements MethodsMetadata { |
||||
|
||||
/** |
||||
* Construct a new {@link MethodsMetadataReadingVisitor} given {@link ClassLoader}. |
||||
* |
||||
* @param classLoader may be {@literal null}. |
||||
*/ |
||||
MethodsMetadataReadingVisitor(@Nullable ClassLoader classLoader) { |
||||
super(classLoader); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[]) |
||||
*/ |
||||
@Override |
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
||||
|
||||
// Skip bridge methods - we're only interested in original user methods.
|
||||
// On JDK 8, we'd otherwise run into double detection of the same method...
|
||||
if ((access & Opcodes.ACC_BRIDGE) != 0) { |
||||
return super.visitMethod(access, name, desc, signature, exceptions); |
||||
} |
||||
|
||||
// Skip constructors
|
||||
if (name.equals("<init>")) { |
||||
return super.visitMethod(access, name, desc, signature, exceptions); |
||||
} |
||||
|
||||
MethodMetadataReadingVisitor visitor = new MethodMetadataReadingVisitor(name, access, getClassName(), |
||||
Type.getReturnType(desc).getClassName(), this.classLoader, this.methodMetadataSet); |
||||
|
||||
this.methodMetadataSet.add(visitor); |
||||
return visitor; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.util.MethodsMetadata#getMethods() |
||||
*/ |
||||
@Override |
||||
public Set<MethodMetadata> getMethods() { |
||||
return Collections.unmodifiableSet(methodMetadataSet); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.util.MethodsMetadata#getMethods(String) |
||||
*/ |
||||
@Override |
||||
public Set<MethodMetadata> getMethods(String name) { |
||||
|
||||
Assert.hasText(name, "Method name must not be null or empty"); |
||||
|
||||
Set<MethodMetadata> result = new LinkedHashSet<>(4); |
||||
|
||||
for (MethodMetadata metadata : methodMetadataSet) { |
||||
if (metadata.getMethodName().equals(name)) { |
||||
result.add(metadata); |
||||
} |
||||
} |
||||
|
||||
return Collections.unmodifiableSet(result); |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
/** |
||||
* Support classes for reading annotation and class-level metadata. |
||||
*/ |
||||
@org.springframework.lang.NonNullApi |
||||
@org.springframework.lang.NonNullFields |
||||
package org.springframework.data.type.classreading; |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
/** |
||||
* Core support package for type introspection. |
||||
*/ |
||||
@org.springframework.lang.NonNullApi |
||||
package org.springframework.data.type; |
||||
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.type; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import java.io.FileNotFoundException; |
||||
import java.io.IOException; |
||||
import java.net.URL; |
||||
import java.net.URLClassLoader; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.core.io.DefaultResourceLoader; |
||||
import org.springframework.data.type.classreading.MethodsMetadataReaderFactory; |
||||
|
||||
/** |
||||
* Unit tests for {@link MethodsMetadataReaderFactory}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class MethodsMetadataReaderFactoryUnitTests { |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldReadFromDefaultClassLoader() throws IOException { |
||||
|
||||
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(); |
||||
MethodsMetadataReader reader = factory.getMetadataReader(getClass().getName()); |
||||
|
||||
assertThat(reader).isNotNull(); |
||||
} |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldReadFromClassLoader() throws IOException { |
||||
|
||||
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(getClass().getClassLoader()); |
||||
MethodsMetadataReader reader = factory.getMetadataReader(getClass().getName()); |
||||
|
||||
assertThat(reader).isNotNull(); |
||||
} |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldNotFindClass() { |
||||
|
||||
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(new URLClassLoader(new URL[0], null)); |
||||
|
||||
assertThatThrownBy(() -> factory.getMetadataReader(getClass().getName())).isInstanceOf(FileNotFoundException.class); |
||||
} |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldReadFromResourceLoader() throws IOException { |
||||
|
||||
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(new DefaultResourceLoader()); |
||||
MethodsMetadataReader reader = factory.getMetadataReader(getClass().getName()); |
||||
|
||||
assertThat(reader).isNotNull(); |
||||
} |
||||
} |
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.type.classreading; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Iterator; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.core.type.MethodMetadata; |
||||
import org.springframework.data.type.MethodsMetadata; |
||||
import org.springframework.data.type.MethodsMetadataReader; |
||||
|
||||
/** |
||||
* Unit tests for {@link DefaultMethodsMetadataReader}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class DefaultMethodsMetadataReaderUnitTests { |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldReadClassMethods() throws IOException { |
||||
|
||||
MethodsMetadata metadata = getMethodsMetadata(Foo.class); |
||||
|
||||
assertThat(metadata.getMethods()).hasSize(3); |
||||
|
||||
Iterator<MethodMetadata> iterator = metadata.getMethods().iterator(); |
||||
|
||||
assertThat(iterator.next().getMethodName()).isEqualTo("one"); |
||||
assertThat(iterator.next().getMethodName()).isEqualTo("two"); |
||||
assertThat(iterator.next().getMethodName()).isEqualTo("three"); |
||||
} |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldReadInterfaceMethods() throws IOException { |
||||
|
||||
MethodsMetadata metadata = getMethodsMetadata(Baz.class); |
||||
|
||||
assertThat(metadata.getMethods()).hasSize(3); |
||||
|
||||
Iterator<MethodMetadata> iterator = metadata.getMethods().iterator(); |
||||
|
||||
assertThat(iterator.next().getMethodName()).isEqualTo("one"); |
||||
assertThat(iterator.next().getMethodName()).isEqualTo("two"); |
||||
assertThat(iterator.next().getMethodName()).isEqualTo("three"); |
||||
} |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldMetadata() throws IOException { |
||||
|
||||
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(); |
||||
MethodsMetadataReader metadataReader = factory.getMetadataReader(getClass().getName()); |
||||
|
||||
assertThat(metadataReader.getClassMetadata()).isNotNull(); |
||||
assertThat(metadataReader.getAnnotationMetadata()).isNotNull(); |
||||
} |
||||
|
||||
@Test // DATACMNS-1206
|
||||
public void shouldReturnMethodMetadataByName() throws IOException { |
||||
|
||||
MethodsMetadata metadata = getMethodsMetadata(Foo.class); |
||||
|
||||
assertThat(metadata.getMethods()).hasSize(3); |
||||
|
||||
assertThat(metadata.getMethods("one")).extracting(MethodMetadata::getMethodName).contains("one"); |
||||
assertThat(metadata.getMethods("foo")).isEmpty(); |
||||
} |
||||
|
||||
private static MethodsMetadata getMethodsMetadata(Class<?> classToIntrospect) throws IOException { |
||||
|
||||
MethodsMetadataReaderFactory factory = new MethodsMetadataReaderFactory(); |
||||
MethodsMetadataReader metadataReader = factory.getMetadataReader(classToIntrospect.getName()); |
||||
return metadataReader.getMethodsMetadata(); |
||||
} |
||||
|
||||
// Create a scenario with a cyclic dependency to mix up methods reported by class.getDeclaredMethods()
|
||||
// That's not exactly deterministic because it depends on when the compiler sees the classes.
|
||||
abstract class Foo { |
||||
|
||||
abstract void one(Foo b); |
||||
|
||||
abstract void two(Bar b); |
||||
|
||||
abstract void three(Foo b); |
||||
} |
||||
|
||||
interface Baz { |
||||
|
||||
void one(Foo b); |
||||
|
||||
void two(Bar b); |
||||
|
||||
void three(Baz b); |
||||
} |
||||
|
||||
abstract class Bar { |
||||
|
||||
abstract void dependOnFoo(Foo f); |
||||
|
||||
abstract void dependOnBaz(Baz f); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue