content: /*******************************************************************************
   * Copyright (c) 2000, 2015 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
   *     Matt Chapman, mpchapman@gmail.com - 89977 Make JDT .java agnostic
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.ui.wizards;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Set;
  
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IResourceProxy;
  import org.eclipse.core.resources.IResourceProxyVisitor;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IPath;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.NullProgressMonitor;
  import org.eclipse.core.runtime.OperationCanceledException;
  import org.eclipse.core.runtime.Path;
  import org.eclipse.wst.jsdt.core.IIncludePathEntry;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.JavaScriptConventions;
  import org.eclipse.wst.jsdt.core.JavaScriptCore;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.ToolFactory;
  import org.eclipse.wst.jsdt.core.compiler.IScanner;
  import org.eclipse.wst.jsdt.core.compiler.ITerminalSymbols;
  import org.eclipse.wst.jsdt.core.compiler.InvalidInputException;
  import org.eclipse.wst.jsdt.internal.core.ClasspathEntry;
  import org.eclipse.wst.jsdt.internal.core.util.Util;
  import org.eclipse.wst.jsdt.ui.PreferenceConstants;
  
  import com.ibm.icu.text.Collator;
  
  public class ClassPathDetector implements IResourceProxyVisitor {
  		
  	private HashMap fSourceFolders;
  	private IProject fProject;
  	private IIncludePathEntry[] fResultClasspath;
  	private IProgressMonitor fMonitor;
  	private IPath[] fExclusionPatterns;
  	
  	private static class CPSorter implements Comparator {
  		private Collator fCollator= Collator.getInstance();
  		public int compare(Object o1, Object o2) {
  			IIncludePathEntry e1= (IIncludePathEntry) o1;
  			IIncludePathEntry e2= (IIncludePathEntry) o2;
  			return fCollator.compare(e1.getPath().toString(), e2.getPath().toString());
  		}
  	}
  
  	public ClassPathDetector(IProject project, IProgressMonitor monitor) throws CoreException {
  		this(project, monitor, ClasspathEntry.EXCLUDE_NONE);
  	}
  	
  	public ClassPathDetector(IProject project, IProgressMonitor monitor,
  				IPath[] exclusionPatterns) throws CoreException {
  		fSourceFolders= new HashMap();
  		fProject= project;
  		fResultClasspath= null;
  		fExclusionPatterns = exclusionPatterns;
  		
  		if (monitor == null) {
  			monitor = new NullProgressMonitor();
  		}
  			
  		detectClasspath(monitor);
  	}
  	
  	private char[][] convertPatternsToChars(IPath path, IPath[] patterns) {
  		int length = patterns.length;
  		char[][] patternChars = new char[length][];
  		IPath prefixPath = path.removeTrailingSeparator();
  		for (int i = 0; i < length; i++) {
  			patternChars[i] =
  				prefixPath.append(patterns[i]).toString().toCharArray();
  		}
  		return patternChars;
  	}
  	
  	/**
  	 * Method detectClasspath.
  	 * @param monitor The progress monitor (not null)
  	 * @throws CoreException 
  	 */
  	private void detectClasspath(IProgressMonitor monitor) throws CoreException {
  		try {
  			monitor.beginTask(NewWizardMessages.ClassPathDetector_operation_description, 4); 
  			
  			fMonitor= monitor;
  			fProject.accept(this, IResource.NONE);
  			monitor.worked(1);
  			
  			ArrayList cpEntries= new ArrayList();
  
  			detectSourceFolders(cpEntries);
  			if (monitor.isCanceled()) {
  				throw new OperationCanceledException();
  			}
  		
  			monitor.worked(1);
  
  			if (cpEntries.isEmpty()) {
  				return;
  			}
  			
  			IIncludePathEntry[] jreEntries= PreferenceConstants.getDefaultJRELibrary();
  			for (int i= 0; i < jreEntries.length; i++) {
  				cpEntries.add(jreEntries[i]);
  			}
  
  			IIncludePathEntry[] entries= (IIncludePathEntry[]) cpEntries.toArray(new IIncludePathEntry[cpEntries.size()]);
  			if (!JavaScriptConventions.validateClasspath(JavaScriptCore.create(fProject), entries).isOK()) {
  				return;
  			}
  
  			fResultClasspath= entries;
  		} finally {
  			monitor.done();
  		}
  	}
  
  	private void detectSourceFolders(ArrayList resEntries) {
  		ArrayList res= new ArrayList();
  		Set sourceFolderSet= fSourceFolders.keySet();
  		for (Iterator iter= sourceFolderSet.iterator(); iter.hasNext();) {
  			IPath path= (IPath) iter.next();
  			char[][] excludedPatternChars = convertPatternsToChars(path, fExclusionPatterns);
  			if (!Util.isExcluded(path, null, excludedPatternChars, true)) {
  				Set<IPath> excluded= new HashSet<IPath>();
  				for (Iterator inner= sourceFolderSet.iterator(); inner.hasNext();) {
  					IPath other= (IPath) inner.next();
  					if (!path.equals(other) && path.isPrefixOf(other)) {
  						IPath pathToExclude= other.removeFirstSegments(path.segmentCount()).addTrailingSeparator();
  						excluded.add(pathToExclude);
  					}
  				}
  				excluded.addAll(Arrays.asList(fExclusionPatterns));
  				IPath[] excludedPaths= (IPath[]) excluded.toArray(new IPath[excluded.size()]);
  				IIncludePathEntry entry= JavaScriptCore.newSourceEntry(path, excludedPaths);
  				res.add(entry);
  			}
  		}
  		Collections.sort(res, new CPSorter());
  		resEntries.addAll(res);
  	}
  
  	private void visitCompilationUnit(IFile file) {
  		IJavaScriptUnit cu= JavaScriptCore.createCompilationUnitFrom(file);
  		if (cu != null) {
  			IJavaScriptUnit workingCopy= null;
  			try {
  				workingCopy= cu.getWorkingCopy(null);
  				IPath relPath= getPackagePath(workingCopy.getSource());
  				IPath packPath= file.getParent().getFullPath();
  				String cuName= file.getName();
  				if (relPath == null) {
  					addToMap(fSourceFolders, packPath, new Path(cuName));
  				} else {
  					IPath folderPath= getFolderPath(packPath, relPath);
  					if (folderPath != null) {
  						addToMap(fSourceFolders, folderPath, relPath.append(cuName));
  					}					
  				}				
  			} catch (JavaScriptModelException e) {
  				// ignore
  			} catch (InvalidInputException e) {
  				// ignore
  			} finally {
  				if (workingCopy != null) {
  					try {
  						workingCopy.discardWorkingCopy();
  					} catch (JavaScriptModelException ignore) {
  					}
  				}
  			}
  		}
  	}
  	
  	private IPath getPackagePath(String source) throws InvalidInputException {
  		IScanner scanner= ToolFactory.createScanner(false, false, false, false);
  		scanner.setSource(source.toCharArray());
  		scanner.resetTo(0, source.length() - 1);
  		int tok= scanner.getNextToken();
  		if (tok != ITerminalSymbols.TokenNamepackage) {
  			return null;
  		}
  		IPath res= Path.EMPTY;
  		do {
  			tok= scanner.getNextToken();
  			if (tok == ITerminalSymbols.TokenNameIdentifier) {
  				res= res.append(new String(scanner.getCurrentTokenSource()));
  			} else {
  				return res;
  			}
  			tok= scanner.getNextToken();
  		} while (tok == ITerminalSymbols.TokenNameDOT);
  		
  		return res;
  	}
  	
  	
  	private void addToMap(HashMap map, IPath folderPath, IPath relPath) {
  		List list= (List) map.get(folderPath);
  		if (list == null) {
  			list= new ArrayList(50);
  			map.put(folderPath, list);
  		}		
  		list.add(relPath);
  	}
  
  	private IPath getFolderPath(IPath packPath, IPath relpath) {
  		int remainingSegments= packPath.segmentCount() - relpath.segmentCount();
  		if (remainingSegments >= 0) {
  			IPath common= packPath.removeFirstSegments(remainingSegments);
  			if (common.equals(relpath)) {
  				return packPath.uptoSegment(remainingSegments);
  			}
  		}
  		return null;
  	}
  	
  	private boolean isValidCUName(String name) {
  		return !JavaScriptConventions.validateCompilationUnitName(name).matches(IStatus.ERROR);
  	}	
  
  	/* (non-Javadoc)
  	 * @see org.eclipse.core.resources.IResourceProxyVisitor#visit(org.eclipse.core.resources.IResourceProxy)
  	 */
  	public boolean visit(IResourceProxy proxy) {
  		if (fMonitor.isCanceled()) {
  			throw new OperationCanceledException();
  		}
  		
  		if (proxy.getType() == IResource.FILE) {
  			String name= proxy.getName();
  			if (isValidCUName(name))
  				visitCompilationUnit((IFile) proxy.requestResource());
  			return false;
  		}
  		return true;
  	}
  		
  	public IIncludePathEntry[] getClasspath() {
  		if (fResultClasspath == null)
  			return new IIncludePathEntry[0];
  		return fResultClasspath;
  	}
  }