/*
* Chrysalix
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* Chrysalix is free software. Unless otherwise indicated, all code in Chrysalix
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* Chrysalix 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.chrysalix.transformation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.chrysalix.Chrysalix;
import org.chrysalix.ChrysalixException;
import org.chrysalix.ChrysalixI18n;
import org.chrysalix.ChrysalixLexicon;
import org.chrysalix.common.CheckArg;
import org.chrysalix.common.Logger;
import org.chrysalix.operation.BuiltInOperationDescriptorProvider;
import org.chrysalix.operation.ValueDescriptorImpl;
import org.chrysalix.transformation.ValidationProblem.Severity;
import org.modelspace.Model;
import org.modelspace.ModelObject;
import org.modelspace.ModelProperty;
import org.modelspace.Modelspace;
import org.modelspace.ModelspaceException;
/**
* A factory for creating {@link Transformation transformation}-related objects.
*/
public class TransformationFactory {
private static final String ERROR_ADDING_TRANSFORMATION_OPERATION =
"The operation '%s' could not be added to transformation '%s.'";
private static final String ERROR_ADDING_TRANSFORMATION_SOURCE_MODEL =
"A source model at path '%s' could not be added to transformation '%s.'";
private static final String ERROR_ADDING_TRANSFORMATION_SOURCE_MODELS =
"An error occurred adding source models to transformation '%s.'";
private static final String ERROR_ADDING_TRANSFORMATION_TARGET_MODEL =
"A target model at path '%s' could not be added to transformation '%s.'";
private static final String ERROR_ADDING_TRANSFORMATION_TARGET_MODELS =
"An error occurred adding target models to transformation '%s.'";
private static final String ERROR_DESCRIPTOR_TYPE = "Error descriptor '%s' was not an operation descriptor";
private static final String ERROR_FINDING_OPERATION_DESCRIPTOR = "Error trying to find operation descriptor with ID '%s'";
private static final String ERROR_FINDING_OPERATIONS = "Unable to obtain operations for transformation model '%s'";
private static final String ERROR_FINDING_SOURCE_MODELS = "Unable to obtain source models for transformation model '%s'";
private static final String ERROR_FINDING_TARGET_MODELS = "Unable to obtain targets models for transformation model '%s'";
private static final String ERROR_FINDING_TRANSFORMATION_MODEL_NAME =
"Unable to determine transformation's name when adding operation '%s'";
private static final String ERROR_REMOVING_TRANSFORMATION_OPERATION =
"The operation '%s' could not be removed from transformation '%s.'";
private static final String ERROR_REMOVING_TRANSFORMATION_SOURCE_MODELS =
"An error occurred removing source models from transformation '%s.'";
private static final String ERROR_REMOVING_TRANSFORMATION_TARGET_MODELS =
"An error occurred removing target models from transformation '%s.'";
private static final String ERROR_REMOVING_UNADDED_TRANSFORMATION_SOURCE_MODEL =
"The source model at path '%s' could not be removed from transformation '%s' as it has never been added.";
private static final String ERROR_REMOVING_UNADDED_TRANSFORMATION_TARGET_MODEL =
"The target model at path '%s' could not be removed from transformation '%s' as it has never been added.";
private static final String NEW_TRANSFORMATION_NAME = "New Transformation";
private static final String UNABLE_TO_FIND_TRANSFORMATION_ID = "Unble to find transformation path";
private static final String UNABLE_TO_FIND_TRANSFORMATION_ID_OR_NAME = "Unable to find transformation path or name";
private static final String UNKNOWN_NAME = "**Unknown**";
private static final OperationDescriptorProvider BUILT_IN_OP_PROVIDER = new BuiltInOperationDescriptorProvider();
static final Logger LOGGER = Logger.logger( TransformationFactory.class );
/**
* A descriptor registry
*/
public static final Map< Modelspace, TransformationFactory > REGISTRY = new HashMap<>();
/**
* @param transformationId
* the source transformation's identifier (cannot be <code>null</code> or empty)
* @param message
* the problem message (can be <code>null</code> or empty)
* @return the problem (never <code>null</code>)
* @throws IllegalArgumentException
* if the transformation identifier is <code>null</code>
*/
public static ValidationProblem createError( final String transformationId,
final String message ) {
CheckArg.notEmpty( transformationId, "transformationId" );
return new Problem( Severity.ERROR, transformationId, message );
}
/**
* Creates an ID using the {@link Chrysalix#NAMESPACE_PREFIX} and {@link Chrysalix#NAMESPACE_URI}.
*
* @param clazz
* the class whose name will be used to construct the identifier (cannot be <code>null</code>)
* @return the ID (never <code>null</code>)
* @throws IllegalArgumentException
* if the class is <code>null</code>
*/
public static String createId( final Class< ? > clazz ) {
return createId( clazz, null );
}
/**
* @param clazz
* the class whose name will be used to construct the identifier (cannot be <code>null</code>)
* @param suffix
* the identifier's suffix (can be <code>null</code> or empty)
* @return the ID (never <code>null</code>)
* @throws IllegalArgumentException
* if the class is <code>null</code>
*/
public static String createId( final Class< ? > clazz,
final String suffix ) {
CheckArg.notNull( clazz, "clazz" );
final StringBuilder result = new StringBuilder( clazz.getName() );
if ( ( suffix != null ) && !suffix.isEmpty() ) {
return result.append( '.' ).append( suffix ).toString();
}
return result.toString();
}
/**
* @param transformationId
* the source transformation's identifier (cannot be <code>null</code>)
* @param message
* the problem message (can be <code>null</code> or empty)
* @return the problem (never <code>null</code>)
* @throws IllegalArgumentException
* if the transformation identifier is <code>null</code>
*/
public static ValidationProblem createInfo( final String transformationId,
final String message ) {
CheckArg.notEmpty( transformationId, "transformationId" );
return new Problem( Severity.INFO, transformationId, message );
}
/**
* @param transformationId
* the source transformation's identifier (cannot be <code>null</code>)
* @param message
* the problem message (can be <code>null</code> or empty)
* @return the problem (never <code>null</code>)
* @throws IllegalArgumentException
* if the transformation identifier is <code>null</code>
*/
public static ValidationProblem createOk( final String transformationId,
final String message ) {
CheckArg.notEmpty( transformationId, "transformationId" );
return new Problem( Severity.OK, transformationId, message );
}
/**
* @return an empty validation problems collection (never <code>null</code>)
*/
public static ValidationProblems createValidationProblems() {
return new Problems();
}
/**
* @param valueId
* the value identifier (cannot be <code>null</code> or empty)
* @param valueDescription
* the value description (cannot be <code>null</code> or empty)
* @param valueName
* the value name (cannot be <code>null</code> or empty)
* @param valueType
* the value type (cannot be <code>null</code>)
* @param isModifiable
* <code>true</code> if value is modifiable
* @param requiredValueCount
* the number of required values (cannot be a negative number)
* @param isUnbounded
* <code>true</code> if there is no limit to the number of values
* @return the value descriptor (never <code>null</code>)
*/
public static < T > ValueDescriptor< T > createValueDescriptor( final String valueId,
final String valueDescription,
final String valueName,
final Class< T > valueType,
final boolean isModifiable,
final int requiredValueCount,
final boolean isUnbounded ) {
return new ValueDescriptorImpl<>( valueId,
valueDescription,
valueName,
valueType,
isModifiable,
requiredValueCount,
isUnbounded );
}
/**
* @param transformationId
* the source transformation's identifier (cannot be <code>null</code> or empty)
* @param message
* the problem message (can be <code>null</code> or empty)
* @return the problem (never <code>null</code>)
* @throws IllegalArgumentException
* if the transformation identifier is <code>null</code>
*/
public static ValidationProblem createWarning( final String transformationId,
final String message ) {
CheckArg.notEmpty( transformationId, "transformationId" );
return new Problem( Severity.WARNING, transformationId, message );
}
/**
* Creates a descriptor that is modifiable, requires one value, and is limited to one value.
*
* @param valueId
* the value identifier (cannot be <code>null</code> or empty)
* @param valueDescription
* the value description (cannot be <code>null</code> or empty)
* @param valueName
* the value name (cannot be <code>null</code> or empty)
* @param valueType
* the value type (cannot be <code>null</code>)
* @return the value descriptor (never <code>null</code>)
*/
public static < T > ValueDescriptor< T > createWritableBoundedOneValueDescriptor( final String valueId,
final String valueDescription,
final String valueName,
final Class< T > valueType ) {
return createValueDescriptor( valueId, valueDescription, valueName, valueType, true, 1, false );
}
private final Modelspace modeler;
private final Set< OperationDescriptorProvider > operationProviders;
private final Set< ValueDescriptor< ? >> descriptors;
/**
* @param factoryModeler
* the modelspace that this factory is working with (cannot be <code>null</code>)
*/
public TransformationFactory( final Modelspace factoryModeler ) {
CheckArg.notNull( factoryModeler, "factoryModeler" );
this.modeler = factoryModeler;
this.descriptors = new HashSet<>();
this.operationProviders = new HashSet<>( 5 );
addOperationProvider( BUILT_IN_OP_PROVIDER );
if ( !REGISTRY.containsKey( this.modeler ) ) {
REGISTRY.put( this.modeler, this );
}
}
/**
* @param provider
* the provider being added (cannot be <code>null</code>)
*/
public void addOperationProvider( final OperationDescriptorProvider provider ) {
CheckArg.notNull( provider, "provider" );
if ( this.operationProviders.add( provider ) ) {
for ( final ValueDescriptor< ? > descriptor : provider.descriptors() ) {
this.descriptors.add( descriptor );
}
}
}
/**
* Creates a new transformation or retrieves the existing one.
*
* @param transformationModel
* the transformation model whose domain object is being created (cannot be <code>null</code> or empty)
* @return the transformation model domain object (never <code>null</code>)
* @throws ModelspaceException
* if an error occurs
* @throws IllegalArgumentException
* if model is <code>null</code> or if it is not a transformation model
*/
public Transformation createTransformation( final Model transformationModel ) throws ModelspaceException {
CheckArg.notNull( transformationModel, "transformationModel" );
CheckArg.notNull( transformationModel.metamodel().id(), Transformation.METAMODEL_ID );
return new TransformationImpl( transformationModel );
}
/**
* Creates a new transformation or retrieves the existing one.
*
* @param path
* the path of the model, not including the name (cannot be <code>null</code> or empty)
* @return the transformation model (never <code>null</code>)
* @throws ModelspaceException
* if an error occurs
* @throws IllegalArgumentException
* if path is <code>null</code> or empty
*/
public Transformation createTransformation( final String path ) throws ModelspaceException {
return new TransformationImpl( this.modeler, path );
}
/**
* @param id
* the identifier of the descriptor being requested (cannot be <code>null</code> or empty)
* @return the descriptor (never <code>null</code>)
* @throws ChrysalixException
* if the descriptor cannot be found
*
*/
public ValueDescriptor< ? > descriptor( final String id ) throws ChrysalixException {
CheckArg.notEmpty( id, "id" );
for ( final ValueDescriptor< ? > descriptor : descriptors() ) {
if ( id.equals( descriptor.id() ) ) {
return descriptor;
}
}
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_FINDING_OPERATION_DESCRIPTOR, id ) );
}
/**
* @return an unmodifiable list of descriptors (never <code>null</code> or empty)
*/
public Set< ValueDescriptor< ? > > descriptors() {
return Collections.unmodifiableSet( this.descriptors );
}
/**
* @return the modeler associated with this factory (never <code>null</code>)
*/
protected Modelspace modelspace() {
return this.modeler;
}
private static final class Problem implements ValidationProblem {
private final Severity severity;
private final String message;
private final String sourceId;
Problem( final Severity problemSeverity,
final String problemPartId,
final String problemMessage ) {
this.severity = problemSeverity;
this.message = problemMessage;
this.sourceId = problemPartId;
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblem#isError()
*/
@Override
public boolean isError() {
return ( this.severity == Severity.ERROR );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblem#isInfo()
*/
@Override
public boolean isInfo() {
return ( this.severity == Severity.INFO );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblem#isOk()
*/
@Override
public boolean isOk() {
return ( this.severity == Severity.OK );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblem#isWarning()
*/
@Override
public boolean isWarning() {
return ( this.severity == Severity.WARNING );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblem#message()
*/
@Override
public String message() {
return this.message;
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblem#severity()
*/
@Override
public Severity severity() {
return this.severity;
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblem#sourceId()
*/
@Override
public String sourceId() {
return this.sourceId;
}
}
private static final class Problems extends ArrayList< ValidationProblem > implements ValidationProblems {
private Severity severity;
Problems() {
this.severity = Severity.OK;
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#add(int, java.lang.Object)
*/
@Override
public void add( final int index,
final ValidationProblem problem ) {
super.add( index, problem );
updateSeverity( problem );
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#add(java.lang.Object)
*/
@Override
public boolean add( final ValidationProblem problem ) {
final boolean added = super.add( problem );
if ( added ) {
updateSeverity( problem );
}
return added;
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#addAll(java.util.Collection)
*/
@Override
public boolean addAll( final Collection< ? extends ValidationProblem > c ) {
if ( this.severity != Severity.ERROR ) {
for ( final ValidationProblem problem : c ) {
updateSeverity( problem );
if ( this.severity == Severity.ERROR ) {
break;
}
}
}
return super.addAll( c );
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#addAll(int, java.util.Collection)
*/
@Override
public boolean addAll( final int index,
final Collection< ? extends ValidationProblem > c ) {
if ( this.severity != Severity.ERROR ) {
for ( final ValidationProblem problem : c ) {
updateSeverity( problem );
if ( this.severity == Severity.ERROR ) {
break;
}
}
}
return super.addAll( index, c );
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#clear()
*/
@Override
public void clear() {
super.clear();
this.severity = Severity.OK;
}
private void determineSeverity() {
this.severity = Severity.OK;
for ( final ValidationProblem problem : this ) {
updateSeverity( problem );
if ( this.severity == Severity.ERROR ) {
break;
}
}
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblems#isError()
*/
@Override
public boolean isError() {
return ( this.severity == Severity.ERROR );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblems#isInfo()
*/
@Override
public boolean isInfo() {
return ( this.severity == Severity.INFO );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblems#isOk()
*/
@Override
public boolean isOk() {
return ( this.severity == Severity.OK );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.ValidationProblems#isWarning()
*/
@Override
public boolean isWarning() {
return ( this.severity == Severity.WARNING );
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#remove(int)
*/
@Override
public ValidationProblem remove( final int index ) {
final ValidationProblem problem = super.remove( index );
if ( this.severity == problem.severity() ) {
determineSeverity();
}
return problem;
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#remove(java.lang.Object)
*/
@Override
public boolean remove( final Object o ) {
final boolean removed = super.remove( o );
if ( removed ) {
final ValidationProblem problem = ( ValidationProblem ) o;
if ( problem.severity() == this.severity ) {
determineSeverity();
}
}
return removed;
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#removeAll(java.util.Collection)
*/
@Override
public boolean removeAll( final Collection< ? > c ) {
final boolean changed = super.removeAll( c );
if ( changed ) {
determineSeverity();
}
return changed;
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#retainAll(java.util.Collection)
*/
@Override
public boolean retainAll( final Collection< ? > c ) {
final boolean changed = super.retainAll( c );
if ( changed ) {
determineSeverity();
}
return changed;
}
/**
* {@inheritDoc}
*
* @see java.util.ArrayList#set(int, java.lang.Object)
*/
@Override
public ValidationProblem set( final int index,
final ValidationProblem newProblem ) {
final ValidationProblem oldProblem = super.set( index, newProblem );
if ( newProblem.severity().isMoreSevereThan( this.severity ) ) {
this.severity = newProblem.severity();
} else if ( oldProblem.severity() == this.severity ) {
determineSeverity();
}
return oldProblem;
}
private void updateSeverity( final ValidationProblem problem ) {
if ( problem.severity().isMoreSevereThan( this.severity ) ) {
this.severity = problem.severity();
}
}
}
private static class TransformationImpl implements Transformation {
private final Model model;
/**
* Constructs an existing transformation model.
*
* @param model
* the transformation model (cannot be <code>null</code>)
*/
TransformationImpl( final Model model ) {
CheckArg.notNull( model, "model" );
this.model = model;
}
/**
* Creates a new transformation model.
*
* @param modeler
* the modeler used to create the transformation model (cannot be <code>null</code>)
* @param parentPath
* the path of the container where the new transformation model will be created (cannot be <code>null</code> or
* empty)
* @throws ModelspaceException
* if an error occurs
*/
TransformationImpl( final Modelspace modeler,
final String parentPath ) throws ModelspaceException {
CheckArg.notNull( modeler, "modeler" );
CheckArg.notEmpty( parentPath, "parentPath" );
this.model = modeler.newModel( parentPath + "/" + ChrysalixI18n.localize( NEW_TRANSFORMATION_NAME ),
Transformation.METAMODEL_ID,
false );
}
/**
* {@inheritDoc}
*
* @see org.modelspace.ModelElement#absolutePath()
*/
@Override
public String absolutePath() throws ModelspaceException {
return this.model.modelRelativePath();
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#add(org.chrysalix.transformation.Operation[])
*/
@Override
public void add( final Operation< ? >... operationsToAdd ) throws ChrysalixException {
CheckArg.isNotEmpty( operationsToAdd, "operationsToAdd" );
for ( final Operation< ? > operation : operationsToAdd ) {
CheckArg.notNull( operation, "operation" );
String opName = ChrysalixI18n.localize( UNKNOWN_NAME );
try {
final ModelObject[] modelObjects = this.model.addChildOfType( ChrysalixLexicon.Operation.NODE_TYPE,
operation.descriptorId() );
final ModelObject operationModelObject = modelObjects[ 0 ];
opName = operationModelObject.name();
LOGGER.debug( "Added operation '%s' with descriptor '%s' to transformation '%s'",
opName,
operation.descriptorId(),
id() );
} catch ( final Exception e ) {
try {
throw new ChrysalixException( e,
ChrysalixI18n.localize( ERROR_ADDING_TRANSFORMATION_OPERATION,
opName,
this.model.name() ) );
} catch ( final ModelspaceException error ) {
throw new ChrysalixException( error,
ChrysalixI18n.localize( ERROR_FINDING_TRANSFORMATION_MODEL_NAME, opName ) );
}
}
}
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#addSource(org.modelspace.Model[])
*/
@Override
public void addSource( final Model... sources ) throws ChrysalixException {
CheckArg.isNotEmpty( sources, "sources" );
try {
final String[] pathBeingAdded = new String[ sources.length ];
for ( int i = 0; i < sources.length; ++i ) {
CheckArg.notNull( sources[ i ], "sources[" + i + ']' );
pathBeingAdded[ i ] = sources[ i ].absolutePath();
}
String[] sourcePaths = new String[ 0 ]; // current sources go here
final ModelProperty property = this.model.property( ChrysalixLexicon.Transformation.SOURCES );
// make sure no duplicates
if ( property != null ) {
sourcePaths = property.stringValues();
for ( final Object path : pathBeingAdded ) {
for ( final Object source : sourcePaths ) {
if ( path.equals( source ) ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_TRANSFORMATION_SOURCE_MODEL,
path,
getName() ) );
}
}
}
}
final String[] newSources = new String[ pathBeingAdded.length + sourcePaths.length ];
System.arraycopy( pathBeingAdded, 0, newSources, 0, pathBeingAdded.length );
if ( sourcePaths.length != 0 ) {
System.arraycopy( sourcePaths, 0, newSources, pathBeingAdded.length, sourcePaths.length );
}
this.model.setProperty( ChrysalixLexicon.Transformation.SOURCES, ( Object[] ) newSources );
} catch ( final Exception e ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_TRANSFORMATION_SOURCE_MODELS,
getName() ) );
}
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#addTarget(org.modelspace.Model[])
*/
@Override
public void addTarget( final Model... targets ) throws ChrysalixException {
CheckArg.isNotEmpty( targets, "targets" );
try {
final String[] pathsBeingAdded = new String[ targets.length ];
for ( int i = 0; i < targets.length; ++i ) {
CheckArg.notNull( targets[ i ], "targets[" + i + ']' );
pathsBeingAdded[ i ] = targets[ i ].absolutePath();
}
String[] targetPaths = new String[ 0 ]; // current targets go here
final ModelProperty property = this.model.property( ChrysalixLexicon.Transformation.TARGETS );
// make sure no duplicates
if ( property != null ) {
targetPaths = property.stringValues();
for ( final Object path : pathsBeingAdded ) {
for ( final Object target : targetPaths ) {
if ( path.equals( target ) ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_TRANSFORMATION_TARGET_MODEL,
path,
getName() ) );
}
}
}
}
final String[] newTargets = new String[ pathsBeingAdded.length + targetPaths.length ];
System.arraycopy( pathsBeingAdded, 0, newTargets, 0, pathsBeingAdded.length );
if ( targetPaths.length != 0 ) {
System.arraycopy( targetPaths, 0, newTargets, pathsBeingAdded.length, targetPaths.length );
}
this.model.setProperty( ChrysalixLexicon.Transformation.TARGETS, ( Object ) newTargets );
} catch ( final Exception e ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_TRANSFORMATION_TARGET_MODELS,
getName() ) );
}
}
private TransformationFactory factory() {
return REGISTRY.get( this.model.modelspace() );
}
private String getName() {
try {
final String name = this.model.name();
return name;
} catch ( final ModelspaceException e ) {
return ChrysalixI18n.localize( UNKNOWN_NAME );
}
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#id()
*/
@Override
public String id() throws ChrysalixException {
try {
return model().absolutePath();
} catch ( final ModelspaceException e ) {
try {
throw new ChrysalixException( e, ChrysalixI18n.localize( UNABLE_TO_FIND_TRANSFORMATION_ID, name() ) );
} catch ( final ModelspaceException error ) {
final ChrysalixException pe =
new ChrysalixException( ChrysalixI18n.localize( UNABLE_TO_FIND_TRANSFORMATION_ID_OR_NAME ) );
pe.addSuppressed( error );
throw pe;
}
}
}
/**
* {@inheritDoc}
*
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator< Operation< ? >> iterator() {
try {
final List< Operation< ? >> copy = Arrays.asList( operations() );
return copy.iterator();
} catch ( final Exception e ) {
throw new RuntimeException( e );
}
}
@Override
public Model model() {
return this.model;
}
/**
* {@inheritDoc}
*
* @see org.modelspace.ModelElement#modelRelativePath()
*/
@Override
public String modelRelativePath() throws ModelspaceException {
return this.model.modelRelativePath();
}
/**
* {@inheritDoc}
*
* @see org.modelspace.ModelElement#name()
*/
@Override
public String name() throws ModelspaceException {
return this.model.name();
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#operations()
*/
@Override
public Operation< ? >[] operations() throws ChrysalixException {
final List< Operation< ? > > operations = new ArrayList<>();
try {
for ( final ModelObject kid : this.model.childrenOfType( ChrysalixLexicon.Operation.NODE_TYPE ) ) {
final ValueDescriptor< ? > descriptor = factory().descriptor( kid.name() );
if ( descriptor instanceof OperationDescriptor ) {
final Operation< ? > operation = ( ( OperationDescriptor< ? > ) descriptor ).newInstance( kid, this );
operations.add( operation );
} else {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_DESCRIPTOR_TYPE, descriptor.id() ) );
}
}
} catch ( final ModelspaceException e ) {
throw new ChrysalixException( e, ChrysalixI18n.localize( ERROR_FINDING_OPERATIONS, getName() ) );
}
return operations.toArray( new Operation< ? >[ operations.size() ] );
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#remove(org.chrysalix.transformation.Operation[])
*/
@Override
public void remove( final Operation< ? >... operationsToRemove ) throws ChrysalixException {
CheckArg.isNotEmpty( operationsToRemove, "operationsToRemove" );
for ( final Operation< ? > operation : operationsToRemove ) {
CheckArg.notNull( operation, "operation" );
try {
this.model.removeChild( operation.descriptorId() );
} catch ( final Exception e ) {
throw new ChrysalixException( e,
ChrysalixI18n.localize( ERROR_REMOVING_TRANSFORMATION_OPERATION,
operation.descriptorId(),
getName() ) );
}
}
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#removeSource(org.modelspace.Model[])
*/
@Override
public void removeSource( final Model... sources ) throws ChrysalixException {
CheckArg.isNotEmpty( sources, "sources" );
try {
final Object[] pathsBeingRemoved = new Object[ sources.length ];
for ( int i = 0; i < sources.length; ++i ) {
CheckArg.notNull( sources[ i ], "targets[" + i + ']' );
pathsBeingRemoved[ i ] = sources[ i ].absolutePath();
}
if ( !this.model.hasProperty( ChrysalixLexicon.Transformation.SOURCES ) ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_TRANSFORMATION_SOURCE_MODELS,
getName() ) );
}
final ModelProperty property = this.model.property( ChrysalixLexicon.Transformation.SOURCES );
// remove paths from current paths
final List< Object > newSourcePaths = new ArrayList<>( Arrays.asList( property.values() ) );
for ( final Object path : pathsBeingRemoved ) {
if ( !newSourcePaths.remove( path ) ) {
// not found to remove
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_UNADDED_TRANSFORMATION_SOURCE_MODEL,
path,
getName() ) );
}
}
final Object[] newValue = ( newSourcePaths.isEmpty() ? null : newSourcePaths.toArray() );
this.model.setProperty( ChrysalixLexicon.Transformation.SOURCES, newValue );
} catch ( final Exception e ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_TRANSFORMATION_SOURCE_MODELS,
getName() ) );
}
}
@Override
public void removeTarget( final Model... targets ) throws ChrysalixException {
CheckArg.isNotEmpty( targets, "targets" );
try {
final Object[] pathsBeingRemoved = new Object[ targets.length ];
for ( int i = 0; i < targets.length; ++i ) {
CheckArg.notNull( targets[ i ], "targets[" + i + ']' );
pathsBeingRemoved[ i ] = targets[ i ].absolutePath();
}
if ( !this.model.hasProperty( ChrysalixLexicon.Transformation.TARGETS ) ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_TRANSFORMATION_TARGET_MODELS,
getName() ) );
}
final ModelProperty property = this.model.property( ChrysalixLexicon.Transformation.TARGETS );
// remove paths from current paths
final List< Object > newTargetPaths = new ArrayList<>( Arrays.asList( property.values() ) );
for ( final Object path : pathsBeingRemoved ) {
if ( !newTargetPaths.remove( path ) ) {
// not found to remove
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_UNADDED_TRANSFORMATION_TARGET_MODEL,
path,
getName() ) );
}
}
final Object[] newValue = ( newTargetPaths.isEmpty() ? null : newTargetPaths.toArray() );
this.model.setProperty( ChrysalixLexicon.Transformation.TARGETS, newValue );
} catch ( final Exception e ) {
throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_TRANSFORMATION_TARGET_MODELS,
getName() ) );
}
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#sources()
*/
@Override
public Model[] sources() throws ChrysalixException {
try {
if ( this.model.hasProperty( ChrysalixLexicon.Transformation.SOURCES ) ) {
final String[] sourceModelPaths =
this.model.property( ChrysalixLexicon.Transformation.SOURCES ).stringValues();
final Set< Model > sources = new HashSet<>( sourceModelPaths.length );
final Modelspace modeler = this.model.modelspace();
for ( final String path : sourceModelPaths ) {
final Model source = modeler.model( path );
if ( source != null ) {
sources.add( source );
} else {
// TODO maybe create a proxy model or missing model???
LOGGER.debug( "Source model at path '%s' was not found", path );
}
}
return sources.toArray( new Model[ sources.size() ] );
}
return Model.NO_MODELS;
} catch ( final ModelspaceException e ) {
throw new ChrysalixException( e, ChrysalixI18n.localize( ERROR_FINDING_SOURCE_MODELS, getName() ) );
}
}
/**
* {@inheritDoc}
*
* @see org.chrysalix.transformation.Transformation#targets()
*/
@Override
public Model[] targets() throws ChrysalixException {
try {
if ( this.model.hasProperty( ChrysalixLexicon.Transformation.TARGETS ) ) {
final String[] targetModelPaths =
this.model.property( ChrysalixLexicon.Transformation.TARGETS ).stringValues();
final Set< Model > targets = new HashSet<>( targetModelPaths.length );
final Modelspace modeler = this.model.modelspace();
for ( final String path : targetModelPaths ) {
final Model target = modeler.model( path );
if ( target != null ) {
targets.add( target );
} else {
// TODO maybe create a proxy model or missing model???
LOGGER.debug( "Target model at path '%s' was not found", path );
}
}
return targets.toArray( new Model[ targets.size() ] );
}
return Model.NO_MODELS;
} catch ( final ModelspaceException e ) {
throw new ChrysalixException( e, ChrysalixI18n.localize( ERROR_FINDING_TARGET_MODELS, getName() ) );
}
}
}
}