package org.jboss.windup.config.metadata;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.furnace.addons.Addon;
import org.jboss.forge.furnace.addons.AddonId;
import org.jboss.forge.furnace.util.Annotations;
import org.jboss.forge.furnace.util.Assert;
import org.jboss.forge.furnace.versions.EmptyVersionRange;
import org.jboss.forge.furnace.versions.Versions;
import org.jboss.windup.config.RuleProvider;
import org.jboss.windup.config.loader.RuleProviderLoader;
import org.jboss.windup.config.phase.MigrationRulesPhase;
import org.jboss.windup.config.phase.RulePhase;
import org.jboss.windup.util.Logging;
import org.ocpsoft.rewrite.config.Rule;
/**
* Fluent builder for creating {@link RuleProviderMetadata} instances. Provides sensible defaults using given required values.
* <p>
* If {@link RulesetMetadata} is available in the {@link Addon} in which this {@link MetadataBuilder} was constructed, this will inherit values from
* {@link RulesetMetadata} for {@link #getTags()}, {@link #getSourceTechnologies()}, {@link #getTargetTechnologies()} and {@link #getRequiredAddons()}.
* <p>
* Inherited metadata is specified by {@link #setRulesetMetadata(RulesetMetadata)}, and is typically performed by the {@link RuleProviderLoader}
* implementation.
*
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*/
public class MetadataBuilder extends AbstractRulesetMetadata implements RuleProviderMetadata
{
private static final Logger LOG = Logging.get(MetadataBuilder.class);
public static final Class<? extends RulePhase> DEFAULT_PHASE = MigrationRulesPhase.class;
private Class<? extends RuleProvider> implementationType;
private String origin;
private Class<? extends RulePhase> phase;
private List<Class<? extends RuleProvider>> executeAfter = new ArrayList<>();
private List<String> executeAfterIDs = new ArrayList<>();
private List<Class<? extends RuleProvider>> executeBefore = new ArrayList<>();
private List<String> executeBeforeIDs = new ArrayList<>();
private String description;
private Set<String> tags = new HashSet<>();
private final Set<TechnologyReference> sourceTechnologies = new HashSet<>();
private final Set<TechnologyReference> targetTechnologies = new HashSet<>();
private final Set<AddonId> requiredAddons = new HashSet<>();
private boolean haltOnException = false;
private boolean overrideProvider = false;
private boolean disabled = false;
private RulesetMetadata parent = new AbstractRulesetMetadata("NULL");
private MetadataBuilder(Class<? extends RuleProvider> implementationType, String providerId)
{
super(providerId);
this.implementationType = implementationType;
}
/**
* Create a new {@link RuleProviderMetadata} builder instance for the given {@link RuleProvider} type, using the provided parameters and
* {@link RulesetMetadata} to seed sensible defaults.
*/
public static MetadataBuilder forProvider(Class<? extends RuleProvider> implementationType)
{
String id = implementationType.getSimpleName();
RuleMetadata metadata = Annotations.getAnnotation(implementationType, RuleMetadata.class);
if (metadata != null && !metadata.id().isEmpty())
id = metadata.id();
return forProvider(implementationType, id);
}
/**
* Create a new {@link RuleProviderMetadata} builder instance for the given {@link RuleProvider} type, and {@link String} ID, using the provided
* parameters and {@link RulesetMetadata} to seed sensible defaults.
*/
public static MetadataBuilder forProvider(Class<? extends RuleProvider> implementationType, String providerId)
{
Assert.notNull(implementationType, "Rule provider Implementation type must not be null.");
Assert.notNull(providerId, "Rule provider ID must not be null.");
MetadataBuilder builder = new MetadataBuilder(implementationType, providerId)
.setOrigin(implementationType.getName() + " loaded from " + implementationType.getClassLoader().toString());
RuleMetadata metadata = Annotations.getAnnotation(implementationType, RuleMetadata.class);
if (metadata == null)
return builder;
builder.setOverrideProvider(metadata.overrideProvider());
if (StringUtils.isNotBlank(metadata.description()))
builder.setDescription(metadata.description());
Class<? extends RuleProvider>[] after = metadata.after();
if (after.length > 0)
builder.setExecuteAfter(Arrays.asList(after));
String[] afterIDs = metadata.afterIDs();
if (afterIDs.length > 0)
builder.setExecuteAfterIDs(Arrays.asList(afterIDs));
Class<? extends RuleProvider>[] before = metadata.before();
if (before.length > 0)
builder.setExecuteBefore(Arrays.asList(before));
String[] beforeIDs = metadata.beforeIDs();
if (beforeIDs.length > 0)
builder.setExecuteBeforeIDs(Arrays.asList(beforeIDs));
builder.setPhase(metadata.phase());
String[] tags = metadata.tags();
if (tags.length > 0)
builder.setTags(Arrays.asList(tags));
Technology[] sourceTechnologies = metadata.sourceTechnologies();
if (sourceTechnologies.length > 0)
{
for (Technology technology : sourceTechnologies)
{
builder.addSourceTechnology(new TechnologyReference(
technology.id(),
"".equals(technology.versionRange().trim())
? new EmptyVersionRange()
: Versions.parseVersionRange(technology.versionRange())));
}
}
Technology[] targetTechnologies = metadata.targetTechnologies();
if (targetTechnologies.length > 0)
{
for (Technology technology : targetTechnologies)
{
builder.addTargetTechnology(new TechnologyReference(
technology.id(),
Versions.parseVersionRange(technology.versionRange())));
}
}
builder.haltOnException = metadata.haltOnException();
builder.disabled = metadata.disabled();
if (builder.disabled)
LOG.info("Disabled provider: " + providerId);
return builder;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (!(o instanceof MetadataBuilder)) return false;
if (!super.equals(o)) return false;
MetadataBuilder that = (MetadataBuilder) o;
return overrideProvider == that.overrideProvider;
}
@Override
public int hashCode()
{
int result = super.hashCode();
result = 31 * result + (overrideProvider ? 1 : 0);
return result;
}
@Override
public Class<? extends RuleProvider> getType()
{
return implementationType;
}
@Override
public RulesetMetadata getRulesetMetadata()
{
return parent;
}
public MetadataBuilder setRulesetMetadata(RulesetMetadata parent)
{
if (parent != null)
this.parent = parent;
return this;
}
@Override
public boolean isOverrideProvider()
{
return overrideProvider;
}
/**
* Sets whether or not this provider's rules should override rules from other providers
* with the same ID.
*/
public MetadataBuilder setOverrideProvider(boolean overrideProvider)
{
this.overrideProvider = overrideProvider;
return this;
}
@Override
public String getOrigin()
{
return origin == null ? super.getOrigin() : origin;
}
/**
* Set the descriptive information indicating where the corresponding {@link RuleProvider} instance is located (eg, a path to an XML file on disk,
* or an {@link Addon} coordinate and class name).
*/
public MetadataBuilder setOrigin(String origin)
{
this.origin = origin;
return this;
}
@Override
public Class<? extends RulePhase> getPhase()
{
return phase == null ? DEFAULT_PHASE : phase;
}
/**
* Set the {@link RulePhase} in which the {@link Rule} instances from the corresponding {@link RuleProvider} instance should be executed.
* <p>
* The default phase is {@link org.jboss.windup.config.phase.MigrationRulesPhase}.
*/
public MetadataBuilder setPhase(Class<? extends RulePhase> phase)
{
this.phase = phase;
return this;
}
@Override
public List<Class<? extends RuleProvider>> getExecuteAfter()
{
return Collections.unmodifiableList(executeAfter);
}
/**
* Set the list of {@link RuleProvider} classes that should execute before the {@link Rule} instances in the corresponding {@link RuleProvider}
* instance.
*
* <p>
* {@link RuleProvider} references can also be specified based on id ({@link #getExecuteAfterIDs}).
*/
public MetadataBuilder setExecuteAfter(List<Class<? extends RuleProvider>> executeAfter)
{
this.executeAfter = new ArrayList<>(executeAfter);
return this;
}
/**
* Ad an entry to the list of {@link RuleProvider} classes that should execute after the {@link Rule} instances in the corresponding
* {@link RuleProvider} instance.
*
* {@link RuleProvider}s can also be specified based on id ({@link #getExecuteBeforeIDs}).
*/
public MetadataBuilder addExecuteAfter(Class<? extends RuleProvider> type)
{
if (type != null)
{
executeAfter.add(type);
}
return this;
}
/**
* Sets the human readable description.
*/
public MetadataBuilder setDescription(String description)
{
this.description = description;
return this;
}
@Override
public String getDescription()
{
return this.description;
}
@Override
public List<String> getExecuteAfterIDs()
{
return Collections.unmodifiableList(executeAfterIDs);
}
/**
* Set the list of the {@link RuleProvider} classes that should execute before the {@link Rule} instances in the corresponding
* {@link RuleProvider} instance.
*
* <p>
* This is returned as a list of Rule IDs in order to support extensions that cannot depend on each other via class names. For example, in the
* case of the Groovy rules extension, a single class covers many rules with their own IDs.
*
* For specifying Java-based rules, {@link #getExecuteAfter()} is preferred.
*/
public MetadataBuilder setExecuteAfterIDs(List<String> executeAfterIDs)
{
this.executeAfterIDs = new ArrayList<>(executeAfterIDs);
return this;
}
/**
* Add an entry to the list of the {@link RuleProvider} classes that should execute before the {@link Rule} instances in the corresponding
* {@link RuleProvider} instance.
*
* <p>
* This is returned as a list of Rule IDs in order to support extensions that cannot depend on each other via class names. For example, in the
* case of the Groovy rules extension, a single class covers many rules with their own IDs.
*
* For specifying Java-based rules, {@link #getExecuteAfter()} is preferred.
*/
public MetadataBuilder addExecuteAfterId(String id)
{
if (id != null)
{
executeAfterIDs.add(id);
}
return this;
}
@Override
public List<Class<? extends RuleProvider>> getExecuteBefore()
{
return Collections.unmodifiableList(executeBefore);
}
/**
* Set the list of {@link RuleProvider} classes that should execute after the {@link Rule} instances in the corresponding {@link RuleProvider}
* instance.
*
* {@link RuleProvider}s can also be specified based on id ({@link #getExecuteBeforeIDs}).
*/
public MetadataBuilder setExecuteBefore(List<Class<? extends RuleProvider>> executeBefore)
{
this.executeBefore = new ArrayList<>(executeBefore);
return this;
}
/**
* Ad an entry to the list of {@link RuleProvider} classes that should execute after the {@link Rule} instances in the corresponding
* {@link RuleProvider} instance.
*
* {@link RuleProvider}s can also be specified based on id ({@link #getExecuteBeforeIDs}).
*/
public MetadataBuilder addExecuteBefore(Class<? extends RuleProvider> type)
{
if (type != null)
{
executeBefore.add(type);
}
return this;
}
@Override
public List<String> getExecuteBeforeIDs()
{
return Collections.unmodifiableList(executeBeforeIDs);
}
/**
* Set the list of the {@link RuleProvider} classes that should execute after the {@link Rule} instances in the corresponding {@link RuleProvider}
* instance.
*
* <p>
* This is returned as a list of Rule IDs in order to support extensions that cannot depend on each other via class names. For example, in the
* case of the Groovy rules extension, a single class covers many rules with their own IDs.
*
* For specifying Java-based rules, {@link #getExecuteBefore()} is preferred.
*/
public MetadataBuilder setExecuteBeforeIDs(List<String> executeBeforeIDs)
{
this.executeBeforeIDs = new ArrayList<>(executeBeforeIDs);
return this;
}
/**
* Add to the list of the {@link RuleProvider} classes that should execute after the {@link Rule} instances in the corresponding
* {@link RuleProvider} instance.
*
* <p>
* This is returned as a list of Rule IDs in order to support extensions that cannot depend on each other via class names. For example, in the
* case of the Groovy rules extension, a single class covers many rules with their own IDs.
*
* For specifying Java-based rules, {@link #getExecuteBefore()} is preferred.
*/
public MetadataBuilder addExecuteBeforeId(String id)
{
if (id != null)
{
executeBeforeIDs.add(id);
}
return this;
}
/**
* Add to the {@link Set} of tags by which this {@link RulesetMetadata} is classified.
* <p>
* Inherits from {@link RulesetMetadata#getTags()} if available.
*/
public MetadataBuilder addTags(String tag, String... tags)
{
if (!StringUtils.isBlank(tag))
this.tags.add(tag.trim());
if (tags != null)
{
for (String t : tags)
{
if (!StringUtils.isBlank(t))
this.tags.add(t.trim());
}
}
return this;
}
public MetadataBuilder addTag(String tag)
{
if (!StringUtils.isBlank(tag))
{
this.tags.add(tag.trim());
}
return this;
}
@Override
public Set<String> getTags()
{
return join(this.tags, parent.getTags(), super.getTags());
}
/**
* Set the tags by which this {@link RulesetMetadata} is classified.
* <p>
* Inherits from {@link RulesetMetadata#getTags()} if available.
*/
public MetadataBuilder setTags(List<String> tags)
{
if (tags == null)
this.tags = new HashSet<>();
else
this.tags = Collections.unmodifiableSet(new HashSet<>(tags));
return this;
}
@Override
public Set<TechnologyReference> getSourceTechnologies()
{
return join(sourceTechnologies, super.getSourceTechnologies(), parent.getSourceTechnologies());
}
/**
* Add to the {@link Set} of source {@link TechnologyReference} instances to which this {@link RuleProvider} is related.
* <p>
* Inherits from {@link RulesetMetadata#getSourceTechnologies()} if available.
*/
public MetadataBuilder addSourceTechnology(TechnologyReference reference)
{
if (reference != null)
sourceTechnologies.add(reference);
return this;
}
@Override
public Set<TechnologyReference> getTargetTechnologies()
{
return join(targetTechnologies, super.getTargetTechnologies(), parent.getTargetTechnologies());
}
/**
* Add to the {@link Set} of target {@link TechnologyReference} instances to which this {@link RuleProvider} is related.
* <p>
* Inherits from {@link RulesetMetadata#getTargetTechnologies()} if available.
*/
public MetadataBuilder addTargetTechnology(TechnologyReference reference)
{
if (reference != null)
targetTechnologies.add(reference);
return this;
}
@Override
public Set<AddonId> getRequiredAddons()
{
return join(requiredAddons, super.getRequiredAddons(), parent.getRequiredAddons());
}
/**
* Add to the {@link Set} of {@link Addon}s required to run this rule-set. (<b>Note:</b> This is typically only used in situations where rules are
* provided externally - such as XML - whereas in Java, the {@link Addon} will already define its dependencies on other addons directly.)
* <p>
* Inherits from {@link RulesetMetadata#getRequiredAddons()} if available.
*/
public MetadataBuilder addRequiredAddon(AddonId reference)
{
if (reference != null)
requiredAddons.add(reference);
return this;
}
/**
* Whether Windup should stop execution if this provider's rule execution ends with an exception.
*
* By default, the exceptions are only logged and the failing rule appears in report. The rule itself is responsible for handling exceptions and
* storing them into the graph.
*/
public MetadataBuilder setHaltOnException(boolean haltOnException)
{
this.haltOnException = haltOnException;
return this;
}
@Override
public boolean isHaltOnException()
{
return haltOnException;
}
@Override
public boolean isDisabled()
{
return disabled;
}
/**
* Join N sets.
*/
@SafeVarargs
private final <T> Set<T> join(Set<T>... sets)
{
Set<T> result = new HashSet<>();
if (sets == null)
return result;
for (Set<T> set : sets)
{
if (set != null)
result.addAll(set);
}
return result;
}
}