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 }