blob: e6f435e2448b8e396c6fdf59fc75b28a0b06423f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.common.internal.modulecore;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jst.common.frameworks.CommonFrameworksPlugin;
import org.eclipse.jst.common.jdt.internal.javalite.JavaLiteUtilities;
import org.eclipse.wst.common.componentcore.internal.ComponentResource;
import org.eclipse.wst.common.componentcore.internal.Property;
import org.eclipse.wst.common.componentcore.internal.StructureEdit;
import org.eclipse.wst.common.componentcore.internal.WorkbenchComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
public class SingleRootUtil {
public interface SingleRootCallback {
public boolean canValidate(IProject project);
public void validate(SingleRootUtil util, IVirtualComponent vc, IProject project, List resourceMaps);
}
/**
* Used to return immediately after the first error code is found.
*/
public static final int INCLUDE_FIRST_ERROR = 0x08;
/**
* Used to capture all status codes (error, warning, info)
*/
public static final int INCLUDE_ALL = 0x07;
/**
* Used to capture all error and warning status codes only.
*/
public static final int INCLUDE_ERRORS_AND_WARNINGS = 0x06;
/**
* Used to capture all error status codes only.
*/
public static final int INCLUDE_ERRORS = 0x04;
private static final int WARNINGS = 0x02;
private static final int INFO = 0x01;
private static final int CANCEL = 0x0;
private static final int GET_SINGLE_ROOT_CONTAINER = 0x09;
private static String USE_SINGLE_ROOT_PROPERTY = "useSingleRoot"; //$NON-NLS-1$
private IVirtualComponent aComponent;
private SingleRootCallback callback;
private List<IContainer> cachedSourceContainers;
private IContainer[] cachedOutputContainers;
private MultiStatus wrapperStatus;
private int VALIDATE_FLAG;
public SingleRootUtil(IVirtualComponent component) {
this(component, null);
}
public SingleRootUtil(IVirtualComponent component, SingleRootCallback callback) {
this.aComponent = component;
this.callback = callback;
}
/**
* Returns true if this module has a simple structure based on a
* single-root folder, and false otherwise.
*
* In a single-root structure, all files that are contained within the root folder
* are part of the module, and are already in the correct module structure. No
* module resources exist outside of this single folder.
*
* @return true if this module has a single-root structure, and
* false otherwise
*/
public boolean isSingleRoot() {
return validateSingleRoot(INCLUDE_FIRST_ERROR).getSeverity() != IStatus.ERROR;
}
/**
* Will attempt to return the IContainer that counts as the "single-root".
* If this module does not qualify as a "single-root" module, this
* method will return null. Otherwise it will return an IContainer
* that may be used as the single-root container.
*
* @return IContainer representing single-root container
*/
public IContainer getSingleRoot() {
IStatus status = validateSingleRoot(GET_SINGLE_ROOT_CONTAINER);
if (status.getSeverity() == IStatus.INFO) {
IStatus[] children = ((MultiStatus) status).getChildren();
ISingleRootStatus rootStatus = (ISingleRootStatus) children[0];
return rootStatus.getSingleRoot();
}
return null;
}
/**
* Validates whether the component module has a single-root structure.
* An IStatus with a severity of OK is returned for a valid single-root
* structure. A MultiStatus containing children of type ISingleRootStatus
* is returned if any status codes were captured during the validation.
* A MultiStatus with a severity of INFO or WARNING is returned for a valid
* single-root structure containing status codes with no severities of ERROR.
* A MultiStatus with a severity of ERROR means the component does not have a
* valid single-root structure.
*
* @param flag - indicates the status codes (by severity) to capture during
* the validation. The INLCUDE_ALL flag will also capture the
* single-root container if a single-root structure was found.
* Valid flags are:
* INCLUDE_ALL
* INCLUDE_ERRORS_AND_WARNINGS
* INCLUDE_ERRORS
* INCLUDE_FIRST_ERROR
*
* @return IStatus
*/
public IStatus validateSingleRoot(int flag) {
VALIDATE_FLAG = flag;
wrapperStatus = null;
StructureEdit edit = null;
try {
edit = StructureEdit.getStructureEditForRead(getProject());
if (edit == null || edit.getComponent() == null) {
reportStatus(ISingleRootStatus.NO_COMPONENT_FOUND);
return getStatus();
}
// 229650 - check to see if the property 'useSingleRoot' is defined.
Boolean useSingleRoot = getUseSingleRootProperty(edit);
if (useSingleRoot != null) {
//check if it was set to false
if (!useSingleRoot.booleanValue()) {
reportStatus(ISingleRootStatus.EXPLICITLY_DISABLED);
}
else {
reportStatus(ISingleRootStatus.SINGLE_ROOT_FORCED, aComponent.getRootFolder().getUnderlyingFolder());
}
return getStatus();
}
// if there are any consumed references, this is not single-root
if (hasConsumableReferences(aComponent)) {
reportStatus(ISingleRootStatus.CONSUMABLE_REFERENCES_FOUND);
if (VALIDATE_FLAG == CANCEL)
return getStatus();
}
// if there are any linked resources then this is not a single-root module
if (rootFoldersHaveLinkedContent()) {
reportStatus(ISingleRootStatus.LINKED_RESOURCES_FOUND);
if (VALIDATE_FLAG == CANCEL)
return getStatus();
}
List resourceMaps = edit.getComponent().getResources();
// If the list is empty, return false
if (resourceMaps.size() < 1) {
reportStatus(ISingleRootStatus.NO_RESOURCE_MAPS_FOUND);
return getStatus();
}
if( callback != null && callback.canValidate(getProject())) {
callback.validate(this, aComponent, getProject(), resourceMaps);
} else {
validateProject(resourceMaps);
}
if (VALIDATE_FLAG == CANCEL)
return getStatus();
if (resourceMaps.size() == 1) {
ComponentResource mapping = (ComponentResource)resourceMaps.get(0);
if (isRootMapping(mapping)) {
IResource sourceResource = getProject().findMember(mapping.getSourcePath());
if (sourceResource != null && sourceResource.exists()) {
if (sourceResource instanceof IContainer && !isSourceContainer((IContainer) sourceResource)) {
reportStatus(ISingleRootStatus.SINGLE_ROOT_CONTAINER_FOUND, (IContainer) sourceResource);
return getStatus();
}
}
}
}
return getStatus();
} finally {
cachedOutputContainers = null;
cachedSourceContainers = null;
if (edit != null)
edit.dispose();
}
}
protected Boolean getUseSingleRootProperty(StructureEdit edit) {
WorkbenchComponent wbComp = edit.getComponent();
final List componentProperties = wbComp.getProperties();
if (componentProperties != null) {
final Iterator componentPropertiesIterator = componentProperties.iterator();
while (componentPropertiesIterator.hasNext()) {
Property wbProperty = (Property) componentPropertiesIterator.next();
if (USE_SINGLE_ROOT_PROPERTY.equals(wbProperty.getName())) {
return Boolean.valueOf(wbProperty.getValue());
}
}
}
return null;
}
protected boolean hasConsumableReferences(IVirtualComponent vc) {
IVirtualReference[] refComponents = vc.getReferences();
for (int i = 0; i < refComponents.length; i++) {
IVirtualReference reference = refComponents[i];
if (reference != null && reference.getDependencyType()==IVirtualReference.DEPENDENCY_TYPE_CONSUMES) {
return true;
}
}
return false;
}
private void validateProject(List resourceMaps) {
// Ensure there are only source folder component resource mappings to the root content folder
if (isRootResourceMapping(resourceMaps)) {
IContainer[] javaOutputFolders = getJavaOutputFolders();
// Verify only one java outputfolder
if (javaOutputFolders.length == 1) {
// By the time we get here we know: for any folders defined as source in the
// .component file that they are also java source folders.
if (!isSourceContainer(javaOutputFolders[0])) {
// The single output folder is NOT a source folder so this is single-rooted. Since the
// output folder (something like classes or bin) is not a source folder, JDT copies all files
// (including non Java files) to this folder, so every resource needed at runtime is located
// in a single directory.
reportStatus(ISingleRootStatus.SINGLE_ROOT_CONTAINER_FOUND, javaOutputFolders[0]);
return;
}
// Verify the java output folder is the same as one of the content roots
IPath javaOutputPath = getJavaOutputFolders()[0].getProjectRelativePath();
IContainer[] rootFolders = aComponent.getRootFolder().getUnderlyingFolders();
for (int i=0; i < rootFolders.length; i++) {
IPath compRootPath = rootFolders[i].getProjectRelativePath();
if (javaOutputPath.equals(compRootPath)) {
reportStatus(ISingleRootStatus.SINGLE_ROOT_CONTAINER_FOUND, aComponent.getRootFolder().getUnderlyingFolder());
return;
}
}
reportStatus(ISingleRootStatus.JAVA_OUTPUT_NOT_A_CONTENT_ROOT);
}
else {
reportStatus(ISingleRootStatus.JAVA_OUTPUT_GREATER_THAN_1);
}
}
}
public IContainer[] getJavaOutputFolders() {
if (cachedOutputContainers == null)
cachedOutputContainers = getJavaOutputFolders(aComponent);
return cachedOutputContainers;
}
public static IContainer[] getJavaOutputFolders(IVirtualComponent component) {
if (component == null)
return new IContainer[0];
List<IContainer> l = JavaLiteUtilities.getJavaOutputContainers(component);
return l.toArray(new IContainer[l.size()]);
}
/**
* Checks if the path argument is to a source container for the project.
*
* @param a workspace relative full path
* @return is path a source container?
*/
public boolean isSourceContainer(IContainer sourceContainer) {
if (cachedSourceContainers == null) {
cachedSourceContainers = getSourceContainers(aComponent);
}
return cachedSourceContainers.contains(sourceContainer);
}
public static List<IContainer> getSourceContainers(IVirtualComponent component) {
if (component == null)
Collections.emptyList();
return JavaLiteUtilities.getJavaSourceContainers(component);
}
/*
* This method returns true if the root folders of this component have any linked resources (folder or file);
* Otherwise false is returned.
*/
private boolean rootFoldersHaveLinkedContent() {
if (this.aComponent != null) {
final IContainer[] rootFolders = this.aComponent.getRootFolder().getUnderlyingFolders();
for (int i = 0; i < rootFolders.length; i++) {
try {
boolean hasLinkedContent = this.hasLinkedContent(rootFolders[i]);
if (hasLinkedContent) {
return true;
}
}
catch (CoreException coreEx) {
CommonFrameworksPlugin.logError(coreEx);
}
}
}
return false;
}
/*
* If the resource to check is a file then this method will return true if the file is linked. If the resource to
* check is a folder then this method will return true if it, any of its sub directories, or any file contained
* with-in this directory of any of it's sub directories are linked. Otherwise false is returned.
*/
private boolean hasLinkedContent(final IResource resourceToCheck) throws CoreException {
if ((resourceToCheck != null) && resourceToCheck.isAccessible()) {
// skip non-accessible files
if (resourceToCheck.isLinked()) {
return true;
}
switch (resourceToCheck.getType()) {
case IResource.FOLDER:
// recursively check sub directory contents
final IResource[] subDirContents = ((IFolder) resourceToCheck).members();
for (int i = 0; i < subDirContents.length; i++) {
if (hasLinkedContent(subDirContents[i])) {
return true;
}
}
break;
case IResource.FILE:
return resourceToCheck.isLinked();
default:
// skip as we only care about files and folders
break;
}
}
return false;
}
/**
* Ensure that any component resource mappings are for source folders and
* that they map to the root content folder
*
* @param resourceMaps
* @return boolean
*/
private boolean isRootResourceMapping(List resourceMaps) {
for (int i=0; i < resourceMaps.size(); i++) {
ComponentResource resourceMap = (ComponentResource) resourceMaps.get(i);
// Verify it maps to "/" for the content root
if (!isRootMapping(resourceMap)) {
reportStatus(ISingleRootStatus.RUNTIME_PATH_NOT_ROOT, resourceMap.getRuntimePath());
if (VALIDATE_FLAG == CANCEL) return false;
}
// verify it is also a src container
IPath sourcePath = resourceMap.getSourcePath();
IResource sourceResource = getProject().findMember(sourcePath);
if (sourceResource != null && sourceResource.exists()) {
if (sourceResource instanceof IContainer && !isSourceContainer((IContainer) sourceResource)) {
reportStatus(ISingleRootStatus.SOURCE_NOT_JAVA_CONTAINER, sourcePath);
}
}
else {
reportStatus(ISingleRootStatus.SOURCE_PATH_NOT_FOUND, sourcePath);
}
if (VALIDATE_FLAG == CANCEL) return false;
}
return true;
}
public boolean isRootMapping(ComponentResource map) {
// Verify it maps to "/" for the content root
if (map.getRuntimePath().equals(Path.ROOT))
return true;
return false;
}
public IProject getProject() {
return aComponent.getProject();
}
public void reportStatus(int code) {
reportStatus(code, null, null);
}
public void reportStatus(int code, IContainer container) {
reportStatus(code, null, container);
}
public void reportStatus(int code, IPath path) {
reportStatus(code, path, null);
}
public void reportStatus(int code, IPath path, IContainer container) {
ISingleRootStatus status = new SingleRootStatus(code, path, container);
if (status.getSeverity() == IStatus.ERROR) {
if ((VALIDATE_FLAG & INCLUDE_FIRST_ERROR) != 0) {
VALIDATE_FLAG = CANCEL;
addStatus(status);
}
else if ((VALIDATE_FLAG & INCLUDE_ERRORS) != 0) {
addStatus(status);
}
}
else if (status.getSeverity() == IStatus.WARNING && (VALIDATE_FLAG & WARNINGS) != 0) {
addStatus(status);
}
else if (status.getSeverity() == IStatus.INFO && (VALIDATE_FLAG & INFO) != 0) {
addStatus(status);
}
}
public IStatus getStatus() {
if (wrapperStatus != null) {
return wrapperStatus;
}
return Status.OK_STATUS;
}
private void addStatus(ISingleRootStatus status) {
if (wrapperStatus == null) {
wrapperStatus = new MultiStatus(CommonFrameworksPlugin.PLUGIN_ID, 0, new IStatus[] { status }, null, null);
} else {
wrapperStatus.add(status);
}
}
}