001/*
002 * Copyright c 2018 Rusi Popov, MDA Tools.net All rights reserved.
003 *
004 * This program and the accompanying materials are made available under the terms of the
005 * Eclipse Public License v2.0 which accompanies this distribution, and is available at
006 * http://www.eclipse.org/legal/epl-v20.html
007 */
008package net.mdatools.modelant.core.operation.element;
009
010import java.util.Collection;
011import java.util.Iterator;
012import java.util.List;
013import java.util.logging.Level;
014import java.util.logging.Logger;
015
016import javax.jmi.model.GeneralizableElement;
017import javax.jmi.reflect.RefAssociation;
018import javax.jmi.reflect.RefAssociationLink;
019import javax.jmi.reflect.RefBaseObject;
020import javax.jmi.reflect.RefEnum;
021import javax.jmi.reflect.RefFeatured;
022import javax.jmi.reflect.RefObject;
023import javax.jmi.reflect.RefStruct;
024
025import net.mdatools.modelant.core.api.Function;
026import net.mdatools.modelant.core.api.match.MatchingCriteria;
027import net.mdatools.modelant.core.util.Navigator;
028
029/**
030 * Print model elements showing only their "important" attributes
031 * and associations closer to the human language
032 * @author Rusi Popov (popovr@mdatools.net)
033 */
034public class PrintElementRestricted implements Function<Object, String> {
035
036  /**
037   * This is a common logger
038   */
039  private static Logger LOGGER = Logger.getLogger( PrintElementRestricted.class.getPackage().getName() );
040
041  /**
042   * What is important to show in order to identify the object being printed
043   */
044  private final MatchingCriteria criteria;
045
046  /**
047   * A common prefix of every line of the string presentation of the printed object
048   */
049  private final String prefix;
050
051  /**
052   * @param prefix
053   * @param criteria not null
054   */
055  public PrintElementRestricted(String prefix, MatchingCriteria criteria) {
056    this.criteria = criteria;
057
058    if ( prefix == null ) {
059      this.prefix = "";
060    } else {
061      this.prefix = prefix;
062    }
063  }
064
065  /**
066   * @param argument The object to start printing from
067   * @return non-null string representation of the
068   */
069  public String execute(Object argument) throws RuntimeException, IllegalArgumentException {
070    StringBuilder result = new StringBuilder(2048);
071
072    append( result, argument, prefix );
073    return result.toString();
074  }
075
076  private void append(StringBuilder result, Object object, String prefix) {
077    if ( object instanceof RefFeatured ) { // note: RefStruct is not checked
078      append( result, (RefBaseObject) object, prefix);
079
080    } else if ( object instanceof Collection ) {
081      append( result, (Collection) object, prefix);
082    } else {
083      result.append( object );
084    }
085  }
086
087  /**
088   * This method dumps the object into the output string buffer indenting the nested elements
089   * with the prefix provided.
090   * @param result
091   * @param forObject
092   * @param prefix
093   */
094  private void append(StringBuilder result, RefBaseObject forObject, String prefix) {
095    Iterator<String> namesIterator;
096    String name;
097    List values;
098    List associated;
099    String nestedPrefix;
100    List<String> asociationNames;
101    List<String> attributeNames;
102    String nested2Prefix;
103
104    result.append( ((GeneralizableElement) forObject.refMetaObject()).getName() );
105
106    if ( forObject instanceof RefObject ) {
107      attributeNames = criteria.getAttributes( (RefObject) forObject );
108      asociationNames = criteria.getAssociations( (RefObject) forObject );
109
110      if ( attributeNames.size() + asociationNames.size() == 0) {
111        // print nothing
112      } else {
113        nestedPrefix = prefix+"  ";
114        nested2Prefix = nestedPrefix+"  ";
115
116        if ( attributeNames.size() + asociationNames.size() == 1
117             || attributeNames.size() == 1
118                && asociationNames.size() == 1) { // there is chance for compact printing
119
120          if ( attributeNames.size() == 1 ) { // a single attribute - print its value only
121            values = Navigator.collectValues( (RefObject) forObject,
122                                              attributeNames.get( 0 ),
123                                              LOGGER.isLoggable( Level.FINE ));
124
125            if ( values.size() > 0 ) { // a non-empty list of non-empty values
126              append( result, values, nestedPrefix );
127            }
128          }
129
130          if ( asociationNames.size() == 1 ) { // a single association
131            // use the value to select the printing format
132
133            name = asociationNames.get( 0 );
134
135            associated = Navigator.collectValues( (RefObject) forObject, name, false );
136
137            if ( associated.size() == 1 ) { // a single value - use compact print
138              result.append( "/" );
139              append(result, (RefObject) associated.get( 0 ), prefix );
140
141            } else if ( associated.size() > 1 ) { // multiple values, use regular print
142              appendAssociated( result, name, associated, nestedPrefix, nested2Prefix );
143            }
144          }
145        } else { // there are many attributes and/or associations, use regular (wide) printing
146
147          // print the non-empty attributes
148          namesIterator = attributeNames.iterator();
149          while ( namesIterator.hasNext() ) {
150            name = namesIterator.next();
151
152            try {
153              values = Navigator.collectValues( (RefObject) forObject, name, false );
154
155              if ( values.size() > 0 ) { // a non-empty list of non-empty values
156                result.append("\n")
157                      .append( nestedPrefix )
158                      .append( name )
159                      .append( "=");
160                append( result, values, prefix );
161              }
162            } catch (Exception ex) {
163              LOGGER.log( Level.FINE,
164                          "Model element: {0} does not support attribute: {1}",
165                          new Object[]{new PrintModelElement(nestedPrefix).execute( forObject ), name});
166            }
167          }
168
169          // print the non-empty associations
170          namesIterator = asociationNames.iterator();
171          while ( namesIterator.hasNext() ) {
172            name = namesIterator.next();
173
174            try {
175              associated = Navigator.collectValues( (RefObject) forObject, name, false );
176
177              appendAssociated( result, name, associated, nestedPrefix, nested2Prefix );
178            } catch (Exception ex) {
179              LOGGER.log( Level.INFO,
180                          "Accessing associated {1} on model element {0} caused {2}",
181                          new Object[]{new PrintModelElement(nestedPrefix).execute( forObject ), name, ex.getMessage()});
182            }
183          }
184        }
185      }
186    }
187  }
188
189  /**
190   * @param result
191   * @param name
192   * @param associated
193   * @param nestedPrefix
194   * @param nested2Prefix
195   */
196  private void appendAssociated(StringBuilder result, String name, List associated, String nestedPrefix,
197                                String nested2Prefix) {
198    Object node;
199    Iterator associatedIterator;
200    if ( associated.size() > 0 ) {
201      result.append( "\n" )
202            .append( nestedPrefix )
203            .append( name )
204            .append( "=");
205
206      // result.append("{");
207      if ( associated.size() == 1 ) {
208        append(result, (RefObject) associated.get( 0 ), nestedPrefix );
209      } else {
210        associatedIterator = associated.iterator();
211        while (associatedIterator.hasNext()) {
212          node = associatedIterator.next();
213
214          result.append("\n").append( nested2Prefix );
215          append(result, (RefObject) node, nested2Prefix );
216        }
217      }
218    }
219  }
220
221  /**
222   * This method requires an M1 object object and a StringBuilder where to describe the object.
223   *
224   * @param result is the buffer where to add the obect's description
225   * @param collection is the M1 object to investigate
226   */
227  private void append(StringBuilder result, Collection collection, String prefix) {
228    Object value;
229    Iterator valuesIterator;
230    boolean onSeparateLine;
231    String nested;
232
233    nested = prefix+"  ";
234    result.append( " " );
235
236    valuesIterator = collection.iterator();
237    if ( !valuesIterator.hasNext() ) { // an empty list
238      result.append( "{}" );
239
240    } else {
241      value = valuesIterator.next();
242
243      onSeparateLine = shouldPrintOnSeparateLine( value );
244
245      if ( !valuesIterator.hasNext() ) { // a single value list - print it without {}
246        appendSingleValue( result, value, onSeparateLine, nested );
247
248      } else { // a list of size > 1
249        result.append( "{" );
250
251        appendSingleValue( result, value, onSeparateLine, nested );
252        while ( valuesIterator.hasNext() ) {
253          value = valuesIterator.next();
254
255          result.append(", ");
256          onSeparateLine |= shouldPrintOnSeparateLine( value );
257
258          appendSingleValue( result, value, onSeparateLine, nested );
259        }
260        if ( onSeparateLine ) { // model elements were printed
261          result.append("\n")
262                .append( prefix )
263                .append( " ");
264        }
265        result.append( "}" );
266      }
267    }
268  }
269
270  /**
271   * @return true when the value is a model element, so it should be printed on separate line(s)
272   */
273  private boolean shouldPrintOnSeparateLine(Object value) {
274    return value instanceof RefBaseObject
275           || value instanceof RefStruct
276           || value instanceof RefEnum
277           || value instanceof RefAssociation
278           || value instanceof RefAssociationLink;
279  }
280
281  /**
282   * @param result
283   * @param value
284   * @param isModelElement is true when value != null && value is a model element
285   * @param nested
286   */
287  private void appendSingleValue(StringBuilder result, Object value, boolean isModelElement, String nested) {
288    if ( isModelElement ) { // print the model elements with the prefix
289      result.append( "\n" )
290            .append( nested );
291      append( result, value, nested);
292
293    } else { // a "primitive" value
294//      result.append( "\"" );
295      append( result, value, nested);
296//      result.append( "\"" );
297    }
298  }
299}