|
| 1 | +/* |
| 2 | + * Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH |
| 3 | + * |
| 4 | + * See the AUTHORS file(s) distributed with this work for additional |
| 5 | + * information regarding authorship. |
| 6 | + * |
| 7 | + * This Source Code Form is subject to the terms of the Mozilla Public |
| 8 | + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 9 | + * file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 10 | + * |
| 11 | + * SPDX-License-Identifier: MPL-2.0 |
| 12 | + */ |
| 13 | + |
| 14 | +package org.eclipse.esmf.ame.buildtime; |
| 15 | + |
| 16 | +import static org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper.*; |
| 17 | + |
| 18 | +import java.io.File; |
| 19 | +import java.io.FileOutputStream; |
| 20 | +import java.io.IOException; |
| 21 | +import java.util.ArrayList; |
| 22 | +import java.util.HashMap; |
| 23 | +import java.util.HashSet; |
| 24 | +import java.util.List; |
| 25 | +import java.util.Map; |
| 26 | +import java.util.Objects; |
| 27 | +import java.util.Properties; |
| 28 | +import java.util.Set; |
| 29 | +import java.util.stream.Collectors; |
| 30 | + |
| 31 | +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper; |
| 32 | +import org.eclipse.esmf.ame.substitution.AdminShellConfig; |
| 33 | +import org.eclipse.esmf.ame.substitution.ImplementationInfo; |
| 34 | +import org.eclipse.esmf.ame.substitution.Target_org_eclipse_digitaltwin_aas4j_v3_dataformat_core_util_ReflectionHelper; |
| 35 | + |
| 36 | +import io.github.classgraph.ClassGraph; |
| 37 | +import io.github.classgraph.ClassInfo; |
| 38 | +import io.github.classgraph.ClassInfoList; |
| 39 | +import io.github.classgraph.ScanResult; |
| 40 | + |
| 41 | +/** |
| 42 | + * This class generates the reflection information normally stored by {@link ReflectionHelper} and serializes it into a |
| 43 | + * .properties file. |
| 44 | + * It is part of the substitution logic for this class, see |
| 45 | + * {@link Target_org_eclipse_digitaltwin_aas4j_v3_dataformat_core_util_ReflectionHelper} for more information. |
| 46 | + * Note that this class is <i>only</i> supposed to run at build time (via execution from the Maven build) and is not |
| 47 | + * part of the |
| 48 | + * resulting CLI codebase. Running this class is configured in the pom.xml of the CLI Maven module (via |
| 49 | + * exec-maven-plugin). |
| 50 | + */ |
| 51 | +public class Aas4jClassSetup { |
| 52 | + private final AdminShellConfig config; |
| 53 | + |
| 54 | + public Aas4jClassSetup() { |
| 55 | + // The following replicates the logic from ReflectionHelper's static constructor, but instead stores its result |
| 56 | + // in the AdminShellConfig object that can then be written to a .properties file |
| 57 | + final ScanResult modelScan = new ClassGraph() |
| 58 | + .enableClassInfo() |
| 59 | + .acceptPackagesNonRecursive( MODEL_PACKAGE_NAME ) |
| 60 | + .scan(); |
| 61 | + config = new AdminShellConfig(); |
| 62 | + config.typesWithModelType = scanModelTypes( modelScan ); |
| 63 | + config.subtypes = scanSubtypes( modelScan ); |
| 64 | + config.jsonMixins = scanMixins( modelScan, JSON_MIXINS_PACKAGE_NAME ); |
| 65 | + config.xmlMixins = scanMixins( modelScan, XML_MIXINS_PACKAGE_NAME ); |
| 66 | + config.defaultImplementations = scanDefaultImplementations( modelScan ); |
| 67 | + config.interfaces = scanAasInterfaces(); |
| 68 | + config.enums = modelScan.getAllEnums().loadClasses( Enum.class ); |
| 69 | + config.interfacesWithoutDefaultImplementation = getInterfacesWithoutDefaultImplementation( modelScan ); |
| 70 | + } |
| 71 | + |
| 72 | + public static void main( final String[] args ) throws IOException { |
| 73 | + final AdminShellConfig config = new Aas4jClassSetup().config; |
| 74 | + final Properties p = config.toProperties(); |
| 75 | + final File out = new File( args[0] ); |
| 76 | + final FileOutputStream outputStream = new FileOutputStream( out ); |
| 77 | + p.store( outputStream, null ); |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * Logic duplicated from {@link ReflectionHelper#hasSubclass(ClassInfo)} |
| 82 | + */ |
| 83 | + private boolean hasSubclass( final ClassInfo clazzInfo ) { |
| 84 | + return !getSubclasses( clazzInfo ).isEmpty(); |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * Logic duplicated from {@link ReflectionHelper#scanModelTypes(ScanResult)} |
| 89 | + */ |
| 90 | + private Set<Class<?>> scanModelTypes( final ScanResult modelScan ) { |
| 91 | + final Set<Class<?>> typesWithModelTypes; |
| 92 | + typesWithModelTypes = MODEL_TYPE_SUPERCLASSES.stream() |
| 93 | + .flatMap( x -> modelScan.getClassesImplementing( x.getName() ) |
| 94 | + .loadClasses().stream() ) |
| 95 | + .collect( Collectors.toSet() ); |
| 96 | + typesWithModelTypes.addAll( MODEL_TYPE_SUPERCLASSES ); |
| 97 | + return typesWithModelTypes; |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * Logic duplicated from {@link ReflectionHelper#scanSubtypes(ScanResult)} |
| 102 | + */ |
| 103 | + private Map<Class<?>, Set<Class<?>>> scanSubtypes( final ScanResult modelScan ) { |
| 104 | + return modelScan.getAllInterfaces().stream() |
| 105 | + .filter( this::hasSubclass ) |
| 106 | + .collect( Collectors.toMap( ClassInfo::loadClass, this::getSubclasses ) ); |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Logic duplicated from {@link ReflectionHelper#getSubclasses(ClassInfo)} |
| 111 | + */ |
| 112 | + private Set<Class<?>> getSubclasses( final ClassInfo clazzInfo ) { |
| 113 | + return new HashSet<>( clazzInfo.getClassesImplementing() |
| 114 | + .directOnly() |
| 115 | + .filter( ClassInfo::isInterface ) |
| 116 | + .loadClasses() ); |
| 117 | + } |
| 118 | + |
| 119 | + /** |
| 120 | + * Logic duplicated from {@link ReflectionHelper#scanMixins(ScanResult, String)} |
| 121 | + */ |
| 122 | + private Map<Class<?>, Class<?>> scanMixins( final ScanResult modelScan, final String packageName ) { |
| 123 | + final ScanResult mixinScan = new ClassGraph() |
| 124 | + .enableClassInfo() |
| 125 | + .acceptPackagesNonRecursive( packageName ) |
| 126 | + .scan(); |
| 127 | + final Map<Class<?>, Class<?>> mixins = new HashMap<>(); |
| 128 | + mixinScan.getAllClasses() |
| 129 | + .filter( x -> x.getSimpleName().endsWith( MIXIN_SUFFIX ) ) |
| 130 | + .loadClasses() |
| 131 | + .forEach( x -> { |
| 132 | + final String modelClassName = x.getSimpleName() |
| 133 | + .substring( 0, x.getSimpleName().length() - MIXIN_SUFFIX.length() ); |
| 134 | + final ClassInfoList modelClassInfos = modelScan.getAllClasses().filter( |
| 135 | + y -> Objects.equals( y.getSimpleName(), modelClassName ) ); |
| 136 | + if ( !modelClassInfos.isEmpty() ) { |
| 137 | + mixins.put( modelClassInfos.get( 0 ).loadClass(), x ); |
| 138 | + } |
| 139 | + } ); |
| 140 | + return mixins; |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * Logic duplicated from {@link ReflectionHelper#scanDefaultImplementations(ScanResult)} |
| 145 | + */ |
| 146 | + private List<ReflectionHelper.ImplementationInfo> scanDefaultImplementations( final ScanResult modelScan ) { |
| 147 | + final ScanResult defaulImplementationScan = new ClassGraph() |
| 148 | + .enableClassInfo() |
| 149 | + .acceptPackagesNonRecursive( DEFAULT_IMPLEMENTATION_PACKAGE_NAME ) |
| 150 | + .scan(); |
| 151 | + final List<ReflectionHelper.ImplementationInfo> defaultImplementations = new ArrayList<>(); |
| 152 | + defaulImplementationScan.getAllClasses() |
| 153 | + .filter( x -> x.getSimpleName().startsWith( DEFAULT_IMPLEMENTATION_PREFIX ) ) |
| 154 | + .loadClasses() |
| 155 | + .forEach( x -> { |
| 156 | + final String interfaceName = x.getSimpleName().substring( |
| 157 | + DEFAULT_IMPLEMENTATION_PREFIX.length() );// using conventions |
| 158 | + final ClassInfoList interfaceClassInfos = modelScan.getAllClasses() |
| 159 | + .filter( y -> y.isInterface() |
| 160 | + && Objects.equals( |
| 161 | + y.getSimpleName(), |
| 162 | + interfaceName ) ); |
| 163 | + if ( !interfaceClassInfos.isEmpty() ) { |
| 164 | + final Class<?> implementedClass = interfaceClassInfos.get( 0 ).loadClass(); |
| 165 | + defaultImplementations.add( new ImplementationInfo( implementedClass, x ) ); |
| 166 | + } |
| 167 | + } ); |
| 168 | + return defaultImplementations; |
| 169 | + } |
| 170 | + |
| 171 | + /** |
| 172 | + * Logic duplicated from {@link ReflectionHelper#scanAasInterfaces()} |
| 173 | + */ |
| 174 | + private Set<Class> scanAasInterfaces() { |
| 175 | + return config.defaultImplementations.stream().map( ReflectionHelper.ImplementationInfo::getInterfaceType ) |
| 176 | + .collect( Collectors.toSet() ); |
| 177 | + } |
| 178 | + |
| 179 | + /** |
| 180 | + * Logic duplicated from {@link ReflectionHelper#getInterfacesWithoutDefaultImplementation(ScanResult)} |
| 181 | + */ |
| 182 | + private Set<Class<?>> getInterfacesWithoutDefaultImplementation( final ScanResult modelScan ) { |
| 183 | + return modelScan.getAllInterfaces().loadClasses().stream() |
| 184 | + .filter( x -> !hasDefaultImplementation( x ) ) |
| 185 | + .collect( Collectors.toSet() ); |
| 186 | + } |
| 187 | + |
| 188 | + /** |
| 189 | + * Logic duplicated from {@link ReflectionHelper#hasDefaultImplementation(Class)} |
| 190 | + */ |
| 191 | + public boolean hasDefaultImplementation( final Class<?> interfaceType ) { |
| 192 | + return config.defaultImplementations.stream().anyMatch( x -> x.getInterfaceType().equals( interfaceType ) ); |
| 193 | + } |
| 194 | +} |
0 commit comments