/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.support;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import spoon.Launcher;
import spoon.SpoonException;
import spoon.compiler.Environment;
import spoon.compiler.InvalidClassPathException;
import spoon.compiler.SpoonFile;
import spoon.compiler.SpoonFolder;
import spoon.processing.FileGenerator;
import spoon.processing.ProblemFixer;
import spoon.processing.ProcessingManager;
import spoon.processing.Processor;
import spoon.processing.ProcessorProperties;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ParentNotInitializedException;
import spoon.reflect.factory.Factory;
import spoon.support.compiler.FileSystemFolder;
import java.io.File;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* This class implements a simple Spoon environment that reports messages in the
* standard output stream (Java-compliant).
*/
public class StandardEnvironment implements Serializable, Environment {
private static final long serialVersionUID = 1L;
private FileGenerator<? extends CtElement> defaultFileGenerator;
private int errorCount = 0;
private transient Factory factory;
ProcessingManager manager;
private boolean processingStopped = false;
private boolean autoImports = false;
private int warningCount = 0;
private String[] sourceClasspath = null;
private boolean preserveLineNumbers = false;
private boolean copyResources = true;
private boolean enableComments = false;
private Logger logger = Launcher.LOGGER;
private Level level = Level.OFF;
private boolean shouldCompile = false;
private boolean skipSelfChecks;
/**
* Creates a new environment with a <code>null</code> default file
* generator.
*/
public StandardEnvironment() {
}
@Override
public void debugMessage(String message) {
logger.debug(message);
}
@Override
public boolean isAutoImports() {
return autoImports;
}
@Override
public void setAutoImports(boolean autoImports) {
this.autoImports = autoImports;
// TODO: unexpected behaviour could occur, if we reset the autoimport AFTER the pretty printer is created...
}
@Override
public FileGenerator<? extends CtElement> getDefaultFileGenerator() {
return defaultFileGenerator;
}
@Override
public Factory getFactory() {
return factory;
}
@Override
public Level getLevel() {
return this.level;
}
@Override
public void setLevel(String level) {
this.level = toLevel(level);
logger.setLevel(this.level);
}
@Override
public boolean shouldCompile() {
return shouldCompile;
}
@Override
public void setShouldCompile(boolean shouldCompile) {
this.shouldCompile = shouldCompile;
}
@Override
public boolean checksAreSkipped() {
return skipSelfChecks;
}
@Override
public void setSelfChecks(boolean skip) {
skipSelfChecks = skip;
}
private Level toLevel(String level) {
if (level == null || level.isEmpty()) {
throw new SpoonException("Wrong level given at Spoon.");
}
return Level.toLevel(level, Level.ALL);
}
@Override
public ProcessingManager getManager() {
return manager;
}
Map<String, ProcessorProperties> processorProperties = new TreeMap<>();
@Override
public ProcessorProperties getProcessorProperties(String processorName) throws Exception {
if (processorProperties.containsKey(processorName)) {
return processorProperties.get(processorName);
}
return null;
}
/**
* Tells if the processing is stopped, generally because one of the
* processors called {@link #setProcessingStopped(boolean)} after reporting
* an error.
*/
@Override
public boolean isProcessingStopped() {
return processingStopped;
}
private void prefix(StringBuffer buffer, Level level) {
if (level == Level.ERROR) {
buffer.append("error: ");
errorCount++;
} else if (level == Level.WARN) {
buffer.append("warning: ");
warningCount++;
}
}
@Override
public void report(Processor<?> processor, Level level, CtElement element, String message) {
StringBuffer buffer = new StringBuffer();
prefix(buffer, level);
// Adding message
buffer.append(message);
// Add sourceposition (javac format)
try {
CtType<?> type = (element instanceof CtType) ? (CtType<?>) element : element.getParent(CtType.class);
SourcePosition sp = element.getPosition();
if (sp == null) {
buffer.append(" (Unknown Source)");
} else {
buffer.append(" at " + type.getQualifiedName() + ".");
CtExecutable<?> exe = (element instanceof CtExecutable) ? (CtExecutable<?>) element : element.getParent(CtExecutable.class);
if (exe != null) {
buffer.append(exe.getSimpleName());
}
buffer.append("(" + sp.getFile().getName() + ":" + sp.getLine() + ")");
}
} catch (ParentNotInitializedException e) {
buffer.append(" (invalid parent)");
}
print(buffer.toString(), level);
}
@Override
public void report(Processor<?> processor, Level level, CtElement element, String message, ProblemFixer<?>... fixes) {
report(processor, level, element, message);
}
@Override
public void report(Processor<?> processor, Level level, String message) {
StringBuffer buffer = new StringBuffer();
prefix(buffer, level);
// Adding message
buffer.append(message);
print(buffer.toString(), level);
}
private void print(String message, Level level) {
if (level.equals(Level.ERROR)) {
logger.error(message);
} else if (level.equals(Level.WARN)) {
logger.warn(message);
} else if (level.equals(Level.DEBUG)) {
logger.debug(message);
} else if (level.equals(Level.INFO)) {
logger.info(message);
}
}
/**
* This method should be called to report the end of the processing.
*/
public void reportEnd() {
logger.info("end of processing: ");
if (warningCount > 0) {
logger.info(warningCount + " warning");
if (warningCount > 1) {
logger.info("s");
}
if (errorCount > 0) {
logger.info(", ");
}
}
if (errorCount > 0) {
logger.info(errorCount + " error");
if (errorCount > 1) {
logger.info("s");
}
}
if ((errorCount + warningCount) > 0) {
logger.info("\n");
} else {
logger.info("no errors, no warnings");
}
}
public void reportProgressMessage(String message) {
logger.info(message);
}
public void setDebug(boolean debug) {
}
public void setDefaultFileGenerator(FileGenerator<? extends CtElement> defaultFileGenerator) {
this.defaultFileGenerator = defaultFileGenerator;
defaultFileGenerator.setFactory(getFactory());
}
public void setManager(ProcessingManager manager) {
this.manager = manager;
}
public void setProcessingStopped(boolean processingStopped) {
this.processingStopped = processingStopped;
}
public void setVerbose(boolean verbose) {
}
int complianceLevel = 7;
public int getComplianceLevel() {
return complianceLevel;
}
public void setComplianceLevel(int level) {
complianceLevel = level;
}
public void setProcessorProperties(String processorName, ProcessorProperties prop) {
processorProperties.put(processorName, prop);
}
boolean useTabulations = false;
public boolean isUsingTabulations() {
return useTabulations;
}
public void useTabulations(boolean tabulation) {
useTabulations = tabulation;
}
int tabulationSize = 4;
public int getTabulationSize() {
return tabulationSize;
}
public void setTabulationSize(int tabulationSize) {
this.tabulationSize = tabulationSize;
}
private ClassLoader classloader;
/*
* cache class loader which loads classes from source class path
* we must cache it to make all the loaded classes compatible
* The cache is reset when setSourceClasspath(...) is called
*/
private ClassLoader inputClassloader;
@Override
public void setInputClassLoader(ClassLoader aClassLoader) {
if (aClassLoader instanceof URLClassLoader) {
final URL[] urls = ((URLClassLoader) aClassLoader).getURLs();
if (urls != null && urls.length > 0) {
List<String> classpath = new ArrayList<>();
for (URL url : urls) {
classpath.add(url.toString());
}
setSourceClasspath(classpath.toArray(new String[0]));
}
return;
}
this.classloader = aClassLoader;
}
@Override
public ClassLoader getInputClassLoader() {
if (classloader != null) {
return classloader;
}
if (inputClassloader == null) {
inputClassloader = new URLClassLoader(urlClasspath(), Thread.currentThread().getContextClassLoader());
}
return inputClassloader;
}
/**
* Creates a URL class path from {@link Environment#getSourceClasspath()}
*/
public URL[] urlClasspath() {
String[] classpath = getSourceClasspath();
int length = (classpath == null) ? 0 : classpath.length;
URL[] urls = new URL[length];
for (int i = 0; i < length; i += 1) {
try {
urls[i] = new File(classpath[i]).toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException("Invalid classpath: " + classpath, e);
}
}
return urls;
}
@Override
public String[] getSourceClasspath() {
return sourceClasspath;
}
@Override
public void setSourceClasspath(String[] sourceClasspath) {
verifySourceClasspath(sourceClasspath);
this.sourceClasspath = sourceClasspath;
this.inputClassloader = null;
}
private void verifySourceClasspath(String[] sourceClasspath) throws InvalidClassPathException {
for (String classPathElem : sourceClasspath) {
// preconditions
File classOrJarFolder = new File(classPathElem);
if (!classOrJarFolder.exists()) {
throw new InvalidClassPathException(classPathElem + " does not exist, it is not a valid folder");
}
if (classOrJarFolder.isDirectory()) {
// it should not contain a java file
SpoonFolder tmp = new FileSystemFolder(classOrJarFolder);
List<SpoonFile> javaFiles = tmp.getAllJavaFiles();
if (javaFiles.size() > 0) {
logger.warn("You're trying to give source code in the classpath, this should be given to " + "addInputSource " + javaFiles);
}
}
}
}
@Override
public int getErrorCount() {
return errorCount;
}
@Override
public int getWarningCount() {
return warningCount;
}
@Override
public boolean isPreserveLineNumbers() {
return preserveLineNumbers;
}
@Override
public void setPreserveLineNumbers(boolean preserveLineNumbers) {
this.preserveLineNumbers = preserveLineNumbers;
}
private boolean noclasspath = false;
@Override
public void setNoClasspath(boolean option) {
noclasspath = option;
}
@Override
public boolean getNoClasspath() {
return noclasspath;
}
@Override
public boolean isCopyResources() {
return copyResources;
}
@Override
public void setCopyResources(boolean copyResources) {
this.copyResources = copyResources;
}
@Override
public boolean isCommentsEnabled() {
return enableComments;
}
@Override
public void setCommentEnabled(boolean commentEnabled) {
this.enableComments = commentEnabled;
}
private String binaryOutputDirectory = Launcher.SPOONED_CLASSES;
@Override
public void setBinaryOutputDirectory(String s) {
this.binaryOutputDirectory = s;
}
@Override
public String getBinaryOutputDirectory() {
return binaryOutputDirectory;
}
}