| /******************************************************************************* |
| * 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(IVirtualComponent vc); |
| 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(); |
| } |
| |
| if (aComponent.isBinary()) { |
| reportStatus(ISingleRootStatus.BINARY_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(aComponent)) { |
| callback.validate(this, aComponent, getProject(), resourceMaps); |
| } |
| if (VALIDATE_FLAG != CANCEL) { |
| validateProject(resourceMaps); |
| } |
| 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) { |
| VALIDATE_FLAG = CANCEL; |
| addStatus(status); |
| } |
| } |
| |
| public int getValidateFlag() { |
| return VALIDATE_FLAG; |
| } |
| |
| public void setValidateFlag(int validateFlag) { |
| VALIDATE_FLAG = validateFlag; |
| } |
| |
| 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); |
| } |
| } |
| |
| } |