
org.efaps.update.schema.program.esjp.ESJPCompiler Maven / Gradle / Ivy
/*
* Copyright 2003 - 2013 The eFaps Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Revision: $Rev: 8848 $
* Last Changed: $Date: 2013-02-19 10:49:59 -0500 (Tue, 19 Feb 2013) $
* Last Changed By: $Author: [email protected] $
*/
package org.efaps.update.schema.program.esjp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import org.efaps.admin.datamodel.Type;
import org.efaps.ci.CIAdminProgram;
import org.efaps.db.Checkin;
import org.efaps.db.Checkout;
import org.efaps.db.Delete;
import org.efaps.db.Insert;
import org.efaps.db.Instance;
import org.efaps.db.MultiPrintQuery;
import org.efaps.db.QueryBuilder;
import org.efaps.update.util.InstallationException;
import org.efaps.util.EFapsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The class is used to compile all checked in ESJP programs. Because the
* dependencies of a class are not known, all ESJP programs stored in eFaps are
* compiled.
*
* @author The eFaps Team
* @version $Id: ESJPCompiler.java 8848 2013-02-19 15:49:59Z [email protected] $
*/
public class ESJPCompiler
{
/**
* Logging instance used in this class.
*/
private static final Logger LOG = LoggerFactory.getLogger(ESJPCompiler.class);
/**
* Type instance of Java program.
*/
private final Type esjpType;
/**
* Type instance of compile EJSP program.
*/
private final Type classType;
/**
* Mapping between ESJP name and the related ESJP source object.
*
* @see #readESJPPrograms()
*/
private final Map name2Source = new HashMap();
/**
* Mapping between already existing compiled ESJP class name and the
* related eFaps id in the database.
*
* @see #readESJPClasses()
*/
private final Map class2id = new HashMap();
/**
* Mapping between the class name and the related ESJP class which must be
* stored.
*
* @see StoreObject
*/
private final Map classFiles
= new HashMap();
/**
* Stores the list of class path needed to compile (if needed).
*/
private final List classPathElements;
/**
* The constructor initialize the two type instances {@link #esjpType} and
* {@link #classType}.
*
* @param _classPathElements list of class path elements
* @see #esjpType
* @see #classType
*/
public ESJPCompiler(final List _classPathElements)
{
this.esjpType = CIAdminProgram.Java.getType();
this.classType = CIAdminProgram.JavaClass.getType();
this.classPathElements = _classPathElements;
}
/**
* All stored ESJP programs in eFaps are compiled. The system Java compiler
* defined from the {@link ToolProvider tool provider} is used for the
* compiler. All old not needed compiled Java classes are automatically
* removed. The compiler error and warning are logged (errors are using
* error-level, warnings are using info-level).
* Debug:
*
* null
: By default, only line number and source file information is generated.
* "none"
: Do not generate any debugging information
* - Generate only some kinds of debugging information, specified by a comma separated
* list of keywords. Valid keywords are:
*
* "source"
: Source file debugging information
* "lines"
: Line number debugging information
* "vars"
: Local variable debugging information
*
*
*
*
* @param _debug String for the debug option
* @param _addRuntimeClassPath Must the classpath from the runtime added
* to the compiler, default: false
* @throws InstallationException if the compile failed
*/
public void compile(final String _debug,
final boolean _addRuntimeClassPath)
throws InstallationException
{
readESJPPrograms();
readESJPClasses();
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
ESJPCompiler.LOG.error("no compiler found for compiler !");
} else {
// output of used compiler
if (ESJPCompiler.LOG.isInfoEnabled()) {
ESJPCompiler.LOG.info(" Using compiler " + compiler.toString());
}
// options for the compiler
final List optionList = new ArrayList();
// set classpath!
// (the list of programs to compile is given to the javac as
// argument array, so the class path could be set in front of the
// programs to compile)
if (this.classPathElements != null) {
// different class path separators depending on the OS
final String sep = System.getProperty("os.name").startsWith("Windows") ? ";" : ":";
final StringBuilder classPath = new StringBuilder();
for (final String classPathElement : this.classPathElements) {
classPath.append(classPathElement).append(sep);
}
if (_addRuntimeClassPath) {
classPath.append(System.getProperty("java.class.path"));
}
optionList.addAll(Arrays.asList("-classpath", classPath.toString()));
} else {
// set compiler's class path to be same as the runtime's
optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
}
//Set the source file encoding name, such as EUCJIS/SJIS. If -encoding is not specified,
//the platform default converter is used.
optionList.addAll(Arrays.asList("-encoding", "UTF-8"));
if (_debug != null) {
optionList.addAll(Arrays.asList("-g", _debug));
}
// logging of compiling classes
if (ESJPCompiler.LOG.isInfoEnabled()) {
for (final SourceObject obj : this.name2Source.values()) {
ESJPCompiler.LOG.info(" Compiling ESJP '" + obj.javaName + "'");
}
}
final FileManager fm = new FileManager(compiler.getStandardFileManager(null, null, null));
final boolean noErrors = compiler.getTask(new ErrorWriter(),
fm,
null,
optionList,
null,
this.name2Source.values())
.call();
if (!noErrors) {
throw new InstallationException("error");
}
// store all compiled ESJP's
for (final ESJPCompiler.StoreObject obj : this.classFiles.values()) {
obj.write();
}
// delete not needed compiled ESJP classes
for (final Long id : this.class2id.values()) {
try {
(new Delete(this.classType, id)).executeWithoutAccessCheck();
} catch (final EFapsException e) {
throw new InstallationException("Could not delete ESJP class with id " + id, e);
}
}
}
}
/**
* All EJSP programs in the eFaps database are read and stored in the
* mapping {@link #name2Source} for further using.
*
* @see #name2Source
* @throws InstallationException if ESJP Java programs could not be read
*/
protected void readESJPPrograms()
throws InstallationException
{
try {
final QueryBuilder queryBldr = new QueryBuilder(this.esjpType);
final MultiPrintQuery multi = queryBldr.getPrint();
multi.addAttribute("Name");
multi.executeWithoutAccessCheck();
while (multi.next()) {
final String name = multi.getAttribute("Name");
final Long id = multi.getCurrentInstance().getId();
final File file = new File(File.separator,
name.replaceAll("\\.", Matcher.quoteReplacement(File.separator))
+ JavaFileObject.Kind.SOURCE.extension);
final URI uri;
try {
uri = new URI("efaps", null, file.getAbsolutePath(), null, null);
} catch (final URISyntaxException e) {
throw new InstallationException("Could not create an URI for " + file, e);
}
this.name2Source.put(name, new SourceObject(uri, name, id));
}
} catch (final EFapsException e) {
throw new InstallationException("Could not fetch the information about installed ESJP's", e);
}
}
/**
* All stored compiled ESJP's classes in the eFaps database are stored in
* the mapping {@link #class2id}. If a ESJP's program is compiled and
* stored with {@link ESJPCompiler.StoreObject#write()}, the class is
* removed. After the compile, {@link ESJPCompiler#compile(String)} removes
* all stored classes which are not needed anymore.
*
* @throws InstallationException if read of the ESJP classes failed
* @see #class2id
*/
protected void readESJPClasses()
throws InstallationException
{
try {
final QueryBuilder queryBldr = new QueryBuilder(this.classType);
final MultiPrintQuery multi = queryBldr.getPrint();
multi.addAttribute("Name");
multi.executeWithoutAccessCheck();
while (multi.next()) {
final String name = multi.getAttribute("Name");
final Long id = multi.getCurrentInstance().getId();
this.class2id.put(name, id);
}
} catch (final EFapsException e) {
throw new InstallationException("Could not fetch the information about compiled ESJP's", e);
}
}
/**
* Error writer to show all errors to the
* {@link ESJPCompiler#LOG compiler logger}.
*/
private final class ErrorWriter
extends Writer
{
/**
* Stub method because only required to derive from {@link Writer}.
*/
@Override
public void close()
{
}
/**
* Stub method because only required to derive from {@link Writer}.
*/
@Override
public void flush()
{
}
/**
* Writes given message to the error log of the compiler.
*
* @param _cbuf buffer with the message
* @param _off offset within the buffer
* @param _len len of the message within the buffer
*/
@Override
public void write(final char[] _cbuf,
final int _off,
final int _len)
{
final String msg = new StringBuilder().append(_cbuf, _off, _len).toString().trim();
if (!"".equals(msg)) {
for (final String line : msg.split("\n")) {
ESJPCompiler.LOG.error(line);
}
}
}
}
/**
* ESJP file manager to handle the compiled ESJP classes.
*/
private final class FileManager
extends ForwardingJavaFileManager
{
/**
* Defined the forwarding Java file manager.
*
* @param _sfm original Java file manager to forward
*/
public FileManager(final StandardJavaFileManager _sfm)
{
super(_sfm);
}
/**
* The method returns always null
to be sure the no file
* is written.
*
* @param _location location for which the file output is searched
* @param _packageName name of the package
* @param _relativeName relative name
* @param _fileObject file object to be used as hint for placement
* @return always null
*/
@Override
public FileObject getFileForOutput(final Location _location,
final String _packageName,
final String _relativeName,
final FileObject _fileObject)
{
return null;
}
/**
* Returns the related Java file object used from the Java compiler to
* store the compiled ESJP.
*
* @param _location location (not used)
* @param _className name of the ESJP class
* @param _kind kind of the source (not used)
* @param _fileObject file object to update (used to get the URI)
* @return Java file object for ESJP used to store the compiled class
* @see ESJPCompiler
*/
@Override
public JavaFileObject getJavaFileForOutput(final Location _location,
final String _className,
final JavaFileObject.Kind _kind,
final FileObject _fileObject)
{
final ESJPCompiler.StoreObject ret = new ESJPCompiler.StoreObject(_fileObject.toUri(), _className);
ESJPCompiler.this.classFiles.put(_className, ret);
return ret;
}
/**
* Checks if given _location
is handled by this Java file
* manager.
*
* @param _location location to prove
* @return true if the _location
is the source path or
* the forwarding standard Java file manager handles the
* _location
; otherwise false
*/
@Override
public boolean hasLocation(final JavaFileManager.Location _location)
{
return StandardLocation.SOURCE_PATH.getName().equals(_location.getName()) || super.hasLocation(_location);
}
/**
* If the _location
is the source path a dummy binary name
* for the ESJP class is returned. The dummy binary name is the name of
* the _javaFileObject
and the extension for
* {@link JavaFileObject.Kind#CLASS Java classes}. If the
* _location
is not the source path, the binary name from
* the forwarded
* {@link StandardJavaFileManager standard Java file manager} is
* returned.
*
* @param _location location
* @param _javaFileObject java file object
* @return name of the binary object for the ESJP or from forwarded
* {@link StandardJavaFileManager standard Java file manager}
*/
@Override
public String inferBinaryName(final JavaFileManager.Location _location,
final JavaFileObject _javaFileObject)
{
final String ret;
if (StandardLocation.SOURCE_PATH.getName().equals(_location.getName())) {
ret = new StringBuilder()
.append(_javaFileObject.getName())
.append(JavaFileObject.Kind.CLASS.extension)
.toString();
} else {
ret = super.inferBinaryName(_location, _javaFileObject);
}
return ret;
}
/**
* If the _location
is the source path and the
* _kinds
includes sources an investigation in the cached
* {@link ESJPCompiler#name2Source ESJP programs} is done and the list of
* ESJP's for given _packageName
is returned.
* In all other case the list of found Java programs from the
* forwarded {@link StandardJavaFileManager standard Java file manager}
* is returned.
*
* @param _location location which must be investigated
* @param _packageName name of searched package
* @param _kinds kinds of file object
* @param _recurse must be searched recursive including sub
* packages (ignored, because not used)
* @return list of found ESJP programs for given
* _packageName
or if not from source path the
* list of Java classes from forwarded standard Java file
* manager
* @throws IOException from forwarded standard Java file manager
*/
@Override
public Iterable list(final Location _location,
final String _packageName,
final Set _kinds,
final boolean _recurse)
throws IOException
{
final Iterable rt;
if (StandardLocation.SOURCE_PATH.getName().equals(_location.getName())
&& _kinds.contains(JavaFileObject.Kind.SOURCE)) {
final List pckObjs = new ArrayList();
final int pckLength = _packageName.length();
for (final Map.Entry entry
: ESJPCompiler.this.name2Source.entrySet()) {
if (entry.getKey().startsWith(_packageName)
&& (entry.getKey().substring(pckLength + 1).indexOf('.') < 0)) {
pckObjs.add(entry.getValue());
}
}
rt = pckObjs;
} else {
rt = super.list(_location, _packageName, _kinds, _recurse);
}
return rt;
}
}
/**
* Holds the information about the ESJP source program which must be
* compiled (and from which the source code is fetched).
*/
private final class SourceObject
extends SimpleJavaFileObject
{
/**
* Name of the ESJP program.
*/
private final String javaName;
/**
* Used internal id in eFaps.
*/
private final long id;
/**
* Initializes the source object.
*
* @param _uri URI of the ESJP
* @param _javaName Java name of the ESJP
* @param _id id used from eFaps within database
*/
private SourceObject(final URI _uri,
final String _javaName,
final long _id)
{
super(_uri, JavaFileObject.Kind.SOURCE);
this.javaName = _javaName;
this.id = _id;
}
/**
* Returns the char sequence of the ESJP source code.
*
* @param _ignoreEncodingErrors ignore encoding error (not used)
* @return source code from the ESJP
* @throws IOException if source could not be read from the eFaps
* database
*/
@Override
public CharSequence getCharContent(final boolean _ignoreEncodingErrors)
throws IOException
{
final StringBuilder ret = new StringBuilder();
try {
final Checkout checkout = new Checkout(Instance.get(ESJPCompiler.this.esjpType, this.id));
final InputStream is = checkout.executeWithoutAccessCheck();
final byte[] bytes = new byte[is.available()];
is.read(bytes);
is.close();
ret.append(new String(bytes));
} catch (final EFapsException e) {
throw new IOException("could not checkout class '" + this.javaName + "'", e);
}
return ret;
}
}
/**
* The class is used to store the result of a Java compilation.
*/
private final class StoreObject
extends SimpleJavaFileObject
{
/**
* Name of the class to compile.
*/
private final String className;
/**
* Byte array output stream to store the result of the compilation.
*
* @see #openOutputStream()
*/
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
/**
* Initializes this store object.
*
* @param _uri URI of the class to store
* @param _className name of the class to store
*/
private StoreObject(final URI _uri,
final String _className)
{
super(_uri, JavaFileObject.Kind.CLASS);
this.className = _className;
}
/**
* Returns this {@link #out} which is used as buffer for the compiled
* ESJP {@link #className}.
*
* @return {@link #out} as output stream
* @see #out
*/
@Override
public OutputStream openOutputStream()
{
return this.out;
}
/**
* The compiled class in _resourceData is stored with the name
* _resourceName in the eFaps database (checked in). If the class
* instance already exists in eFaps, the class data is updated. Otherwise, the
* compiled class is new inserted in eFaps (related to the original Java
* program).
*/
public void write()
{
if (ESJPCompiler.LOG.isDebugEnabled()) {
ESJPCompiler.LOG.debug("write '" + this.className + "'");
}
try {
final Long id = ESJPCompiler.this.class2id.get(this.className);
Instance instance;
if (id == null) {
final String parent = this.className.replaceAll(".class$", "").replaceAll("\\$.*", "");
final ESJPCompiler.SourceObject parentId = ESJPCompiler.this.name2Source.get(parent);
final Insert insert = new Insert(ESJPCompiler.this.classType);
insert.add("Name", this.className);
insert.add("ProgramLink", "" + parentId.id);
insert.executeWithoutAccessCheck();
instance = insert.getInstance();
insert.close();
} else {
instance = Instance.get(ESJPCompiler.this.classType, id);
ESJPCompiler.this.class2id.remove(this.className);
}
final Checkin checkin = new Checkin(instance);
checkin.executeWithoutAccessCheck(this.className,
new ByteArrayInputStream(this.out.toByteArray()),
this.out.toByteArray().length);
//CHECKSTYLE:OFF
} catch (final Exception e) {
//CHECKSTYLE:ON
ESJPCompiler.LOG.error("unable to write to eFaps ESJP class '" + this.className + "'", e);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy