Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2021 Robert Bosch Manufacturing Solutions GmbH
*
* See the AUTHORS file(s) distributed with this work for additional
* information regarding authorship.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*/
package io.openmanufacturing.sds.aspectmodel.java;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.vocabulary.RDF;
import io.openmanufacturing.sds.aspectmodel.java.exception.CodeGenerationException;
import io.openmanufacturing.sds.aspectmodel.resolver.services.DataType;
import io.openmanufacturing.sds.aspectmodel.vocabulary.BAMM;
import io.openmanufacturing.sds.metamodel.Characteristic;
import io.openmanufacturing.sds.metamodel.Collection;
import io.openmanufacturing.sds.metamodel.Either;
import io.openmanufacturing.sds.metamodel.Entity;
import io.openmanufacturing.sds.metamodel.Enumeration;
import io.openmanufacturing.sds.metamodel.HasProperties;
import io.openmanufacturing.sds.metamodel.Property;
import io.openmanufacturing.sds.metamodel.Quantifiable;
import io.openmanufacturing.sds.metamodel.Scalar;
import io.openmanufacturing.sds.metamodel.SortedSet;
import io.openmanufacturing.sds.metamodel.StructureElement;
import io.openmanufacturing.sds.metamodel.Trait;
import io.openmanufacturing.sds.metamodel.Type;
import com.google.common.base.CaseFormat;
import com.google.common.base.Converter;
import io.openmanufacturing.sds.aspectmetamodel.KnownVersion;
public class AspectModelJavaUtil {
public static final Converter TO_CONSTANT = CaseFormat.UPPER_CAMEL
.converterTo( CaseFormat.UPPER_UNDERSCORE );
private AspectModelJavaUtil() {
}
/**
* Determines the type of a property and wraps it in an Optional if it has been marked as optional.
*
* @param metaProperty the property meta model instance to determine the type for
* @param importTracker the import tracker for the current context
* @return the final type of the property
*/
public static String getPropertyType( final Property metaProperty, final boolean inclValidation,
final ImportTracker importTracker ) {
final String propertyType = determinePropertyType( metaProperty.getCharacteristic(), inclValidation,
importTracker );
if ( metaProperty.isOptional() ) {
return containerType( Optional.class, propertyType, Optional.empty() );
}
return propertyType;
}
/**
* Determines whether the property has a container type, i.e. it will result in an Optional, Collection or
* something similar.
*
* @param metaProperty the property to check
* @return {@code true} if the property has a container type, {@code false} else
*/
public static boolean hasContainerType( final Property metaProperty ) {
return metaProperty.isOptional() || (metaProperty.getEffectiveCharacteristic() instanceof Collection)
|| metaProperty.getDataType().map( dataType -> dataType.getUrn().equals( RDF.langString.getURI() ) )
.orElse( false );
}
/**
* Determines whether the property has a Quantifiable characteristic that actually has a Unit assigned.
*
* @param characteristic the characteristic to check
* @return {@code true} if the property carries a Unit, {@code false} else
*/
public static boolean hasUnit( final Characteristic characteristic ) {
if ( characteristic instanceof Quantifiable ) {
final Quantifiable quantifiable = (Quantifiable) characteristic;
return quantifiable.getUnit().isPresent();
}
return false;
}
/**
* Determines the type of a property
*
* @param characteristic the {@link Characteristic} which describes the data type for a property
* @param inclValidation a boolean indicating whether the element validation annotations should be included for
* the Collection declarations
* @return {@link String} containing the definition of the Java Data Type for the property
*/
public static String determinePropertyType( final Characteristic characteristic,
final boolean inclValidation, final ImportTracker importTracker ) {
final Optional dataType = characteristic.getDataType();
if ( characteristic instanceof Collection ) {
return determineCollectionType( (Collection) characteristic, inclValidation, importTracker );
}
if ( characteristic instanceof Enumeration ) {
return characteristic.getName();
}
if ( characteristic instanceof Trait ) {
final Characteristic baseCharacteristic = ((Trait) characteristic).getBaseCharacteristic();
if ( baseCharacteristic instanceof Collection ) {
return determineCollectionType( (Collection) baseCharacteristic, inclValidation, importTracker );
}
}
if ( characteristic instanceof Either ) {
importTracker.importExplicit( Either.class );
return Either.class.getTypeName();
}
return getDataType( dataType, importTracker );
}
public static String determineCollectionAspectClassDefinition( final StructureElement element,
final ImportTracker importTracker ) {
importTracker.importExplicit( CollectionAspect.class );
for ( final Property property : element.getProperties() ) {
final Characteristic characteristic = property.getEffectiveCharacteristic();
if ( characteristic instanceof Collection ) {
final String collectionType = determineCollectionType( (Collection) characteristic, false,
importTracker );
final String dataType = getDataType( characteristic.getDataType(), importTracker );
return String.format( "public class %s implements CollectionAspect<%s,%s>",
element.getName(), collectionType, dataType );
}
}
throw new CodeGenerationException( "Tried to generate a Collection Aspect class definition, but no "
+ "Property has a Collection Characteristic in " + element.getName() );
}
private static String determineCollectionType( final Collection collection, final boolean inclValidation,
final ImportTracker importTracker ) {
final Optional dataType = collection.getDataType();
final Optional elementConstraint = inclValidation ?
buildConstraintForCollectionElements( collection, importTracker ) :
Optional.empty();
if ( collection.isAllowDuplicates() && collection.isOrdered() ) {
importTracker.importExplicit( List.class );
return containerType( List.class, getDataType( dataType, importTracker ), elementConstraint );
}
if ( !collection.isAllowDuplicates() && collection.isOrdered() ) {
importTracker.importExplicit( LinkedHashSet.class );
return containerType( LinkedHashSet.class, getDataType( dataType, importTracker ), elementConstraint );
}
if ( collection.isAllowDuplicates() && !collection.isOrdered() ) {
importTracker.importExplicit( java.util.Collection.class );
return containerType( java.util.Collection.class, getDataType( dataType, importTracker ),
elementConstraint );
}
if ( !collection.isAllowDuplicates() && !collection.isOrdered() ) {
importTracker.importExplicit( Set.class );
return containerType( Set.class, getDataType( dataType, importTracker ), elementConstraint );
}
throw new CodeGenerationException( "Could not determine Java collection type for " + collection.getName() );
}
private static Optional buildConstraintForCollectionElements( final Collection collection,
final ImportTracker importTracker ) {
return collection.getElementCharacteristic()
.filter( elementCharacteristic -> Trait.class
.isAssignableFrom( elementCharacteristic.getClass() ) )
.map( elementCharacteristic -> buildConstraintsForCharacteristic(
(Trait) elementCharacteristic, importTracker ) );
}
public static String containerType( final Class> containerClass, final String elementType,
final Optional elementConstraint ) {
final StringBuilder containerTypeBuilder = new StringBuilder().append( containerClass.getName() )
.append( "<" );
elementConstraint.ifPresent( containerTypeBuilder::append );
containerTypeBuilder.append( elementType ).append( ">" );
return containerTypeBuilder.toString();
}
@SuppressWarnings( "squid:S1166" ) // Exception is thrown as regular part of logic
public static boolean classAnyOf( final Set> classes, final String className ) {
try {
final Class> clazz = Class.forName( className );
return classes.stream().anyMatch( clazz2 -> clazz2.isAssignableFrom( clazz ) );
} catch ( final ClassNotFoundException cnf ) {
return false;
}
}
/**
* Determines the Java Data Type
*
* @param dataType the raw data type
* @param importTracker the importTracker in the current context
* @return a {@link String} containing the definition of the Java Data Type
*/
public static String getDataType( final Optional dataType, final ImportTracker importTracker ) {
return dataType.map( type -> {
final Type actualDataType = dataType.get();
if ( actualDataType instanceof Entity ) {
return ((Entity) actualDataType).getName();
}
if ( actualDataType instanceof Scalar ) {
final Resource typeResource = ResourceFactory.createResource( actualDataType.getUrn() );
if ( typeResource.getURI().equals( RDF.langString.getURI() ) ) {
importTracker.importExplicit( java.util.Map.class );
importTracker.importExplicit( java.util.Locale.class );
return "Map";
}
final Class> result = DataType
.getJavaTypeForMetaModelType( typeResource, actualDataType.getMetaModelVersion() );
importTracker.importExplicit( result );
return result.getTypeName();
}
throw new CodeGenerationException( "Could not determine Java type for model type that is "
+ "neither Scalar nor Entity: " + type.getUrn() );
} ).orElseThrow(
() -> new CodeGenerationException( "Failed to determine Java data type for empty model type" ) );
}
/**
* Convert a string given as upper or lower camel case into a constant format.
*
* For example {@code someVariable} would become {@code SOME_VARIABLE}.
*
* @param upperOrLowerCamelString the string to convert
* @return the string formatted as a constant.
*/
public static String toConstant( final String upperOrLowerCamelString ) {
return TO_CONSTANT.convert( StringUtils.capitalize( upperOrLowerCamelString ) );
}
public static String createLiteral( final String value ) {
return "\"" + StringEscapeUtils.escapeJava( value ) + "\"";
}
/**
* Generates an enum key based on the given value
*
* @param optionalType the raw data type of the given value
* @param value the actual value to generate the enum key for
* @param importTracker the import tracker for the current context
* @return a string representing the enum key
*/
public static String generateEnumKey( final Optional optionalType, final Object value,
final ImportTracker importTracker ) {
final String dataType = getDataType( optionalType, importTracker );
if ( isNumberType( dataType ) ) {
return "NUMBER_" + value.toString().replaceAll( "[^\\p{Alnum}]", "_" );
}
if ( isEntityType( optionalType ) ) {
return optionalType.map( type -> {
@SuppressWarnings( "unchecked" ) final String valueAsString = ((Map) value)
.get( new BAMM( type.getMetaModelVersion() ).name().toString() );
return toConstant( valueAsString.substring( valueAsString.lastIndexOf( '#' ) + 1 ) );
} ).orElse( generateDefaultEnumKey( value ) );
}
return generateDefaultEnumKey( value );
}
private static String generateDefaultEnumKey( final Object value ) {
return value.toString().replaceAll( "([^\\p{Alnum}_])", "_" ).replaceAll( "^[^\\p{Alpha}_]", "_" )
.toUpperCase();
}
public static boolean isNumberType( final String dataTypeName ) {
return classAnyOf( Set.of( Number.class ), dataTypeName );
}
public static boolean isEntityType( final Optional dataType ) {
return dataType.map( type -> type instanceof Entity ).orElse( false );
}
/**
* Takes a class body with FQCNs and replaces them with applied imports (i.e. simply use the class name).
*/
public static String applyImports( final String body, final ImportTracker importTracker ) {
String importsApplied = body;
for ( final String oneImport : importTracker.getUsedImports() ) {
final String className = oneImport.substring( oneImport.lastIndexOf( '.' ) + 1 );
importsApplied = importsApplied.replaceAll( oneImport, className );
}
return importsApplied;
}
public static boolean isPropertyNotInPayload( final Property property, final ImportTracker importTracker ) {
if ( property.isNotInPayload() ) {
importTracker.importExplicit( "com.fasterxml.jackson.annotation.JsonIgnore" );
return true;
}
return false;
}
public static String buildConstraintsForCharacteristic( final Trait trait, final ImportTracker importTracker ) {
return trait.getConstraints().stream().map( constraint ->
new ConstraintAnnotationBuilder().setConstraintClass( constraint )
.setImportTracker( importTracker )
.build() ).collect( Collectors.joining() );
}
public static boolean anyPropertyNotInPayload( final HasProperties element ) {
return element.getProperties().stream().anyMatch( Property::isNotInPayload );
}
public static List getPropertiesInPayload( final HasProperties element ) {
final Predicate notInPayload = Property::isNotInPayload;
final Predicate inPayload = notInPayload.negate();
return element.getProperties().stream().filter( inPayload ).collect( Collectors.toList() );
}
public static String generateInitializer( final Property property, final String value,
final ImportTracker importTracker, final ValueInitializer valueInitializer ) {
return property.getDataType().map( type -> {
final Resource typeResource = ResourceFactory.createResource( type.getUrn() );
final KnownVersion metaModelVersion = property.getMetaModelVersion();
final Class> result = DataType.getJavaTypeForMetaModelType( typeResource, metaModelVersion );
importTracker.importExplicit( result );
return valueInitializer.apply( typeResource, value, metaModelVersion );
} ).orElseThrow( () -> new CodeGenerationException(
"The Either Characteristic is not allowed for Properties used as elements in a StructuredValue" ) );
}
public static String generateEnumValue( final Optional valueDataType, final Object value,
final boolean isOptional, final ImportTracker importTracker ) {
final String dataTypeName = getDataType( valueDataType, importTracker );
if ( dataTypeName.contains( "String" ) ) {
final String escapedValue = StringEscapeUtils.escapeJava( (String) value );
return isOptional ? String.format( "Optional.of(\"%s\")", escapedValue ) : "\"" + escapedValue + "\"";
}
if ( dataTypeName.contains( "Boolean" ) ) {
return value.toString();
}
if ( isNumberType( dataTypeName ) ) {
return generateNumericEnumValue( value, isOptional, dataTypeName );
}
if ( isEntityType( valueDataType ) ) {
return generateEntityEnumValue( valueDataType, value, importTracker, dataTypeName );
}
return value.toString();
}
private static String generateEntityEnumValue( final Optional valueDataType, final Object value,
final ImportTracker importTracker, final String dataTypeName ) {
return valueDataType.map( type -> {
final Entity entity = (Entity) type;
return String.format( "new %s(%s)", dataTypeName, entity.getProperties().stream().map( property ->
generateComplexValue( property, value, importTracker ) )
.collect( Collectors.joining( "," ) ) );
} ).orElseThrow( () -> new CodeGenerationException( "Can not generate enum value for empty type" ) );
}
private static String generateNumericEnumValue( final Object value, final boolean isOptional,
final String dataTypeName ) {
if ( needsValueOf( dataTypeName ) ) {
return isOptional ? String.format( "Optional.of(%s.valueOf(%s))", dataTypeName, value ) :
String.format( "%s.valueOf(%s)", dataTypeName, value );
}
if ( needsValueOfString( dataTypeName ) ) {
return isOptional ? String.format( "Optional.of(%s.valueOf(\"%s\"))", dataTypeName, value ) :
String.format( "%s.valueOf(\"%s\")", dataTypeName, value );
}
return isOptional ? String.format( "Optional.of(%s)", value ) : value.toString();
}
private static String generateComplexValue( final Property property, final Object valueMap,
final ImportTracker importTracker ) {
if ( property.getCharacteristic() instanceof io.openmanufacturing.sds.metamodel.Set ) {
@SuppressWarnings( "unchecked" ) final Set