Skip to main content
summaryrefslogtreecommitdiffstats
blob: 0a5f438476f44ef2c80d1a1e7477f57b9f447526 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/*******************************************************************************
 * 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);
		}
	}
	
}

Back to the top