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.Arrays;
011import java.util.List;
012
013import javax.jmi.reflect.InvalidNameException;
014import javax.jmi.reflect.RefFeatured;
015
016import net.mdatools.modelant.core.api.Function;
017import net.mdatools.modelant.core.util.Navigator;
018
019/**
020 * Starting from an object navigate a path of associations do some processing at the end of the path
021 * <pre>
022 * Format:
023 *    empty
024 * |  asssociationToOneName{.asssociationToOneName}
025 * | [asssociationToOneName{.asssociationToOneName}.](associationToMany | attributeName)
026 *
027 *  associationToOneName = name
028 *                         | METAOBJECT
029 *                         | METACLASSNAME
030 *                         | IPACKAGE
031 *                         | OPACKAGE
032 *
033 *  associationToManyName = name
034 *
035 *  attributeName = name
036 *                  | MOFID
037 * where:
038 *   name is the name of an association in *-to-one multiplicity. In the case when there is no attribute name,
039 *     the last used name can be an association *-to-many
040 *   MOFID refers the MOF ID of the object to process
041 *   METAOBJECT retrieves the meta-object of the object to process. This allows reflective navigation.
042 *   METACLASSNAME retrieves the qualified (in the meta-model) name of the meta-class for the processed object.
043 *   IPACKAGE retrieves the immediate package in the meta-model the processed object is in
044 *   OPACKAGE retrieves the outer-most package (the extent) where object processed is in
045 *
046 * </pre>
047 * @author Rusi Popov (popovr@mdatools.net)
048 */
049public abstract class NavigateObjectPath<R> implements Function<RefFeatured, R> {
050
051  private static final PrintModelElement PRINT_MODEL_ELEMENT = new PrintModelElement();
052
053
054  private String[] parsedPath;
055
056  /**
057   * @param path a non-null path following *-to-one associations and ending optionally with attribute or *-to-many association
058   */
059  protected NavigateObjectPath(String path) {
060    assert path != null : "Expected a non-null path provided";
061    parsedPath = path.split( Navigator.OBJECT_PATH_SEPARATORS );
062  }
063
064
065  /**
066   * @param path a non-null path following *-to-one associations and ending optionally with attribute or *-to-many association
067   */
068  protected NavigateObjectPath(String[] path) {
069    assert path != null : "Expected a non-null path provided";
070    parsedPath = path;
071  }
072
073
074  /**
075   * @param start non-null object to start naviagtion from
076   * @return the object processed
077   * @throws RuntimeException
078   * @throws IllegalArgumentException
079   * @see net.mdatools.modelant.core.api.Function#execute(java.lang.Object)
080   */
081  public final R execute(final RefFeatured start) throws RuntimeException, IllegalArgumentException {
082    R result;
083    Object current;
084    Object associated;
085    String itemName;
086
087    if (start == null) {
088      throw new IllegalArgumentException("Expected non-null object to start navigating from down the path "
089                                         +Arrays.asList(parsedPath));
090    }
091
092    if (parsedPath.length == 0) { // empty path
093      result = processEmptyPath(start);
094
095    } else { // non-empty path to navigate
096      result = null;
097      current = start;
098
099      for (int i=0; i< parsedPath.length; i++) { // INVARIANT: current instanceof RefFEatured
100        itemName = parsedPath[i];
101
102        try {
103          associated = Navigator.getReflectiveValue( current, itemName );
104        } catch (Exception ex) {
105          throw new IllegalArgumentException("Starting from "+ PRINT_MODEL_ELEMENT.execute( start )
106                                          + " down the path: " + pathUpTo( i )
107                                          + " reached " + PRINT_MODEL_ELEMENT.execute( current )
108                                          + " reading '"+itemName
109                                          + "' on which caused ", ex);
110        }
111
112        if ( i < parsedPath.length-1 ) { // there are still associations to go through
113
114          if (!(associated instanceof RefFeatured)) {
115            throw new IllegalArgumentException(
116                                            "Starting from "+ PRINT_MODEL_ELEMENT.execute( start )
117                                            + " down the path: " + pathUpTo( i )
118                                            + " reached " + PRINT_MODEL_ELEMENT.execute( current )
119                                            + " for which accessing '"+itemName
120                                            + "' produced "+PRINT_MODEL_ELEMENT.execute( associated )
121                                            + " instead of the expected RefFEatured instance");
122          }
123          current = associated;
124
125        } else { // the last association/attribute to set
126
127          try {
128            if ( associated instanceof RefFeatured ) {
129              result = processLast(start, (RefFeatured) current, itemName, (RefFeatured) associated);
130            } else {
131              result = processLast(start, (RefFeatured) current, itemName, associated);
132            }
133          } catch (Exception ex) {
134            throw new IllegalArgumentException("Starting from "+ PRINT_MODEL_ELEMENT.execute( start )
135                                            + " down the path: " + pathUpTo( i )
136                                            + " reached " + PRINT_MODEL_ELEMENT.execute( current )
137                                            + " for which processing '"+itemName
138                                            + "' caused ", ex);
139          }
140        }
141      }
142    }
143    return result;
144  }
145
146  /**
147   * @param i >=0
148   * @return the path up to i-th element of paresedPath, excluding it
149   */
150  private List<String> pathUpTo(int i) {
151    return Arrays.asList(Arrays.copyOf( parsedPath,i));
152  }
153
154  /**
155   * Processing an EMPTY navigation path
156   * @param start not null object the navigation started from
157   * @return operation result
158   */
159  protected abstract R processEmptyPath(RefFeatured start);
160
161
162  /**
163   * Processing of the final association *-to-ONE in the path
164   * @param start not null object the navigation started from
165   * @param current not null object reached down the path EXCEPT the last name in that path
166   * @param itemName the non-null, non-empty last name in the path, which is an association *-to-ONE,
167   *        already validated as accessible through {@link Navigator#getReflectiveValue(Object, String)}
168   * @param associated the reached last associated to current model element in the association itemName
169   * @return operation result
170   */
171  protected abstract R processLast(RefFeatured start, RefFeatured current, String itemName, RefFeatured associated);
172
173  /**
174   * Processing of the final attribute or association *-to-MANY in the path
175   * @param start not null object the navigation started from
176   * @param value the attribute value or association *-to-MANY reached at the end of the path
177   * @param itemName the non-null, non-empty last name in the path, which is an association *-to-MANY or attribute name,
178   *        already validated as accessible through {@link Navigator#getReflectiveValue(Object, String)}
179   * @return operation result
180   */
181  protected abstract R processLast(RefFeatured start, RefFeatured current, String itemName, Object value);
182}