| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed 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.jetbrains.jps.incremental.instrumentation; |
| |
| import com.intellij.compiler.instrumentation.InstrumentationClassFinder; |
| import com.intellij.execution.process.BaseOSProcessHandler; |
| import com.intellij.execution.process.ProcessAdapter; |
| import com.intellij.execution.process.ProcessEvent; |
| import com.intellij.execution.process.ProcessOutputTypes; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.SystemProperties; |
| import gnu.trove.THashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.jps.ModuleChunk; |
| import org.jetbrains.jps.ProjectPaths; |
| import org.jetbrains.jps.incremental.*; |
| import org.jetbrains.jps.incremental.messages.BuildMessage; |
| import org.jetbrains.jps.incremental.messages.CompilerMessage; |
| import org.jetbrains.jps.model.java.JpsJavaExtensionService; |
| import org.jetbrains.jps.model.java.JpsJavaSdkType; |
| import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration; |
| import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions; |
| import org.jetbrains.jps.model.java.compiler.RmicCompilerOptions; |
| import org.jetbrains.jps.model.library.sdk.JpsSdk; |
| import org.jetbrains.jps.service.SharedThreadPool; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.rmi.Remote; |
| import java.util.*; |
| import java.util.concurrent.Future; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: 11/30/12 |
| */ |
| public class RmiStubsGenerator extends ClassProcessingBuilder { |
| private static final String REMOTE_INTERFACE_NAME = Remote.class.getName().replace('.', '/'); |
| private static final File[] EMPTY_FILE_ARRAY = new File[0]; |
| private static Key<Boolean> IS_ENABLED = Key.create("_rmic_compiler_enabled_"); |
| |
| public RmiStubsGenerator() { |
| super(BuilderCategory.CLASS_INSTRUMENTER); |
| } |
| |
| @Override |
| protected String getProgressMessage() { |
| return "Generating RMI stubs..."; |
| } |
| |
| @NotNull |
| @Override |
| public String getPresentableName() { |
| return "rmic"; |
| } |
| |
| @Override |
| public void buildStarted(CompileContext context) { |
| super.buildStarted(context); |
| final RmicCompilerOptions rmicOptions = getOptions(context); |
| IS_ENABLED.set(context, rmicOptions != null && rmicOptions.IS_EANABLED); |
| } |
| |
| @Override |
| protected boolean isEnabled(CompileContext context, ModuleChunk chunk) { |
| return IS_ENABLED.get(context, Boolean.FALSE); |
| } |
| |
| @Override |
| protected ExitCode performBuild(CompileContext context, ModuleChunk chunk, InstrumentationClassFinder finder, OutputConsumer outputConsumer) { |
| ExitCode exitCode = ExitCode.NOTHING_DONE; |
| if (!outputConsumer.getCompiledClasses().isEmpty()) { |
| final Map<ModuleBuildTarget, Collection<ClassItem>> remoteClasses = new THashMap<ModuleBuildTarget, Collection<ClassItem>>(); |
| for (ModuleBuildTarget target : chunk.getTargets()) { |
| for (CompiledClass compiledClass : outputConsumer.getTargetCompiledClasses(target)) { |
| try { |
| if (isRemote(compiledClass, finder)) { |
| Collection<ClassItem> list = remoteClasses.get(target); |
| if (list == null) { |
| list = new ArrayList<ClassItem>(); |
| remoteClasses.put(target, list); |
| } |
| list.add(new ClassItem(compiledClass)); |
| } |
| } |
| catch (IOException e) { |
| context.processMessage(new CompilerMessage(getPresentableName(), e)); |
| } |
| } |
| } |
| if (!remoteClasses.isEmpty()) { |
| exitCode = generateRmiStubs(context, remoteClasses, chunk, outputConsumer); |
| } |
| } |
| return exitCode; |
| } |
| |
| private ExitCode generateRmiStubs(final CompileContext context, |
| Map<ModuleBuildTarget, Collection<ClassItem>> remoteClasses, |
| ModuleChunk chunk, |
| OutputConsumer outputConsumer) { |
| ExitCode exitCode = ExitCode.NOTHING_DONE; |
| |
| final Collection<File> classpath = ProjectPaths.getCompilationClasspath(chunk, false); |
| final StringBuilder buf = new StringBuilder(); |
| for (File file : classpath) { |
| if (buf.length() > 0) { |
| buf.append(File.pathSeparator); |
| } |
| buf.append(file.getPath()); |
| } |
| final String classpathString = buf.toString(); |
| final String rmicPath = getPathToRmic(chunk); |
| final RmicCompilerOptions options = getOptions(context); |
| final List<ModuleBuildTarget> targetsProcessed = new ArrayList<ModuleBuildTarget>(remoteClasses.size()); |
| |
| for (Map.Entry<ModuleBuildTarget, Collection<ClassItem>> entry : remoteClasses.entrySet()) { |
| try { |
| final ModuleBuildTarget target = entry.getKey(); |
| final Collection<String> cmdLine = createStartupCommand( |
| target, rmicPath, classpathString, options, entry.getValue() |
| ); |
| final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(cmdLine)); |
| final BaseOSProcessHandler handler = new BaseOSProcessHandler(process, null, null) { |
| @Override |
| protected Future<?> executeOnPooledThread(Runnable task) { |
| return SharedThreadPool.getInstance().executeOnPooledThread(task); |
| } |
| }; |
| |
| final RmicOutputParser stdOutParser = new RmicOutputParser(context, getPresentableName()); |
| final RmicOutputParser stdErrParser = new RmicOutputParser(context, getPresentableName()); |
| handler.addProcessListener(new ProcessAdapter() { |
| @Override |
| public void onTextAvailable(ProcessEvent event, Key outputType) { |
| if (outputType == ProcessOutputTypes.STDOUT) { |
| stdOutParser.append(event.getText()); |
| } |
| else if (outputType == ProcessOutputTypes.STDERR) { |
| stdErrParser.append(event.getText()); |
| } |
| } |
| |
| @Override |
| public void processTerminated(ProcessEvent event) { |
| super.processTerminated(event); |
| } |
| }); |
| handler.startNotify(); |
| handler.waitFor(); |
| targetsProcessed.add(target); |
| if (stdErrParser.isErrorsReported() || stdOutParser.isErrorsReported()) { |
| break; |
| } |
| else { |
| final int exitValue = handler.getProcess().exitValue(); |
| if (exitValue != 0) { |
| context.processMessage(new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, "RMI stub generation failed")); |
| break; |
| } |
| } |
| } |
| catch (IOException e) { |
| context.processMessage(new CompilerMessage(getPresentableName(), e)); |
| break; |
| } |
| } |
| |
| // registering generated files |
| final Map<File, File[]> fsCache = new THashMap<File, File[]>(FileUtil.FILE_HASHING_STRATEGY); |
| for (ModuleBuildTarget target : targetsProcessed) { |
| final Collection<ClassItem> items = remoteClasses.get(target); |
| for (ClassItem item : items) { |
| File[] children = fsCache.get(item.parentDir); |
| if (children == null) { |
| children = item.parentDir.listFiles(); |
| if (children == null) { |
| children = EMPTY_FILE_ARRAY; |
| } |
| fsCache.put(item.parentDir, children); |
| } |
| final Collection<File> files = item.selectGeneratedFiles(children); |
| if (!files.isEmpty()) { |
| final Collection<String> sources = Collections.singleton(item.compiledClass.getSourceFile().getPath()); |
| for (File generated : files) { |
| try { |
| outputConsumer.registerOutputFile(target, generated, sources); |
| } |
| catch (IOException e) { |
| context.processMessage(new CompilerMessage(getPresentableName(), e)); |
| } |
| } |
| } |
| } |
| } |
| |
| return exitCode; |
| } |
| |
| private static Collection<String> createStartupCommand(final ModuleBuildTarget target, |
| final String compilerPath, |
| final String classpath, |
| final RmicCompilerOptions config, |
| final Collection<ClassItem> items) { |
| final List<String> commandLine = new ArrayList<String>(); |
| commandLine.add(compilerPath); |
| |
| if (config.DEBUGGING_INFO) { |
| commandLine.add("-g"); |
| } |
| if(config.GENERATE_IIOP_STUBS) { |
| commandLine.add("-iiop"); |
| } |
| final StringTokenizer tokenizer = new StringTokenizer(config.ADDITIONAL_OPTIONS_STRING, " \t\r\n"); |
| while(tokenizer.hasMoreTokens()) { |
| final String token = tokenizer.nextToken(); |
| commandLine.add(token); |
| } |
| |
| commandLine.add("-classpath"); |
| commandLine.add(classpath); |
| |
| commandLine.add("-d"); |
| final File outputDir = target.getOutputDir(); |
| assert outputDir != null; |
| commandLine.add(outputDir.getPath()); |
| |
| for (ClassItem item : items) { |
| commandLine.add(item.compiledClass.getClassName()); |
| } |
| return commandLine; |
| } |
| |
| private static String getPathToRmic(ModuleChunk chunk) { |
| final JpsSdk<?> sdk = chunk.representativeTarget().getModule().getSdk(JpsJavaSdkType.INSTANCE); |
| if (sdk != null) { |
| final String executable = JpsJavaSdkType.getJavaExecutable(sdk); |
| if (executable != null) { |
| final int idx = FileUtil.toSystemIndependentName(executable).lastIndexOf("/"); |
| if (idx >= 0) { |
| return executable.substring(0, idx) + "/rmic"; |
| } |
| } |
| } |
| return SystemProperties.getJavaHome() + "/bin/rmic"; |
| } |
| |
| private static boolean isRemote(CompiledClass compiled, InstrumentationClassFinder finder) throws IOException{ |
| try { |
| final InstrumentationClassFinder.PseudoClass pseudoClass = finder.loadClass(compiled.getClassName()); |
| if (pseudoClass != null && !pseudoClass.isInterface()) { |
| for (InstrumentationClassFinder.PseudoClass anInterface : pseudoClass.getInterfaces()) { |
| if (isRemoteInterface(anInterface, REMOTE_INTERFACE_NAME)) { |
| return true; |
| } |
| } |
| } |
| } |
| catch (ClassNotFoundException ignored) { |
| } |
| return false; |
| } |
| |
| private static boolean isRemoteInterface(InstrumentationClassFinder.PseudoClass iface, final String remoteInterfaceName) |
| throws IOException, ClassNotFoundException { |
| if (remoteInterfaceName.equals(iface.getName())) { |
| return true; |
| } |
| for (InstrumentationClassFinder.PseudoClass superIface : iface.getInterfaces()) { |
| if (isRemoteInterface(superIface, remoteInterfaceName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Nullable |
| private static RmicCompilerOptions getOptions(CompileContext context) { |
| final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getCompilerConfiguration(context.getProjectDescriptor().getProject()); |
| if (config != null) { |
| final JpsJavaCompilerOptions options = config.getCompilerOptions("Rmic"); |
| if (options instanceof RmicCompilerOptions) { |
| return (RmicCompilerOptions)options; |
| } |
| } |
| return null; |
| } |
| |
| private static final class ClassItem { |
| static final String[] GEN_SUFFIXES = {"_Stub.class", "_Skel.class", "_Tie.class"}; |
| final CompiledClass compiledClass; |
| final File parentDir; |
| final String baseName; |
| |
| ClassItem(CompiledClass compiledClass) { |
| this.compiledClass = compiledClass; |
| final File outputFile = compiledClass.getOutputFile(); |
| parentDir = outputFile.getParentFile(); |
| baseName = StringUtil.trimEnd(outputFile.getName(), ".class"); |
| } |
| |
| @NotNull |
| public Collection<File> selectGeneratedFiles(File[] candidates) { |
| if (candidates == null || candidates.length == 0) { |
| return Collections.emptyList(); |
| } |
| final Collection<File> result = new SmartList<File>(); |
| final String[] suffixes = new String[GEN_SUFFIXES.length]; |
| for (int i = 0; i < GEN_SUFFIXES.length; i++) { |
| suffixes[i] = baseName + GEN_SUFFIXES[i]; |
| } |
| for (File candidate : candidates) { |
| final String name = candidate.getName(); |
| for (String suffix : suffixes) { |
| if (name.endsWith(suffix)) { |
| result.add(candidate); |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| } |
| |
| private static class RmicOutputParser extends LineOutputWriter { |
| private final CompileContext myContext; |
| private final String myCompilerName; |
| private boolean myErrorsReported = false; |
| |
| private RmicOutputParser(CompileContext context, String name) { |
| myContext = context; |
| myCompilerName = name; |
| } |
| |
| private boolean isErrorsReported() { |
| return myErrorsReported; |
| } |
| |
| @Override |
| protected void lineAvailable(String line) { |
| if (!StringUtil.isEmpty(line)) { |
| BuildMessage.Kind kind = BuildMessage.Kind.INFO; |
| if (line.contains("error")) { |
| kind = BuildMessage.Kind.ERROR; |
| myErrorsReported = true; |
| } |
| else if (line.contains("warning")) { |
| kind = BuildMessage.Kind.WARNING; |
| } |
| myContext.processMessage(new CompilerMessage(myCompilerName, kind, line)); |
| } |
| } |
| } |
| } |