1 /******************************************************************************* 2 * Copyright (c) 2012, 2017 Ecliptical Software Inc. and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Ecliptical Software Inc. - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.pde.ds.internal.annotations; 15 16 import java.util.Collection; 17 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.Map; 20 21 import org.eclipse.core.resources.ICommand; 22 import org.eclipse.core.resources.IContainer; 23 import org.eclipse.core.resources.IFile; 24 import org.eclipse.core.resources.IFolder; 25 import org.eclipse.core.resources.IProject; 26 import org.eclipse.core.resources.IProjectDescription; 27 import org.eclipse.core.resources.IResource; 28 import org.eclipse.core.runtime.CoreException; 29 import org.eclipse.core.runtime.IPath; 30 import org.eclipse.jdt.core.ICompilationUnit; 31 import org.eclipse.jdt.core.IJavaElement; 32 import org.eclipse.jdt.core.compiler.BuildContext; 33 import org.eclipse.jdt.core.compiler.CategorizedProblem; 34 import org.eclipse.jdt.core.dom.ASTRequestor; 35 import org.eclipse.jdt.core.dom.CompilationUnit; 36 37 public class AnnotationProcessor extends ASTRequestor { 38 39 private static final String DS_BUILDER = "org.eclipse.pde.ds.core.builder"; //$NON-NLS-1$ 40 41 static final Debug debug = Debug.getDebug("ds-annotation-builder/processor"); //$NON-NLS-1$ 42 43 private final ProjectContext context; 44 45 private final Map<ICompilationUnit, BuildContext> fileMap; 46 47 private boolean hasBuilder; 48 AnnotationProcessor(ProjectContext context, Map<ICompilationUnit, BuildContext> fileMap)49 public AnnotationProcessor(ProjectContext context, Map<ICompilationUnit, BuildContext> fileMap) { 50 this.context = context; 51 this.fileMap = fileMap; 52 } 53 getCompilationUnitKey(ICompilationUnit source)54 static String getCompilationUnitKey(ICompilationUnit source) { 55 IJavaElement parent = source.getParent(); 56 if (parent == null) { 57 return source.getElementName(); 58 } 59 60 return String.format("%s/%s", parent.getElementName().replace('.', '/'), source.getElementName()); //$NON-NLS-1$ 61 } 62 63 @Override acceptAST(ICompilationUnit source, CompilationUnit ast)64 public void acceptAST(ICompilationUnit source, CompilationUnit ast) { 65 // determine CU key 66 String cuKey = getCompilationUnitKey(source); 67 68 context.getUnprocessed().remove(cuKey); 69 70 ProjectState state = context.getState(); 71 HashMap<String, String> dsKeys = new HashMap<>(); 72 HashSet<DSAnnotationProblem> problems = new HashSet<>(); 73 74 ast.accept(new AnnotationVisitor(this, state, dsKeys, problems)); 75 76 // track abandoned files (may be garbage) 77 Collection<String> oldDSKeys = state.updateMappings(cuKey, dsKeys); 78 if (oldDSKeys != null) { 79 oldDSKeys.removeAll(dsKeys.values()); 80 context.getAbandoned().addAll(oldDSKeys); 81 } 82 83 if (!problems.isEmpty()) { 84 char[] filename = source.getResource().getFullPath().toString().toCharArray(); 85 for (DSAnnotationProblem problem : problems) { 86 problem.setOriginatingFileName(filename); 87 if (problem.getSourceStart() >= 0) { 88 problem.setSourceLineNumber(ast.getLineNumber(problem.getSourceStart())); 89 } 90 } 91 92 BuildContext buildContext = fileMap.get(source); 93 if (buildContext != null) { 94 buildContext.recordNewProblems(problems.toArray(new CategorizedProblem[problems.size()])); 95 } 96 } 97 } 98 ensureDSProject(IProject project)99 private void ensureDSProject(IProject project) throws CoreException { 100 IProjectDescription description = project.getDescription(); 101 ICommand[] commands = description.getBuildSpec(); 102 103 for (ICommand command : commands) { 104 if (DS_BUILDER.equals(command.getBuilderName())) { 105 return; 106 } 107 } 108 109 ICommand[] newCommands = new ICommand[commands.length + 1]; 110 System.arraycopy(commands, 0, newCommands, 0, commands.length); 111 ICommand command = description.newCommand(); 112 command.setBuilderName(DS_BUILDER); 113 newCommands[newCommands.length - 1] = command; 114 description.setBuildSpec(newCommands); 115 project.setDescription(description, null); 116 } 117 ensureExists(IFolder folder)118 private void ensureExists(IFolder folder) throws CoreException { 119 if (folder.exists()) { 120 return; 121 } 122 123 IContainer parent = folder.getParent(); 124 if (parent != null && parent.getType() == IResource.FOLDER) { 125 ensureExists((IFolder) parent); 126 } 127 128 folder.create(true, true, null); 129 } 130 verifyOutputLocation(IFile file)131 void verifyOutputLocation(IFile file) throws CoreException { 132 if (hasBuilder) { 133 return; 134 } 135 136 hasBuilder = true; 137 IProject project = file.getProject(); 138 139 IPath parentPath = file.getParent().getProjectRelativePath(); 140 if (!parentPath.isEmpty()) { 141 IFolder folder = project.getFolder(parentPath); 142 ensureExists(folder); 143 } 144 145 try { 146 ensureDSProject(project); 147 } catch (CoreException e) { 148 Activator.log(e); 149 } 150 } 151 }