package org.springframework.roo.classpath.operations;
import static org.springframework.roo.model.JavaType.OBJECT;
import static org.springframework.roo.model.RooJavaType.ROO_EQUALS;
import static org.springframework.roo.model.RooJavaType.ROO_JAVA_BEAN;
import static org.springframework.roo.model.RooJavaType.ROO_SERIALIZABLE;
import static org.springframework.roo.model.RooJavaType.ROO_TO_STRING;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.ConstructorMetadata;
import org.springframework.roo.classpath.details.ConstructorMetadataBuilder;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
import org.springframework.roo.model.ReservedWords;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.shell.converters.StaticFieldConverter;
/**
* OSGi implementation of {@link ClasspathOperations}.
*
* @author Andrew Swan
* @since 1.2.0
*/
@Component
@Service
public class ClasspathOperationsImpl implements ClasspathOperations {
@Reference
MetadataService metadataService;
@Reference
PathResolver pathResolver;
@Reference
ProjectOperations projectOperations;
@Reference
StaticFieldConverter staticFieldConverter;
@Reference
TypeLocationService typeLocationService;
@Reference
TypeManagementService typeManagementService;
protected void activate(final ComponentContext context) {
staticFieldConverter.add(InheritanceType.class);
}
protected void deactivate(final ComponentContext context) {
staticFieldConverter.remove(InheritanceType.class);
}
@Override
public void createClass(final JavaType name, final boolean rooAnnotations,
final LogicalPath path, final JavaType superclass, final JavaType implementsType,
final boolean createAbstract, final boolean permitReservedWords) {
if (!permitReservedWords) {
ReservedWords.verifyReservedWordsNotPresent(name);
}
Validate.isTrue(!JdkJavaType.isPartOfJavaLang(name.getSimpleTypeName()),
"Class name '%s' is part of java.lang", name.getSimpleTypeName());
int modifier = Modifier.PUBLIC;
if (createAbstract) {
modifier |= Modifier.ABSTRACT;
}
final String declaredByMetadataId = PhysicalTypeIdentifier.createIdentifier(name, path);
final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, modifier, name,
PhysicalTypeCategory.CLASS);
if (!superclass.equals(OBJECT)) {
final ClassOrInterfaceTypeDetails superclassClassOrInterfaceTypeDetails =
typeLocationService.getTypeDetails(superclass);
if (superclassClassOrInterfaceTypeDetails != null) {
cidBuilder.setSuperclass(new ClassOrInterfaceTypeDetailsBuilder(
superclassClassOrInterfaceTypeDetails));
}
}
final List<JavaType> extendsTypes = new ArrayList<JavaType>();
extendsTypes.add(superclass);
cidBuilder.setExtendsTypes(extendsTypes);
if (implementsType != null) {
final Set<JavaType> implementsTypes = new LinkedHashSet<JavaType>();
final ClassOrInterfaceTypeDetails typeDetails =
typeLocationService.getTypeDetails(declaredByMetadataId);
if (typeDetails != null) {
implementsTypes.addAll(typeDetails.getImplementsTypes());
}
implementsTypes.add(implementsType);
cidBuilder.setImplementsTypes(implementsTypes);
}
if (rooAnnotations) {
final List<AnnotationMetadataBuilder> annotations =
new ArrayList<AnnotationMetadataBuilder>();
annotations.add(new AnnotationMetadataBuilder(ROO_JAVA_BEAN));
annotations.add(new AnnotationMetadataBuilder(ROO_TO_STRING));
annotations.add(new AnnotationMetadataBuilder(ROO_EQUALS));
annotations.add(new AnnotationMetadataBuilder(ROO_SERIALIZABLE));
cidBuilder.setAnnotations(annotations);
}
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
}
@Override
public void createConstructor(final JavaType name, final Set<String> fields) {
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService.getTypeDetails(name);
Validate.notNull(javaTypeDetails, "The type specified, '%s', doesn't exist",
name.getFullyQualifiedTypeName());
final String declaredByMetadataId =
PhysicalTypeIdentifier.createIdentifier(name,
pathResolver.getFocusedPath(Path.SRC_MAIN_JAVA));
final List<FieldMetadata> constructorFields = new ArrayList<FieldMetadata>();
final List<? extends FieldMetadata> declaredFields = javaTypeDetails.getDeclaredFields();
if (fields != null) {
for (final String field : fields) {
declared: for (final FieldMetadata declaredField : declaredFields) {
if (field.equals(declaredField.getFieldName().getSymbolName())) {
constructorFields.add(declaredField);
break declared;
}
}
}
if (constructorFields.isEmpty()) {
// User supplied a set of fields that do not exist in the
// class, so return without creating any constructor
throw new IllegalArgumentException(
String.format(
"The set of fields provided for the constructor does not exist in the class '%s'",
name));
}
}
// Search for an existing constructor
final List<JavaType> parameterTypes = new ArrayList<JavaType>();
for (final FieldMetadata fieldMetadata : constructorFields) {
parameterTypes.add(fieldMetadata.getFieldType());
}
final ConstructorMetadata result = javaTypeDetails.getDeclaredConstructor(parameterTypes);
if (result != null) {
// Found an existing constructor on this class
throw new IllegalArgumentException(String.format(
"The class '%s' already has a constructor method with the same arguments and it cannot "
+ "be created. Use '--force' parameter to overrite it.", name));
}
final List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
bodyBuilder.appendFormalLine("super();");
for (final FieldMetadata field : constructorFields) {
final String fieldName = field.getFieldName().getSymbolName();
bodyBuilder.appendFormalLine("this." + fieldName + " = " + fieldName + ";");
parameterNames.add(field.getFieldName());
}
// Create the constructor
final ConstructorMetadataBuilder constructorBuilder =
new ConstructorMetadataBuilder(declaredByMetadataId);
constructorBuilder.setModifier(Modifier.PUBLIC);
constructorBuilder.setParameterTypes(AnnotatedJavaType.convertFromJavaTypes(parameterTypes));
constructorBuilder.setParameterNames(parameterNames);
constructorBuilder.setBodyBuilder(bodyBuilder);
final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(javaTypeDetails);
cidBuilder.addConstructor(constructorBuilder);
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
}
@Override
public void createEnum(final JavaType name, final LogicalPath path,
final boolean permitReservedWords) {
if (!permitReservedWords) {
ReservedWords.verifyReservedWordsNotPresent(name);
}
final String physicalTypeId = PhysicalTypeIdentifier.createIdentifier(name, path);
final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(physicalTypeId, Modifier.PUBLIC, name,
PhysicalTypeCategory.ENUMERATION);
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
}
@Override
public void createInterface(final JavaType name, final LogicalPath path,
final boolean permitReservedWords) {
if (!permitReservedWords) {
ReservedWords.verifyReservedWordsNotPresent(name);
}
final String declaredByMetadataId = PhysicalTypeIdentifier.createIdentifier(name, path);
final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, Modifier.PUBLIC, name,
PhysicalTypeCategory.INTERFACE);
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
}
@Override
public void enumConstant(final JavaType name, final JavaSymbolName fieldName,
final boolean permitReservedWords) {
if (!permitReservedWords) {
// No need to check the "name" as if the class exists it is assumed
// it is a legal name
ReservedWords.verifyReservedWordsNotPresent(fieldName);
}
final String declaredByMetadataId =
PhysicalTypeIdentifier.createIdentifier(name,
pathResolver.getFocusedPath(Path.SRC_MAIN_JAVA));
typeManagementService.addEnumConstant(declaredByMetadataId, fieldName);
}
@Override
public void focus(final JavaType type) {
Validate.notNull(type, "Specify the type to focus on");
final String physicalTypeIdentifier = typeLocationService.getPhysicalTypeIdentifier(type);
Validate.notNull(physicalTypeIdentifier, "Cannot locate the type %s",
type.getFullyQualifiedTypeName());
final PhysicalTypeMetadata ptm =
(PhysicalTypeMetadata) metadataService.get(physicalTypeIdentifier);
Validate.notNull(ptm, "Class %s does not exist",
PhysicalTypeIdentifier.getFriendlyName(physicalTypeIdentifier));
}
@Override
public boolean isProjectAvailable() {
return projectOperations.isFocusedProjectAvailable();
}
/**
* Replaces a JavaType fullyQualifiedName for a shorter name using '~' for
* TopLevelPackage
*
* @param cid
* ClassOrInterfaceTypeDetails of a JavaType
* @param currentText
* String current text for option value
* @return the String representing a JavaType with its name shortened
*/
public String replaceTopLevelPackageString(ClassOrInterfaceTypeDetails cid, String currentText) {
String javaTypeFullyQualilfiedName = cid.getType().getFullyQualifiedTypeName();
String javaTypeString = "";
String topLevelPackageString = "";
// Add module value to topLevelPackage when necessary
if (StringUtils.isNotBlank(cid.getType().getModule())
&& !cid.getType().getModule().equals(projectOperations.getFocusedModuleName())) {
// Target module is not focused
javaTypeString = cid.getType().getModule().concat(LogicalPath.MODULE_PATH_SEPARATOR);
topLevelPackageString =
projectOperations.getTopLevelPackage(cid.getType().getModule())
.getFullyQualifiedPackageName();
} else if (StringUtils.isNotBlank(cid.getType().getModule())
&& cid.getType().getModule().equals(projectOperations.getFocusedModuleName())
&& (currentText.startsWith(cid.getType().getModule()) || cid.getType().getModule()
.startsWith(currentText)) && StringUtils.isNotBlank(currentText)) {
// Target module is focused but user wrote it
javaTypeString = cid.getType().getModule().concat(LogicalPath.MODULE_PATH_SEPARATOR);
topLevelPackageString =
projectOperations.getTopLevelPackage(cid.getType().getModule())
.getFullyQualifiedPackageName();
} else {
// Not multimodule project
topLevelPackageString =
projectOperations.getFocusedTopLevelPackage().getFullyQualifiedPackageName();
}
// Autocomplete with abbreviate or full qualified mode
String auxString =
javaTypeString.concat(StringUtils.replace(javaTypeFullyQualilfiedName,
topLevelPackageString, "~"));
if ((StringUtils.isBlank(currentText) || auxString.startsWith(currentText))
&& StringUtils.contains(javaTypeFullyQualilfiedName, topLevelPackageString)) {
// Value is for autocomplete only or user wrote abbreviate value
javaTypeString = auxString;
} else {
// Value could be for autocomplete or for validation
javaTypeString = String.format("%s%s", javaTypeString, javaTypeFullyQualilfiedName);
}
return javaTypeString;
}
/**
* Replaces a JavaType fullyQualifiedName for a shorter name using '~' for
* TopLevelPackage
*
* @param cid
* ClassOrInterfaceTypeDetails of a JavaType
*
* @return the String representing a JavaType with its name shortened
*/
@Override
public String replaceTopLevelPackage(ClassOrInterfaceTypeDetails cid) {
String javaTypeFullyQualilfiedName = cid.getType().getFullyQualifiedTypeName();
String javaTypeString = "";
String topLevelPackageString = "";
// Add module value to topLevelPackage when necessary
if (StringUtils.isNotBlank(cid.getType().getModule())) {
// Target module is not focused
javaTypeString = cid.getType().getModule().concat(LogicalPath.MODULE_PATH_SEPARATOR);
topLevelPackageString =
projectOperations.getTopLevelPackage(cid.getType().getModule())
.getFullyQualifiedPackageName();
} else {
// Not multimodule project
topLevelPackageString =
projectOperations.getFocusedTopLevelPackage().getFullyQualifiedPackageName();
}
return javaTypeString.concat(StringUtils.replace(javaTypeFullyQualilfiedName,
topLevelPackageString, "~"));
}
}