package org.springframework.roo.classpath;
import java.util.Arrays;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
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.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.ReferenceStrategy;
import org.apache.felix.scr.annotations.References;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.DefaultPhysicalTypeMetadata;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsBuilder;
import org.springframework.roo.classpath.scanner.MemberDetailsDecorator;
import org.springframework.roo.file.monitor.event.FileEvent;
import org.springframework.roo.file.monitor.event.FileEventListener;
import org.springframework.roo.file.monitor.event.FileOperation;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataItem;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.ProjectOperations;
import org.osgi.service.component.ComponentContext;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.springframework.roo.support.logging.HandlerUtils;
/**
* Monitors for *.java files and produces a {@link PhysicalTypeMetadata} for
* each, also providing type creation and deleting methods. Prior to 1.2.0, the
* default implementation of PhysicalTypeMetadataProvider was
* JavaParserMetadataProvider.
*
* @author Ben Alex
* @author James Tyrrell
* @since 1.2.0
*/
@Component
@Service
@References(value = {@Reference(name = "memberHoldingDecorator",
strategy = ReferenceStrategy.EVENT, policy = ReferencePolicy.DYNAMIC,
referenceInterface = MemberDetailsDecorator.class,
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)})
public class DefaultPhysicalTypeMetadataProvider implements PhysicalTypeMetadataProvider,
FileEventListener {
protected final static Logger LOGGER = HandlerUtils
.getLogger(DefaultPhysicalTypeMetadataProvider.class);
// ------------ OSGi component attributes ----------------
private BundleContext context;
protected void activate(final ComponentContext context) {
this.context = context.getBundleContext();
}
private final SortedSet<MemberDetailsDecorator> decorators = new TreeSet<MemberDetailsDecorator>(
new Comparator<MemberDetailsDecorator>() {
public int compare(final MemberDetailsDecorator o1, final MemberDetailsDecorator o2) {
return o1.getClass().getName().compareTo(o2.getClass().getName());
}
});
private FileManager fileManager;
private MetadataDependencyRegistry metadataDependencyRegistry;
private MetadataService metadataService;
private ProjectOperations projectOperations;
private TypeLocationService typeLocationService;
private TypeParsingService typeParsingService;
// Mutex
private final Object lock = new Object();
protected void bindMemberHoldingDecorator(final MemberDetailsDecorator decorator) {
synchronized (lock) {
decorators.add(decorator);
}
}
public MetadataItem get(final String metadataIdentificationString) {
if (fileManager == null) {
fileManager = getFileManager();
}
Validate.notNull(fileManager, "FileManager is required");
if (metadataDependencyRegistry == null) {
metadataDependencyRegistry = getMetadataDependencyRegistry();
}
Validate.notNull(metadataDependencyRegistry, "MetadataDependencyRegistry is required");
if (projectOperations == null) {
projectOperations = getProjectOperations();
}
Validate.notNull(projectOperations, "ProjectOperations is required");
if (typeLocationService == null) {
typeLocationService = getTypeLocationService();
}
Validate.notNull(typeLocationService, "TypeLocationService is required");
if (typeParsingService == null) {
typeParsingService = getTypeParsingService();
}
Validate.notNull(typeParsingService, "TypeParsingService is required");
Validate.isTrue(PhysicalTypeIdentifier.isValid(metadataIdentificationString),
"Metadata id '%s' is not valid for this metadata provider", metadataIdentificationString);
final String canonicalPath =
typeLocationService.getPhysicalTypeCanonicalPath(metadataIdentificationString);
if (StringUtils.isBlank(canonicalPath)) {
return null;
}
metadataDependencyRegistry.deregisterDependencies(metadataIdentificationString);
if (!fileManager.exists(canonicalPath)) {
// Couldn't find the file, so return null to distinguish from a file
// that was found but could not be parsed
return null;
}
final JavaType javaType = PhysicalTypeIdentifier.getJavaType(metadataIdentificationString);
final ClassOrInterfaceTypeDetails typeDetails =
typeParsingService.getTypeAtLocation(canonicalPath, metadataIdentificationString, javaType);
if (typeDetails == null) {
return null;
}
final PhysicalTypeMetadata result =
new DefaultPhysicalTypeMetadata(metadataIdentificationString, canonicalPath, typeDetails);
final ClassOrInterfaceTypeDetails details = result.getMemberHoldingTypeDetails();
if (details != null && details.getPhysicalTypeCategory() == PhysicalTypeCategory.CLASS
&& details.getExtendsTypes().size() == 1) {
// This is a class, and it extends another class
if (details.getSuperclass() != null) {
// We have a dependency on the superclass, and there is metadata
// available for the superclass
// We won't implement the full MetadataNotificationListener
// here, but rely on MetadataService's fallback
// (which is to evict from cache and call get again given
// JavaParserMetadataProvider doesn't implement
// MetadataNotificationListener, then notify everyone we've
// changed)
final String superclassId = details.getSuperclass().getDeclaredByMetadataId();
metadataDependencyRegistry.registerDependency(superclassId, result.getId());
} else {
// We have a dependency on the superclass, but no metadata is
// available
// We're left with no choice but to register for every physical
// type change, in the hope we discover our parent someday
for (final LogicalPath sourcePath : projectOperations.getPathResolver().getSourcePaths()) {
final String possibleSuperclass =
PhysicalTypeIdentifier.createIdentifier(details.getExtendsTypes().get(0), sourcePath);
metadataDependencyRegistry.registerDependency(possibleSuperclass, result.getId());
}
}
}
MemberDetails memberDetails = new MemberDetailsBuilder(Arrays.asList(details)).build();
// Loop until such time as we complete a full loop where no changes are
// made to the result
boolean additionalLoopRequired = true;
while (additionalLoopRequired) {
additionalLoopRequired = false;
for (final MemberDetailsDecorator decorator : decorators) {
final MemberDetails newResult =
decorator.decorateTypes(DefaultPhysicalTypeMetadataProvider.class.getName(),
memberDetails);
Validate.isTrue(newResult != null, "Decorator '%s' returned an illegal result", decorator
.getClass().getName());
if (!newResult.equals(memberDetails)) {
additionalLoopRequired = true;
memberDetails = newResult;
}
}
}
return new DefaultPhysicalTypeMetadata(metadataIdentificationString, canonicalPath,
(ClassOrInterfaceTypeDetails) memberDetails.getDetails().get(0));
}
public String getProvidesType() {
return PhysicalTypeIdentifier.getMetadataIdentiferType();
}
public void onFileEvent(final FileEvent fileEvent) {
if (metadataDependencyRegistry == null) {
metadataDependencyRegistry = getMetadataDependencyRegistry();
}
Validate.notNull(metadataDependencyRegistry, "MetadataDependencyRegistry is required");
if (metadataService == null) {
metadataService = getMetadataService();
}
Validate.notNull(metadataService, "MetadataService is required");
if (typeLocationService == null) {
typeLocationService = getTypeLocationService();
}
Validate.notNull(typeLocationService, "TypeLocationService is required");
final String fileIdentifier = fileEvent.getFileDetails().getCanonicalPath();
// Check to see if file is of interest
if (fileIdentifier.endsWith(".java")
&& fileEvent.getOperation() != FileOperation.MONITORING_FINISH
&& !fileIdentifier.endsWith("package-info.java")) {
// Figure out the PhysicalTypeIdentifier
final String id = typeLocationService.getPhysicalTypeIdentifier(fileIdentifier);
if (id == null) {
return;
}
// Now we've worked out the id, we can publish the event in case
// others were interested
metadataService.evictAndGet(id);
metadataDependencyRegistry.notifyDownstream(id);
}
}
protected void unbindMemberHoldingDecorator(final MemberDetailsDecorator decorator) {
synchronized (lock) {
decorators.remove(decorator);
}
}
public FileManager getFileManager() {
// Get all Services implement FileManager interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(FileManager.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (FileManager) this.context.getService(ref);
}
LOGGER.warning("Cannot load FileManager on DefaultPhysicalTypeMetadataProvider.");
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load FileManager on DefaultPhysicalTypeMetadataProvider.");
return null;
}
}
public MetadataDependencyRegistry getMetadataDependencyRegistry() {
// Get all Services implement MetadataDependencyRegistry interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(MetadataDependencyRegistry.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (MetadataDependencyRegistry) this.context.getService(ref);
}
LOGGER
.warning("Cannot load MetadataDependencyRegistry on DefaultPhysicalTypeMetadataProvider.");
return null;
} catch (InvalidSyntaxException e) {
LOGGER
.warning("Cannot load MetadataDependencyRegistry on DefaultPhysicalTypeMetadataProvider.");
return null;
}
}
public MetadataService getMetadataService() {
// Get all Services implement MetadataService interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(MetadataService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (MetadataService) this.context.getService(ref);
}
LOGGER.warning("Cannot load MetadataService on DefaultPhysicalTypeMetadataProvider.");
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load MetadataService on DefaultPhysicalTypeMetadataProvider.");
return null;
}
}
public ProjectOperations getProjectOperations() {
// Get all Services implement ProjectOperations interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(ProjectOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (ProjectOperations) this.context.getService(ref);
}
LOGGER.warning("Cannot load ProjectOperations on DefaultPhysicalTypeMetadataProvider.");
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load ProjectOperations on DefaultPhysicalTypeMetadataProvider.");
return null;
}
}
public TypeLocationService getTypeLocationService() {
// Get all Services implement TypeLocationService interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(TypeLocationService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (TypeLocationService) this.context.getService(ref);
}
LOGGER.warning("Cannot load TypeLocationService on DefaultPhysicalTypeMetadataProvider.");
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeLocationService on DefaultPhysicalTypeMetadataProvider.");
return null;
}
}
public TypeParsingService getTypeParsingService() {
// Get all Services implement TypeParsingService interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(TypeParsingService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (TypeParsingService) this.context.getService(ref);
}
LOGGER.warning("Cannot load TypeParsingService on DefaultPhysicalTypeMetadataProvider.");
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeParsingService on DefaultPhysicalTypeMetadataProvider.");
return null;
}
}
}