/*
* 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
*
*
* 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 extends JavaFileObject> 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 extends JavaFileObject> 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 extends Report2> 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 extends Report2> 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 extends JavaFileObject> 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(); }
//}
}