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.ArrayList;
011import java.util.Collection;
012import java.util.HashSet;
013import java.util.Iterator;
014import java.util.List;
015import java.util.Set;
016
017import javax.jmi.model.ModelElement;
018import javax.jmi.model.MofClass;
019import javax.jmi.reflect.JmiException;
020import javax.jmi.reflect.RefAssociation;
021import javax.jmi.reflect.RefAssociationLink;
022import javax.jmi.reflect.RefBaseObject;
023import javax.jmi.reflect.RefClass;
024import javax.jmi.reflect.RefEnum;
025import javax.jmi.reflect.RefFeatured;
026import javax.jmi.reflect.RefPackage;
027import javax.jmi.reflect.RefStruct;
028
029import net.mdatools.modelant.core.api.Function;
030
031/**
032 * Print the actual attributes and values of model elements using only the
033 * reflection interfaces of JMI - it is independent of the actual metamodel being processed. Its
034 * toString() method handles the proper result formatting.
035 * <pre>
036 * Usage:
037 *
038 * System.err.println( "model element ="+ new ModelElementPrinter().execute( modelElement ) );
039 * </pre>
040 * @author Rusi Popov (popovr@mdatools.net)
041 */
042public class PrintModelElement implements Function<Object, String>{
043  /**
044   * The wrapped model element
045   */
046  private final String prefix;
047
048  public PrintModelElement() {
049    this("");
050  }
051
052  /**
053   * The only constructor of ModelElementPrinter is to wrap up a non-null model element to print.
054   * @param prefix to all lines
055   */
056  public PrintModelElement(String prefix) {
057    if ( prefix != null ) {
058      this.prefix = prefix;
059    } else {
060      this.prefix = "";
061    }
062  }
063
064  /**
065   * Prints the elements nested according to the prefix
066   * @param object the model element to print
067   * @return the string representation of this, with the prefix added to each line
068   */
069  public String execute(Object object) throws RuntimeException, IllegalArgumentException {
070    StringBuilder result = new StringBuilder( 2048 );
071
072    toString( object, result, prefix, new HashSet() );
073
074    return result.toString();
075  }
076
077  /**
078   * @param prefix
079   * @see java.lang.Object#toString()
080   */
081  private void toString(Object wrapped, StringBuilder result, String prefix, Set visited) {
082    if ( !visited.contains( wrapped ) ) {
083      // Note that only the model elements are checked if they were visited
084
085      if ( wrapped instanceof JmiException ) {
086        append( result, prefix, (JmiException) wrapped, visited );
087
088      } else if ( wrapped instanceof RefClass ) {
089        visited.add( wrapped );
090        append( result, (RefClass) wrapped, visited );
091
092      } else if ( wrapped instanceof RefPackage ) {
093        visited.add( wrapped );
094        append( result, (RefPackage) wrapped, visited );
095
096      } else if ( wrapped instanceof RefAssociation ) {
097        visited.add( wrapped );
098        append( result, prefix, (RefAssociation) wrapped, visited );
099
100      } else if ( wrapped instanceof RefFeatured ) {
101        visited.add( wrapped );
102        append( result, prefix, (RefFeatured) wrapped, visited );
103
104      } else if ( wrapped instanceof RefStruct ) {
105        visited.add( wrapped );
106        append( result, prefix, (RefStruct) wrapped, visited );
107
108      } else if ( wrapped instanceof RefAssociationLink ) {
109        visited.add( wrapped );
110        append( result, prefix, (RefAssociationLink) wrapped, visited );
111
112      } else if ( wrapped instanceof RefEnum ) {
113        visited.add( wrapped );
114        append( result, (RefEnum) wrapped, visited );
115
116      } else if ( wrapped instanceof Collection ) {
117        append( result, prefix, (Collection) wrapped, visited );
118
119//      } else if ( wrapped != null ) {
120//        result.append( wrapped.getClass() ).append( "  " );
121//        result.append( wrapped );
122//
123      } else {
124        result.append( wrapped );
125      }
126    } else {
127      result.append("<visited>");
128    }
129  }
130
131  /**
132   * @param result
133   * @param exception
134   */
135  private void append(StringBuilder result, String prefix, JmiException exception, Set visited) {
136    result.append( exception.getClass() );
137    result.append( " {\n" );
138    result.append( prefix );
139    result.append( "  message = " );
140    result.append( exception.getMessage() );
141    result.append( "\n" );
142
143    result.append( prefix );
144    result.append( "  elementInError = " );
145    toString(exception.getElementInError(), result, prefix+"  ", visited);
146    result.append( "\n" );
147
148    result.append( prefix );
149    result.append( "  objectInError = " );
150    toString( exception.getObjectInError(), result, prefix+"  ", visited );
151    result.append( "\n" );
152
153    result.append( prefix );
154    result.append( "}" );
155  }
156
157
158  /**
159   * This method requires an M1 object object and a StringBuilder where to describe the object.
160   *
161   * @param result is the buffer where to add the obect's description
162   * @param object is the M1 object to investigate
163   */
164  private void append(StringBuilder result, String prefix, RefFeatured object, Set visited) {
165    MofClass metaObject;
166    List allMetaObjects;
167    ModelElement contents;
168    Object value;
169    Iterator contentsIterator;
170    Iterator metaObjectsIterator;
171    String prefixNested;
172
173    metaObject = (MofClass) object.refMetaObject();
174    prefixNested = prefix+"  ";
175
176    result.append( metaObject.getName() )
177          .append( " {\n" );
178
179    // find the description of the class of this object and their superclasses
180    // Each element of allMetaObjects is an M2 object of MOF classes that describes
181    // the object's class or its superclass
182
183    allMetaObjects = new ArrayList();
184
185    allMetaObjects.add( metaObject );
186    allMetaObjects.addAll( metaObject.allSupertypes() );
187
188    // explore all features (attributes) of all (super)classes print their values
189    metaObjectsIterator = allMetaObjects.iterator();
190    while ( metaObjectsIterator.hasNext() ) {
191      metaObject = (MofClass) metaObjectsIterator.next();
192
193      contentsIterator = metaObject.getContents().iterator();
194      while ( contentsIterator.hasNext() ) {
195        contents = (ModelElement) contentsIterator.next();
196
197        if ( contents instanceof javax.jmi.model.Attribute ) { // Handling Structs causes infinite loop, so StructuralFeatures are not considered
198          // print the attribute's name
199          result.append(prefixNested).append( contents.getName() );
200          try {
201            value = object.refGetValue( contents );
202
203            // print the defaultAttribute value
204            result.append( "=" );
205            toString( value, result, prefixNested, visited );
206
207          } catch (Exception e) { // this should not happen
208            result.append( " exception caused: " + e.getClass().getName() +" : "+ e.getMessage() );
209          }
210          result.append( ",\n" );
211        }
212      }
213    }
214    result.append( prefix )
215          .append( "}" );
216  }
217
218
219  /**
220   * This method requires an M1 object object and a StringBuilder where to describe the object.
221   *
222   * @param result is the buffer where to add the obect's description
223   * @param object is the M1 object to investigate
224   */
225  private void append(StringBuilder result, String prefix, RefStruct object, Set visited) {
226    String name;
227    Object value;
228    Iterator namesIterator;
229    List typeNames;
230    String prefixNested;
231
232    prefixNested = prefix+"  ";
233
234    typeNames = object.refTypeName(); // size > 0
235    result.append( typeNames.get( typeNames.size()-1 ) )
236          .append( " {\n" );
237
238    namesIterator = object.refFieldNames().iterator();
239    while ( namesIterator.hasNext() ) {
240      name = (String) namesIterator.next();
241
242      // print the attribute's name
243      result.append(prefixNested).append( name );
244      try {
245        // get the value of the defaultAttribute
246        value = object.refGetValue( name );
247
248        // print the defaultAttribute value
249        result.append( "=" );
250        toString( value, result, prefixNested, visited );
251
252      } catch (Exception e) { // this should not happen
253        result.append( " exception caused: " + e.getClass().getName() + " : " + e.getMessage() );
254      }
255      result.append( ",\n" );
256    }
257    result.append( prefix )
258          .append( "}" );
259  }
260
261
262  /**
263   * This method requires an M1 object object and a StringBuilder where to describe the object.
264   *
265   * @param result is the buffer where to add the obect's description
266   * @param object is the M1 object to investigate
267   */
268  private void append(StringBuilder result, RefPackage object, Set visited) {
269    result.append( "RefPackage ")
270          .append( object.refMetaObject().refGetValue( "name" ) );
271  }
272
273  /**
274   * @param result is the buffer where to add the obect's description
275   * @param object non-null enum to print
276   */
277  private void append(StringBuilder result, RefEnum object, Set visited) {
278    result.append( "RefEnum ")
279          .append( object.refTypeName() )
280          .append( " " )
281          .append( object.toString() );
282  }
283
284
285  /**
286   * This method requires an M1 object object and a StringBuilder where to describe the object.
287   *
288   * @param result is the buffer where to add the obect's description
289   * @param object is the M1 object to investigate
290   */
291  private void append(StringBuilder result, RefClass object, Set visited) {
292    result.append( "RefClass ")
293          .append( object.refMetaObject().refGetValue( "name" ) );
294  }
295
296
297  /**
298   * This method requires an M1 object object and a StringBuilder where to describe the object.
299   *
300   * @param result is the buffer where to add the obect's description
301   * @param object is the M1 object/association to investigate
302   */
303  private void append(StringBuilder result, String prefix, RefAssociation object, Set visited) {
304    Iterator<RefAssociationLink> linksIterator;
305    RefAssociationLink link;
306
307    result.append( "RefAssociation ")
308          .append( object.refMetaObject().refGetValue( "name" ) );
309    result.append( " {\n");
310
311//    linksIterator = object.refAllLinks().iterator();
312//    while ( linksIterator.hasNext() ) {
313//      link = linksIterator.next();
314//
315//      result.append( prefix );
316//      toString( link, result, prefix+"  ", visited );
317//      result.append( "\n");
318//    }
319
320    result.append( prefix )
321          .append( "}");
322  }
323
324
325  /**
326   * This method requires an M1 object object and a StringBuilder where to describe the object.
327   *
328   * @param result is the buffer where to add the obect's description
329   * @param object is the M1 object to investigate
330   */
331  private void append(StringBuilder result, String prefix, RefAssociationLink object, Set visited) {
332    result.append( "RefAssociationLink ")
333          .append( "{\n")
334          .append( prefix )
335          .append( "  firstEnd=");
336           toString( object.refFirstEnd(), result, prefix+"  ", visited);
337           result.append("\n")
338          .append( prefix )
339          .append( "  secondEnd=");
340           toString( object.refSecondEnd(), result, prefix+"  ", visited);
341           result.append("\n")
342          .append( prefix )
343          .append("}");
344  }
345
346  /**
347   * This method requires an M1 object object and a StringBuilder where to describe the object.
348   *
349   * @param result is the buffer where to add the obect's description
350   * @param collection is the M1 object to investigate
351   */
352  private void append(StringBuilder result, String prefix, Collection collection, Set visited) {
353    Object value;
354    Iterator valuesIterator;
355    boolean onSeparateLine;
356
357    result.append( "{" );
358
359    onSeparateLine = false;
360
361    valuesIterator = collection.iterator();
362    while ( valuesIterator.hasNext() ) {
363      value = valuesIterator.next();
364
365      onSeparateLine |= value instanceof RefBaseObject
366                        || value instanceof RefStruct
367                        || value instanceof RefEnum
368                        || value instanceof RefAssociation
369                        || value instanceof RefAssociationLink;
370
371      if ( onSeparateLine ) { // print the model elements with the prefix
372        result.append( "\n" );
373        result.append( prefix+"  " );
374      }
375      toString( value, result, prefix+"  ", visited );
376      if ( valuesIterator.hasNext() ) {
377        result.append(",");
378      }
379    }
380    if ( onSeparateLine ) { // model elements were printed
381      result.append("\n")
382            .append( prefix )
383            .append( "}" );
384
385    } else { // a regular list
386      result.append( "}" );
387    }
388  }
389
390  /**
391   * @param wrapped
392   * @return a non-null object with its toString() printing the wrapped element
393   */
394  public Object toPrint(Object wrapped ) {
395    return new ToString(wrapped);
396  }
397
398  /**
399   * Use the parent printer to form the toString() contents
400   */
401  private class ToString {
402    private final Object wrapped;
403
404    public ToString(Object wrapped) {
405      this.wrapped = wrapped;
406    }
407
408    /**
409     * @see java.lang.Object#toString()
410     */
411    public String toString() {
412      return execute(wrapped);
413    }
414  }
415}