/**
* Copyright 2011-2017 Asakusa Framework 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.
*/
package com.asakusafw.compiler.flow;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.compiler.common.JavaName;
import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.utils.java.model.syntax.CompilationUnit;
import com.asakusafw.utils.java.model.syntax.ModelFactory;
import com.asakusafw.utils.java.model.syntax.Name;
import com.asakusafw.utils.java.model.syntax.SimpleName;
import com.asakusafw.utils.java.model.util.Models;
/**
* Represents a compiler environment for flow DSL compiler.
* @since 0.1.0
* @version 0.2.6
*/
public class FlowCompilingEnvironment {
static final Logger LOG = LoggerFactory.getLogger(FlowCompilingEnvironment.class);
private final FlowCompilerConfiguration config;
private final AtomicBoolean initialized = new AtomicBoolean(false);
private final AtomicInteger counter = new AtomicInteger();
private String firstError;
/**
* Creates a new instance.
* @param configuration the compiler settings
* @throws IllegalArgumentException if the parameter {@code null}
*/
public FlowCompilingEnvironment(FlowCompilerConfiguration configuration) {
Precondition.checkMustNotBeNull(configuration, "configuration"); //$NON-NLS-1$
this.config = configuration;
clearError();
}
/**
* Initializes this object.
* @return this
*/
public FlowCompilingEnvironment bless() {
if (initialized.compareAndSet(false, true) == false) {
return this;
}
config.getDataClasses().initialize(this);
config.getExternals().initialize(this);
config.getPackager().initialize(this);
config.getProcessors().initialize(this);
config.getGraphRewriters().initialize(this);
clearError();
return this;
}
/**
* Returns a previous error message.
* @return a previous error message, or {@code null} if not error
*/
public String getErrorMessage() {
return firstError;
}
/**
* Returns whether this saw any compile errors or not.
* @return {@code true} if saw any compile errors, or otherwise {@code false}
*/
public final boolean hasError() {
return firstError != null;
}
/**
* Clears compile errors.
* @see #hasError()
*/
public final void clearError() {
firstError = null;
}
/**
* Returns the Java DOM factory.
* @return the Java DOM factory
*/
public ModelFactory getModelFactory() {
return config.getFactory();
}
/**
* Returns the repository of available flow element processors in this environment.
* @return the repository of available
*/
public FlowElementProcessor.Repository getProcessors() {
return config.getProcessors();
}
/**
* Returns the repository of available data model classes in this environment.
* @return the repository of available
*/
public DataClassRepository getDataClasses() {
return config.getDataClasses();
}
/**
* Returns the repository of available external I/O processors in this environment.
* @return the repository of available
*/
public ExternalIoDescriptionProcessor.Repository getExternals() {
return config.getExternals();
}
/**
* Returns the repository of available flow graph rewriters in this environment.
* @return the repository of available
*/
public FlowGraphRewriter.Repository getGraphRewriters() {
return config.getGraphRewriters();
}
/**
* Returns the ID of the target batch.
* @return the batch ID
*/
public String getBatchId() {
return config.getBatchId();
}
/**
* Returns the flow ID of the target jobflow.
* @return the flow ID
*/
public String getFlowId() {
return config.getFlowId();
}
/**
* Returns the qualified flow ID of the target jobflow.
* @return the qualified flow ID
*/
public String getTargetId() {
return MessageFormat.format("{0}.{1}", getBatchId(), getFlowId()); //$NON-NLS-1$
}
/**
* Returns the base package name for generated Java classes.
* @return the package name
*/
public Name getTargetPackageName() {
Name root = Models.toName(getModelFactory(), config.getRootPackageName());
Name batch = Models.toName(getModelFactory(), normalize(getBatchId()));
Name flow = Models.toName(getModelFactory(), normalize(getFlowId()));
return Models.append(getModelFactory(), root, batch, flow);
}
private String normalize(String name) {
assert name != null;
StringBuilder buf = new StringBuilder();
String[] segments = name.split(Pattern.quote(".")); //$NON-NLS-1$
buf.append(memberName(segments[0]));
for (int i = 1; i < segments.length; i++) {
buf.append('.');
buf.append(memberName(segments[i]));
}
return buf.toString();
}
private String memberName(String string) {
assert string != null;
if (string.isEmpty()) {
return "_"; //$NON-NLS-1$
}
return JavaName.of(string).toMemberName();
}
/**
* Returns the Java package name for generating classes about main phase.
* @param stageNumber the target stage number
* @return the target package name
* @throws IllegalArgumentException if the stage number is negative
*/
public Name getStagePackageName(int stageNumber) {
if (stageNumber < 0) {
throw new IllegalArgumentException("stageNumber must be a positive integer"); //$NON-NLS-1$
}
return Models.append(
config.getFactory(),
getTargetPackageName(),
String.format("stage%04d", stageNumber)); //$NON-NLS-1$
}
/**
* Returns the Java package name for generating resources.
* @param resourceKind the resource kind
* @return the target package name
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Name getResourcePackage(String resourceKind) {
Precondition.checkMustNotBeNull(resourceKind, "resourceKind"); //$NON-NLS-1$
return Models.append(getModelFactory(),
getTargetPackageName(),
normalize(resourceKind));
}
/**
* Returns a unique name using the internal sequence number.
* @param prefix the prefix
* @return the unique name
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public SimpleName createUniqueName(String prefix) {
Precondition.checkMustNotBeNull(prefix, "prefix"); //$NON-NLS-1$
return getModelFactory().newSimpleName(prefix + counter.incrementAndGet());
}
/**
* Returns the Java package name for generating classes about prologue phase.
* @param moduleId the target module ID
* @return the target package name
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Name getProloguePackageName(String moduleId) {
Precondition.checkMustNotBeNull(moduleId, "moduleId"); //$NON-NLS-1$
return Models.append(
config.getFactory(),
getTargetPackageName(),
MessageFormat.format("{0}.prologue", memberName(moduleId))); //$NON-NLS-1$
}
/**
* Returns the Java package name for generating classes about epilogue phase.
* @param moduleId the target module ID
* @return the target package name
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Name getEpiloguePackageName(String moduleId) {
Precondition.checkMustNotBeNull(moduleId, "moduleId"); //$NON-NLS-1$
return Models.append(
config.getFactory(),
getTargetPackageName(),
MessageFormat.format("{0}.epilogue", memberName(moduleId))); //$NON-NLS-1$
}
/**
* Returns the base location of runtime working area.
* @return the base location of runtime working area
*/
public Location getTargetLocation() {
return config
.getRootLocation()
.append(getBatchId())
.append(getFlowId());
}
/**
* Returns the location of runtime working area for the stage.
* @param stageNumber the target stage number
* @return the target resource location
* @throws IllegalArgumentException if the stage number is negative
*/
public Location getStageLocation(int stageNumber) {
if (stageNumber < 0) {
throw new IllegalArgumentException("stageNumber must be a positive integer"); //$NON-NLS-1$
}
String stageSuffix = String.format("stage%04d", stageNumber); //$NON-NLS-1$
return getTargetLocation().append(stageSuffix);
}
/**
* Returns the location of runtime working area for the prologue stage.
* @param moduleId the module ID
* @return the target resource location
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Location getPrologueLocation(String moduleId) {
Precondition.checkMustNotBeNull(moduleId, "moduleId"); //$NON-NLS-1$
return getTargetLocation()
.append("prologue") //$NON-NLS-1$
.append(moduleId);
}
/**
* Returns the location of runtime working area for the epilogue stage.
* @param moduleId the module ID
* @return the target resource location
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Location getEpilogueLocation(String moduleId) {
Precondition.checkMustNotBeNull(moduleId, "moduleId"); //$NON-NLS-1$
return getTargetLocation()
.append("epilogue") //$NON-NLS-1$
.append(moduleId);
}
/**
* Emits a Java source program into the jobflow package.
* @param source the source program
* @throws IOException if failed to output
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public void emit(CompilationUnit source) throws IOException {
Precondition.checkMustNotBeNull(source, "source"); //$NON-NLS-1$
try (PrintWriter writer = config.getPackager().openWriter(source)) {
Models.emit(source, writer);
}
}
/**
* Creates a generic resource file into the jobflow package.
* @param packageNameOrNull the target package name
* @param subPath the sub-path from the target package
* @return the output stream for writing contents of the created resource
* @throws IOException if failed to output
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public OutputStream openResource(Name packageNameOrNull, String subPath) throws IOException {
Precondition.checkMustNotBeNull(subPath, "subPath"); //$NON-NLS-1$
return config.getPackager().openStream(packageNameOrNull, subPath);
}
/**
* Returns the class loader for loading compiler services.
* @return the class loader
*/
public ClassLoader getServiceClassLoader() {
return config.getServiceClassLoader();
}
/**
* Returns the compiler options.
* @return the compiler options
*/
public FlowCompilerOptions getOptions() {
return config.getOptions();
}
/**
* Returns the current build ID.
* @return current build ID, or {@code null} if not defined
* @since 0.4.0
*/
public String getBuildId() {
return config.getBuildId();
}
/**
* Adds an erroneous information to this environment.
* @param format the message format ({@link MessageFormat} style)
* @param arguments the message arguments
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public void error(String format, Object...arguments) {
Precondition.checkMustNotBeNull(format, "format"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(arguments, "arguments"); //$NON-NLS-1$
String text = format(format, arguments);
LOG.error(text);
if (firstError == null) {
firstError = text;
}
}
private String format(String format, Object[] args) {
assert format != null;
assert args != null;
if (args.length == 0) {
return format;
}
return MessageFormat.format(format, args);
}
/**
* An extension interface for classes that requires a {@link FlowCompilingEnvironment} for its initialization.
*/
public interface Initializable {
/**
* Initializes this object.
* @param environment the current environment
*/
void initialize(FlowCompilingEnvironment environment);
}
/**
* A skeletal implementation of {@link Initializable}.
*/
public abstract static class Initialized implements Initializable {
private FlowCompilingEnvironment environment;
@Override
public final void initialize(FlowCompilingEnvironment env) {
Precondition.checkMustNotBeNull(env, "env"); //$NON-NLS-1$
this.environment = env;
doInitialize();
}
/**
* Initializes this object in sub-classes.
*/
protected void doInitialize() {
return;
}
/**
* Returns the current environment object.
* @return the current environment
*/
protected FlowCompilingEnvironment getEnvironment() {
return environment;
}
}
}