/* * 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.operation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; 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.transformation.Operation; import org.chrysalix.transformation.OperationDescriptor; import org.chrysalix.transformation.Transformation; import org.chrysalix.transformation.TransformationFactory; import org.chrysalix.transformation.ValidationProblems; import org.chrysalix.transformation.Value; import org.chrysalix.transformation.ValueDescriptor; import org.modelspace.ModelElement; import org.modelspace.ModelObject; import org.modelspace.ModelProperty; import org.modelspace.ModelspaceException; /** * A base class implementation for an {@link Operation operation}. * * @param <T> * the operation's result type */ abstract class AbstractOperation< T > extends ValueImpl< T > implements Operation< T > { protected static final String ERROR_ADDING_OR_REMOVING_OPERATION_INPUT = "There was an error adding or removing terms for operation '%s' in transformation '%s' using descriptor '%s'"; protected static final String ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME = "There was an error adding or removing terms for an operation (name cannot be determined) in transformation '%s' using descriptor '%s'"; private static final String ERROR_FINDING_ALL_INPUTS = "There was an error obtaining the inputs for operation '%s' in transformation '%s.'"; private static final String ERROR_FINDING_ALL_INPUTS_UNKNOWN_NAME = "There was an error obtaining the inputs for operation (name cannot be determined) in transformation '%s.'"; private static final String ERROR_FINDING_DESCRIPTOR_INPUTS = "There was an error obtaining the inputs for descriptor '%s' of operation '%s' in transformation '%s.'"; private static final String ERROR_FINDING_DESCRIPTOR_INPUTS_UNKNOWN_NAME = "There was an error obtaining the inputs for descriptor '%s' of operation (name cannot be determined) in transformation '%s.'"; private static final String ERROR_FINDING_NAME = "There was an error obtaining an operation name in transformation '%s.'"; protected static final String ERROR_FINDING_PATH = "There was an error obtaining an operation path in transformation '%s.'"; private static final String ERROR_FINDING_TRANSFORMATION_ID = "There was an error obtaining a transformation path.'"; private static final String ERROR_REMOVING_OPERATION_INPUT = "There was an error removing terms for operation '%s' in transformation '%s'"; private static final String ERROR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME = "There was an error removing terms for operation (name cannot be determined) in transformation '%s'"; protected static final String HAS_NO_TERMS = "'%s' operation in transformation '%s' has no terms"; protected static final String INVALID_TERM_COUNT = "The operation '%s' in transformation '%s' has an invalid term count of '%s.'"; protected static final String INVALID_TERM_TYPE = "The operation '%s' in transformation '%s' has a term with an invalid type or has a term that is null"; protected static final String MESSAGE = "%s"; protected static final String MUST_HAVE_ONE_TERM = "'%s' operation in transformation '%s' requires one and only one term"; protected static final String OPERATION_HAS_ERRORS = "The operation '%s' in transformation '%s' has errors and a result cannot being calculated"; private static final String OPERATION_RESULT_NOT_MODIFIABLE = "The '%s' operation's result in transformation '%s' is not directly modifiable"; private static final String OPERATION_RESULT_NOT_MODIFIABLE_UNKNOWN_NAME = "The operation (name cannot be determined) result in transformation '%s' is not directly modifiable"; protected static final String OPERATION_VALIDATION_ERROR = "An exception occurred when validating the input of operation '%s' in transformation '%s'"; private static final String UNABLE_TO_FIND_FACTORY = "Could not find transformation factory for operation"; private final Logger logger; protected final ValidationProblems problems; private final ModelObject operation; private final Transformation transformation; /** * @param operation * the operation model object being wrapped by this domain object (cannot be <code>null</code>) * @param transformation * the transformation that owns this operation (cannot be <code>null</code>) * @throws ModelspaceException * if an error with the model object occurs * @throws ChrysalixException * if a non-model object error occurs */ protected AbstractOperation( final ModelObject operation, final Transformation transformation ) throws ModelspaceException, ChrysalixException { super( operation.name(), operation ); this.operation = operation; this.transformation = transformation; this.problems = TransformationFactory.createValidationProblems(); this.logger = Logger.logger( getClass() ); } /** * {@inheritDoc} * * @see org.chrysalix.transformation.Operation#addInput(java.lang.String, java.lang.Object[]) */ @Override public void addInput( final String descriptorId, final Object... valuesBeingAdded ) throws ChrysalixException { CheckArg.notEmpty( descriptorId, "descriptorId" ); CheckArg.isNotEmpty( valuesBeingAdded, "valuesBeingAdded" ); if ( !isValidInputDescriptorId( descriptorId ) ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException e ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( e ); throw pe; } } for ( final Object value : valuesBeingAdded ) { if ( value == null ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException e ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( e ); throw pe; } } try { // add one at a time final ModelObject[] added = this.operation.addChildOfType( ChrysalixLexicon.Input.NODE_TYPE, descriptorId ); final ModelObject input = added[ 0 ]; if ( value instanceof ModelProperty ) { input.setProperty( ChrysalixLexicon.Input.PATH, true ); input.setProperty( ChrysalixLexicon.Input.VALUE, ( ( ModelProperty ) value ).absolutePath() ); } else if ( value instanceof Operation ) { input.setProperty( ChrysalixLexicon.Input.PATH, true ); input.setProperty( ChrysalixLexicon.Input.VALUE, ( ( Operation< ? > ) value ).absolutePath() ); } else if ( value instanceof Value< ? > ) { input.setProperty( ChrysalixLexicon.Input.PATH, false ); input.setProperty( ChrysalixLexicon.Input.VALUE, ( ( Value< ? > ) value ).get() ); } else { input.setProperty( ChrysalixLexicon.Input.PATH, false ); input.setProperty( ChrysalixLexicon.Input.VALUE, value ); } } catch ( final Exception e ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( error ); throw pe; } } } } /** * @return the operation's output value (can be <code>null</code>) * @throws ChrysalixException * if an error occurs */ protected abstract T calculate() throws ChrysalixException; @SuppressWarnings( "unchecked" ) protected final OperationDescriptor< T > descriptor() throws ChrysalixException { return ( OperationDescriptor< T > ) factory().descriptor( descriptorId() ); } protected ValueDescriptor< ? > descriptor( final String id ) throws ChrysalixException { for ( final ValueDescriptor< ? > descriptor : descriptor().inputDescriptors() ) { if ( descriptor.name().equals( id ) ) { return descriptor; } } return null; } private TransformationFactory factory() throws ChrysalixException { String opName = null; try { opName = operation.name(); return TransformationFactory.REGISTRY.get( this.operation.model().modelspace() ); } catch ( final ModelspaceException e ) { throw new ChrysalixException( e, ChrysalixI18n.localize( UNABLE_TO_FIND_FACTORY, opName ) ); } } /** * {@inheritDoc} * <p> * <strong>Do not call if there are validation errors as this will throw an exception.</strong> * * @see org.chrysalix.operation.ValueImpl#get() */ @Override public final T get() throws ChrysalixException { if ( problems().isError() ) { try { throw new ChrysalixException( ChrysalixI18n.localize( AbstractOperation.OPERATION_HAS_ERRORS, absolutePath(), transformationId() ) ); } catch ( final ModelspaceException e ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( AbstractOperation.ERROR_FINDING_PATH, transformationId() ) ); pe.addSuppressed( e ); throw pe; } } return calculate(); } /** * {@inheritDoc} * * @see org.chrysalix.transformation.Operation#inputs() */ @Override public Value< ? >[] inputs() throws ChrysalixException { try { final ModelObject[] kids = this.operation.childrenOfType( ChrysalixLexicon.Input.NODE_TYPE ); final Value< ? >[] inputs = new Value< ? >[ kids.length ]; for ( int i = 0; i < kids.length; ++i ) { final String descriptorId = kids[ i ].name(); inputs[ i ] = new ValueImpl< Object >( descriptorId, kids[ i ] ); } return inputs; } catch ( final Exception e ) { try { throw new ChrysalixException( e, ChrysalixI18n.localize( ERROR_FINDING_ALL_INPUTS, name(), this.transformation.id() ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_FINDING_ALL_INPUTS_UNKNOWN_NAME, this.transformation.id() ) ); pe.addSuppressed( error ); throw pe; } } } /** * @param descriptorId * the identifier of the {@link ValueDescriptor descriptor} whose inputs are being requested (cannot be <code>null</code> * or invalid) * @return the values (never <code>null</code> but can be empty) * @throws ChrysalixException * if an error occurs * @see #isValidInputDescriptorId(String) * @throws IllegalArgumentException * if the descriptor is not found */ protected List< Value< ? >> inputs( final String descriptorId ) throws ChrysalixException { CheckArg.notNull( descriptor( descriptorId ), "descriptorId" ); try { final ModelObject[] kids = this.operation.children( descriptorId ); if ( kids == null ) { return Collections.emptyList(); } final List< Value< ? >> inputs = new ArrayList<>(); for ( final ModelObject kid : kids ) { final Value< ? > input = new ValueImpl< Object >( descriptorId, kid ); inputs.add( input ); } return inputs; } catch ( final Exception e ) { try { throw new ChrysalixException( e, ChrysalixI18n.localize( ERROR_FINDING_DESCRIPTOR_INPUTS, descriptorId, name(), this.transformation.id() ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_FINDING_DESCRIPTOR_INPUTS_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( error ); throw pe; } } } protected boolean isValidInputDescriptorId( final String id ) { try { return ( descriptor( id ) != null ); } catch ( final ChrysalixException e ) { return false; } } /** * {@inheritDoc} * * @see java.lang.Iterable#iterator() */ @Override public Iterator< Value< ? >> iterator() { try { return Arrays.asList( inputs() ).iterator(); } catch ( final ChrysalixException e ) { throw new RuntimeException( e ); } } /** * {@inheritDoc} * * @see org.chrysalix.transformation.Value#name() */ @Override public String name() throws ModelspaceException { try { return descriptorId(); } catch ( final ChrysalixException e ) { try { throw new ModelspaceException( e, ChrysalixI18n.localize( ERROR_FINDING_NAME, this.transformation.id() ) ); } catch ( final ChrysalixException error ) { final ModelspaceException me = new ModelspaceException( e, ERROR_FINDING_TRANSFORMATION_ID ); me.addSuppressed( error ); throw me; } } } /** * {@inheritDoc} * * @see org.chrysalix.transformation.Operation#problems() */ @SuppressWarnings( "unused" ) @Override public ValidationProblems problems() throws ChrysalixException { return this.problems; } /** * {@inheritDoc} * * @see org.chrysalix.transformation.Operation#removeInput(java.lang.String, java.lang.Object[]) */ @Override public void removeInput( final String descriptorId, final Object... valuesBeingRemoved ) throws ChrysalixException { CheckArg.notEmpty( descriptorId, "descriptorId" ); CheckArg.isNotEmpty( valuesBeingRemoved, "valuesBeingRemoved" ); if ( !isValidInputDescriptorId( descriptorId ) ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException e ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( e ); throw pe; } } final List< Value< ? >> inputs = inputs( descriptorId ); // error if there are no inputs to delete if ( inputs.isEmpty() ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException e ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( e ); throw pe; } } for ( final Object valueToDelete : valuesBeingRemoved ) { if ( valueToDelete == null ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_OPERATION_INPUT, name(), this.transformation.id() ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, this.transformation.id() ) ); pe.addSuppressed( error ); throw pe; } } boolean removed = false; // find input for ( final Value< ? > input : inputs ) { try { final Object value = input.get(); if ( valueToDelete.equals( input ) || valueToDelete.equals( input.get() ) ) { this.operation.removeChild( input.name() ); removed = true; } else { if ( ( valueToDelete instanceof ModelProperty ) || ( valueToDelete instanceof Operation ) ) { final String path = ( ( ModelElement ) valueToDelete ).absolutePath(); if ( path.equals( value ) ) { this.operation.removeChild( input.name() ); removed = true; } } } } catch ( final ModelspaceException e ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_OPERATION_INPUT, name(), this.transformation.id() ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, this.transformation.id() ) ); pe.addSuppressed( error ); throw pe; } } } if ( !removed ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, this.transformation.id() ) ); pe.addSuppressed( error ); throw pe; } } } } /** * {@inheritDoc} * <p> * This throws {@link UnsupportedOperationException}. * * @see org.chrysalix.operation.ValueImpl#set(java.lang.Object) */ @Override public void set( final Object proposedValue ) throws ChrysalixException { try { throw new UnsupportedOperationException( ChrysalixI18n.localize( OPERATION_RESULT_NOT_MODIFIABLE, name(), this.transformation.id() ) ); } catch ( final ModelspaceException e ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( OPERATION_RESULT_NOT_MODIFIABLE_UNKNOWN_NAME, transformationId(), descriptorId() ) ); pe.addSuppressed( e ); throw pe; } } /** * {@inheritDoc} * * @see org.chrysalix.transformation.Operation#setInput(java.lang.String, java.lang.Object[]) */ @Override public void setInput( final String descriptorId, final Object... valuesBeingSet ) throws ChrysalixException { if ( !isValidInputDescriptorId( descriptorId ) ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException e ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( e ); throw pe; } } // remove all previous values try { for ( final ModelObject input : this.operation.childrenOfType( ChrysalixLexicon.Input.NODE_TYPE ) ) { this.operation.removeChild( input.name() ); this.logger.debug( "Removed input '%s' for descriptor '%s'", input.name(), descriptorId ); } } catch ( final ModelspaceException e ) { throw new ChrysalixException( e, ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); } for ( final Object value : valuesBeingSet ) { if ( value == null ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( error ); throw pe; } } try { final ModelObject[] added = this.operation.addChildOfType( ChrysalixLexicon.Input.NODE_TYPE, descriptorId ); final ModelObject input = added[ 0 ]; if ( value instanceof ModelProperty ) { input.setProperty( ChrysalixLexicon.Input.PATH, true ); input.setProperty( ChrysalixLexicon.Input.VALUE, ( ( ModelProperty ) value ).absolutePath() ); } else if ( value instanceof Operation ) { input.setProperty( ChrysalixLexicon.Input.PATH, true ); input.setProperty( ChrysalixLexicon.Input.VALUE, ( ( Operation< ? > ) value ).absolutePath() ); } else if ( value instanceof Value< ? > ) { input.setProperty( ChrysalixLexicon.Input.PATH, false ); input.setProperty( ChrysalixLexicon.Input.VALUE, ( ( Value< ? > ) value ).get() ); } else { input.setProperty( ChrysalixLexicon.Input.PATH, false ); input.setProperty( ChrysalixLexicon.Input.VALUE, value ); } } catch ( final Exception e ) { try { throw new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT, name(), transformationId(), descriptorId ) ); } catch ( final ModelspaceException error ) { final ChrysalixException pe = new ChrysalixException( ChrysalixI18n.localize( ERROR_ADDING_OR_REMOVING_OPERATION_INPUT_UNKNOWN_NAME, transformationId(), descriptorId ) ); pe.addSuppressed( error ); throw pe; } } } } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { try { final StringBuilder builder = new StringBuilder( descriptor().name() ); builder.append( '(' ); boolean firstTime = true; for ( final Value< ? > term : inputs() ) { if ( firstTime ) { firstTime = false; } else { builder.append( ", " ); } builder.append( factory().descriptor( term.descriptorId() ).signature() ); } builder.append( ')' ); return builder.toString(); } catch ( final ChrysalixException e ) { this.logger.debug( e, "Error calculating toString of operation" ); return super.toString(); } } /** * {@inheritDoc} * * @see org.chrysalix.transformation.Operation#transformation() */ @Override public Transformation transformation() { return this.transformation; } protected String transformationId() throws ChrysalixException { return this.transformation.id(); } }