/**
* 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.text.MessageFormat;
import java.util.Collections;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.vocabulary.flow.graph.Inline;
import com.asakusafw.vocabulary.flow.processor.PartialAggregation;
import com.asakusafw.vocabulary.operator.Logging;
/**
* Options for flow DSL compiler.
* @since 0.1.0
* @version 0.7.3
*/
public class FlowCompilerOptions {
static final Logger LOG = LoggerFactory.getLogger(FlowCompilerOptions.class);
/**
* The system property key of serialized compiler options.
* The serialized compiler options is the form of the following rule:
<pre><code>
OptionList:
OptionList "," Option
Option
Option:
Key "=" Value
(Empty String)
Key:
EscapedCharacter+
Value:
EscapedCharacter*
EscapedString:
(character excludes "=", "," and "\")
"\="
"\,"
"\\"
</code></pre>
* Note that, the {@code Key} must be one of {@link Item} or start with {@link #PREFIX_EXTRA_OPTION}.
*/
public static final String K_OPTIONS = "com.asakusafw.compiler.options"; //$NON-NLS-1$
/**
* The key prefix for extra options.
*/
public static final String PREFIX_EXTRA_OPTION = "X"; //$NON-NLS-1$
/**
* Represents a kind of compiler option item.
*/
public enum Item {
/**
* The default value of whether combine operation is enabled or not.
* Default value: {@code false}.
* @see PartialAggregation#DEFAULT
*/
enableCombiner(false) {
@Override public void setTo(FlowCompilerOptions options, boolean value) {
options.setEnableCombiner(value);
}
},
/**
* The default value of whether in-lining flow-part is enabled or not.
* Default value: {@code true}.
* @see Inline#DEFAULT
*/
compressFlowPart(true) {
@Override public void setTo(FlowCompilerOptions options, boolean value) {
options.setCompressFlowPart(value);
}
},
/**
* The default value of whether compressing concurrent stages is enabled or not.
* Default value: {@code true}.
*/
compressConcurrentStage(true) {
@Override public void setTo(FlowCompilerOptions options, boolean value) {
options.setCompressConcurrentStage(value);
}
},
/**
* The default value of whether hash-join for tiny inputs is enabled or not.
* Default value: {@code true}.
*/
hashJoinForTiny(true) {
@Override public void setTo(FlowCompilerOptions options, boolean value) {
options.setHashJoinForTiny(value);
}
},
/**
* The default value of whether hash-join for small inputs is enabled or not.
* Default value: {@code false}.
*/
hashJoinForSmall(false) {
@Override public void setTo(FlowCompilerOptions options, boolean value) {
options.setHashJoinForSmall(value);
}
},
/**
* The default value of whether debug level logging is enabled or not.
* Default value: {@code false}.
* @see Logging#value()
*/
enableDebugLogging(false) {
@Override public void setTo(FlowCompilerOptions options, boolean value) {
options.setEnableDebugLogging(value);
}
},
;
/**
* The default value.
*/
public final boolean defaultValue;
Item(boolean defaultValue) {
this.defaultValue = defaultValue;
}
/**
* Sets this option item into the target options.
* @param options the target options
* @param value the option value
* @throws IllegalArgumentException if any parameter is {@code null}
*/
public abstract void setTo(FlowCompilerOptions options, boolean value);
}
/**
* A common value for extra options.
* @since 0.2.5
*/
public enum GenericOptionValue {
/**
* The option is enabled.
*/
ENABLED("enabled,enable,t,true,y,yes,on"), //$NON-NLS-1$
/**
* The option is disabled.
*/
DISABLED("disabled,disable,f,false,n,no,off"), //$NON-NLS-1$
/**
* The option should be auto detected.
*/
AUTO("auto"), //$NON-NLS-1$
/**
* The option which is invalid.
*/
INVALID("invalid"), //$NON-NLS-1$
;
private final String primary;
private final Set<String> symbols;
GenericOptionValue(String symbols) {
assert symbols != null;
String first = null;
this.symbols = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
for (String s : symbols.split(",")) { //$NON-NLS-1$
String token = s.trim();
if (token.isEmpty() == false) {
if (first == null) {
first = token;
}
this.symbols.add(token);
}
}
if (first == null) {
throw new IllegalArgumentException();
}
this.primary = first;
Collections.addAll(this.symbols, symbols);
}
/**
* Returns the symbol of the value.
* @return the symbol
*/
public String getSymbol() {
return primary;
}
/**
* Returns a value corresponding to the symbol.
* @param symbol target symbol
* @return corresponded value, or {@link FlowCompilerOptions.GenericOptionValue#INVALID} if does not exist
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public static GenericOptionValue fromSymbol(String symbol) {
if (symbol == null) {
throw new IllegalArgumentException("symbol must not be null"); //$NON-NLS-1$
}
for (GenericOptionValue value : values()) {
if (value.symbols.contains(symbol)) {
return value;
}
}
return INVALID;
}
}
private volatile boolean enableCombiner;
private volatile boolean compressFlowPart;
private volatile boolean compressConcurrentStage;
private volatile boolean hashJoinForTiny;
private volatile boolean hashJoinForSmall;
private volatile boolean enableDebugLogging;
private final Map<String, String> extraAttributes = new ConcurrentHashMap<>();
/**
* Creates a new instance with default option values.
*/
public FlowCompilerOptions() {
for (Item item : Item.values()) {
item.setTo(this, item.defaultValue);
}
}
private static final Pattern OPTION = Pattern.compile("\\s*(\\+|-)\\s*([0-9A-Za-z_\\-]+)\\s*"); //$NON-NLS-1$
private static final Pattern EXTRA_OPTION = Pattern.compile("\\s*X([0-9A-Za-z_\\-]+)\\s*=([^,]*)"); //$NON-NLS-1$
/**
* Creates a new instance from the properties object.
* @param properties the target properties object
* @return the created instance
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public static FlowCompilerOptions load(Properties properties) {
Precondition.checkMustNotBeNull(properties, "properties"); //$NON-NLS-1$
String[] options = properties.getProperty(K_OPTIONS, "").split("\\s*,\\s*"); //$NON-NLS-1$ //$NON-NLS-2$
FlowCompilerOptions results = new FlowCompilerOptions();
for (String option : options) {
if (option.isEmpty()) {
continue;
}
Matcher optionMatcher = OPTION.matcher(option);
if (optionMatcher.matches()) {
boolean value = optionMatcher.group(1).equals("+"); //$NON-NLS-1$
String name = optionMatcher.group(2);
try {
Item item = Item.valueOf(name);
item.setTo(results, value);
} catch (NoSuchElementException e) {
LOG.warn(MessageFormat.format(
Messages.getString("FlowCompilerOptions.warnUnknownOption"), //$NON-NLS-1$
option));
}
} else {
Matcher extraMatcher = EXTRA_OPTION.matcher(option);
if (extraMatcher.matches()) {
String key = extraMatcher.group(1).trim();
String value = extraMatcher.group(2).trim();
results.extraAttributes.put(key, value);
} else {
LOG.warn(MessageFormat.format(
Messages.getString("FlowCompilerOptions.warnMalformedOption"), //$NON-NLS-1$
option));
}
}
}
return results;
}
/**
* Returns the option value of {@link Item#enableCombiner}.
* @return the option value
*/
public boolean isEnableCombiner() {
return enableCombiner;
}
/**
* Sets the option value of {@link Item#enableCombiner}.
* @param enable the option value
*/
public void setEnableCombiner(boolean enable) {
this.enableCombiner = enable;
}
/**
* Returns the option value of {@link Item#compressFlowPart}.
* @return the option value
*/
public boolean isCompressFlowPart() {
return compressFlowPart;
}
/**
* Sets the option value of {@link Item#compressFlowPart}.
* @param enable the option value
*/
public void setCompressFlowPart(boolean enable) {
this.compressFlowPart = enable;
}
/**
* Returns the option value of {@link Item#compressConcurrentStage}.
* @return the option value
*/
public boolean isCompressConcurrentStage() {
return compressConcurrentStage;
}
/**
* Sets the option value of {@link Item#compressConcurrentStage}.
* @param enable the option value
*/
public void setCompressConcurrentStage(boolean enable) {
this.compressConcurrentStage = enable;
}
/**
* Returns the option value of {@link Item#hashJoinForTiny}.
* @return the option value
*/
public boolean isHashJoinForTiny() {
return hashJoinForTiny;
}
/**
* Sets the option value of {@link Item#hashJoinForTiny}.
* @param enable the option value
*/
public void setHashJoinForTiny(boolean enable) {
this.hashJoinForTiny = enable;
}
/**
* Returns the option value of {@link Item#hashJoinForSmall}.
* @return the option value
*/
public boolean isHashJoinForSmall() {
return hashJoinForSmall;
}
/**
* Sets the option value of {@link Item#hashJoinForSmall}.
* @param enable the option value
*/
public void setHashJoinForSmall(boolean enable) {
this.hashJoinForSmall = enable;
}
/**
* Returns the option value of {@link Item#enableDebugLogging}.
* @return the option value
*/
public boolean isEnableDebugLogging() {
return this.enableDebugLogging;
}
/**
* Sets the option value of {@link Item#enableDebugLogging}.
* @param enable the option value
*/
public void setEnableDebugLogging(boolean enable) {
this.enableDebugLogging = enable;
}
/**
* Returns the extra option key name for the option name.
* @param optionName the original option name
* @return the key name for the option
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public String getExtraAttributeKeyName(String optionName) {
if (optionName == null) {
throw new IllegalArgumentException("optionName must not be null"); //$NON-NLS-1$
}
return PREFIX_EXTRA_OPTION + optionName;
}
/**
* Returns the extra attribute.
* @param name attribute name
* @return related value, or {@code null} if not configured
*/
public String getExtraAttribute(String name) {
return this.extraAttributes.get(name);
}
/**
* Returns the extra attribute.
* @param name attribute name
* @param defaultValue the default value
* @return related value, or the default value if the attribute is not set
* @since 0.7.1
*/
public String getExtraAttribute(String name, String defaultValue) {
String value = this.extraAttributes.get(name);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Returns the extra attribute.
* @param name attribute name
* @param defaultValue the default value
* @return related value, or {@code null} if not configured
*/
public GenericOptionValue getGenericExtraAttribute(String name, GenericOptionValue defaultValue) {
String value = this.extraAttributes.get(name);
if (value == null) {
return defaultValue;
}
GenericOptionValue symbol = GenericOptionValue.fromSymbol(value);
return symbol;
}
/**
* Configures the extra attribute.
* @param name attribute name
* @param value attribute value (nullable)
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public void putExtraAttribute(String name, String value) {
if (name == null) {
throw new IllegalArgumentException("name must not be null"); //$NON-NLS-1$
}
if (value == null) {
this.extraAttributes.remove(name);
} else {
this.extraAttributes.put(name, value);
}
}
}