Project

General

Profile

3556a_rev_11269.diff

Sergey Ivanovskiy, 05/11/2018 09:21 AM

Download (16.9 KB)

View differences:

src/com/goldencode/p2j/util/SourceNameMapper.java 2018-05-07 16:24:49 +0000
83 83
**                           - absolute name
84 84
**                           - PROPATH
85 85
**                           - direct match.
86
** 039 CA  20180504          Added support for folder aliasing: these are specified via the
87
**                           "path-aliases" per-server directory node, and provide a mapping of
88
**                           alias folder name to their 'source' counterpart.  Also, added support
89
**                           to identify the target source program for a RUN program.r statement.
86 90
*/
87 91

  
88 92
/*
......
142 146

  
143 147
import java.io.*;
144 148
import java.util.*;
149
import java.util.function.*;
150
import java.util.logging.*;
145 151
import java.net.*;
146 152
import org.w3c.dom.*;
147 153
import com.goldencode.util.*;
154
import com.goldencode.p2j.directory.*;
148 155
import com.goldencode.p2j.library.*;
149 156
import com.goldencode.p2j.security.*;
150 157

  
......
184 191
 */
185 192
public class SourceNameMapper
186 193
{
194
   /** logger */
195
   private static final Logger LOG = LogHelper.getLogger(SourceNameMapper.class.getName());
196

  
187 197
   /** Context-local data. */
188 198
   private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
189 199
   {
......
227 237
   /** Named events attribute. */
228 238
   private static final String PUBLISHED_EVENTS = "published-events";
229 239
   
240
   /** Map of aliases (r-code folders) to their source folders (conversion folders). */
241
   private static Map<String, String> pathAliases = null;
242
   
230 243
   /** Progress procedure file name to Java class name map table. */
231 244
   private static volatile Map<String, ExternalProgram> p2jMap = null;
232 245
   
246
   /**
247
    * Map of program names without extension to the programs which can be resolved with this name. 
248
    */
249
   private static volatile Map<String, List<ExternalProgram>> p2jNonExtMap = null;
250
   
233 251
   /** Progress Java class name to procedure file name map table. */
234 252
   private static Map<String, ExternalProgram> j2pMap = null;
235 253
   
......
851 869
      
852 870
      return extProg.search(iename, func);
853 871
   }
854
   
872

  
855 873
   /**
856 874
    * Convert a Progress file name into Java class name. The input name can be an absolute file
857 875
    * name, a file name relative to current location or a file name relative to some directory
......
891 909
    * "c:/path/to/project/stcases/uast/rundir/absolute/program.p"
892 910
    * "rundir/../../uast/rundir/relative/test11.p."
893 911
    * "../uast/rundir/relative/test5.p"
894
    *
912
    * <p>
913
    * If no program name is explicitly found with a <code>.r</code> extension, R-code program 
914
    * names are looked up using the {@link #p2jNonExtMap} (the program name without the extension).
915
    * <p> 
916
    * If {@link #pathAliases} is specified, and the program can be found (as it is or after 
917
    * removing the <code>.r</code> extension) in the propath, the lookup falls back to resolving
918
    * the aliased folders in the program name, via {@link #resolvePathAliases}.
919
    * 
920
    * 
895 921
    * @param   legacyProgName
896 922
    *          Progress file name.
897 923
    *
......
901 927
    */
902 928
   private static String[] convertName(String legacyProgName)
903 929
   {
930
      Function<String, ExternalProgram> lookupDirectName;
931
      Function<String, ExternalProgram> lookupCompiledRcode;
932
      Function<String, ExternalProgram> lookupAliasedDirectName;
933
      Function<String, ExternalProgram> lookupAliasedCompiledRcode;
934
      
935
      Function<String, Boolean> isRcode = 
936
         (pname -> pname.endsWith(".r") || (!caseSens && pname.endsWith(".R")));
937
      boolean legacyRcode = isRcode.apply(legacyProgName);
938
      boolean hasAliases = pathAliases != null;
939
      String msg = "Found first match %s for program %s, and additional %s";
940
      
941
      lookupDirectName = (pname -> p2jMap.get(pname));
942
      lookupCompiledRcode = !legacyRcode ? null : (pname ->
943
      {
944
         if (!isRcode.apply(pname))
945
         {
946
            return null;
947
         }
948
         
949
         pname = pname.substring(0, pname.length() - ".r".length());
950
         
951
         ExternalProgram extProg = null;
952
         
953
         // we could use a trie, but keeping a match of names without extension to the full 
954
         // program names (with extension) is easier
955
         List<ExternalProgram> progs = p2jNonExtMap.get(pname);
956
         
957
         if (progs != null && !progs.isEmpty())
958
         {
959
            extProg = progs.get(0);
960

  
961
            if (LOG.isLoggable(Level.WARNING))
962
            {
963
               for (int i = 1; i < progs.size(); i++)
964
               {
965
                  LOG.log(Level.WARNING, String.format(msg, 
966
                                                       extProg.pname, 
967
                                                       legacyProgName, 
968
                                                       progs.get(i).pname));
969
               }
970
            }
971
         }
972
         
973
         return extProg;
974
      });
975
      lookupAliasedDirectName = !hasAliases ? null : (pname ->
976
      {
977
         String[] aliases = resolvePathAliases(pname);
978
         
979
         ExternalProgram extProg = null;
980
         
981
         for (String aliasName : aliases)
982
         {
983
            ExternalProgram prog = lookupDirectName.apply(aliasName);
984
            
985
            if (prog != null)
986
            {
987
               if (extProg != null)
988
               {
989
                  if (LOG.isLoggable(Level.WARNING))
990
                  {
991
                     LOG.log(Level.WARNING, String.format(msg, 
992
                                                          extProg.pname, 
993
                                                          legacyProgName, 
994
                                                          prog.pname));
995
                  }
996
               }
997
               else
998
               {
999
                  extProg = prog;
1000
               }
1001
            }
1002
         }
1003
         
1004
         return extProg;
1005
      });
1006
      lookupAliasedCompiledRcode = !legacyRcode || !hasAliases ? null : (pname ->
1007
      {
1008
         if (!isRcode.apply(pname))
1009
         {
1010
            return null;
1011
         }
1012
         
1013
         String[] aliases = resolvePathAliases(pname);
1014
         
1015
         ExternalProgram extProg = null;
1016
         
1017
         for (String aliasName : aliases)
1018
         {
1019
            ExternalProgram prog = lookupCompiledRcode.apply(aliasName);
1020
            
1021
            if (prog != null)
1022
            {
1023
               if (extProg != null)
1024
               {
1025
                  LOG.log(Level.WARNING, String.format(msg, 
1026
                                                       extProg.pname, 
1027
                                                       legacyProgName, 
1028
                                                       prog.pname));
1029
               }
1030
               else
1031
               {
1032
                  extProg = prog;
1033
               }
1034
            }
1035
         }
1036
         
1037
         return extProg;
1038
      });
1039
      
1040
      @SuppressWarnings("unchecked")
1041
      Function<String, ExternalProgram>[] lookups = new Function[]
1042
      {
1043
         lookupDirectName,
1044
         lookupCompiledRcode,
1045
         lookupAliasedDirectName,
1046
         lookupAliasedCompiledRcode,
1047
      };
1048
      
1049
      for (Function<String, ExternalProgram> lookup : lookups)
1050
      {
1051
         if (lookup == null)
1052
         {
1053
            continue;
1054
         }
1055

  
1056
         String[] res = convertName(legacyProgName, lookup);
1057
         if (res != null)
1058
         {
1059
            return res;
1060
         }
1061
      }
1062
      
1063
      return null;
1064
   }
1065

  
1066
   /**
1067
    * Convert a Progress file name into Java class name. The input name can be an absolute file
1068
    * name, a file name relative to current location or a file name relative to some directory
1069
    * listed in the PROPATH variable. Before processing, the input file name is processed by
1070
    * {@link #canonicalize}.
1071
    * <p>
1072
    * A simple lookup is tried first. If this fails direct check for absolute path if attempted
1073
    * using one of the roots from {@code legacyRoots}. In multiple project scenario, the project
1074
    * name is also added as a path token in order to match the structure from
1075
    * {@code name_map.xml}.
1076
    * <p>
1077
    * If this mapping does not exist then each path segment of the PROPATH is tried in order. 
1078
    * The given program name is check to see if it is prefixed with the PROPATH segment, if so
1079
    * that prefix is removed and a lookup is done with this name. The first match found ends the
1080
    * search.
1081
    * <p>
1082
    * Absolute paths are converted into relative paths based on the existence of a matching path
1083
    * in the legacy PROPATH (see {@link EnvironmentOps#getLegacySearchPath}). To handle additional
1084
    * absolute paths, one must add the appropriate path prefix to the legacy PROPATH and it will
1085
    * be removed here.
1086
    * <p>
1087
    * This same PROPATH processing is how names relative to the current directory can be handled.
1088
    * This is the only way to handle that case since the current directory in the source system is
1089
    * runtime information that is not available here.
1090
    * <p>
1091
    * The legacy PROPATH is the key configuration tool by which one specifies to this method how
1092
    * to remove absolute and current directory relative prefixes.  This means that the legacy
1093
    * PROPATH that is used here may need to be different than the PROPATH on the original system.
1094
    * <p>
1095
    * As a fallback, if no mapping can be found and if the given {@code progName} is a fully
1096
    * qualified Java class name which references a valid Java class, then the {@code progName} is
1097
    * returned unchanged.
1098
    * <p>
1099
    * In Windows it can verify the absolute or relative program name is in current PROPATH 
1100
    * directory set.  This allows to properly map the following program names used in
1101
    * PROGRESS RUN statement:
1102
    * "c:/path/to/project/stcases/uast/rundir/absolute/program.p"
1103
    * "rundir/../../uast/rundir/relative/test11.p."
1104
    * "../uast/rundir/relative/test5.p"
1105
    *
1106
    * @param   legacyProgName
1107
    *          Progress file name.
1108
    * @param   lookup
1109
    *          The lookup function used to resolve a program name to a {@link ExternalProgram}.
1110
    *
1111
    * @return  A {@code String} array with two components: Java class name for the given Progress
1112
    *          file name as first element and the legacy procedure name in second element of the
1113
    *          array. If no appropriate mapping is present {@code null} is returned.
1114
    */
1115
   private static String[] convertName(String legacyProgName, 
1116
                                       Function<String, ExternalProgram> lookup)
1117
   {
904 1118
      initMappingData();
905 1119
      
906 1120
      // get the original propath and split it into pieces
......
965 1179
            
966 1180
            // regardless if there are '..' up paths or not, we must check the relName against
967 1181
            // our dictionary - don't use the propath here.
968
            extProg = p2jMap.get(relName);
1182
            extProg = lookup.apply(relName);
969 1183
            
970 1184
            if (extProg == null)
971 1185
            {
......
974 1188
               String token = Utils.getProjectToken();
975 1189
               if (token != null)
976 1190
               {
977
                  extProg = p2jMap.get(token + fileSep + relName);
1191
                  extProg = lookup.apply(token + fileSep + relName);
978 1192
               }
979 1193
            }
980 1194
            
......
1022 1236
               // absolute folder.
1023 1237

  
1024 1238
               // check for a match
1025
               extProg = p2jMap.get(canonicalize(relName));
1239
               extProg = lookup.apply(canonicalize(relName));
1026 1240
               
1027 1241
               if (extProg != null)
1028 1242
               {
......
1038 1252
      // case 3. check the simple form of the name - in this case, just check for an exact match
1039 1253
      if (extProg == null)
1040 1254
      {
1041
         extProg = p2jMap.get(canonicalize(progName));
1255
         extProg = lookup.apply(canonicalize(progName));
1042 1256
      }
1043 1257

  
1044 1258
      // case 4. fallback processing
......
1217 1431
   }
1218 1432
   
1219 1433
   /**
1434
    * Given a program name, replace all folder names which have an associated 'source' folder,
1435
    * and return the new path.
1436
    * 
1437
    * @param    pname
1438
    *           The legacy program name.
1439
    *           
1440
    * @return   The resolved program name.
1441
    */
1442
   private static String[] resolvePathAliases(String pname)
1443
   {
1444
      String[] entries = pname.split(fileSep);
1445
      
1446
      // for now, we replace all found aliases with their source folders;  if needed, we can 
1447
      // compute all power sets made from pathAliases (will be 2^(pathAliases.size()) subsets)
1448
      
1449
      StringBuilder sb = new StringBuilder();
1450
      for (int i = 0; i < entries.length - 1; i++)
1451
      {
1452
         String entry = entries[i];
1453
         
1454
         // if this entry is an alias, get its source
1455
         String source = pathAliases.get(entry);
1456
         
1457
         sb.append(source == null ? entry : source).append(fileSep);
1458
      }
1459
      sb.append(entries[entries.length - 1]);
1460
      
1461
      return new String[] { sb.toString() };
1462
   }
1463
   
1464
   /**
1465
    * Load the path aliases from the directory, into the {@link #pathAliases} map.  This map 
1466
    * contains as key the 'aliased' name which is an alias for a 'source' folder.
1467
    * <p>
1468
    * The directory contains these mappings in the <code>/server/default/path-aliases</code> or
1469
    * <code>/server/&lt;server-id&gt;/path-aliases</code> node.  The child nodes have as class 
1470
    * <code>path-alias</code> and this structure:
1471
    * <code>
1472
    *     &lt;node class="path-alias" name="alias1"&gt;
1473
    *     <br>&nbsp;&nbsp;&nbsp;
1474
    *       &lt;node-attribute name="source" value="src1"/&gt;
1475
    *       <br>&nbsp;&nbsp;&nbsp;
1476
    *       &lt;node-attribute name="alias" value="obj1"/&gt;
1477
    *       <br>
1478
    *     &lt;/node&gt;
1479
    * </code>
1480
    * <p>
1481
    * The <code>alias1</code> name is just an unique ID for each child.
1482
    */
1483
   private static void loadPathAliases()
1484
   {
1485
      DirectoryService ds = DirectoryService.getInstance();
1486
      
1487
      if (!ds.bind())
1488
         throw new RuntimeException("directory bind failed");
1489
      
1490
      try
1491
      {
1492
         String path = Utils.findDirectoryNodePath(ds, "path-aliases", "container", false);
1493
         if (path == null)
1494
         {
1495
            return;
1496
         }
1497
         
1498
         String[] aliasPaths = ds.enumerateNodes(path);
1499
         if (aliasPaths.length == 0)
1500
         {
1501
            return;
1502
         }
1503
   
1504
         pathAliases = new HashMap<>();
1505
         for (String aliasPath : aliasPaths)
1506
         {
1507
            aliasPath = path + "/" + aliasPath;
1508
            String source = ds.getNodeString(aliasPath, "source");
1509
            String alias = ds.getNodeString(aliasPath, "alias");
1510
   
1511
            if (!caseSens)
1512
            {
1513
               source = source.toLowerCase();
1514
               alias = alias.toLowerCase();
1515
            }
1516
            
1517
            pathAliases.put(alias, source);
1518
         }
1519
      }
1520
      finally
1521
      {
1522
         ds.unbind();
1523
      }
1524
   }
1525
   
1526
   /**
1220 1527
    * This method loads all mapping data from a URL which is calculated
1221 1528
    * using the <code>pkgroot</code> and the <code>name_map.xml</code> with
1222 1529
    * all "." characters converted to file separators and an extra file
......
1243 1550
         try
1244 1551
         {
1245 1552
            p2jMap = new HashMap<>(1024);
1553
            p2jNonExtMap = new HashMap<>(1024);
1246 1554
            j2pMap = new HashMap<>(1024);
1247 1555
            
1248 1556
            // lookup the descriptors for the original file system
......
1344 1652
               ExternalProgram ep = buildExternalProgram(pname, jname, classMap);
1345 1653
               p2jMap.put(ep.pname, ep);
1346 1654
               j2pMap.put(ep.jname, ep);
1655
               
1656
               String nonExtName = pname;
1657
               int lastDotIdx = nonExtName.lastIndexOf('.');
1658
               if (lastDotIdx > 0)
1659
               {
1660
                  nonExtName = nonExtName.substring(0, lastDotIdx);
1661
               }
1662
               
1663
               List<ExternalProgram> progs = p2jNonExtMap.get(nonExtName);
1664
               if (progs == null)
1665
               {
1666
                  p2jNonExtMap.put(nonExtName, progs = new ArrayList<>(1));
1667
               }
1668
               
1669
               progs.add(ep);
1347 1670
            }
1671
            
1672
            loadPathAliases();
1348 1673
         }
1349 1674
         catch (Exception e)
1350 1675
         {
src/dir_schema.xml 2018-05-07 16:24:49 +0000
212 212
         <class-attribute name="account" type="STRING"  mandatory="false"  multiple="true" immutable="false" />
213 213
      </object-class>
214 214
      
215
      <object-class name="path-alias" leaf="true" immutable="false">
216
         <class-attribute name="source" type="STRING" mandatory="false" multiple="false" immutable="false" />
217
         <class-attribute name="alias"  type="STRING" mandatory="false" multiple="false" immutable="false" />
218
      </object-class>
215 219
</schema-root>