This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch endpoint-dsl in repository https://gitbox.apache.org/repos/asf/camel.git
commit 5c684ea3774a5c2a0cc8388bb19a4d83ae180977 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Wed Jun 5 10:50:26 2019 +0200 First pass at a generated endpoint DSL --- components/camel-blueprint/pom.xml | 1 + components/camel-spring/pom.xml | 1 + .../camel/component/web3j/Web3jConfiguration.java | 2 +- components/pom.xml | 1 + .../model/endpoint/EndpointConfiguration.java | 5 + .../camel/maven/packaging/EndpointDslMojo.java | 631 +++++++++++++++ .../camel/maven/packaging/generics/ClassUtil.java | 218 +++++ .../maven/packaging/generics/GenericsUtil.java | 880 +++++++++++++++++++++ .../generics/OwbGenericArrayTypeImpl.java | 64 ++ .../generics/OwbParametrizedTypeImpl.java | 125 +++ .../packaging/generics/OwbTypeVariableImpl.java | 182 +++++ .../packaging/generics/OwbWildcardTypeImpl.java | 82 ++ .../maven/packaging/model/EndpointOptionModel.java | 9 + .../camel/maven/packaging/srcgen/GenericType.java | 2 +- .../camel/maven/packaging/srcgen/JavaClass.java | 39 +- 15 files changed, 2237 insertions(+), 5 deletions(-) diff --git a/components/camel-blueprint/pom.xml b/components/camel-blueprint/pom.xml index 3179463..cd197ec 100644 --- a/components/camel-blueprint/pom.xml +++ b/components/camel-blueprint/pom.xml @@ -357,6 +357,7 @@ <include name="org/apache/camel/LoggingLevel.java"/> <include name="org/apache/camel/ManagementStatisticsLevel.java"/> <include name="**/package-info.java"/> + <exclude name="org/apache/camel/model/endpoint/*.java"/> </fileset> <fileset dir="${basedir}/../../core/camel-util/src/main/java"> <include diff --git a/components/camel-spring/pom.xml b/components/camel-spring/pom.xml index 2a873b7..8ba2a98 100644 --- a/components/camel-spring/pom.xml +++ b/components/camel-spring/pom.xml @@ -386,6 +386,7 @@ <include name="org/apache/camel/ShutdownRunningTask.java"/> <include name="org/apache/camel/WaitForTaskToComplete.java"/> <include name="org/apache/camel/package-info.java"/> + <exclude name="org/apache/camel/model/endpoint/*.java"/> </fileset> <fileset dir="${basedir}/../../core/camel-util/src/main/java"> <include name="org/apache/camel/concurrent/ThreadPoolRejectedPolicy.java"/> diff --git a/components/camel-web3j/src/main/java/org/apache/camel/component/web3j/Web3jConfiguration.java b/components/camel-web3j/src/main/java/org/apache/camel/component/web3j/Web3jConfiguration.java index 70db1be..d6a901e 100644 --- a/components/camel-web3j/src/main/java/org/apache/camel/component/web3j/Web3jConfiguration.java +++ b/components/camel-web3j/src/main/java/org/apache/camel/component/web3j/Web3jConfiguration.java @@ -48,7 +48,7 @@ public class Web3jConfiguration implements Cloneable { @UriParam(label = "producer") private String address; - @UriParam(label = "common", javaType = "String") + @UriParam(label = "common", javaType = "java.lang.String") private List<String> topics; @UriParam(label = "producer") diff --git a/components/pom.xml b/components/pom.xml index ded9f83..97da906 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -368,6 +368,7 @@ <goal>prepare-spring-boot-starter</goal> <goal>prepare-spring-boot-auto-configuration</goal> <goal>generate-legal</goal> + <goal>generate-endpoint-dsl</goal> </goals> <phase>prepare-package</phase> </execution> diff --git a/core/camel-core/src/main/java/org/apache/camel/model/endpoint/EndpointConfiguration.java b/core/camel-core/src/main/java/org/apache/camel/model/endpoint/EndpointConfiguration.java new file mode 100644 index 0000000..a8b85bf --- /dev/null +++ b/core/camel-core/src/main/java/org/apache/camel/model/endpoint/EndpointConfiguration.java @@ -0,0 +1,5 @@ +package org.apache.camel.model.endpoint; + +public class EndpointConfiguration { + +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointDslMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointDslMojo.java new file mode 100644 index 0000000..c6730e1 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointDslMojo.java @@ -0,0 +1,631 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.maven.packaging; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.annotation.Generated; + +import org.apache.camel.maven.packaging.generics.GenericsUtil; +import org.apache.camel.maven.packaging.model.ComponentModel; +import org.apache.camel.maven.packaging.model.ComponentOptionModel; +import org.apache.camel.maven.packaging.model.EndpointOptionModel; +import org.apache.camel.maven.packaging.srcgen.GenericType; +import org.apache.camel.maven.packaging.srcgen.GenericType.BoundType; +import org.apache.camel.maven.packaging.srcgen.JavaClass; +import org.apache.camel.maven.packaging.srcgen.Property; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; +import org.apache.camel.spi.UriPath; +import org.apache.camel.spi.annotations.Component; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.jboss.forge.roaster.model.util.Strings; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.springframework.boot.liquibase.SpringPackageScanClassResolver; + +import static org.apache.camel.maven.packaging.AbstractGeneratorMojo.updateResource; +import static org.apache.camel.maven.packaging.JSonSchemaHelper.getSafeValue; +import static org.apache.camel.maven.packaging.PackageHelper.findCamelCoreDirectory; +import static org.apache.camel.maven.packaging.PackageHelper.loadText; + +/** + * Generate Spring Boot auto configuration files for Camel components and data + * formats. + */ +@Mojo(name = "generate-endpoint-dsl", threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.PROCESS_CLASSES) +public class EndpointDslMojo extends AbstractMojo { + + private static final Map<String, String> PRIMITIVEMAP; + private static final Map<String, Class> PRIMITIVE_CLASSES = new HashMap<>(); + + static { + PRIMITIVEMAP = new HashMap<>(); + PRIMITIVEMAP.put("boolean", "java.lang.Boolean"); + PRIMITIVEMAP.put("char", "java.lang.Character"); + PRIMITIVEMAP.put("long", "java.lang.Long"); + PRIMITIVEMAP.put("int", "java.lang.Integer"); + PRIMITIVEMAP.put("integer", "java.lang.Integer"); + PRIMITIVEMAP.put("byte", "java.lang.Byte"); + PRIMITIVEMAP.put("short", "java.lang.Short"); + PRIMITIVEMAP.put("double", "java.lang.Double"); + PRIMITIVEMAP.put("float", "java.lang.Float"); + + PRIMITIVE_CLASSES.put("int", int.class); + PRIMITIVE_CLASSES.put("short", short.class); + PRIMITIVE_CLASSES.put("long", long.class); + PRIMITIVE_CLASSES.put("byte", byte.class); + PRIMITIVE_CLASSES.put("char", char.class); + PRIMITIVE_CLASSES.put("float", float.class); + PRIMITIVE_CLASSES.put("double", double.class); + PRIMITIVE_CLASSES.put("boolean", boolean.class); + PRIMITIVE_CLASSES.put("void", void.class); + } + + private static final String[] IGNORE_MODULES = {/* Non-standard -> */ }; + + /** + * The maven project. + */ + @Parameter(property = "project", required = true, readonly = true) + protected MavenProject project; + + /** + * The project build directory + */ + @Parameter(defaultValue = "${project.build.directory}") + protected File buildDir; + + /** + * The base directory + */ + @Parameter(defaultValue = "${basedir}") + protected File baseDir; + + DynamicClassLoader projectClassLoader; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + // Do not generate code for ignored module + if (Arrays.asList(IGNORE_MODULES).contains(project.getArtifactId())) { + getLog().info("Component auto-configuration will not be created: component contained in the ignore list"); + return; + } + + executeAll(); + } + + private void executeAll() throws MojoExecutionException, MojoFailureException { + Map<File, Supplier<String>> files = PackageHelper.findJsonFiles(buildDir, p -> p.isDirectory() || p.getName().endsWith(".json")).stream() + .collect(Collectors.toMap(Function.identity(), s -> cache(() -> loadJson(s)))); + + executeComponent(files); + } + + private static String loadJson(File file) { + try (InputStream is = new FileInputStream(file)) { + return loadText(is); + } catch (IOException e) { + throw new IOError(e); + } + } + + private static <T> Supplier<T> cache(Supplier<T> supplier) { + return new Supplier<T>() { + T value; + + @Override + public T get() { + if (value == null) { + value = supplier.get(); + } + return value; + } + }; + } + + private void executeComponent(Map<File, Supplier<String>> jsonFiles) throws MojoExecutionException, MojoFailureException { + // find the component names + Set<String> componentNames = new TreeSet<>(); + findComponentNames(buildDir, componentNames); + + // create auto configuration for the components + if (!componentNames.isEmpty()) { + getLog().debug("Found " + componentNames.size() + " components"); + + List<ComponentModel> allModels = new LinkedList<>(); + for (String componentName : componentNames) { + String json = loadComponentJson(jsonFiles, componentName); + if (json != null) { + ComponentModel model = generateComponentModel(componentName, json); + allModels.add(model); + } + } + + // Group the models by implementing classes + Map<String, List<ComponentModel>> grModels = allModels.stream().collect(Collectors.groupingBy(ComponentModel::getJavaType)); + for (String componentClass : grModels.keySet()) { + List<ComponentModel> compModels = grModels.get(componentClass); + ComponentModel model = compModels.get(0); // They should be + // equivalent + List<String> aliases = compModels.stream().map(ComponentModel::getScheme).sorted().collect(Collectors.toList()); + + String pkg = "org.apache.camel.model.endpoint"; + + String overrideComponentName = null; + if (aliases.size() > 1) { + // determine component name when there are multiple ones + overrideComponentName = model.getArtifactId().replace("camel-", ""); + } + + createEndpointDsl(pkg, model, overrideComponentName); + } + } + } + + private void createEndpointDsl(String packageName, ComponentModel model, String overrideComponentName) throws MojoFailureException { + int pos = model.getJavaType().lastIndexOf("."); + String name = model.getJavaType().substring(pos + 1); + name = name.replace("Component", "Endpoint"); + + Class<?> realComponentClass = loadClass(model.getJavaType()); + Class<?> realEndpointClass = findEndpointClass(realComponentClass); + + final JavaClass javaClass = new JavaClass(getProjectClassLoader()); + javaClass.setPackage(packageName); + javaClass.setName(name); + + Map<String, JavaClass> enumClasses = new HashMap<>(); + + JavaClass commonClass = javaClass.addNestedType().setPublic().setStatic(true); + commonClass.setName(name.replace("Endpoint", "Common")); + commonClass.extendSuperType("EndpointConfiguration"); + + JavaClass consumerClass = javaClass.addNestedType().setPublic().setStatic(true); + consumerClass.setName(name.replace("Endpoint", "Consumer")); + consumerClass.extendSuperType(name.replace("Endpoint", "Common")); + + JavaClass producerClass = javaClass.addNestedType().setPublic().setStatic(true); + producerClass.setName(name.replace("Endpoint", "Producer")); + producerClass.extendSuperType(name.replace("Endpoint", "Common")); + + String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; + if (!Strings.isBlank(model.getDescription())) { + doc = model.getDescription() + "\n\n" + doc; + } + javaClass.getJavaDoc().setText(doc); + + javaClass.addAnnotation(Generated.class.getName()) + .setStringValue("value", EndpointDslMojo.class.getName()); + + for (EndpointOptionModel option : model.getEndpointOptions()) { + + JavaClass target = commonClass; + if (option.getLabel() != null) { + if (option.getLabel().contains("producer")) { + target = producerClass; + } else if (option.getLabel().contains("consumer")) { + target = consumerClass; + } + } + + GenericType gtype; + try { + Field field = findField(realComponentClass, realEndpointClass, option); + gtype = new GenericType(GenericsUtil.resolveType(realEndpointClass, field)); + gtype = getType(javaClass, enumClasses, option.getEnums(), gtype.toString()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + + Property prop = target.addProperty(gtype, option.getName()); + if ("true".equals(option.getDeprecated())) { + prop.getField().addAnnotation(Deprecated.class); + prop.getAccessor().addAnnotation(Deprecated.class); + prop.getMutator().addAnnotation(Deprecated.class); + } + if (!Strings.isBlank(option.getDescription())) { + String desc = option.getDescription(); + if (!desc.endsWith(".")) { + desc = desc + "."; + } + desc = desc + " The option is a " + option.getJavaType() + " type."; + prop.getField().getJavaDoc().setFullText(desc); + } + } + + String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; + writeSourceIfChanged(javaClass, fileName, false); + } + + private Class<?> findEndpointClass(Class<?> componentClass) { + String endpointName = componentClass.getCanonicalName().replaceFirst("Component", "Endpoint"); + if ("org.apache.camel.component.disruptor.vm.DisruptorVmEndpoint".equals(endpointName)) { + endpointName = "org.apache.camel.component.disruptor.DisruptorEndpoint"; + } else if ("org.apache.camel.component.etcd.EtcdEndpoint".equals(endpointName)) { + endpointName = "org.apache.camel.component.etcd.AbstractEtcdPollingEndpoint"; + } else if ("org.apache.camel.websocket.jsr356.JSR356WebSocketEndpoint".equals(endpointName)) { + endpointName = "org.apache.camel.websocket.jsr356.JSR356Endpoint"; + } + return loadClass(endpointName); + } + + private Field findField(Class<?> realComponentClass, Class<?> realEndpointClass, EndpointOptionModel option) throws NoSuchFieldException { + Field field = null; + List<Class<?>> classes = new ArrayList<>(); + classes.add(realComponentClass); + classes.add(realEndpointClass); + while (!classes.isEmpty()) { + Class cl = classes.remove(0); + for (Field f : cl.getDeclaredFields()) { + String n = f.getName(); + UriPath path = f.getAnnotation(UriPath.class); + if (path != null && !Strings.isBlank(path.name())) { + n = path.name(); + } + UriParam param = f.getAnnotation(UriParam.class); + if (param != null && !Strings.isBlank(param.name())) { + n = param.name(); + } + if (n.equals(option.getName())) { + field = f; + break; + } + if (f.getType().isAnnotationPresent(UriParams.class)) { + classes.add(f.getType()); + } + } + if (field != null) { + break; + } + cl = cl.getSuperclass(); + if (cl != null) { + classes.add(cl); + } + } + if (field == null) { + throw new NoSuchFieldException("Could not find field for option " + option.getName()); + } + return field; + } + + static boolean isPrimitive(String type) { + return PRIMITIVE_CLASSES.containsKey(type); + } + + private Class<?> loadClass(String loadClassName) { + Class<?> optionClass; + String org = loadClassName; + while (true) { + try { + optionClass = getProjectClassLoader().loadClass(loadClassName); + break; + } catch (ClassNotFoundException e) { + int dotIndex = loadClassName.lastIndexOf('.'); + if (dotIndex == -1) { + throw new IllegalArgumentException(org); + } else { + loadClassName = loadClassName.substring(0, dotIndex) + "$" + loadClassName.substring(dotIndex + 1); + } + } + } + return optionClass; + } + + + private GenericType getType(JavaClass javaClass, Map<String, JavaClass> enumClasses, String enums, String type) { + type = type.trim(); + // Check if this is an array + if (type.endsWith("[]")) { + GenericType t = getType(javaClass, enumClasses, enums, type.substring(0, type.length() - 2)); + return new GenericType(Array.newInstance(t.getRawClass(), 0).getClass(), t); + } + // Check if this is a generic + int genericIndex = type.indexOf('<'); + if (genericIndex > 0) { + if (!type.endsWith(">")) { + throw new IllegalArgumentException("Can not load type: " + type); + } + GenericType base = getType(javaClass, enumClasses, enums, type.substring(0, genericIndex)); + if (base.getRawClass() == Object.class) { + return base; + } + String[] params = splitParams(type.substring(genericIndex + 1, type.length() - 1)); + GenericType[] types = new GenericType[params.length]; + for (int i = 0; i < params.length; i++) { + types[i] = getType(javaClass, enumClasses, enums, params[i]); + } + return new GenericType(base.getRawClass(), types); + } + // Primitive + if (isPrimitive(type)) { + return new GenericType(PRIMITIVE_CLASSES.get(type)); + } + // Extends + if (type.startsWith("? extends ")) { + String raw = type.substring("? extends ".length()); + return new GenericType(loadClass(raw), BoundType.Extends); + } + // Super + if (type.startsWith("? super ")) { + String raw = type.substring("? extends ".length()); + return new GenericType(loadClass(raw), BoundType.Super); + } + // Wildcard + if (type.equals("?")) { + return new GenericType(Object.class, BoundType.Extends); + } + if (loadClass(type).isEnum() && !Strings.isBlank(enums) && !isCamelCoreType(type)) { + String enumClassName = type.substring(type.lastIndexOf('.') + 1); + if (enumClassName.contains("$")) { + enumClassName = enumClassName.substring(enumClassName.indexOf('$') + 1); + } + JavaClass enumClass = enumClasses.get(enumClassName); + if (enumClass == null) { + enumClass = javaClass.addNestedType().setPublic().setStatic(true) + .setName(enumClassName).setEnum(true); + enumClasses.put(enumClassName, enumClass); + for (String value : enums.split(",")) { + enumClass.addValue(value + .replace('.', '_') + .replace('-', '_')); + } + } + type = javaClass.getPackage() + "." + javaClass.getName() + "$" + enumClassName; + return new GenericType(generateDummyClass(type)); + } + if (!isCamelCoreType(type)) { + getLog().debug("Substituting java.lang.Object to " + type); + return new GenericType(Object.class); + } + return new GenericType(loadClass(type)); + } + + private String[] splitParams(String string) { + List<String> params = new ArrayList<>(); + int cur = 0; + int start = 0; + int opened = 0; + while (true) { + int nextComma = string.indexOf(',', cur); + int nextOpen = string.indexOf('<', cur); + int nextClose = string.indexOf('>', cur); + if (nextComma < 0) { + params.add(string.substring(start)); + return params.toArray(new String[0]); + } else if ((nextOpen < 0 || nextComma < nextOpen) + && (nextClose < 0 || nextComma < nextClose) + && opened == 0) { + params.add(string.substring(start, nextComma)); + start = cur = nextComma + 1; + } else if (nextOpen < 0) { + if (--opened < 0) { + throw new IllegalStateException(); + } + cur = nextClose + 1; + } else if (nextClose < 0 || nextOpen < nextClose) { + ++opened; + cur = nextOpen + 1; + } else { + if (--opened < 0) { + throw new IllegalStateException(); + } + cur = nextClose + 1; + } + } + } + + private boolean isCamelCoreType(String type) { + return type.startsWith("java.") + || type.matches("org\\.apache\\.camel\\.(spi\\.)?([A-Za-z]+)"); + } + + protected DynamicClassLoader getProjectClassLoader() { + if (projectClassLoader == null) { + final List<?> classpathElements; + try { + classpathElements = project.getTestClasspathElements(); + } catch (org.apache.maven.artifact.DependencyResolutionRequiredException e) { + throw new RuntimeException(e.getMessage(), e); + } + final URL[] urls = new URL[classpathElements.size()]; + int i = 0; + for (Iterator<?> it = classpathElements.iterator(); it.hasNext(); i++) { + try { + urls[i] = new File((String)it.next()).toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + projectClassLoader = new DynamicClassLoader(urls, tccl != null ? tccl : getClass().getClassLoader()); + } + return projectClassLoader; + } + + static class DynamicClassLoader extends URLClassLoader { + public DynamicClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + public Class defineClass(String name, byte[] data) { + return super.defineClass(name, data, 0, data.length); + } + } + + private Class generateDummyClass(String clazzName) { + try { + return getProjectClassLoader().loadClass(clazzName); + } catch (ClassNotFoundException e) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, clazzName.replace('.', '/'), null, "java/lang/Object", null); + cw.visitEnd(); + return getProjectClassLoader().defineClass(clazzName, cw.toByteArray()); + } + } + + private static String loadComponentJson(Map<File, Supplier<String>> jsonFiles, String componentName) { + return loadJsonOfType(jsonFiles, componentName, "component"); + } + + private static String loadJsonOfType(Map<File, Supplier<String>> jsonFiles, String modelName, String type) { + for (Map.Entry<File, Supplier<String>> entry : jsonFiles.entrySet()) { + if (entry.getKey().getName().equals(modelName + ".json")) { + String json = entry.getValue().get(); + if (json.contains("\"kind\": \"" + type + "\"")) { + return json; + } + } + } + return null; + } + + private static ComponentModel generateComponentModel(String componentName, String json) { + List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false); + + ComponentModel component = new ComponentModel(true); + component.setScheme(getSafeValue("scheme", rows)); + component.setSyntax(getSafeValue("syntax", rows)); + component.setAlternativeSyntax(getSafeValue("alternativeSyntax", rows)); + component.setTitle(getSafeValue("title", rows)); + component.setDescription(getSafeValue("description", rows)); + component.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows)); + component.setLabel(getSafeValue("label", rows)); + component.setDeprecated(getSafeValue("deprecated", rows)); + component.setDeprecationNote(getSafeValue("deprecationNote", rows)); + component.setConsumerOnly(getSafeValue("consumerOnly", rows)); + component.setProducerOnly(getSafeValue("producerOnly", rows)); + component.setJavaType(getSafeValue("javaType", rows)); + component.setGroupId(getSafeValue("groupId", rows)); + component.setArtifactId(getSafeValue("artifactId", rows)); + component.setVersion(getSafeValue("version", rows)); + + rows = JSonSchemaHelper.parseJsonSchema("componentProperties", json, true); + for (Map<String, String> row : rows) { + ComponentOptionModel option = new ComponentOptionModel(); + option.setName(getSafeValue("name", row)); + option.setDisplayName(getSafeValue("displayName", row)); + option.setKind(getSafeValue("kind", row)); + option.setType(getSafeValue("type", row)); + option.setJavaType(getSafeValue("javaType", row)); + option.setDeprecated(getSafeValue("deprecated", row)); + option.setDeprecationNote(getSafeValue("deprecationNote", row)); + option.setDescription(getSafeValue("description", row)); + option.setDefaultValue(getSafeValue("defaultValue", row)); + option.setEnums(getSafeValue("enum", row)); + component.addComponentOption(option); + } + + rows = JSonSchemaHelper.parseJsonSchema("properties", json, true); + for (Map<String, String> row : rows) { + EndpointOptionModel option = new EndpointOptionModel(); + option.setName(getSafeValue("name", row)); + option.setDisplayName(getSafeValue("displayName", row)); + option.setKind(getSafeValue("kind", row)); + option.setGroup(getSafeValue("group", row)); + option.setLabel(getSafeValue("label", row)); + option.setRequired(getSafeValue("required", row)); + option.setType(getSafeValue("type", row)); + option.setJavaType(getSafeValue("javaType", row)); + option.setEnums(getSafeValue("enum", row)); + option.setPrefix(getSafeValue("prefix", row)); + option.setMultiValue(getSafeValue("multiValue", row)); + option.setDeprecated(getSafeValue("deprecated", row)); + option.setDeprecationNote(getSafeValue("deprecationNote", row)); + option.setDefaultValue(getSafeValue("defaultValue", row)); + option.setDescription(getSafeValue("description", row)); + option.setEnumValues(getSafeValue("enum", row)); + component.addEndpointOption(option); + } + + return component; + } + + private void findComponentNames(File dir, Set<String> componentNames) { + File f = new File(dir, "classes/META-INF/services/org/apache/camel/component"); + + if (f.exists() && f.isDirectory()) { + File[] files = f.listFiles(); + if (files != null) { + for (File file : files) { + // skip directories as there may be a sub .resolver + // directory + if (file.isDirectory()) { + continue; + } + String name = file.getName(); + if (name.charAt(0) != '.') { + componentNames.add(name); + } + } + } + } + } + + private void writeSourceIfChanged(JavaClass source, String fileName, boolean innerClassesLast) throws MojoFailureException { + writeSourceIfChanged(source.printClass(innerClassesLast), fileName); + } + + private void writeSourceIfChanged(String source, String fileName) throws MojoFailureException { + File core = findCamelCoreDirectory(project.getBasedir()); + + File target = new File(new File(core, "src/main/java"), fileName); + + try { + String header; + try (InputStream is = getClass().getClassLoader().getResourceAsStream("license-header-java.txt")) { + header = loadText(is); + } + String code = header + source; + getLog().debug("Source code generated:\n" + code); + + updateResource(null, target.toPath(), code); + } catch (Exception e) { + throw new MojoFailureException("IOError with file " + target, e); + } + } + +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/ClassUtil.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/ClassUtil.java new file mode 100644 index 0000000..7c1d0ea --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/ClassUtil.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.camel.maven.packaging.generics; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * Utility classes with respect to the class operations. + * + * @author <a href="mailto:gurkanerdo...@yahoo.com">Gurkan Erdogdu</a> + * @since 1.0 + */ +public final class ClassUtil { + public static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPERS_MAP; + + static { + Map<Class<?>, Class<?>> primitiveToWrappersMap = new HashMap<Class<?>, Class<?>>(); + primitiveToWrappersMap.put(Integer.TYPE, Integer.class); + primitiveToWrappersMap.put(Float.TYPE, Float.class); + primitiveToWrappersMap.put(Double.TYPE, Double.class); + primitiveToWrappersMap.put(Character.TYPE, Character.class); + primitiveToWrappersMap.put(Long.TYPE, Long.class); + primitiveToWrappersMap.put(Byte.TYPE, Byte.class); + primitiveToWrappersMap.put(Short.TYPE, Short.class); + primitiveToWrappersMap.put(Boolean.TYPE, Boolean.class); + primitiveToWrappersMap.put(Void.TYPE, Void.class); + PRIMITIVE_TO_WRAPPERS_MAP = Collections.unmodifiableMap(primitiveToWrappersMap); + } + + public static final Type[] NO_TYPES = new Type[0]; + + /* + * Private constructor + */ + private ClassUtil() { + throw new UnsupportedOperationException(); + } + + public static boolean isSame(Type type1, Type type2) { + if ((type1 instanceof Class) && ((Class<?>) type1).isPrimitive()) { + type1 = PRIMITIVE_TO_WRAPPERS_MAP.get(type1); + } + if ((type2 instanceof Class) && ((Class<?>) type2).isPrimitive()) { + type2 = PRIMITIVE_TO_WRAPPERS_MAP.get(type2); + } + return type1 == type2; + } + + public static Class<?> getPrimitiveWrapper(Class<?> clazz) { + return PRIMITIVE_TO_WRAPPERS_MAP.get(clazz); + + } + + /** + * Gets the class of the given type arguments. + * <p> + * If the given type {@link Type} parameters is an instance of the + * {@link ParameterizedType}, it returns the raw type otherwise it return + * the casted {@link Class} of the type argument. + * </p> + * + * @param type class or parametrized type + * @return + */ + public static Class<?> getClass(Type type) + { + return getClazz(type); + } + + + /** + * Returns true if type is an instance of <code>ParameterizedType</code> + * else otherwise. + * + * @param type type of the artifact + * @return true if type is an instance of <code>ParameterizedType</code> + */ + public static boolean isParametrizedType(Type type) + { + return type instanceof ParameterizedType; + } + + /** + * Returns true if type is an instance of <code>WildcardType</code> + * else otherwise. + * + * @param type type of the artifact + * @return true if type is an instance of <code>WildcardType</code> + */ + public static boolean isWildCardType(Type type) + { + return type instanceof WildcardType; + } + + + /** + * Returns true if rhs is assignable type + * to the lhs, false otherwise. + * + * @param lhs left hand side class + * @param rhs right hand side class + * @return true if rhs is assignable to lhs + */ + public static boolean isClassAssignableFrom(Class<?> lhs, Class<?> rhs) + { + if(lhs.isPrimitive()) + { + lhs = getPrimitiveWrapper(lhs); + } + + if(rhs.isPrimitive()) + { + rhs = getPrimitiveWrapper(rhs); + } + + if (lhs.isAssignableFrom(rhs)) + { + return true; + } + + return false; + } + + /** + * Return raw class type for given type. + * + * @param type base type instance + * @return class type for given type + */ + public static Class<?> getClazz(Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + return (Class<?>) pt.getRawType(); + } else if (type instanceof Class) { + return (Class<?>) type; + } else if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + return Array.newInstance(getClazz(arrayType.getGenericComponentType()), 0).getClass(); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + Type[] bounds = wildcardType.getUpperBounds(); + if (bounds.length > 1) { + throw new IllegalArgumentException("Illegal use of wild card type with more than one upper bound: " + wildcardType); + } else if (bounds.length == 0) { + return Object.class; + } else { + return getClass(bounds[0]); + } + } else if (type instanceof TypeVariable) { + TypeVariable<?> typeVariable = (TypeVariable<?>) type; + if (typeVariable.getBounds().length > 1) { + throw new IllegalArgumentException("Illegal use of type variable with more than one bound: " + typeVariable); + } else { + Type[] bounds = typeVariable.getBounds(); + if (bounds.length == 0) { + return Object.class; + } else { + return getClass(bounds[0]); + } + } + } else { + throw new IllegalArgumentException("Unsupported type " + type); + } + } + + + public static boolean isRawClassEquals(Type ipType, Type apiType) { + Class ipClass = getRawPrimitiveType(ipType); + Class apiClass = getRawPrimitiveType(apiType); + + if (ipClass == null || apiClass == null) { + // we found some illegal generics + return false; + } + + return ipClass.equals(apiClass); + } + + private static Class getRawPrimitiveType(Type type) { + if (type instanceof Class) { + if (((Class) type).isPrimitive()) { + return getPrimitiveWrapper((Class) type); + } + return (Class) type; + } + + if (type instanceof ParameterizedType) { + return getRawPrimitiveType(((ParameterizedType) type).getRawType()); + } + + return null; + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/GenericsUtil.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/GenericsUtil.java new file mode 100644 index 0000000..ec68bfb --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/GenericsUtil.java @@ -0,0 +1,880 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.camel.maven.packaging.generics; + + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Utility classes for generic type operations. + */ +public final class GenericsUtil { + public static boolean satisfiesDependency(boolean isDelegateOrEvent, boolean isProducer, Type injectionPointType, Type beanType) { + if (beanType instanceof TypeVariable || beanType instanceof WildcardType || beanType instanceof GenericArrayType) { + return isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, beanType); + } else { + Type injectionPointRawType = injectionPointType instanceof ParameterizedType ? ((ParameterizedType) injectionPointType).getRawType() : injectionPointType; + Type beanRawType = beanType instanceof ParameterizedType ? ((ParameterizedType) beanType).getRawType() : beanType; + + if (ClassUtil.isSame(injectionPointRawType, beanRawType)) { + return isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, beanType); + } + } + + return false; + } + + public static boolean satisfiesDependencyRaw(boolean isDelegateOrEvent, boolean isProducer, Type injectionPointType, Type beanType) { + if (beanType instanceof TypeVariable || beanType instanceof WildcardType || beanType instanceof GenericArrayType) { + return isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, beanType); + } else { + Type injectionPointRawType = injectionPointType instanceof ParameterizedType ? ((ParameterizedType) injectionPointType).getRawType() : injectionPointType; + Type beanRawType = beanType instanceof ParameterizedType ? ((ParameterizedType) beanType).getRawType() : beanType; + + if (ClassUtil.isSame(injectionPointRawType, beanRawType)) { + return isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointRawType, beanRawType); + } else { + Class bean = (Class) beanType; + if (bean.getSuperclass() != null && ClassUtil.isRawClassEquals(injectionPointType, bean.getSuperclass())) { + return true; + } + + Class<?>[] interfaces = bean.getInterfaces(); + if (interfaces == null || interfaces.length == 0) { + return false; + } + + for (Class<?> clazz : interfaces) { + if (ClassUtil.isRawClassEquals(injectionPointType, clazz)) { + return true; + } + } + } + } + + return false; + } + + /** + * 5.2.3 and 5.2.4 + */ + public static boolean isAssignableFrom(boolean isDelegateOrEvent, boolean isProducer, Type requiredType, Type beanType) { + if (requiredType instanceof Class) { + return isAssignableFrom(isDelegateOrEvent, (Class<?>) requiredType, beanType); + } else if (requiredType instanceof ParameterizedType) { + return isAssignableFrom(isDelegateOrEvent, isProducer, (ParameterizedType) requiredType, beanType); + } else if (requiredType instanceof TypeVariable) { + return isAssignableFrom(isDelegateOrEvent, (TypeVariable<?>) requiredType, beanType); + } else if (requiredType instanceof GenericArrayType) { + return Class.class.isInstance(beanType) && Class.class.cast(beanType).isArray() + && isAssignableFrom(isDelegateOrEvent, (GenericArrayType) requiredType, beanType); + } else if (requiredType instanceof WildcardType) { + return isAssignableFrom(isDelegateOrEvent, (WildcardType) requiredType, beanType); + } else { + throw new IllegalArgumentException("Unsupported type " + requiredType.getClass()); + } + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, Class<?> injectionPointType, Type beanType) { + if (beanType instanceof Class) { + return isAssignableFrom(injectionPointType, (Class<?>) beanType); + } else if (beanType instanceof TypeVariable) { + return isAssignableFrom(isDelegateOrEvent, injectionPointType, (TypeVariable<?>) beanType); + } else if (beanType instanceof ParameterizedType) { + return isAssignableFrom(isDelegateOrEvent, injectionPointType, (ParameterizedType) beanType); + } else if (beanType instanceof GenericArrayType) { + return isAssignableFrom(isDelegateOrEvent, injectionPointType, (GenericArrayType) beanType); + } else if (beanType instanceof WildcardType) { + return isAssignableFrom(isDelegateOrEvent, (Type) injectionPointType, (WildcardType) beanType); + } else { + throw new IllegalArgumentException("Unsupported type " + injectionPointType.getClass()); + } + } + + private static boolean isAssignableFrom(Class<?> injectionPointType, Class<?> beanType) { + return ClassUtil.isClassAssignableFrom(injectionPointType, beanType); + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, Class<?> injectionPointType, TypeVariable<?> beanType) { + for (Type bounds : beanType.getBounds()) { + if (isAssignableFrom(isDelegateOrEvent, injectionPointType, bounds)) { + return true; + } + } + return false; + } + + /** + * CDI Spec. 5.2.4: "A parameterized bean type is considered assignable to a raw required type + * if the raw generics are identical and all type parameters of the bean type are either unbounded type variables or java.lang.Object." + */ + private static boolean isAssignableFrom(boolean isDelegateOrEvent, Class<?> injectionPointType, ParameterizedType beanType) { + if (beanType.getRawType() != injectionPointType) { + return false; //raw generics don't match + } + + if (isDelegateOrEvent) { + // for delegate and events we match 'in reverse' kind off + // @Observes ProcessInjectionPoint<?, Instance> does also match Instance<SomeBean> + return isAssignableFrom(true, injectionPointType, beanType.getRawType()); + } + + for (Type typeArgument : beanType.getActualTypeArguments()) { + if (typeArgument == Object.class) { + continue; + } + if (!(typeArgument instanceof TypeVariable)) { + return false; //neither object nor type variable + } + TypeVariable<?> typeVariable = (TypeVariable<?>) typeArgument; + for (Type bounds : typeVariable.getBounds()) { + if (bounds != Object.class) { + return false; //bound type variable + } + } + } + return true; + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, Class<?> injectionPointType, GenericArrayType beanType) { + return injectionPointType.isArray() && isAssignableFrom(isDelegateOrEvent, injectionPointType.getComponentType(), beanType.getGenericComponentType()); + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, Type injectionPointType, WildcardType beanType) { + for (Type bounds : beanType.getLowerBounds()) { + if (!isAssignableFrom(isDelegateOrEvent, false, bounds, injectionPointType)) { + return false; + } + } + for (Type bounds : beanType.getUpperBounds()) { + if (isAssignableFrom(isDelegateOrEvent, false, injectionPointType, bounds)) { + return true; + } + } + return false; + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, boolean isProducer, ParameterizedType injectionPointType, Type beanType) { + if (beanType instanceof Class) { + return isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, (Class<?>) beanType); + } else if (beanType instanceof TypeVariable) { + return isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, (TypeVariable<?>) beanType); + } else if (beanType instanceof ParameterizedType) { + return isAssignableFrom(isDelegateOrEvent, injectionPointType, (ParameterizedType) beanType); + } else if (beanType instanceof WildcardType) { + return isAssignableFrom(isDelegateOrEvent, injectionPointType, (WildcardType) beanType); + } else if (beanType instanceof GenericArrayType) { + return false; + } else { + throw new IllegalArgumentException("Unsupported type " + beanType.getClass()); + } + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, boolean isProducer, ParameterizedType injectionPointType, Class<?> beanType) { + Class<?> rawInjectionPointType = getRawType(injectionPointType); + if (rawInjectionPointType.equals(beanType)) { + if (isProducer) { + for (final Type t : injectionPointType.getActualTypeArguments()) { + if (!TypeVariable.class.isInstance(t) || !isNotBound(TypeVariable.class.cast(t).getBounds())) { + if (!Class.class.isInstance(t) || Object.class != t) { + return false; + } + } + } + } + return true; + } + if (!rawInjectionPointType.isAssignableFrom(beanType)) { + return false; + } + if (beanType.getSuperclass() != null && isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, beanType.getGenericSuperclass())) { + return true; + } + for (Type genericInterface : beanType.getGenericInterfaces()) { + if (isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, genericInterface)) { + return true; + } + } + return false; + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, boolean isProducer, ParameterizedType injectionPointType, TypeVariable<?> beanType) { + final Type[] types = beanType.getBounds(); + if (isNotBound(types)) { + return true; + } + for (final Type bounds : types) { + if (isAssignableFrom(isDelegateOrEvent, isProducer, injectionPointType, bounds)) { + return true; + } + } + return false; + } + + /** + * CDI Spec. 5.2.4 + */ + private static boolean isAssignableFrom(boolean isDelegateOrEvent, ParameterizedType injectionPointType, ParameterizedType beanType) { + if (injectionPointType.getRawType() != beanType.getRawType()) { + return false; + } + boolean swapParams = !isDelegateOrEvent; + Type[] injectionPointTypeArguments = injectionPointType.getActualTypeArguments(); + Type[] beanTypeArguments = beanType.getActualTypeArguments(); + for (int i = 0; i < injectionPointTypeArguments.length; i++) { + Type injectionPointTypeArgument = injectionPointTypeArguments[i]; + Type beanTypeArgument = beanTypeArguments[i]; + + // for this special case it's actually an 'assignable to', thus we swap the params, see CDI-389 + // but this special rule does not apply to Delegate injection points... + if (swapParams && + (injectionPointTypeArgument instanceof Class || injectionPointTypeArgument instanceof TypeVariable) && + beanTypeArgument instanceof TypeVariable) { + final Type[] bounds = ((TypeVariable<?>) beanTypeArgument).getBounds(); + final boolean isNotBound = isNotBound(bounds); + if (!isNotBound) { + for (final Type upperBound : bounds) { + if (!isAssignableFrom(true, false, upperBound, injectionPointTypeArgument)) { + return false; + } + } + } + } else if (swapParams && injectionPointTypeArgument instanceof TypeVariable) { + return false; + } else if (isDelegateOrEvent && injectionPointTypeArgument instanceof Class && beanTypeArgument instanceof Class) { + // if no wildcard type was given then we require a real exact match. + return injectionPointTypeArgument.equals(beanTypeArgument); + + } else if (!isAssignableFrom(isDelegateOrEvent, false, injectionPointTypeArgument, beanTypeArgument)) { + return false; + } + } + return true; + } + + private static boolean isNotBound(final Type... bounds) { + return bounds == null || bounds.length == 0 || (bounds.length == 1 && Object.class == bounds[0]); + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, TypeVariable<?> injectionPointType, Type beanType) { + for (Type bounds : injectionPointType.getBounds()) { + if (!isAssignableFrom(isDelegateOrEvent, false, bounds, beanType)) { + return false; + } + } + return true; + } + + // rules are a bit different when in an array so we handle ParameterizedType manually (not reusing isAssignableFrom) + private static boolean isAssignableFrom(boolean isDelegateOrEvent, GenericArrayType injectionPointType, Type beanType) { + final Type genericComponentType = injectionPointType.getGenericComponentType(); + final Class componentType = Class.class.cast(beanType).getComponentType(); + if (Class.class.isInstance(genericComponentType)) { + return Class.class.cast(genericComponentType).isAssignableFrom(componentType); + } + if (ParameterizedType.class.isInstance(genericComponentType)) { + return isAssignableFrom(isDelegateOrEvent, false, ParameterizedType.class.cast(genericComponentType).getRawType(), componentType); + } + return isAssignableFrom(isDelegateOrEvent, false, genericComponentType, componentType); + } + + private static boolean isAssignableFrom(boolean isDelegateOrEvent, WildcardType injectionPointType, Type beanType) { + if (beanType instanceof TypeVariable) { + return isAssignableFrom(isDelegateOrEvent, injectionPointType, (TypeVariable<?>) beanType); + } + for (Type bounds : injectionPointType.getLowerBounds()) { + if (!isAssignableFrom(isDelegateOrEvent, false, beanType, bounds)) { + return false; + } + } + for (Type bounds : injectionPointType.getUpperBounds()) { + Set<Type> beanTypeClosure = getTypeClosure(beanType); + boolean isAssignable = false; + for (Type beanSupertype : beanTypeClosure) { + if (isAssignableFrom(isDelegateOrEvent, false, bounds, beanSupertype) + || (Class.class.isInstance(bounds) + && ParameterizedType.class.isInstance(beanSupertype) + && bounds == ParameterizedType.class.cast(beanSupertype).getRawType())) { + isAssignable = true; + break; + } + } + if (!isAssignable) { + return false; + } + } + return true; + } + + /** + * CDI 1.1 Spec. 5.2.4, third bullet point + */ + private static boolean isAssignableFrom(boolean isDelegateOrEvent, WildcardType injectionPointType, TypeVariable<?> beanType) { + for (Type upperBound : injectionPointType.getUpperBounds()) { + for (Type bound : beanType.getBounds()) { + if (!isAssignableFrom(isDelegateOrEvent, false, upperBound, bound) && !isAssignableFrom(isDelegateOrEvent, false, bound, upperBound)) { + return false; + } + } + } + for (Type lowerBound : injectionPointType.getLowerBounds()) { + for (Type bound : beanType.getBounds()) { + if (!isAssignableFrom(isDelegateOrEvent, false, bound, lowerBound)) { + return false; + } + } + } + return true; + } + + /** + * @return <tt>true</tt>, if the specified type declaration contains an unresolved type variable. + */ + public static boolean containsTypeVariable(Type type) { + if (type instanceof Class) { + return false; + } else if (type instanceof TypeVariable) { + return true; + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + return containTypeVariable(parameterizedType.getActualTypeArguments()); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + return containTypeVariable(wildcardType.getUpperBounds()) || containTypeVariable(wildcardType.getLowerBounds()); + } else if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + return containsTypeVariable(arrayType.getGenericComponentType()); + } else { + throw new IllegalArgumentException("Unsupported type " + type.getClass().getName()); + } + + } + + public static boolean containTypeVariable(Collection<? extends Type> types) { + return containTypeVariable(types.toArray(new Type[types.size()])); + } + + public static boolean containTypeVariable(Type[] types) { + for (Type type : types) { + if (containsTypeVariable(type)) { + return true; + } + } + return false; + } + + /** + * @param type to check + * @return {@code true} if the given type contains a {@link WildcardType} + * {@code false} otherwise + */ + public static boolean containsWildcardType(Type type) { + if (!(type instanceof ParameterizedType)) { + return false; + } + + for (Type typeArgument : getParameterizedType(type).getActualTypeArguments()) { + if (ClassUtil.isParametrizedType(typeArgument)) { + if (containsWildcardType(typeArgument)) { + return true; + } + } else { + if (ClassUtil.isWildCardType(typeArgument)) { + return true; + } + } + } + + return false; + } + + + /** + * Resolves the actual type of the specified field for the type hierarchy specified by the given subclass + */ + public static Type resolveType(Class<?> subclass, Field field) { + return resolveType(field.getGenericType(), subclass, newSeenList()); + } + + /** + * Resolves the actual return type of the specified method for the type hierarchy specified by the given subclass + */ + public static Type resolveReturnType(Class<?> subclass, Method method) { + return resolveType(method.getGenericReturnType(), subclass, newSeenList()); + } + + /** + * Resolves the actual parameter generics of the specified constructor for the type hierarchy specified by the given subclass + */ + public static Type[] resolveParameterTypes(Class<?> subclass, Constructor<?> constructor) { + return resolveTypes(constructor.getGenericParameterTypes(), subclass); + } + + /** + * Resolves the actual parameter generics of the specified method for the type hierarchy specified by the given subclass + */ + public static Type[] resolveParameterTypes(Class<?> subclass, Method method) { + return resolveTypes(method.getGenericParameterTypes(), subclass); + } + + /** + * Resolves the actual type of the specified type for the type hierarchy specified by the given subclass + */ + public static Type resolveType(Type type, Class<?> subclass, Member member) { + return resolveType(type, subclass, newSeenList()); + } + + public static Type resolveType(Type type, Class<?> subclass, Member member, Collection<TypeVariable<?>> seen) { + return resolveType(type, subclass, seen); + } + + public static Type resolveType(Type type, Type actualType, Collection<TypeVariable<?>> seen) { + if (type instanceof Class) { + return type; + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + + Type[] resolvedTypeArguments; + if (Enum.class.equals(parameterizedType.getRawType())) { + // Enums derive from themselves, which would create an infinite loop + // we directly escape the loop if we detect this. + resolvedTypeArguments = new Type[]{new OwbWildcardTypeImpl(new Type[]{Enum.class}, ClassUtil.NO_TYPES)}; + } else { + resolvedTypeArguments = resolveTypes(parameterizedType.getActualTypeArguments(), actualType, seen); + + } + + return new OwbParametrizedTypeImpl(parameterizedType.getOwnerType(), parameterizedType.getRawType(), resolvedTypeArguments); + } else if (type instanceof TypeVariable) { + TypeVariable<?> variable = (TypeVariable<?>) type; + return resolveTypeVariable(variable, actualType, seen); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + Type[] upperBounds = resolveTypes(wildcardType.getUpperBounds(), actualType, seen); + Type[] lowerBounds = resolveTypes(wildcardType.getLowerBounds(), actualType, seen); + return new OwbWildcardTypeImpl(upperBounds, lowerBounds); + } else if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + return createArrayType(resolveType(arrayType.getGenericComponentType(), actualType, seen)); + } else { + throw new IllegalArgumentException("Unsupported type " + type.getClass().getName()); + } + } + + public static Type[] resolveTypes(Type[] types, Type actualType, Collection<TypeVariable<?>> seen) { + Type[] resolvedTypeArguments = new Type[types.length]; + for (int i = 0; i < types.length; i++) { + final Type type = resolveType(types[i], actualType, seen); + if (type != null) // means a stackoverflow was avoided, just keep what we have + { + resolvedTypeArguments[i] = type; + } + } + return resolvedTypeArguments; + } + + public static Type[] resolveTypes(Type[] types, Type actualType) { + Type[] resolvedTypeArguments = new Type[types.length]; + for (int i = 0; i < types.length; i++) { + resolvedTypeArguments[i] = resolveType(types[i], actualType, newSeenList()); + } + return resolvedTypeArguments; + } + + public static Set<Type> getTypeClosure(Class<?> type) { + return getTypeClosure(type, type); + } + + public static Set<Type> getTypeClosure(Type actualType) { + return getTypeClosure(actualType, actualType); + } + + /** + * Returns the type closure for the specified parameters. + * <h3>Example 1:</h3> + * <p> + * Take the following classes: + * </p> + * <code> + * public class Foo<T> { + * private T t; + * } + * public class Bar extends Foo<Number> { + * } + * </code> + * <p> + * To get the type closure of T in the context of Bar (which is {Number.class, Object.class}), you have to call this method like + * </p> + * <code> + * GenericUtil.getTypeClosure(Foo.class.getDeclaredField("t").getType(), Bar.class, Foo.class); + * </code> + * <h3>Example 2:</h3> + * <p> + * Take the following classes: + * </p> + * <code> + * public class Foo<T> { + * private T t; + * } + * public class Bar<T> extends Foo<T> { + * } + * </code> + * <p> + * To get the type closure of Bar<T> in the context of Foo<Number> (which are besides Object.class the <tt>ParameterizedType</tt>s Bar<Number> and Foo<Number>), + * you have to call this method like + * </p> + * <code> + * GenericUtil.getTypeClosure(Foo.class, new TypeLiteral<Foo<Number>>() {}.getType(), Bar.class); + * </code> + * + * @param type the type to get the closure for + * @param actualType the context to bind type variables + * @return the type closure + */ + public static Set<Type> getTypeClosure(Type type, Type actualType) { + Class<?> rawType = getRawType(type); + Class<?> actualRawType = getRawType(actualType); + if (rawType.isAssignableFrom(actualRawType) && rawType != actualRawType) { + return getTypeClosure(actualType, type); + } + if (hasTypeParameters(type)) { + type = getParameterizedType(type); + } + return getDirectTypeClosure(type, actualType); + } + + public static Set<Type> getDirectTypeClosure(final Type type, final Type actualType) { + Set<Type> typeClosure = new HashSet<Type>(); + typeClosure.add(Object.class); + fillTypeHierarchy(typeClosure, type, actualType); + return typeClosure; + } + + private static void fillTypeHierarchy(Set<Type> set, Type type, Type actualType) { + if (type == null) { + return; + } + Type resolvedType = GenericsUtil.resolveType(type, actualType, newSeenList()); + set.add(resolvedType); + Class<?> resolvedClass = GenericsUtil.getRawType(resolvedType, actualType); + if (resolvedClass.getSuperclass() != null) { + fillTypeHierarchy(set, resolvedClass.getGenericSuperclass(), resolvedType); + } + for (Type interfaceType : resolvedClass.getGenericInterfaces()) { + fillTypeHierarchy(set, interfaceType, resolvedType); + } + } + + private static Collection<TypeVariable<?>> newSeenList() { + return new ArrayList<TypeVariable<?>>(); + } + + public static boolean hasTypeParameters(Type type) { + if (type instanceof Class) { + Class<?> classType = (Class<?>) type; + return classType.getTypeParameters().length > 0; + } + return false; + } + + public static ParameterizedType getParameterizedType(Type type) { + if (type instanceof ParameterizedType) { + return (ParameterizedType) type; + } else if (type instanceof Class) { + Class<?> classType = (Class<?>) type; + return new OwbParametrizedTypeImpl(classType.getDeclaringClass(), classType, classType.getTypeParameters()); + } else { + throw new IllegalArgumentException(type.getClass().getSimpleName() + " is not supported"); + } + } + + public static <T> Class<T> getRawType(Type type) { + return getRawType(type, null); + } + + static <T> Class<T> getRawType(Type type, Type actualType) { + if (type instanceof Class) { + return (Class<T>) type; + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + return getRawType(parameterizedType.getRawType(), actualType); + } else if (type instanceof TypeVariable) { + TypeVariable<?> typeVariable = (TypeVariable<?>) type; + Type mostSpecificType = getMostSpecificType(getRawTypes(typeVariable.getBounds(), actualType), typeVariable.getBounds()); + return getRawType(mostSpecificType, actualType); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + Type mostSpecificType = getMostSpecificType(getRawTypes(wildcardType.getUpperBounds(), actualType), wildcardType.getUpperBounds()); + return getRawType(mostSpecificType, actualType); + } else if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + return getRawType(createArrayType(getRawType(arrayType.getGenericComponentType(), actualType)), actualType); + } else { + throw new IllegalArgumentException("Unsupported type " + type.getClass().getName()); + } + } + + private static <T> Class<T>[] getRawTypes(Type[] types) { + return getRawTypes(types, null); + } + + private static <T> Class<T>[] getRawTypes(Type[] types, Type actualType) { + Class<T>[] rawTypes = new Class[types.length]; + for (int i = 0; i < types.length; i++) { + rawTypes[i] = getRawType(types[i], actualType); + } + return rawTypes; + } + + private static Type getMostSpecificType(Class<?>[] types, Type[] genericTypes) { + Class<?> mostSpecificType = types[0]; + int mostSpecificIndex = 0; + for (int i = 0; i < types.length; i++) { + if (mostSpecificType.isAssignableFrom(types[i])) { + mostSpecificType = types[i]; + mostSpecificIndex = i; + } + } + return genericTypes[mostSpecificIndex]; + } + + private static Class<?>[] getClassTypes(Class<?>[] rawTypes) { + List<Class<?>> classTypes = new ArrayList<Class<?>>(); + for (Class<?> rawType : rawTypes) { + if (!rawType.isInterface()) { + classTypes.add(rawType); + } + } + return classTypes.toArray(new Class[classTypes.size()]); + } + + private static Type resolveTypeVariable(TypeVariable<?> variable, Type actualType, Collection<TypeVariable<?>> seen) { + if (actualType == null) { + return variable; + } + Class<?> declaringClass = getDeclaringClass(variable.getGenericDeclaration()); + Class<?> actualClass = getRawType(actualType); + if (actualClass == declaringClass) { + return resolveTypeVariable(variable, variable.getGenericDeclaration(), getParameterizedType(actualType), seen); + } else if (actualClass.isAssignableFrom(declaringClass)) { + Class<?> directSubclass = getDirectSubclass(declaringClass, actualClass); + Type[] typeArguments = resolveTypeArguments(directSubclass, actualType); + Type directSubtype = new OwbParametrizedTypeImpl(directSubclass.getDeclaringClass(), directSubclass, typeArguments); + return resolveTypeVariable(variable, directSubtype, seen); + } else // if (declaringClass.isAssignableFrom(actualClass)) + { + Type genericSuperclass = getGenericSuperclass(actualClass, declaringClass); + if (genericSuperclass == null) { + return variable; + } else if (genericSuperclass instanceof Class) { + // special handling for type erasure + Class<?> superclass = (Class<?>) genericSuperclass; + genericSuperclass = new OwbParametrizedTypeImpl(superclass.getDeclaringClass(), superclass, getRawTypes(superclass.getTypeParameters())); + } else { + ParameterizedType genericSupertype = getParameterizedType(genericSuperclass); + Type[] typeArguments = resolveTypeArguments(getParameterizedType(actualType), genericSupertype); + genericSuperclass = new OwbParametrizedTypeImpl(genericSupertype.getOwnerType(), genericSupertype.getRawType(), typeArguments); + } + Type resolvedType = resolveTypeVariable(variable, genericSuperclass, seen); + if (resolvedType instanceof TypeVariable) { + TypeVariable<?> resolvedTypeVariable = (TypeVariable<?>) resolvedType; + TypeVariable<?>[] typeParameters = actualClass.getTypeParameters(); + for (int i = 0; i < typeParameters.length; i++) { + if (typeParameters[i].getName().equals(resolvedTypeVariable.getName())) { + resolvedType = getParameterizedType(actualType).getActualTypeArguments()[i]; + break; + } + } + } + return resolvedType; + } + } + + private static Class<?> getDeclaringClass(GenericDeclaration declaration) { + if (declaration instanceof Class) { + return (Class<?>) declaration; + } else if (declaration instanceof Member) { + return ((Member) declaration).getDeclaringClass(); + } else { + throw new IllegalArgumentException("Unsupported type " + declaration.getClass()); + } + } + + private static Type resolveTypeVariable(TypeVariable<?> variable, GenericDeclaration declaration, ParameterizedType type, + Collection<TypeVariable<?>> seen) { + int index = getIndex(declaration, variable); + if (declaration instanceof Class) { + if (index >= 0) { + return type.getActualTypeArguments()[index]; + } else { + index = getIndex(type, variable); + if (index >= 0) { + return declaration.getTypeParameters()[index]; + } + } + } else { + if (seen.contains(variable)) { + return null; + } + seen.add(variable); + + Type[] resolvedBounds = resolveTypes(declaration.getTypeParameters()[index].getBounds(), type, seen); + return OwbTypeVariableImpl.createTypeVariable(variable, resolvedBounds); + } + return variable; + } + + private static int getIndex(GenericDeclaration declaration, TypeVariable<?> variable) { + Type[] typeParameters = declaration.getTypeParameters(); + for (int i = 0; i < typeParameters.length; i++) { + if (typeParameters[i] instanceof TypeVariable) { + TypeVariable<?> variableArgument = (TypeVariable<?>) typeParameters[i]; + if (variableArgument.getName().equals(variable.getName())) { + return i; + } + } + } + return -1; + } + + private static int getIndex(ParameterizedType type, TypeVariable<?> variable) { + Type[] actualTypeArguments = type.getActualTypeArguments(); + for (int i = 0; i < actualTypeArguments.length; i++) { + if (actualTypeArguments[i] instanceof TypeVariable) { + TypeVariable<?> variableArgument = (TypeVariable<?>) actualTypeArguments[i]; + if (variableArgument.getName().equals(variable.getName())) { + return i; + } + } + } + return -1; + } + + private static Class<?> getDirectSubclass(Class<?> declaringClass, Class<?> actualClass) { + if (actualClass.isInterface()) { + Class<?> subclass = declaringClass; + for (Class<?> iface : declaringClass.getInterfaces()) { + if (iface == actualClass) { + return subclass; + } + if (actualClass.isAssignableFrom(iface)) { + subclass = iface; + } else { + subclass = declaringClass.getSuperclass(); + } + } + return getDirectSubclass(subclass, actualClass); + } else { + Class<?> directSubclass = declaringClass; + while (directSubclass.getSuperclass() != actualClass) { + directSubclass = directSubclass.getSuperclass(); + } + return directSubclass; + } + } + + private static Type getGenericSuperclass(Class<?> subclass, Class<?> superclass) { + if (!superclass.isInterface()) { + return subclass.getGenericSuperclass(); + } else { + for (Type genericInterface : subclass.getGenericInterfaces()) { + if (getRawType(genericInterface) == superclass) { + return genericInterface; + } + } + } + return superclass; + } + + private static Type[] resolveTypeArguments(Class<?> subclass, Type supertype) { + if (supertype instanceof ParameterizedType) { + ParameterizedType parameterizedSupertype = (ParameterizedType) supertype; + return resolveTypeArguments(subclass, parameterizedSupertype); + } else { + return subclass.getTypeParameters(); + } + } + + private static Type[] resolveTypeArguments(Class<?> subclass, ParameterizedType parameterizedSupertype) { + Type genericSuperclass = getGenericSuperclass(subclass, getRawType(parameterizedSupertype)); + if (!(genericSuperclass instanceof ParameterizedType)) { + return subclass.getTypeParameters(); + } + ParameterizedType parameterizedSuperclass = (ParameterizedType) genericSuperclass; + Type[] typeParameters = subclass.getTypeParameters(); + Type[] actualTypeArguments = parameterizedSupertype.getActualTypeArguments(); + return resolveTypeArguments(parameterizedSuperclass, typeParameters, actualTypeArguments); + } + + private static Type[] resolveTypeArguments(ParameterizedType subtype, ParameterizedType parameterizedSupertype) { + return resolveTypeArguments(getParameterizedType(getRawType(subtype)), parameterizedSupertype.getActualTypeArguments(), subtype.getActualTypeArguments()); + } + + private static Type[] resolveTypeArguments(ParameterizedType parameterizedType, Type[] typeParameters, Type[] actualTypeArguments) { + Type[] resolvedTypeArguments = new Type[typeParameters.length]; + for (int i = 0; i < typeParameters.length; i++) { + resolvedTypeArguments[i] = resolveTypeArgument(parameterizedType, typeParameters[i], actualTypeArguments); + } + return resolvedTypeArguments; + } + + private static Type resolveTypeArgument(ParameterizedType parameterizedType, Type typeParameter, Type[] actualTypeArguments) { + if (typeParameter instanceof TypeVariable) { + TypeVariable<?> variable = (TypeVariable<?>) typeParameter; + int index = getIndex(parameterizedType, variable); + if (index == -1) { + return typeParameter; + } else { + return actualTypeArguments[index]; + } + } else if (typeParameter instanceof GenericArrayType) { + GenericArrayType array = (GenericArrayType) typeParameter; + return createArrayType(resolveTypeArgument(parameterizedType, array.getGenericComponentType(), actualTypeArguments)); + } else { + return typeParameter; + } + } + + private static Type createArrayType(Type componentType) { + if (componentType instanceof Class) { + return Array.newInstance((Class<?>) componentType, 0).getClass(); + } else { + return new OwbGenericArrayTypeImpl(componentType); + } + } + + public static Type resolveType(ParameterizedType parameterizedType, Type metadataType) { + return resolveType(parameterizedType, metadataType, newSeenList()); + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbGenericArrayTypeImpl.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbGenericArrayTypeImpl.java new file mode 100644 index 0000000..2b3722b --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbGenericArrayTypeImpl.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.camel.maven.packaging.generics; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; + + +public class OwbGenericArrayTypeImpl implements GenericArrayType { + + private Type componentType; + + public OwbGenericArrayTypeImpl(Type componentType) { + this.componentType = componentType; + } + + @Override + public Type getGenericComponentType() { + return componentType; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return componentType.hashCode(); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof GenericArrayType) { + return ((GenericArrayType) obj).getGenericComponentType().equals(componentType); + } else { + return false; + } + + } + + public String toString() { + return componentType + "[]"; + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbParametrizedTypeImpl.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbParametrizedTypeImpl.java new file mode 100644 index 0000000..2cf3a95 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbParametrizedTypeImpl.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.camel.maven.packaging.generics; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; + +/** + * Custom parametrized type implementation. + * + * @version $Rev: 1621935 $ $Date: 2014-09-02 09:07:32 +0200 (Tue, 02 Sep 2014) $ + */ +public class OwbParametrizedTypeImpl implements ParameterizedType { + /** + * Owner type + */ + private final Type owner; + + /** + * Raw type + */ + private final Type rawType; + + /** + * Actual type arguments + */ + private final Type[] types; + + /** + * New instance. + * + * @param owner owner + * @param raw raw + */ + public OwbParametrizedTypeImpl(Type owner, Type raw, Type... types) { + this.owner = owner; + rawType = raw; + this.types = types; + } + + @Override + public Type[] getActualTypeArguments() { + return types.clone(); + } + + @Override + public Type getOwnerType() { + return owner; + } + + @Override + public Type getRawType() { + return rawType; + } + + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return Arrays.hashCode(types) ^ (owner == null ? 0 : owner.hashCode()) ^ (rawType == null ? 0 : rawType.hashCode()); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof ParameterizedType) { + ParameterizedType that = (ParameterizedType) obj; + Type thatOwnerType = that.getOwnerType(); + Type thatRawType = that.getRawType(); + return (owner == null ? thatOwnerType == null : owner.equals(thatOwnerType)) + && (rawType == null ? thatRawType == null : rawType.equals(thatRawType)) + && Arrays.equals(types, that.getActualTypeArguments()); + } else { + return false; + } + + } + + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append(((Class<?>) rawType).getName()); + Type[] actualTypes = getActualTypeArguments(); + if (actualTypes.length > 0) { + buffer.append("<"); + int length = actualTypes.length; + for (int i = 0; i < length; i++) { + if (actualTypes[i] instanceof Class) { + buffer.append(((Class<?>) actualTypes[i]).getSimpleName()); + } else { + buffer.append(actualTypes[i].toString()); + } + if (i != actualTypes.length - 1) { + buffer.append(", "); + } + } + + buffer.append(">"); + } + + return buffer.toString(); + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbTypeVariableImpl.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbTypeVariableImpl.java new file mode 100644 index 0000000..7abd222 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbTypeVariableImpl.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.camel.maven.packaging.generics; + +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; + + +public class OwbTypeVariableImpl +{ + private static final Class<?>[] TYPE_VARIABLE_TYPES = new Class<?>[]{TypeVariable.class}; + + /** + * Java TypeVariable is different in various JDK versions. Thus it is not possible to e.g. + * write a custom TypeVariable which works in either Java7 and Java8 as they introduced + * new methods in Java8 which have return generics which only exist in Java8 :( + * + * As workaround we dynamically crate a proxy to wrap this and do the delegation manually. + * This is of course slower, but as we do not use it often it might not have much impact. + * + * @param typeVariable + * @param bounds + * @return the typeVariable with the defined bounds. + */ + public static TypeVariable createTypeVariable(TypeVariable typeVariable, Type... bounds) + { + TypeVariable tv = (TypeVariable) Proxy.newProxyInstance(OwbTypeVariableImpl.class.getClassLoader(), TYPE_VARIABLE_TYPES, + new OwbTypeVariableInvocationHandler(typeVariable, bounds)); + + return tv; + } + + + + public static class OwbTypeVariableInvocationHandler implements InvocationHandler + { + + private String name; + private GenericDeclaration genericDeclaration; + private Type[] bounds; + + + public OwbTypeVariableInvocationHandler(TypeVariable typeVariable, Type... bounds) + { + name = typeVariable.getName(); + genericDeclaration = typeVariable.getGenericDeclaration(); + if (bounds == null || bounds.length == 0) + { + this.bounds = typeVariable.getBounds(); + } + else + { + this.bounds = bounds; + } + } + + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + String methodName = method.getName(); + if ("equals".equals(methodName)) + { + return typeVariableEquals(args[0]); + } + else if ("hashCode".equals(methodName)) + { + return typeVariableHashCode(); + } + else if ("toString".equals(methodName)) + { + return typeVariableToString(); + } + else if ("getName".equals(methodName)) + { + return getName(); + } + else if ("getGenericDeclaration".equals(methodName)) + { + return getGenericDeclaration(); + } + else if ("getBounds".equals(methodName)) + { + return getBounds(); + } + + + // new method from java8... + return null; + } + + /** method from TypeVariable */ + public String getName() + { + return name; + } + + /** method from TypeVariable */ + public GenericDeclaration getGenericDeclaration() + { + return genericDeclaration; + } + + /** method from TypeVariable */ + public Type[] getBounds() + { + return bounds.clone(); + } + + /** method from TypeVariable */ + public int typeVariableHashCode() + { + return Arrays.hashCode(bounds) ^ name.hashCode() ^ genericDeclaration.hashCode(); + } + + /** method from TypeVariable */ + public boolean typeVariableEquals(Object object) + { + if (this == object) + { + return true; + } + else if (object instanceof TypeVariable) + { + TypeVariable<?> that = (TypeVariable<?>)object; + return name.equals(that.getName()) && genericDeclaration.equals(that.getGenericDeclaration()) && Arrays.equals(bounds, that.getBounds()); + } + else + { + return false; + } + + } + + /** method from TypeVariable */ + public String typeVariableToString() + { + StringBuilder buffer = new StringBuilder(); + buffer.append(name); + if (bounds.length > 0) + { + buffer.append(" extends "); + boolean first = true; + for (Type bound: bounds) + { + if (first) + { + first = false; + } + else + { + buffer.append(','); + } + buffer.append(' ').append(bound); + } + } + return buffer.toString(); + } + + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbWildcardTypeImpl.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbWildcardTypeImpl.java new file mode 100644 index 0000000..aebcfd0 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/OwbWildcardTypeImpl.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.camel.maven.packaging.generics; + +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; + +public class OwbWildcardTypeImpl implements WildcardType { + + private Type[] upperBounds; + private Type[] lowerBounds; + + public OwbWildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { + this.upperBounds = upperBounds.clone(); + this.lowerBounds = lowerBounds.clone(); + } + + @Override + public Type[] getUpperBounds() { + return upperBounds.clone(); + } + + @Override + public Type[] getLowerBounds() { + return lowerBounds.clone(); + } + + public String toString() { + StringBuilder buffer = new StringBuilder("?"); + if (upperBounds.length > 0) { + buffer.append(" extends"); + boolean first = true; + for (Type upperBound : upperBounds) { + if (first) { + first = false; + } else { + buffer.append(','); + } + buffer.append(' '); + if (upperBound instanceof Class) { + buffer.append(((Class<?>) upperBound).getSimpleName()); + } else { + buffer.append(upperBound); + } + } + } + if (lowerBounds.length > 0) { + buffer.append(" super"); + boolean first = true; + for (Type lowerBound : lowerBounds) { + if (first) { + first = false; + } else { + buffer.append(','); + } + buffer.append(' '); + if (lowerBound instanceof Class) { + buffer.append(((Class<?>) lowerBound).getSimpleName()); + } else { + buffer.append(lowerBound); + } + } + } + return buffer.toString(); + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/model/EndpointOptionModel.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/model/EndpointOptionModel.java index 87d8df8..3416df5 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/model/EndpointOptionModel.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/model/EndpointOptionModel.java @@ -26,6 +26,7 @@ public class EndpointOptionModel { private String displayName; private String kind; private String group; + private String label; private String required; private String type; private String javaType; @@ -74,6 +75,14 @@ public class EndpointOptionModel { this.group = group; } + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + public String getRequired() { return required; } diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/GenericType.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/GenericType.java index 3004605..8baa0ac 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/GenericType.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/GenericType.java @@ -35,7 +35,7 @@ public class GenericType { private static final Map<String, Class> PRIMITIVE_CLASSES = new HashMap<>(); - enum BoundType { + public enum BoundType { Exact, Extends, Super } diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/JavaClass.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/JavaClass.java index 22ae6ca..0be7f1d 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/JavaClass.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/srcgen/JavaClass.java @@ -25,6 +25,8 @@ import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; +import org.jboss.forge.roaster.model.util.Strings; + public class JavaClass { ClassLoader classLoader; @@ -38,6 +40,7 @@ public class JavaClass { List<Field> fields = new ArrayList<>(); List<Method> methods = new ArrayList<>(); List<JavaClass> nested = new ArrayList<>(); + List<String> values = new ArrayList<>(); Javadoc javadoc = new Javadoc(); boolean isStatic; boolean isPublic = true; @@ -57,6 +60,14 @@ public class JavaClass { this.parent = parent; } + protected ClassLoader getClassLoader() { + if (classLoader == null && parent != null) { + return parent.getClassLoader(); + } else { + return classLoader; + } + } + public JavaClass setStatic(boolean aStatic) { isStatic = aStatic; return this; @@ -72,6 +83,9 @@ public class JavaClass { return this; } + public String getPackage() { + return packageName; + } public JavaClass setPackage(String packageName) { this.packageName = packageName; return this; @@ -121,7 +135,7 @@ public class JavaClass { public Annotation addAnnotation(String type) { try { - Class<?> cl = classLoader.loadClass(type); + Class<?> cl = getClassLoader().loadClass(type); return addAnnotation(cl); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Unable to parse type", e); @@ -139,9 +153,9 @@ public class JavaClass { public Property addProperty(String type, String name) { try { - return addProperty(GenericType.parse(type, classLoader), name); + return addProperty(GenericType.parse(type, getClassLoader()), name); } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Unable to parse type", e); + throw new IllegalArgumentException("Unable to parse type " + type + " for property " + name, e); } } @@ -173,6 +187,10 @@ public class JavaClass { return clazz; } + public void addValue(String value) { + values.add(value); + } + public boolean isClass() { return isClass; } @@ -236,6 +254,21 @@ public class JavaClass { printJavadoc(sb, indent, javadoc); printAnnotations(sb, indent, annotations); + if (isEnum) { + sb.append(indent) + .append(isPublic ? "public " : "") + .append(isStatic ? "static " : "") + .append("enum ").append(name).append(" {\n") + .append(indent) + .append(" ") + .append(Strings.join(values, ", ")) + .append(";\n") + .append(indent) + .append("}"); + return; + + } + StringBuilder sb2 = new StringBuilder(); sb2.append(indent); if (isPublic) {