All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.uima.migratev3.jcas.MigrateJCas Maven / Gradle / Ivy

There is a newer version: 3.6.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.migratev3.jcas;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.uima.UIMARuntimeException;
import org.apache.uima.cas.impl.TypeImpl;
import org.apache.uima.cas.impl.TypeSystemImpl;
import org.apache.uima.cas.impl.UimaDecompiler;
import org.apache.uima.internal.util.CommandLineParser;
import org.apache.uima.internal.util.Misc;
import org.apache.uima.internal.util.UIMAClassLoader;
import org.apache.uima.internal.util.function.Runnable_withException;
import org.apache.uima.pear.tools.PackageBrowser;
import org.apache.uima.pear.tools.PackageInstaller;
import org.apache.uima.util.FileUtils;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.AnnotationDeclaration;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.expr.AssignExpr;
import com.github.javaparser.ast.expr.BinaryExpr;
import com.github.javaparser.ast.expr.CastExpr;
import com.github.javaparser.ast.expr.EnclosedExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.Name;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.NullLiteralExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.expr.SimpleName;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.EmptyStmt;
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.stmt.IfStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.printer.PrettyPrinter;
import com.github.javaparser.printer.PrettyPrinterConfiguration;

/**
 * 

A driver that scans given roots for source and/or class Java files that contain JCas classes * *

  • identifies which ones appear to be JCas classes (heuristic) *
    • of these, identifies which ones appear to be v2 *
      • converts these to v3
    * *
  • also can receive a list of individual class names
  • *
  • also can do a single source file
  • *
* *

Creates summary and detailed reports of its actions. * *

Files representing JCas classes to convert are discovered by walking file system * directories from various roots, specified as input. The tool operates in 1 of two exclusive * "modes": migrating from sources (e.g., .java files) and migrating using compiled classes. * *

Compiled classes are decompiled and then migrated. This decompilation step usually * requires a java classpath, which is supplied using the -migrateClasspath parameter. * Exception: migrating PEAR files, which contain their own specification for a classpath. * *

The same JCas class may be encountered multiple * times while walking the directory tree from the roots, * with the same or different definition. All of these definitions are migrated. * *

Copies of the original and the converted files are put into the output file tree. * *

Directory structure, starting at -outputDirectory (which if not specified, is a new temp directory). * The "a0", "a1" represent non-identical alternative definitions for the same class. *

 *     converted/      
 *       v2/    these are the decompiled or "found" source files
 *         a0/x/y/z/javapath/.../Classname.java   root-id + fully qualified java class + package as slashified name
 *                             /Classname2.java etc.
 *         a1/x/y/z/javapath/.../Classname.java  if there are different root-ids
 *         ...
 *       v3/
 *         a0/x/y/z/javapath/.../Classname.java   fully qualified java class + package as slashified name
 *                             /Classname2.java etc.
 *         a1/x/y/z/javapath/.../Classname.java   if there are different root-ids
 *         ...
 *        
 *       v3-classes - the compiled form if from classes and a java compiler was available
 *                    The first directory is the id of the Jar or PEAR container.
 *                    The second directory is the alternative.
 *                  
 *         23/a0/fully/slashified/package/class-name.class  << parallel structure as v3/       
 *         
 *       jars/ - copies of the original JARs with the converted JCas classes
 *               The first directory is the id of the Jar or PEAR container
 *         7/jar-file-name-last-part.jar
 *         12/jar-file-name-last-part.jar
 *         14/   etc.
 *       
 *       pears - copies of the original PEARs with the converted JCas classes, if there were no duplicates
 *         8/pear-file-name-last-art.pear
 *         9/   etc.
 *       
 *     not-converted/   (skipped)
 *     logs/
 *       jar-map.txt   list of index to paths
 *       pear-map.txt  list of index to paths
 *       processed.txt
 *       duplicates.txt
 *       builtinsNotExtended.txt
 *       failed.txt
 *       skippedBuiltins.txt
 *       nonJCasFiles.txt
 *       woraroundDir.txt
 *       deletedCheckModified.txt
 *       manualInspection.txt
 *       pearFileUpdates.txt
 *       jarFileUpdates.txt
 *       ...
 * 
* *

Operates in one of two modes: *

 *   Mode 1: Given classes-roots and/or individual class names, and a migrateClasspath, 
 *     scans the classes-routes looking for classes candidates
 *       - determines the class name,
 *       - decompiles that
 *       - migrates that decompiled source.
 *      
 *     if a Java compiler (JDK) is available,
 *       - compiles the results
 *       - does reassembly for Jars and PEARs, replacing the JCas classes.   
 *       
 *   Mode 2: Given sources-roots or a single source java file
 *     scans the sources-routes looking for candidates
 *       - migrates that decompiled source.
 * 
* *

Note: Each run clears the output directory before starting the migration. * *

Note: classpath may be specified using -migrateClassPath or as the class path used to run this tool. */ public class MigrateJCas extends VoidVisitorAdapter { /* ***************************************************** * Internals * * Unique IDs of v2 and v3 artifacts: * RootId + classname * * RootIdContainers (Set) hold all discovered rootIds, at each Jar/Pear nesting level * including outer level (no Jar/Pear). * These are kept in a push-down stack * * * Processing roots collection: done for source or class * - iterate, for all roots * -- processCollection for candidates rooted at that root * --- candidate is .java or .class, with path, with pearClasspath string * ---- migrate called on each candidate * ----- check to see if already done, and if so, skip. * ------ means: same byte or source code associated with same fqcn * * Root-ids: created for each unique pathpart in front of fully-qualified class name * created for each unique path to Jar or PEAR * * Caching to speed up duplicate processing: * - decompiling: if the byte[] is already done, use other value (if augmented migrateClasspath is the same) * - source-migrating: if the source strings are the same. * * Multiple sources for single class: * classname2multiSources: TreeMap from fqcn to CommonConverted (string or bytes) * CommonConverted: supports multiple paths having identical string/bytes. * * Compiling: driven from c2ps array of fqcn, path * - may have multiple entries for same fqcn, with different paths, * -- only if different values for the impl * - set when visiting top-level compilation unit non-built-in type * */ /** manange the indention of printing routines */ private static final int[] indent = new int[1]; private static StringBuilder si(StringBuilder sb) { return Misc.indent(sb, indent); } private static StringBuilder flush(StringBuilder sb) { System.out.print(sb); sb.setLength(0); return sb; } private static final Integer INTEGER0 = 0; private static int nextContainerId = 0; /****************************************************************** * Container - exists in tree structure, has super, sub containers * -- subcontainers: has path to it * - holds set of rootIds in that container * - topmost one has null parent, and null pathToJarOrPear ******************************************************************/ private static class Container implements Comparable { final int id = nextContainerId++; final Container parent; // null if at top level /** root to scan from. * Pears: is the loc in temp space of installed pear * Jars: is the file system mounted on the Jar * -- for inner Jars, the Jar is copied out into temp space. */ Path root; final Path rootOrig; // for Jars and Pears, the original path ending in jar or pear final Set subContainers = new TreeSet<>(); // tree set for better ordering final List candidates = new ArrayList<>(); final List convertedItems = new ArrayList<>(); final List v3CompiledPathAndContainerItemPath = new ArrayList<>(); final boolean isPear; final boolean isJar; final boolean isSingleJavaSource; /** can't use Path as the type, because the equals for Path is object == */ final Set _Types = new HashSet<>(); // has the non_Type path only if the _Type is found boolean haveDifferentCapitalizedNamesCollidingOnWindows = false; String pearClasspath; // not final - set by subroutine after defaulting /** Cache of already done compiled classes, to avoid redoing them * Kept by container, because the classpath could change the decompile */ private Map origBytesToCommonConverted = new HashMap<>(); Container(Container parent, Path root) { this.parent = parent; if (parent != null) { parent.subContainers.add(this); this.pearClasspath = parent.pearClasspath; // default, when expanding Jars. } this.rootOrig = root; String s = root.toString().toLowerCase(); isJar = s.endsWith(".jar"); isPear = s.endsWith(".pear"); isSingleJavaSource = s.endsWith(".java"); this.root = (isPear || isJar) ? installJarOrPear() : root; // // debug // if (!isPear && isJar) { // System.out.println("debug prepare jar: " + this); // } } /** * Called when a new container is created * @param container * @param path * @return install directory */ private Path installJarOrPear() { try { Path theJarOrPear = rootOrig; if (!theJarOrPear.getFileSystem().equals(FileSystems.getDefault())) { // pear is embedded in another pear or jar, so copy the Jar (intact) to a temp spot so it's no longer embedded theJarOrPear = getTempOutputPathForJarOrPear(theJarOrPear); Files.copy(rootOrig, theJarOrPear, StandardCopyOption.REPLACE_EXISTING); } if (isPear) { // extract the pear just to get the classpath File pearInstallDir = Files.createTempDirectory(getTempDir(), "installedPear").toFile(); PackageBrowser ip = PackageInstaller.installPackage(pearInstallDir, rootOrig.toFile(), false); String newClasspath = ip.buildComponentClassPath(); String parentClasspath = parent.pearClasspath; this.pearClasspath = (null == parentClasspath || 0 == parentClasspath.length()) ? newClasspath : newClasspath + File.pathSeparator + parentClasspath; } FileSystem pfs = FileSystems.newFileSystem(theJarOrPear, null); return pfs.getPath("/"); } catch (IOException e) { throw new RuntimeException(e); } } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder sb = toString1(); indent[0] += 2; try { si(sb); // new line + indent sb.append("subContainers="); Misc.addElementsToStringBuilder(indent, sb, Misc.setAsList(subContainers), -1, (sbx, i) -> sbx.append(i.id)).append(','); si(sb).append("paths migrated="); // new line + indent Misc.addElementsToStringBuilder(indent, sb, candidates, -1, StringBuilder::append).append(','); // si(sb).append("v3CompilePath="); // new line + indent // Misc.addElementsToStringBuilder(indent, sb, v3CompiledPathAndContainerItemPath, 100, StringBuilder::append); } finally { indent[0] -=2; si(sb).append(']'); } return sb.toString(); } public StringBuilder toString1() { StringBuilder sb = new StringBuilder(); si(sb); // initial nl and indentation sb.append(isJar ? "Jar " : isPear ? "PEAR " : ""); sb.append("container [id=").append(id) .append(", parent.id=").append((null == parent) ? "null" : parent.id) .append(", root or pathToJarOrPear=").append(rootOrig).append(','); return sb; } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return 31 * id; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Container other = (Container) obj; if (id != other.id) return false; return true; } @Override public int compareTo(Container o) { return Integer.compare(id, o.id); } } /** * A path to a .java or .class file in some container, for the v2 version * For Jars and Pears, the path is relative to the zip "/" dir */ private static class ContainerAndPath implements Comparable { final Path path; final Container container; ContainerAndPath(Path path, Container container) { this.path = path; this.container = container; } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(ContainerAndPath o) { int r = path.compareTo(o.path); if (r != 0) { return r; } return Integer.compare(container.id, o.container.id); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("ContainerAndPath [path=").append(path).append(", container=").append(container.id).append("]"); return sb.toString(); } } /** * This class holds information used to replace compiled items in Jars and Pears. * * a pair of the v3CompiledPath (which is the container nbr/a0/ + the package-class-name slash + ".class" * and the Container origRoot up to the start of the package and class name * for the item being compiled. * - Note: if a Jar has the same compiled class at multiple nesting levels, each one will have * an instance of this class */ private static class V3CompiledPathAndContainerItemPath { final Path v3CompiledPath; final String pathInContainer; public V3CompiledPathAndContainerItemPath(Path v3CompiledPath, String pathInContainer) { this.v3CompiledPath = v3CompiledPath; this.pathInContainer = pathInContainer; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder sb = new StringBuilder(); si(sb).append("v3CompiledPathAndContainerPartPath ["); indent[0] += 2; try { si(sb).append("v3CompiledPath=").append(v3CompiledPath); si(sb).append("pathInContainer=").append(pathInContainer); } finally { indent[0] -= 2; si(sb).append("]"); } return sb.toString(); } } private static final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); /**************************************************************** * Command line parameters ****************************************************************/ private static final String SOURCE_FILE_ROOTS = "-sourcesRoots"; private static final String CLASS_FILE_ROOTS = "-classesRoots"; private static final String OUTPUT_DIRECTORY = "-outputDirectory"; // private static final String SKIP_TYPE_CHECK = "-skipTypeCheck"; private static final String MIGRATE_CLASSPATH = "-migrateClasspath"; // private static final String CLASSES = "-classes"; // individual classes to migrate, get from supplied classpath private static final Type intType = PrimitiveType.intType(); private static final Type callSiteType = JavaParser.parseType("CallSite"); private static final Type methodHandleType = JavaParser.parseType("MethodHandle"); private static final Type stringType = JavaParser.parseType("String"); private static final EnumSet public_static_final = EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); private static final EnumSet private_static_final = EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL); private static final PrettyPrinterConfiguration printWithoutComments = new PrettyPrinterConfiguration(); static { printWithoutComments.setPrintComments(false); } private static final PrettyPrinterConfiguration printCu = new PrettyPrinterConfiguration(); static { printCu.setIndent(" "); } private static final String ERROR_DECOMPILING = "!!! ERROR:"; static private boolean isSource = false; static private Path tempDir = null; /***************************************************************************************************/ private String packageName; // with dots? private String className; // (omitting package) private String packageAndClassNameSlash; // next 3 set at start of migrate for item being migrated private CommonConverted current_cc; private Path current_path; private Container current_container; /** includes trailing / */ private String outputDirectory; /** includes trailing / */ private String outDirConverted; /** includes trailing / */ private String outDirSkipped; /** includes trailing / */ private String outDirLog; private Container[] sourcesRoots = null; // only one of these has 1 or more Container instances private Container[] classesRoots = null; private CompilationUnit cu; // save this value in the class instance to avoid recomputing it private ClassLoader cachedMigrateClassLoader = null; private String migrateClasspath = null; // private String individualClasses = null; // to decompile /** * CommonConverted next id, by fqcn * key: fqcn_slashes value: next id */ private Map nextCcId = new HashMap<>(); /** * Common info about a particular source-code instance of a class * Used to avoid duplicate work for the same JCas definition * Used to track identical and non-identical duplicate defs * * When processing from sourcesRoots: * use map: origSourceToCommonConverted key = source string * if found, skip conversion, use previous converted result. * * When processing from classesRoots: * use map: origBytesToCommonConverted key = byte[], kept by container in container * if found, use previous converted results */ private class CommonConverted { /** * starts at 0, incr for each new instance for a particular fqcn_slash * can't be assigned until fqcn known */ int id = -1; // temp value final String v2Source; // remembered original source final byte[] v2ByteCode; // remembered original bytes /** all paths + their containers having the same converted result * Need container because might change classpath for compiling * - path is to v2 source or compiled class*/ final Set containersAndV2Paths = new HashSet<>(); String v3Source; // if converted, the result /** converted/v3/id-of-cc/pkg/name/classname.java */ Path v3SourcePath; // path to converted source or null String fqcn_slash; // full name of the class e.g. java/util/Foo. unknown for sources at first CommonConverted(String origSource, byte[] v2ByteCode, Path path, Container container, String fqcn_slash) { this.v2Source = origSource; this.v2ByteCode = v2ByteCode; containersAndV2Paths.add(new ContainerAndPath(path, container)); this.fqcn_slash = fqcn_slash; } /** * * @param container having this commonConverted instance * @return the path to .java or .class file. * If the container is a Jar or PEAR, it is the path within that Jar or Pear FileSystem */ Path getV2SourcePath(Container container) { for (ContainerAndPath cp : containersAndV2Paths) { if (cp.container == container) { return cp.path; } } throw new RuntimeException("internalError"); } int getId() { if (id < 0) { Integer nextId = nextCcId.computeIfAbsent(fqcn_slash, s -> INTEGER0); nextCcId.put(fqcn_slash, nextId + 1); this.id = nextId; } return id; } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return v2Source == null ? 0 : v2Source.hashCode(); } /* equal if the v2source is equal */ @Override public boolean equals(Object obj) { return obj instanceof CommonConverted && v2Source != null && v2Source.equals(((CommonConverted)obj).v2Source); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder sb = new StringBuilder(); int maxLen = 10; si(sb).append("CommonConverted [v2Source=").append(Misc.elide(v2Source, 100)); indent[0] += 2; try { si(sb).append("v2ByteCode="); sb.append(v2ByteCode != null ? Arrays.toString(Arrays.copyOf(v2ByteCode, Math.min(v2ByteCode.length, maxLen))) : "null").append(','); si(sb).append("containersAndPaths=") .append(containersAndV2Paths != null ? Misc.ppList(indent, Misc.setAsList(containersAndV2Paths), -1, StringBuilder::append) : "null").append(','); si(sb).append("v3SourcePath=").append(v3SourcePath).append(','); si(sb).append("fqcn_slash=").append(fqcn_slash).append("]").append('\n'); } finally { indent[0] -= 2; } return sb.toString(); } } /** Cache of already converted source classes, to avoid redoing them; * - key is the actual source * - value is CommonConverted * This cache is over all containers * */ private Map sourceToCommonConverted = new HashMap<>(); /** * A map from fqcn_slash to a list of converted sources * one per non-duplicated source */ private Map> classname2multiSources = new TreeMap<>(); /************************************ * Reporting ************************************/ // private final List v2JCasFiles = new ArrayList<>(); // unused // private final List v3JCasFiles = new ArrayList<>(); // unused private final List nonJCasFiles = new ArrayList<>(); // path, reason private final List failedMigration = new ArrayList<>(); // path, reason private final List skippedBuiltins = new ArrayList<>(); // path, "built-in" private final List deletedCheckModified = new ArrayList<>(); // path, deleted check string private final List pathWorkaround = new ArrayList<>(); // original, workaround private final List pearClassReplace = new ArrayList<>(); // pear, classname private final List jarClassReplace = new ArrayList<>(); // jar, classname private final List manualInspection = new ArrayList<>(); // path, reason // private final List embeddedJars = new ArrayList<>(); // source, temp private boolean isV2JCas; // false at start of migrate, set to true if a v2 class candidate is discovered private boolean isConvert2v3; // true at start of migrate, set to false if conversion fails, left true if already a v3 private boolean isBuiltinJCas; // false at start of migrate, set to true if a built-in class is discovered /************************************ * Context for visits ************************************/ /** * if non-null, we're inside the ast for a likely JCas getter or setter method */ private MethodDeclaration get_set_method; private String featName; private boolean isGetter; private boolean isArraySetter; /** * the range name part for _getXXXValue.. calls */ private Object rangeNamePart; /** * the range name part for _getXXXValue.. calls without converting Ref to Feature */ private String rangeNameV2Part; /** * temp place to insert static final int feature declarations */ private NodeList> fi_fields = new NodeList<>(); private Set featNames = new HashSet<>(); private boolean hasV2Constructors; private boolean hasV3Constructors; private boolean error_decompiling = false; private boolean badClassName; private int itemCount; /** * set if getAndProcessCasndidatesInContainer encounters a class where it cannot do the compile */ private boolean unableToCompile; final private StringBuilder psb = new StringBuilder(); public MigrateJCas() { } public static void main(String[] args) { (new MigrateJCas()).run(args); } /*********************************** * Main * @param args - ***********************************/ void run(String[] args) { CommandLineParser clp = parseCommandArgs(args); System.out.format("Output top directory: %s%n", outputDirectory); // clear output dir FileUtils.deleteRecursive(new File(outputDirectory)); isSource = sourcesRoots != null; boolean isOk; if (isSource) { isOk = processRootsCollection("source", sourcesRoots, clp); } else { if (javaCompiler == null) { System.out.println("The migration tool cannot compile the migrated files, \n" + " because no Java compiler is available.\n" + " To make one available, run this tool using a Java JDK, not JRE"); } isOk = processRootsCollection("classes", classesRoots, clp); } // if (individualClasses != null) { // processCollection("individual classes: ", new Iterator() { // Iterator it = Arrays.asList(individualClasses.split(File.pathSeparator)).iterator(); // public boolean hasNext() {return it.hasNext();} // public String next() { // return prepareIndividual(it.next());} // }); // } if (error_decompiling) { isOk = false; } isOk = report() && isOk; System.out.println("Migration finished " + (isOk ? "with no unusual conditions." : "with 1 or more unusual conditions that need manual checking.")); } /** * called for compiled input when a compiler is available and don't have name collision * if the container is a PEAR or a Jar * Updates a copy of the Pear or Jar * @param container */ private void postProcessPearOrJar(Container container) { Path outDir = Paths.get(outputDirectory, container.isJar ? "jars" : "pears", Integer.toString(container.id)); withIOX(() -> Files.createDirectories(outDir)); si(psb).append("Replacing .class files in copy of ").append(container.rootOrig); flush(psb); try { // copy the pear or jar so we don't change the original Path lastPartOfPath = container.rootOrig.getFileName(); if (null == lastPartOfPath) throw new RuntimeException("Internal Error"); Path pearOrJarCopy = Paths.get(outputDirectory, container.isJar ? "jars" : "pears", Integer.toString(container.id), lastPartOfPath.toString()); Files.copy(container.rootOrig, pearOrJarCopy); // put up a file system on the pear or jar FileSystem pfs = FileSystems.newFileSystem(pearOrJarCopy, null); // replace the .class files in this PEAR or Jar with corresponding v3 ones indent[0] += 2; String[] previousSkip = {""}; container.v3CompiledPathAndContainerItemPath.forEach(c_p -> { if (Files.exists(c_p.v3CompiledPath)) { withIOX(() -> Files.copy(c_p.v3CompiledPath, pfs.getPath(c_p.pathInContainer), StandardCopyOption.REPLACE_EXISTING)); reportPearOrJarClassReplace(pearOrJarCopy.toString(), c_p.v3CompiledPath.toString(), container); } else { String pstr = c_p.v3CompiledPath.toString(); String pstr2 = pstr; if (previousSkip[0] != "") { int cmn = findFirstCharDifferent(previousSkip[0], pstr); pstr2 = cmn > 5 ? ("..." + pstr.substring(cmn)) : pstr; } previousSkip[0] = pstr; si(psb).append("Skipping replacing ").append(pstr2) .append(" because it could not be found, perhaps due to compile errors."); flush(psb); } }); indent[0] -= 2; // for (CommonConverted cc : container.convertedItems) { // Map v3ccs = cc.v3CompiledResultPaths; // v3ccs.forEach((v3ccc, v3cc_path) -> // { // if (v3ccc == container) { // String path_in_v3_classes = cc.v3CompiledResultPaths.get(container).toString(); // // withIOX(() -> Files.copy(v3cc_path, pfs.getPath(path_in_v3_classes))); // reportPearOrJarClassReplace(pearOrJarCopy.toString(), path_in_v3_classes, container); // } // }); // } pfs.close(); } catch (IOException e) { throw new RuntimeException(e); } } /** * Compile all the migrated JCas classes in this container, adjusting the classpath * if the container is a Jar or Pear to include the Jar or PEAR. * * As a side effect, it saves in the container, a list of all the compiled things together * with the path in container part, for use by a subsequent step to update copies of the jars/pears. * * The items in the container are broken into batches of multiple classes to be compiled together. * - The grouping is to group by alternative number. This insures that multiple * definitions of the same class are done separately (otherwise the compiler complains * about multiple definitions). * * As a side effect. compiling update the container, adding all the compiled items * to v3CompiledPathAndContainerItemPath * * @param container - * @return true if compiled 1 or more sources, false if nothing was compiled */ private boolean compileV3SourcesCommon2(Container container) { String classesBaseDir = outDirConverted + "v3-classes/" + container.id; // specify the classpath. For PEARs use a class loader that loads first. String classpath = getCompileClassPath(container); // // debug // String[] cpa = classpath.split(File.pathSeparator); // System.out.println("debug - compilation classpath"); // int j = 0; // for (String s : cpa) System.out.println("debug classpath: " + (++j) + " " + s); // get a list of compilation unit path strings to the converted/v3/nnn/path /** * containerRoot is * rootOrig or for Jars/Pears the Path to "/" in the zip file system */ Path containerRoot = null; /** * The Cu Path Strings for one container might have multiple instances of the class. * These might be for identical or different sources. * - This happens when a root has multiple paths to instances of the same class. * - Multiple compiled-paths might be for the same classname * * For non-identical sources, the commonContainer instance "id" is spliced into the * v3 migrated source path: see getBaseOutputPath, e.g. converted/2/a3/fqcn/slashed/name.java * * The compiler will complain if you feed it the same compilation unit classname twice, with * different paths saying "duplicate class definition". * - Fix: do compilation in batches, one for each different commonConverted id. */ Map> cu_path_strings_by_ccId = new TreeMap<>(); // tree map to have nice order of keys indent[0] += 2; boolean isEmpty = true; for (CommonConverted cc : container.convertedItems) { if (cc.v3SourcePath == null) continue; // skip items that failed migration isEmpty = false; // relativePathInContainer = the whole path with the first part (up to the end of the container root) stripped off /** * itemPath is the original path in the container to where the source or class file is * For Jars and PEARs, it is relative to the Jar or PEAR */ Path itemPath = cc.getV2SourcePath(container); if (null == containerRoot) { // lazy setup on first call // for Pears, must use the == filesystem, otherwise get // ProviderMismatchException containerRoot = (container.isJar || container.isPear) ? itemPath.getFileSystem().getPath("/") : container.rootOrig; } /** * relativePathInContainer might be x/y/z/a/b/c/name.class * (ends in .class because we only get here when the input is class files) */ String relativePathInContainer = containerRoot.relativize(itemPath).toString(); container.v3CompiledPathAndContainerItemPath.add( new V3CompiledPathAndContainerItemPath( Paths.get(classesBaseDir, "a" + cc.id, cc.fqcn_slash + ".class" /*relativePathInContainer*/), relativePathInContainer)); ArrayList items = cu_path_strings_by_ccId.computeIfAbsent(cc.id, x -> new ArrayList<>()); items.add(cc.v3SourcePath.toString()); } if (isEmpty) { si(psb).append("Skipping compiling for container ").append(container.id).append(" ").append(container.rootOrig); si(psb).append(" because non of the v2 classes were migrated (might have been built-ins)"); flush(psb); return false; } else { si(psb).append("Compiling for container ").append(container.id).append(" ").append(container.rootOrig); flush(psb); } // List cu_path_strings = container.convertedItems.stream() // .filter(cc -> cc.v3SourcePath != null) // .peek(cc -> container.v3CompiledPathAndContainerItemPath.add( // new V3CompiledPathAndContainerItemPath( // Paths.get(classesBaseDir, cc.v3SourcePath.toString()), // getPathInContainer(container, cc).toString()))) // .map(cc -> cc.v3SourcePath.toString()) // .collect(Collectors.toList()); boolean resultOk = true; for (int ccId = 0;; ccId++) { // do each version of classes separately List cups = cu_path_strings_by_ccId.get(ccId); if (cups == null) { break; } StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, Charset.forName("UTF-8")); Iterable compilationUnits = fileManager.getJavaFileObjectsFromStrings(cups); // //debug // System.out.println("Debug: list of compilation unit strings for iteration " + i); // int[] k = new int[] {0}; // cups.forEach(s -> System.out.println(Integer.toString(++(k[0])) + " " + s)); // System.out.println("debug end"); String classesBaseDirN = classesBaseDir + "/a" + ccId; withIOX(() -> Files.createDirectories(Paths.get(classesBaseDirN))); Iterable options = Arrays.asList("-d", classesBaseDirN, "-classpath", classpath); si(psb).append("Compiling for commonConverted version ").append(ccId) .append(", ").append(cups.size()).append(" classes"); flush(psb); DiagnosticCollector diagnostics = new DiagnosticCollector<>(); /*********** Compile ***********/ resultOk = javaCompiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits).call() && resultOk; /********************************/ indent[0] += 2; for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { JavaFileObject s = diagnostic.getSource(); si(psb).append(diagnostic.getKind()); int lineno = (int) diagnostic.getLineNumber(); if (lineno != Diagnostic.NOPOS) { psb.append(" on line ").append(diagnostic.getLineNumber()); } int pos = (int) diagnostic.getPosition(); if (pos != Diagnostic.NOPOS) { psb.append(", position: ").append(diagnostic.getColumnNumber()); } if (s != null) { psb.append(" in ").append(s.toUri()); } si(psb).append(" ").append(diagnostic.getMessage(null)); flush(psb); } withIOX( () -> fileManager.close()); indent[0] -= 2; si(psb).append("Compilation finished").append( resultOk ? " with no errors." : "with some errors."); flush(psb); } indent[0] -= 2; unableToCompile = !resultOk; return true; } /** * The classpath used to compile is (in precedence order) * - the classpath for this migration app (first in order to pick up v3 support, overriding others) * - any Pears, going up the parent chain, closest ones first * - any Jars, going up the parent chain, closest ones last * - passed in migrate classpath * @return the classpath to use in compiling the jcasgen'd sources */ private String getCompileClassPath(Container container) { // start with this (the v3migration tool) app's classpath to a cp string URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); URL[] urls = systemClassLoader.getURLs(); StringBuilder cp = new StringBuilder(); boolean firstTime = true; for (URL url : urls) { if (! firstTime) { cp.append(File.pathSeparatorChar); } else { firstTime = false; } cp.append(url.getPath()); } // pears up the classpath, closest first Container c = container; while (c != null) { if (c.isPear) { cp.append(File.pathSeparator).append(c.pearClasspath); } c = c.parent; } // add the migrateClasspath, expanded if (null != migrateClasspath) { cp.append(File.pathSeparator).append(Misc.expandClasspath(migrateClasspath)); } // add the Jars, closest last c = container; List ss = new ArrayList<>(); while (c != null) { if (c.isJar) { ss.add(c.root.toString()); } c = c.parent; } Collections.reverse(ss); ss.forEach(s -> cp.append(File.pathSeparator).append(s)); // System.out.println("debug: compile classpath = " + cp.toString()); return cp.toString(); } /** * iterate to process collections from all roots * Called once, to process either sources or classes * @return false if unable to compile, true otherwise */ private boolean processRootsCollection(String kind, Container[] roots, CommandLineParser clp) { unableToCompile = false; // preinit psb.setLength(0); indent[0] = 0; itemCount = 1; for (Container rootContainer : roots) { showWorkStart(rootContainer); // adds candidates to root containers, and adds sub containers for Jars and Pears getAndProcessCandidatesInContainer(rootContainer); // for (Path path : rootContainer.candidates) { // // CommonConverted cc = getSource(path, rootContainer); // migrate(cc, rootContainer, path); // // if ((i % 50) == 0) System.out.format("%4d%n ", Integer.valueOf(i)); // i++; // } } si(psb).append("Total number of candidates processed: ").append(itemCount - 1); flush(psb); indent[0] = 0; return !unableToCompile; } private void showWorkStart(Container rootContainer) { si(psb).append("Migrating " + rootContainer.rootOrig.toString()); indent[0] += 2; si(psb).append("Each character is one class"); si(psb).append(" . means normal class"); si(psb).append(" b means built in"); si(psb).append(" i means identical duplicate"); si(psb).append(" d means non-identical definition for the same JCas class"); si(psb).append(" nnn at the end of the line is the number of classes migrated\n"); flush(psb); } /** * parse command line args * @param args - * @return the CommandLineParser instance */ private CommandLineParser parseCommandArgs(String[] args) { CommandLineParser clp = createCmdLineParser(); try { clp.parseCmdLine(args); } catch (Exception e) { throw new RuntimeException(e); } if (!checkCmdLineSyntax(clp)) { printUsage(); System.exit(2); } if (clp.isInArgsList(CLASS_FILE_ROOTS)) { classesRoots = getRoots(clp, CLASS_FILE_ROOTS); } if (clp.isInArgsList(SOURCE_FILE_ROOTS)) { sourcesRoots = getRoots(clp, SOURCE_FILE_ROOTS); } return clp; } private Container[] getRoots(CommandLineParser clp, String kind) { String[] paths = clp.getParamArgument(kind).split("\\" + File.pathSeparator); Container[] cs = new Container[paths.length]; int i = 0; for (String path : paths) { cs[i++] = new Container(null, Paths.get(path)); } return cs; } /** * @param p the path to the compiled or non-compiled source * @param container the container * @return the instance of the CommonConverted object, * and update the container's convertedItems list if needed to include it */ private CommonConverted getSource(Path p, Container container) { try { byte[] localV2ByteCode = null; CommonConverted cc; String v2Source; if (!isSource) { localV2ByteCode = Files.readAllBytes(p); // only use prev decompiled if same container cc = container.origBytesToCommonConverted.get(localV2ByteCode); if (null != cc) { return cc; } // decompile side effect: sets fqcn try { v2Source = decompile(localV2ByteCode, container.pearClasspath); } catch (RuntimeException e) { badClassName = true; e.printStackTrace(); v2Source = null; } if (badClassName) { System.err.println("Candidate with bad Class Name is: " + p.toString()); return null; } final byte[] finalbc = localV2ByteCode; cc = sourceToCommonConverted.computeIfAbsent(v2Source, src -> new CommonConverted(src, finalbc, p, container, packageAndClassNameSlash)); // cc = new CommonConverted(v2Source, localV2ByteCode, p, container, packageAndClassNameSlash); container.origBytesToCommonConverted.put(localV2ByteCode, cc); } else { v2Source = FileUtils.reader2String(Files.newBufferedReader(p)); cc = sourceToCommonConverted.get(v2Source); if (null == cc) { cc = new CommonConverted(v2Source, null, p, container, "unknown"); sourceToCommonConverted.put(v2Source, cc); } else { // add this new path + container to set of pathsAndContainers kept by this CommonConverted object cc.containersAndV2Paths.add(new ContainerAndPath(p, container)); } } //Containers have list of CommonConverted, which, in turn // have Set of ContainerAndPath elements. // (the same JCas class might appear in two different paths in a container) if (!container.convertedItems.contains(cc)) { container.convertedItems.add(cc); } return cc; } catch (IOException e) { throw new RuntimeException(e); } } /** * Migrate one JCas definition, writes to Sysem.out 1 char to indicate progress. * * The source is either direct, or a decompiled version of a .class file (missing comments, etc.). * * This method only called if heuristics indicate this is a V2 JCas class definition. * * Skips the migration if already done. * Skips if decompiling, and it failed. * * The goal is to preserve as much as possible of existing customization. * The general approach is to parse the source into an AST, and use visitor methods. * For getter/setter methods that are for features (heurstic), set up a context for inner visitors * identifying the getter / setter. * - reuse method declarator, return value casts, value expressions * - remove feature checking statement, array bounds checking statement, if present. * - replace the simpleCore (see Jg), replace the arrayCore * * For constructors, replace the 2-arg one that has arguments: * addr and TOP_Type with the v3 one using TypeImpl, CasImpl. * * Add needed imports. * Add for each feature the _FI_xxx static field declarator. * * Leave other top level things alone * - additional constructors. * - other methods not using jcasType refs * * @param source - the source, either directly from a .java file, or a decompiled .class file */ private void migrate(CommonConverted cc, Container container, Path path) { if (null == cc) { System.err.println("Skipping this component due to decompile failure: " + path.toString()); System.err.println(" in container: " + container); isConvert2v3 = false; error_decompiling = true; return; } if (cc.v3Source != null) { // next updates classname2multiSources for tracking non-identical defs boolean identical = collectInfoForReports(cc); assert identical; psb.append("i"); flush(psb); cc.containersAndV2Paths.add(new ContainerAndPath(path, container)); return; } assert cc.v2Source != null; packageName = null; className = null; packageAndClassNameSlash = null; cu = null; String source = cc.v2Source; isConvert2v3 = true; // preinit, set false if convert fails isV2JCas = false; // preinit, set true by reportV2Class, called by visit to ClassOrInterfaceDeclaration, // when it has v2 constructors, and the right type and type_index_id field declares isBuiltinJCas = false; featNames.clear(); fi_fields.clear(); try { // to reset the next 3 items current_cc = cc; current_container = container; current_path = path; // System.out.println("Migrating source before migration:\n"); // System.out.println(source); // System.out.println("\n\n\n"); if (source.startsWith(ERROR_DECOMPILING)) { System.err.println("Decompiling failed for class: " + cc.toString() + "\n got: " + Misc.elide(source, 300, false)); System.err.println("Please check the migrateClasspath"); if (null == migrateClasspath) { System.err.println("classpath of this app is"); System.err.println(System.getProperty("java.class.path")); } else { System.err.println(" first part of migrateClasspath argument was: " + Misc.elide(migrateClasspath, 300, false)); System.err.println(" Value used was:"); URL[] urls = Misc.classpath2urls(migrateClasspath); for (URL url : urls) { System.err.println(" " + url.toString()); } } System.err.println("Skipping this component"); isConvert2v3 = false; error_decompiling = true; return; } StringReader sr = new StringReader(source); try { cu = JavaParser.parse(sr); addImport("java.lang.invoke.CallSite"); addImport("java.lang.invoke.MethodHandle"); addImport("org.apache.uima.cas.impl.CASImpl"); addImport("org.apache.uima.cas.impl.TypeImpl"); addImport("org.apache.uima.cas.impl.TypeSystemImpl"); this.visit(cu, null); // side effect: sets the className, packageAndClassNameSlash, packageName new removeEmptyStmts().visit(cu, null); if (isConvert2v3) { removeImport("org.apache.uima.jcas.cas.TOP_Type"); } if (isConvert2v3 && fi_fields.size() > 0) { NodeList> classMembers = cu.getTypes().get(0).getMembers(); int positionOfFirstConstructor = findConstructor(classMembers); if (positionOfFirstConstructor < 0) { throw new RuntimeException(); } classMembers.addAll(positionOfFirstConstructor, fi_fields); } ImportDeclaration firstImport = cu.getImports().get(0); String transformedMessage = String.format(" Migrated by uimaj-v3-migration-jcas, %s%n" + " Container: %s%n" + " Path in container: %s%n", new Date(), container.toString1(), path.toString()).replace('\\','/'); Optional existingComment = firstImport.getComment(); if (existingComment.isPresent()) { Comment comment = existingComment.get(); comment.setContent(comment.getContent() + "\n" + transformedMessage); } else { firstImport.setBlockComment(transformedMessage); } if (isSource) { sourceToCommonConverted.put(source, cc); } boolean identicalFound = collectInfoForReports(cc); assert ! identicalFound; if (isV2JCas) { writeV2Orig(cc, isConvert2v3); } if (isConvert2v3) { cc.v3Source = new PrettyPrinter(printCu).print(cu); writeV3(cc); } psb.append(isBuiltinJCas ? "b" : (classname2multiSources.get(cc.fqcn_slash).size() == 1) ? "." : "d"); // means non-identical duplicate flush(psb); } catch (IOException e) { e.printStackTrace(); throw new UIMARuntimeException(e); } catch (Exception e) { System.out.println("debug: exception caught, source was\n" + source); throw new UIMARuntimeException(e); } } finally { current_cc = null; current_container = null; current_path = null; } } /** * Called when have already converted this exact source or * when we just finished converting this source. * Add this instance to the tracking information for multiple versions (identical or not) of a class * @return true if this is an identical duplicate of one already done */ private boolean collectInfoForReports(CommonConverted cc) { String fqcn_slash = cc.fqcn_slash; // track, by fqcn, all duplicates (identical or not) // // for a given fully qualified class name (slashified), // // find the list of CommonConverteds - one per each different version // // create it if null List commonConverteds = classname2multiSources .computeIfAbsent(fqcn_slash, k -> new ArrayList<>()); // search to see if this instance already in the set // if so, add the path to the set of identicals // For class sources case, we compare the decompiled version boolean found = commonConverteds.contains(cc); if (!found) { commonConverteds.add(cc); } return found; } /****************** * Visitors ******************/ /** * Capture the type name from all top-level types * AnnotationDeclaration, Empty, and Enum */ @Override public void visit(AnnotationDeclaration n, Object ignore) { updateClassName(n); super.visit(n, ignore); } // @Override // public void visit(EmptyTypeDeclaration n, Object ignore) { // updateClassName(n); // super.visit(n, ignore); // } @Override public void visit(EnumDeclaration n, Object ignore) { updateClassName(n); super.visit(n, ignore); } /** * Check if the top level class looks like a JCas class, and report if not: * has 0, 1, and 2 element constructors * has static final field defs for type and typeIndexID * * Also check if V2 style: 2 arg constructor arg types * Report if looks like V3 style due to args of 2 arg constructor * * if class doesn't extend anything, not a JCas class. * if class is enum, not a JCas class * @param n - * @param ignore - */ @Override public void visit(ClassOrInterfaceDeclaration n, Object ignore) { // do checks to see if this is a JCas class; if not report skipped Optional maybeParent = n.getParentNode(); if (maybeParent.isPresent()) { Node parent = maybeParent.get(); if (parent instanceof CompilationUnit) { updateClassName(n); if (isBuiltinJCas) { // is a built-in class, skip it super.visit(n, ignore); return; } NodeList supers = n.getExtendedTypes(); if (supers == null || supers.size() == 0) { reportNotJCasClass("class doesn't extend a superclass"); super.visit(n, ignore); return; } NodeList> members = n.getMembers(); setHasJCasConstructors(members); if (hasV2Constructors && hasTypeFields(members)) { reportV2Class(); super.visit(n, ignore); return; } if (hasV2Constructors) { reportNotJCasClassMissingTypeFields(); return; } if (hasV3Constructors) { reportV3Class(); return; } reportNotJCasClass("missing v2 constructors"); return; } } super.visit(n, ignore); return; } @Override public void visit(PackageDeclaration n, Object ignored) { packageName = n.getNameAsString(); super.visit(n, ignored); } /*************** * Constructors * - modify the 2 arg constructor - changing the args and the body * @param n - the constructor node * @param ignored - */ @Override public void visit(ConstructorDeclaration n, Object ignored) { super.visit(n, ignored); // processes the params if (!isConvert2v3) { // for enums, annotations return; } List ps = n.getParameters(); if (ps.size() == 2 && getParmTypeName(ps, 0).equals("int") && getParmTypeName(ps, 1).equals("TOP_Type")) { /** public Foo(TypeImpl type, CASImpl casImpl) { * super(type, casImpl); * readObject(); */ setParameter(ps, 0, "TypeImpl", "type"); setParameter(ps, 1, "CASImpl", "casImpl"); // Body: change the 1st statement (must be super) NodeList stmts = n.getBody().getStatements(); if (!(stmts.get(0) instanceof ExplicitConstructorInvocationStmt)) { recordBadConstructor("missing super call"); return; } NodeList args = ((ExplicitConstructorInvocationStmt)(stmts.get(0))).getArguments(); args.set(0, new NameExpr("type")); args.set(1, new NameExpr("casImpl")); // leave the rest unchanged. } } private final static Pattern refGetter = Pattern.compile("(ll_getRef(Array)?Value)|" + "(ll_getFSForRef)"); private final static Pattern word1 = Pattern.compile("\\A(\\w*)"); // word chars starting at beginning \\A means beginning /***************************** * Method Declaration Visitor * Heuristic to determine if a feature getter or setter: * - name: is 4 or more chars, starting with get or set, with 4th char uppercase * is not "getTypeIndexID" * - (optional - if comments are available:) * getter for xxx, setter for xxx * - for getter: has 0 or 1 arg (1 arg case for indexed getter, arg must be int type) * - for setter: has 1 or 2 args * * Workaround for decompiler - getters which return FSs might be missing the cast to the return value type * *****************************/ @Override public void visit(MethodDeclaration n, Object ignore) { String name = n.getNameAsString(); isGetter = isArraySetter = false; do { // to provide break exit if (name.length() >= 4 && ((isGetter = name.startsWith("get")) || name.startsWith("set")) && Character.isUpperCase(name.charAt(3)) && !name.equals("getTypeIndexID")) { List ps = n.getParameters(); if (isGetter) { if (ps.size() > 1) break; } else { // is setter if (ps.size() > 2 || ps.size() == 0) break; if (ps.size() == 2) { if (!getParmTypeName(ps, 0).equals("int")) break; isArraySetter = true; } } // get the range-part-name and convert to v3 range ("Ref" changes to "Feature") String bodyString = n.getBody().get().toString(printWithoutComments); int i = bodyString.indexOf("jcasType.ll_cas.ll_"); if (i < 0) break; String s = bodyString.substring(i + "jcasType.ll_cas.ll_get".length()); // also for ...ll_set - same length! if (s.startsWith("FSForRef(")) { // then it's the wrapper and the wrong instance. i = s.indexOf("jcasType.ll_cas.ll_"); if (i < 0) { reportUnrecognizedV2Code("Found \"jcasType.ll_cas.ll_[set or get]...FSForRef(\" but didn't find following \"jcasType.ll_cas_ll_\"\n" + n.toString()); break; } s = s.substring(i + "jcasType.ll_cas.ll_get".length()); } i = s.indexOf("Value"); if (i < 0) { reportUnrecognizedV2Code("Found \"jcasType.ll_cas.ll_[set or get]\" but didn't find following \"Value\"\n" + n.toString()); break; // give up } s = Character.toUpperCase(s.charAt(0)) + s.substring(1, i); rangeNameV2Part = s; rangeNamePart = s.equals("Ref") ? "Feature" : s; // get feat name following ")jcasType).casFeatCode_xxxxx, i = bodyString.indexOf("jcasType).casFeatCode_"); if (i == -1) { reportUnrecognizedV2Code("Didn't find \"...jcasType).casFeatCode_\"\n" + n.toString()); break; } Matcher m = word1.matcher(bodyString.substring(i + "jcasType).casFeatCode_".length() )); if (!m.find()) { reportUnrecognizedV2Code("Found \"...jcasType).casFeatCode_\" but didn't find subsequent word\n" + n.toString()); break; } featName = m.group(1); String fromMethod = Character.toLowerCase(name.charAt(3)) + name.substring(4); if (!featName.equals(fromMethod)) { // don't report if the only difference is the first letter captialization if (!(Character.toLowerCase(featName.charAt(0)) + featName.substring(1)).equals(fromMethod)) { reportMismatchedFeatureName(String.format("%-25s %s", featName, name)); } } // add _FI_xxx = TypeSystemImpl.getAdjustedFeatureOffset("xxx"); // replaced Sept 2017 // NodeList args = new NodeList<>(); // args.add(new StringLiteralExpr(featName)); // VariableDeclarator vd = new VariableDeclarator( // intType, // "_FI_" + featName, // new MethodCallExpr(new NameExpr("TypeSystemImpl"), new SimpleName("getAdjustedFeatureOffset"), args)); // if (featNames.add(featName)) { // returns true if it was added, false if already in the set of featNames // fi_fields.add(new FieldDeclaration(public_static_final, vd)); // } // add _FC_xxx = TypeSystemImpl.createCallSite(ccc.class, "xxx"); // add _FH_xxx = _FC_xxx.dynamicInvoker(); // add _FeatName_xxx = "xxx" // https://issues.apache.org/jira/browse/UIMA-5575 if (featNames.add(featName)) { // returns true if it was added, false if already in the set of featNames // _FC_xxx = TypeSystemImpl.createCallSite(ccc.class, "xxx"); MethodCallExpr initCallSite = new MethodCallExpr(new NameExpr("TypeSystemImpl"), "createCallSite"); initCallSite.addArgument(new FieldAccessExpr(new NameExpr(className), "class")); initCallSite.addArgument(new StringLiteralExpr(featName)); VariableDeclarator vd_FC = new VariableDeclarator(callSiteType, "_FC_" + featName, initCallSite); fi_fields.add(new FieldDeclaration(private_static_final, vd_FC)); // _FH_xxx = _FC_xxx.dynamicInvoker(); MethodCallExpr initInvoker = new MethodCallExpr(new NameExpr(vd_FC.getName()), "dynamicInvoker"); VariableDeclarator vd_FH = new VariableDeclarator(methodHandleType, "_FH_" + featName, initInvoker); fi_fields.add(new FieldDeclaration(private_static_final, vd_FH)); // _FeatName_xxx = "xxx" // https://issues.apache.org/jira/browse/UIMA-5575 VariableDeclarator vd_fn = new VariableDeclarator(stringType, "_FeatName_" + featName, new StringLiteralExpr(featName)); fi_fields.add(new FieldDeclaration(public_static_final, vd_fn)); } /** * add missing cast stmt for * return stmts where the value being returned: * - doesn't have a cast already * - has the expression be a methodCallExpr with a name which looks like: * ll_getRefValue or * ll_getRefArrayValue */ if (isGetter && "Feature".equals(rangeNamePart)) { for (Statement stmt : n.getBody().get().getStatements()) { if (stmt instanceof ReturnStmt) { Expression e = getUnenclosedExpr(((ReturnStmt)stmt).getExpression().get()); if ((e instanceof MethodCallExpr)) { String methodName = ((MethodCallExpr)e).getNameAsString(); if (refGetter.matcher(methodName).matches()) { // ll_getRefValue or ll_getRefArrayValue addCastExpr(stmt, n.getType()); } } } } } get_set_method = n; // used as a flag during inner "visits" to signal // we're inside a likely feature setter/getter } // end of test for getter or setter method } while (false); // do once, provide break exit super.visit(n, ignore); get_set_method = null; // after visiting, reset the get_set_method to null } /** * Visitor for if stmts * - removes feature missing test */ @Override public void visit(IfStmt n, Object ignore) { do { // if (get_set_method == null) break; // sometimes, these occur outside of recogn. getters/setters Expression c = n.getCondition(), e; BinaryExpr be, be2; List stmts; if ((c instanceof BinaryExpr) && ((be = (BinaryExpr)c).getLeft() instanceof FieldAccessExpr) && ((FieldAccessExpr)be.getLeft()).getNameAsString().equals("featOkTst")) { // remove the feature missing if statement // verify the remaining form if (! (be.getRight() instanceof BinaryExpr) || ! ((be2 = (BinaryExpr)be.getRight()).getRight() instanceof NullLiteralExpr) || ! (be2.getLeft() instanceof FieldAccessExpr) || ! ((e = getExpressionFromStmt(n.getThenStmt())) instanceof MethodCallExpr) || ! (((MethodCallExpr)e).getNameAsString()).equals("throwFeatMissing")) { reportDeletedCheckModified("The featOkTst was modified:\n" + n.toString() + '\n'); } BlockStmt parent = (BlockStmt) n.getParentNode().get(); stmts = parent.getStatements(); stmts.set(stmts.indexOf(n), new EmptyStmt()); //dont remove // otherwise iterators fail // parent.getStmts().remove(n); return; } } while (false); super.visit(n, ignore); } /** * visitor for method calls */ @Override public void visit(MethodCallExpr n, Object ignore) { Optional p1, p2, p3 = null; Node updatedNode = null; NodeList args; do { if (get_set_method == null) break; /** remove checkArraybounds statement **/ if (n.getNameAsString().equals("checkArrayBounds") && ((p1 = n.getParentNode()).isPresent() && p1.get() instanceof ExpressionStmt) && ((p2 = p1.get().getParentNode()).isPresent() && p2.get() instanceof BlockStmt) && ((p3 = p2.get().getParentNode()).isPresent() && p3.get() == get_set_method)) { NodeList stmts = ((BlockStmt)p2.get()).getStatements(); stmts.set(stmts.indexOf(p1.get()), new EmptyStmt()); return; } // convert simpleCore expression ll_get/setRangeValue boolean useGetter = isGetter || isArraySetter; if (n.getNameAsString().startsWith("ll_" + (useGetter ? "get" : "set") + rangeNameV2Part + "Value")) { args = n.getArguments(); if (args.size() != (useGetter ? 2 : 3)) break; String suffix = useGetter ? "Nc" : rangeNamePart.equals("Feature") ? "NcWj" : "Nfc"; String methodName = "_" + (useGetter ? "get" : "set") + rangeNamePart + "Value" + suffix; args.remove(0); // remove the old addr arg // arg 0 converted when visiting args FieldAccessExpr n.setScope(null); n.setName(methodName); } // convert array sets/gets String z = "ll_" + (isGetter ? "get" : "set"); String nname = n.getNameAsString(); if (nname.startsWith(z) && nname.endsWith("ArrayValue")) { String s = nname.substring(z.length()); s = s.substring(0, s.length() - "Value".length()); // s = "ShortArray", etc. if (s.equals("RefArray")) s = "FSArray"; if (s.equals("IntArray")) s = "IntegerArray"; EnclosedExpr ee = new EnclosedExpr( new CastExpr(new ClassOrInterfaceType(s), n.getArguments().get(0))); n.setScope(ee); // the getter for the array fs n.setName(isGetter ? "get" : "set"); n.getArguments().remove(0); } /** remove ll_getFSForRef **/ /** remove ll_getFSRef **/ if (n.getNameAsString().equals("ll_getFSForRef") || n.getNameAsString().equals("ll_getFSRef")) { updatedNode = replaceInParent(n, n.getArguments().get(0)); } } while (false); if (updatedNode != null) { updatedNode.accept(this, null); } else { super.visit(n, null); } } /** * visitor for field access expressions * - convert ((...type_Type)jcasType).casFeatCode_XXXX to _FI_xxx * @param n - * @param ignore - */ @Override public void visit(FieldAccessExpr n, Object ignore) { Expression e; Optional oe; String nname = n.getNameAsString(); if (get_set_method != null) { if (nname.startsWith("casFeatCode_") && ((oe = n.getScope()).isPresent()) && ((e = getUnenclosedExpr(oe.get())) instanceof CastExpr) && ("jcasType".equals(getName(((CastExpr)e).getExpression())))) { String featureName = nname.substring("casFeatCode_".length()); // replaceInParent(n, new NameExpr("_FI_" + featureName)); // repl last in List (args) MethodCallExpr getint = new MethodCallExpr(null, "wrapGetIntCatchException"); getint.addArgument(new NameExpr("_FH_" + featureName)); replaceInParent(n, getint); return; } else if (nname.startsWith("casFeatCode_")) { reportMigrateFailed("Found field casFeatCode_ ... without a previous cast expr using jcasType"); } } super.visit(n, ignore); } private class removeEmptyStmts extends VoidVisitorAdapter { @Override public void visit(BlockStmt n, Object ignore) { n.getStatements().removeIf(statement -> statement instanceof EmptyStmt); super.visit(n, ignore); } // @Override // public void visit(MethodDeclaration n, Object ignore) { // if (n.getNameAsString().equals("getModifiablePrimitiveNodes")) { // System.out.println("debug"); // } // super.visit(n, ignore); // if (n.getNameAsString().equals("getModifiablePrimitiveNodes")) { // System.out.println("debug"); // } // } } /** * converted files: * java name, path (sorted by java name, v3 name only) * not-converted: * java name, path (sorted by java name) * duplicates: * java name, path (sorted by java name) * @return true if it's likely everything converted OK. */ private boolean report() { System.out.println("\n\nMigration Summary"); System.out.format("Output top directory: %s%n", outputDirectory); System.out.format("Date/time: %tc%n", new Date()); pprintRoots("Sources", sourcesRoots); pprintRoots("Classes", classesRoots); boolean isOk2 = true; try { // these reports, if non-empty items, imply something needs manual checking, so reset isOk2 isOk2 = reportPaths("Workaround Directories", "workaroundDir.txt", pathWorkaround) && isOk2; isOk2 = reportPaths("Reports of converted files where a deleted check was customized", "deletedCheckModified.txt", deletedCheckModified) && isOk2; isOk2 = reportPaths("Reports of converted files needing manual inspection", "manualInspection.txt", manualInspection) && isOk2; isOk2 = reportPaths("Reports of files which failed migration", "failed.txt", failedMigration) && isOk2; isOk2 = reportPaths("Reports of non-JCas files", "NonJCasFiles.txt", nonJCasFiles) && isOk2; isOk2 = reportPaths("Builtin JCas classes - skipped - need manual checking to see if they are modified", "skippedBuiltins.txt", skippedBuiltins) && isOk2; // these reports, if non-empty, do not imply OK issues reportPaths("Reports of updated Jars", "jarFileUpdates.txt", jarClassReplace); reportPaths("Reports of updated PEARs", "pearFileUpdates.txt", pearClassReplace); // computeDuplicates(); // reportPaths("Report of duplicates - not identical", "nonIdenticalDuplicates.txt", nonIdenticalDuplicates); // reportPaths("Report of duplicates - identical", "identicalDuplicates.txt", identicalDuplicates); // isOk2 = reportDuplicates() && isOk2; // false if non-identical duplicates return isOk2; } catch (IOException e) { throw new RuntimeException(e); } } private void pprintRoots(String kind, Container[] roots) { if (roots != null && roots.length > 0) { try { try (BufferedWriter bw = Files.newBufferedWriter(makePath(outDirLog + "ItemsProcessed"), StandardOpenOption.CREATE)) { logPrintNl(kind + " Roots:", bw); indent[0] += 2; try { for (Container container : roots) { pprintContainer(container, bw); } logPrintNl("", bw); } finally { indent[0] -= 2; } } } catch (IOException e) { throw new UIMARuntimeException(e); } } } private void pprintContainer(Container container, BufferedWriter bw) throws IOException { logPrintNl(container.toString(), bw); if (container.subContainers.size() > 1) { logPrintNl("", bw); indent[0] += 2; for (Container subc : container.subContainers) { pprintContainer(subc, bw); } } } // private void computeDuplicates() { // List toCheck = new ArrayList<>(c2ps); // toCheck.addAll(extendableBuiltins); // sortReport2(toCheck); // ClassnameAndPath prevP = new ClassnameAndPath(null, null); // List sameList = new ArrayList<>(); // boolean areAllEqual = true; // // for (ClassnameAndPath p : toCheck) { // if (!p.getFirst().equals(prevP.getFirst())) { // // addToIdenticals(sameList, areAllEqual); // sameList.clear(); // areAllEqual = true; // // prevP = p; // continue; // } // // // have 2nd or subsequent same class // if (sameList.size() == 0) { // sameList.add(prevP); // } // sameList.add(p); // if (areAllEqual) { // if (isFilesMiscompare(p.path, prevP.path)) { // areAllEqual = false; // } // } // } // // addToIdenticals(sameList, areAllEqual); // } // /** // * Compare two java source or class files // * @param p1 // * @param p2 // * @return // */ // private boolean isFilesMiscompare(Path p1, Path p2) { // String s1 = (p1); // String s2 = (p2); // return !s1.equals(s2); // } // private void addToIdenticals(List sameList, boolean areAllEqual) { // if (sameList.size() > 0) { // if (areAllEqual) { // identicalDuplicates.addAll(sameList); // } else { // nonIdenticalDuplicates.addAll(sameList); // } // } // } /** * * @param name * @return a path made from name, with directories created * @throws IOException */ private Path makePath(String name) throws IOException { Path p = Paths.get(name); Path parent = p.getParent(); // all the parts of the path up to the final segment if (parent == null) { return p; } try { Files.createDirectories(parent); } catch (FileAlreadyExistsException e) { // parent already exists but is not a directory! // caused by running on Windows system which ignores "case" // there's a file at /x/y/ named "z", but the path wants to be /x/y/Z/ // Workaround: change "z" to "z_c" c for capitalization issue current_container.haveDifferentCapitalizedNamesCollidingOnWindows = true; Path fn = parent.getFileName(); if (fn == null) { throw new IllegalArgumentException(); } String newDir = fn.toString() + "_c"; Path parent2 = parent.getParent(); Path p2 = parent2 == null ? Paths.get(newDir) : Paths.get(parent2.toString(), newDir); try { Files.createDirectories(p2); } catch (FileAlreadyExistsException e2) { // parent already exists but is not a directory! throw new RuntimeException(e2); } reportPathWorkaround(parent.toString(), p2.toString()); Path lastPartOfPath = p.getFileName(); if (null == lastPartOfPath) throw new RuntimeException(); return Paths.get(p2.toString(), lastPartOfPath.toString()); } return p; } private void logPrint(String msg, Writer bw) throws IOException { System.out.print(msg); bw.write(msg); } private void logPrintNl(String msg, Writer bw) throws IOException { logPrint(msg, bw); logPrint("\n", bw); } /** * prints "There were no xxx" if there are no items. * prints a title, followed by a ================== underneath it * * prints a sorted report of two fields. * * @param title title of report * @param fileName file name to save the report in (as well as print to sysout * @param items the set of items to report on- * @return true if items were empty * @throws IOException - */ private boolean reportPaths(String title, String fileName, List> items) throws IOException { if (items.size() == 0) { System.out.println("There were no " + title); return true; } System.out.println("\n" + title); for (int i = 0; i < title.length(); i++) System.out.print('='); System.out.println(""); try (BufferedWriter bw = Files.newBufferedWriter(makePath(outDirLog + fileName), StandardOpenOption.CREATE)) { List> sorted = new ArrayList<>(items); sortReport2(sorted); int max = 0; int nbrFirsts = 0; Object prevFirst = null; for (Report2 p : sorted) { max = Math.max(max, p.getFirstLength()); Comparable first = p.getFirst(); if (first != prevFirst) { prevFirst = first; nbrFirsts ++; } } /** * Two styles. * Style 1: where nbrFirst <= 25% nbr: first on separate line, seconds indented * Style 2: firsts and seconds on same line. */ int i = 1; boolean style1 = nbrFirsts <= sorted.size() / 4; prevFirst = null; for (Report2 p : sorted) { if (style1) { if (prevFirst != p.getFirst()) { prevFirst = p.getFirst(); logPrintNl(String.format("\n For: %s", p.getFirst()), bw); } logPrintNl(String.format(" %5d %s", i, p.getSecond()), bw); } else { logPrintNl(String.format("%5d %-" +max+ "s %s", i, p.getFirst(), p.getSecond()), bw); } i++; } System.out.println(""); } // end of try-with-resources return false; } private boolean isZipFs(Object o) { // Surprise! sometimes the o is not an instance of FileSystem but is the zipfs anyways return o.getClass().getName().contains("zipfs"); // java 8 and 9 } /** * Sort the items on first, then second * @param items */ private void sortReport2(List> items) { items.sort((o1, o2) -> { int r = protectedCompare(o1.getFirst(), o2.getFirst()); if (r == 0) { r = protectedCompare(o1.getSecond(), o2.getSecond()); } return r; }); } /** * protect against comparing zip fs with non-zip fs - these are not comparable to each other in IBM Java 8 * @return - */ private int protectedCompare(Comparable comparable, Comparable comparable2) { //debug try { if (isZipFs(comparable)) { if (isZipFs(comparable2)) { return comparable.compareTo((T) comparable2); // both zip } else { return 1; } } else { if (isZipFs(comparable2)) { return -1; } else { return comparable.compareTo((T) comparable2); // both not zip } } } catch (ClassCastException e) { //debug System.out.format("Internal error: c1: %b c2: %b%n c1: %s%n c2: %s%n", isZipFs(comparable), isZipFs(comparable2), comparable.getClass().getName(), comparable2.getClass().getName()); throw e; } } /** * Called only for top level roots. Sub containers recurse getCandidates_processFile2. * * Walk the directory tree rooted at root * - descend subdirectories * - descend Jar file * -- descend nested Jar files (!) * by extracting these to a temp dir, and keeping a back reference to where they were extracted from. * * output the paths representing the classes to migrate: * classes having a _Type partner * excluding things other than .java or .classes, and excluding anything with "$" in the name * - the path includes the "file system". * @param root * @throws IOException */ private void getAndProcessCandidatesInContainer(Container container) { // current_paths2RootIds = top_paths2RootIds; // don't do lower, that's called within Jars etc. if (container.isSingleJavaSource) { getCandidates_processFile2(container.root, container); } else { try (Stream stream = Files.walk(container.root, FileVisitOption.FOLLOW_LINKS)) { // needed to release file handles stream.forEachOrdered( // only puts into the RootIds possible Fqcn (ending in either .class or .java) p -> getCandidates_processFile2(p, container)); } catch (IOException e) { throw new RuntimeException(e); } } // walk from root container, remove items not JCas candidates // prunes empty rootIds and subContainer nodes removeNonJCas(container); if (container.candidates.size() == 0 && container.subContainers.size() == 0) { // above call might remove all candidates Container parent = container.parent; if (parent != null) { // System.out.println("No Candidates found, removing container: " + container.toString() ); // // debug // System.out.println("debug: " + container.rootOrig.toString()); parent.subContainers.remove(container); } return; } si(psb).append("Migrating JCas files "); psb.append( container.isJar ? "in Jar: " : container.isPear ? "in Pear: " : "from root: "); psb.append(container.rootOrig); indent[0] += 2; si(psb); flush(psb); try { for (Path path : container.candidates) { CommonConverted cc = getSource(path, container); // migrate checks to see if already done, outputs a "." or some other char for the candidate migrate(cc, container, path); //defer any compilation to container level if ((itemCount % 50) == 0) { psb.append(" ").append(itemCount); si(psb); flush(psb); } itemCount++; } psb.append(" ").append(itemCount - 1); flush(psb); if (isSource) { return; // done } if (!isSource && !container.haveDifferentCapitalizedNamesCollidingOnWindows && javaCompiler != null) { boolean somethingCompiled = compileV3SourcesCommon2(container); if (container.isPear || container.isJar) { if (somethingCompiled) { postProcessPearOrJar(container); } } return; } unableToCompile = true; return; // unable to do post processing or compiling } finally { indent[0] -= 2; } } // removes nonJCas candidates private void removeNonJCas(Container container) { Iterator it = container.candidates.iterator(); while (it.hasNext()) { String candidate = it.next().toString(); // remove non JCas classes // //debug // System.out.println("debug, testing to remove: " + candidate); // if (candidate.indexOf("Corrected") >= 0) { // if (!container._Types.contains(candidate)) { // System.out.println("debug dumping _Types map keys to see why ... Corrected.class not there"); // System.out.println("debug key is=" + candidate); // System.out.println("keys are:"); // int i = 0; // for (String k : container._Types) { // if (i == 4) { // i = 0; // System.out.println(""); // } // System.out.print(k + ", "); // } // } else { // System.out.println("debug container._Types did contain " + candidate); // } // } if (!container.isSingleJavaSource && !container._Types.contains(candidate)) { it.remove(); } } } /** * Called from Stream walker starting at a root or starting at an imbedded Jar or Pear. * * adds all the .java or .class files to the candidates, including _Type if not skipping the _Type check * * Handling embedded jar files * - single level Jar (at the top level of the default file system) * -- handle using an overlayed file system * - embedded Jars within Jars: * - not supported by Zip File System Provider (it only supports one level) * - handle by extracting to a temp dir, and then using the Zip File System Provider * * For PEARs, check for and disallow nested PEARs; install the PEAR, set the pear classpath for * recursive processing with the Pear. * * For Jar and PEAR files, use local variable + recursive call to update current_paths2RootIds map * to new one for the Jar / Pear, and then process recursiveloy * * @param path the path to a .java or .class or .jar or .pear that was walked to * @param pearClasspath - a string representing a path to the pear's classpath if there is one, or null * @param container the container for the * - rootIds (which have the JCas candidates) and * - subContainers for imbedded Pears and Jars */ private void getCandidates_processFile2(Path path, Container container) { String pathString = path.toString(); final boolean isPear = pathString.endsWith(".pear"); // path.endsWith does not mean this !! final boolean isJar = pathString.endsWith(".jar"); if (isPear || isJar) { Container subc = new Container(container, path); getAndProcessCandidatesInContainer(subc); return; } if (pathString.endsWith(isSource ? ".java" : ".class")) { // Skip candidates except .java or .class addToCandidates(path, container); } } /** * if _Type kind, add artifactId to set kept in current rootIdContainer * If currently scanning within a PEAR, * record 2-way map from unzipped path to internal path inside pear * Used when doing pear reassembly. * * If currently scanning within a Jar or a PEAR, * add unzipped path to list of all subparts for containing Jar or PEAR * These paths are used as unique ids to things needing to be replaced in the Jar or PEAR, * when doing re-assembly. For compiled classes migration, only, since source migration * doesn't do re-assembly. * * @param path * @param pearClassPath */ private void addToCandidates(Path path, Container container) { String ps = path.toString(); if (ps.endsWith(isSource ? "_Type.java" : "_Type.class")) { container._Types.add(isSource ? (ps.substring(0, ps.length() - 10) + ".java") : (ps.substring(0, ps.length() - 11) + ".class")); // if (container.isJar) { // System.out.println("debug add container._Types " + Paths.get(ps.substring(0, ps.length() - 11)).toString() + ".class".toString() + " for Jar " + container.rootOrig.getFileName().toString()); // } return; } if (ps.contains("$")) { return; // don't add these kinds of things, they're not JCas classes } //debug // if (container.isJar) { // System.out.println("debug add candidate " + path.toString() + " for Jar " + container.rootOrig.getFileName().toString()); // } container.candidates.add(path); } /** * For Jars inside other Jars, we copy the Jar to a temp spot in the default file system * Extracted Jar is marked delete-on-exit * * @param path embedded Jar to copy (only the last name is used, in constructing the temp dir) * @return a temporary file in the local temp directory that is a copy of the Jar * @throws IOException - */ private static Path getTempOutputPathForJarOrPear(Path path) throws IOException { Path localTempDir = getTempDir(); if (path == null ) { throw new IllegalArgumentException(); } Path fn = path.getFileName(); if (fn == null) { throw new IllegalArgumentException(); } Path tempPath = Files.createTempFile(localTempDir, fn.toString(), ""); tempPath.toFile().deleteOnExit(); return tempPath; } private static Path getTempDir() throws IOException { if (tempDir == null) { tempDir = Files.createTempDirectory("migrateJCas"); tempDir.toFile().deleteOnExit(); } return tempDir; } private static final CommandLineParser createCmdLineParser() { CommandLineParser parser = new CommandLineParser(); parser.addParameter(SOURCE_FILE_ROOTS, true); parser.addParameter(CLASS_FILE_ROOTS, true); parser.addParameter(OUTPUT_DIRECTORY, true); // parser.addParameter(SKIP_TYPE_CHECK, false); parser.addParameter(MIGRATE_CLASSPATH, true); // parser.addParameter(CLASSES, true); return parser; } private final boolean checkCmdLineSyntax(CommandLineParser clp) { if (clp.getRestArgs().length > 0) { System.err.println("Error parsing CVD command line: unknown argument(s):"); String[] args = clp.getRestArgs(); for (int i = 0; i < args.length; i++) { System.err.print(" "); System.err.print(args[i]); } System.err.println(); return false; } if (!clp.isInArgsList(SOURCE_FILE_ROOTS) && !clp.isInArgsList(CLASS_FILE_ROOTS)) { System.err.println("Neither sources file roots nor classes file roots parameters specified; please specify just one."); return false; } if (clp.isInArgsList(SOURCE_FILE_ROOTS) && clp.isInArgsList(CLASS_FILE_ROOTS)) { System.err.println("both sources file roots and classes file roots parameters specified; please specify just one."); return false; } if (clp.isInArgsList(OUTPUT_DIRECTORY)) { outputDirectory = Paths.get(clp.getParamArgument(OUTPUT_DIRECTORY)).toString(); if (!outputDirectory.endsWith("/")) { outputDirectory = outputDirectory + "/"; } } else { try { outputDirectory = Files.createTempDirectory("migrateJCasOutput").toString() + "/"; } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } } outDirConverted = outputDirectory + "converted/"; outDirSkipped = outputDirectory + "not-converted/"; outDirLog = outputDirectory + "logs/"; if (clp.isInArgsList(MIGRATE_CLASSPATH)) { migrateClasspath = clp.getParamArgument(MIGRATE_CLASSPATH); } else { if (clp.isInArgsList(CLASS_FILE_ROOTS)) { System.err.println("WARNING: classes file roots is specified, but the\n" + " migrateClasspath parameter is missing\n"); } } // if (clp.isInArgsList(CLASSES)) { // individualClasses = clp.getParamArgument(CLASSES); // } return true; } // called to decompile a string of bytes. // - first get the class name (fully qualified) // and skip decompiling if already decompiled this class // for this pearClasspath // - this handles multiple class definitions, insuring // only one decompile happens per pearClasspath (including null) /** * Caller does any caching to avoid this method. * * @param b bytecode to decompile * @param pearClasspath to prepend to the classpath * @return */ private String decompile(byte[] b, String pearClasspath) { badClassName = false; String classNameWithSlashes = Misc.classNameFromByteCode(b); packageAndClassNameSlash = classNameWithSlashes; ClassLoader cl = getClassLoader(pearClasspath); UimaDecompiler ud = new UimaDecompiler(cl, null); if (classNameWithSlashes == null || classNameWithSlashes.length() < 2) { System.err.println("Failed to extract class name from binary code, " + "name found was \"" + ((classNameWithSlashes == null) ? "null" : classNameWithSlashes) + "\"\n byte array was:"); System.err.println(Misc.dumpByteArray(b, 2000)); badClassName = true; } return ud.decompileToString(classNameWithSlashes, b); } /** * The classloader to use in decompiling, if it is provided, is one that delegates first * to the parent. This may need fixing for PEARs * @return classloader to use for migrate decompiling */ private ClassLoader getClassLoader(String pearClasspath) { if (null == pearClasspath) { if (null == cachedMigrateClassLoader) { cachedMigrateClassLoader = (null == migrateClasspath) ? this.getClass().getClassLoader() : new UIMAClassLoader(Misc.classpath2urls(migrateClasspath)); } return cachedMigrateClassLoader; } else { try { return new UIMAClassLoader((null == migrateClasspath) ? pearClasspath : (pearClasspath + File.pathSeparator + migrateClasspath)); } catch (MalformedURLException e) { throw new UIMARuntimeException(e); } } } private void addImport(String s) { cu.getImports().add(new ImportDeclaration(new Name(s), false, false)); } private void removeImport(String s) { Iterator it = cu.getImports().iterator(); while (it.hasNext()) { ImportDeclaration impDcl = it.next(); if (impDcl.getNameAsString().equals(s)) { it.remove(); break; } } } /****************** * AST Utilities ******************/ private Node replaceInParent(Node n, Expression v) { Optional maybeParent = n.getParentNode(); if (maybeParent.isPresent()) { Node parent = n.getParentNode().get(); if (parent instanceof EnclosedExpr) { ((EnclosedExpr)parent).setInner(v); } else if (parent instanceof MethodCallExpr) { // args in the arg list List args = ((MethodCallExpr)parent).getArguments(); args.set(args.indexOf(n), v); v.setParentNode(parent); } else if (parent instanceof ExpressionStmt) { ((ExpressionStmt)parent).setExpression(v); } else if (parent instanceof CastExpr) { ((CastExpr)parent).setExpression(v); } else if (parent instanceof ReturnStmt) { ((ReturnStmt)parent).setExpression(v); } else if (parent instanceof AssignExpr) { ((AssignExpr)parent).setValue(v); } else if (parent instanceof VariableDeclarator) { ((VariableDeclarator)parent).setInitializer(v); } else if (parent instanceof ObjectCreationExpr) { List args = ((ObjectCreationExpr)parent).getArguments(); int i = args.indexOf(n); if (i < 0) throw new RuntimeException(); args.set(i, v); } else { System.out.println(parent.getClass().getName()); throw new RuntimeException(); } return v; } System.out.println("internal error replacing in parent: no parent for node: " + n.getClass().getName()); System.out.println(" node: " + n.toString()); System.out.println(" expression replacing: " + v.toString()); throw new RuntimeException(); } /** * * @param p the parameter to modify * @param t the name of class or interface * @param name the name of the variable */ private void setParameter(List ps, int i, String t, String name) { Parameter p = ps.get(i); p.setType(new ClassOrInterfaceType(t)); p.setName(new SimpleName(name)); } private int findConstructor(NodeList> classMembers) { int i = 0; for (BodyDeclaration bd : classMembers) { if (bd instanceof ConstructorDeclaration) { return i; } i++; } return -1; } private boolean hasTypeFields(NodeList> members) { boolean hasType = false; boolean hasTypeId = false; for (BodyDeclaration bd : members) { if (bd instanceof FieldDeclaration) { FieldDeclaration f = (FieldDeclaration)bd; EnumSet m = f.getModifiers(); if (m.contains(Modifier.PUBLIC) && m.contains(Modifier.STATIC) && m.contains(Modifier.FINAL) // && // getTypeName(f.getType()).equals("int") ) { List vds = f.getVariables(); for (VariableDeclarator vd : vds) { if (vd.getType().equals(intType)) { String n = vd.getNameAsString(); if (n.equals("type")) hasType = true; if (n.equals("typeIndexID")) hasTypeId = true; if (hasTypeId && hasType) { return true; } } } } } } // end of for return false; } /** * Heuristic: * JCas classes have 0, 1, and 2 arg constructors with particular arg types * 0 - * 1 - JCas * 2 - int, TOP_Type (v2) or TypeImpl, CASImpl (v3) * * Additional 1 and 2 arg constructors are permitted. * * Sets fields hasV2Constructors, hasV3Constructors * @param members */ private void setHasJCasConstructors(NodeList> members) { boolean has0ArgConstructor = false; boolean has1ArgJCasConstructor = false; boolean has2ArgJCasConstructorV2 = false; boolean has2ArgJCasConstructorV3 = false; for (BodyDeclaration bd : members) { if (bd instanceof ConstructorDeclaration) { List ps = ((ConstructorDeclaration)bd).getParameters(); if (ps.size() == 0) has0ArgConstructor = true; if (ps.size() == 1 && getParmTypeName(ps, 0).equals("JCas")) { has1ArgJCasConstructor = true; } if (ps.size() == 2) { if (getParmTypeName(ps, 0).equals("int") && getParmTypeName(ps, 1).equals("TOP_Type")) { has2ArgJCasConstructorV2 = true; } else if (getParmTypeName(ps, 0).equals("TypeImpl") && getParmTypeName(ps, 1).equals("CASImpl")) { has2ArgJCasConstructorV3 = true; } } // end of 2 arg constructor } // end of is-constructor } // end of for loop hasV2Constructors = has0ArgConstructor && has1ArgJCasConstructor && has2ArgJCasConstructorV2; hasV3Constructors = has0ArgConstructor && has1ArgJCasConstructor && has2ArgJCasConstructorV3; } private String getParmTypeName(List p, int i) { return getTypeName(p.get(i).getType()); } private String getTypeName(Type t) { // if (t instanceof ReferenceType) { // t = ((ReferenceType)t).getType(); // } if (t instanceof PrimitiveType) { return ((PrimitiveType)t).toString(); } if (t instanceof ClassOrInterfaceType) { return ((ClassOrInterfaceType)t).getNameAsString(); } Misc.internalError(); return null; } /** * Get the name of a field * @param e - * @return the field name or null */ private String getName(Expression e) { e = getUnenclosedExpr(e); if (e instanceof NameExpr) { return ((NameExpr)e).getNameAsString(); } if (e instanceof FieldAccessExpr) { return ((FieldAccessExpr)e).getNameAsString(); } return null; } /** * Called on Annotation Decl, Class/intfc decl, empty type decl, enum decl * Does nothing unless at top level of compilation unit * * Otherwise, adds an entry to c2ps for the classname and package, plus full path * * @param n type being declared */ private void updateClassName(TypeDeclaration n) { Optional pnode = n.getParentNode(); Node node; if (pnode.isPresent() && (node = pnode.get()) instanceof CompilationUnit) { CompilationUnit cu2 = (CompilationUnit) node; className = cu2.getType(0).getNameAsString(); String packageAndClassName = (className.contains(".")) ? className : packageName + '.' + className; packageAndClassNameSlash = packageAndClassName.replace('.', '/'); // assert current_cc.fqcn_slash == null; // for decompiling, already set assert (current_cc.fqcn_slash != null) ? current_cc.fqcn_slash.equals(packageAndClassNameSlash) : true; current_cc.fqcn_slash = packageAndClassNameSlash; TypeImpl ti = TypeSystemImpl.staticTsi.getType(Misc.javaClassName2UimaTypeName(packageAndClassName)); if (null != ti) { // is a built-in type // ContainerAndPath p = new ContainerAndPath( // current_path, // current_container,packageAndClassNameSlash, // current_cc., // current_cc.pearClasspath); skippedBuiltins.add(new PathContainerAndReason(current_path, current_container, "built-in")); isBuiltinJCas = true; isConvert2v3 = false; return; } else { VariableDeclarator vd_typename = new VariableDeclarator( stringType, "_TypeName", new StringLiteralExpr(packageAndClassName)); fi_fields.add(new FieldDeclaration(public_static_final, vd_typename)); } return; } return; } private Expression getExpressionFromStmt(Statement stmt) { stmt = getStmtFromStmt(stmt); if (stmt instanceof ExpressionStmt) { return getUnenclosedExpr(((ExpressionStmt)stmt).getExpression()); } return null; } private Expression getUnenclosedExpr(Expression e) { while (e instanceof EnclosedExpr) { e = ((EnclosedExpr)e).getInner().get(); } return e; } /** * Unwrap (possibly nested) 1 statement blocks * @param stmt - * @return unwrapped (non- block) statement */ private Statement getStmtFromStmt(Statement stmt) { while (stmt instanceof BlockStmt) { NodeList stmts = ((BlockStmt) stmt).getStatements(); if (stmts.size() == 1) { stmt = stmts.get(0); continue; } return null; } return stmt; } private void addCastExpr(Statement stmt, Type castType) { ReturnStmt rstmt = (ReturnStmt) stmt; Optional o_expr = rstmt.getExpression(); Expression expr = o_expr.isPresent() ? o_expr.get() : null; CastExpr ce = new CastExpr(castType, expr); rstmt.setExpression(ce); // removes the parent link from expr if (expr != null) { expr.setParentNode(ce); // restore it } } /******************** * Recording results ********************/ private void recordBadConstructor(String msg) { reportMigrateFailed("Constructor is incorrect, " + msg); } // private void reportParseException() { // reportMigrateFailed("Unparsable Java"); // } private void migrationFailed(String reason) { failedMigration.add(new PathContainerAndReason(current_path, current_container, reason)); isConvert2v3 = false; } private void reportMigrateFailed(String m) { System.out.format("Skipping this file due to error: %s, path: %s%n", m, current_path); migrationFailed(m); } private void reportV2Class() { // v2JCasFiles.add(current_path); isV2JCas = true; } private void reportV3Class() { // v3JCasFiles.add(current_path); isConvert2v3 = true; } private void reportNotJCasClass(String reason) { nonJCasFiles.add(new PathContainerAndReason(current_path, current_container, reason)); isConvert2v3 = false; } private void reportNotJCasClassMissingTypeFields() { reportNotJCasClass("missing required type and/or typeIndexID static fields"); } private void reportDeletedCheckModified(String m) { deletedCheckModified.add(new PathContainerAndReason(current_path, current_container, m)); } private void reportMismatchedFeatureName(String m) { manualInspection.add(new PathContainerAndReason(current_path, current_container, "This getter/setter name doesn't match internal feature name: " + m)); } private void reportUnrecognizedV2Code(String m) { migrationFailed("V2 code not recognized:\n" + m); } private void reportPathWorkaround(String orig, String modified) { pathWorkaround.add(new String1AndString2(orig, modified)); } private void reportPearOrJarClassReplace(String pearOrJar, String classname, Container kind) { // pears or jars if (kind.isPear) { pearClassReplace.add(new String1AndString2(pearOrJar, classname)); } else { jarClassReplace.add(new String1AndString2(pearOrJar, classname)); } } /***********************************************/ /** * Output directory for source and migrated files * Consisting of converted/skipped, v2/v3, a+cc.id, slashified classname * @param cc - * @param isV2 - * @param wasConverted - * @return converted/skipped, v2/v3, a+cc.id, slashified classname */ private String getBaseOutputPath(CommonConverted cc, boolean isV2, boolean wasConverted) { StringBuilder sb = new StringBuilder(); sb.append(wasConverted ? outDirConverted : outDirSkipped); sb.append(isV2 ? "v2/" : "v3/"); sb.append("a").append(cc.getId()).append('/'); sb.append(cc.fqcn_slash).append(".java"); return sb.toString(); } private void writeV2Orig(CommonConverted cc, boolean wasConverted) throws IOException { String base = getBaseOutputPath(cc, true, wasConverted); // adds numeric suffix if dupls FileUtils.writeToFile(makePath(base), cc.v2Source); } private void writeV3(CommonConverted cc) throws IOException { String base = getBaseOutputPath(cc, false, true); cc.v3SourcePath = makePath(base); String data = fixImplementsBug(cc.v3Source); FileUtils.writeToFile(cc.v3SourcePath, data); } private void printUsage() { System.out.println( "Usage: java org.apache.uima.migratev3.jcas.MigrateJCas \n" + " [-sourcesRoots ]\n" + " [-classesRoots ]\n" + " [-outputDirectory a-writable-directory-path (optional)\n" + " if omitted, a temporary directory is used\n" + " if not omitted, the directory contents WILL BE ERASED at the start.\n" + " [-migrateClasspath a-class-path to use in decompiling, when -classesRoots is specified\n" + " also used when compiling the migrated classes.\n" + " NOTE: either -sourcesRoots or -classesRoots is required, but only one may be specified.\n" + " NOTE: classesRoots are scanned for JCas classes, which are then decompiled, and the results processed like sourcesRoots\n" ); } private static final Pattern implementsEmpty = Pattern.compile("implements \\{"); private String fixImplementsBug(String data) { return implementsEmpty.matcher(data).replaceAll("{"); } /********************************************************************* * Reporting classes *********************************************************************/ private static abstract class Report2 { public abstract Comparable getFirst(); // Eclipse on linux complained if not public, was OK on windows public abstract Comparable getSecond(); abstract int getFirstLength(); } private static class PathContainerAndReason extends Report2 { final ContainerAndPath cap; final String reason; PathContainerAndReason(ContainerAndPath cap, String reason) { this.cap = cap; this.reason = reason; } PathContainerAndReason(Path path, Container container, String reason) { this(new ContainerAndPath(path, container), reason); } @Override public Comparable getFirst() { return cap; } @Override public Comparable getSecond() { return reason; } @Override int getFirstLength() { return cap.toString().length(); } } private static class String1AndString2 extends Report2 { String s1; String s2; String1AndString2(String s1, String s2) { this.s1 = s1; this.s2 = s2; } @Override public Comparable getFirst() { return s1; } @Override public Comparable getSecond() { return s2; } @Override int getFirstLength() { return s1.toString().length(); } } private static void withIOX(Runnable_withException r) { try { r.run(); } catch (Exception e) { throw new UIMARuntimeException(e); } } private int findFirstCharDifferent(String s1, String s2) { int s1l = s1.length(); int s2l = s2.length(); for (int i = 0;;i++) { if (i == s1l || i == s2l) { return i; } if (s1.charAt(i) != s2.charAt(i)) { return i; } } } // private String drop_Type(String s) { // return s.substring(0, isSource ? "_Type.java".length() // : "_Type.class".length()) + // (isSource ? ".java" : ".class"); // } ///***************** //* Root-id //*****************/ //private static int nextRootId = 1; // ///*********************************************************************** //* Root-id - this is the path part up to the start of the package name. //* - it is relative to container //* - has the collection of artifacts that might be candidates, having this rootId //* - has the collection of _Type things having this rootId //* - "null" path is OK - means package name starts immediately //* There is no Root-id for path ending in Jar or PEAR - these created containers instead //***********************************************************************/ //private static class RootId { // final int id = nextRootId++; // /** // * The path relative to the the container (if any) (= Jar or Pear) // * - for Pears, the path is as if it was not installed, but within the PEAR file // */ // final Path path; // // /** The container holding this RootId */ // final Container container; // /** // * For this rootId, all of the fully qualified classnames that are migration eligible. // * - not all might be migrated, if upon further inspection they are not JCas class files. // */ // final Set fqcns = new HashSet<>(); // final Set fqcns_ignore_case = new HashSet<>(); // boolean haveDifferentCapitalizedNamesCollidingOnWindows = false; // // RootId(Path path, Container container) { // this.path = path; // this.container = container; // } // // /* (non-Javadoc) // * @see java.lang.Object#toString() // */ // @Override // public String toString() { // return "RootId [id=" // + id // + ", path=" // + path // + ", container=" // + container.id // + ", fqcns=" // + Misc.ppList(Misc.setAsList(fqcns)) // + ", fqcns_Type=" // + Misc.ppList(Misc.setAsList(fqcns_Type)) // + "]"; // } // // void add(Fqcn fqcn) { // boolean wasNotPresent = fqcns.add(fqcn); // boolean lc = fqcns_ignore_case.add(fqcn.fqcn_dots.toLowerCase()); // if (!lc && wasNotPresent) { // haveDifferentCapitalizedNamesCollidingOnWindows = true; // } // } // // boolean hasMatching_Type(Fqcn fqcn) { // // } //} ///** //* Called from Stream walker starting at a root or starting at an imbedded Jar or Pear. //* //* adds all the .java or .class files to the candidates, including _Type if not skipping the _Type check //* Handling embedded jar files //* - single level Jar (at the top level of the default file system) //* -- handle using an overlayed file system //* - embedded Jars within Jars: //* - not supported by Zip File System Provider (it only supports one level) //* - handle by extracting to a temp dir, and then using the Zip File System Provider //* @param path the path to a .java or .class or .jar or .pear //* @param pearClasspath - a string representing a path to the pear's classpath if there is one, or null //*/ //private void getCandidates_processFile(Path path, String pearClasspath) { //// if (path.toString().contains("commons-httpclient-3.1.jar")) //// System.out.println("Debug: " + path.toString()); //// System.out.println("debug processing " + path); // try { //// URI pathUri = path.toUri(); // String pathString = path.toString(); // final boolean isPear = pathString.endsWith(".pear"); // path.endsWith does not mean this !! // final boolean isJar = pathString.endsWith(".jar"); // // if (isJar || isPear) { // if (!path.getFileSystem().equals(FileSystems.getDefault())) { // // embedded Pear or Jar: extract to temp // Path out = getTempOutputPathForJar(path); // Files.copy(path, out, StandardCopyOption.REPLACE_EXISTING); //// embeddedJars.add(new PathAndPath(path, out)); // path = out; // path points to pear or jar // } // // Path start; // final String localPearClasspath; // if (isPear) { // if (pearClasspath != null) { // throw new UIMARuntimeException("Nested PEAR files not supported"); // } // //// pear_current = new PearOrJar(path); //// pears.add(pear_current); // // add pear classpath info // File pearInstallDir = Files.createTempDirectory(getTempDir(), "installedPear").toFile(); // PackageBrowser ip = PackageInstaller.installPackage(pearInstallDir, path.toFile(), false); // localPearClasspath = ip.buildComponentClassPath(); // String[] children = pearInstallDir.list(); // if (children == null || children.length != 1) { // Misc.internalError(); // } // pearResolveStart = Paths.get(pearInstallDir.getAbsolutePath(), children[0]); // // start = pearInstallDir.toPath(); // } else { // if (isJar) { // PearOrJar jarInfo = new PearOrJar(path); // pear_or_jar_current_stack.push(jarInfo); // jars.add(jarInfo); // } // // localPearClasspath = pearClasspath; // FileSystem jfs = FileSystems.newFileSystem(Paths.get(path.toUri()), null); // start = jfs.getPath("/"); // } // // try (Stream stream = Files.walk(start)) { // needed to release file handles // stream.forEachOrdered( // p -> getCandidates_processFile(p, localPearClasspath)); // } // if (isJar) { // pear_or_jar_current_stack.pop(); // } // if (isPear) { // pear_current = null; // } // } else { // // is not a .jar or .pear file. add .java or .class files to initial candidate set // // will be filtered additionally later //// System.out.println("debug path ends with java or class " + pathString.endsWith(isSource ? ".java" : ".class") + " " + pathString); // if (pathString.endsWith(isSource ? ".java" : ".class")) { // candidates.add(new Candidate(path, pearClasspath)); // if (!isSource && null != pear_current) { // // inside a pear, which has been unzipped into pearInstallDir; // path2InsidePearOrJarPath.put(path.toString(), pearResolveStart.relativize(path).toString()); // pear_current.pathsToCandidateFiles.add(path.toString()); // } // // if (!isSource && pear_or_jar_current_stack.size() > 0) { // // inside a jar, not contained in a pear // pear_or_jar_current_stack.getFirst().pathsToCandidateFiles.add(path.toString()); // } // } // } // } catch (IOException e) { // throw new RuntimeException(e); // } //} //private void postProcessPearsOrJars(String kind, List pearsOrJars, List classReplace) { // pears or jars //try { // Path outDir = Paths.get(outputDirectory, kind); // FileUtils.deleteRecursive(outDir.toFile()); // Files.createDirectories(outDir); //} catch (IOException e) { // throw new RuntimeException(e); //} // //// pearsOrJars may have entries with 0 candidate paths. This happens when we scan them //// but find nothing to convert. //// eliminate these. // //Iterator it = pearsOrJars.iterator(); //while (it.hasNext()) { // PearOrJar poj = it.next(); // if (poj.pathsToCandidateFiles.size() == 0) { // it.remove(); // } else { //// //debug //// if (poj.pathToPearOrJar.toString().contains("commons-httpclient-3.1")) { //// System.err.println("debug found converted things inside commons-httpclient");; //// for (String x : poj.pathsToCandidateFiles) { //// System.err.println(x); //// } //// System.err.println(""); //// } // } //} // //it = pearsOrJars.iterator(); //while (it.hasNext()) { // PearOrJar poj = it.next(); // if (poj.pathsToCandidateFiles.size() == 0) { // System.err.print("debug failed to remove unconverted Jar"); // } //} // //if (pearsOrJars.size() == 0) { // System.out.format("No .class files were replaced in %s.%n", kind); //} else { // System.out.format("replacing .class files in %,d %s%n", pearsOrJars.size(), kind); // for (PearOrJar p : pearsOrJars) { // pearOrJarPostProcessing(p, kind); // } // try { // reportPaths("Reports of updated " + kind, kind + "FileUpdates.txt", classReplace); // // } catch (IOException e) { // throw new RuntimeException(e); // } //} // //} ///** //* When running the compiler to compile v3 sources, we need a classpath that at a minimum //* includes uimaj-core. The strategy is to use the invoker of this tool's classpath as //* specified from the application class loader //* @return true if no errors //*/ //private boolean compileV3SourcesCommon(List items, String msg, String pearClasspath) { // // if (items.size() == 0) { // return true; // } // System.out.format("Compiling %,d classes %s -- This may take a while!%n", c2ps.size(), msg); // StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, Charset.forName("UTF-8")); // // List cus = items.stream() // .map(c -> outDirConverted + "v3/" + c.classname + ".java") // .collect(Collectors.toList()); // // Iterable compilationUnitStrings = cus; // // Iterable compilationUnits = // fileManager.getJavaFileObjectsFromStrings(compilationUnitStrings); // // // specify where the output classes go // String classesBaseDir = outDirConverted + "v3-classes"; // try { // Files.createDirectories(Paths.get(classesBaseDir)); // } catch (IOException e) { // throw new UIMARuntimeException(e); // } // // specify the classpath // String classpath = getCompileClassPath(pearClasspath); // Iterable options = Arrays.asList("-d", classesBaseDir, // "-classpath", classpath); // return javaCompiler.getTask(null, fileManager, null, options, null, compilationUnits).call(); //} ///** //* Called after class is migrated //* Given a path to a class (source or class file), //* return the URL to the class as found in the classpath. //* This returns the "first" one found in the classpath, in the case of duplicates. //* @param path //* @return the location of the class in the class path //*/ //private URL getPathForClass(Path path) { // return (null == packageAndClassNameSlash) // ? null // : migrateClassLoader.getResource(packageAndClassNameSlash + ".class"); //} //private void getBaseOutputPath() { //String s = packageAndClassNameSlash; //int i = 0; //while (!usedPackageAndClassNames.add(s)) { // i = i + 1; // s = packageAndClassNameSlash + "_dupid_" + i; //} //packageAndClassNameSlash_i = i; //} //private String prepareIndividual(String classname) { //candidate = new Candidate(Paths.get(classname)); // a pseudo path //packageName = null; //className = null; //packageAndClassNameSlash = null; //cu = null; //return decompile(classname); // always look up in classpath // // to decompile individual source - put in sourcesRoots //} //if (!isSource) { // skip this recording if source //if (null != pear_current) { // // inside a pear, which has been unzipped into a temporary pearInstallDir; // // we don't want that temporary dir to be part of the path. // path2InsidePearOrJarPath.put(path.toString(), pearResolveStart.relativize(path).toString()); // pear_current.pathsToCandidateFiles.add(path.toString()); //} // //if (!isSource && pear_or_jar_current_stack.size() > 0) { // // inside a jar, not contained in a pear // pear_or_jar_current_stack.getFirst().pathsToCandidateFiles.add(path.toString()); //} //} //} ///** //* For a given candidate, use its path: //* switch the ...java to ..._Type.java, or ...class to ..._Type.class //* look thru all the candidates //* @param cand //* @param start //* @return //*/ //private boolean has_Type(Candidate cand, int start) { // if (start >= candidates.size()) { // return false; // } // // String sc = cand.p.toString(); // String sc_minus_suffix = sc.substring(0, sc.length() - ( isSource ? ".java".length() : ".class".length())); // String sc_Type = sc_minus_suffix + ( isSource ? "_Type.java" : "_Type.class"); // // a string which sorts beyond the candidate + a suffix of "_" // String s_end = sc_minus_suffix + (char) (((int)'_') + 1); // for (Candidate c : candidates.subList(start, candidates.size())) { // String s = c.p.toString(); // if (s_end.compareTo(s) < 0) { // return false; // not found, we're already beyond where it would be found // } // if (s.equals(sc_Type)) { // return true; // } // } // return false; //} //private final static Comparator pathComparator = new Comparator() { //@Override //public int compare(Candidate o1, Candidate o2) { // return o1.p.toString().compareTo(o2.p.toString()); //} //}; //// there may be several same-name roots not quite right //// xxx_Type$1.class // //private void addIfPreviousIsSameName(List c, int i) { //if (i == 0) return; //String _Type = candidates.get(i).toString(); ////String prev = r.get(i-1).getPath(); //String prefix = _Type.substring(0, _Type.length() - ("_Type." + (isSource ? "java" : "class")).length()); //i--; //while (i >= 0) { // String s = candidates.get(i).toString(); // if ( ! s.startsWith(prefix)) { // break; // } // if (s.substring(prefix.length()).equals((isSource ? ".java" : ".class"))) { // c.add(candidates.get(i)); // break; // } // i--; //} //} // //for (int i = 0; i < pearOrJar.pathsToCandidateFiles.size(); i++) { // String candidatePath = pearOrJar.pathsToCandidateFiles.get(i); // String path_in_v3_classes = isPear // ? getPath_in_v3_classes(candidatePath) // : candidatePath; // // Path src = Paths.get(outputDirectory, "converted/v3-classes", path_in_v3_classes // + (isPear ? ".class" : "")); // Path tgt = pfs.getPath( // "/", // isPear // ? path2InsidePearOrJarPath.get(candidatePath) // needs to be bin/org/... etc // : candidatePath); // needs to be org/... etc // if (Files.exists(src)) { // Files.copy(src, tgt, StandardCopyOption.REPLACE_EXISTING); // reportPearOrJarClassReplace(pearOrJarCopy.toString(), path_in_v3_classes, kind); // } //} ///** for compiled mode, do recompiling and reassembly of Jars and Pears */ // //private boolean compileAndReassemble(CommonConverted cc, Container container, Path path) { // boolean noErrors = true; // if (javaCompiler != null) { // if (container.haveDifferentCapitalizedNamesCollidingOnWindows) { // System.out.println("Skipping compiling / reassembly because class " + container.toString() + " has multiple names differing only in capitalization, please resolve first."); // } else { // // // noErrors = compileV3PearSources(container, path); // noErrors = noErrors && compileV3NonPearSources(container, path); // // postProcessPearsOrJars("jars" , jars , jarClassReplace); // postProcessPearsOrJars("pears", pears, pearClassReplace); // //// //// try { //// Path pearOutDir = Paths.get(outputDirectory, "pears"); //// FileUtils.deleteRecursive(pearOutDir.toFile()); //// Files.createDirectories(pearOutDir); //// } catch (IOException e) { //// throw new RuntimeException(e); //// } //// //// System.out.format("replacing .class files in %,d PEARs%n", pears.size()); //// for (PearOrJar p : pears) { //// pearOrJarPostProcessing(p); //// } //// try { //// reportPaths("Reports of updated Pears", "pearFileUpdates.txt", pearClassReplace); //// } catch (IOException e) { //// throw new RuntimeException(e); //// } // } // } // // return noErrors; //} ///** //* @return true if no errors //*/ //private boolean compileV3PearSources() { // boolean noError = true; // Map> p2c = c2ps.stream() // .filter(c -> c.pearClasspath != null) // .collect(Collectors.groupingBy(c -> c.pearClasspath)); // // List>> ea = p2c.entrySet().stream() // .sorted(Comparator.comparing(Entry::getKey)) //(e1, e2) -> e1.getKey().compareTo(e2.getKey()) // .collect(Collectors.toList()); // // for (Entry> e : ea) { // noError = noError && compileV3SourcesCommon(e.getValue(), "for Pear " + e.getKey(), e.getKey() ); // } // return noError; //} // ///** //* @return true if no errors //*/ //private boolean compileV3NonPearSources() { // // List cnps = c2ps.stream() // .filter(c -> c.pearClasspath == null) // .collect(Collectors.toList()); // // return compileV3SourcesCommon(cnps, "(non PEAR)", null); //} ///** //* @param pathInPear a complete path to a class inside an (installed) pear //* @return the part starting after the top node of the install dir //*/ //private String getPath_in_v3_classes(String pathInPear) { // return path2classname.get(pathInPear); //} //private boolean reportDuplicates() throws IOException { //List> nonIdenticals = new ArrayList<>(); //List onlyIdenticals = new ArrayList<>(); // //classname2multiSources.forEach( // (classname, ccs) -> { // if (ccs.size() > 1) { // nonIdenticals.add(ccs); // } else { // CommonConverted cc = ccs.get(0); // if (cc.containersAndV2Paths.size() > 1) // onlyIdenticals.add(cc); // the same item in multiple containers and/or paths // } // } // ); // //if (nonIdenticals.size() == 0) { // if (onlyIdenticals.size() == 0) { // System.out.println("There were no duplicates found."); // } else { // // report identical duplicates // try (BufferedWriter bw = Files.newBufferedWriter(makePath(outDirLog + "identical_duplicates.txt"), StandardOpenOption.CREATE)) { // logPrintNl("Report of Identical duplicates:", bw); // for (CommonConverted cc : onlyIdenticals) { // int i = 0; // logPrintNl("Class: " + cc.fqcn_slash, bw); // for (ContainerAndPath cp : cc.containersAndV2Paths) { // logPrintNl(" " + (++i) + " " + cp, bw); // } // logPrintNl("", bw); // } // } // } // return true; //} // //// non-identicals, print out all of them //try (BufferedWriter bw = Files.newBufferedWriter(makePath(outDirLog + "nonIdentical_duplicates.txt"), StandardOpenOption.CREATE)) { // logPrintNl("Report of non-identical duplicates", bw); // for (List nonIdentical : nonIdenticals) { // String fqcn = nonIdentical.get(0).fqcn_slash; // logPrintNl(" classname: " + fqcn, bw); // int i = 1; // // for each cc, and within each cc, for each containerAndPath // for (CommonConverted cc : nonIdentical) { //// logPrintNl(" version " + i, bw); // assert fqcn.equals(cc.fqcn_slash); // int j = 1; // boolean isSame = cc.containersAndV2Paths.size() > 1; // boolean isFirstTime = true; // for (ContainerAndPath cp : cc.containersAndV2Paths) { // String first = isSame && isFirstTime // ? " same: " // : isSame // ? " " // : " "; // isFirstTime = false; // logPrintNl(first + i + "." + (j++) + " " + cp, bw); // } // indent[0] -= 6; //// logPrintNl("", bw); // i++; // } //// logPrintNl("", bw); // } //} //return false; //} //private static class PathAndReason extends Report2 { //Path path; //String reason; //PathAndReason(Path path, String reason) { // this.path = path; // this.reason = reason; //} //@Override //public Comparable getFirst() { return path; } //@Override //public Comparable getSecond() { return reason; } //@Override //int getFirstLength() { return path.toString().length(); } //} }