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}