org.aspectj.ajdt.internal.compiler.lookup.PushinCollector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjtools Show documentation
Show all versions of aspectjtools Show documentation
Tools from the AspectJ project
/* *******************************************************************
* Copyright (c) 2010 Contributors
* 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:
* Andy Clement - SpringSource
* ******************************************************************/
package org.aspectj.ajdt.internal.compiler.lookup;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.aspectj.ajdt.internal.compiler.IOutputClassFileNameProvider;
import org.aspectj.asm.internal.CharOperation;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.patterns.ExactTypePattern;
import org.aspectj.weaver.patterns.TypePattern;
/**
* Collects up information about the application of ITDs and relevant declares - it can then output source code as if those ITDs had
* been pushed in. Supports the simulated push-in of:
*
* - declare at_type
*
- itd method
*
- itd field
*
- itd ctor
*
- declare parents
*
*
* @author Andy Clement
* @since 1.6.9
*/
public class PushinCollector {
private final static String OPTION_SUFFIX = "suffix";
private final static String OPTION_DIR = "dir";
private final static String OPTION_PKGDIRS = "packageDirs";
private final static String OPTION_DEBUG = "debug";
private final static String OPTION_LINENUMS = "lineNums";
private final static String OPTION_DUMPUNCHANGED = "dumpUnchanged";
private World world;
private boolean debug = false;
private boolean dumpUnchanged = false;
private IOutputClassFileNameProvider outputFileNameProvider;
private String specifiedOutputDirectory;
private boolean includePackageDirs;
private boolean includeLineNumberComments;
private String suffix;
// This first collection stores the 'text' for the declarations.
private Map codeRepresentation = new HashMap();
// This stores the new annotations
private Map> additionalAnnotations = new HashMap>();
// This stores the new parents
private Map> additionalParents = new HashMap>();
// This indicates which types are affected by which intertype declarations
private Map> newDeclarations = new HashMap>();
private PushinCollector(World world, Properties configuration) {
this.world = world;
// Configure the instance based on the input properties
specifiedOutputDirectory = configuration.getProperty(OPTION_DIR);
includePackageDirs = configuration.getProperty(OPTION_PKGDIRS, "true").equalsIgnoreCase("true");
includeLineNumberComments = configuration.getProperty(OPTION_LINENUMS, "false").equalsIgnoreCase("true");
debug = configuration.getProperty(OPTION_DEBUG, "false").equalsIgnoreCase("true");
dumpUnchanged = configuration.getProperty(OPTION_DUMPUNCHANGED, "false").equalsIgnoreCase("true");
String specifiedSuffix = configuration.getProperty(OPTION_SUFFIX, "pushedin");
if (specifiedSuffix.length() > 0) {
StringBuilder sb = new StringBuilder();
sb.append(".").append(specifiedSuffix);
suffix = sb.toString();
} else {
suffix = "";
}
if (debug) {
System.out.println("Configured to create pushin side files:" + configuration);
System.out.println("dumpUnchanged=" + dumpUnchanged + "\nincludePackageDirs=" + includePackageDirs);
}
}
private String getName(CompilationUnitDeclaration cud) {
if (cud == null) {
return "UNKNOWN";
}
if (cud.scope == null) {
return "UNKNOWN";
}
if (cud.scope.referenceContext == null) {
return "UNKNOWN";
}
return new String(cud.scope.referenceContext.getFileName());
}
/**
* @return true if the type is affected by something (itd/declare anno/declare parent)
*/
private boolean hasChanged(SourceTypeBinding stb) {
return newDeclarations.get(stb) != null || additionalParents.get(stb) != null || additionalAnnotations.get(stb) != null;
}
/**
* Produce the modified source that looks like the itds and declares have been applied.
*/
public void dump(CompilationUnitDeclaration compilationUnitDeclaration, String outputFileLocation) {
if (compilationUnitDeclaration.scope.topLevelTypes == null || compilationUnitDeclaration.scope.topLevelTypes.length == 0) {
return;
}
SourceTypeBinding[] types = compilationUnitDeclaration.scope.topLevelTypes;
if (types == null || types.length == 0) {
return;
}
// Process all types working from end to start as whatever we do (insert-wise) will affect locations later in the file
StringBuffer sourceContents = new StringBuffer();
// put the whole original file in the buffer
boolean changed = false;
sourceContents.append(compilationUnitDeclaration.compilationResult.compilationUnit.getContents());
for (int t = types.length - 1; t >= 0; t--) {
SourceTypeBinding sourceTypeBinding = compilationUnitDeclaration.scope.topLevelTypes[t];
if (!hasChanged(sourceTypeBinding)) {
if (debug) {
System.out.println(getName(compilationUnitDeclaration) + " has nothing applied");
}
continue;
}
changed = true;
int bodyEnd = sourceTypeBinding.scope.referenceContext.bodyEnd; // last '}' of the type
List declarations = newDeclarations.get(sourceTypeBinding);
if (declarations != null) {
for (AbstractMethodDeclaration md : declarations) {
RepresentationAndLocation ral = codeRepresentation.get(md);
if (ral != null) {
String s = ral.textualRepresentation;
sourceContents.insert(bodyEnd, "\n" + s + "\n");
if (includeLineNumberComments && ral.linenumber != -1) {
sourceContents.insert(bodyEnd, "\n // " + ral.linenumber);
}
}
}
}
// fix up declare parents - may need to attach them to existing ones
TypeReference sr = sourceTypeBinding.scope.referenceContext.superclass;
TypeReference[] trs = sourceTypeBinding.scope.referenceContext.superInterfaces;
List newParents = additionalParents.get(sourceTypeBinding);
StringBuffer extendsString = new StringBuffer();
StringBuffer implementsString = new StringBuffer();
if (newParents != null && newParents.size() > 0) {
for (ExactTypePattern newParent : newParents) {
ResolvedType newParentType = newParent.getExactType().resolve(world);
if (newParentType.isInterface()) {
if (implementsString.length() > 0) {
implementsString.append(",");
}
implementsString.append(newParentType.getName());
} else {
extendsString.append(newParentType.getName());
}
}
if (trs == null && sr == null) {
// nothing after the class declaration, let's insert what we need to
// Find the position just before the type opening '{'
int beforeOpeningCurly = sourceTypeBinding.scope.referenceContext.bodyStart - 1;
if (implementsString.length() != 0) {
implementsString.insert(0, "implements ");
implementsString.append(" ");
sourceContents.insert(beforeOpeningCurly, implementsString);
}
if (extendsString.length() != 0) {
extendsString.insert(0, "extends ");
extendsString.append(" ");
sourceContents.insert(beforeOpeningCurly, extendsString);
}
}
}
List annos = additionalAnnotations.get(sourceTypeBinding);
if (annos != null && annos.size() > 0) {
for (String anno : annos) {
sourceContents.insert(sourceTypeBinding.scope.referenceContext.declarationSourceStart, anno + " ");
}
}
}
if (changed || (!changed && dumpUnchanged)) {
try {
if (debug) {
System.out.println("Pushed in output file being written to " + outputFileLocation);
System.out.println(sourceContents);
}
FileWriter fos = new FileWriter(new File(outputFileLocation));
fos.write(sourceContents.toString());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Encapsulates a text representation (source code) for a member and the line where it was declared.
*/
private static class RepresentationAndLocation {
String textualRepresentation;
int linenumber;
public RepresentationAndLocation(String textualRepresentation, int linenumber) {
this.textualRepresentation = textualRepresentation;
this.linenumber = linenumber;
}
}
public void recordInterTypeMethodDeclarationCode(AbstractMethodDeclaration md, String s, int line) {
codeRepresentation.put(md, new RepresentationAndLocation(s, line));
}
public void recordInterTypeFieldDeclarationCode(AbstractMethodDeclaration md, String s, int line) {
codeRepresentation.put(md, new RepresentationAndLocation(s, line));
}
public void recordInterTypeConstructorDeclarationCode(AbstractMethodDeclaration md, String s, int line) {
codeRepresentation.put(md, new RepresentationAndLocation(s, line));
}
// public void recordDeclareAnnotationDeclarationCode(AbstractMethodDeclaration md, String value) {
// codeRepresentation.put(md, new RepresentationAndLocation(value, -1));
// }
public void tagAsMunged(SourceTypeBinding sourceType, AbstractMethodDeclaration sourceMethod) {
if (sourceMethod == null) {
// seen when an ITD field is made onto an interface. It matches, but the sourceMethod is null.
// can be null for binary weave (there is no source method)
return;
}
List amds = newDeclarations.get(sourceType);
if (amds == null) {
amds = new ArrayList();
newDeclarations.put(sourceType, amds);
}
amds.add(sourceMethod);
}
public void tagAsMunged(SourceTypeBinding sourceType, String annotationString) {
List annos = additionalAnnotations.get(sourceType);
if (annos == null) {
annos = new ArrayList();
additionalAnnotations.put(sourceType, annos);
}
annos.add(annotationString);
}
public void dump(CompilationUnitDeclaration unit) {
String outputFile = getOutputFileFor(unit);
if (debug) {
System.out
.println("Output location is " + outputFile + " for " + new String(unit.scope.referenceContext.getFileName()));
}
dump(unit, outputFile);
}
private String getOutputFileFor(CompilationUnitDeclaration unit) {
StringBuffer sb = new StringBuffer();
// Create the directory portion of the output location
if (specifiedOutputDirectory != null) {
sb.append(specifiedOutputDirectory).append(File.separator);
} else {
String sss = outputFileNameProvider.getOutputClassFileName("A".toCharArray(), unit.compilationResult);
sb.append(sss, 0, sss.length() - 7);
}
// Create the subdirectory structure matching the package declaration
if (includePackageDirs) {
char[][] packageName = unit.compilationResult.packageName;
if (packageName != null) {
sb.append(CharOperation.concatWith(unit.compilationResult.packageName, File.separatorChar));
sb.append(File.separator);
}
}
new File(sb.toString()).mkdirs();
// Create the filename portion
String filename = new String(unit.getFileName()); // gives 'n:\A.java'
int index = filename.lastIndexOf('/');
int index2 = filename.lastIndexOf('\\');
if (index > index2) {
sb.append(filename.substring(index + 1));
} else if (index2 > index) {
sb.append(filename.substring(index2 + 1));
} else {
sb.append(filename);
}
// Add the suffix (may be an empty string)
sb.append(suffix);
return sb.toString();
}
public void tagAsMunged(SourceTypeBinding sourceType, TypePattern typePattern) {
if (typePattern instanceof ExactTypePattern) {
List annos = additionalParents.get(sourceType);
if (annos == null) {
annos = new ArrayList();
additionalParents.put(sourceType, annos);
}
annos.add((ExactTypePattern) typePattern);
}
}
/**
* Checks if the aspectj.pushin property is set - this is the main condition for triggering the creation of pushed-in source
* files. If not set just to 'true', the value of the property is processed as configuration. Configurable options are:
*
* - dir=XXXX - to set the output directory for the pushed in files
*
- suffix=XXX - to set the suffix, can be blank to get just '.java'
*
*/
public static PushinCollector createInstance(World world) {
try {
String property = System.getProperty("aspectj.pushin");
if (property == null) {
return null;
}
Properties configuration = new Properties();
StringTokenizer tokenizer = new StringTokenizer(property, ",");
while (tokenizer.hasMoreElements()) {
String token = tokenizer.nextToken();
// Simplest thing to do is turn it on 'aspectj.pushin=true'
if (token.equalsIgnoreCase("true")) {
continue;
}
int positionOfEquals = token.indexOf("=");
if (positionOfEquals != -1) {
// it is an option
String optionName = token.substring(0, positionOfEquals);
String optionValue = token.substring(positionOfEquals + 1);
configuration.put(optionName, optionValue);
} else {
// it is a flag
configuration.put(token, "true");
}
}
return new PushinCollector(world, configuration);
} catch (Exception e) {
// unable to read system properties...
}
return null;
}
public void setOutputFileNameProvider(IOutputClassFileNameProvider outputFileNameProvider) {
this.outputFileNameProvider = outputFileNameProvider;
}
}